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.

Files changed (236) hide show
  1. {modelable-1.1.0 → modelable-1.2.0}/.gitignore +3 -0
  2. {modelable-1.1.0 → modelable-1.2.0}/PKG-INFO +1 -1
  3. {modelable-1.1.0 → modelable-1.2.0}/mypy-baseline.txt +27 -27
  4. {modelable-1.1.0 → modelable-1.2.0}/pyproject.toml +1 -1
  5. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/compile.py +118 -16
  6. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/csharp.py +7 -1
  7. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/rust.py +7 -1
  8. modelable-1.2.0/tests/test_cli_compile.py +174 -0
  9. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_csharp.py +9 -0
  10. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_rust.py +38 -1
  11. {modelable-1.1.0 → modelable-1.2.0}/uv.lock +1 -1
  12. {modelable-1.1.0 → modelable-1.2.0}/.python-version +0 -0
  13. {modelable-1.1.0 → modelable-1.2.0}/LICENSE +0 -0
  14. {modelable-1.1.0 → modelable-1.2.0}/README.md +0 -0
  15. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/__init__.py +0 -0
  16. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/__main__.py +0 -0
  17. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/_pydantic_py314_compat.py +0 -0
  18. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/cli.py +0 -0
  19. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/__init__.py +0 -0
  20. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/apicurio.py +0 -0
  21. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/codegen.py +0 -0
  22. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/common.py +0 -0
  23. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/create.py +0 -0
  24. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/diff.py +0 -0
  25. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/graph.py +0 -0
  26. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/llm.py +0 -0
  27. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/lsp.py +0 -0
  28. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/runtime.py +0 -0
  29. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/scenario.py +0 -0
  30. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/spec.py +0 -0
  31. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/sync.py +0 -0
  32. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/commands/workspace.py +0 -0
  33. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/compat/__init__.py +0 -0
  34. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/compat/checker.py +0 -0
  35. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/compat/diff.py +0 -0
  36. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/compiler/__init__.py +0 -0
  37. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/compiler/compiler.py +0 -0
  38. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/compiler/workspace.py +0 -0
  39. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/diagnostics/__init__.py +0 -0
  40. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/diagnostics/model.py +0 -0
  41. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/__init__.py +0 -0
  42. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/base.py +0 -0
  43. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/dbt_yaml.py +0 -0
  44. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/diagnostics.py +0 -0
  45. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/fhir.py +0 -0
  46. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/fhir_validator.py +0 -0
  47. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/go.py +0 -0
  48. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/grpc.py +0 -0
  49. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/java.py +0 -0
  50. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/json_schema.py +0 -0
  51. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/markdown.py +0 -0
  52. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/odcs.py +0 -0
  53. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/openlineage.py +0 -0
  54. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/openmetadata.py +0 -0
  55. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/protobuf.py +0 -0
  56. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/python.py +0 -0
  57. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/shapes.py +0 -0
  58. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/sql.py +0 -0
  59. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/targets.py +0 -0
  60. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/emitters/typescript.py +0 -0
  61. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/expressions/__init__.py +0 -0
  62. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/expressions/cel.py +0 -0
  63. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/governance/__init__.py +0 -0
  64. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/governance/checker.py +0 -0
  65. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/governance/por.py +0 -0
  66. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/grammar/__init__.py +0 -0
  67. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/grammar/modelable.lark +0 -0
  68. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/graph/__init__.py +0 -0
  69. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/graph/export.py +0 -0
  70. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/__init__.py +0 -0
  71. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/chat.py +0 -0
  72. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/config.py +0 -0
  73. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/context.py +0 -0
  74. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/engine.py +0 -0
  75. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/importers.py +0 -0
  76. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/provenance.py +0 -0
  77. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/providers.py +0 -0
  78. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/qa.py +0 -0
  79. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/recommendations.py +0 -0
  80. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/redaction.py +0 -0
  81. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/render.py +0 -0
  82. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/update_plan.py +0 -0
  83. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/llm/validation_help.py +0 -0
  84. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/__init__.py +0 -0
  85. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/__main__.py +0 -0
  86. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/code_actions.py +0 -0
  87. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/completion.py +0 -0
  88. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/definition.py +0 -0
  89. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/diagnostics.py +0 -0
  90. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/document_symbols.py +0 -0
  91. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/federation.py +0 -0
  92. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/folding.py +0 -0
  93. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/formatting.py +0 -0
  94. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/highlight.py +0 -0
  95. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/hover.py +0 -0
  96. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/inlay_hints.py +0 -0
  97. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/references.py +0 -0
  98. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/rename.py +0 -0
  99. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/semantic_tokens.py +0 -0
  100. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/server.py +0 -0
  101. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/workspace.py +0 -0
  102. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/lsp/workspace_symbols.py +0 -0
  103. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/parser/__init__.py +0 -0
  104. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/parser/ir.py +0 -0
  105. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/parser/parse.py +0 -0
  106. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/parser/transformer.py +0 -0
  107. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/parser/wire.py +0 -0
  108. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/planner/__init__.py +0 -0
  109. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/planner/lineage.py +0 -0
  110. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/planner/planner.py +0 -0
  111. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/planner/plans.py +0 -0
  112. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/py.typed +0 -0
  113. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/__init__.py +0 -0
  114. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/apicurio.py +0 -0
  115. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/base.py +0 -0
  116. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/factory.py +0 -0
  117. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/ids.py +0 -0
  118. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/index.py +0 -0
  119. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/local.py +0 -0
  120. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/oci.py +0 -0
  121. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/openlineage.py +0 -0
  122. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/resolver.py +0 -0
  123. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/schema.sql +0 -0
  124. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/registry/signature.py +0 -0
  125. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/release.py +0 -0
  126. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/runtime/__init__.py +0 -0
  127. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/runtime/adapter/__init__.py +0 -0
  128. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/runtime/adapter/base.py +0 -0
  129. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/runtime/adapter/postgres.py +0 -0
  130. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/specs/__init__.py +0 -0
  131. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/specs/tracking.py +0 -0
  132. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/validation/__init__.py +0 -0
  133. {modelable-1.1.0 → modelable-1.2.0}/src/modelable/validation/semantic.py +0 -0
  134. {modelable-1.1.0 → modelable-1.2.0}/tests/conftest.py +0 -0
  135. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/auto_projection_complex.mdl +0 -0
  136. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/billing_projection.mdl +0 -0
  137. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/breaking_changes.mdl +0 -0
  138. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/cel_error_cases.mdl +0 -0
  139. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/customer.mdl +0 -0
  140. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/federation_multi_registry/consumer.mdl +0 -0
  141. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/federation_multi_registry/provider.mdl +0 -0
  142. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/federation_multi_registry/workspace.mdl +0 -0
  143. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/fhir_patient_profile.mdl +0 -0
  144. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/governance_export_model.mdl +0 -0
  145. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/materialized_projection_chain.mdl +0 -0
  146. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/missing_header.mdl +0 -0
  147. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/multi_domain_joins.mdl +0 -0
  148. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/multi_language_target.mdl +0 -0
  149. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/no_owner.mdl +0 -0
  150. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/pii_governance.mdl +0 -0
  151. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/projection_of_projection.mdl +0 -0
  152. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/sql_and_dbt_targets.mdl +0 -0
  153. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/wire_golden/golden/protobuf/platform_widget_v1.proto +0 -0
  154. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/wire_golden/golden/rust/platform_widget_v1.rs +0 -0
  155. {modelable-1.1.0 → modelable-1.2.0}/tests/fixtures/wire_golden/wire_golden.mdl +0 -0
  156. {modelable-1.1.0 → modelable-1.2.0}/tests/helpers.py +0 -0
  157. {modelable-1.1.0 → modelable-1.2.0}/tests/test_apicurio_registry.py +0 -0
  158. {modelable-1.1.0 → modelable-1.2.0}/tests/test_apicurio_testcontainers.py +0 -0
  159. {modelable-1.1.0 → modelable-1.2.0}/tests/test_auto_projection.py +0 -0
  160. {modelable-1.1.0 → modelable-1.2.0}/tests/test_cel_validation.py +0 -0
  161. {modelable-1.1.0 → modelable-1.2.0}/tests/test_cli.py +0 -0
  162. {modelable-1.1.0 → modelable-1.2.0}/tests/test_cli_create.py +0 -0
  163. {modelable-1.1.0 → modelable-1.2.0}/tests/test_cli_inspect.py +0 -0
  164. {modelable-1.1.0 → modelable-1.2.0}/tests/test_codegen_docker_smoke.py +0 -0
  165. {modelable-1.1.0 → modelable-1.2.0}/tests/test_codegen_targets.py +0 -0
  166. {modelable-1.1.0 → modelable-1.2.0}/tests/test_compatibility.py +0 -0
  167. {modelable-1.1.0 → modelable-1.2.0}/tests/test_compiler.py +0 -0
  168. {modelable-1.1.0 → modelable-1.2.0}/tests/test_conformance_fixture.py +0 -0
  169. {modelable-1.1.0 → modelable-1.2.0}/tests/test_diagnostics.py +0 -0
  170. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_dbt_yaml.py +0 -0
  171. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_fhir.py +0 -0
  172. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_go.py +0 -0
  173. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_grpc.py +0 -0
  174. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_java.py +0 -0
  175. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_json_schema.py +0 -0
  176. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_markdown.py +0 -0
  177. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_odcs.py +0 -0
  178. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_openlineage.py +0 -0
  179. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_openmetadata.py +0 -0
  180. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_protobuf.py +0 -0
  181. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_python.py +0 -0
  182. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_sql.py +0 -0
  183. {modelable-1.1.0 → modelable-1.2.0}/tests/test_emit_typescript.py +0 -0
  184. {modelable-1.1.0 → modelable-1.2.0}/tests/test_fhir_validator.py +0 -0
  185. {modelable-1.1.0 → modelable-1.2.0}/tests/test_fixtures_regression.py +0 -0
  186. {modelable-1.1.0 → modelable-1.2.0}/tests/test_governance.py +0 -0
  187. {modelable-1.1.0 → modelable-1.2.0}/tests/test_grammar.py +0 -0
  188. {modelable-1.1.0 → modelable-1.2.0}/tests/test_graph_export.py +0 -0
  189. {modelable-1.1.0 → modelable-1.2.0}/tests/test_impact_analysis.py +0 -0
  190. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lineage.py +0 -0
  191. {modelable-1.1.0 → modelable-1.2.0}/tests/test_llm_features.py +0 -0
  192. {modelable-1.1.0 → modelable-1.2.0}/tests/test_llm_provider_integration.py +0 -0
  193. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_code_actions.py +0 -0
  194. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_completion.py +0 -0
  195. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_completion_mirrors.py +0 -0
  196. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_definition.py +0 -0
  197. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_diagnostics.py +0 -0
  198. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_document_symbols.py +0 -0
  199. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_features.py +0 -0
  200. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_federation_diagnostics.py +0 -0
  201. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_folding.py +0 -0
  202. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_formatting.py +0 -0
  203. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_highlight.py +0 -0
  204. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_hover.py +0 -0
  205. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_inlay_hints.py +0 -0
  206. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_integration.py +0 -0
  207. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_references.py +0 -0
  208. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_rename.py +0 -0
  209. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_semantic_tokens.py +0 -0
  210. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_server.py +0 -0
  211. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_workspace.py +0 -0
  212. {modelable-1.1.0 → modelable-1.2.0}/tests/test_lsp_workspace_symbols.py +0 -0
  213. {modelable-1.1.0 → modelable-1.2.0}/tests/test_mypy_baseline.py +0 -0
  214. {modelable-1.1.0 → modelable-1.2.0}/tests/test_openlineage_sync.py +0 -0
  215. {modelable-1.1.0 → modelable-1.2.0}/tests/test_openlineage_testcontainers.py +0 -0
  216. {modelable-1.1.0 → modelable-1.2.0}/tests/test_openmetadata_testcontainers.py +0 -0
  217. {modelable-1.1.0 → modelable-1.2.0}/tests/test_planner.py +0 -0
  218. {modelable-1.1.0 → modelable-1.2.0}/tests/test_por.py +0 -0
  219. {modelable-1.1.0 → modelable-1.2.0}/tests/test_postgres_adapter.py +0 -0
  220. {modelable-1.1.0 → modelable-1.2.0}/tests/test_py314_compat.py +0 -0
  221. {modelable-1.1.0 → modelable-1.2.0}/tests/test_registry.py +0 -0
  222. {modelable-1.1.0 → modelable-1.2.0}/tests/test_registry_ids.py +0 -0
  223. {modelable-1.1.0 → modelable-1.2.0}/tests/test_registry_index.py +0 -0
  224. {modelable-1.1.0 → modelable-1.2.0}/tests/test_release_metadata.py +0 -0
  225. {modelable-1.1.0 → modelable-1.2.0}/tests/test_release_workflow.py +0 -0
  226. {modelable-1.1.0 → modelable-1.2.0}/tests/test_repository_release.py +0 -0
  227. {modelable-1.1.0 → modelable-1.2.0}/tests/test_resolver.py +0 -0
  228. {modelable-1.1.0 → modelable-1.2.0}/tests/test_samples.py +0 -0
  229. {modelable-1.1.0 → modelable-1.2.0}/tests/test_semantic.py +0 -0
  230. {modelable-1.1.0 → modelable-1.2.0}/tests/test_serialization_hints.py +0 -0
  231. {modelable-1.1.0 → modelable-1.2.0}/tests/test_spec_tracking.py +0 -0
  232. {modelable-1.1.0 → modelable-1.2.0}/tests/test_transformer.py +0 -0
  233. {modelable-1.1.0 → modelable-1.2.0}/tests/test_validate_surface_detection.py +0 -0
  234. {modelable-1.1.0 → modelable-1.2.0}/tests/test_version_range_resolution.py +0 -0
  235. {modelable-1.1.0 → modelable-1.2.0}/tests/test_wire_golden.py +0 -0
  236. {modelable-1.1.0 → modelable-1.2.0}/tests/test_workspace.py +0 -0
