Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unwind uml package #263

Merged
merged 12 commits into from Feb 6, 2020
12 changes: 8 additions & 4 deletions gaphor/UML/__init__.py
@@ -1,6 +1,10 @@
from gaphor.UML import modelfactory as model
from gaphor.UML.collection import collection
from gaphor.UML.elementfactory import ElementFactory
from gaphor.UML.uml2 import *
# Here, order matters
from gaphor.UML.uml2 import * # noqa: isort:skip
from gaphor.UML.presentation import Presentation # noqa: isort:skip
from gaphor.UML.elementfactory import ElementFactory # noqa: isort:skip
from gaphor.UML import modelfactory as model # noqa: isort:skip

from gaphor.UML.umlfmt import format
from gaphor.UML.umllex import parse

import gaphor.UML.uml2overrides # noqa: isort:skip
57 changes: 0 additions & 57 deletions gaphor/UML/diagram.py
Expand Up @@ -9,10 +9,6 @@

import gaphas

from gaphor.UML.event import DiagramItemCreated
from gaphor.UML.properties import umlproperty
from gaphor.UML.uml2 import Namespace, PackageableElement

log = logging.getLogger(__name__)


Expand Down Expand Up @@ -63,56 +59,3 @@ def select(self, expression=lambda e: True):
"""Return a list of all canvas items that match expression."""

return list(filter(expression, self.get_all_items()))


class Diagram(Namespace, PackageableElement):
"""Diagrams may contain model elements and can be owned by a Package.
"""

def __init__(self, id=None, model=None):
"""Initialize the diagram with an optional id and element model.
The diagram also has a canvas."""

super().__init__(id, model)
self.canvas = DiagramCanvas(self)

package: umlproperty[Namespace]

def save(self, save_func):
"""Apply the supplied save function to this diagram and the canvas."""

super().save(save_func)
save_func("canvas", self.canvas)

def postload(self):
"""Handle post-load functionality for the diagram canvas."""
super().postload()
self.canvas.postload()

def create(self, type, parent=None, subject=None):
"""Create a new canvas item on the canvas. It is created with
a unique ID and it is attached to the diagram's root item. The type
parameter is the element class to create. The new element also has an
optional parent and subject."""

return self.create_as(type, str(uuid.uuid1()), parent, subject)

def create_as(self, type, id, parent=None, subject=None):
assert issubclass(type, gaphas.Item)
item = type(id, self.model)
if subject:
item.subject = subject
self.canvas.add(item, parent)
self.model.handle(DiagramItemCreated(self.model, item))
return item

def unlink(self):
"""Unlink all canvas items then unlink this diagram."""

for item in self.canvas.get_all_items():
try:
item.unlink()
except (AttributeError, KeyError):
pass

super().unlink()
76 changes: 63 additions & 13 deletions gaphor/UML/element.py
Expand Up @@ -7,15 +7,12 @@

import logging
import uuid
from typing import TYPE_CHECKING, Optional, Sequence, Type, Union
from typing import Callable, Iterator, Optional, Type, TypeVar, Union

from gaphor.UML.elementdispatcher import EventWatcher
from gaphor.UML.properties import relation_many, relation_one, umlproperty

if TYPE_CHECKING:
from gaphor.UML.elementfactory import ElementFactory # noqa
from gaphor.UML.presentation import Presentation # noqa
from typing_extensions import Protocol

from gaphor.UML.event import ElementUpdated
from gaphor.UML.properties import relation_many, relation_one, umlproperty

__all__ = ["Element"]

Expand All @@ -39,7 +36,7 @@ class Element:
"""

def __init__(
self, id: Optional[Id] = None, model: Optional["ElementFactory"] = None
self, id: Optional[Id] = None, model: Optional[RepositoryProtocol] = None
):
"""
Create an element. As optional parameters an id and model can be
Expand All @@ -64,7 +61,7 @@ def id(self) -> Id:
return self._id

