modelable 1.1.0__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of modelable might be problematic. Click here for more details.
- {modelable-1.1.0 → modelable-1.2.0}/.gitignore +3 -0
- {modelable-1.1.0 → modelable-1.2.0}/PKG-INFO +1 -1
- {modelable-1.1.0 → modelable-1.2.0}/mypy-baseline.txt +27 -27
- {modelable-1.1.0 → modelable-1.2.0}/pyproject.toml +1 -1
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/compile.py +118 -16
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/csharp.py +7 -1
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/rust.py +7 -1
- modelable-1.2.0/tests/test_cli_compile.py +174 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_csharp.py +9 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_rust.py +38 -1
- {modelable-1.1.0 → modelable-1.2.0}/uv.lock +1 -1
- {modelable-1.1.0 → modelable-1.2.0}/.python-version +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/LICENSE +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/README.md +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/__main__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/_pydantic_py314_compat.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/cli.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/apicurio.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/codegen.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/common.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/create.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/diff.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/graph.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/llm.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/lsp.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/runtime.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/scenario.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/spec.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/sync.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/workspace.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/compat/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/compat/checker.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/compat/diff.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/compiler/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/compiler/compiler.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/compiler/workspace.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/diagnostics/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/diagnostics/model.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/base.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/dbt_yaml.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/diagnostics.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/fhir.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/fhir_validator.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/go.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/grpc.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/java.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/json_schema.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/markdown.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/odcs.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/openlineage.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/openmetadata.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/protobuf.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/python.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/shapes.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/sql.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/targets.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/typescript.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/expressions/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/expressions/cel.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/governance/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/governance/checker.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/governance/por.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/grammar/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/grammar/modelable.lark +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/graph/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/graph/export.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/chat.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/config.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/context.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/engine.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/importers.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/provenance.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/providers.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/qa.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/recommendations.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/redaction.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/render.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/update_plan.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/validation_help.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/__main__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/code_actions.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/completion.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/definition.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/diagnostics.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/document_symbols.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/federation.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/folding.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/formatting.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/highlight.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/hover.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/inlay_hints.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/references.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/rename.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/semantic_tokens.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/server.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/workspace.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/workspace_symbols.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/parser/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/parser/ir.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/parser/parse.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/parser/transformer.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/parser/wire.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/planner/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/planner/lineage.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/planner/planner.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/planner/plans.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/py.typed +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/apicurio.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/base.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/factory.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/ids.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/index.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/local.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/oci.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/openlineage.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/resolver.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/schema.sql +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/signature.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/release.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/runtime/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/runtime/adapter/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/runtime/adapter/base.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/runtime/adapter/postgres.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/specs/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/specs/tracking.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/validation/__init__.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/src/modelable/validation/semantic.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/conftest.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/auto_projection_complex.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/billing_projection.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/breaking_changes.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/cel_error_cases.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/customer.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/federation_multi_registry/consumer.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/federation_multi_registry/provider.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/federation_multi_registry/workspace.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/fhir_patient_profile.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/governance_export_model.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/materialized_projection_chain.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/missing_header.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/multi_domain_joins.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/multi_language_target.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/no_owner.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/pii_governance.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/projection_of_projection.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/sql_and_dbt_targets.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/wire_golden/golden/protobuf/platform_widget_v1.proto +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/wire_golden/golden/rust/platform_widget_v1.rs +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/wire_golden/wire_golden.mdl +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/helpers.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_apicurio_registry.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_apicurio_testcontainers.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_auto_projection.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_cel_validation.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_cli.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_cli_create.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_cli_inspect.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_codegen_docker_smoke.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_codegen_targets.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_compatibility.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_compiler.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_conformance_fixture.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_diagnostics.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_dbt_yaml.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_fhir.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_go.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_grpc.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_java.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_json_schema.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_markdown.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_odcs.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_openlineage.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_openmetadata.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_protobuf.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_python.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_sql.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_typescript.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_fhir_validator.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_fixtures_regression.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_governance.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_grammar.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_graph_export.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_impact_analysis.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lineage.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_llm_features.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_llm_provider_integration.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_code_actions.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_completion.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_completion_mirrors.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_definition.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_diagnostics.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_document_symbols.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_features.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_federation_diagnostics.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_folding.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_formatting.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_highlight.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_hover.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_inlay_hints.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_integration.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_references.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_rename.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_semantic_tokens.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_server.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_workspace.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_workspace_symbols.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_mypy_baseline.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_openlineage_sync.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_openlineage_testcontainers.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_openmetadata_testcontainers.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_planner.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_por.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_postgres_adapter.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_py314_compat.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_registry.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_registry_ids.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_registry_index.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_release_metadata.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_release_workflow.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_repository_release.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_resolver.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_samples.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_semantic.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_serialization_hints.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_spec_tracking.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_transformer.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_validate_surface_detection.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_version_range_resolution.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_wire_golden.py +0 -0
- {modelable-1.1.0 → modelable-1.2.0}/tests/test_workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modelable
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Compiler and language server for versioned, domain-owned data models
|
|
5
5
|
Project-URL: Homepage, https://github.com/ktjn/modelable
|
|
6
6
|
Project-URL: Documentation, https://ktjn.github.io/modelable/
|
|
@@ -34,11 +34,11 @@ src/modelable/compiler/workspace.py:215: error: Missing type arguments for gener
|
|
|
34
34
|
src/modelable/compiler/workspace.py:223: error: Missing type arguments for generic type "tuple" [type-arg]
|
|
35
35
|
src/modelable/emitters/csharp.py:21: error: Incompatible types in assignment (expression has type "list[ProjectionVersion]", variable has type "list[ModelVersion]") [assignment]
|
|
36
36
|
src/modelable/emitters/csharp.py:23: error: Argument 3 to "_emit_projection" has incompatible type "ModelVersion"; expected "ProjectionVersion" [arg-type]
|
|
37
|
-
src/modelable/emitters/csharp.py:
|
|
38
|
-
src/modelable/emitters/csharp.py:
|
|
39
|
-
src/modelable/emitters/csharp.py:
|
|
40
|
-
src/modelable/emitters/csharp.py:
|
|
41
|
-
src/modelable/emitters/csharp.py:
|
|
37
|
+
src/modelable/emitters/csharp.py:264: error: Function is missing a return type annotation [no-untyped-def]
|
|
38
|
+
src/modelable/emitters/csharp.py:264: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
|
|
39
|
+
src/modelable/emitters/csharp.py:278: error: Item "ProjectionField" of "FieldDef | ProjectionField" has no attribute "optional" [union-attr]
|
|
40
|
+
src/modelable/emitters/csharp.py:278: error: Item "ProjectionField" of "FieldDef | ProjectionField" has no attribute "type" [union-attr]
|
|
41
|
+
src/modelable/emitters/csharp.py:81: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
|
|
42
42
|
src/modelable/emitters/dbt_yaml.py:274: error: Item "ProjectionField" of "FieldDef | ProjectionField" has no attribute "type" [union-attr]
|
|
43
43
|
src/modelable/emitters/dbt_yaml.py:40: error: Incompatible types in assignment (expression has type "list[ProjectionVersion]", variable has type "list[ModelVersion]") [assignment]
|
|
44
44
|
src/modelable/emitters/dbt_yaml.py:42: error: Argument 3 to "_emit_projection" has incompatible type "ModelVersion"; expected "ProjectionVersion" [arg-type]
|
|
@@ -137,31 +137,31 @@ src/modelable/emitters/rust.py:116: error: Missing type arguments for generic ty
|
|
|
137
137
|
src/modelable/emitters/rust.py:128: error: Incompatible types in assignment (expression has type "list[ProjectionVersion]", variable has type "list[ModelVersion]") [assignment]
|
|
138
138
|
src/modelable/emitters/rust.py:130: error: "ModelVersion" has no attribute "source" [attr-defined]
|
|
139
139
|
src/modelable/emitters/rust.py:135: error: Argument 3 to "_emit_projection" has incompatible type "ModelVersion"; expected "ProjectionVersion" [arg-type]
|
|
140
|
-
src/modelable/emitters/rust.py:
|
|
141
|
-
src/modelable/emitters/rust.py:
|
|
142
|
-
src/modelable/emitters/rust.py:
|
|
143
|
-
src/modelable/emitters/rust.py:
|
|
144
|
-
src/modelable/emitters/rust.py:
|
|
145
|
-
src/modelable/emitters/rust.py:
|
|
140
|
+
src/modelable/emitters/rust.py:190: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
|
|
141
|
+
src/modelable/emitters/rust.py:190: error: Missing type arguments for generic type "set" [type-arg]
|
|
142
|
+
src/modelable/emitters/rust.py:201: error: Missing type arguments for generic type "set" [type-arg]
|
|
143
|
+
src/modelable/emitters/rust.py:316: error: Missing type arguments for generic type "dict" [type-arg]
|
|
144
|
+
src/modelable/emitters/rust.py:372: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
|
|
145
|
+
src/modelable/emitters/rust.py:381: error: Missing type arguments for generic type "dict" [type-arg]
|
|
146
146
|
src/modelable/emitters/rust.py:41: error: Missing type arguments for generic type "dict" [type-arg]
|
|
147
|
-
src/modelable/emitters/rust.py:
|
|
147
|
+
src/modelable/emitters/rust.py:467: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
|
|
148
148
|
src/modelable/emitters/rust.py:56: error: Missing type arguments for generic type "frozenset" [type-arg]
|
|
149
|
-
src/modelable/emitters/rust.py:
|
|
150
|
-
src/modelable/emitters/rust.py:
|
|
151
|
-
src/modelable/emitters/rust.py:
|
|
152
|
-
src/modelable/emitters/rust.py:
|
|
153
|
-
src/modelable/emitters/rust.py:
|
|
154
|
-
src/modelable/emitters/rust.py:
|
|
149
|
+
src/modelable/emitters/rust.py:578: error: Missing type arguments for generic type "dict" [type-arg]
|
|
150
|
+
src/modelable/emitters/rust.py:735: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
|
|
151
|
+
src/modelable/emitters/rust.py:782: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
|
|
152
|
+
src/modelable/emitters/rust.py:811: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
|
|
153
|
+
src/modelable/emitters/rust.py:837: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
|
|
154
|
+
src/modelable/emitters/rust.py:851: error: Returning Any from function declared to return "str" [no-any-return]
|
|
155
155
|
src/modelable/emitters/rust.py:93: error: Item "dict[str, object]" of "dict[str, object] | str" has no attribute "rstrip" [union-attr]
|
|
156
|
-
src/modelable/emitters/rust.py:
|
|
157
|
-
src/modelable/emitters/rust.py:
|
|
158
|
-
src/modelable/emitters/rust.py:
|
|
159
|
-
src/modelable/emitters/rust.py:
|
|
160
|
-
src/modelable/emitters/rust.py:
|
|
161
|
-
src/modelable/emitters/rust.py:
|
|
162
|
-
src/modelable/emitters/rust.py:
|
|
163
|
-
src/modelable/emitters/rust.py:
|
|
164
|
-
src/modelable/emitters/rust.py:
|
|
156
|
+
src/modelable/emitters/rust.py:946: error: Function is missing a return type annotation [no-untyped-def]
|
|
157
|
+
src/modelable/emitters/rust.py:946: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
|
|
158
|
+
src/modelable/emitters/rust.py:960: error: Item "ProjectionField" of "FieldDef | ProjectionField" has no attribute "optional" [union-attr]
|
|
159
|
+
src/modelable/emitters/rust.py:960: error: Item "ProjectionField" of "FieldDef | ProjectionField" has no attribute "type" [union-attr]
|
|
160
|
+
src/modelable/emitters/rust.py:964: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
|
|
161
|
+
src/modelable/emitters/rust.py:964: error: Missing type arguments for generic type "dict" [type-arg]
|
|
162
|
+
src/modelable/emitters/rust.py:972: error: Returning Any from function declared to return "dict[Any, Any]" [no-any-return]
|
|
163
|
+
src/modelable/emitters/rust.py:977: error: Returning Any from function declared to return "dict[Any, Any]" [no-any-return]
|
|
164
|
+
src/modelable/emitters/rust.py:981: error: Returning Any from function declared to return "dict[Any, Any]" [no-any-return]
|
|
165
165
|
src/modelable/emitters/sql.py:196: error: Function is missing a return type annotation [no-untyped-def]
|
|
166
166
|
src/modelable/emitters/sql.py:196: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
|
|
167
167
|
src/modelable/emitters/sql.py:211: error: Function is missing a return type annotation [no-untyped-def]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import dataclasses
|
|
3
4
|
import json
|
|
4
5
|
import sys
|
|
5
6
|
from pathlib import Path
|
|
@@ -26,6 +27,7 @@ from modelable.emitters.rust import emit_rust
|
|
|
26
27
|
from modelable.emitters.sql import emit_sql
|
|
27
28
|
from modelable.emitters.targets import list_implemented_codegen_targets
|
|
28
29
|
from modelable.emitters.typescript import emit_typescript
|
|
30
|
+
from modelable.parser.ir import ArrayType, FieldType, MapType, MdlFile, NamedType, ObjectType
|
|
29
31
|
from modelable.planner.plans import write_plans
|
|
30
32
|
from modelable.registry.factory import get_registry
|
|
31
33
|
from modelable.registry.ids import allocate_registry_ids, read_lock_file, write_lock_file
|
|
@@ -39,6 +41,75 @@ _DEFAULT_OUT_DIRS: dict[str, Path] = {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
|
|
44
|
+
def _collect_named_type_names(field_type: FieldType, result: set[str]) -> None:
|
|
45
|
+
"""Recursively collect NamedType names referenced by a field type."""
|
|
46
|
+
if isinstance(field_type, NamedType):
|
|
47
|
+
result.add(field_type.name)
|
|
48
|
+
elif isinstance(field_type, ArrayType):
|
|
49
|
+
_collect_named_type_names(field_type.item, result)
|
|
50
|
+
elif isinstance(field_type, MapType):
|
|
51
|
+
_collect_named_type_names(field_type.value, result)
|
|
52
|
+
elif isinstance(field_type, ObjectType):
|
|
53
|
+
for field in field_type.fields:
|
|
54
|
+
_collect_named_type_names(field.type, result)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _domain_defining(mdl: MdlFile, name: str) -> str | None:
|
|
58
|
+
"""Return the name of the domain that defines a model or semantic type, or None."""
|
|
59
|
+
for domain in mdl.domains:
|
|
60
|
+
if name in domain.models:
|
|
61
|
+
return domain.name
|
|
62
|
+
if any(decl.name == name for decl in domain.semantic_types):
|
|
63
|
+
return domain.name
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _find_domain_scope_violations(mdl: MdlFile, requested: set[str]) -> list[str]:
|
|
68
|
+
"""Find dependencies of requested domains that resolve only outside the requested set.
|
|
69
|
+
|
|
70
|
+
Checks both NamedType field references (recursively, across arrays/maps/nested
|
|
71
|
+
objects) and projection source/join model references. Uses the full, unfiltered
|
|
72
|
+
`mdl` to resolve where a dependency actually lives, so a reference to a domain
|
|
73
|
+
that isn't part of the workspace at all is left alone (that is `validate`'s job,
|
|
74
|
+
not this filter's).
|
|
75
|
+
"""
|
|
76
|
+
violations: list[str] = []
|
|
77
|
+
for domain in mdl.domains:
|
|
78
|
+
if domain.name not in requested:
|
|
79
|
+
continue
|
|
80
|
+
for model_name, versions in domain.models.items():
|
|
81
|
+
for version in versions:
|
|
82
|
+
for field in version.fields:
|
|
83
|
+
names: set[str] = set()
|
|
84
|
+
_collect_named_type_names(field.type, names)
|
|
85
|
+
for name in names:
|
|
86
|
+
target_domain = _domain_defining(mdl, name)
|
|
87
|
+
if (
|
|
88
|
+
target_domain is not None
|
|
89
|
+
and target_domain != domain.name
|
|
90
|
+
and target_domain not in requested
|
|
91
|
+
):
|
|
92
|
+
violations.append(
|
|
93
|
+
f"{domain.name}.{model_name}@{version.version} field '{field.name}' "
|
|
94
|
+
f"references type '{name}' defined in domain '{target_domain}', "
|
|
95
|
+
"which is excluded by --domain"
|
|
96
|
+
)
|
|
97
|
+
for projection_name, proj_versions in domain.projections.items():
|
|
98
|
+
for proj_version in proj_versions:
|
|
99
|
+
ref_models = [proj_version.source.model, *(join.model for join in proj_version.joins)]
|
|
100
|
+
for ref_model in ref_models:
|
|
101
|
+
try:
|
|
102
|
+
source_domain, _ = ref_model.rsplit(".", 1)
|
|
103
|
+
except ValueError:
|
|
104
|
+
continue
|
|
105
|
+
if source_domain != domain.name and source_domain not in requested:
|
|
106
|
+
violations.append(
|
|
107
|
+
f"{domain.name}.{projection_name}@{proj_version.version} references "
|
|
108
|
+
f"'{ref_model}' in domain '{source_domain}', which is excluded by --domain"
|
|
109
|
+
)
|
|
110
|
+
return violations
|
|
111
|
+
|
|
112
|
+
|
|
42
113
|
def register_compile_commands(cli_group: click.Group) -> None:
|
|
43
114
|
cli_group.add_command(compile)
|
|
44
115
|
cli_group.add_command(docs)
|
|
@@ -78,6 +149,13 @@ def register_compile_commands(cli_group: click.Group) -> None:
|
|
|
78
149
|
is_flag=True,
|
|
79
150
|
help="Tolerate ledger entries with no matching 'registry: true' declaration instead of erroring.",
|
|
80
151
|
)
|
|
152
|
+
@click.option(
|
|
153
|
+
"--domain",
|
|
154
|
+
"domains",
|
|
155
|
+
multiple=True,
|
|
156
|
+
default=(),
|
|
157
|
+
help="Restrict compilation to the named domain(s) (repeatable). Omit to compile the whole workspace.",
|
|
158
|
+
)
|
|
81
159
|
def compile(
|
|
82
160
|
source: Path,
|
|
83
161
|
target: str,
|
|
@@ -85,10 +163,34 @@ def compile(
|
|
|
85
163
|
registry_path: str,
|
|
86
164
|
registry_ids_path: Path,
|
|
87
165
|
allow_orphaned_registry_ids: bool,
|
|
166
|
+
domains: tuple[str, ...],
|
|
88
167
|
) -> None:
|
|
89
168
|
"""Compile Modelable definitions and write the local registry index."""
|
|
90
169
|
workspace = load_workspace_or_exit(source)
|
|
91
170
|
|
|
171
|
+
emit_workspace = workspace
|
|
172
|
+
if domains:
|
|
173
|
+
known_domains = {d.name for d in workspace.mdl.domains}
|
|
174
|
+
unknown_domains = sorted(set(domains) - known_domains)
|
|
175
|
+
if unknown_domains:
|
|
176
|
+
raise click.ClickException(
|
|
177
|
+
f"Unknown --domain value(s): {', '.join(unknown_domains)}. "
|
|
178
|
+
f"Available domains: {', '.join(sorted(known_domains))}"
|
|
179
|
+
)
|
|
180
|
+
requested = set(domains)
|
|
181
|
+
violations = _find_domain_scope_violations(workspace.mdl, requested)
|
|
182
|
+
if violations:
|
|
183
|
+
raise click.ClickException(
|
|
184
|
+
"Cannot scope compilation with --domain: the requested domain(s) have "
|
|
185
|
+
"dependencies outside the requested set:\n"
|
|
186
|
+
+ "\n".join(f" - {v}" for v in violations)
|
|
187
|
+
+ "\nAdd the missing domain(s) to --domain, or narrow the requested set."
|
|
188
|
+
)
|
|
189
|
+
scoped_domains = [d for d in workspace.mdl.domains if d.name in domains]
|
|
190
|
+
emit_workspace = dataclasses.replace(
|
|
191
|
+
workspace, mdl=workspace.mdl.model_copy(update={"domains": scoped_domains})
|
|
192
|
+
)
|
|
193
|
+
|
|
92
194
|
existing_registry_ids = read_lock_file(registry_ids_path)
|
|
93
195
|
try:
|
|
94
196
|
registry_ids = allocate_registry_ids(
|
|
@@ -119,14 +221,14 @@ def compile(
|
|
|
119
221
|
output.mkdir(parents=True, exist_ok=True)
|
|
120
222
|
|
|
121
223
|
if target == "json-schema":
|
|
122
|
-
artifacts = emit_json_schema(
|
|
224
|
+
artifacts = emit_json_schema(emit_workspace, output)
|
|
123
225
|
for art in artifacts:
|
|
124
226
|
_write_artifact_text(art.path, json.dumps(art.content, indent=2, ensure_ascii=False) + "\n")
|
|
125
227
|
_print_artifact_result(art)
|
|
126
228
|
if not artifacts:
|
|
127
229
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
128
230
|
elif target == "markdown":
|
|
129
|
-
artifacts = emit_markdown(
|
|
231
|
+
artifacts = emit_markdown(emit_workspace, output)
|
|
130
232
|
for art in artifacts:
|
|
131
233
|
assert isinstance(art.content, str)
|
|
132
234
|
_write_artifact_text(art.path, art.content)
|
|
@@ -134,7 +236,7 @@ def compile(
|
|
|
134
236
|
if not artifacts:
|
|
135
237
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
136
238
|
elif target == "typescript":
|
|
137
|
-
artifacts = emit_typescript(
|
|
239
|
+
artifacts = emit_typescript(emit_workspace, output)
|
|
138
240
|
for art in artifacts:
|
|
139
241
|
assert isinstance(art.content, str)
|
|
140
242
|
_write_artifact_text(art.path, art.content)
|
|
@@ -142,7 +244,7 @@ def compile(
|
|
|
142
244
|
if not artifacts:
|
|
143
245
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
144
246
|
elif target == "csharp":
|
|
145
|
-
artifacts = emit_csharp(
|
|
247
|
+
artifacts = emit_csharp(emit_workspace, output)
|
|
146
248
|
for art in artifacts:
|
|
147
249
|
assert isinstance(art.content, str)
|
|
148
250
|
_write_artifact_text(art.path, art.content)
|
|
@@ -150,7 +252,7 @@ def compile(
|
|
|
150
252
|
if not artifacts:
|
|
151
253
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
152
254
|
elif target == "java":
|
|
153
|
-
artifacts = emit_java(
|
|
255
|
+
artifacts = emit_java(emit_workspace, output)
|
|
154
256
|
for art in artifacts:
|
|
155
257
|
assert isinstance(art.content, str)
|
|
156
258
|
_write_artifact_text(art.path, art.content)
|
|
@@ -158,7 +260,7 @@ def compile(
|
|
|
158
260
|
if not artifacts:
|
|
159
261
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
160
262
|
elif target == "python":
|
|
161
|
-
artifacts = emit_python(
|
|
263
|
+
artifacts = emit_python(emit_workspace, output)
|
|
162
264
|
for art in artifacts:
|
|
163
265
|
assert isinstance(art.content, str)
|
|
164
266
|
_write_artifact_text(art.path, art.content)
|
|
@@ -166,7 +268,7 @@ def compile(
|
|
|
166
268
|
if not artifacts:
|
|
167
269
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
168
270
|
elif target == "rust":
|
|
169
|
-
artifacts = emit_rust(
|
|
271
|
+
artifacts = emit_rust(emit_workspace, output, registry_ids=registry_ids)
|
|
170
272
|
for art in artifacts:
|
|
171
273
|
assert isinstance(art.content, str)
|
|
172
274
|
_write_artifact_text(art.path, art.content)
|
|
@@ -174,7 +276,7 @@ def compile(
|
|
|
174
276
|
if not artifacts:
|
|
175
277
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
176
278
|
elif target == "go":
|
|
177
|
-
artifacts = emit_go(
|
|
279
|
+
artifacts = emit_go(emit_workspace, output)
|
|
178
280
|
for art in artifacts:
|
|
179
281
|
assert isinstance(art.content, str)
|
|
180
282
|
_write_artifact_text(art.path, art.content)
|
|
@@ -182,7 +284,7 @@ def compile(
|
|
|
182
284
|
if not artifacts:
|
|
183
285
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
184
286
|
elif target == "dbt-yaml":
|
|
185
|
-
artifacts = emit_dbt_yaml(
|
|
287
|
+
artifacts = emit_dbt_yaml(emit_workspace, output)
|
|
186
288
|
for art in artifacts:
|
|
187
289
|
assert isinstance(art.content, str)
|
|
188
290
|
_write_artifact_text(art.path, art.content)
|
|
@@ -190,7 +292,7 @@ def compile(
|
|
|
190
292
|
if not artifacts:
|
|
191
293
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
192
294
|
elif target == "fhir-profile":
|
|
193
|
-
artifacts = emit_fhir_profile(
|
|
295
|
+
artifacts = emit_fhir_profile(emit_workspace, output)
|
|
194
296
|
for art in artifacts:
|
|
195
297
|
assert isinstance(art.content, str)
|
|
196
298
|
_write_artifact_text(art.path, art.content)
|
|
@@ -198,7 +300,7 @@ def compile(
|
|
|
198
300
|
if not artifacts:
|
|
199
301
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
200
302
|
elif target == "openmetadata":
|
|
201
|
-
artifacts = emit_openmetadata(
|
|
303
|
+
artifacts = emit_openmetadata(emit_workspace, output)
|
|
202
304
|
for art in artifacts:
|
|
203
305
|
content = (
|
|
204
306
|
json.dumps(art.content, indent=2, ensure_ascii=False) + "\n"
|
|
@@ -211,7 +313,7 @@ def compile(
|
|
|
211
313
|
if not artifacts:
|
|
212
314
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
213
315
|
elif target == "openlineage":
|
|
214
|
-
artifacts = emit_openlineage(
|
|
316
|
+
artifacts = emit_openlineage(emit_workspace, output)
|
|
215
317
|
for art in artifacts:
|
|
216
318
|
assert isinstance(art.content, dict)
|
|
217
319
|
_write_artifact_text(art.path, json.dumps(art.content, indent=2, ensure_ascii=False) + "\n")
|
|
@@ -219,7 +321,7 @@ def compile(
|
|
|
219
321
|
if not artifacts:
|
|
220
322
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
221
323
|
elif target == "odcs":
|
|
222
|
-
artifacts = emit_odcs(
|
|
324
|
+
artifacts = emit_odcs(emit_workspace, output)
|
|
223
325
|
for art in artifacts:
|
|
224
326
|
assert isinstance(art.content, str)
|
|
225
327
|
_write_artifact_text(art.path, art.content)
|
|
@@ -227,7 +329,7 @@ def compile(
|
|
|
227
329
|
if not artifacts:
|
|
228
330
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
229
331
|
elif target == "protobuf":
|
|
230
|
-
artifacts = emit_protobuf(
|
|
332
|
+
artifacts = emit_protobuf(emit_workspace, output)
|
|
231
333
|
for art in artifacts:
|
|
232
334
|
assert isinstance(art.content, str)
|
|
233
335
|
_write_artifact_text(art.path, art.content)
|
|
@@ -235,7 +337,7 @@ def compile(
|
|
|
235
337
|
if not artifacts:
|
|
236
338
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
237
339
|
elif target == "grpc":
|
|
238
|
-
artifacts = emit_grpc(
|
|
340
|
+
artifacts = emit_grpc(emit_workspace, output)
|
|
239
341
|
for art in artifacts:
|
|
240
342
|
assert isinstance(art.content, str)
|
|
241
343
|
_write_artifact_text(art.path, art.content)
|
|
@@ -244,7 +346,7 @@ def compile(
|
|
|
244
346
|
console.print("[yellow]No artifacts generated.[/yellow]")
|
|
245
347
|
elif target in ("sql-postgres", "sql-clickhouse"):
|
|
246
348
|
dialect = target.removeprefix("sql-")
|
|
247
|
-
artifacts = emit_sql(
|
|
349
|
+
artifacts = emit_sql(emit_workspace, output, dialect)
|
|
248
350
|
for art in artifacts:
|
|
249
351
|
assert isinstance(art.content, str)
|
|
250
352
|
_write_artifact_text(art.path, art.content)
|
|
@@ -30,7 +30,13 @@ def _artifact_id(domain: str, name: str, version: int) -> str:
|
|
|
30
30
|
|
|
31
31
|
def _pascalize(value: str) -> str:
|
|
32
32
|
parts = [part for part in re.split(r"[^A-Za-z0-9]+", value) if part]
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
def _title(part: str) -> str:
|
|
35
|
+
if part.isupper():
|
|
36
|
+
return part[:1] + part[1:].lower()
|
|
37
|
+
return part[:1].upper() + part[1:]
|
|
38
|
+
|
|
39
|
+
return "".join(_title(part) for part in parts) or "Generated"
|
|
34
40
|
|
|
35
41
|
|
|
36
42
|
def _namespace_name(domain: str) -> str:
|
|
@@ -167,7 +167,13 @@ def _artifact_id(domain: str, name: str, version: int) -> str:
|
|
|
167
167
|
|
|
168
168
|
def _pascalize(value: str) -> str:
|
|
169
169
|
parts = [part for part in re.split(r"[^A-Za-z0-9]+", value) if part]
|
|
170
|
-
|
|
170
|
+
|
|
171
|
+
def _title(part: str) -> str:
|
|
172
|
+
if part.isupper():
|
|
173
|
+
return part[:1] + part[1:].lower()
|
|
174
|
+
return part[:1].upper() + part[1:]
|
|
175
|
+
|
|
176
|
+
return "".join(_title(part) for part in parts) or "Generated"
|
|
171
177
|
|
|
172
178
|
|
|
173
179
|
def _snake_case(value: str) -> str:
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from click.testing import CliRunner
|
|
4
|
+
|
|
5
|
+
from modelable.cli import cli
|
|
6
|
+
|
|
7
|
+
_TWO_DOMAIN_MDL = """
|
|
8
|
+
domain logs {
|
|
9
|
+
owner: "test-team"
|
|
10
|
+
entity LogEntry @ 1 (additive) {
|
|
11
|
+
@key logId: uuid
|
|
12
|
+
message: string
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
domain nlq {
|
|
17
|
+
owner: "test-team"
|
|
18
|
+
entity Query @ 1 (additive) {
|
|
19
|
+
@key queryId: uuid
|
|
20
|
+
text: string
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_compile_domain_flag_restricts_output(tmp_path):
|
|
27
|
+
mdl = tmp_path / "workspace.mdl"
|
|
28
|
+
mdl.write_text(_TWO_DOMAIN_MDL, encoding="utf-8")
|
|
29
|
+
out = tmp_path / "dist" / "rust"
|
|
30
|
+
|
|
31
|
+
runner = CliRunner()
|
|
32
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
33
|
+
result = runner.invoke(
|
|
34
|
+
cli,
|
|
35
|
+
["compile", str(mdl), "--target", "rust", "--domain", "logs", "--out", str(out)],
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
assert result.exit_code == 0, result.output
|
|
39
|
+
assert (out / "logs" / "logs_log_entry_v1.rs").exists()
|
|
40
|
+
assert not (out / "nlq").exists()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_compile_domain_flag_is_additive(tmp_path):
|
|
44
|
+
mdl = tmp_path / "workspace.mdl"
|
|
45
|
+
mdl.write_text(_TWO_DOMAIN_MDL, encoding="utf-8")
|
|
46
|
+
out = tmp_path / "dist" / "rust"
|
|
47
|
+
|
|
48
|
+
runner = CliRunner()
|
|
49
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
50
|
+
result = runner.invoke(
|
|
51
|
+
cli,
|
|
52
|
+
[
|
|
53
|
+
"compile",
|
|
54
|
+
str(mdl),
|
|
55
|
+
"--target",
|
|
56
|
+
"rust",
|
|
57
|
+
"--domain",
|
|
58
|
+
"logs",
|
|
59
|
+
"--domain",
|
|
60
|
+
"nlq",
|
|
61
|
+
"--out",
|
|
62
|
+
str(out),
|
|
63
|
+
],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
assert result.exit_code == 0, result.output
|
|
67
|
+
assert (out / "logs" / "logs_log_entry_v1.rs").exists()
|
|
68
|
+
assert (out / "nlq" / "nlq_query_v1.rs").exists()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_compile_unknown_domain_errors_clearly(tmp_path):
|
|
72
|
+
mdl = tmp_path / "workspace.mdl"
|
|
73
|
+
mdl.write_text(_TWO_DOMAIN_MDL, encoding="utf-8")
|
|
74
|
+
out = tmp_path / "dist" / "rust"
|
|
75
|
+
|
|
76
|
+
runner = CliRunner()
|
|
77
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
78
|
+
result = runner.invoke(
|
|
79
|
+
cli,
|
|
80
|
+
["compile", str(mdl), "--target", "rust", "--domain", "bogus", "--out", str(out)],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
assert result.exit_code != 0
|
|
84
|
+
assert "bogus" in result.output
|
|
85
|
+
assert "logs" in result.output
|
|
86
|
+
assert "nlq" in result.output
|
|
87
|
+
assert not out.exists()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_compile_without_domain_flag_compiles_whole_workspace(tmp_path):
|
|
91
|
+
mdl = tmp_path / "workspace.mdl"
|
|
92
|
+
mdl.write_text(_TWO_DOMAIN_MDL, encoding="utf-8")
|
|
93
|
+
out = tmp_path / "dist" / "rust"
|
|
94
|
+
|
|
95
|
+
runner = CliRunner()
|
|
96
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
97
|
+
result = runner.invoke(
|
|
98
|
+
cli,
|
|
99
|
+
["compile", str(mdl), "--target", "rust", "--out", str(out)],
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
assert result.exit_code == 0, result.output
|
|
103
|
+
assert (out / "logs" / "logs_log_entry_v1.rs").exists()
|
|
104
|
+
assert (out / "nlq" / "nlq_query_v1.rs").exists()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
_CROSS_DOMAIN_PROJECTION_MDL = """
|
|
108
|
+
domain customer {
|
|
109
|
+
owner: "test-team"
|
|
110
|
+
entity Customer @ 1 (additive) {
|
|
111
|
+
@key customerId: uuid
|
|
112
|
+
displayName: string
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
domain billing {
|
|
117
|
+
owner: "test-team"
|
|
118
|
+
|
|
119
|
+
projection BillingCustomer @ 1
|
|
120
|
+
from customer.Customer @ 1 as c
|
|
121
|
+
{
|
|
122
|
+
customerId <- c.customerId
|
|
123
|
+
displayName <- c.displayName
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_compile_domain_flag_errors_on_excluded_projection_source(tmp_path):
|
|
130
|
+
mdl = tmp_path / "workspace.mdl"
|
|
131
|
+
mdl.write_text(_CROSS_DOMAIN_PROJECTION_MDL, encoding="utf-8")
|
|
132
|
+
out = tmp_path / "dist" / "rust"
|
|
133
|
+
|
|
134
|
+
runner = CliRunner()
|
|
135
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
136
|
+
result = runner.invoke(
|
|
137
|
+
cli,
|
|
138
|
+
["compile", str(mdl), "--target", "rust", "--domain", "billing", "--out", str(out)],
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
assert result.exit_code != 0
|
|
142
|
+
assert "customer" in result.output
|
|
143
|
+
assert "billing.BillingCustomer" in result.output
|
|
144
|
+
assert not out.exists()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_compile_domain_flag_succeeds_when_projection_source_included(tmp_path):
|
|
148
|
+
mdl = tmp_path / "workspace.mdl"
|
|
149
|
+
mdl.write_text(_CROSS_DOMAIN_PROJECTION_MDL, encoding="utf-8")
|
|
150
|
+
out = tmp_path / "dist" / "rust"
|
|
151
|
+
|
|
152
|
+
runner = CliRunner()
|
|
153
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
154
|
+
result = runner.invoke(
|
|
155
|
+
cli,
|
|
156
|
+
[
|
|
157
|
+
"compile",
|
|
158
|
+
str(mdl),
|
|
159
|
+
"--target",
|
|
160
|
+
"rust",
|
|
161
|
+
"--domain",
|
|
162
|
+
"billing",
|
|
163
|
+
"--domain",
|
|
164
|
+
"customer",
|
|
165
|
+
"--out",
|
|
166
|
+
str(out),
|
|
167
|
+
],
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
assert result.exit_code == 0, result.output
|
|
171
|
+
text = (out / "billing" / "billing_billing_customer_v1.rs").read_text(encoding="utf-8")
|
|
172
|
+
# The projection field must keep the source's real type (uuid), not degrade to a lossy String.
|
|
173
|
+
assert "pub customer_id: uuid::Uuid," in text
|
|
174
|
+
assert "pub customer_id: String," not in text
|
|
@@ -7,6 +7,15 @@ from modelable.compiler.workspace import load_workspace
|
|
|
7
7
|
from modelable.emitters.csharp import emit_csharp
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def test_pascalize_titlecases_all_uppercase_tokens():
|
|
11
|
+
from modelable.emitters.csharp import _pascalize
|
|
12
|
+
|
|
13
|
+
assert _pascalize("INTERNAL") == "Internal"
|
|
14
|
+
assert _pascalize("SERVER_CLIENT") == "ServerClient"
|
|
15
|
+
assert _pascalize("AlreadyPascalCase") == "AlreadyPascalCase"
|
|
16
|
+
assert _pascalize("camelCase") == "CamelCase"
|
|
17
|
+
|
|
18
|
+
|
|
10
19
|
def test_emit_csharp_model_and_projection(tmp_path):
|
|
11
20
|
mdl = tmp_path / "test.mdl"
|
|
12
21
|
mdl.write_text(
|
|
@@ -1169,6 +1169,43 @@ domain telecom {
|
|
|
1169
1169
|
assert " 3gpp," not in art.content
|
|
1170
1170
|
|
|
1171
1171
|
|
|
1172
|
+
def test_pascalize_titlecases_all_uppercase_tokens():
|
|
1173
|
+
from modelable.emitters.rust import _pascalize
|
|
1174
|
+
|
|
1175
|
+
assert _pascalize("INTERNAL") == "Internal"
|
|
1176
|
+
assert _pascalize("SERVER_CLIENT") == "ServerClient"
|
|
1177
|
+
assert _pascalize("AlreadyPascalCase") == "AlreadyPascalCase"
|
|
1178
|
+
assert _pascalize("camelCase") == "CamelCase"
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
def test_emit_rust_screaming_snake_case_enum_value_becomes_pascal_case(tmp_path):
|
|
1182
|
+
(tmp_path / "model.mdl").write_text(
|
|
1183
|
+
"""
|
|
1184
|
+
domain tracing {
|
|
1185
|
+
owner: "test-team"
|
|
1186
|
+
entity Span @ 1 (additive) {
|
|
1187
|
+
@key spanId: uuid
|
|
1188
|
+
spanKind: enum(INTERNAL, SERVER, CLIENT, PRODUCER, CONSUMER)
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
""",
|
|
1192
|
+
encoding="utf-8",
|
|
1193
|
+
)
|
|
1194
|
+
workspace = load_workspace(tmp_path)
|
|
1195
|
+
artifacts = emit_rust(workspace, tmp_path / "out")
|
|
1196
|
+
art = next(a for a in artifacts if a.ref == "tracing.Span@1")
|
|
1197
|
+
# Rust variant names are idiomatic PascalCase, not the raw wire casing
|
|
1198
|
+
assert "Internal," in art.content
|
|
1199
|
+
assert "Server," in art.content
|
|
1200
|
+
assert "Client," in art.content
|
|
1201
|
+
assert "Producer," in art.content
|
|
1202
|
+
assert "Consumer," in art.content
|
|
1203
|
+
assert "INTERNAL," not in art.content
|
|
1204
|
+
# The wire value is preserved via serde rename so the wire contract doesn't shift
|
|
1205
|
+
assert '#[serde(rename = "INTERNAL")]' in art.content
|
|
1206
|
+
assert '#[serde(rename = "SERVER")]' in art.content
|
|
1207
|
+
|
|
1208
|
+
|
|
1172
1209
|
def test_emit_rust_wire_rust_type_applies_to_array_element(tmp_path):
|
|
1173
1210
|
(tmp_path / "model.mdl").write_text(
|
|
1174
1211
|
"""
|
|
@@ -1444,6 +1481,6 @@ binding span-binding {
|
|
|
1444
1481
|
proj = next(a for a in artifacts if a.ref == "telemetry.SpanRow@1")
|
|
1445
1482
|
# Match arm generated for enum → String conversion
|
|
1446
1483
|
assert "span_kind: match src.span_kind {" in proj.content
|
|
1447
|
-
assert 'TelemetrySpanV1SpanKind::
|
|
1484
|
+
assert 'TelemetrySpanV1SpanKind::Internal => "INTERNAL".to_string(),' in proj.content
|
|
1448
1485
|
# Must NOT use .into() for the enum field
|
|
1449
1486
|
assert "span_kind: src.span_kind.into()," not in proj.content
|