Contribuindo para o BESSER
Este skill cobre os fluxos de trabalho processuais para contribuir com o código-fonte do BESSER. Para detalhes de arquitetura e convenções de código, consulte também o arquivo CLAUDE.md do projeto, que é carregado automaticamente no contexto.
Configuração de Desenvolvimento
git clone https://github.com/<seu-usuario>/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 # para construir a documentação
pip install -e . # instalação editável
Verifique: python tests/BUML/metamodel/structural/library/library.py
Python 3.10+ é necessário (dependência do Django 5.x).
Adicionando um Novo Gerador
Este é o tipo de contribuição mais comum. Para pular o código boilerplate, execute o scaffold incluído:
python scripts/scaffold_generator.py <nome> <caminho/para/repo/BESSER>
# ex.: python scripts/scaffold_generator.py graphql ~/code/BESSER
Isso cria um pacote stub funcional (classe geradora + template Jinja + pytest), executável imediatamente. Você ainda precisa implementar a lógica real, registrar o gerador na configuração do editor web e adicionar documentação — coberto nas etapas abaixo.
Se preferir fazer manualmente, siga estas 6 etapas:
Etapa 1: Criar o Pacote do Gerador
besser/generators/meu_gerador/
__init__.py # exportar: from .meu_gerador import MeuGerador
meu_gerador.py # a classe geradora
templates/ # Templates Jinja2
output_template.py.j2
Copie a estrutura de um gerador simples existente (ex.: python_classes/).
Etapa 2: Implementar GeneratorInterface
# besser/generators/meu_gerador/meu_gerador.py
import os
from jinja2 import Environment, FileSystemLoader
from besser.generators import GeneratorInterface
class MeuGerador(GeneratorInterface):
def __init__(self, model, output_dir: str = None):
super().__init__(model, output_dir)
def generate(self):
"""Gerar saída a partir do modelo 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("minha_saida.py")
with open(file_path, "w", encoding="utf-8") as f:
f.write(output)
Requisitos principais:
- Chame
super().__init__(model, output_dir)— isso configuraself.modeleself.output_dir. - Use
self.build_generation_path(nome_arquivo)para obter o caminho correto do arquivo de saída. - Use
self.build_generation_dir()se precisar apenas do caminho do diretório. - Os templates Jinja2 devem estar em um subdiretório
templates/junto ao gerador. - A saída deve ser determinística — o mesmo modelo produz a mesma saída todas as vezes (sem timestamps, valores aleatórios, etc.).
- Helpers reutilizáveis vão em
besser/utilities/, não no gerador.
Etapa 3: Escrever Templates Jinja2
{# besser/generators/meu_gerador/templates/output_template.py.j2 #}
# Gerado por BESSER MeuGerador
{% for cls in classes %}
class {{ cls.name }}:
{% for attr in cls.attributes %}
{{ attr.name }}: {{ attr.type.name }}
{% endfor %}
{% endfor %}
Se for gerar JSX/React, use delimitadores não padrão para evitar conflitos:
env = Environment(
loader=FileSystemLoader(templates_path),
variable_start_string="[[",
variable_end_string="]]",
)
Etapa 4: Registrar no Backend do Editor Web
Adicione seu gerador em besser/utilities/web_modeling_editor/backend/config/generators.py:
from besser.generators.meu_gerador import MeuGerador
GERADORES = {
# ... geradores existentes ...
"meu_gerador": InformacaoGerador(
generator_class=MeuGerador,
output_type="file", # "file" para arquivo único, "zip" para múltiplos arquivos
file_extension=".py", # extensão do arquivo de saída
category="minha_categoria", # rótulo de agrupamento
requires_class_diagram=True, # True se precisar de um DomainModel
),
}
Também atualize get_filename_for_generator() no mesmo arquivo se o gerador produzir um nome de arquivo específico.
Etapa 5: Escrever Testes
Crie tests/generators/meu_gerador/test_meu_gerador.py:
import os
import shutil
import pytest
from besser.BUML.metamodel.structural import (
DomainModel, Class, Property, BinaryAssociation,
Multiplicity, StringType, IntegerType,
)
from besser.generators.meu_gerador import MeuGerador
@pytest.fixture
def domain_model():
"""Construir um modelo de teste pequeno."""
prop_nome = Property(name="nome", type=StringType)
prop_idade = Property(name="idade", type=IntegerType)
pessoa = Class(name="Pessoa", attributes={prop_nome, prop_idade})
model = DomainModel(name="ModeloTeste", types={pessoa})
return model
@pytest.fixture
def output_dir(tmp_path):
return str(tmp_path)
def test_gerar_cria_arquivo_saida(domain_model, output_dir):
gen = MeuGerador(model=domain_model, output_dir=output_dir)
gen.generate()
assert os.path.exists(os.path.join(output_dir, "minha_saida.py"))
def test_gerar_contem_nome_classe(domain_model, output_dir):
gen = MeuGerador(model=domain_model, output_dir=output_dir)
gen.generate()
with open(os.path.join(output_dir, "minha_saida.py")) as f:
conteudo = f.read()
assert "Pessoa" in conteudo
assert "nome" in conteudo
assert "idade" in conteudo
Padrões de teste usados em todo o código:
- Use a fixture
tmp_pathoutmpdirpara diretórios de saída (limpos automaticamente). - Construa modelos inline pequenos como fixtures — não dependa de arquivos externos.
- Teste tanto a estrutura (arquivos existem, nomes de classe presentes) quanto o conteúdo (lógica de negócios).
- Para geradores que produzem Python importável, use
importlibpara importar e testar dinamicamente o código gerado. - Limpe com
shutil.rmtree()se não estiver usando fixtures tmp do pytest.
Etapa 6: Documentar
Adicione documentação em docs/source/generators/meu_gerador.rst:
MeuGerador
==========
O MeuGerador produz ... a partir de um modelo de domínio B-UML.
Pré-requisitos
--------------
- BESSER instalado
- ...
Uso
----
.. code-block:: python
from besser.generators.meu_gerador import MeuGerador
gen = MeuGerador(model=meu_modelo, output_dir="./saida")
gen.generate()
Saída
-----
O gerador produz um único arquivo ``minha_saida.py`` contendo ...
Adicione-o ao índice de geradores em docs/source/generators.rst.
Adicionando um Novo Metamodelo / Sub-DSL
Etapa 1: Projetar o Metamodelo
Crie novas classes em besser/BUML/metamodel/sua_dsl/:
besser/BUML/metamodel/sua_dsl/
__init__.py
sua_dsl.py # classes do metamodelo
Siga padrões existentes:
- Estenda
NamedElementouElementcomo classes base. - Use propriedades privadas com validação getter/setter.
- Mantenha nomenclatura consistente com pacotes de metamodelo existentes.
Etapa 2: Adicionar Conversores (se necessário integração com editor web)
Os conversores traduzem entre JSON do frontend e objetos B-UML:
besser/utilities/web_modeling_editor/backend/services/converters/
json_to_buml/process_sua_dsl.py # JSON → BUML
buml_to_json/sua_dsl_to_json.py # BUML → JSON
Os conversores devem ser simétricos: qualquer recurso suportado em uma direção deve funcionar na outra. Teste ida e volta: JSON → BUML → JSON deve produzir o original.
Etapa 3: Adicionar Validação
Se o metamodelo tiver restrições além do que os setters impõem:
def validar(self, raise_exception=True):
erros = []
avisos = []
# Verificar regras estruturais
if alguma_condicao_ruim:
erros.append("Mensagem de erro descritiva")
resultado = {"success": len(erros) == 0, "errors": erros, "warnings": avisos}
if raise_exception and erros:
raise ValueError("\n".join(erros))
return resultado
Etapa 4: Conectar ao Backend
- Adicione endpoint(s) em
besser/utilities/web_modeling_editor/backend/backend.py. - Adicione modelos de requisição/resposta Pydantic em
backend/models/. - Adicione rota de validação sob
/validate-diagramse aplicável.
Etapa 5: Escrever Testes
Coloque testes em tests/BUML/metamodel/sua_dsl/:
- Testes de construção de modelo válido.
- Testes de modelo inválido (espera
ValueError,TypeError). - Testes de conversor ida e volta, se aplicável.
Etapa 6: Documentar
- Documentação do metamodelo:
docs/source/buml_language/sua_dsl.rst - Adicione ao índice
docs/source/buml_language.rst.
Testes
Executar Todos os Testes
python -m pytest
Executar Testes Específicos
# Por diretório
python -m pytest tests/generators/sqlalchemy/
# Por palavra-chave
python -m pytest -k "test_django"
# Com saída verbosa
python -m pytest -v tests/BUML/metamodel/structural/
# Parar na primeira falha
python -m pytest -x
Convenções de Teste
- Arquivos de teste:
test_*.py - Funções de teste:
test_* - Use
@pytest.fixturepara modelos e diretórios reutilizáveis. - Use
tmp_pathpara diretórios de saída (limpos automaticamente pelo pytest). - Afirme conteúdo específico, não apenas a existência do arquivo.
- Teste condições de erro com
pytest.raises(ValueError).
O que Testar
Para alterações no metamodelo: validação de construção, restrições de setter, erros esperados para entrada inválida, resultados de validar().
Para geradores: existência do arquivo de saída, conteúdo chave na saída (nomes de classe, mapeamentos de relacionamento), casos de borda (modelo vazio, herança profunda, muitos-para-muitos).
Para conversores: fidelidade de ida e volta (JSON → BUML → JSON deve ser equivalente).
Construindo Documentação
cd docs
# Windows:
make.bat html
# macOS/Linux:
make html
# Visualizar:
# Abra docs/build/html/index.html no seu navegador
A documentação usa Sphinx com o tema Furo. Os arquivos fonte são reStructuredText ( .rst) em docs/source/.
Estrutura da Doc
| Caminho | Contém |
|---|---|
docs/source/buml_language/ | Documentação do metamodelo |
docs/source/generators/ | Documentação do gerador |
docs/source/web_editor.rst | Documentação da API do editor web |
docs/source/utilities/ | Documentação de utilitários |
docs/source/contributing/ | Guias do contribuidor |
docs/source/releases/ | Notas de versão |
docs/source/_static/ | Imagens e recursos compartilhados |
docs/source/img/ | Diagramas e capturas de tela |
Referências Cruzadas
Use papéis Sphinx para links cruzados:
:doc:\generators/django`` — link para outra página de documentação:mod:\besser.generators.django`` — link para a API do módulo:class:\besser.BUML.metamodel.structural.Class`` — link para a API da classe:ref:\nome-do-rotulo`` — link para uma seção rotulada
Estilo de Código
- PEP 8 com indentação de 4 espaços.
- Limite de linha de 120 caracteres (configurado em
pyproject.tomlsob[tool.pylint.'FORMAT']). - Type hints para APIs públicas.
- Docstrings PEP 257.
- Ordem de importação: biblioteca padrão, terceiros, módulos locais.
- Funções/variáveis
snake_case, classesPascalCase, constantesUPPER_CASE. - Sem imports curinga, sem re-exportações implícitas.
- Linting: pylint (sem hooks ruff/black/pre-commit atualmente configurados).
Convenções de Commit e PR
Mensagens de Commit
Use Commits Convencionais:
feat: adicionar gerador Terraform para AWS EKS
fix: corrigir análise de multiplicidade para associações 0..*
refactor: extrair helpers de template compartilhados para besser.utilities
docs: documentar uso do QiskitGenerator
test: adicionar testes de ida e volta para conversor de modelo GUI
Fluxo de Trabalho do Pull Request
- Crie um branch de tópico:
git checkout -b feature/adicionar-meu-gerador. - Faça commits focados e logicamente agrupados.
- Execute testes localmente:
python -m pytest. - Construa documentação:
cd docs && make html. - Rebase no
mastermais recente:git fetch origin && git rebase origin/master. - Faça push e abra PR contra
master. - Preencha o template do PR: descrição, testes executados, contexto extra.
- Duas aprovações de mantenedor necessárias para merge (uma aprovação suficiente após 14 dias).
Alterações Entre Repositórios (BESSER + Frontend)
Quando alterações afetam tanto o BESSER quanto o frontend do editor web:
- Implemente e comite alterações do WME no repositório WME.
- Implemente alterações do BESSER neste repositório.
- Atualize o ponteiro do submódulo:
cd besser/utilities/web_modeling_editor/frontend git fetch && git checkout <commit> cd ../../../.. git add besser/utilities/web_modeling_editor/frontend - Vincule os dois PRs para que os revisores façam merge na ordem correta.
CI / Release
- CI: Não há fluxo de trabalho automatizado de testes — os testes são executados localmente antes dos PRs.
- Release: Acionado ao criar um GitHub Release. O fluxo de trabalho
python-publish.ymlconstrói e publica no PyPI usandoPYPI_API_TOKEN. - Versão: Definida em
setup.cfgsob[metadata] version. - Notas de versão: Adicionadas como arquivos RST em
docs/source/releases/.
Armadilhas Comuns de Contribuição
- Não duplique lógica — helpers compartilhados vão em
besser/utilities/, não em geradores individuais. - Mantenha determinismo — geradores devem produzir saída idêntica para entrada idêntica.
- Mantenha conversores simétricos — se JSON → BUML suporta um recurso, BUML → JSON deve também.
- Atualize a documentação — qualquer alteração no backend provavelmente precisa de atualizações em
docs/source/. - Não mexa no submódulo do frontend a menos que explicitamente necessário. Mudanças na UI vão para o repositório WME upstream.
- Limpe recursos — sempre use
try/finallypara diretórios temporários e manipuladores de arquivo. - Teste ida e volta — especialmente para conversores (JSON → BUML → JSON deve ser identidade).