model2data 1.16.1__tar.gz → 1.16.2__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.
Files changed (198) hide show
  1. {model2data-1.16.1/model2data.egg-info → model2data-1.16.2}/PKG-INFO +1 -1
  2. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/sql.py +24 -9
  3. {model2data-1.16.1 → model2data-1.16.2/model2data.egg-info}/PKG-INFO +1 -1
  4. {model2data-1.16.1 → model2data-1.16.2}/pyproject.toml +1 -1
  5. {model2data-1.16.1 → model2data-1.16.2}/tests/test_metrics_values.py +15 -0
  6. {model2data-1.16.1 → model2data-1.16.2}/LICENSE +0 -0
  7. {model2data-1.16.1 → model2data-1.16.2}/README.md +0 -0
  8. {model2data-1.16.1 → model2data-1.16.2}/README_PYPI.md +0 -0
  9. {model2data-1.16.1 → model2data-1.16.2}/model2data/__init__.py +0 -0
  10. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/__init__.py +0 -0
  11. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/LICENSE +0 -0
  12. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/VENDORED.md +0 -0
  13. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/__init__.py +0 -0
  14. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/_classes/__init__.py +0 -0
  15. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/_classes/base.py +0 -0
  16. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/_classes/column.py +0 -0
  17. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/_classes/enum.py +0 -0
  18. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/_classes/expression.py +0 -0
  19. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/_classes/index.py +0 -0
  20. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/_classes/note.py +0 -0
  21. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/_classes/project.py +0 -0
  22. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/_classes/reference.py +0 -0
  23. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/_classes/sticky_note.py +0 -0
  24. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/_classes/table.py +0 -0
  25. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/_classes/table_group.py +0 -0
  26. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/classes/__init__.py +0 -0
  27. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/constants.py +0 -0
  28. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/database.py +0 -0
  29. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/definitions/__init__.py +0 -0
  30. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/definitions/column.py +0 -0
  31. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/definitions/common.py +0 -0
  32. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/definitions/enum.py +0 -0
  33. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/definitions/generic.py +0 -0
  34. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/definitions/index.py +0 -0
  35. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/definitions/project.py +0 -0
  36. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/definitions/records.py +0 -0
  37. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/definitions/reference.py +0 -0
  38. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/definitions/sticky_note.py +0 -0
  39. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/definitions/table.py +0 -0
  40. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/definitions/table_group.py +0 -0
  41. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/definitions/table_partial.py +0 -0
  42. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/exceptions.py +0 -0
  43. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/parser/__init__.py +0 -0
  44. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/parser/blueprints.py +0 -0
  45. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/parser/parser.py +0 -0
  46. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/__init__.py +0 -0
  47. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/base.py +0 -0
  48. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/__init__.py +0 -0
  49. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/default/__init__.py +0 -0
  50. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/default/column.py +0 -0
  51. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/default/enum.py +0 -0
  52. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/default/expression.py +0 -0
  53. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/default/index.py +0 -0
  54. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/default/note.py +0 -0
  55. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/default/project.py +0 -0
  56. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/default/reference.py +0 -0
  57. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/default/renderer.py +0 -0
  58. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/default/sticky_note.py +0 -0
  59. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/default/table.py +0 -0
  60. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/default/table_group.py +0 -0
  61. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/dbml/default/utils.py +0 -0
  62. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/sql/__init__.py +0 -0
  63. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/sql/default/__init__.py +0 -0
  64. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/sql/default/column.py +0 -0
  65. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/sql/default/enum.py +0 -0
  66. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/sql/default/expression.py +0 -0
  67. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/sql/default/index.py +0 -0
  68. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/sql/default/note.py +0 -0
  69. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/sql/default/reference.py +0 -0
  70. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/sql/default/renderer.py +0 -0
  71. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/sql/default/table.py +0 -0
  72. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/renderer/sql/default/utils.py +0 -0
  73. {model2data-1.16.1 → model2data-1.16.2}/model2data/_vendor/pydbml/tools.py +0 -0
  74. {model2data-1.16.1 → model2data-1.16.2}/model2data/cli.py +0 -0
  75. {model2data-1.16.1 → model2data-1.16.2}/model2data/dbt/__init__.py +0 -0
  76. {model2data-1.16.1 → model2data-1.16.2}/model2data/dbt/hint_tests.py +0 -0
  77. {model2data-1.16.1 → model2data-1.16.2}/model2data/dbt/naming.py +0 -0
  78. {model2data-1.16.1 → model2data-1.16.2}/model2data/dbt/project.py +0 -0
  79. {model2data-1.16.1 → model2data-1.16.2}/model2data/dbt/templates/dbt_project.yml.jinja +0 -0
  80. {model2data-1.16.1 → model2data-1.16.2}/model2data/dbt/templates/hint_macros/model2data_hint_tests.sql +0 -0
  81. {model2data-1.16.1 → model2data-1.16.2}/model2data/dbt/templates/hint_macros/model2data_parent_tests.sql +0 -0
  82. {model2data-1.16.1 → model2data-1.16.2}/model2data/dbt/templates/hint_macros/model2data_when_tests.sql +0 -0
  83. {model2data-1.16.1 → model2data-1.16.2}/model2data/dbt/templates/history_macros/model2data_history_tests.sql +0 -0
  84. {model2data-1.16.1 → model2data-1.16.2}/model2data/dbt/templates/macros/generate_schema_name.sql +0 -0
  85. {model2data-1.16.1 → model2data-1.16.2}/model2data/dbt/templates/profiles.yml.jinja +0 -0
  86. {model2data-1.16.1 → model2data-1.16.2}/model2data/dbt/tests.py +0 -0
  87. {model2data-1.16.1 → model2data-1.16.2}/model2data/defects/__init__.py +0 -0
  88. {model2data-1.16.1 → model2data-1.16.2}/model2data/defects/apply.py +0 -0
  89. {model2data-1.16.1 → model2data-1.16.2}/model2data/defects/checks.py +0 -0
  90. {model2data-1.16.1 → model2data-1.16.2}/model2data/defects/presets.py +0 -0
  91. {model2data-1.16.1 → model2data-1.16.2}/model2data/defects/report.py +0 -0
  92. {model2data-1.16.1 → model2data-1.16.2}/model2data/generate/__init__.py +0 -0
  93. {model2data-1.16.1 → model2data-1.16.2}/model2data/generate/core.py +0 -0
  94. {model2data-1.16.1 → model2data-1.16.2}/model2data/generate/days.py +0 -0
  95. {model2data-1.16.1 → model2data-1.16.2}/model2data/generate/faker.py +0 -0
  96. {model2data-1.16.1 → model2data-1.16.2}/model2data/generate/hints.py +0 -0
  97. {model2data-1.16.1 → model2data-1.16.2}/model2data/generate/history.py +0 -0
  98. {model2data-1.16.1 → model2data-1.16.2}/model2data/generate/kinds.py +0 -0
  99. {model2data-1.16.1 → model2data-1.16.2}/model2data/generate/options.py +0 -0
  100. {model2data-1.16.1 → model2data-1.16.2}/model2data/generate/parents.py +0 -0
  101. {model2data-1.16.1 → model2data-1.16.2}/model2data/generate/relationships.py +0 -0
  102. {model2data-1.16.1 → model2data-1.16.2}/model2data/generate/timeline.py +0 -0
  103. {model2data-1.16.1 → model2data-1.16.2}/model2data/generate/when.py +0 -0
  104. {model2data-1.16.1 → model2data-1.16.2}/model2data/guide/setup.md +0 -0
  105. {model2data-1.16.1 → model2data-1.16.2}/model2data/guide/triage.md +0 -0
  106. {model2data-1.16.1 → model2data-1.16.2}/model2data/guide/tune.md +0 -0
  107. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/__init__.py +0 -0
  108. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/check.py +0 -0
  109. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/columns.py +0 -0
  110. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/dbt.py +0 -0
  111. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/expression.py +0 -0
  112. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/graph.py +0 -0
  113. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/infer.py +0 -0
  114. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/lightdash.py +0 -0
  115. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/ossie.py +0 -0
  116. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/reader.py +0 -0
  117. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/semantic.py +0 -0
  118. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/types.py +0 -0
  119. {model2data-1.16.1 → model2data-1.16.2}/model2data/metrics/values.py +0 -0
  120. {model2data-1.16.1 → model2data-1.16.2}/model2data/model/__init__.py +0 -0
  121. {model2data-1.16.1 → model2data-1.16.2}/model2data/model/_yaml.py +0 -0
  122. {model2data-1.16.1 → model2data-1.16.2}/model2data/model/dbml.py +0 -0
  123. {model2data-1.16.1 → model2data-1.16.2}/model2data/model/document.py +0 -0
  124. {model2data-1.16.1 → model2data-1.16.2}/model2data/model/dump.py +0 -0
  125. {model2data-1.16.1 → model2data-1.16.2}/model2data/model/engine.py +0 -0
  126. {model2data-1.16.1 → model2data-1.16.2}/model2data/model/errors.py +0 -0
  127. {model2data-1.16.1 → model2data-1.16.2}/model2data/model/reader.py +0 -0
  128. {model2data-1.16.1 → model2data-1.16.2}/model2data/model/types.py +0 -0
  129. {model2data-1.16.1 → model2data-1.16.2}/model2data/model/validate.py +0 -0
  130. {model2data-1.16.1 → model2data-1.16.2}/model2data/output.py +0 -0
  131. {model2data-1.16.1 → model2data-1.16.2}/model2data/parse/__init__.py +0 -0
  132. {model2data-1.16.1 → model2data-1.16.2}/model2data/parse/dbml.py +0 -0
  133. {model2data-1.16.1 → model2data-1.16.2}/model2data/spec/PUBLISHING.md +0 -0
  134. {model2data-1.16.1 → model2data-1.16.2}/model2data/spec/README.md +0 -0
  135. {model2data-1.16.1 → model2data-1.16.2}/model2data/spec/examples/coffee_webshop.metrics.yml +0 -0
  136. {model2data-1.16.1 → model2data-1.16.2}/model2data/spec/examples/coffee_webshop.model2data.yml +0 -0
  137. {model2data-1.16.1 → model2data-1.16.2}/model2data/spec/metrics/README.md +0 -0
  138. {model2data-1.16.1 → model2data-1.16.2}/model2data/spec/metrics/metrics.schema.json +0 -0
  139. {model2data-1.16.1 → model2data-1.16.2}/model2data/spec/model.schema.json +0 -0
  140. {model2data-1.16.1 → model2data-1.16.2}/model2data/utils.py +0 -0
  141. {model2data-1.16.1 → model2data-1.16.2}/model2data.egg-info/SOURCES.txt +0 -0
  142. {model2data-1.16.1 → model2data-1.16.2}/model2data.egg-info/dependency_links.txt +0 -0
  143. {model2data-1.16.1 → model2data-1.16.2}/model2data.egg-info/entry_points.txt +0 -0
  144. {model2data-1.16.1 → model2data-1.16.2}/model2data.egg-info/requires.txt +0 -0
  145. {model2data-1.16.1 → model2data-1.16.2}/model2data.egg-info/top_level.txt +0 -0
  146. {model2data-1.16.1 → model2data-1.16.2}/setup.cfg +0 -0
  147. {model2data-1.16.1 → model2data-1.16.2}/tests/test_as_of_anchor.py +0 -0
  148. {model2data-1.16.1 → model2data-1.16.2}/tests/test_cli.py +0 -0
  149. {model2data-1.16.1 → model2data-1.16.2}/tests/test_cli_model.py +0 -0
  150. {model2data-1.16.1 → model2data-1.16.2}/tests/test_column_time_hints.py +0 -0
  151. {model2data-1.16.1 → model2data-1.16.2}/tests/test_coverage_gaps.py +0 -0
  152. {model2data-1.16.1 → model2data-1.16.2}/tests/test_days.py +0 -0
  153. {model2data-1.16.1 → model2data-1.16.2}/tests/test_dbml_parser.py +0 -0
  154. {model2data-1.16.1 → model2data-1.16.2}/tests/test_dbml_parser_fuzz.py +0 -0
  155. {model2data-1.16.1 → model2data-1.16.2}/tests/test_dbt_integration.py +0 -0
  156. {model2data-1.16.1 → model2data-1.16.2}/tests/test_dbt_naming.py +0 -0
  157. {model2data-1.16.1 → model2data-1.16.2}/tests/test_dbt_project.py +0 -0
  158. {model2data-1.16.1 → model2data-1.16.2}/tests/test_dbt_tests.py +0 -0
  159. {model2data-1.16.1 → model2data-1.16.2}/tests/test_defects.py +0 -0
  160. {model2data-1.16.1 → model2data-1.16.2}/tests/test_defects_dbt.py +0 -0
  161. {model2data-1.16.1 → model2data-1.16.2}/tests/test_defects_replay.py +0 -0
  162. {model2data-1.16.1 → model2data-1.16.2}/tests/test_defects_report.py +0 -0
  163. {model2data-1.16.1 → model2data-1.16.2}/tests/test_defects_review.py +0 -0
  164. {model2data-1.16.1 → model2data-1.16.2}/tests/test_determinism.py +0 -0
  165. {model2data-1.16.1 → model2data-1.16.2}/tests/test_distributions.py +0 -0
  166. {model2data-1.16.1 → model2data-1.16.2}/tests/test_examples_model.py +0 -0
  167. {model2data-1.16.1 → model2data-1.16.2}/tests/test_faker_name_inference.py +0 -0
  168. {model2data-1.16.1 → model2data-1.16.2}/tests/test_fast_paths.py +0 -0
  169. {model2data-1.16.1 → model2data-1.16.2}/tests/test_generation.py +0 -0
  170. {model2data-1.16.1 → model2data-1.16.2}/tests/test_guide.py +0 -0
  171. {model2data-1.16.1 → model2data-1.16.2}/tests/test_hint_tests.py +0 -0
  172. {model2data-1.16.1 → model2data-1.16.2}/tests/test_history.py +0 -0
  173. {model2data-1.16.1 → model2data-1.16.2}/tests/test_issue_lines.py +0 -0
  174. {model2data-1.16.1 → model2data-1.16.2}/tests/test_lone_country.py +0 -0
  175. {model2data-1.16.1 → model2data-1.16.2}/tests/test_metrics_cli.py +0 -0
  176. {model2data-1.16.1 → model2data-1.16.2}/tests/test_metrics_dbt.py +0 -0
  177. {model2data-1.16.1 → model2data-1.16.2}/tests/test_metrics_expression.py +0 -0
  178. {model2data-1.16.1 → model2data-1.16.2}/tests/test_metrics_lightdash.py +0 -0
  179. {model2data-1.16.1 → model2data-1.16.2}/tests/test_metrics_ossie.py +0 -0
  180. {model2data-1.16.1 → model2data-1.16.2}/tests/test_metrics_reader.py +0 -0
  181. {model2data-1.16.1 → model2data-1.16.2}/tests/test_metrics_semantic.py +0 -0
  182. {model2data-1.16.1 → model2data-1.16.2}/tests/test_metrics_spec.py +0 -0
  183. {model2data-1.16.1 → model2data-1.16.2}/tests/test_model.py +0 -0
  184. {model2data-1.16.1 → model2data-1.16.2}/tests/test_model_dbml.py +0 -0
  185. {model2data-1.16.1 → model2data-1.16.2}/tests/test_model_edges.py +0 -0
  186. {model2data-1.16.1 → model2data-1.16.2}/tests/test_model_messages.py +0 -0
  187. {model2data-1.16.1 → model2data-1.16.2}/tests/test_model_vocabulary.py +0 -0
  188. {model2data-1.16.1 → model2data-1.16.2}/tests/test_options.py +0 -0
  189. {model2data-1.16.1 → model2data-1.16.2}/tests/test_parent_dates.py +0 -0
  190. {model2data-1.16.1 → model2data-1.16.2}/tests/test_release_stress.py +0 -0
  191. {model2data-1.16.1 → model2data-1.16.2}/tests/test_reproducibility.py +0 -0
  192. {model2data-1.16.1 → model2data-1.16.2}/tests/test_row_identity.py +0 -0
  193. {model2data-1.16.1 → model2data-1.16.2}/tests/test_shaping.py +0 -0
  194. {model2data-1.16.1 → model2data-1.16.2}/tests/test_spec_alignment.py +0 -0
  195. {model2data-1.16.1 → model2data-1.16.2}/tests/test_spec_schema.py +0 -0
  196. {model2data-1.16.1 → model2data-1.16.2}/tests/test_table_seeds.py +0 -0
  197. {model2data-1.16.1 → model2data-1.16.2}/tests/test_timeline.py +0 -0
  198. {model2data-1.16.1 → model2data-1.16.2}/tests/test_when.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: model2data
3
- Version: 1.16.1
3
+ Version: 1.16.2
4
4
  Summary: Turn a data model into realistic synthetic data, a runnable dbt project and metrics with known values
5
5
  Author: JB Analytica
6
6
  License-Expression: MIT
@@ -5,11 +5,11 @@ Two dialects of the same thing:
5
5
  - **warehouse SQL** (`query`, `scalar`), which the known values run on DuckDB
6
6
  and the generated dbt tests run on DuckDB or Postgres. It reads relations a
7
7
  caller names (a DuckDB table, a dbt `ref()`), joins the tables a metric's
8
- filter and time are on along their many-to-one path with `left join` (a row
9
- whose parent is missing stays, and fails any filter on the parent, as a null
10
- does), and casts every column it reads to the type its kind is read as
11
- (`model2data.metrics.columns.SQL_TYPES`), so the answer does not depend on
12
- how a CSV loader guessed the column's type.
8
+ filter is on (and, by month, its time) along their many-to-one path with
9
+ `left join` (a row whose parent is missing stays, and fails any filter on the
10
+ parent, as a null does), and casts every column it reads to the type its kind
11
+ is read as (`model2data.metrics.columns.SQL_TYPES`), so the answer does not
12
+ depend on how a CSV loader guessed the column's type.
13
13
  - **ANSI SQL for Ossie** (`ansi`), over `dataset.field` names, with no joins:
