Юпітер і сценарії

Одним із способів роботи з моделями є використання графічного інтерфейсу. Однак ви також можете бути зацікавлені в тому, щоб отримати більше від ваших моделей, взаємодіючи з ними за допомогою скриптів Python та блокнотів Jupyter.

Ви можете використовувати сценарії, щоб:

  • Вивчіть модель, перевірте наявність (не)дійсних умов.

  • Згенеруйте код, як це робиться для моделі даних Gaphor.

  • Оновіть модель з іншого джерела, наприклад файлу CSV.

Оскільки Gaphor написаний на Python, він також функціонує як бібліотека.

Початок роботи

Для початку вам знадобиться консоль Python. Ви можете використовувати інтерактивну консоль у Gaphor, використовувати блокнот Jupyter, хоча для цього потрібно налаштувати середовище розробки Python.

Запит моделі

Першим кроком є завантаження моделі. Для цього вам знадобиться gaphor.core.modeling.ElementFactory. ElementFactory відповідає за створення та підтримку моделі. Вона діє як репозиторій для моделі, поки ви над нею працюєте.

from gaphor.core.modeling import ElementFactory

element_factory = ElementFactory()

Модуль gaphor.storage містить все необхідне для завантаження та збереження моделей. Gaphor підтримує декілька мов моделювання. Служба ModelingLanguageService об’єднує ці мови і полегшує логіці завантажувача пошук відповідних класів.

Примітка

У версіях до 2.13 потрібен EventManager. У пізніших версіях ModelingLanguageService можна ініціалізувати без менеджера подій.

from gaphor.core.eventmanager import EventManager
from gaphor.services.modelinglanguage import ModelingLanguageService
from gaphor.storage import storage

event_manager = EventManager()

modeling_language = ModelingLanguageService(event_manager=event_manager)

with open("../models/Core.gaphor", encoding="utf-8") as file_obj:
    storage.load(
        file_obj,
        element_factory,
        modeling_language,
    )

На цьому етапі модель завантажується в element_factory і її можна запитувати.

Примітка

Мова моделювання складається з елементів моделі та елементів діаграми. Графічні компоненти завантажуються окремо. Для найпростіших маніпуляцій GTK (інструментарій графічного інтерфейсу, який ми використовуємо) не потрібен, але ви можете зіткнутися з ситуаціями, коли Gaphor намагається завантажити бібліотеку GTK.

Один трюк, щоб уникнути цього (принаймні, під час генерації документів Sphinx) полягає у використанні функції mock autodoc для імітації бібліотек GTK і GDK. Однак для відтворення тексту потрібно встановити Pango.

Простий запит лише показує, які елементи є в моделі. Метод ElementFactory.select() повертає ітератор. Іноді простіше отримати список безпосередньо. Для таких випадків ви можете використовувати ElementFatory.lselect(). Тут ми вибираємо останні п’ять елементів:

for element in element_factory.lselect()[:5]:
    print(element)
<gaphor.UML.uml.Package element 3867dda4-7a95-11ea-a112-7f953848cf85>
<gaphor.UML.uml.Diagram element 3867dda5-7a95-11ea-a112-7f953848cf85>
<gaphor.UML.classes.klass.ClassItem element 5cdae47f-7a95-11ea-a112-7f953848cf85>
<gaphor.UML.classes.klass.ClassItem element 639b48d1-7a95-11ea-a112-7f953848cf85>
<gaphor.UML.classes.association.AssociationItem element 68e63fac-7a95-11ea-a112-7f953848cf85>

Елементи також можна запитувати за типом і за допомогою функції предикату:

from gaphor import UML
for element in element_factory.select(UML.Class):
    print(element.name)
Diagram
Presentation
StyleSheet
Property
Tagged
ElementChange
ValueChange
RefChange
PendingChange
Base
for diagram in element_factory.select(
    lambda e: isinstance(e, UML.Class) and e.name == "Diagram"
):
    print(diagram)
<gaphor.UML.uml.Class element 5cdae47e-7a95-11ea-a112-7f953848cf85>

Тепер, скажімо, ми хочемо створити просту (псевдо)код. Ми можемо перебирати атрибути класу та писати деякі результати.

diagram: UML.Class

def qname(element):
    return ".".join(element.qualifiedName)

diagram = next(element_factory.select(lambda e: isinstance(e, UML.Class) and e.name == "Diagram"))

