diff --git a/.github/workflows/converter-microsoft-ci.yml b/.github/workflows/converter-microsoft-ci.yml index fc177244..d1217277 100644 --- a/.github/workflows/converter-microsoft-ci.yml +++ b/.github/workflows/converter-microsoft-ci.yml @@ -24,13 +24,15 @@ on: branches: [ "main" ] paths: - 'converters/microsoft/**' - - 'core-spec/ossie-schema.json' + - 'core-spec/**' + - 'examples/**' - '.github/workflows/converter-microsoft-ci.yml' pull_request: branches: [ "main" ] paths: - 'converters/microsoft/**' - - 'core-spec/ossie-schema.json' + - 'core-spec/**' + - 'examples/**' - '.github/workflows/converter-microsoft-ci.yml' jobs: diff --git a/converters/microsoft/README.md b/converters/microsoft/README.md index e9abb765..f9f801c3 100644 --- a/converters/microsoft/README.md +++ b/converters/microsoft/README.md @@ -24,6 +24,13 @@ semantic model. Power BI output is available as either a TMSL `model.bim` mappin TMDL document. The conversion is offline and requires no Power BI or Fabric connection. +Each Ossie JSON/YAML document contains one model, with `name`, `datasets`, +`relationships`, and `metrics` at the root alongside `version`. Legacy +`semantic_model` wrappers must be unwrapped before conversion; split documents +containing multiple models into separate files. Because the Ossie schema requires at +least one dataset, importing a Power BI model fails explicitly when every table is +malformed or excluded from the vendor-neutral model. + ## Installation ```bash @@ -171,7 +178,7 @@ logging.getLogger("ossie_microsoft").addHandler(logging.StreamHandler()) | Power BI (TMSL) | Apache Ossie | |-----------------|--------------| -| `name` / `model.description` | `semantic_model.name` / `.description` | +| `name` / `model.description` | `name` / `description` | | `model.tables[]` | `datasets[]` | | table partition source (`entity`, `m`, `query`, `calculated`) | `dataset.source` | | `table.columns[]` | `dataset.fields[]` | diff --git a/converters/microsoft/src/ossie_microsoft/ossie_to_semantic_model.py b/converters/microsoft/src/ossie_microsoft/ossie_to_semantic_model.py index d80814b5..e495dfd3 100644 --- a/converters/microsoft/src/ossie_microsoft/ossie_to_semantic_model.py +++ b/converters/microsoft/src/ossie_microsoft/ossie_to_semantic_model.py @@ -155,6 +155,23 @@ def convert_ossie_to_semantic_model( if not isinstance(document, dict): raise TypeError("input must be Apache Ossie YAML text or a parsed document") + if "semantic_model" in document: + raise ValueError( + "Legacy 'semantic_model' wrappers are not supported; " + "place model properties at the document root" + ) + if "dialects" in document or "vendors" in document: + raise ValueError("Root dialects and vendors are not supported by the Ossie spec") + if not document.get("name"): + raise ValueError("document requires 'name' and 'datasets' at the root") + datasets = document.get("datasets") + if ( + not isinstance(datasets, list) + or not datasets + or any(not isinstance(dataset, dict) or not dataset.get("name") for dataset in datasets) + ): + raise ValueError("document 'datasets' must be a non-empty list of named objects") + version = document.get("version") if version and version != OSSIE_VERSION: warn( @@ -162,25 +179,13 @@ def convert_ossie_to_semantic_model( f"document targets Apache Ossie spec {version}, this converter targets " f"{OSSIE_VERSION}; conversion may be incomplete", ) - - models = document.get("semantic_model") - if not isinstance(models, list) or not models or not isinstance(models[0], dict): - raise ValueError("document is missing a 'semantic_model' entry") - if len(models) > 1: - warn( - "document", - f"a model.bim holds a single model; converting the first of {len(models)} " - "and skipping the rest", - ) - semantic_model = models[0] + semantic_model = document stash = read_stash(semantic_model) _warn_foreign_extensions("model", semantic_model) warn_unsupported("model", semantic_model, OSSIE_UNSUPPORTED, "Power BI", _DROPPED) - tables, table_columns, generated_partitions = _convert_datasets( - semantic_model.get("datasets") or [] - ) + tables, table_columns, generated_partitions = _convert_datasets(datasets) _apply_measures(tables, semantic_model.get("metrics") or []) _restore_excluded_measures(tables, stash.get("excludedMeasures") or []) @@ -730,9 +735,6 @@ def _apply_measures(tables, metrics): table = by_name.get(stash.get("table")) if table is None: - if not tables: - warn(scope, "the model has no table to hold the measure; skipped") - continue table = tables[0] warn( scope, diff --git a/converters/microsoft/src/ossie_microsoft/semantic_model_to_ossie.py b/converters/microsoft/src/ossie_microsoft/semantic_model_to_ossie.py index a65cf12c..6e267660 100644 --- a/converters/microsoft/src/ossie_microsoft/semantic_model_to_ossie.py +++ b/converters/microsoft/src/ossie_microsoft/semantic_model_to_ossie.py @@ -146,6 +146,10 @@ def build_ossie_document(bim_file): t for t in model.get("tables") or [] if isinstance(t, dict) and t.get("name") ] tables = [t for t in all_tables if _is_exported_table(t)] + if not tables: + raise ValueError( + "model.bim has no tables that can be exported as Apache Ossie datasets" + ) excluded_tables = [t for t in all_tables if not _is_exported_table(t)] exported_names = {t["name"] for t in tables} @@ -179,7 +183,7 @@ def build_ossie_document(bim_file): excluded_measures, ) - return {"version": OSSIE_VERSION, "semantic_model": [semantic_model]} + return {"version": OSSIE_VERSION, **semantic_model} # --------------------------------------------------------------------------- diff --git a/converters/microsoft/tests/conftest.py b/converters/microsoft/tests/conftest.py index bbf4a9cf..124a61ad 100644 --- a/converters/microsoft/tests/conftest.py +++ b/converters/microsoft/tests/conftest.py @@ -46,4 +46,4 @@ def osi(bim): @pytest.fixture(scope="module") def model(osi): - return osi["semantic_model"][0] + return {key: value for key, value in osi.items() if key != "version"} diff --git a/converters/microsoft/tests/test_edge_cases.py b/converters/microsoft/tests/test_edge_cases.py index 1e05accf..cf3d5cb8 100644 --- a/converters/microsoft/tests/test_edge_cases.py +++ b/converters/microsoft/tests/test_edge_cases.py @@ -26,6 +26,7 @@ import warnings import pytest +import yaml from ossie_microsoft import convert_ossie_to_semantic_model from ossie_microsoft._common import ( @@ -59,7 +60,7 @@ def _model(**overrides): ], } semantic_model.update(overrides) - return {"version": OSSIE_VERSION, "semantic_model": [semantic_model]} + return {"version": OSSIE_VERSION, **semantic_model} def _convert(document): @@ -121,7 +122,8 @@ def test_a_malformed_extension_entry_is_ignored(): def test_a_non_dict_table_is_skipped(): bim = {"name": "m", "model": {"tables": ["nonsense", {"name": None}]}} - assert build_ossie_document(bim)["semantic_model"][0].get("datasets") == [] + with pytest.raises(ValueError, match="no tables that can be exported"): + build_ossie_document(bim) def test_a_non_dict_column_is_skipped(): @@ -129,7 +131,7 @@ def test_a_non_dict_column_is_skipped(): "name": "m", "model": {"tables": [{"name": "T", "columns": ["nonsense", {"noName": 1}]}]}, } - model = build_ossie_document(bim)["semantic_model"][0] + model = build_ossie_document(bim) assert model["datasets"][0].get("fields") is None @@ -138,23 +140,30 @@ def test_a_non_dict_measure_is_skipped(): "name": "m", "model": {"tables": [{"name": "T", "measures": ["nonsense", {"noName": 1}]}]}, } - model = build_ossie_document(bim)["semantic_model"][0] + model = build_ossie_document(bim) assert model.get("metrics") is None def test_a_non_dict_relationship_is_skipped(): - bim = {"name": "m", "model": {"tables": [], "relationships": ["nonsense"]}} - assert build_ossie_document(bim)["semantic_model"][0].get("relationships") is None + bim = { + "name": "m", + "model": {"tables": [{"name": "T"}], "relationships": ["nonsense"]}, + } + assert build_ossie_document(bim).get("relationships") is None -def test_a_non_dict_dataset_is_skipped(): - bim = _convert(_model(datasets=["nonsense", {"noName": 1}])) - assert bim["model"]["tables"] == [] +@pytest.mark.parametrize( + "datasets", + [None, {}, [], "dataset", ["nonsense"], [{"name": "T"}, "nonsense"], [{}]], +) +def test_datasets_must_be_a_non_empty_list_of_named_objects(datasets): + with pytest.raises(ValueError, match="non-empty list of named objects"): + _convert(_model(datasets=datasets)) def test_a_non_dict_field_is_skipped(): document = _model() - document["semantic_model"][0]["datasets"][0]["fields"] = ["nonsense", {"noName": 1}] + document["datasets"][0]["fields"] = ["nonsense", {"noName": 1}] bim = _convert(document) assert bim["model"]["tables"][0]["columns"] == [] @@ -172,7 +181,7 @@ def test_a_non_dict_metric_is_skipped(): def test_a_composite_unique_key_is_reported(): """TMSL marks uniqueness per column; a composite constraint has no equivalent.""" document = _model() - document["semantic_model"][0]["datasets"][0]["unique_keys"] = [["C", "D"]] + document["datasets"][0]["unique_keys"] = [["C", "D"]] with pytest.warns(UserWarning, match="composite unique constraint"): bim = _convert(document) assert "isUnique" not in bim["model"]["tables"][0]["columns"][0] @@ -180,28 +189,18 @@ def test_a_composite_unique_key_is_reported(): def test_a_malformed_unique_key_is_ignored(): document = _model() - document["semantic_model"][0]["datasets"][0]["unique_keys"] = ["not a list"] + document["datasets"][0]["unique_keys"] = ["not a list"] _convert(document) def test_an_unrecognized_datatype_is_reported_and_left_unspecified(): document = _model() - document["semantic_model"][0]["datasets"][0]["fields"][0]["datatype"] = "Fictional" + document["datasets"][0]["fields"][0]["datatype"] = "Fictional" with pytest.warns(UserWarning, match="unrecognized Apache Ossie data type"): bim = _convert(document) assert "dataType" not in bim["model"]["tables"][0]["columns"][0] -def test_a_measure_with_no_table_to_live_on_is_reported(): - """A Power BI measure must belong to a table; with no tables there is nowhere.""" - document = _model(datasets=[], metrics=[ - {"name": "M", "expression": make_expression("SUM(x)", "DAX")} - ]) - with pytest.warns(UserWarning, match="no table to hold the measure"): - bim = _convert(document) - assert bim["model"]["tables"] == [] - - def test_a_relationship_to_a_missing_table_is_reported(): document = _model(relationships=[ {"name": "r", "from": "Nope", "from_columns": ["C"], "to": "T", "to_columns": ["C"]} @@ -229,7 +228,7 @@ def test_a_duplicate_measure_name_is_qualified_by_its_table(): }, } with pytest.warns(UserWarning, match="duplicate measure name"): - model = build_ossie_document(bim)["semantic_model"][0] + model = build_ossie_document(bim) assert [m["name"] for m in model["metrics"]] == ["Total", "B.Total"] @@ -261,7 +260,7 @@ def test_a_relationship_missing_an_endpoint_is_reported_and_preserved(): }, } with pytest.warns(UserWarning, match="missing an endpoint"): - model = build_ossie_document(bim)["semantic_model"][0] + model = build_ossie_document(bim) # Preserved, so a round trip back to Power BI does not delete it. assert read_stash(model)["excludedRelationships"][0]["name"] == "broken" @@ -287,7 +286,7 @@ def test_a_duplicate_relationship_name_is_reported_and_preserved(): }, } with pytest.warns(UserWarning, match="duplicate relationship"): - model = build_ossie_document(bim)["semantic_model"][0] + model = build_ossie_document(bim) assert len(model["relationships"]) == 1 assert read_stash(model)["excludedRelationships"][0]["name"] == "dup" @@ -301,9 +300,24 @@ def test_the_cli_writes_to_stdout_without_an_output_path(tmp_path, capsys): from ossie_microsoft.cli import main src = tmp_path / "m.bim" - src.write_text(json.dumps({"name": "m", "model": {"tables": []}}), encoding="utf-8") + src.write_text( + json.dumps({"name": "m", "model": {"tables": [{"name": "T"}]}}), + encoding="utf-8", + ) assert main(["import", "-i", str(src)]) == 0 - assert "semantic_model" in capsys.readouterr().out + assert yaml.safe_load(capsys.readouterr().out)["name"] == "m" + + +def test_the_cli_rejects_a_model_without_an_exportable_dataset(tmp_path, capsys): + from ossie_microsoft.cli import main + + src = tmp_path / "m.bim" + out = tmp_path / "m.yaml" + src.write_text(json.dumps({"name": "m", "model": {"tables": []}}), encoding="utf-8") + + assert main(["import", "-i", str(src), "-o", str(out)]) == 1 + assert "no tables that can be exported" in capsys.readouterr().err + assert not out.exists() def test_the_cli_reports_a_bad_file_without_a_traceback(tmp_path, capsys): diff --git a/converters/microsoft/tests/test_ossie_to_semantic_model.py b/converters/microsoft/tests/test_ossie_to_semantic_model.py index ea4684b0..951ed929 100644 --- a/converters/microsoft/tests/test_ossie_to_semantic_model.py +++ b/converters/microsoft/tests/test_ossie_to_semantic_model.py @@ -42,7 +42,7 @@ def bim_out(model): with warnings.catch_warnings(): warnings.simplefilter("ignore") return convert_ossie_to_semantic_model( - {"version": OSSIE_VERSION, "semantic_model": [model]} + {"version": OSSIE_VERSION, **model} ) @@ -60,7 +60,7 @@ def _annotation(target, name): def _convert(semantic_model): return convert_ossie_to_semantic_model( - {"version": OSSIE_VERSION, "semantic_model": [semantic_model]} + {"version": OSSIE_VERSION, **semantic_model} ) @@ -136,19 +136,29 @@ def test_a_document_without_a_model_is_rejected(): def test_a_foreign_spec_version_warns(): with pytest.warns(UserWarning, match="targets Apache Ossie spec"): convert_ossie_to_semantic_model( - {"version": "9.9.9", "semantic_model": [_minimal()]} + {"version": "9.9.9", **_minimal()} ) -def test_only_the_first_model_is_converted(): - document = {"version": OSSIE_VERSION, "semantic_model": [_minimal(), _minimal()]} - with pytest.warns(UserWarning, match="single model"): - bim = convert_ossie_to_semantic_model(document) - assert len(bim["model"]["tables"]) == 1 +@pytest.mark.parametrize("wrapper", [None, [], {}, [_minimal()], [_minimal(), _minimal()]]) +@pytest.mark.parametrize("include_root_model", [False, True]) +def test_legacy_wrappers_are_rejected(wrapper, include_root_model): + document = {"version": OSSIE_VERSION, "semantic_model": wrapper} + if include_root_model: + document.update(_minimal()) + with pytest.raises(ValueError, match="Legacy 'semantic_model'"): + convert_ossie_to_semantic_model(document) + + +@pytest.mark.parametrize("property_name", ["dialects", "vendors"]) +def test_removed_root_metadata_is_rejected(property_name): + document = {"version": OSSIE_VERSION, **_minimal(), property_name: []} + with pytest.raises(ValueError, match="Root dialects and vendors"): + convert_ossie_to_semantic_model(document) def test_tmsl_is_the_default_and_can_be_selected_explicitly(): - document = {"version": OSSIE_VERSION, "semantic_model": [_minimal()]} + document = {"version": OSSIE_VERSION, **_minimal()} assert convert_ossie_to_semantic_model(document) == convert_ossie_to_semantic_model( document, output_format="tmsl" @@ -156,7 +166,7 @@ def test_tmsl_is_the_default_and_can_be_selected_explicitly(): def test_tmdl_serializes_the_completed_tmsl_model(monkeypatch): - document = {"version": OSSIE_VERSION, "semantic_model": [_minimal()]} + document = {"version": OSSIE_VERSION, **_minimal()} expected = "database Model\n\n\tmodel Model\n" received = [] @@ -454,7 +464,7 @@ def test_a_preserved_partition_is_replayed(bim_out): def test_yaml_text_and_source_parameters_generate_a_direct_lake_partition(): - document = {"version": OSSIE_VERSION, "semantic_model": [_minimal()]} + document = {"version": OSSIE_VERSION, **_minimal()} bim = convert_ossie_to_semantic_model( yaml.safe_dump(document), source={"workspaceId": "workspace", "itemId": "item"}, @@ -531,7 +541,7 @@ def test_an_unqualified_source_names_the_entity_without_inventing_a_schema(): def test_a_missing_onelake_location_is_reported_rather_than_assumed(): - document = {"version": OSSIE_VERSION, "semantic_model": [_minimal()]} + document = {"version": OSSIE_VERSION, **_minimal()} with pytest.warns(UserWarning, match="placeholder ids"): bim = convert_ossie_to_semantic_model(document, source={"workspaceId": "w"}) @@ -539,7 +549,7 @@ def test_a_missing_onelake_location_is_reported_rather_than_assumed(): def test_a_non_mapping_onelake_location_is_rejected(): - document = {"version": OSSIE_VERSION, "semantic_model": [_minimal()]} + document = {"version": OSSIE_VERSION, **_minimal()} with pytest.raises(TypeError, match="workspaceId and itemId"): convert_ossie_to_semantic_model(document, source="workspace/item") @@ -570,7 +580,7 @@ def test_an_explicit_compatible_source_reuses_the_preserved_database_query(): {"name": "Other", "kind": "m", "expression": "42"}, ] semantic_model, existing_partition = _mixed_partition_model(expressions) - document = {"version": OSSIE_VERSION, "semantic_model": [semantic_model]} + document = {"version": OSSIE_VERSION, **semantic_model} bim = convert_ossie_to_semantic_model(document, source=source) @@ -589,7 +599,7 @@ def test_a_conflicting_database_query_gets_a_collision_free_name(): {"name": "Unrelated", "kind": "m", "expression": "let X = 1 in X"}, ] semantic_model, existing_partition = _mixed_partition_model(expressions) - document = {"version": OSSIE_VERSION, "semantic_model": [semantic_model]} + document = {"version": OSSIE_VERSION, **semantic_model} bim = convert_ossie_to_semantic_model( document, source={"workspaceId": "current-workspace", "itemId": "current-item"} @@ -615,7 +625,7 @@ def test_a_non_m_database_query_is_not_reused_for_new_partitions(): {"name": "DatabaseQuery", "kind": "parameter", "expression": '"old"'} ] semantic_model, _ = _mixed_partition_model(expressions) - document = {"version": OSSIE_VERSION, "semantic_model": [semantic_model]} + document = {"version": OSSIE_VERSION, **semantic_model} bim = convert_ossie_to_semantic_model( document, source={"workspaceId": "workspace", "itemId": "item"} @@ -635,7 +645,7 @@ def test_a_scalar_database_query_expression_can_be_reused(): generated = _database_query("workspace", "item") generated["expression"] = "\n".join(generated["expression"]) semantic_model, _ = _mixed_partition_model([generated]) - document = {"version": OSSIE_VERSION, "semantic_model": [semantic_model]} + document = {"version": OSSIE_VERSION, **semantic_model} bim = convert_ossie_to_semantic_model( document, source={"workspaceId": "workspace", "itemId": "item"} @@ -1060,7 +1070,7 @@ def test_a_row_number_column_is_restored(): warnings.simplefilter("ignore") osi = yaml.safe_load(convert_semantic_model_to_ossie(bim)) result = convert_ossie_to_semantic_model(osi) - assert [f["name"] for f in osi["semantic_model"][0]["datasets"][0]["fields"]] == ["C"] + assert [f["name"] for f in osi["datasets"][0]["fields"]] == ["C"] assert [c["name"] for c in _table(result, "T")["columns"]] == ["RowNumber", "C"] diff --git a/converters/microsoft/tests/test_semantic_model_to_ossie.py b/converters/microsoft/tests/test_semantic_model_to_ossie.py index 95f13a42..a8b522f3 100644 --- a/converters/microsoft/tests/test_semantic_model_to_ossie.py +++ b/converters/microsoft/tests/test_semantic_model_to_ossie.py @@ -82,7 +82,7 @@ def test_cli_writes_ossie_yaml(tmp_path): out = tmp_path / "model.yaml" assert main(["import", "-i", str(FIXTURES / "sales_model.bim"), "-o", str(out)]) == 0 document = yaml.safe_load(out.read_text(encoding="utf-8")) - assert document["semantic_model"][0]["name"] == "sales_model" + assert document["name"] == "sales_model" def test_cli_reports_errors_without_traceback(tmp_path, capsys): @@ -99,7 +99,8 @@ def test_cli_reports_errors_without_traceback(tmp_path, capsys): def test_document_header(osi): assert osi["version"] == "0.2.0.dev0" - assert len(osi["semantic_model"]) == 1 + assert "semantic_model" not in osi + assert osi["name"] == "sales_model" def test_model_name_and_description(model): @@ -108,7 +109,7 @@ def test_model_name_and_description(model): def test_accepts_model_bim_json_text(): - document = {"name": "x", "model": {"tables": []}} + document = {"name": "x", "model": {"tables": [{"name": "T"}]}} assert convert_semantic_model_to_ossie(json.dumps(document)) == ( convert_semantic_model_to_ossie(document) ) @@ -131,7 +132,7 @@ def test_exports_only_user_facing_tables(model): assert [d["name"] for d in model["datasets"]] == ["Sales", "Customer", "Calendar"] -def test_calculation_group_is_skipped_with_a_warning(): +def test_an_only_calculation_group_is_rejected_before_warning(): bim = { "name": "m", "model": { @@ -143,12 +144,13 @@ def test_calculation_group_is_skipped_with_a_warning(): ] }, } - with pytest.warns(UserWarning, match="calculation groups are not converted"): - document = build_ossie_document(bim) - assert document["semantic_model"][0]["datasets"] == [] + with warnings.catch_warnings(): + warnings.simplefilter("error") + with pytest.raises(ValueError, match="no tables that can be exported"): + build_ossie_document(bim) -def test_calculated_table_is_skipped_with_a_warning(): +def test_an_only_calculated_table_is_rejected_before_warning(): bim = { "name": "m", "model": { @@ -168,9 +170,10 @@ def test_calculated_table_is_skipped_with_a_warning(): ] }, } - with pytest.warns(UserWarning, match="calculated tables are not converted"): - document = build_ossie_document(bim) - assert document["semantic_model"][0]["datasets"] == [] + with warnings.catch_warnings(): + warnings.simplefilter("error") + with pytest.raises(ValueError, match="no tables that can be exported"): + build_ossie_document(bim) def test_row_number_column_is_skipped(model): @@ -304,7 +307,7 @@ def _flip_osi(): def test_a_one_to_many_relationship_is_flipped_to_many_to_one(): - model = _flip_osi()["semantic_model"][0] + model = _flip_osi() rel = model["relationships"][0] assert rel["from"] == "Sales" assert rel["from_columns"] == ["OrderDate"] @@ -313,7 +316,7 @@ def test_a_one_to_many_relationship_is_flipped_to_many_to_one(): def test_a_flipped_relationship_records_its_original_orientation(): - model = _flip_osi()["semantic_model"][0] + model = _flip_osi() stash = read_stash(model["relationships"][0]) assert stash["flipped"] is True assert stash["fromCardinality"] == "one" @@ -335,7 +338,7 @@ def test_a_flipped_relationship_is_exported_the_way_power_bi_wrote_it(): def test_an_unchanged_pre_snapshot_stash_still_restores_the_original_orientation(): osi = _flip_osi() - relationship = osi["semantic_model"][0]["relationships"][0] + relationship = osi["relationships"][0] stash = read_stash(relationship) stash.pop("normalizedEndpoints") write_stash(relationship, stash) @@ -350,7 +353,7 @@ def test_an_unchanged_pre_snapshot_stash_still_restores_the_original_orientation def test_reversed_ossie_endpoints_are_not_reversed_again_by_a_stale_flip_marker(): osi = _flip_osi() - relationship = osi["semantic_model"][0]["relationships"][0] + relationship = osi["relationships"][0] relationship["from"], relationship["to"] = relationship["to"], relationship["from"] relationship["from_columns"], relationship["to_columns"] = ( relationship["to_columns"], @@ -368,7 +371,7 @@ def test_reversed_ossie_endpoints_are_not_reversed_again_by_a_stale_flip_marker( def test_edited_ossie_endpoints_do_not_replay_stale_cardinalities(): osi = _flip_osi() - relationship = osi["semantic_model"][0]["relationships"][0] + relationship = osi["relationships"][0] relationship["from_columns"] = ["AlternateOrderDate"] relationship["to_columns"] = ["AlternateDate"] @@ -462,7 +465,7 @@ def test_a_model_without_power_bi_specifics_has_no_stash(): }, } osi = yaml.safe_load(convert_semantic_model_to_ossie(bim)) - dataset = osi["semantic_model"][0]["datasets"][0] + dataset = osi["datasets"][0] assert "custom_extensions" not in dataset assert "custom_extensions" not in dataset["fields"][0] @@ -545,7 +548,7 @@ def annotated(name, value): } document = build_ossie_document(bim) - model = document["semantic_model"][0] + model = document dataset = _dataset(model, "Orders") field = _field(dataset, "CustomerId") metric = _metric(model, "Order Count") @@ -652,7 +655,7 @@ def _single_field_datatype(tmsl_type, format_string=None): column["formatString"] = format_string bim = {"name": "m", "model": {"tables": [{"name": "T", "columns": [column]}]}} osi = yaml.safe_load(convert_semantic_model_to_ossie(bim)) - return osi["semantic_model"][0]["datasets"][0]["fields"][0].get("datatype") + return osi["datasets"][0]["fields"][0].get("datatype") # --- lossy steps are reported ---------------------------------------------- @@ -694,7 +697,7 @@ def test_a_measure_without_an_expression_is_preserved_exactly(): } with pytest.warns(UserWarning, match="no expression"): osi = build_ossie_document(bim) - model = osi["semantic_model"][0] + model = osi assert read_stash(model)["excludedMeasures"] == [ {"table": "T", "measure": measure, "index": 1} ] @@ -732,11 +735,11 @@ def test_an_excluded_measure_with_a_missing_home_table_warns(): } with pytest.warns(UserWarning, match="no expression"): osi = build_ossie_document(bim) - osi["semantic_model"][0]["datasets"] = [] + osi["datasets"] = [{"name": "Present", "source": "present"}] with pytest.warns(UserWarning, match="home table 'Gone' is missing"): out = convert_ossie_to_semantic_model(osi) - assert out["model"]["tables"] == [] + assert [table["name"] for table in out["model"]["tables"]] == ["Present"] def test_an_authored_metric_wins_over_an_excluded_measure_collision(): @@ -757,7 +760,7 @@ def test_an_authored_metric_wins_over_an_excluded_measure_collision(): osi = build_ossie_document(bim) metric = {"name": "M", "expression": make_expression("1", "DAX")} write_stash(metric, {"table": "T"}) - osi["semantic_model"][0]["metrics"] = [metric] + osi["metrics"] = [metric] out = convert_ossie_to_semantic_model(osi) assert out["model"]["tables"][0]["measures"] == [{"name": "M", "expression": "1"}] diff --git a/converters/microsoft/tests/test_tom_integration.py b/converters/microsoft/tests/test_tom_integration.py index 2e7c0018..c7f94f96 100644 --- a/converters/microsoft/tests/test_tom_integration.py +++ b/converters/microsoft/tests/test_tom_integration.py @@ -84,8 +84,8 @@ def test_a_tmdl_document_imports_back_to_an_equivalent_ossie_model(monkeypatch): ) from_tmdl = yaml.safe_load(convert_semantic_model_to_ossie(tmdl)) - expected = from_tmsl["semantic_model"][0] - received = from_tmdl["semantic_model"][0] + expected = from_tmsl + received = from_tmdl assert received["name"] == expected["name"] assert received["description"] == expected["description"] assert [d["name"] for d in received["datasets"]] == [