Skip to content

feat: Add safe data preview results#36

Open
kw-datamasque wants to merge 3 commits into
mainfrom
typed-safe-data-preview
Open

feat: Add safe data preview results#36
kw-datamasque wants to merge 3 commits into
mainfrom
typed-safe-data-preview

Conversation

@kw-datamasque

Copy link
Copy Markdown

Adds SafeDataPreviewOptions on InDataDiscoveryConfig and a typed safe_data_preview field on schema-discovery result columns. Changelog entry under 1.1.8 (unreleased).

@ClassicMMT
ClassicMMT force-pushed the typed-safe-data-preview branch 3 times, most recently from 69ef38f to 9111d65 Compare July 16, 2026 02:27
@ClassicMMT ClassicMMT changed the title feat(discovery): Add typed Safe Data Preview options and result field feat: Add safe data preview results Jul 16, 2026
@ClassicMMT
ClassicMMT marked this pull request as ready for review July 16, 2026 02:33

@cph-datamasque cph-datamasque left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have a think about whether the UnknownPreview is a safe idea from the point of view of a person using this library.

Remove tests that are just testing pydantic, and collapse repetitive tests using pytest.mark.parametrize().

pass
try:
return UnknownPreview.model_validate(value)
except ValidationError:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of future-proofing fallback isn't present for any other model in the client, so unsure how I feel about it - catching client bugs is good and having a defined shape makes using it much easier. But on the other hand we can catch bugs using tests. What do you think?

Comment thread tests/test_discovery.py
}


def test_schema_discovery_request_model_dump_includes_safe_data_preview():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of several tests that don't add any value. This is just testing pydantic's behaviour. If you want to test this, at least call the API method that sends the SD request and check the request body (or fold into one of the existing tests that does that).

Comment thread tests/test_discovery.py
assert preview.statistics_common.count_distinct == 988


def test_schema_discovery_result_without_safe_data_preview():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Borderline not worth it (again you're testing that the default is None, which it is because we can see it right there - no actual logic tested, except for the bit where the validator tolerates and spits out None when given None).

_assert_no_unexpected_extras(item)


def test_each_preview_parses_to_its_typed_variant():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use pytest.mark.parametrize()

assert type(parse_safe_data_preview(_UNSUPPORTED_PREVIEW)) is UnsupportedPreview


def test_each_preview_round_trips():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests pydantic not our code, remove

assert parse_safe_data_preview(_UNSUPPORTED_PREVIEW).model_dump(mode="json") == _UNSUPPORTED_PREVIEW


def test_each_preview_has_no_untyped_fields():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use pytest.mark.parametrize

assert preview.statistics_kind.reason == "Binary data isn't previewed"


def test_none_passes_through():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

already tested by the one I flagged earlier, remove one of them

assert preview.statistics_common.count_null == 0


def test_unparseable_payload_falls_back_without_raising():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the kind of thing I was worried about with the fallback. If someone wrote some code using the library that tried to do maths on the count_row, it would just crash. We should at least ensure UnknownPreview has all fields present, typed, and with defaults if we are going to keep it

assert preview.kind == 123


def test_non_dict_preview_is_none():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be an error?

Comment thread HISTORY.rst
Comment on lines +10 to +13
* ``safe_data_preview`` on ``InDataDiscoveryConfig`` (via ``SafeDataPreviewOptions``)
to configure or disable the preview.
* ``safe_data_preview`` on schema-discovery result columns and file-discovery locators,
typed by ``kind``.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line length is 120

Suggested change
* ``safe_data_preview`` on ``InDataDiscoveryConfig`` (via ``SafeDataPreviewOptions``)
to configure or disable the preview.
* ``safe_data_preview`` on schema-discovery result columns and file-discovery locators,
typed by ``kind``.
* ``safe_data_preview`` on ``InDataDiscoveryConfig`` (via ``SafeDataPreviewOptions``) to configure or disable the preview.
* ``safe_data_preview`` on schema-discovery result columns and file-discovery locators, typed by ``kind``.

Comment thread tests/test_safe_data_preview.py Outdated
assert parse_safe_data_preview(42) is None


def _column_data(**overrides: object) -> dict:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function naming: could we use verb_noun here for clarity, e.g. _build_column_data()?



class StringStatistics(BaseModel):
"""String preview statistics; `patterns`/`first_chars` appear only at the matching disclosure level."""

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""String preview statistics; `patterns`/`first_chars` appear only at the matching disclosure level."""
"""String preview statistics; `patterns`/`first_chars` appear at or above certain disclosure levels."""

Since patterns appears even if the user selects the first_chars disclosure level

return UnknownPreview.model_construct(**value) # type: ignore[arg-type]


SafeDataPreview = SerializeAsAny[ColumnPreview]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Being explicit here could help mypy.

Suggestion:

Suggested change
SafeDataPreview = SerializeAsAny[ColumnPreview]
SafeDataPreview = Union[
StringPreview, NumericPreview, TemporalPreview, BooleanPreview, UnsupportedPreview, UnknownPreview
]

unsupported = "unsupported"


class UnsupportedPreviewReason(str, Enum):

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will stale if DataMasque's unsupported preview reasons change or get added to, and furthermore it's only used in tests (Might have been intended to be used in UnsupportedStatistics)?

Can we delete UnsupportedPreviewReason() and leave UnsupportedStatistics untyped with reason: str like it currently is?

@ClassicMMT
ClassicMMT force-pushed the typed-safe-data-preview branch from fc7ac8d to 62e576e Compare July 17, 2026 04:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants