diff --git a/gaphor/C4Model/modelinglanguage.py b/gaphor/C4Model/modelinglanguage.py index a8d024b5b..fb0d72e49 100644 --- a/gaphor/C4Model/modelinglanguage.py +++ b/gaphor/C4Model/modelinglanguage.py @@ -1,6 +1,6 @@ """C4 Model Language entrypoint.""" -from typing import Iterable +from collections.abc import Iterable from gaphor.abc import ModelingLanguage from gaphor.C4Model import c4model, diagramitems diff --git a/gaphor/C4Model/propertypages.py b/gaphor/C4Model/propertypages.py index 2e41fe756..b0db36201 100644 --- a/gaphor/C4Model/propertypages.py +++ b/gaphor/C4Model/propertypages.py @@ -1,5 +1,3 @@ -from typing import Union - from gaphor.C4Model import c4model from gaphor.core import Transaction from gaphor.diagram.propertypages import ( @@ -22,9 +20,7 @@ PropertyPages.register(c4model.C4Dependency, NamePropertyPage) class DescriptionPropertyPage(PropertyPageBase): order = 14 - def __init__( - self, subject: Union[c4model.C4Container, c4model.C4Person], event_manager - ): + def __init__(self, subject: c4model.C4Container | c4model.C4Person, event_manager): super().__init__() assert subject self.subject = subject diff --git a/gaphor/RAAML/modelinglanguage.py b/gaphor/RAAML/modelinglanguage.py index 0d2c1347f..0dfba7286 100644 --- a/gaphor/RAAML/modelinglanguage.py +++ b/gaphor/RAAML/modelinglanguage.py @@ -1,7 +1,7 @@ """The RAAML Modeling Language module is the entrypoint for RAAML related assets.""" -from typing import Iterable +from collections.abc import Iterable from gaphor.abc import ModelingLanguage from gaphor.core import gettext diff --git a/gaphor/SysML/blocks/connectors.py b/gaphor/SysML/blocks/connectors.py index 7779a1c1a..61f4f29c7 100644 --- a/gaphor/SysML/blocks/connectors.py +++ b/gaphor/SysML/blocks/connectors.py @@ -1,5 +1,3 @@ -from typing import Union - from gaphas.connector import Handle, Port from gaphor import UML @@ -18,7 +16,7 @@ from gaphor.UML.deployments import ConnectorItem class BlockProperyProxyPortConnector: def __init__( self, - block_or_property: Union[BlockItem, PropertyItem, InterfaceBlockItem], + block_or_property: BlockItem | PropertyItem | InterfaceBlockItem, proxy_port: ProxyPortItem, ) -> None: self.element = block_or_property @@ -71,14 +69,14 @@ class PropertyConnectorConnector(RelationshipConnect): """Connect a Connector to a Port or Property.""" line: ConnectorItem - element: Union[PropertyItem, ProxyPortItem] + element: PropertyItem | ProxyPortItem def allow(self, handle, port): element = self.element # Element should be connected -> have a subject return super().allow(handle, port) and isinstance( - element.subject, (UML.Port, UML.Property) + element.subject, UML.Port | UML.Property ) def connect_subject(self, handle): diff --git a/gaphor/SysML/modelinglanguage.py b/gaphor/SysML/modelinglanguage.py index 3ca2aa9e0..8e2babe23 100644 --- a/gaphor/SysML/modelinglanguage.py +++ b/gaphor/SysML/modelinglanguage.py @@ -1,7 +1,7 @@ """The SysML Modeling Language module is the entrypoint for SysML related assets.""" -from typing import Iterable +from collections.abc import Iterable from gaphor.abc import ModelingLanguage from gaphor.core import gettext diff --git a/gaphor/SysML/tests/test_diagramitems.py b/gaphor/SysML/tests/test_diagramitems.py index 0768d24a1..844b6259a 100644 --- a/gaphor/SysML/tests/test_diagramitems.py +++ b/gaphor/SysML/tests/test_diagramitems.py @@ -49,7 +49,7 @@ def test_all_named_elements_have_named_items(item_class, element_class): assert issubclass(item_class, Named) if issubclass(item_class, Named): - assert issubclass(element_class, (NamedElement, Relationship)) + assert issubclass(element_class, NamedElement | Relationship) CLASSIFIED_EXCLUSIONS = [] diff --git a/gaphor/UML/actions/flowconnect.py b/gaphor/UML/actions/flowconnect.py index 10bf7305f..d08ba91c0 100644 --- a/gaphor/UML/actions/flowconnect.py +++ b/gaphor/UML/actions/flowconnect.py @@ -1,7 +1,5 @@ """Flow item adapter connections.""" -from typing import Type, Union - from gaphor import UML from gaphor.core.modeling import swap_element_type from gaphor.diagram.connectors import Connector, RelationshipConnect @@ -25,7 +23,7 @@ from gaphor.UML.actions.pin import InputPinItem, OutputPinItem class FlowConnect(RelationshipConnect): """Connect FlowItem and Action, initial/final nodes.""" - line: Union[ControlFlowItem, ObjectFlowItem] + line: ControlFlowItem | ObjectFlowItem def allow(self, handle, port): line = self.line @@ -33,9 +31,9 @@ class FlowConnect(RelationshipConnect): if ( handle is line.head - and isinstance(subject, (UML.FinalNode, UML.InputPin)) + and isinstance(subject, UML.FinalNode | UML.InputPin) or handle is line.tail - and isinstance(subject, (UML.InitialNode, UML.OutputPin)) + and isinstance(subject, UML.InitialNode | UML.OutputPin) ): return False @@ -65,7 +63,7 @@ class FlowConnect(RelationshipConnect): element_type = get_model_element(type(line)) metadata = get_diagram_item_metadata(type(line)) - relation: Union[UML.ControlFlow, UML.ObjectFlow] = self.relationship_or_new( + relation: UML.ControlFlow | UML.ObjectFlow = self.relationship_or_new( element_type, metadata["head"], metadata["tail"] ) @@ -81,8 +79,8 @@ class FlowConnect(RelationshipConnect): otc = self.get_connected(opposite) if ( opposite - and (isinstance(line, (ControlFlowItem, ObjectFlowItem))) - and isinstance(otc, (ForkNodeItem, DecisionNodeItem)) + and (isinstance(line, ControlFlowItem | ObjectFlowItem)) + and isinstance(otc, ForkNodeItem | DecisionNodeItem) ): adapter = Connector(otc, line) adapter.combine_nodes() @@ -94,8 +92,8 @@ class FlowConnect(RelationshipConnect): otc = self.get_connected(opposite) if ( opposite - and (isinstance(line, (ControlFlowItem, ObjectFlowItem))) - and isinstance(otc, (ForkNodeItem, DecisionNodeItem)) + and (isinstance(line, ControlFlowItem | ObjectFlowItem)) + and isinstance(otc, ForkNodeItem | DecisionNodeItem) ): adapter = Connector(otc, line) adapter.decombine_nodes() @@ -120,9 +118,9 @@ class FlowForkDecisionNodeFlowConnect(FlowConnect): """Abstract class with common behaviour for Fork/Join node and Decision/Merge node.""" - element: Union[ForkNodeItem, DecisionNodeItem] - fork_node_cls: Type[UML.ControlNode] - join_node_cls: Type[UML.ControlNode] + element: ForkNodeItem | DecisionNodeItem + fork_node_cls: type[UML.ControlNode] + join_node_cls: type[UML.ControlNode] def allow(self, handle, port): # No cyclic connect is possible on a Flow/Decision node: diff --git a/gaphor/UML/actions/pinconnect.py b/gaphor/UML/actions/pinconnect.py index 779086bdc..28a3bc803 100644 --- a/gaphor/UML/actions/pinconnect.py +++ b/gaphor/UML/actions/pinconnect.py @@ -31,7 +31,7 @@ class ActionPinConnector: UML.InputPin if isinstance(pin, InputPinItem) else UML.OutputPin ) - assert isinstance(pin.subject, (UML.InputPin, UML.OutputPin)) + assert isinstance(pin.subject, UML.InputPin | UML.OutputPin) pin.subject.opaqueAction = self.action.subject # This raises the item in the item hierarchy diff --git a/gaphor/UML/classes/association.py b/gaphor/UML/classes/association.py index c2b67c3da..fd0a69fb0 100644 --- a/gaphor/UML/classes/association.py +++ b/gaphor/UML/classes/association.py @@ -9,7 +9,6 @@ Plan: from dataclasses import replace from math import pi -from typing import Optional from gaphas.connector import Handle from gaphas.geometry import Rectangle, distance_rectangle_point @@ -328,7 +327,7 @@ class AssociationEnd: be recreated by the owning Association. """ - def __init__(self, owner: AssociationItem, end: Optional[str] = None): + def __init__(self, owner: AssociationItem, end: str | None = None): self._canvas = None self._owner = owner self._end = end @@ -358,7 +357,7 @@ class AssociationEnd: return self._owner.head if self is self._owner.head_end else self._owner.tail @property - def subject(self) -> Optional[UML.Property]: + def subject(self) -> UML.Property | None: return getattr(self.owner, f"{self._end}_subject") # type:ignore[no-any-return] @property diff --git a/gaphor/UML/classes/classespropertypages.py b/gaphor/UML/classes/classespropertypages.py index 891f9eb40..fe2815839 100644 --- a/gaphor/UML/classes/classespropertypages.py +++ b/gaphor/UML/classes/classespropertypages.py @@ -55,7 +55,7 @@ class NamedElementPropertyPage(NamePropertyPage): not self.subject or UML.recipes.is_metaclass(self.subject) or isinstance( - self.subject, (UML.ActivityPartition, UML.ActivityParameterNode) + self.subject, UML.ActivityPartition | UML.ActivityParameterNode ) ): return diff --git a/gaphor/UML/classes/containmentconnect.py b/gaphor/UML/classes/containmentconnect.py index 6dc250bda..fa26e3f92 100644 --- a/gaphor/UML/classes/containmentconnect.py +++ b/gaphor/UML/classes/containmentconnect.py @@ -31,7 +31,7 @@ class ContainmentConnect(BaseConnector): return super().allow(handle, port) and ( isinstance(container, UML.Package) - and isinstance(contained, (UML.Type, UML.Package)) + and isinstance(contained, UML.Type | UML.Package) or isinstance(container, UML.Package) and isinstance(contained, (Diagram)) or isinstance(container, UML.Class) @@ -59,11 +59,11 @@ class ContainmentConnect(BaseConnector): if hct and oct: if oct.subject and hct.subject in oct.subject.ownedElement: - assert isinstance(hct.subject, (UML.Type, UML.Package)) + assert isinstance(hct.subject, UML.Type | UML.Package) ungroup(oct.subject, hct.subject) hct.subject.package = owner_package(hct.diagram.owner) if hct.subject and oct.subject in hct.subject.ownedElement: - assert isinstance(oct.subject, (UML.Type, UML.Package)) + assert isinstance(oct.subject, UML.Type | UML.Package) ungroup(hct.subject, oct.subject) oct.subject.package = owner_package(oct.diagram.owner) diff --git a/gaphor/UML/compartments.py b/gaphor/UML/compartments.py index 24bb1b56c..a87e3901f 100644 --- a/gaphor/UML/compartments.py +++ b/gaphor/UML/compartments.py @@ -1,4 +1,4 @@ -from typing import Callable +from collections.abc import Callable from gaphas.geometry import Rectangle diff --git a/gaphor/UML/copypaste.py b/gaphor/UML/copypaste.py index ef6cd9529..1e6994359 100644 --- a/gaphor/UML/copypaste.py +++ b/gaphor/UML/copypaste.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Iterator, NamedTuple +from collections.abc import Iterator +from typing import NamedTuple from gaphor.core.modeling.element import Id from gaphor.diagram.copypaste import ( diff --git a/gaphor/UML/deployments/connectorconnect.py b/gaphor/UML/deployments/connectorconnect.py index a3b9c23d2..d812ea36b 100644 --- a/gaphor/UML/deployments/connectorconnect.py +++ b/gaphor/UML/deployments/connectorconnect.py @@ -4,8 +4,6 @@ Implemented using interface item in assembly connector mode, see `gaphor.diagram.connector` module for details. """ -from typing import Union - from gaphor import UML from gaphor.diagram.connectors import BaseConnector, Connector from gaphor.UML.classes.component import ComponentItem @@ -22,7 +20,7 @@ class LegacyConnectorConnectBase(BaseConnector): palette. """ - element: Union[ComponentItem, InterfaceItem] + element: ComponentItem | InterfaceItem line: ConnectorItem def get_connecting(self, iface, both=False): diff --git a/gaphor/UML/interactions/interactionsconnect.py b/gaphor/UML/interactions/interactionsconnect.py index 210023109..f6d16214a 100644 --- a/gaphor/UML/interactions/interactionsconnect.py +++ b/gaphor/UML/interactions/interactionsconnect.py @@ -1,7 +1,5 @@ """Message item connection adapters.""" -from typing import Optional - from gaphor import UML from gaphor.core.modeling import Element, Presentation from gaphor.diagram.connectors import BaseConnector, Connector @@ -12,7 +10,7 @@ from gaphor.UML.interactions.lifeline import LifelineItem from gaphor.UML.interactions.message import MessageItem -def get_connected(item, handle) -> Optional[Presentation[Element]]: +def get_connected(item, handle) -> Presentation[Element] | None: """Get item connected to a handle.""" if cinfo := item.diagram.connections.get_connection(handle): return cinfo.connected # type: ignore[no-any-return] # noqa: F723 @@ -145,7 +143,7 @@ class MessageLifelineConnect(BaseConnector): def disconnect(self, handle): line = self.line - send: Optional[Presentation[Element]] = get_connected(line, line.head) + send: Presentation[Element] | None = get_connected(line, line.head) received = self.get_connected(line.tail) # if a message is a deleted message, then the disconnection causes @@ -248,7 +246,7 @@ class ExecutionSpecificationExecutionSpecificationConnect(BaseConnector): # Can connect child exec spec if parent is not connected return True - connected_item: Optional[Presentation[Element]] + connected_item: Presentation[Element] | None connected_item = self.get_connected(self.element.handles()[0]) assert connected_item Connector(connected_item, self.line).connect(handle, None) diff --git a/gaphor/UML/modelinglanguage.py b/gaphor/UML/modelinglanguage.py index 3a4c10d9b..5089bd24e 100644 --- a/gaphor/UML/modelinglanguage.py +++ b/gaphor/UML/modelinglanguage.py @@ -1,7 +1,7 @@ """The UML Modeling Language module is the entrypoint for UML related assets.""" -from typing import Iterable +from collections.abc import Iterable from gaphor.abc import ModelingLanguage from gaphor.core import gettext diff --git a/gaphor/UML/recipes.py b/gaphor/UML/recipes.py index 8734f4382..0d4d0b197 100644 --- a/gaphor/UML/recipes.py +++ b/gaphor/UML/recipes.py @@ -9,7 +9,8 @@ Functions collected in this module allow to from __future__ import annotations import itertools -from typing import Iterable, Sequence, TypeVar +from collections.abc import Iterable, Sequence +from typing import TypeVar from gaphor.UML.uml import ( Artifact, diff --git a/gaphor/UML/sanitizerservice.py b/gaphor/UML/sanitizerservice.py index 96e023217..8405800b9 100644 --- a/gaphor/UML/sanitizerservice.py +++ b/gaphor/UML/sanitizerservice.py @@ -1,7 +1,7 @@ """The Sanitize module is dedicated to adapters (stuff) that keeps the model clean and in sync with diagrams.""" -from typing import Iterable +from collections.abc import Iterable from gaphor import UML from gaphor.abc import Service diff --git a/gaphor/UML/tests/test_diagramitems.py b/gaphor/UML/tests/test_diagramitems.py index f00b70b28..c40c6d5dc 100644 --- a/gaphor/UML/tests/test_diagramitems.py +++ b/gaphor/UML/tests/test_diagramitems.py @@ -61,7 +61,7 @@ def test_all_named_elements_have_named_items(item_class, element_class): assert issubclass(item_class, Named) if issubclass(item_class, Named): - assert issubclass(element_class, (NamedElement, Relationship)) + assert issubclass(element_class, NamedElement | Relationship) CLASSIFIED_EXCLUSIONS = [ diff --git a/gaphor/UML/tests/test_uml2.py b/gaphor/UML/tests/test_uml2.py index c285078ff..6a521d129 100644 --- a/gaphor/UML/tests/test_uml2.py +++ b/gaphor/UML/tests/test_uml2.py @@ -72,9 +72,9 @@ def test_class(factory): assert ( operation1 in element.feature ), f"Classifier.feature does not contain ownedOperation - {element.feature}" - assert operation1 in element.ownedMember, ( - "Namespace.ownedMember does not contain ownedOperation" % element.ownedMember - ) + assert ( + operation1 in element.ownedMember + ), "Namespace.ownedMember does not contain ownedOperation".format() def test_comment(factory): diff --git a/gaphor/UML/umlfmt.py b/gaphor/UML/umlfmt.py index 2977e185a..c9f1c0707 100644 --- a/gaphor/UML/umlfmt.py +++ b/gaphor/UML/umlfmt.py @@ -1,7 +1,6 @@ """Formatting of UML elements like attributes, operations, stereotypes, etc.""" import re -from typing import Tuple from gaphor.core.format import format from gaphor.i18n import gettext @@ -70,7 +69,7 @@ def format_property( tag_vals.extend(format(slot) for slot in el.appliedStereotype[:].slot if slot) if tag_vals: - s.append(" { %s }" % ", ".join(tag_vals)) + s.append(" {{ {} }}".format(", ".join(tag_vals))) if note and el.note: s.append(f" # {el.note}") @@ -78,7 +77,7 @@ def format_property( return "".join(s) -def format_association_end(el) -> Tuple[str, str]: +def format_association_end(el) -> tuple[str, str]: """Format association end.""" name = "" if el.name: @@ -91,7 +90,7 @@ def format_association_end(el) -> Tuple[str, str]: m = [format_multiplicity(el, bare=True)] if slots := [format(slot) for slot in el.appliedStereotype[:].slot if slot]: - m.append(" { %s }" % ",\n".join(slots)) + m.append(" {{ {} }}".format(",\n".join(slots))) mult = "".join(m) return name, mult diff --git a/gaphor/UML/umloverrides.py b/gaphor/UML/umloverrides.py index ca36e5e88..c086d4f6e 100644 --- a/gaphor/UML/umloverrides.py +++ b/gaphor/UML/umloverrides.py @@ -62,8 +62,8 @@ def property_navigability(self: uml.Property) -> list[bool | None]: return [None] # assume unknown owner = self.opposite.type if ( - isinstance(owner, (uml.Class, uml.DataType, uml.Interface)) - and isinstance(self.type, (uml.Class, uml.DataType, uml.Interface)) + isinstance(owner, uml.Class | uml.DataType | uml.Interface) + and isinstance(self.type, uml.Class | uml.DataType | uml.Interface) and (self in owner.ownedAttribute) or self in assoc.navigableOwnedEnd ): diff --git a/gaphor/abc.py b/gaphor/abc.py index c9319119b..554a33153 100644 --- a/gaphor/abc.py +++ b/gaphor/abc.py @@ -1,7 +1,8 @@ from __future__ import annotations import abc -from typing import TYPE_CHECKING, Iterable +from collections.abc import Iterable +from typing import TYPE_CHECKING if TYPE_CHECKING: from gaphor.core.modeling import Element diff --git a/gaphor/action.py b/gaphor/action.py index ba00999ea..3c8b8c58c 100644 --- a/gaphor/action.py +++ b/gaphor/action.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Any, Callable, Iterator, get_type_hints +from collections.abc import Callable, Iterator +from typing import Any, get_type_hints class action: diff --git a/gaphor/application.py b/gaphor/application.py index e58ac3687..77ce664a3 100644 --- a/gaphor/application.py +++ b/gaphor/application.py @@ -8,8 +8,9 @@ from __future__ import annotations import importlib.metadata import logging +from collections.abc import Iterator from pathlib import Path -from typing import Iterator, TypeVar, cast +from typing import TypeVar, cast from uuid import uuid1 from gaphor.abc import ActionProvider, Service diff --git a/gaphor/codegen/coder.py b/gaphor/codegen/coder.py index 8dad630b3..9181f6418 100644 --- a/gaphor/codegen/coder.py +++ b/gaphor/codegen/coder.py @@ -27,8 +27,8 @@ import contextlib import logging import sys import textwrap +from collections.abc import Iterable from pathlib import Path -from typing import Iterable from gaphor import UML from gaphor.codegen.override import Overrides diff --git a/gaphor/codegen/override.py b/gaphor/codegen/override.py index ce4475deb..8b6353baa 100644 --- a/gaphor/codegen/override.py +++ b/gaphor/codegen/override.py @@ -9,7 +9,6 @@ This is a simple rip-off of the override script used in PyGTK. # ruff: noqa: T201 import re -from typing import List OVERRIDE_RE = re.compile( r"^override\s+(?P[\w.]+)(?:\((?P[^)]+)\))?\s*(?::\s*(?P[\w\s\[\],\"| ]+))?$" @@ -35,7 +34,7 @@ class Overrides: # bufs contains a list of (lines, line_number) pairs. bufs = [] line_number = 1 - lines: List[str] = [] + lines: list[str] = [] line = fp.readline() linenum = 1 while line: diff --git a/gaphor/core/changeset/compare.py b/gaphor/core/changeset/compare.py index 19b49f52e..e70f5990a 100644 --- a/gaphor/core/changeset/compare.py +++ b/gaphor/core/changeset/compare.py @@ -1,7 +1,7 @@ from __future__ import annotations +from collections.abc import Iterable from operator import setitem -from typing import Iterable from gaphor.core.modeling import ( Element, diff --git a/gaphor/core/modeling/collection.py b/gaphor/core/modeling/collection.py index 7af9111c8..80d50f9e1 100644 --- a/gaphor/core/modeling/collection.py +++ b/gaphor/core/modeling/collection.py @@ -3,7 +3,8 @@ from __future__ import annotations import contextlib -from typing import Generic, Iterator, Sequence, Type, TypeVar, overload +from collections.abc import Iterator, Sequence +from typing import Generic, TypeVar, overload from gaphor.core.modeling.event import AssociationUpdated @@ -13,7 +14,7 @@ T = TypeVar("T") class collection(Generic[T]): """Collection (set-like) for model elements' 1:n and n:m relationships.""" - def __init__(self, property, object, type: Type[T]): + def __init__(self, property, object, type: type[T]): self.property = property self.object = object self.type = type diff --git a/gaphor/core/modeling/diagram.py b/gaphor/core/modeling/diagram.py index 21594e7cd..07b36bae5 100644 --- a/gaphor/core/modeling/diagram.py +++ b/gaphor/core/modeling/diagram.py @@ -80,7 +80,7 @@ class DrawContext: dropzone: bool -@lru_cache() +@lru_cache def attrname(obj, lower_name): """Look up a real attribute name based on a lower case (normalized) name.""" @@ -94,7 +94,7 @@ def rgetattr(obj, names): """Recursively get a name, based on a list of names.""" name, *tail = names v = getattr(obj, attrname(obj, name), NO_ATTR) - if isinstance(v, (collection, list, tuple)): + if isinstance(v, collection | list | tuple): if tail and not v: yield NO_ATTR if tail: @@ -112,7 +112,7 @@ def attrstr(obj): """Returns lower-case string representation of an attribute.""" if isinstance(obj, str): return obj.lower() - elif isinstance(obj, (bool, int)): + elif isinstance(obj, bool | int): return "true" if obj else "" elif isinstance(obj, Element): return obj.__class__.__name__.lower() diff --git a/gaphor/core/modeling/element.py b/gaphor/core/modeling/element.py index 860061c11..5dc96b12d 100644 --- a/gaphor/core/modeling/element.py +++ b/gaphor/core/modeling/element.py @@ -4,7 +4,8 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Callable, Iterator, Protocol, TypeVar, overload +from collections.abc import Callable, Iterator +from typing import TYPE_CHECKING, Protocol, TypeVar, overload from uuid import uuid1 from gaphor.core.modeling.collection import collection diff --git a/gaphor/core/modeling/elementdispatcher.py b/gaphor/core/modeling/elementdispatcher.py index 6e3e5b28b..957b630eb 100644 --- a/gaphor/core/modeling/elementdispatcher.py +++ b/gaphor/core/modeling/elementdispatcher.py @@ -242,17 +242,14 @@ class ElementDispatcher(Service): # Handle add/removal of handlers based on the kind of event # Filter out handlers that have no remaining properties if ( - isinstance(event, (AssociationSet, AssociationDeleted)) + isinstance(event, AssociationSet | AssociationDeleted) and event.old_value ): for handler, remainders in handlers.items(): for remainder in remainders: self._remove_handlers(event.old_value, remainder[0], handler) - if ( - isinstance(event, (AssociationSet, AssociationAdded)) - and event.new_value - ): + if isinstance(event, AssociationSet | AssociationAdded) and event.new_value: for handler, remainders in handlers.items(): for remainder in remainders: self._add_handlers(event.new_value, remainder, handler) diff --git a/gaphor/core/modeling/elementfactory.py b/gaphor/core/modeling/elementfactory.py index 8129ba253..a985915a8 100644 --- a/gaphor/core/modeling/elementfactory.py +++ b/gaphor/core/modeling/elementfactory.py @@ -3,8 +3,9 @@ from __future__ import annotations from collections import OrderedDict +from collections.abc import Callable, Iterator from contextlib import contextmanager -from typing import Callable, Iterator, Protocol, TypeVar, overload +from typing import Protocol, TypeVar, overload from gaphor.abc import Service from gaphor.core.eventmanager import EventManager, event_handler diff --git a/gaphor/core/modeling/properties.py b/gaphor/core/modeling/properties.py index d769ca469..65da9ada3 100644 --- a/gaphor/core/modeling/properties.py +++ b/gaphor/core/modeling/properties.py @@ -26,16 +26,13 @@ methods: from __future__ import annotations import logging +from collections.abc import Callable, Iterable, Sequence from typing import ( Any, - Callable, Generic, - Iterable, Literal, Protocol, - Sequence, TypeVar, - Union, overload, ) @@ -100,12 +97,12 @@ class relation_many(Protocol[E]): def __delete__(self, obj) -> None: ... -relation = Union[relation_one, relation_many] +relation = relation_one | relation_many T = TypeVar("T") -Lower = Union[Literal[0], Literal[1], Literal[2]] -Upper = Union[Literal[1], Literal[2], Literal["*"]] +Lower = Literal[0] | Literal[1] | Literal[2] +Upper = Literal[1] | Literal[2] | Literal["*"] class umlproperty: @@ -210,7 +207,7 @@ class attribute(umlproperty, Generic[T]): or self.type ) - if self.type is int and isinstance(value, (str, bool)): + if self.type is int and isinstance(value, str | bool): value = 0 if value == "False" else 1 if value == "True" else int(value) if value == self.get(obj): @@ -439,7 +436,7 @@ class association(umlproperty): self.stub = associationstub(self) # Do not let property start with underscore, or it will not be found # as an umlproperty. - setattr(self.type, "GAPHOR__associationstub__%x" % id(self), self.stub) + setattr(self.type, f"GAPHOR__associationstub__{id(self):x}", self.stub) self.stub.set(value, obj) def delete(self, obj, value, from_opposite=False, do_notify=True): @@ -519,7 +516,7 @@ class associationstub(umlproperty): """ def __init__(self, association: association): - super().__init__("stub_%x" % id(self)) + super().__init__(f"stub_{id(self):x}") self.association = association def __get__(self, obj, class_=None): @@ -605,7 +602,7 @@ class derived(umlproperty, Generic[T]): def add(self, subset): self.subsets.add(subset) assert isinstance( - subset, (association, derived) + subset, association | derived ), f"have element {subset}, expected association" subset.dependent_properties.add(self) @@ -814,7 +811,7 @@ class redefine(umlproperty): ): super().__init__(name) assert isinstance( - original, (association, derived) + original, association | derived ), f"expected association or derived, got {original}" self.decl_class = decl_class self.type = type diff --git a/gaphor/core/modeling/tests/test_properties.py b/gaphor/core/modeling/tests/test_properties.py index 12d73e225..5b01ec60d 100644 --- a/gaphor/core/modeling/tests/test_properties.py +++ b/gaphor/core/modeling/tests/test_properties.py @@ -164,12 +164,12 @@ def test_association_1_n(): b2 = B() b1.two = a1 - assert len(b1.two) == 1, "len(b1.two) == %d" % len(b1.two) + assert len(b1.two) == 1, f"len(b1.two) == {len(b1.two)}" assert a1 in b1.two assert a1.one is b1, f"{a1.one}/{b1}" b1.two = a1 b1.two = a1 - assert len(b1.two) == 1, "len(b1.two) == %d" % len(b1.two) + assert len(b1.two) == 1, f"len(b1.two) == {len(b1.two)}" assert a1 in b1.two assert a1.one is b1, f"{a1.one}/{b1}" diff --git a/gaphor/core/styling/__init__.py b/gaphor/core/styling/__init__.py index f2de67bee..b741f7712 100644 --- a/gaphor/core/styling/__init__.py +++ b/gaphor/core/styling/__init__.py @@ -1,8 +1,8 @@ from __future__ import annotations import functools -from collections.abc import Hashable -from typing import Callable, Iterator, Protocol, Sequence, TypedDict, Union +from collections.abc import Callable, Hashable, Iterator, Sequence +from typing import Protocol, TypedDict from gaphor.core.styling.compiler import compile_style_sheet from gaphor.core.styling.declarations import ( @@ -38,7 +38,7 @@ Style = TypedDict( "dash-style": Sequence[Number], "padding": Padding, "font-family": str, - "font-size": Union[int, float, str], + "font-size": int | float | str, "font-style": FontStyle, "font-weight": FontWeight, "justify-content": JustifyContent, diff --git a/gaphor/core/styling/compiler.py b/gaphor/core/styling/compiler.py index a95cfd8d8..0e79f33b0 100644 --- a/gaphor/core/styling/compiler.py +++ b/gaphor/core/styling/compiler.py @@ -5,8 +5,9 @@ Ayoub. """ import re +from collections.abc import Callable, Iterator from functools import singledispatch -from typing import Callable, Dict, Iterator, Literal, Tuple, Union +from typing import Literal import tinycss2 @@ -17,10 +18,10 @@ from gaphor.core.styling.declarations import parse_declarations split_whitespace = re.compile("[^ \t\r\n\f]+").findall -Rule = Union[ - Tuple[Callable[[object], bool], Dict[str, object]], - Tuple[Literal["error"], Union[tinycss2.ast.ParseError, selectors.SelectorError]], -] +Rule = ( + tuple[Callable[[object], bool], dict[str, object]] + | tuple[Literal["error"], tinycss2.ast.ParseError | selectors.SelectorError] +) def compile_style_sheet(*css: str) -> Iterator[Rule]: diff --git a/gaphor/core/styling/declarations.py b/gaphor/core/styling/declarations.py index 4f19d3df0..609e4302d 100644 --- a/gaphor/core/styling/declarations.py +++ b/gaphor/core/styling/declarations.py @@ -1,15 +1,16 @@ """Definitions (types) for style sheets.""" +from collections.abc import Callable, Sequence from enum import Enum -from typing import Callable, Dict, List, NamedTuple, Optional, Sequence, Tuple, Union +from typing import NamedTuple import tinycss2.color3 from tinycss2.ast import FunctionBlock from tinycss2.parser import parse_declaration_list -Number = Union[int, float] -Color = Tuple[float, float, float, float] # RGBA -Padding = Tuple[Number, Number, Number, Number] # top/right/bottom/left +Number = int | float +Color = tuple[float, float, float, float] # RGBA +Padding = tuple[Number, Number, Number, Number] # top/right/bottom/left class TextAlign(Enum): @@ -82,9 +83,7 @@ class _Declarations: """Convert raw CSS declarations into Gaphor styling declarations.""" def __init__(self) -> None: - self.declarations: List[ - Tuple[str, Callable[[str, object], Optional[object]]] - ] = [] + self.declarations: list[tuple[str, Callable[[str, object], object | None]]] = [] def register(self, *properties): def reg(func): @@ -162,19 +161,19 @@ def parse_color(prop, value): "vertical-spacing", "border-radius", ) -def parse_positive_number(prop, value) -> Optional[Number]: +def parse_positive_number(prop, value) -> Number | None: return value if isinstance(value, number) and value >= 0 else None @declarations.register( "opacity", ) -def parse_factor(prop, value) -> Optional[Number]: +def parse_factor(prop, value) -> Number | None: return value if isinstance(value, number) and 0 <= value <= 1 else None @declarations.register("font-family") -def parse_string(prop, value) -> Optional[str]: +def parse_string(prop, value) -> str | None: if isinstance(value, str): return value elif value: @@ -183,14 +182,14 @@ def parse_string(prop, value) -> Optional[str]: @declarations.register("font-size") -def parse_font_size(prop, value) -> Union[None, int, float, str]: +def parse_font_size(prop, value) -> None | int | float | str: if isinstance(value, number) and value > 0: return value return value if isinstance(value, str) and value in FONT_SIZE_VALUES else None @declarations.register("padding") -def parse_padding(prop, value) -> Optional[Padding]: +def parse_padding(prop, value) -> Padding | None: if isinstance(value, number): return (value, value, value, value) n = len(value) @@ -207,18 +206,18 @@ def parse_padding(prop, value) -> Optional[Padding]: @declarations.register("dash-style") def parse_sequence_numbers( - prop, value: Union[Number, Sequence[Number]] -) -> Optional[Sequence[Number]]: + prop, value: Number | Sequence[Number] +) -> Sequence[Number] | None: if value == 0: return () if isinstance(value, number): return (value, value) - elif all(isinstance(v, (int, float)) for v in value): + elif all(isinstance(v, int | float) for v in value): return value return None -enum_styles: Dict[str, Dict[str, object]] = { +enum_styles: dict[str, dict[str, object]] = { "font-style": {e.value: e for e in FontStyle}, "font-weight": {e.value: e for e in FontWeight}, "justify-content": {e.value: e for e in JustifyContent}, diff --git a/gaphor/core/styling/pseudo.py b/gaphor/core/styling/pseudo.py index aafbeff95..6edad6399 100644 --- a/gaphor/core/styling/pseudo.py +++ b/gaphor/core/styling/pseudo.py @@ -1,4 +1,4 @@ -from typing import Iterator, Sequence +from collections.abc import Iterator, Sequence from gaphor.core.styling import CompiledStyleSheet, Style, StyleNode diff --git a/gaphor/core/styling/selectors.py b/gaphor/core/styling/selectors.py index 669ec09dd..63e572b68 100644 --- a/gaphor/core/styling/selectors.py +++ b/gaphor/core/styling/selectors.py @@ -298,7 +298,7 @@ class CombinedSelector: return a1 + a2, b1 + b2, c1 + c2 def __repr__(self): - return "{!r}{}{!r}".format(self.left, self.combinator, self.right) + return f"{self.left!r}{self.combinator}{self.right!r}" class CompoundSelector: @@ -347,7 +347,7 @@ class NamespaceSelector: self.namespace = namespace def __repr__(self): - return "|" if self.namespace == "" else "{%s}|" % self.namespace + return "|" if self.namespace == "" else f"{{{self.namespace}}}|" class IDSelector: @@ -382,8 +382,8 @@ class AttributeSelector: self.value = value def __repr__(self): - namespace = "*|" if self.namespace is None else "{%s}" % self.namespace - return "[{}{}{}{!r}]".format(namespace, self.name, self.operator, self.value) + namespace = "*|" if self.namespace is None else f"{{{self.namespace}}}" + return f"[{namespace}{self.name}{self.operator}{self.value!r}]" class PseudoClassSelector: @@ -414,4 +414,4 @@ class FunctionalPseudoClassSelector: self.arguments = arguments def __repr__(self): - return ":{}{!r}".format(self.name, tuple(self.arguments)) + return f":{self.name}{tuple(self.arguments)!r}" diff --git a/gaphor/diagram/connectors.py b/gaphor/diagram/connectors.py index 0e04e0ed1..92e80e70b 100644 --- a/gaphor/diagram/connectors.py +++ b/gaphor/diagram/connectors.py @@ -8,7 +8,8 @@ from __future__ import annotations import functools import itertools -from typing import Iterator, Protocol, TypeVar +from collections.abc import Iterator +from typing import Protocol, TypeVar from gaphas.connections import Connection from gaphas.connector import ConnectionSink, Handle, Port @@ -129,7 +130,7 @@ Connector: FunctionDispatcher[type[ConnectorProtocol]] = multidispatch(object, o ) -@functools.lru_cache() +@functools.lru_cache def can_connect(parent, element_type) -> bool: parent_type = type(parent) get_registration = Connector.registry.get_registration @@ -170,8 +171,8 @@ class RelationshipConnect(BaseConnector): head - tuple (association name on the line, association name on the element) tail - tuple (association name on the line, association name on the element) """ - assert isinstance(head, (association, redefine)), f"head is {head}" - assert isinstance(tail, (association, redefine)), f"tail is {tail}" + assert isinstance(head, association | redefine), f"head is {head}" + assert isinstance(tail, association | redefine), f"tail is {tail}" line = self.line diff --git a/gaphor/diagram/copypaste.py b/gaphor/diagram/copypaste.py index 8bcf3ffcc..e3feb56d8 100644 --- a/gaphor/diagram/copypaste.py +++ b/gaphor/diagram/copypaste.py @@ -21,9 +21,9 @@ complete the model loading. from __future__ import annotations -from collections.abc import Iterable +from collections.abc import Callable, Collection, Iterable, Iterator from functools import singledispatch -from typing import Callable, Collection, Iterator, NamedTuple +from typing import NamedTuple from gaphor.core.modeling import Diagram, Presentation from gaphor.core.modeling.collection import collection diff --git a/gaphor/diagram/diagramtoolbox.py b/gaphor/diagram/diagramtoolbox.py index 370b476a5..62c21b0d9 100644 --- a/gaphor/diagram/diagramtoolbox.py +++ b/gaphor/diagram/diagramtoolbox.py @@ -7,14 +7,9 @@ the actions bound to the toolbuttons should change as well. import getpass import time +from collections.abc import Callable, Collection, Sequence from typing import ( - Callable, - Collection, NamedTuple, - Optional, - Sequence, - Tuple, - Type, TypeVar, ) @@ -25,7 +20,7 @@ from gaphor.core.modeling import Comment, Diagram, Element, Picture, Presentatio from gaphor.diagram import general from gaphor.diagram.group import group -ItemFactory = Callable[[Diagram, Optional[Presentation]], Presentation] +ItemFactory = Callable[[Diagram, Presentation | None], Presentation] P = TypeVar("P", bound=Presentation, covariant=True) ConfigFuncType = Callable[[P], None] @@ -34,8 +29,8 @@ class ToolDef(NamedTuple): id: str name: str icon_name: str - shortcut: Optional[str] - item_factory: Optional[ItemFactory] + shortcut: str | None + item_factory: ItemFactory | None handle_index: int = -1 @@ -57,7 +52,7 @@ class DiagramType: self.name = name self.sections = sections - def allowed(self, element: Type[Element]) -> bool: + def allowed(self, element: type[Element]) -> bool: return True def create(self, element_factory, element): @@ -75,11 +70,11 @@ DiagramTypes = Sequence[DiagramType] class ElementCreateInfo(NamedTuple): id: str name: str - element_type: Type[Element] - allowed_owning_elements: Collection[Type[Element]] + element_type: type[Element] + allowed_owning_elements: Collection[type[Element]] -def tooliter(toolbox_actions: Sequence[Tuple[str, Sequence[ToolDef]]]): +def tooliter(toolbox_actions: Sequence[tuple[str, Sequence[ToolDef]]]): """Iterate toolbox items, regardless of section headers.""" for _name, section in toolbox_actions: yield from section @@ -92,9 +87,9 @@ def get_tool_def(modeling_language, tool_name): def new_item_factory( - item_class: Type[Presentation], - subject_class: Optional[Type[Element]] = None, - config_func: Optional[ConfigFuncType] = None, + item_class: type[Presentation], + subject_class: type[Element] | None = None, + config_func: ConfigFuncType | None = None, ): """``config_func`` may be a function accepting the newly created item.""" diff --git a/gaphor/diagram/drop.py b/gaphor/diagram/drop.py index 480911eb5..789501188 100644 --- a/gaphor/diagram/drop.py +++ b/gaphor/diagram/drop.py @@ -1,7 +1,7 @@ from __future__ import annotations import logging -from typing import Callable +from collections.abc import Callable from gaphas.geometry import Rectangle from gaphas.item import NW, SE diff --git a/gaphor/diagram/general/connectors.py b/gaphor/diagram/general/connectors.py index eb5d6362e..2fd656eb8 100644 --- a/gaphor/diagram/general/connectors.py +++ b/gaphor/diagram/general/connectors.py @@ -1,7 +1,6 @@ """Connect comments.""" import logging -from typing import Union from gaphor.core.modeling import Comment from gaphor.diagram.connectors import BaseConnector, Connector @@ -17,7 +16,7 @@ logger = logging.getLogger(__name__) class CommentLineElementConnect(BaseConnector): """Connect a comment line to any element item.""" - element: Union[CommentItem, ElementPresentation] + element: CommentItem | ElementPresentation line: CommentLineItem def allow(self, handle, port): diff --git a/gaphor/diagram/group.py b/gaphor/diagram/group.py index be606c190..b637035d4 100644 --- a/gaphor/diagram/group.py +++ b/gaphor/diagram/group.py @@ -9,7 +9,7 @@ from __future__ import annotations import itertools -from typing import Callable +from collections.abc import Callable from generic.multidispatch import FunctionDispatcher, multidispatch diff --git a/gaphor/diagram/propertypages.py b/gaphor/diagram/propertypages.py index a3253fdad..c9e4273e5 100644 --- a/gaphor/diagram/propertypages.py +++ b/gaphor/diagram/propertypages.py @@ -8,7 +8,7 @@ from __future__ import annotations import abc import textwrap -from typing import Iterator +from collections.abc import Iterator import gaphas.item from gaphas.segment import Segment diff --git a/gaphor/diagram/selection.py b/gaphor/diagram/selection.py index 3e6e41348..f6dd15007 100644 --- a/gaphor/diagram/selection.py +++ b/gaphor/diagram/selection.py @@ -1,4 +1,4 @@ -from typing import Iterable, Optional, Set +from collections.abc import Iterable from gaphas.item import Item from gaphas.selection import Selection as _Selection @@ -7,8 +7,8 @@ from gaphas.selection import Selection as _Selection class Selection(_Selection): def __init__(self): super().__init__() - self._dropzone_item: Optional[Item] = None - self._grayed_out_items: Set[Item] = set() + self._dropzone_item: Item | None = None + self._grayed_out_items: set[Item] = set() def clear(self): self._dropzone_item = None @@ -16,16 +16,16 @@ class Selection(_Selection): super().clear() @property - def dropzone_item(self) -> Optional[Item]: + def dropzone_item(self) -> Item | None: return self._dropzone_item @dropzone_item.setter - def dropzone_item(self, item: Optional[Item]) -> None: + def dropzone_item(self, item: Item | None) -> None: if item is not self._dropzone_item: self._dropzone_item = item @property - def grayed_out_items(self) -> Set[Item]: + def grayed_out_items(self) -> set[Item]: return self._grayed_out_items @grayed_out_items.setter diff --git a/gaphor/diagram/shapes.py b/gaphor/diagram/shapes.py index 585bb701b..a1297353a 100644 --- a/gaphor/diagram/shapes.py +++ b/gaphor/diagram/shapes.py @@ -1,11 +1,11 @@ from __future__ import annotations import math -from collections.abc import Iterable, Iterator, Sequence +from collections.abc import Callable, Iterable, Iterator, Sequence from dataclasses import replace from enum import Enum from math import pi -from typing import Callable, Protocol +from typing import Protocol from gaphas.geometry import Rectangle diff --git a/gaphor/diagram/support.py b/gaphor/diagram/support.py index 5ef56c73b..698bf3ea5 100644 --- a/gaphor/diagram/support.py +++ b/gaphor/diagram/support.py @@ -1,7 +1,5 @@ """For ease of creation, maintain a mapping from Element to Diagram Item.""" -from typing import Dict - from gaphor.core.modeling import Element, Presentation @@ -16,8 +14,8 @@ def represents(uml_element, **metadata): # Map elements to their (default) representation. -_element_to_item_map: Dict[type[Element], type[Presentation]] = {} -_item_to_metadata_map: Dict[type[Presentation], dict[str, object]] = {} +_element_to_item_map: dict[type[Element], type[Presentation]] = {} +_item_to_metadata_map: dict[type[Presentation], dict[str, object]] = {} def get_diagram_item(element_cls: type[Element]) -> type[Presentation] | None: diff --git a/gaphor/diagram/tools/handlemove.py b/gaphor/diagram/tools/handlemove.py index b07718967..f28e6ce5c 100644 --- a/gaphor/diagram/tools/handlemove.py +++ b/gaphor/diagram/tools/handlemove.py @@ -1,5 +1,3 @@ -from typing import Optional - from gaphas.connector import Handle from gaphas.guide import GuidedItemHandleMoveMixin from gaphas.handlemove import ConnectionSinkType, HandleMove, ItemHandleMove @@ -43,7 +41,7 @@ class GrayOutLineHandleMoveMixin: def glue( self, pos: Pos, distance: int = ItemHandleMove.GLUE_DISTANCE - ) -> Optional[ConnectionSinkType]: + ) -> ConnectionSinkType | None: sink = super().glue(pos, distance) # type: ignore[misc] self.view.selection.dropzone_item = sink and sink.item return sink diff --git a/gaphor/diagram/tools/placement.py b/gaphor/diagram/tools/placement.py index 7eac48147..9dfcc664a 100644 --- a/gaphor/diagram/tools/placement.py +++ b/gaphor/diagram/tools/placement.py @@ -1,5 +1,4 @@ import logging -from typing import Optional from gaphas.decorators import g_async from gaphas.handlemove import HandleMove @@ -23,7 +22,7 @@ class PlacementState: self.factory = factory self.event_manager = event_manager self.handle_index = handle_index - self.moving: Optional[MoveType] = None + self.moving: MoveType | None = None def placement_tool(factory: ItemFactory, event_manager, handle_index: int): diff --git a/gaphor/diagram/tools/txtool.py b/gaphor/diagram/tools/txtool.py index 99010a6df..5ee281426 100644 --- a/gaphor/diagram/tools/txtool.py +++ b/gaphor/diagram/tools/txtool.py @@ -1,7 +1,7 @@ from __future__ import annotations import logging -from typing import Iterable +from collections.abc import Iterable from gi.repository import Gtk diff --git a/gaphor/entrypoint.py b/gaphor/entrypoint.py index ff2436a46..f6f6f81ff 100644 --- a/gaphor/entrypoint.py +++ b/gaphor/entrypoint.py @@ -2,14 +2,14 @@ import functools import importlib.metadata import inspect import logging -from typing import Dict, TypeVar +from typing import TypeVar T = TypeVar("T") logger = logging.getLogger(__name__) -def initialize(scope, services=None, **known_services: T) -> Dict[str, T]: +def initialize(scope, services=None, **known_services: T) -> dict[str, T]: return init_entry_points(load_entry_points(scope, services), **known_services) @@ -18,7 +18,7 @@ def list_entry_points(group): return importlib.metadata.entry_points(group=group) -def load_entry_points(scope, services=None) -> Dict[str, type]: +def load_entry_points(scope, services=None) -> dict[str, type]: """Load services from resources.""" uninitialized_services = {} for ep in list_entry_points(scope): @@ -30,15 +30,15 @@ def load_entry_points(scope, services=None) -> Dict[str, type]: def init_entry_points( - uninitialized_services: Dict[str, type[T]], **known_services: T -) -> Dict[str, T]: + uninitialized_services: dict[str, type[T]], **known_services: T +) -> dict[str, T]: """Instantiate service definitions, taking into account dependencies defined in the constructor. Given a dictionary `{name: service-class}`, return a map `{name: service-instance}`. """ - ready: Dict[str, T] = known_services.copy() + ready: dict[str, T] = known_services.copy() def pop(name): try: diff --git a/gaphor/plugins/autolayout/pydot.py b/gaphor/plugins/autolayout/pydot.py index 352948fd8..0b02aeecb 100644 --- a/gaphor/plugins/autolayout/pydot.py +++ b/gaphor/plugins/autolayout/pydot.py @@ -1,7 +1,7 @@ from __future__ import annotations +from collections.abc import Iterable, Iterator from functools import singledispatch -from typing import Iterable, Iterator import pydot from gaphas.connector import ConnectionSink, Connector diff --git a/gaphor/plugins/console/console.py b/gaphor/plugins/console/console.py index 747f4e7b9..e5c70c726 100644 --- a/gaphor/plugins/console/console.py +++ b/gaphor/plugins/console/console.py @@ -14,7 +14,6 @@ import code import sys import textwrap import traceback -from typing import Dict, List import jedi from gi.repository import Gdk, GLib, Gtk, Pango @@ -152,7 +151,7 @@ class GTKInterpreterConsole(Gtk.ScrolledWindow): __gtype_name__ = "GTKInterpreterConsole" - def __init__(self, locals: Dict[str, object], banner=banner): + def __init__(self, locals: dict[str, object], banner=banner): Gtk.ScrolledWindow.__init__(self) self.set_vexpand(True) @@ -165,8 +164,8 @@ class GTKInterpreterConsole(Gtk.ScrolledWindow): self.interpreter = code.InteractiveInterpreter(locals) self.locals = locals - self.buffer: List[str] = [] - self.history: List[str] = [] + self.buffer: list[str] = [] + self.history: list[str] = [] self.banner = banner self.text_controller = Gtk.EventControllerKey.new() diff --git a/gaphor/plugins/diagramexport/exportall.py b/gaphor/plugins/diagramexport/exportall.py index d502c3553..850b0d155 100644 --- a/gaphor/plugins/diagramexport/exportall.py +++ b/gaphor/plugins/diagramexport/exportall.py @@ -1,6 +1,5 @@ import logging from pathlib import Path -from typing import List from gaphor.core.modeling import Diagram from gaphor.diagram.export import escape_filename @@ -10,7 +9,7 @@ log = logging.getLogger(__name__) def pkg2dir(package): """Return directory path from package class.""" - name: List[str] = [] + name: list[str] = [] while package: name.insert(0, package.name) package = package.package diff --git a/gaphor/services/componentregistry.py b/gaphor/services/componentregistry.py index f55f99782..6ac0553b9 100644 --- a/gaphor/services/componentregistry.py +++ b/gaphor/services/componentregistry.py @@ -2,7 +2,8 @@ import functools import inspect -from typing import Iterator, List, Tuple, Type, TypeVar +from collections.abc import Iterator +from typing import TypeVar from gaphor.abc import Service @@ -18,7 +19,7 @@ class ComponentRegistry(Service): components.""" def __init__(self) -> None: - self._comp: List[Tuple[str, object]] = [] + self._comp: list[tuple[str, object]] = [] def shutdown(self) -> None: pass @@ -36,7 +37,7 @@ class ComponentRegistry(Service): def unregister(self, component: object) -> None: self._comp = [(n, c) for n, c in self._comp if c is not component] - def get(self, base: Type[T], name: str) -> T: + def get(self, base: type[T], name: str) -> T: found = [(n, c) for n, c in self._comp if isinstance(c, base) and n == name] if len(found) > 1: raise ComponentLookupError( @@ -48,7 +49,7 @@ class ComponentRegistry(Service): ) return found[0][1] - def all(self, base: Type[T]) -> Iterator[Tuple[str, T]]: + def all(self, base: type[T]) -> Iterator[tuple[str, T]]: return ((n, c) for n, c in self._comp if isinstance(c, base)) def partial(self, func): diff --git a/gaphor/services/modelinglanguage.py b/gaphor/services/modelinglanguage.py index 9bbd4b655..cd19ac004 100644 --- a/gaphor/services/modelinglanguage.py +++ b/gaphor/services/modelinglanguage.py @@ -1,4 +1,4 @@ -from typing import Dict, Iterable +from collections.abc import Iterable from gaphor.abc import ActionProvider, ModelingLanguage, Service from gaphor.action import action @@ -27,7 +27,7 @@ class ModelingLanguageService(Service, ActionProvider, ModelingLanguage): self.event_manager = event_manager self.properties = properties - self._modeling_languages: Dict[str, ModelingLanguage] = initialize( + self._modeling_languages: dict[str, ModelingLanguage] = initialize( "gaphor.modelinglanguages" ) if event_manager: diff --git a/gaphor/services/properties.py b/gaphor/services/properties.py index 00652d5bf..69368a7d5 100644 --- a/gaphor/services/properties.py +++ b/gaphor/services/properties.py @@ -8,7 +8,6 @@ import ast import logging import pprint from pathlib import Path -from typing import Dict from gaphor import settings from gaphor.abc import Service @@ -48,7 +47,7 @@ class Properties(Service): def __init__(self, event_manager): self.event_manager = event_manager self.filename: Path = properties_filename("") - self._properties: Dict[str, object] = {} + self._properties: dict[str, object] = {} event_manager.subscribe(self.on_model_loaded) event_manager.subscribe(self.on_model_saved) diff --git a/gaphor/services/undomanager.py b/gaphor/services/undomanager.py index 48fddbb9e..768855dc3 100644 --- a/gaphor/services/undomanager.py +++ b/gaphor/services/undomanager.py @@ -13,7 +13,7 @@ NOTE: it would be nice to use actions in conjunction with functools.partial. from __future__ import annotations import logging -from typing import Callable, List +from collections.abc import Callable from gaphor.abc import ActionProvider, Service from gaphor.action import action @@ -57,7 +57,7 @@ class ActionStack: """ def __init__(self): - self._actions: List[Callable[[], None]] = [] + self._actions: list[Callable[[], None]] = [] def add(self, action): self._actions.append(action) @@ -121,8 +121,8 @@ class UndoManager(Service, ActionProvider): def __init__(self, event_manager, element_factory): self.event_manager = event_manager self.element_factory: RepositoryProtocol = element_factory - self._undo_stack: List[ActionStack] = [] - self._redo_stack: List[ActionStack] = [] + self._undo_stack: list[ActionStack] = [] + self._redo_stack: list[ActionStack] = [] self._stack_depth = 20 self._current_transaction = None diff --git a/gaphor/storage/recovery.py b/gaphor/storage/recovery.py index 6fcfd0341..948a90a02 100644 --- a/gaphor/storage/recovery.py +++ b/gaphor/storage/recovery.py @@ -343,7 +343,7 @@ class Recorder: @event_handler(AssociationAdded, AssociationSet) def on_association_set_event(self, event: AssociationSet | AssociationAdded): - if isinstance(event, (DerivedUpdated, RedefinedAdded, RedefinedSet)): + if isinstance(event, DerivedUpdated | RedefinedAdded | RedefinedSet): return self.events.append( ( @@ -356,7 +356,7 @@ class Recorder: @event_handler(AssociationDeleted) def on_association_delete_event(self, event: AssociationDeleted): - if isinstance(event, (DerivedUpdated, RedefinedDeleted)): + if isinstance(event, DerivedUpdated | RedefinedDeleted): return self.events.append( ( diff --git a/gaphor/storage/storage.py b/gaphor/storage/storage.py index 7b57d7f6e..101a6e3e1 100644 --- a/gaphor/storage/storage.py +++ b/gaphor/storage/storage.py @@ -8,8 +8,8 @@ __all__ = ["load", "save"] import io import logging +from collections.abc import Callable, Iterable from functools import partial -from typing import Callable, Iterable from gaphor import application from gaphor.core.modeling import Diagram, Element, ElementFactory, Presentation diff --git a/gaphor/storage/tests/test_xmlwriter.py b/gaphor/storage/tests/test_xmlwriter.py index 3f23f79d9..286f9a470 100644 --- a/gaphor/storage/tests/test_xmlwriter.py +++ b/gaphor/storage/tests/test_xmlwriter.py @@ -18,7 +18,7 @@ def test_elements_1(): xml_w.startElement("foo", {}) xml_w.endElement("foo") - xml = """\n""" % sys.getdefaultencoding() + xml = f"""\n""" assert w.s == xml, f"{w.s} != {xml}" @@ -31,10 +31,7 @@ def test_elements_2(): xml_w.endElement("bar") xml_w.endElement("foo") - xml = ( - """\n\n\n""" - % sys.getdefaultencoding() - ) + xml = f"""\n\n\n""" assert w.s == xml, w.s @@ -48,10 +45,7 @@ def test_elements_test(): xml_w.endElement("bar") xml_w.endElement("foo") - xml = ( - """\n\nhello\n""" - % sys.getdefaultencoding() - ) + xml = f"""\n\nhello\n""" assert w.s == xml, w.s @@ -63,10 +57,7 @@ def test_elements_ns_default(): xml_w.startElementNS(("http://gaphor.devjavu.com/schema", "foo"), "qn", {}) xml_w.endElementNS(("http://gaphor.devjavu.com/schema", "foo"), "qn") - xml = ( - """\n""" - % sys.getdefaultencoding() - ) + xml = f"""\n""" assert w.s == xml, w.s @@ -78,8 +69,5 @@ def test_elements_ns_1(): xml_w.startElementNS(("http://gaphor.devjavu.com/schema", "foo"), "qn", {}) xml_w.endElementNS(("http://gaphor.devjavu.com/schema", "foo"), "qn") - xml = ( - """\n""" - % sys.getdefaultencoding() - ) + xml = f"""\n""" assert w.s == xml, w.s diff --git a/gaphor/storage/xmlwriter.py b/gaphor/storage/xmlwriter.py index 3e1d8ec40..e467d7f32 100644 --- a/gaphor/storage/xmlwriter.py +++ b/gaphor/storage/xmlwriter.py @@ -1,5 +1,4 @@ import xml.sax.handler -from typing import Dict, List, Tuple from xml.sax.saxutils import escape, quoteattr # See whether the xmlcharrefreplace error handler is @@ -18,9 +17,9 @@ class XMLWriter(xml.sax.handler.ContentHandler): super().__init__() self._out = out self._encoding = encoding - self._ns_contexts: List[Dict[str, str]] = [{}] # contains uri -> prefix dicts + self._ns_contexts: list[dict[str, str]] = [{}] # contains uri -> prefix dicts self._current_context = self._ns_contexts[-1] - self._undeclared_ns_maps: List[Tuple[str, str]] = [] + self._undeclared_ns_maps: list[tuple[str, str]] = [] self._in_cdata = False self._in_start_tag = False diff --git a/gaphor/ui/copyservice.py b/gaphor/ui/copyservice.py index 0b0ef64d0..3f531edaa 100644 --- a/gaphor/ui/copyservice.py +++ b/gaphor/ui/copyservice.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Callable, Collection +from collections.abc import Callable, Collection from gi.repository import Gdk, GLib, GObject diff --git a/gaphor/ui/diagrampage.py b/gaphor/ui/diagrampage.py index 2e49a7984..fbb4a0738 100644 --- a/gaphor/ui/diagrampage.py +++ b/gaphor/ui/diagrampage.py @@ -1,7 +1,6 @@ import functools import importlib import logging -from typing import Optional from gaphas.guide import GuidePainter from gaphas.painter import FreeHandPainter, HandlePainter, PainterChain @@ -79,9 +78,9 @@ class DiagramPage: self.modeling_language = modeling_language self.style_manager = Adw.StyleManager.get_default() - self.view: Optional[GtkView] = None - self.widget: Optional[Gtk.Widget] = None - self.diagram_css: Optional[Gtk.CssProvider] = None + self.view: GtkView | None = None + self.widget: Gtk.Widget | None = None + self.diagram_css: Gtk.CssProvider | None = None self.rubberband_state = RubberbandState() self.context_menu = Gtk.PopoverMenu.new_from_model(popup_model(diagram)) diff --git a/gaphor/ui/elementeditor.py b/gaphor/ui/elementeditor.py index 4016bf90f..5d8bc0db4 100644 --- a/gaphor/ui/elementeditor.py +++ b/gaphor/ui/elementeditor.py @@ -2,7 +2,7 @@ import importlib.resources import logging -from typing import Iterator, Optional +from collections.abc import Iterator from unicodedata import normalize from babel import Locale @@ -166,7 +166,7 @@ class EditorStack: self.diagrams = diagrams self.properties = properties - self.vbox: Optional[Gtk.Box] = None + self.vbox: Gtk.Box | None = None self._current_item = None def open(self, builder): diff --git a/gaphor/ui/filedialog.py b/gaphor/ui/filedialog.py index 0caaaa6ea..809b24b37 100644 --- a/gaphor/ui/filedialog.py +++ b/gaphor/ui/filedialog.py @@ -5,8 +5,8 @@ from __future__ import annotations import os import sys +from collections.abc import Callable from pathlib import Path -from typing import Callable from gi.repository import Gio, Gtk diff --git a/gaphor/ui/filemanager.py b/gaphor/ui/filemanager.py index d9a4ffbe7..46b28ceb2 100644 --- a/gaphor/ui/filemanager.py +++ b/gaphor/ui/filemanager.py @@ -4,9 +4,9 @@ from __future__ import annotations import logging import tempfile +from collections.abc import Callable from functools import partial from pathlib import Path -from typing import Callable from gaphas.decorators import g_async from gi.repository import Adw, Gio, GLib, Gtk diff --git a/gaphor/ui/mainwindow.py b/gaphor/ui/mainwindow.py index 1fa5cfba1..d991e5aed 100644 --- a/gaphor/ui/mainwindow.py +++ b/gaphor/ui/mainwindow.py @@ -3,8 +3,8 @@ from __future__ import annotations import logging +from collections.abc import Callable from pathlib import Path -from typing import Callable from gi.repository import Gio, GLib, Gtk diff --git a/gaphor/ui/modelmerge/organize.py b/gaphor/ui/modelmerge/organize.py index 5e133a478..de91e6e1c 100644 --- a/gaphor/ui/modelmerge/organize.py +++ b/gaphor/ui/modelmerge/organize.py @@ -1,7 +1,7 @@ from __future__ import annotations +from collections.abc import Iterable, Sequence from itertools import groupby -from typing import Iterable, Sequence from gi.repository import Gio, GObject @@ -93,7 +93,7 @@ def organize_changes(element_factory, modeling_language): def not_presentation(change: RefChange): element_type = lookup_element(change.property_ref) - return element_type and not issubclass(element_type, (Diagram, Presentation)) + return element_type and not issubclass(element_type, Diagram | Presentation) def composite_and_not_presentation(change: RefChange): return composite(change) and not_presentation(change) diff --git a/gaphor/ui/modelmerge/tests/test_organize_uml.py b/gaphor/ui/modelmerge/tests/test_organize_uml.py index 8c7ca3393..0b59ce9eb 100644 --- a/gaphor/ui/modelmerge/tests/test_organize_uml.py +++ b/gaphor/ui/modelmerge/tests/test_organize_uml.py @@ -1,4 +1,4 @@ -from typing import Iterable +from collections.abc import Iterable from gaphor import UML from gaphor.core.changeset.compare import compare diff --git a/gaphor/ui/toolbox.py b/gaphor/ui/toolbox.py index 71a397b3d..398656f53 100644 --- a/gaphor/ui/toolbox.py +++ b/gaphor/ui/toolbox.py @@ -5,7 +5,6 @@ This is the toolbox in the lower left of the screen. import functools import logging -from typing import Optional, Tuple from gi.repository import Gdk, GLib, GObject, Gtk @@ -34,8 +33,8 @@ class Toolbox(UIComponent): self.event_manager = event_manager self.properties = properties self.modeling_language = modeling_language - self._toolbox: Optional[Gtk.Box] = None - self._toolbox_container: Optional[Gtk.ScrolledWindow] = None + self._toolbox: Gtk.Box | None = None + self._toolbox_container: Gtk.ScrolledWindow | None = None self._current_diagram_type = "" def open(self) -> Gtk.ScrolledWindow: @@ -201,7 +200,7 @@ def create_toolbox_button( action_name: str, icon_name: str, label: str, - shortcut: Optional[str], + shortcut: str | None, draggable: bool, ) -> Gtk.Button: """Creates a tool button for the toolbox.""" @@ -255,6 +254,6 @@ _upper_offset: int = ord("A") - ord("a") @functools.cache -def parse_shortcut(shortcut: str) -> Tuple[Tuple[int, int], Gdk.ModifierType]: +def parse_shortcut(shortcut: str) -> tuple[tuple[int, int], Gdk.ModifierType]: _, key, mod = Gtk.accelerator_parse(shortcut) return (key, key + _upper_offset), mod diff --git a/gaphor/ui/treemodel.py b/gaphor/ui/treemodel.py index 011d6a567..ae801bab3 100644 --- a/gaphor/ui/treemodel.py +++ b/gaphor/ui/treemodel.py @@ -55,7 +55,7 @@ class TreeItem(GObject.Object): self.icon_visible = bool( self.icon and not isinstance( - element, (UML.Parameter, UML.Property, UML.Operation) + element, UML.Parameter | UML.Property | UML.Operation ) ) self.attributes = pango_attributes(element) @@ -135,14 +135,12 @@ def visible(element: Element) -> bool: return not ( isinstance( element, - ( - Comment, - Presentation, - StyleSheet, - UML.InstanceSpecification, - UML.OccurrenceSpecification, - UML.Slot, - ), + Comment + | Presentation + | StyleSheet + | UML.InstanceSpecification + | UML.OccurrenceSpecification + | UML.Slot, ) or ( # Some types we want to show, except at top level @@ -311,7 +309,7 @@ def pango_attributes(element): attrs.insert( Pango.attr_style_new( Pango.Style.ITALIC - if isinstance(element, (UML.Classifier, UML.BehavioralFeature)) + if isinstance(element, UML.Classifier | UML.BehavioralFeature) and element.isAbstract else Pango.Style.NORMAL ) diff --git a/gaphor/ui/treesearch.py b/gaphor/ui/treesearch.py index cb38266c9..c18092199 100644 --- a/gaphor/ui/treesearch.py +++ b/gaphor/ui/treesearch.py @@ -1,5 +1,5 @@ import functools -from typing import Iterable +from collections.abc import Iterable from unicodedata import normalize from gaphor.ui.treemodel import TreeItem, tree_item_sort diff --git a/pyproject.toml b/pyproject.toml index ddfaea86a..25cb81308 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -259,10 +259,11 @@ select = [ "C", "E", "F", + "PLW1", "SLF", "T20", + "UP", "W", - "PLW1", ] extend-select = ["I"] diff --git a/tests/test_action_issue.py b/tests/test_action_issue.py index e2262ecf1..9bd015429 100644 --- a/tests/test_action_issue.py +++ b/tests/test_action_issue.py @@ -31,7 +31,7 @@ def test_action_issue(element_factory, modeling_language, test_models): for e in actions + flows: assert 1 == len(e.presentation), e - for i in diagram.select(lambda e: isinstance(e, (ControlFlowItem, ActionItem))): + for i in diagram.select(lambda e: isinstance(e, ControlFlowItem | ActionItem)): assert i.subject, i # Loaded as: diff --git a/tests/test_auto_layouting.py b/tests/test_auto_layouting.py index 5bf6998c9..b5f12f8e7 100644 --- a/tests/test_auto_layouting.py +++ b/tests/test_auto_layouting.py @@ -7,7 +7,7 @@ from __future__ import annotations import contextlib import itertools -from typing import Iterable +from collections.abc import Iterable import hypothesis from hypothesis import settings diff --git a/tests/test_model_consistency.py b/tests/test_model_consistency.py index 011ac9164..6c06a80b4 100644 --- a/tests/test_model_consistency.py +++ b/tests/test_model_consistency.py @@ -24,9 +24,9 @@ from __future__ import annotations import contextlib import itertools +from collections.abc import Iterable from functools import singledispatch from io import StringIO -from typing import Iterable import hypothesis from gaphas.connector import Handle diff --git a/tests/test_placement_tools.py b/tests/test_placement_tools.py index 3152970c5..26206b19c 100644 --- a/tests/test_placement_tools.py +++ b/tests/test_placement_tools.py @@ -1,5 +1,3 @@ -from typing import Dict, List - import pytest from gi.repository import Gtk @@ -110,7 +108,7 @@ def test_placement_partition(tab, element_factory, event_manager): ], ) def test_uml_toolbox_actions_shortcut_unique(toolbox_actions): - shortcuts: Dict[str, List[str]] = {} + shortcuts: dict[str, list[str]] = {} for _category, items in toolbox_actions: for action_name, _label, _icon_name, shortcut, *_rest in items: