model2data 1.10.3__tar.gz → 1.10.4__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.
- {model2data-1.10.3/model2data.egg-info → model2data-1.10.4}/PKG-INFO +1 -1
- {model2data-1.10.3 → model2data-1.10.4}/README.md +3 -3
- {model2data-1.10.3 → model2data-1.10.4}/model2data/generate/days.py +16 -3
- {model2data-1.10.3 → model2data-1.10.4}/model2data/generate/faker.py +29 -0
- {model2data-1.10.3 → model2data-1.10.4/model2data.egg-info}/PKG-INFO +1 -1
- {model2data-1.10.3 → model2data-1.10.4}/pyproject.toml +1 -1
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_cli_model.py +31 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_days.py +26 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_generation.py +64 -0
- {model2data-1.10.3 → model2data-1.10.4}/LICENSE +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/README_PYPI.md +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/LICENSE +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/VENDORED.md +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/_classes/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/_classes/base.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/_classes/column.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/_classes/enum.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/_classes/expression.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/_classes/index.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/_classes/note.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/_classes/project.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/_classes/reference.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/_classes/sticky_note.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/_classes/table.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/_classes/table_group.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/classes/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/constants.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/database.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/column.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/common.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/enum.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/generic.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/index.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/project.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/records.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/reference.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/sticky_note.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/table.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/table_group.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/table_partial.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/exceptions.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/parser/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/parser/blueprints.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/parser/parser.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/base.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/column.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/enum.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/expression.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/index.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/note.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/project.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/reference.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/renderer.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/sticky_note.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/table.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/table_group.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/utils.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/column.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/enum.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/expression.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/index.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/note.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/reference.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/renderer.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/table.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/utils.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/tools.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/cli.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/dbt/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/dbt/hint_tests.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/dbt/naming.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/dbt/project.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/dbt/templates/dbt_project.yml.jinja +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/dbt/templates/hint_macros/model2data_hint_tests.sql +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/dbt/templates/history_macros/model2data_history_tests.sql +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/dbt/templates/macros/generate_schema_name.sql +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/dbt/templates/profiles.yml.jinja +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/dbt/tests.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/defects/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/defects/apply.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/defects/checks.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/defects/presets.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/defects/report.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/generate/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/generate/core.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/generate/hints.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/generate/history.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/generate/kinds.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/generate/options.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/generate/relationships.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/generate/timeline.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/model/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/model/_yaml.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/model/dbml.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/model/document.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/model/dump.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/model/engine.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/model/errors.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/model/reader.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/model/types.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/model/validate.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/output.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/parse/__init__.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/parse/dbml.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/spec/PUBLISHING.md +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/spec/README.md +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/spec/examples/coffee_webshop.model2data.yml +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/spec/model.schema.json +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data/utils.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data.egg-info/SOURCES.txt +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data.egg-info/dependency_links.txt +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data.egg-info/entry_points.txt +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data.egg-info/requires.txt +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/model2data.egg-info/top_level.txt +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/setup.cfg +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_as_of_anchor.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_cli.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_column_time_hints.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_coverage_gaps.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_dbml_parser.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_dbml_parser_fuzz.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_dbt_integration.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_dbt_naming.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_dbt_project.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_dbt_tests.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_defects.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_defects_dbt.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_defects_replay.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_defects_report.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_defects_review.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_determinism.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_distributions.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_examples_model.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_faker_name_inference.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_fast_paths.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_hint_tests.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_history.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_issue_lines.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_lone_country.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_model.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_model_dbml.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_model_edges.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_model_messages.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_model_vocabulary.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_options.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_release_stress.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_reproducibility.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_row_identity.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_shaping.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_spec_alignment.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_spec_schema.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_table_seeds.py +0 -0
- {model2data-1.10.3 → model2data-1.10.4}/tests/test_timeline.py +0 -0
|
@@ -222,14 +222,14 @@ jobs:
|
|
|
222
222
|
```
|
|
223
223
|
|
|
224
224
|
Inputs: `files` (glob, default `**/*.model2data.yml`), `version` (default: the release the tag
|
|
225
|
-
points at, e.g. `1.10.
|
|
225
|
+
points at, e.g. `1.10.4`; a specifier such as `>=1.10,<2` also works), `python-version` (3.12).
|
|
226
226
|
|
|
227
227
|
**pre-commit**
|
|
228
228
|
|
|
229
229
|
```yaml
|
|
230
230
|
repos:
|
|
231
231
|
- repo: https://github.com/JB-Analytica/model2data
|
|
232
|
-
rev: v1.10.
|
|
232
|
+
rev: v1.10.4
|
|
233
233
|
hooks:
|
|
234
234
|
- id: model2data-validate
|
|
235
235
|
```
|
|
@@ -243,7 +243,7 @@ model2data:
|
|
|
243
243
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
244
244
|
changes: ["**/*.model2data.yml"]
|
|
245
245
|
script:
|
|
246
|
-
- pip install model2data==1.10.
|
|
246
|
+
- pip install model2data==1.10.4
|
|
247
247
|
- model2data validate --glob "**/*.model2data.yml"
|
|
248
248
|
```
|
|
249
249
|
|
|
@@ -598,17 +598,26 @@ class _Engine:
|
|
|
598
598
|
lone_country=lone,
|
|
599
599
|
)
|
|
600
600
|
if unique and pool is None and len(state):
|
|
601
|
-
values = self._avoid(values, set(state[column.name].dropna().tolist()), column)
|
|
601
|
+
values = self._avoid(values, set(state[column.name].dropna().tolist()), column, key)
|
|
602
602
|
return values
|
|
603
603
|
|
|
604
|
-
def _avoid(self, values: list, taken: set, column: ColumnDef) -> list:
|
|
604
|
+
def _avoid(self, values: list, taken: set, column: ColumnDef, key: str) -> list:
|
|
605
605
|
"""Replace values that repeat one the table already holds."""
|
|
606
606
|
taken = set(taken)
|
|
607
607
|
out = []
|
|
608
|
+
repeats = 0
|
|
608
609
|
for value in values:
|
|
609
610
|
candidate = value
|
|
610
611
|
if candidate is not None and candidate in taken:
|
|
611
|
-
if
|
|
612
|
+
if column.enum_values:
|
|
613
|
+
# A suffix would make it a value the enum does not have:
|
|
614
|
+
# take a member no row holds yet, or keep the repeat.
|
|
615
|
+
free = [m for m in column.enum_values if m not in taken]
|
|
616
|
+
if free:
|
|
617
|
+
candidate = random.choice(free)
|
|
618
|
+
else:
|
|
619
|
+
repeats += 1
|
|
620
|
+
elif isinstance(value, str):
|
|
612
621
|
counter = 1
|
|
613
622
|
while candidate in taken:
|
|
614
623
|
counter += 1
|
|
@@ -623,6 +632,10 @@ class _Engine:
|
|
|
623
632
|
if candidate is not None:
|
|
624
633
|
taken.add(candidate)
|
|
625
634
|
out.append(candidate)
|
|
635
|
+
if repeats:
|
|
636
|
+
self.warnings.append(
|
|
637
|
+
f"{key}.{column.name}: {repeats} duplicate value(s) on day {self.day}"
|
|
638
|
+
)
|
|
626
639
|
return out
|
|
627
640
|
|
|
628
641
|
# -- time ---------------------------------------------------------------
|
|
@@ -796,6 +796,8 @@ def generate_column_values(
|
|
|
796
796
|
# when it carried an explicit `null_rate`.
|
|
797
797
|
if not force_not_null and "not null" not in column.settings and "pk" not in column.settings:
|
|
798
798
|
_null_out(values, _null_fraction_for(column, row_count), column.default, row_count)
|
|
799
|
+
if ensure_unique:
|
|
800
|
+
values = _distinct_members(values, column.enum_values, unique_label)
|
|
799
801
|
return values
|
|
800
802
|
|
|
801
803
|
dtype = column.data_type.lower()
|
|
@@ -1150,6 +1152,33 @@ def _distinct_parent_values(fk_values: list, row_count: int, unique_label: str)
|
|
|
1150
1152
|
return values
|
|
1151
1153
|
|
|
1152
1154
|
|
|
1155
|
+
def _distinct_members(values: list, members: list, unique_label: str) -> list:
|
|
1156
|
+
"""A unique enum column's values, each repeated member swapped for a free one.
|
|
1157
|
+
|
|
1158
|
+
Only a repeat is drawn again, after the ordinary draw and the null pass, so
|
|
1159
|
+
a draw that already came out distinct keeps every byte (the determinism
|
|
1160
|
+
promise); one with repeats used to fail its generated dbt `unique` test.
|
|
1161
|
+
With fewer members than rows the rest stay repeated and are reported the
|
|
1162
|
+
way `_deduplicate` reports a unique column it could not keep unique.
|
|
1163
|
+
"""
|
|
1164
|
+
held = set(values)
|
|
1165
|
+
free = [member for member in members if member not in held]
|
|
1166
|
+
seen: set = set()
|
|
1167
|
+
result = []
|
|
1168
|
+
unresolved = 0
|
|
1169
|
+
for value in values:
|
|
1170
|
+
if value is not None and value in seen:
|
|
1171
|
+
if free:
|
|
1172
|
+
value = free.pop(random.randrange(len(free)))
|
|
1173
|
+
else:
|
|
1174
|
+
unresolved += 1
|
|
1175
|
+
seen.add(value)
|
|
1176
|
+
result.append(value)
|
|
1177
|
+
if unresolved:
|
|
1178
|
+
_duplicate_unique_columns.append(f"{unique_label}: {unresolved} duplicate value(s)")
|
|
1179
|
+
return result
|
|
1180
|
+
|
|
1181
|
+
|
|
1153
1182
|
# Faker's `text` reaches each word through five layers of provider methods,
|
|
1154
1183
|
# copying the locale's word list twice per sentence on the way; a text column
|
|
1155
1184
|
# spends most of a run's generation time there. `_text` and `_sentence` make
|
|
@@ -371,6 +371,37 @@ def test_validate_summary_agrees_with_its_count(tmp_path):
|
|
|
371
371
|
assert "❌ 2 of 2 model files do not conform." in both.output
|
|
372
372
|
|
|
373
373
|
|
|
374
|
+
UNIQUE_ENUM = """\
|
|
375
|
+
model2data: 0.3.0
|
|
376
|
+
name: u
|
|
377
|
+
enums:
|
|
378
|
+
plan: [free, starter, pro, enterprise]
|
|
379
|
+
tables:
|
|
380
|
+
plans:
|
|
381
|
+
columns:
|
|
382
|
+
id: {type: bigint, pk: true}
|
|
383
|
+
plan_name: {type: plan, unique: true, not_null: true}
|
|
384
|
+
run: {rows: 10, rows_per_table: {plans: ROWS}, seed: 42, as_of: 2026-01-01}
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def test_a_unique_enum_with_a_member_per_row_writes_each_member_once(in_tmp):
|
|
389
|
+
model = _write(in_tmp, "u.model2data.yml", UNIQUE_ENUM.replace("ROWS", "4"))
|
|
390
|
+
result = runner.invoke(app, ["--file", str(model)])
|
|
391
|
+
assert result.exit_code == 0, result.output
|
|
392
|
+
plans = pd.read_csv(in_tmp / "dbt_u" / "seeds" / "raw" / "plans.csv")
|
|
393
|
+
assert sorted(plans["plan_name"]) == ["enterprise", "free", "pro", "starter"]
|
|
394
|
+
assert "Unique columns left with duplicate values" not in result.output
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def test_a_unique_enum_with_too_few_members_warns_in_the_summary(in_tmp):
|
|
398
|
+
model = _write(in_tmp, "u.model2data.yml", UNIQUE_ENUM.replace("ROWS", "6"))
|
|
399
|
+
result = runner.invoke(app, ["--file", str(model)])
|
|
400
|
+
assert result.exit_code == 0, result.output
|
|
401
|
+
assert "Unique columns left with duplicate values" in result.output
|
|
402
|
+
assert "plans.plan_name: 2 duplicate value(s)" in result.output
|
|
403
|
+
|
|
404
|
+
|
|
374
405
|
def test_action_uses_setup_python_v6():
|
|
375
406
|
import yaml
|
|
376
407
|
|
|
@@ -429,6 +429,32 @@ def test_a_unique_foreign_key_runs_out_of_parents_and_says_so():
|
|
|
429
429
|
assert days[1].tables["passports"].state["person_id"].dropna().is_unique
|
|
430
430
|
|
|
431
431
|
|
|
432
|
+
def test_a_unique_enum_takes_free_members_across_days_and_says_when_none_are_left():
|
|
433
|
+
members = ["a", "b", "c", "d", "e", "f"]
|
|
434
|
+
data = {
|
|
435
|
+
"model2data": "0.2.0",
|
|
436
|
+
"enums": {"slot": members},
|
|
437
|
+
"tables": {
|
|
438
|
+
"slots": {
|
|
439
|
+
"incremental": {"new_per_day": 2},
|
|
440
|
+
"columns": {
|
|
441
|
+
"id": {"type": "bigint", "pk": True},
|
|
442
|
+
"name": {"type": "slot", "unique": True, "not_null": True},
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
}
|
|
447
|
+
days = generate_days(from_dict(data), 2, base_rows=3, seed=4, as_of=AS_OF)
|
|
448
|
+
# Never a suffixed value the enum does not have.
|
|
449
|
+
for day in days:
|
|
450
|
+
assert set(day.tables["slots"].state["name"]) <= set(members)
|
|
451
|
+
# Day 1 still has three free members for its two rows.
|
|
452
|
+
assert days[1].tables["slots"].state["name"].is_unique
|
|
453
|
+
assert not days[1].warnings
|
|
454
|
+
# Day 2 has one free member for two rows: one repeat, said.
|
|
455
|
+
assert days[2].warnings == ["slots.name: 1 duplicate value(s) on day 2"]
|
|
456
|
+
|
|
457
|
+
|
|
432
458
|
def test_the_model_roundtrips_with_incremental():
|
|
433
459
|
model = load(DAILY)
|
|
434
460
|
assert model.tables["orders"].incremental == Incremental(
|
|
@@ -519,6 +519,70 @@ def test_unique_column_with_room_reports_nothing():
|
|
|
519
519
|
assert get_duplicate_unique_columns() == []
|
|
520
520
|
|
|
521
521
|
|
|
522
|
+
PLANS = ["free", "starter", "pro", "enterprise"]
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _plans_table(settings: set[str]) -> dict[str, TableDef]:
|
|
526
|
+
return {
|
|
527
|
+
"plans": TableDef(
|
|
528
|
+
name="plans",
|
|
529
|
+
columns=[
|
|
530
|
+
ColumnDef("id", "bigint", {"pk"}),
|
|
531
|
+
ColumnDef("plan_name", "plan", settings, enum_values=PLANS),
|
|
532
|
+
],
|
|
533
|
+
)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def test_unique_enum_with_a_member_per_row_takes_each_member_once():
|
|
538
|
+
# The enum has exactly as many members as the table has rows: a draw
|
|
539
|
+
# without replacement, so every member appears once and none twice.
|
|
540
|
+
for seed in range(20):
|
|
541
|
+
df = generate_data_from_dbml(
|
|
542
|
+
_plans_table({"unique", "not null"}), [], base_rows=4, seed=seed
|
|
543
|
+
)["plans"]
|
|
544
|
+
assert sorted(df["plan_name"]) == sorted(PLANS), seed
|
|
545
|
+
assert get_duplicate_unique_columns() == []
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def test_unique_enum_with_more_members_than_rows_stays_unique():
|
|
549
|
+
df = generate_data_from_dbml(_plans_table({"unique", "not null"}), [], base_rows=3, seed=1)[
|
|
550
|
+
"plans"
|
|
551
|
+
]
|
|
552
|
+
assert df["plan_name"].is_unique
|
|
553
|
+
assert set(df["plan_name"]) <= set(PLANS)
|
|
554
|
+
assert get_duplicate_unique_columns() == []
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def test_unique_enum_with_fewer_members_than_rows_is_reported():
|
|
558
|
+
df = generate_data_from_dbml(_plans_table({"unique", "not null"}), [], base_rows=6, seed=1)[
|
|
559
|
+
"plans"
|
|
560
|
+
]
|
|
561
|
+
# Every member is used before any repeats, and the two repeats are said.
|
|
562
|
+
assert set(df["plan_name"]) == set(PLANS)
|
|
563
|
+
assert get_duplicate_unique_columns() == ["plans.plan_name: 2 duplicate value(s)"]
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def test_unique_enum_that_draws_distinct_keeps_the_bytes_of_the_plain_draw():
|
|
567
|
+
# The determinism promise: only a repeat is drawn again, so a seed whose
|
|
568
|
+
# ordinary draw already came out distinct gives the same table as before.
|
|
569
|
+
kept = 0
|
|
570
|
+
for seed in range(40):
|
|
571
|
+
plain = generate_data_from_dbml(_plans_table({"not null"}), [], base_rows=3, seed=seed)
|
|
572
|
+
unique = generate_data_from_dbml(
|
|
573
|
+
_plans_table({"unique", "not null"}), [], base_rows=3, seed=seed
|
|
574
|
+
)
|
|
575
|
+
if plain["plans"]["plan_name"].is_unique:
|
|
576
|
+
kept += 1
|
|
577
|
+
assert plain["plans"].equals(unique["plans"]), seed
|
|
578
|
+
assert kept
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def test_nullable_unique_enum_keeps_its_non_null_values_distinct():
|
|
582
|
+
df = generate_data_from_dbml(_plans_table({"unique"}), [], base_rows=4, seed=2)["plans"]
|
|
583
|
+
assert df["plan_name"].dropna().is_unique
|
|
584
|
+
|
|
585
|
+
|
|
522
586
|
def test_resolvable_composite_key_reports_nothing():
|
|
523
587
|
# The mirror of the test above: when the dedup pass genuinely succeeds,
|
|
524
588
|
# the warning must stay silent (no false positives).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/sticky_note.py
RENAMED
|
File without changes
|
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/table_group.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/definitions/table_partial.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/__init__.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/column.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/enum.py
RENAMED
|
File without changes
|
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/index.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/note.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/project.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/reference.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/renderer.py
RENAMED
|
File without changes
|
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/table.py
RENAMED
|
File without changes
|
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/dbml/default/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/__init__.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/column.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/enum.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/expression.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/index.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/note.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/reference.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/renderer.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/table.py
RENAMED
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/_vendor/pydbml/renderer/sql/default/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/dbt/templates/macros/generate_schema_name.sql
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model2data-1.10.3 → model2data-1.10.4}/model2data/spec/examples/coffee_webshop.model2data.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|