14
14
  an Ossie consumer joins along the relationships the document declares.
15
15
 
@@ -152,11 +152,15 @@ def aggregate_sql(
152
152
  class _From:
153
153
  """The `from` and `join` clauses of a simple metric, and the alias of each table in them.
154
154
 
155
+ Joins only the tables in `needed` (of `metric.joins`, in its order): a table the
156
+ query reads no column of stays out, so a parent with a duplicated key cannot repeat
157
+ the metric's rows in a query that does not need it.
158
+
155
159
  Every column the query reads is written by `column`, which also records it in `read`
156
160
  (by table key), so `columns_read` knows them from the same code that writes them.
157
161
  """
158
162
 
159
- def __init__(self, metric: ResolvedMetric, relation: Relation):
163
+ def __init__(self, metric: ResolvedMetric, relation: Relation, needed: set[str]):
160
164
  assert metric.table is not None
161
165
  self.lines = [f"from {relation(metric.table)} as t0"]
162
166
  self.alias = {metric.table: "t0"}
@@ -164,6 +168,8 @@ class _From:
164
168
  by_prefix: dict[tuple, str] = {(): "t0"}
165
169
  table_of = {"t0": metric.table}
166
170
  for table, path in metric.joins.items():
171
+ if table not in needed:
172
+ continue
167
173
  for length in range(1, len(path) + 1):
168
174
  prefix = path[:length]
169
175
  if prefix in by_prefix:
@@ -194,9 +200,18 @@ class _From:
194
200
 
195
201
 
196
202
  def _parts(
197
- semantic: SemanticModel, metric: ResolvedMetric, relation: Relation
203
+ semantic: SemanticModel, metric: ResolvedMetric, relation: Relation, by_month: bool
198
204
  ) -> tuple[str, _From]:
199
- clauses = _From(metric, relation)
205
+ # The total joins what its filter reads; by month, the time column's table as well.
206
+ needed = (
207
+ {condition.column.rpartition(".")[0] for condition in metric.where.conditions()}
208
+ if metric.where is not None
209
+ else set()
210
+ )
211
+ if by_month:
212
+ assert metric.time is not None
213
+ needed.add(metric.time.rpartition(".")[0])
214
+ clauses = _From(metric, relation, needed)
200
215
 
201
216
  def column_of(path: str) -> tuple[str, ColumnKind]:
202
217
  table, column = find_column(semantic.model, path)
@@ -217,7 +232,7 @@ def _query(
217
232
  semantic: SemanticModel, name: str, relation: Relation, by_month: bool
218
233
  ) -> tuple[str, _From]:
219
234
  metric = semantic.metrics[name]
220
- aggregate, clauses = _parts(semantic, metric, relation)
235
+ aggregate, clauses = _parts(semantic, metric, relation, by_month)
221
236
  joins = "\n".join(clauses.lines)
222
237
  if not by_month:
223
238
  return f"select {aggregate} as value\n{joins}", clauses
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: model2data
3
- Version: 1.16.1
3
+ Version: 1.16.2
4
4
  Summary: Turn a data model into realistic synthetic data, a runnable dbt project and metrics with known values
5
5
  Author: JB Analytica
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "model2data"
7
- version = "1.16.1"
7
+ version = "1.16.2"
8
8
  description = "Turn a data model into realistic synthetic data, a runnable dbt project and metrics with known values"
9
9
  readme = "README_PYPI.md"
10
10
  requires-python = ">=3.10"
@@ -180,6 +180,21 @@ def test_a_metric_without_time_has_no_months(values):
180
180
  assert values["with_undated"].by_month is None
181
181
 
182
182
 
183
+ def test_a_duplicated_parent_key_does_not_repeat_rows_in_a_total():
184
+ # Lines are dated by their order: the total joins no order, so a repeated order 1
185
+ # (as the `messy` defects preset repeats keys) cannot count lines 1 and 2 twice.
186
+ frames = _frames()
187
+ frames["orders"] = pd.concat([frames["orders"], frames["orders"].iloc[[0]]])
188
+ semantic = resolve(SHOP, load(METRICS, SHOP))
189
+ found = known_values(semantic, frames, seed=3, as_of="2025-03-01").values
190
+ assert found["lines_qty"].value == 11 # not 16, lines 1 and 2 again
191
+ assert found["lines_count"].value == 5
192
+ assert "join" not in query(semantic, "lines_qty", quote)
193
+ assert "join" in query(semantic, "lines_qty", quote, by_month=True)
194
+ # A filter on the order still needs it, and only it.
195
+ assert query(semantic, "paid_or_big", quote).count("left join") == 1
196
+
197
+
183
198
  def test_the_json_is_stable_text():
184
199
  metrics = load(
185
200
  "model2data-metrics: 0.1.0\nmodel: shop\ninfer: false\nmetrics:\n"
File without changes
File without changes
File without changes
File without changes