print(f"class {diagram.name}({', '.join(qname(g) for g in diagram.general)}):")
for attribute in diagram.attribute:
    if attribute.typeValue:
        # Simple attribute
        print(f"    {attribute.name}: {attribute.typeValue}")
    elif attribute.type:
        # Association
        print(f"    {attribute.name}: {qname(attribute.type)}")
class Diagram(Root.Base):
    diagramType: String
    ownedPresentation: Presentation.Presentation

Щоб дізнатися, які відносини можна запитувати, перегляньте документацію modeling language. Моделі даних Gaphor були створені за допомогою мови UML.

Ви можете дізнатися більше про властивість моделі, роздрукувавши її.

print(UML.Class.ownedAttribute)
<redefine ownedAttribute[0..*]: Property = <association ownedAttribute: Property[0..*] <>-> structuredClassifier>>

У цьому випадку це повідомляє нам, що тип UML.Class.ownedAttribute є UML.Property. UML.Property.class_ встановлюється як клас власника, коли встановлено ownedAttribute. Це двонаправлене відношення.

Намалюйте схему

Ще одна приємна функція - малювання схем. На даний момент для цього потрібна функція. Ця поведінка схожа на директиву diagram directive.

from gaphor.core.modeling import Diagram
from gaphor.extensions.ipython import draw

d = next(element_factory.select(Diagram))
draw(d, format="svg")
_images/8055ed121116ba11eb0165656cbd844f45f5a3c552b336b70af5a8d920ef47b4.svg

Створіть діаграму

(Потрібен Gaphor 2.13)

Тепер давайте зробимо дещо більш вигадливе. У нас все ще є основна модель, завантажена у фабриці елементів. З цієї моделі ми можемо створити кастомну схему. З невеликою допомогою сервісу авторозмітки ми можемо зробити її читабельною схемою.

Щоб створити діаграму, ми кидаємо елементи на діаграму. Елементи на діаграмі представляють елемент у моделі. Ми також відкинемо всі асоціації на моделі. Тільки якщо обидва кінці можуть з’єднуватися, асоціація буде додана.

from gaphor.diagram.drop import drop
from gaphor.extensions.ipython import auto_layout

temp_diagram = element_factory.create(Diagram)

for name in ["Presentation", "Diagram", "Base"]:
    element = next(element_factory.select(
        lambda e: isinstance(e, UML.Class) and e.name == name
    ))
    drop(element, temp_diagram, x=0, y=0)

# Drop all assocations, see what sticks
for association in element_factory.lselect(UML.Association):
    drop(association, temp_diagram, x=0, y=0)

auto_layout(temp_diagram)

draw(temp_diagram, format="svg")
_images/505bcb5aa0beeb5e8c16f7104aba67a1cc396760e71181215c6d3655eaaf804a.svg

Діаграма не ідеальна, але ви отримуєте картину.

Оновіть модель

Оновлення моделі завжди починається з фабрики елементів: саме там створюються елементи.

Щоб створити екземпляр класу UML, ви можете:

my_class = element_factory.create(UML.Class)
my_class.name = "MyClass"

Щоб надати йому атрибут, створіть тип атрибута (UML.Property), а потім призначте значення атрибута.

my_attr = element_factory.create(UML.Property)
my_attr.name = "my_attr"
my_attr.typeValue = "string"
my_class.ownedAttribute = my_attr

Додавання його до діаграми виглядає так:

my_diagram = element_factory.create(Diagram)
drop(my_class, my_diagram, x=0, y=0)
draw(my_diagram, format="svg")
_images/e486297d7612372e3918bfc9fdf4a0e24496515a7facd1b7174041ad2ef5a25a.svg

Якщо ви збережете модель, ваші зміни збережуться:

model_filename = "../my-model.gaphor"
with open(model_filename, "w") as out:
    storage.save(out, element_factory)

Оновлення елементів

Якщо вам потрібно оновити існуючі елементи, це можна зробити, відстежуючи ідентифікатор елемента. Кожен елемент у моделі має унікальний внутрішній ідентифікатор. Знову нам потрібні імпортовані дані з Gaphor:

from pathlib import Path

from gaphor import UML
from gaphor import SysML
from gaphor.application import Session  # needed to run services
from gaphor.transaction import Transaction  # needed to make changes
from gaphor.storage import storage  # needed to save to file

Потім запустіть сервіси, якими ми будемо користуватися:

session = Session(
    services=[
        "event_manager",
        "component_registry",
        "element_factory",
        "element_dispatcher",
        "modeling_language",
    ]
)