@@ -28,3 +28,6 @@ scripts/__pycache__/
28
28
  vscode/.vscode-test/
29
29
  vscode/out/
30
30
  vscode/*.vsix
31
+
32
+ # Subagent-driven-development progress ledger (scratch state, not project output)
33
+ .superpowers/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modelable
3
- Version: 1.1.0
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:258: error: Function is missing a return type annotation [no-untyped-def]
38
- src/modelable/emitters/csharp.py:258: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
39
- src/modelable/emitters/csharp.py:272: error: Item "ProjectionField" of "FieldDef | ProjectionField" has no attribute "optional" [union-attr]
40
- src/modelable/emitters/csharp.py:272: error: Item "ProjectionField" of "FieldDef | ProjectionField" has no attribute "type" [union-attr]
41
- src/modelable/emitters/csharp.py:75: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
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:184: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
141
- src/modelable/emitters/rust.py:184: error: Missing type arguments for generic type "set" [type-arg]
142
- src/modelable/emitters/rust.py:195: error: Missing type arguments for generic type "set" [type-arg]
143
- src/modelable/emitters/rust.py:310: error: Missing type arguments for generic type "dict" [type-arg]
144
- src/modelable/emitters/rust.py:366: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
145
- src/modelable/emitters/rust.py:375: error: Missing type arguments for generic type "dict" [type-arg]
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:461: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
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:572: error: Missing type arguments for generic type "dict" [type-arg]
150
- src/modelable/emitters/rust.py:729: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
151
- src/modelable/emitters/rust.py:776: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
152
- src/modelable/emitters/rust.py:805: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
153
- src/modelable/emitters/rust.py:831: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
154
- src/modelable/emitters/rust.py:845: error: Returning Any from function declared to return "str" [no-any-return]
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:940: error: Function is missing a return type annotation [no-untyped-def]
157
- src/modelable/emitters/rust.py:940: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
158
- src/modelable/emitters/rust.py:954: error: Item "ProjectionField" of "FieldDef | ProjectionField" has no attribute "optional" [union-attr]
159
- src/modelable/emitters/rust.py:954: error: Item "ProjectionField" of "FieldDef | ProjectionField" has no attribute "type" [union-attr]
160
- src/modelable/emitters/rust.py:958: error: Function is missing a type annotation for one or more parameters [no-untyped-def]
161
- src/modelable/emitters/rust.py:958: error: Missing type arguments for generic type "dict" [type-arg]
162
- src/modelable/emitters/rust.py:966: error: Returning Any from function declared to return "dict[Any, Any]" [no-any-return]
163
- src/modelable/emitters/rust.py:971: error: Returning Any from function declared to return "dict[Any, Any]" [no-any-return]
164
- src/modelable/emitters/rust.py:975: error: Returning Any from function declared to return "dict[Any, Any]" [no-any-return]
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]
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "modelable"
7
- version = "1.1.0"
7
+ version = "1.2.0"
8
8
  description = "Compiler and language server for versioned, domain-owned data models"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -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(workspace, output)
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(workspace, output)
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(workspace, output)
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(workspace, output)
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(workspace, output)
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(workspace, output)
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(workspace, output, registry_ids=registry_ids)
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(workspace, output)
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(workspace, output)
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(workspace, output)
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(workspace, output)
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(workspace, output)
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(workspace, output)
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(workspace, output)
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(workspace, output)
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(workspace, output, dialect)
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
- return "".join(part[:1].upper() + part[1:] for part in parts) or "Generated"
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
- return "".join(part[:1].upper() + part[1:] for part in parts) or "Generated"
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::INTERNAL => "INTERNAL".to_string(),' in proj.content
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
@@ -348,7 +348,7 @@ wheels = [
348
348
 
349
349
  [[package]]
350
350
  name = "modelable"
351
- version = "1.1.0"
351
+ version = "1.2.0"
352
352
  source = { editable = "." }
353
353
  dependencies = [
354
354
  { name = "click" },