From 99be3cfd759e82bb830d0292a946e4134e78e8b2 Mon Sep 17 00:00:00 2001 From: jiatolentino Date: Fri, 6 Nov 2026 20:00:58 +0800 Subject: [PATCH] feat: DM-4438 expose the document path on a schema discovery result --- HISTORY.rst | 3 +++ datamasque/client/models/discovery.py | 11 +++++++- tests/test_discovery.py | 36 +++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 191e613..e175568 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,6 +5,9 @@ History 1.6.0 (unreleased) ------------------ +* Added an optional ``path`` field to ``SchemaDiscoveryColumn``: where a document-database field sits within its document, + as a list of keys with ``*`` for an array position, such as ``["contact", "*", "email"]``. + It is ``None`` for a relational column. * Added a read-only ``project`` field to ``ConnectionConfig``, ``Ruleset``, ``RulesetLibrary``, ``DiscoveryConfig``, ``DiscoveryConfigLibrary``, ``RGConfig`` and ``TableReference``: the ``-`` reference of the project the resource belongs to, such as ``1-default``. diff --git a/datamasque/client/models/discovery.py b/datamasque/client/models/discovery.py index bacbc8d..f2ccbd0 100644 --- a/datamasque/client/models/discovery.py +++ b/datamasque/client/models/discovery.py @@ -8,6 +8,7 @@ from datamasque.client.models.connection import ConnectionConfig, ConnectionId, unwrap_connection_id from datamasque.client.models.data_selection import ( HashColumnsTableConfig, + JsonPath, Locator, UserSelection, deserialize_locator, @@ -375,7 +376,14 @@ class LengthUnit(Enum): class SchemaDiscoveryColumn(BaseModel): - """Column-level data in a schema discovery result.""" + """ + Column-level data in a schema discovery result. + + `path` is where a document-database field sits within its document, as a list of keys, + with `*` for an array position, such as `["contact", "*", "email"]`. + The result's `column` holds the same path joined with dots. + It is absent for a relational column, and for a result stored by a DataMasque version that did not record paths. + """ model_config = ConfigDict(extra="allow") @@ -394,6 +402,7 @@ class SchemaDiscoveryColumn(BaseModel): referencing_foreign_keys: list[ReferencingForeignKey] constraint: str # Primary or Unique, or empty string if column does not participate in a PK/UK safe_data_preview: Optional[SafeDataPreview] = None + path: Optional[JsonPath] = None class SchemaDiscoveryResult(BaseModel): diff --git a/tests/test_discovery.py b/tests/test_discovery.py index 83f9348..2ba1d7f 100644 --- a/tests/test_discovery.py +++ b/tests/test_discovery.py @@ -812,6 +812,42 @@ def test_schema_discovery_result_without_max_length_unit_is_none() -> None: assert result.data.max_length_unit is None +def test_schema_discovery_result_parses_document_path(): + row = _schema_discovery_row(1, "contact.*.email") + row["data"]["path"] = ["contact", "*", "email"] + + result = SchemaDiscoveryResult.model_validate(row) + + assert result.column == "contact.*.email" + assert result.data.path == ["contact", "*", "email"] + + +def test_schema_discovery_result_parses_an_integer_path_segment(): + """The server's path type allows an array index, so one must not fail to parse.""" + row = _schema_discovery_row(1, "contact.0.email") + row["data"]["path"] = ["contact", 0, "email"] + + result = SchemaDiscoveryResult.model_validate(row) + + assert result.data.path == ["contact", 0, "email"] + + +def test_schema_discovery_result_refuses_a_path_that_is_not_a_list(): + """`path` is typed, so a value of the wrong shape is an error rather than passed through.""" + row = _schema_discovery_row(1, "contact.email") + row["data"]["path"] = "contact.email" + + with pytest.raises(ValidationError, match="path"): + SchemaDiscoveryResult.model_validate(row) + + +def test_schema_discovery_result_without_path_is_none(): + """A relational column, or a result stored before DataMasque recorded paths, carries no path.""" + result = SchemaDiscoveryResult.model_validate(_schema_discovery_row(1, "email")) + + assert result.data.path is None + + def test_list_schema_discovery_results_follows_pagination(client): run_id = RunId(42) page1 = {