Mitwirken an BESSER
Diese Skill-Datei beschreibt die prozessualen Arbeitsabläufe für die Mitarbeit an der BESSER-Codebasis. Informationen zur Architektur und zu den Code-Konventionen finden Sie auch in der Datei CLAUDE.md des Projekts, die automatisch in den Kontext geladen wird.
Entwicklungseinrichtung
git clone https://github.com/<dein-benutzername>/BESSER.git
cd BESSER
python -m venv venv
# Windows: venv\Scripts\activate
# macOS/Linux: source venv/bin/activate
pip install -r requirements.txt
pip install -r docs/requirements.txt # zum Erstellen der Dokumentation
pip install -e . # editierbare Installation
Überprüfen: python tests/BUML/metamodel/structural/library/library.py
Python 3.10+ ist erforderlich (Django 5.x-Abhängigkeit).
Hinzufügen eines neuen Generators
Dies ist die häufigste Art von Beitrag. Um das Boilerplate zu überspringen, führen Sie das gebündelte Gerüst aus:
python scripts/scaffold_generator.py <name> <pfad/zu/BESSER/repo>
# z.B.: python scripts/scaffold_generator.py graphql ~/code/BESSER
Dies erstellt ein funktionsfähiges Stub-Paket (Generator-Klasse + Jinja-Template + Pytest), das sofort ausführbar ist. Sie müssen dennoch die eigentliche Logik implementieren, den Generator in der Web-Editor-Konfiguration registrieren und eine Dokumentation hinzufügen – dies wird in den folgenden Schritten behandelt.
Wenn Sie es lieber von Hand machen möchten, folgen Sie diesen 6 Schritten:
Schritt 1: Erstellen Sie das Generator-Paket
besser/generators/mein_generator/
__init__.py # Export: from .mein_generator import MeinGenerator
mein_generator.py # die Generator-Klasse
templates/ # Jinja2-Templates
ausgabe_template.py.j2
Kopieren Sie das Gerüst von einem vorhandenen einfachen Generator (z.B. python_classes/).
Schritt 2: Implementieren Sie GeneratorInterface
# besser/generators/mein_generator/mein_generator.py
import os
from jinja2 import Environment, FileSystemLoader
from besser.generators import GeneratorInterface
class MeinGenerator(GeneratorInterface):
def __init__(self, model, output_dir: str = None):
super().__init__(model, output_dir)
def generate(self):
"""Erzeuge Ausgabe aus dem B-UML-Modell."""
templates_path = os.path.join(os.path.dirname(__file__), "templates")
env = Environment(
loader=FileSystemLoader(templates_path),
trim_blocks=True,
lstrip_blocks=True,
)
template = env.get_template("ausgabe_template.py.j2")
output = template.render(
classes=self.model.get_classes(),
associations=self.model.associations,
)
file_path = self.build_generation_path("meine_ausgabe.py")
with open(file_path, "w", encoding="utf-8") as f:
f.write(output)
Wichtige Anforderungen:
- Rufen Sie
super().__init__(model, output_dir)auf – dies richtetself.modelundself.output_direin. - Verwenden Sie
self.build_generation_path(dateiname), um den korrekten Ausgabedateipfad zu erhalten. - Verwenden Sie
self.build_generation_dir(), wenn Sie nur den Verzeichnispfad benötigen. - Jinja2-Templates sollten sich in einem Unterverzeichnis
templates/neben dem Generator befinden. - Die Ausgabe muss deterministisch sein – dasselbe Modell erzeugt jedes Mal dieselbe Ausgabe (keine Zeitstempel, Zufallswerte usw.).
- Wiederverwendbare Hilfsfunktionen gehören in
besser/utilities/, nicht in den Generator.
Schritt 3: Schreiben Sie Jinja2-Templates
{# besser/generators/mein_generator/templates/ausgabe_template.py.j2 #}
# Generiert von BESSER MeinGenerator
{% for cls in classes %}
class {{ cls.name }}:
{% for attr in cls.attributes %}
{{ attr.name }}: {{ attr.type.name }}
{% endfor %}
{% endfor %}
Wenn Sie JSX/React generieren, verwenden Sie nicht standardmäßige Trennzeichen, um Konflikte zu vermeiden:
env = Environment(
loader=FileSystemLoader(templates_path),
variable_start_string="[[",
variable_end_string="]]",
)
Schritt 4: Im Web-Editor-Backend registrieren
Fügen Sie Ihren Generator zu besser/utilities/web_modeling_editor/backend/config/generators.py hinzu:
from besser.generators.mein_generator import MeinGenerator
GENERATORS = {
# ... bestehende Generatoren ...
"mein_generator": GeneratorInfo(
generator_class=MeinGenerator,
output_type="file", # "file" für einzelne Datei, "zip" für mehrere Dateien
file_extension=".py", # Dateierweiterung der Ausgabe
category="meine_kategorie", # Gruppenbezeichnung
requires_class_diagram=True, # True, wenn ein DomainModel benötigt wird
),
}
Aktualisieren Sie auch get_filename_for_generator() in derselben Datei, wenn der Generator einen bestimmten Dateinamen erzeugt.
Schritt 5: Tests schreiben
Erstellen Sie tests/generators/mein_generator/test_mein_generator.py:
import os
import shutil
import pytest
from besser.BUML.metamodel.structural import (
DomainModel, Class, Property, BinaryAssociation,
Multiplicity, StringType, IntegerType,
)
from besser.generators.mein_generator import MeinGenerator
@pytest.fixture
def domain_model():
"""Erstelle ein kleines Testmodell."""
name_prop = Property(name="name", type=StringType)
age_prop = Property(name="alter", type=IntegerType)
person = Class(name="Person", attributes={name_prop, age_prop})
model = DomainModel(name="TestModel", types={person})
return model
@pytest.fixture
def output_dir(tmp_path):
return str(tmp_path)
def test_generate_erzeugt_ausgabedatei(domain_model, output_dir):
gen = MeinGenerator(model=domain_model, output_dir=output_dir)
gen.generate()
assert os.path.exists(os.path.join(output_dir, "meine_ausgabe.py"))
def test_generate_enthält_klassennamen(domain_model, output_dir):
gen = MeinGenerator(model=domain_model, output_dir=output_dir)
gen.generate()
with open(os.path.join(output_dir, "meine_ausgabe.py")) as f:
content = f.read()
assert "Person" in content
assert "name" in content
assert "alter" in content
Testmuster, die in der gesamten Codebasis verwendet werden:
- Verwenden Sie
tmp_pathodertmpdir-Fixtures für Ausgabeverzeichnisse (werden automatisch bereinigt). - Erstellen Sie kleine Inline-Modelle als Fixtures – verlassen Sie sich nicht auf externe Dateien.
- Testen Sie sowohl die Struktur (Dateien existieren, Klassennamen vorhanden) als auch den Inhalt (Geschäftslogik).
- Verwenden Sie für Generatoren, die importierbares Python erzeugen,
importlib, um den generierten Code dynamisch zu importieren und zu testen. - Bereinigen Sie mit
shutil.rmtree(), wenn Sie keine Pytest-tmp-Fixtures verwenden.
Schritt 6: Dokumentieren
Fügen Sie Dokumentation in docs/source/generators/mein_generator.rst hinzu:
MeinGenerator
=============
Der MeinGenerator erzeugt ... aus einem B-UML-Domänenmodell.
Voraussetzungen
---------------
- BESSER installiert
- ...
Verwendung
----------
.. code-block:: python
from besser.generators.mein_generator import MeinGenerator
gen = MeinGenerator(model=mein_model, output_dir="./output")
gen.generate()
Ausgabe
-------
Der Generator erzeugt eine einzelne Datei ``meine_ausgabe.py`` mit ...
Fügen Sie es zum Generatoren-Index in docs/source/generators.rst hinzu.
Hinzufügen eines neuen Metamodells / Sub-DSL
Schritt 1: Entwerfen Sie das Metamodell
Erstellen Sie neue Klassen in besser/BUML/metamodel/ihre_dsl/:
besser/BUML/metamodel/ihre_dsl/
__init__.py
ihre_dsl.py # Metamodell-Klassen
Folgen Sie den vorhandenen Mustern:
- Erweitern Sie
NamedElementoderElementals Basisklassen. - Verwenden Sie private Eigenschaften mit Getter/Setter-Validierung.
- Halten Sie die Namensgebung konsistent mit den vorhandenen Metamodell-Paketen.
Schritt 2: Konverter hinzufügen (falls Web-Editor-Integration erforderlich)
Konverter übersetzen zwischen Frontend-JSON und B-UML-Objekten:
besser/utilities/web_modeling_editor/backend/services/converters/
json_to_buml/process_ihre_dsl.py # JSON → BUML
buml_to_json/ihre_dsl_to_json.py # BUML → JSON
Konverter müssen symmetrisch sein: Jedes in einer Richtung unterstützte Feature muss auch in der anderen funktionieren. Testen Sie Round-Trips: JSON → BUML → JSON sollte das Original ergeben.
Schritt 3: Validierung hinzufügen
Falls das Metamodell Einschränkungen hat, die über die Setter hinausgehen:
def validate(self, raise_exception=True):
errors = []
warnings = []
# Strukturelle Regeln prüfen
if some_bad_condition:
errors.append("Beschreibende Fehlermeldung")
result = {"success": len(errors) == 0, "errors": errors, "warnings": warnings}
if raise_exception and errors:
raise ValueError("\n".join(errors))
return result
Schritt 4: Ins Backend einbinden
- Fügen Sie Endpunkt(e) in
besser/utilities/web_modeling_editor/backend/backend.pyhinzu. - Fügen Sie Pydantic-Anfrage-/Antwortmodelle in
backend/models/hinzu. - Fügen Sie ggf. eine Validierungsroute unter
/validate-diagramhinzu.
Schritt 5: Tests schreiben
Platzieren Sie Tests in tests/BUML/metamodel/ihre_dsl/:
- Tests zur Konstruktion gültiger Modelle.
- Tests für ungültige Modelle (erwarten Sie
ValueError,TypeError). - Round-Trip-Konverter-Tests, falls zutreffend.
Schritt 6: Dokumentieren
- Metamodell-Dokumentation:
docs/source/buml_language/ihre_dsl.rst - Fügen Sie es zum Index
docs/source/buml_language.rsthinzu.
Testen
Alle Tests ausführen
python -m pytest
Bestimmte Tests ausführen
# Nach Verzeichnis
python -m pytest tests/generators/sqlalchemy/
# Nach Schlüsselwort
python -m pytest -k "test_django"
# Mit ausführlicher Ausgabe
python -m pytest -v tests/BUML/metamodel/structural/
# Beim ersten Fehler anhalten
python -m pytest -x
Testkonventionen
- Testdateien:
test_*.py - Testfunktionen:
test_* - Verwenden Sie
@pytest.fixturefür wiederverwendbare Modelle und Verzeichnisse. - Verwenden Sie
tmp_pathfür Ausgabeverzeichnisse (werden von Pytest automatisch bereinigt). - Überprüfen Sie bestimmte Inhalte, nicht nur die Existenz von Dateien.
- Testen Sie Fehlerbedingungen mit
pytest.raises(ValueError).
Was zu testen ist
Für Metamodell-Änderungen: Konstruktionsvalidierung, Setter-Einschränkungen, erwartete Fehler bei ungültiger Eingabe, Ergebnisse von validate().
Für Generatoren: Existenz der Ausgabedatei, Schlüsselinhalte in der Ausgabe (Klassennamen, Beziehungszuordnungen), Randfälle (leeres Modell, tiefe Vererbung, viele-zu-viele).
Für Konverter: Round-Trip-Treue (JSON → BUML → JSON sollte äquivalent sein).
Dokumentation erstellen
cd docs
# Windows:
make.bat html
# macOS/Linux:
make html
# Anzeigen:
# Öffnen Sie docs/build/html/index.html in Ihrem Browser
Die Dokumentation verwendet Sphinx mit dem Furo-Theme. Quelldateien sind reStructuredText (".rst") in docs/source/.
Dokumentationsstruktur
| Pfad | Enthält |
|---|---|
docs/source/buml_language/ | Metamodell-Dokumentation |
docs/source/generators/ | Generator-Dokumentation |
docs/source/web_editor.rst | Web-Editor-API-Dokumentation |
docs/source/utilities/ | Hilfsprogramm-Dokumentation |
docs/source/contributing/ | Mitwirkenden-Anleitungen |
docs/source/releases/ | Versionshinweise |
docs/source/_static/ | Gemeinsame Bilder und Assets |
docs/source/img/ | Diagramme und Screenshots |
Querverweise
Verwenden Sie Sphinx-Rollen für Querverweise:
:doc:\generators/django`` — Link zu einer anderen Dokumentationsseite:mod:\besser.generators.django`` — Link zur Modul-API:class:\besser.BUML.metamodel.structural.Class`` — Link zur Klassen-API:ref:\label-name`` — Link zu einem markierten Abschnitt
Code-Stil
- PEP 8 mit 4 Leerzeichen Einzug.
- 120 Zeichen Zeilenlänge (konfiguriert in
pyproject.tomlunter[tool.pylint.'FORMAT']). - Typannotationen für öffentliche APIs.
- PEP 257 Docstrings.
- Importreihenfolge: Standardbibliothek, Drittanbieter, lokale Module.
snake_casefür Funktionen/Variablen,PascalCasefür Klassen,UPPER_CASEfür Konstanten.- Keine Wildcard-Importe, keine impliziten Re-Exports.
- Linting: pylint (derzeit keine ruff/black/pre-commit-Hooks konfiguriert).
Commit- und PR-Konventionen
Commit-Nachrichten
Verwenden Sie Conventional Commits:
feat: Terraform-Generator für AWS EKS hinzugefügt
fix: Multiplizitätsparsing für 0..*-Assoziationen korrigiert
refactor: Gemeinsame Template-Helfer in besser.utilities extrahiert
docs: QiskitGenerator-Verwendung dokumentiert
test: Round-Trip-Tests für GUI-Modellkonverter hinzugefügt
Pull-Request-Workflow
- Erstellen Sie einen Themenbranch:
git checkout -b feature/füge-meinen-generator-hinzu. - Machen Sie fokussierte, logisch gruppierte Commits.
- Führen Sie lokale Tests aus:
python -m pytest. - Erstellen Sie die Dokumentation:
cd docs && make html. - Rebasen Sie auf den neuesten
master:git fetch origin && git rebase origin/master. - Pushen und öffnen Sie einen PR gegen
master. - Füllen Sie die PR-Vorlage aus: Beschreibung, ausgeführte Tests, zusätzlicher Kontext.
- Für die Zusammenführung sind zwei Maintainer-Zustimmungen erforderlich (eine Zustimmung reicht nach 14 Tagen aus).
Änderungen über Repos hinweg (BESSER + Frontend)
Wenn Änderungen sowohl BESSER als auch das Web-Editor-Frontend betreffen:
- Implementieren und committen Sie WME-Änderungen im WME-Repo.
- Implementieren Sie BESSER-Änderungen in diesem Repo.
- Aktualisieren Sie den Submodule-Zeiger:
cd besser/utilities/web_modeling_editor/frontend git fetch && git checkout <commit> cd ../../../.. git add besser/utilities/web_modeling_editor/frontend - Verlinken Sie die beiden PRs, damit die Reviewer in der richtigen Reihenfolge mergen.
CI / Release
- CI: Es gibt keinen automatisierten Test-Workflow – Tests werden vor PRs lokal ausgeführt.
- Release: Wird durch das Erstellen eines GitHub-Releases ausgelöst. Der
python-publish.yml-Workflow erstellt und veröffentlicht auf PyPI unter Verwendung vonPYPI_API_TOKEN. - Version: Definiert in
setup.cfgunter[metadata] version. - Versionshinweise: Werden als RST-Dateien in
docs/source/releases/hinzugefügt.
Häufige Fallstricke bei Beiträgen
- Logik nicht duplizieren – gemeinsame Helfer gehören in
besser/utilities/, nicht in einzelne Generatoren. - Determinismus wahren – Generatoren müssen für identische Eingabe identische Ausgabe erzeugen.
- Konverter symmetrisch halten – wenn JSON → BUML ein Feature unterstützt, muss BUML → JSON es ebenfalls unterstützen.
- Dokumentation aktualisieren – jede Backend-Änderung erfordert wahrscheinlich Aktualisierungen in
docs/source/. - Das Frontend-Submodule nicht berühren – es sei denn, es ist ausdrücklich erforderlich. UI-Änderungen gehen an das übergeordnete WME-Repo.
- Ressourcen bereinigen – immer
try/finallyfür temporäre Verzeichnisse und Dateihandles verwenden. - Round-Trips testen – insbesondere für Konverter (JSON → BUML → JSON sollte identisch sein).