@property
def model(self) -> "ElementFactory":
def model(self) -> RepositoryProtocol:
"The owning model, raises AssertionError when model is not set."
assert (
self._model
Expand All @@ -75,7 +72,7 @@ def model(self) -> "ElementFactory":
owner: relation_one[Element]
ownedComment: relation_many[Element]
ownedElement: relation_many[Element]
presentation: relation_many[Presentation]
presentation: relation_many[Element]

def umlproperties(self):
"""
Expand Down Expand Up @@ -143,9 +140,12 @@ def handle(self, event):
if model:
model.handle(event)

def watcher(self, default_handler=None):
dispatcher = self._model.element_dispatcher if self._model else None
return EventWatcher(self, dispatcher, default_handler)
def watcher(self, default_handler=None) -> EventWatcherProtocol:
model = self._model
if model:
return model.watcher(self, default_handler)
else:
return DummyEventWatcher()

# OCL methods: (from SMW by Ivan Porres (http://www.abo.fi/~iporres/smw))

Expand All @@ -160,3 +160,53 @@ def isTypeOf(self, other: Element):
Returns true if the object is of the same type as other.
"""
return isinstance(self, type(other))


class DummyEventWatcher:
def watch(self, path: str, handler: Optional[Handler] = None) -> DummyEventWatcher:
return self

def subscribe_all(self) -> None:
pass

def unsubscribe_all(self) -> None:
pass


T = TypeVar("T", bound=Element)

Handler = Callable[[ElementUpdated], None]


class RepositoryProtocol(Protocol):
def create(self, type: Type[T]) -> T:
...

def create_as(self, type: Type[T], id: str) -> T:
...

def select(
self, expression: Optional[Callable[[Element], bool]] = None
) -> Iterator[Element]:
...

def watcher(
self, element: Element, default_handler: Optional[Handler] = None
) -> EventWatcherProtocol:
...

def handle(self, event: object) -> None:
...


class EventWatcherProtocol(Protocol):
def watch(
self, path: str, handler: Optional[Handler] = None
) -> EventWatcherProtocol:
...

def subscribe_all(self) -> None:
...

def unsubscribe_all(self) -> None:
...
17 changes: 8 additions & 9 deletions gaphor/UML/elementdispatcher.py
@@ -1,11 +1,14 @@
"""
"""

from __future__ import annotations

from logging import getLogger
from typing import Callable, Dict, List, Optional, Set, Tuple

from gaphor import UML
from gaphor.core import event_handler
from gaphor.UML import uml2
from gaphor.UML.element import Element, Handler
from gaphor.UML.event import (
AssociationAdded,
AssociationDeleted,
Expand All @@ -15,8 +18,6 @@
)
from gaphor.UML.properties import umlproperty

Handler = Callable[[ElementUpdated], None]


class EventWatcher:
"""
Expand All @@ -31,7 +32,7 @@ def __init__(
self.default_handler = default_handler
self._watched_paths: Dict[str, Handler] = dict()

def watch(self, path: str, handler: Optional[Handler] = None):
def watch(self, path: str, handler: Optional[Handler] = None) -> EventWatcher:
"""
Watch a certain path of elements starting with the DiagramItem.
The handler is optional and will default the default provided at
Expand Down Expand Up @@ -101,13 +102,11 @@ def __init__(self, event_manager):
self.event_manager = event_manager
# Table used to fire events:
# (event.element, event.property): { handler: set(path, ..), ..}
self._handlers: Dict[
Tuple[uml2.Element, umlproperty], Dict[Handler, Set]
] = dict()
self._handlers: Dict[Tuple[Element, umlproperty], Dict[Handler, Set]] = dict()

# Fast resolution when handlers are disconnected
# handler: [(element, property), ..]
self._reverse: Dict[Handler, List[Tuple[uml2.Element, umlproperty]]] = dict()
self._reverse: Dict[Handler, List[Tuple[Element, umlproperty]]] = dict()

self.event_manager.subscribe(self.on_model_loaded)
self.event_manager.subscribe(self.on_element_change_event)
Expand All @@ -131,7 +130,7 @@ def _path_to_properties(self, element, path):
prop = getattr(c, attr)
tpath.append(prop)
if cname:
c = getattr(uml2, cname)
c = getattr(UML, cname)
assert issubclass(c, prop.type), "{} should be a subclass of {}".format(
c, prop.type
)
Expand Down
12 changes: 9 additions & 3 deletions gaphor/UML/elementfactory.py
@@ -1,5 +1,7 @@
"""Factory for and registration of model elements."""

from __future__ import annotations

import uuid
from collections import OrderedDict
from contextlib import contextmanager
Expand All @@ -15,10 +17,10 @@
)

from gaphor.abc import Service
from gaphor.UML.diagram import Diagram
from gaphor.UML.element import Element, UnlinkEvent
from gaphor.UML.elementdispatcher import ElementDispatcher
from gaphor.UML.elementdispatcher import ElementDispatcher, EventWatcher
from gaphor.UML.event import ElementCreated, ElementDeleted, ModelFlushed, ModelReady
from gaphor.UML.uml2 import Diagram

if TYPE_CHECKING:
from gaphor.services.eventmanager import EventManager # noqa
Expand All @@ -40,7 +42,7 @@ class ElementFactory(Service):
flushed: all element are removed from the factory (element is None)
"""

def __init__(self, event_manager: Optional["EventManager"] = None):
def __init__(self, event_manager: Optional[EventManager] = None):
self.event_manager = event_manager
self.element_dispatcher = (
ElementDispatcher(event_manager) if event_manager else None
Expand Down Expand Up @@ -155,6 +157,10 @@ def is_empty(self) -> bool:
"""
return bool(self._elements)

def watcher(self, element: Element, default_handler=None):
element_dispatcher = self.element_dispatcher
return EventWatcher(element, element_dispatcher, default_handler)

def flush(self) -> None:
"""Flush all elements (remove them from the factory).

Expand Down
6 changes: 3 additions & 3 deletions gaphor/UML/event.py
Expand Up @@ -47,7 +47,7 @@ def __init__(self, element, association, old_value, new_value):
element being set. The old_value parameter is the old association
and the new_value parameter is the new association."""

AssociationUpdated.__init__(self, element, association)
super().__init__(element, association)
self.old_value = old_value
self.new_value = new_value

Expand All @@ -60,7 +60,7 @@ def __init__(self, element, association, new_value):
has been added to. The association parameter is the association
element being added."""

AssociationUpdated.__init__(self, element, association)
super().__init__(element, association)
self.new_value = new_value


Expand All @@ -72,7 +72,7 @@ def __init__(self, element, association, old_value):
has been deleted from. The association parameter is the deleted
association element."""

AssociationUpdated.__init__(self, element, association)
super().__init__(element, association)
self.old_value = old_value


Expand Down
2 changes: 1 addition & 1 deletion gaphor/UML/modelfactory.py
Expand Up @@ -163,7 +163,7 @@ def create_extension(metaclass: Class, stereotype: Stereotype) -> Extension:
), "Metaclass and Stereotype are from different models"

model = metaclass.model
ext = model.create(Extension)
ext: Extension = model.create(Extension)
p = model.create(Property)
ext_end = model.create(ExtensionEnd)

Expand Down
5 changes: 5 additions & 0 deletions gaphor/UML/presentation.py
Expand Up @@ -83,3 +83,8 @@ def unlink(self):
if self.canvas:
self.canvas.remove(self)
super().unlink()


Element.presentation = association(
"presentation", Presentation, composite=True, opposite="subject"
)