BESSERへの貢献
このスキルは、BESSERコードベースへの貢献に関する手続き的なワークフローをカバーしています。アーキテクチャの詳細やコーディング規約については、プロジェクトの CLAUDE.md ファイルも参照してください。このファイルは自動的にコンテキストに読み込まれます。
開発環境のセットアップ
git clone https://github.com/<あなたのユーザー名>/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 # ドキュメントビルド用
pip install -e . # 編集可能インストール
確認: python tests/BUML/metamodel/structural/library/library.py
Python 3.10+ が必要です(Django 5.x 依存関係のため)。
新しいジェネレーターの追加
最も一般的な貢献のタイプです。ボイラープレートをスキップするには、付属のスキャフォールドを実行します。
python scripts/scaffold_generator.py <名前> <BESSERリポジトリへのパス>
# 例: python scripts/scaffold_generator.py graphql ~/code/BESSER
これにより、動作可能なスタブパッケージ(ジェネレータークラス + Jinjaテンプレート + pytest)が書き込まれ、すぐに実行可能になります。ただし、実際のロジックの実装、Webエディターの設定への登録、ドキュメントの追加は、以下の手順で行う必要があります。
手動で行う場合は、以下の6つの手順に従ってください。
ステップ1: ジェネレーターパッケージの作成
besser/generators/my_generator/
__init__.py # エクスポート: from .my_generator import MyGenerator
my_generator.py # ジェネレータークラス
templates/ # Jinja2テンプレート
output_template.py.j2
既存のシンプルなジェネレーター(例: python_classes/)からスキャフォールドをコピーします。
ステップ2: GeneratorInterface の実装
# besser/generators/my_generator/my_generator.py
import os
from jinja2 import Environment, FileSystemLoader
from besser.generators import GeneratorInterface
class MyGenerator(GeneratorInterface):
def __init__(self, model, output_dir: str = None):
super().__init__(model, output_dir)
def generate(self):
"""B-UMLモデルから出力を生成します。"""
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("output_template.py.j2")
output = template.render(
classes=self.model.get_classes(),
associations=self.model.associations,
)
file_path = self.build_generation_path("my_output.py")
with open(file_path, "w", encoding="utf-8") as f:
f.write(output)
主な要件:
super().__init__(model, output_dir)を呼び出す — これによりself.modelとself.output_dirが設定されます。self.build_generation_path(filename)を使用して正しい出力ファイルパスを取得します。- ディレクトリパスのみが必要な場合は
self.build_generation_dir()を使用します。 - Jinja2テンプレートはジェネレーターと同じディレクトリの
templates/サブディレクトリに配置します。 - 出力は決定論的である必要があります — 同じモデルは毎回同じ出力を生成します(タイムスタンプ、ランダム値などは不可)。
- 再利用可能なヘルパーは
besser/utilities/に配置し、ジェネレーター内には配置しません。
ステップ3: Jinja2テンプレートの作成
{# besser/generators/my_generator/templates/output_template.py.j2 #}
# Generated by BESSER MyGenerator
{% for cls in classes %}
class {{ cls.name }}:
{% for attr in cls.attributes %}
{{ attr.name }}: {{ attr.type.name }}
{% endfor %}
{% endfor %}
JSX/Reactを生成する場合は、競合を避けるために非標準のデリミタを使用します:
env = Environment(
loader=FileSystemLoader(templates_path),
variable_start_string="[[",
variable_end_string="]]",
)
ステップ4: Webエディターバックエンドへの登録
ジェネレーターを besser/utilities/web_modeling_editor/backend/config/generators.py に追加します:
from besser.generators.my_generator import MyGenerator
GENERATORS = {
# ... 既存のジェネレーター ...
"my_generator": GeneratorInfo(
generator_class=MyGenerator,
output_type="file", # "file"(単一ファイル)または "zip"(複数ファイル)
file_extension=".py", # 出力のファイル拡張子
category="my_category", # グループ化ラベル
requires_class_diagram=True, # DomainModelが必要な場合はTrue
),
}
ジェネレーターが特定のファイル名を生成する場合は、同じファイルの get_filename_for_generator() も更新します。
ステップ5: テストの作成
tests/generators/my_generator/test_my_generator.py を作成します:
import os
import shutil
import pytest
from besser.BUML.metamodel.structural import (
DomainModel, Class, Property, BinaryAssociation,
Multiplicity, StringType, IntegerType,
)
from besser.generators.my_generator import MyGenerator
@pytest.fixture
def domain_model():
"""小さなテストモデルを構築します。"""
name_prop = Property(name="name", type=StringType)
age_prop = Property(name="age", 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_creates_output_file(domain_model, output_dir):
gen = MyGenerator(model=domain_model, output_dir=output_dir)
gen.generate()
assert os.path.exists(os.path.join(output_dir, "my_output.py"))
def test_generate_contains_class_name(domain_model, output_dir):
gen = MyGenerator(model=domain_model, output_dir=output_dir)
gen.generate()
with open(os.path.join(output_dir, "my_output.py")) as f:
content = f.read()
assert "Person" in content
assert "name" in content
assert "age" in content
コードベース全体で使用されるテストパターン:
- 出力ディレクトリには
tmp_pathまたはtmpdirフィクスチャを使用します(自動クリーンアップ)。 - フィクスチャとして小さなインラインモデルを構築します — 外部ファイルに依存しないでください。
- 構造(ファイルが存在する、クラス名が含まれている)と内容(ビジネスロジック)の両方をテストします。
- インポート可能なPythonを生成するジェネレーターの場合は、
importlibを使用して生成されたコードを動的にインポートしてテストします。 - pytestのtmpフィクスチャを使用しない場合は、
shutil.rmtree()でクリーンアップします。
ステップ6: ドキュメントの作成
docs/source/generators/my_generator.rst にドキュメントを追加します:
MyGenerator
===========
MyGenerator は、B-UMLドメインモデルから ... を生成します。
前提条件
------------
- BESSERがインストールされていること
- ...
使用方法
-----
.. code-block:: python
from besser.generators.my_generator import MyGenerator
gen = MyGenerator(model=my_model, output_dir="./output")
gen.generate()
出力
------
このジェネレーターは、... を含む単一のファイル ``my_output.py`` を生成します。
docs/source/generators.rst のジェネレーター一覧に追加します。
新しいメタモデル/サブDSLの追加
ステップ1: メタモデルの設計
besser/BUML/metamodel/your_dsl/ に新しいクラスを作成します:
besser/BUML/metamodel/your_dsl/
__init__.py
your_dsl.py # メタモデルクラス
既存のパターンに従います:
- 基本クラスとして
NamedElementまたはElementを拡張します。 - ゲッター/セッターによる検証を行うプライベートプロパティを使用します。
- 既存のメタモデルパッケージと一貫性のある命名を維持します。
ステップ2: コンバーターの追加(Webエディター統合が必要な場合)
コンバーターは、フロントエンドのJSONとB-UMLオブジェクト間の変換を行います:
besser/utilities/web_modeling_editor/backend/services/converters/
json_to_buml/process_your_dsl.py # JSON → BUML
buml_to_json/your_dsl_to_json.py # BUML → JSON
コンバーターは対称的でなければなりません: 一方の方向でサポートされている機能は、もう一方の方向でも機能する必要があります。ラウンドトリップをテストします: JSON → BUML → JSON は元のデータと一致する必要があります。
ステップ3: バリデーションの追加
メタモデルにセッターで強制できる以上の制約がある場合:
def validate(self, raise_exception=True):
errors = []
warnings = []
# 構造ルールのチェック
if some_bad_condition:
errors.append("説明的なエラーメッセージ")
result = {"success": len(errors) == 0, "errors": errors, "warnings": warnings}
if raise_exception and errors:
raise ValueError("\n".join(errors))
return result
ステップ4: バックエンドへの組み込み
besser/utilities/web_modeling_editor/backend/backend.pyにエンドポイントを追加します。backend/models/にPydanticのリクエスト/レスポンスモデルを追加します。- 該当する場合は
/validate-diagramの下にバリデーションルートを追加します。
ステップ5: テストの作成
テストは tests/BUML/metamodel/your_dsl/ に配置します:
- 有効なモデル構築テスト。
- 無効なモデルテスト(
ValueError、TypeErrorが期待される)。 - 該当する場合はラウンドトリップコンバーターテスト。
ステップ6: ドキュメントの作成
- メタモデルのドキュメント:
docs/source/buml_language/your_dsl.rst docs/source/buml_language.rst一覧に追加します。
テスト
すべてのテストを実行
python -m pytest
特定のテストを実行
# ディレクトリ指定
python -m pytest tests/generators/sqlalchemy/
# キーワード指定
python -m pytest -k "test_django"
# 詳細出力あり
python -m pytest -v tests/BUML/metamodel/structural/
# 最初の失敗で停止
python -m pytest -x
テストの規約
- テストファイル:
test_*.py - テスト関数:
test_* - 再利用可能なモデルとディレクトリには
@pytest.fixtureを使用します。 - 出力ディレクトリには
tmp_pathを使用します(pytestが自動クリーンアップ)。 - ファイルの存在だけでなく、特定の内容をアサートします。
- エラー条件は
pytest.raises(ValueError)でテストします。
テスト対象
メタモデルの変更: 構築検証、セッター制約、無効な入力に対する期待されるエラー、validate() の結果。
ジェネレーター: 出力ファイルの存在、出力内のキーコンテンツ(クラス名、関連マッピング)、エッジケース(空のモデル、深い継承、多対多)。
コンバーター: ラウンドトリップの忠実性(JSON → BUML → JSON が同等であること)。
ドキュメントのビルド
cd docs
# Windows:
make.bat html
# macOS/Linux:
make html
# 表示:
# ブラウザで docs/build/html/index.html を開く
ドキュメントはSphinxとFuroテーマを使用しています。ソースファイルは docs/source/ にある reStructuredText(.rst)です。
ドキュメント構造
| パス | 内容 |
|---|---|
docs/source/buml_language/ | メタモデルのドキュメント |
docs/source/generators/ | ジェネレーターのドキュメント |
docs/source/web_editor.rst | WebエディターAPIドキュメント |
docs/source/utilities/ | ユーティリティのドキュメント |
docs/source/contributing/ | コントリビューターガイド |
docs/source/releases/ | リリースノート |
docs/source/_static/ | 共有画像とアセット |
docs/source/img/ | 図とスクリーンショット |
相互参照
クロスリンクにはSphinxのロールを使用します:
:doc:\generators/django`` — 別のドキュメントページへのリンク:mod:\besser.generators.django`` — モジュールAPIへのリンク:class:\besser.BUML.metamodel.structural.Class`` — クラスAPIへのリンク:ref:\label-name`` — ラベル付きセクションへのリンク
コードスタイル
- PEP 8、4スペースインデント。
- 120文字の行制限(
pyproject.tomlの[tool.pylint.'FORMAT']で設定)。 - 公開APIには型ヒント。
- PEP 257 docstring。
- インポート順: 標準ライブラリ、サードパーティ、ローカルモジュール。
- 関数/変数は
snake_case、クラスはPascalCase、定数はUPPER_CASE。 - ワイルドカードインポート、暗黙の再エクスポートは禁止。
- リンター: pylint(現在ruff/black/pre-commitフックは設定されていません)。
コミットとPRの規約
コミットメッセージ
Conventional Commits を使用します:
feat: AWS EKS用のTerraformジェネレーターを追加
fix: 0..*関連の多重度解析を修正
refactor: 共有テンプレートヘルパーをbesser.utilitiesに抽出
docs: QiskitGeneratorの使用法をドキュメント化
test: GUIモデルコンバーターのラウンドトリップテストを追加
プルリクエストのワークフロー
- トピックブランチを作成:
git checkout -b feature/add-my-generator。 - 焦点を絞った、論理的にグループ化されたコミットを行います。
- ローカルでテストを実行:
python -m pytest。 - ドキュメントをビルド:
cd docs && make html。 - 最新の
masterにリベース:git fetch origin && git rebase origin/master。 - プッシュして
masterに対してPRを開きます。 - PRテンプレートに記入: 説明、実行したテスト、追加のコンテキスト。
- マージには2人のメンテナー承認が必要です(14日後に1人の承認で十分)。
クロスリポジトリの変更(BESSER + フロントエンド)
変更がBESSERとWebエディターフロントエンドの両方に影響する場合:
- WMEリポジトリ でWMEの変更を実装してコミットします。
- このリポジトリでBESSERの変更を実装します。
- サブモジュールポインタを更新:
cd besser/utilities/web_modeling_editor/frontend git fetch && git checkout <コミット> cd ../../../.. git add besser/utilities/web_modeling_editor/frontend - 2つのPRをリンクして、レビューアが正しい順序でマージできるようにします。
CI / リリース
- CI: 自動テストワークフローはありません — テストはPR前にローカルで実行されます。
- リリース: GitHub Releaseを作成することでトリガーされます。
python-publish.ymlワークフローがビルドし、PYPI_API_TOKENを使用してPyPIに公開します。 - バージョン:
setup.cfgの[metadata] versionで定義されます。 - リリースノート:
docs/source/releases/にRSTファイルとして追加されます。
よくある貢献の落とし穴
- ロジックを重複させない — 共有ヘルパーは個々のジェネレーターではなく
besser/utilities/に配置します。 - 決定論を維持する — 同一の入力に対してジェネレーターは同一の出力を生成する必要があります。
- コンバーターを対称的に保つ — JSON → BUML が機能をサポートしている場合、BUML → JSON もサポートする必要があります。
- ドキュメントを更新する — バックエンドの変更は、ほとんどの場合
docs/source/の更新が必要です。 - 明示的に必要な場合を除き、フロントエンドサブモジュールに触れない — UIの変更は上流のWMEリポジトリに行います。
- リソースをクリーンアップする — 一時ディレクトリとファイルハンドルには常に
try/finallyを使用します。 - ラウンドトリップをテストする — 特にコンバーターの場合(JSON → BUML → JSON は同一である必要があります)。