Юпітер і сценарії¶
Одним із способів роботи з моделями є використання графічного інтерфейсу. Однак ви також можете бути зацікавлені в тому, щоб отримати більше від ваших моделей, взаємодіючи з ними за допомогою скриптів 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")
Створіть діаграму¶
(Потрібен 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")
Діаграма не ідеальна, але ви отримуєте картину.
Оновіть модель¶
Оновлення моделі завжди починається з фабрики елементів: саме там створюються елементи.
Щоб створити екземпляр класу 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")
Якщо ви збережете модель, ваші зміни збережуться:
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
codermodule] (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)
Ось інший приклад: