Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 ``<id>-<slug>`` reference of the project the resource belongs to, such as ``1-default``.
Expand Down
11 changes: 10 additions & 1 deletion datamasque/client/models/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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")

Expand All @@ -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):
Expand Down
36 changes: 36 additions & 0 deletions tests/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
Loading