# Get services we need.
element_factory = session.get_service("element_factory")
modeling_language = session.get_service("modeling_language")
event_manager = session.get_service("event_manager")

і завантажте модель до сеансу

with open(model_filename, encoding="utf-8") as file_obj:
    storage.load(
        file_obj,
        element_factory,
        modeling_language,
    )

# Now we query the model to get the element we want to change:
the_class = next(
    element_factory.select(
        lambda e: isinstance(e, UML.Class) and e.name == "MyClass"
    ))

Важливо, що зміни вносяться як частина «Транзакції». Тут ми знаходимо елемент із тим самим id, а потім оновлюємо вміст. Потім ми зберігаємо змінену модель у файл.

# change the name and write back into the model
with Transaction(event_manager) as ctx:
    the_class.name = "Not My Class Anymore"
    the_class.ownedAttribute[0].typeValue = "updated string"

# Write changes to file here
with open(model_filename, "w") as out:
    storage.save(out, element_factory)

Що ще

Що ще потрібно знати…

  • Gaphor підтримує похідні асоціації. Наприклад, element.owner вказує на елемент owner. Для атрибута, який буде його класом, що містить.

  • Модуль gaphor.UML.recipes містить кілька функцій для маніпулювання елементами та їхніми асоціаціями.

  • Тести для даної мови моделювання є хорошим місцем для пошуку довідкових прикладів створення та модифікації елементів.

  • Усі моделі даних описано в розділі «Мови моделювання» документів.

  • Якщо ви використовуєте консоль Gaphor, вам потрібно буде застосувати всі зміни в транзакції, інакше вони призведуть до помилки.

  • Якщо вам потрібен повний приклад генератора коду, погляньте на [Gaphor’s coder module] (https://github.com/gaphor/gaphor/blob/main/gaphor/codegen/coder.py). Цей модуль використовується для генерації коду для моделей даних, що використовуються у Gaphor.

  • Ця сторінка відображається за допомогою MyST-NB. Це насправді блокнот Юпітера!

Приклади

Розширюючи наведену вище інформацію, у наступних фрагментах показано, як створювати вимоги та інтерфейси.

Вимоги до текстових полів

txts = ['req1', 'req2', 'bob the cat']
outfile = "requirement_example.gaphor"
with Transaction(event_manager) as ctx:
    my_diagram = element_factory.create(Diagram)
    my_diagram.name= 'my diagram'
    reqPackage = element_factory.create(UML.Package)
    reqPackage.name = "Requirements"
    drop(reqPackage, my_diagram, x=0, y=0)


    for req_id,txt in enumerate(txts):
        my_class = element_factory.create(SysML.sysml.Requirement)
        my_class.name = f"{req_id}-{txt[:3]}"
        my_class.text = f"{txt}"
        my_class.externalId = f"{req_id}"

        drop(my_class, my_diagram, x=0, y=0)

    # Save the model or export diagrams.

Інтерфейси зі словників

# get interface definitions from file into this dictionary format
interfaces = {'Interface1': ['signal1:type1', 'signal2:type1', 'signal3:type1'],
              'Interface2': ['signal4:type2', 'signal5:type2', 'signal6:type2']}
outfile = 'interface_example.gaphor'
with Transaction(event_manager) as ctx:
    my_diagram = element_factory.create(UML.Diagram)
    my_diagram.name=' my diagram'
    intPackage = element_factory.create(UML.Package)
    intPackage.name = "Interfaces"
    drop(intPackage, my_diagram, x=0, y=0)


    for interface,signals in interfaces.items():
        my_class = element_factory.create(UML.uml.Interface)
        my_class.name = f"{interface}"
        for s in signals:
            my_attr = element_factory.create(UML.Property)
            name,vtype = s.split(':')
            my_attr.name = name
            my_attr.typeValue = vtype
            my_class.ownedAttribute = my_attr

        drop(my_class, my_diagram, x=0, y=0)


    # Save the model or export diagrams.

Базова перевірка

Деякі прості перевірки можна запустити за допомогою кількох невеликих функцій для вибору та оцінки елементів.

# As before assume we have a factory service and the model is loaded
# Define a function to select an element
def element_select(element):
    return isinstance(element, UML.Class)


# Define a validation rule - names must be capatalised
def rule(element):
    return element.name[0].isupper()


# Define a message to display if the element fails the validation
msg = "Class Names must be capitalised"

element = next(element_factory.select(element_select))
is_valid = rule(element)
if not is_valid:
    print(msg)

Ось інший приклад: