Skip to content

feat: DM-3945 add table reference CRUD client APIs#40

Open
beneboy wants to merge 3 commits into
mainfrom
DM-3945-table-reference-api
Open

feat: DM-3945 add table reference CRUD client APIs#40
beneboy wants to merge 3 commits into
mainfrom
DM-3945-table-reference-api

Conversation

@beneboy

@beneboy beneboy commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Adds Python client support for the TableReference object introduced by DM-3945 / datamasque/datamasque!3534.

What

  • New TableReferenceClient mixin over /api/table-references/: list_table_references, get_table_reference, get_table_reference_by_name, create_table_reference, update_table_reference, create_or_update_table_reference, delete_table_reference_by_id_if_exists, delete_table_reference_by_name_if_exists.
  • New models: TableReference, TableReferenceOptions, TableReferenceFormat (csv/parquet), TableReferenceId. connection accepts a ConnectionId or a whole ConnectionConfig (its id is extracted), matching MaskingRunRequest; a connection_id property narrows the type back for reads.
  • Exported from datamasque.client; mixed into DataMasqueClient; HISTORY.rst entry and version bump to 1.1.8.

Notes on matching the server

  • The list endpoint is unpaginated and uses the same serializer as the detail endpoint, so get_table_reference_by_name needs one request rather than two. There is no name_exact filter server-side, so the name match is made client-side over the full listing.
  • DELETE archives rather than removes, which frees the name for reuse; create_or_update_table_reference keys on the name.
  • options are omitted from the request entirely when unset (the server keeps what it has stored) and replaced wholesale when set. They are sent regardless of the connection's type, mirroring the server, which accepts and stores them for database connections too.
  • source is not validated client-side. Its meaning is discriminated by the connection's own type (file path vs. dotted schema.table), and the file format is the explicit options.format choice rather than being inferred from the path's suffix.

Testing

21 new tests; full suite (421) green. make lint clean (ruff, ruff format, mypy).

🤖 Generated with Claude Code

Wraps the new `/api/table-references/` endpoints from datamasque/datamasque!3534,
mirroring the existing connection and discovery-config-library client patterns.

- New `TableReferenceClient` mixin: list, get, get-by-name, create, update,
  create-or-update, delete by id/name.
- New `TableReference`, `TableReferenceOptions` and `TableReferenceFormat` models.
  `connection` accepts a `ConnectionId` or a whole `ConnectionConfig`, as run requests do.
- The list endpoint is unpaginated and shares the detail serializer, so a by-name
  lookup needs a single request; the endpoint takes no name filter, so the match is
  made client-side.
- Deletes archive rather than remove, which frees the name for reuse.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@beneboy
beneboy requested a review from elliotsimpson-dm July 23, 2026 22:04

@field_validator("connection", mode="before")
@classmethod
def _unwrap_connection(cls, value: Any) -> Any:

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.

Suggested change
def _unwrap_connection(cls, value: Any) -> Any:
def _unwrap_connection(cls, value: object) -> object:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 70b3544.

Comment thread tests/test_table_references.py Outdated


@pytest.fixture
def sample_table_reference_list_response() -> list[dict[str, Any]]:

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.

Change Any to object here and elsewhere in module.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 70b3544.

"""The string in the source data to read as a null value, or `None` to read no value as null."""


class TableReference(BaseModel):

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.

Do we want to strip server-only fields, the same way _strip_server_only_fields does for connections?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Checked — unlike connections there's no secret/write-only field on this model (no password equivalent). Server response only ever contains name/connection/source/options/id/created/modified, all modeled; id/created/modified already excluded from request bodies via Field(exclude=True). No stripping needed here.


* file connection — a path to the file within the connection's fileset.
The format comes from `options.format`, never from the path's suffix.
* database connection — a dotted, schema-qualified ``schema.table`` reference.

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.

Suggested change
* database connectiona dotted, schema-qualified ``schema.table`` reference.
* database connectiona dotted, schema-qualified `schema.table` reference.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 70b3544.

Comment thread datamasque/client/table_references.py Outdated
if table_reference.id is None:
raise ValueError("Cannot update a table reference that has not been created yet (id is None)")

data = table_reference.model_dump(exclude_none=True, by_alias=True, mode="json")

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.

exclude_none=True will cause null_str to be omitted from the payload.

Suggested change
data = table_reference.model_dump(exclude_none=True, by_alias=True, mode="json")
data = table_reference.model_dump(by_alias=True, mode="json")
if data.get("options") is None:
data.pop("options", None)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 70b3544.

beneboy and others added 2 commits July 24, 2026 16:38
- Use `object` instead of `Any` in the new model validator and test
  annotations.
- Fix docstring formatting for the schema.table reference.
- Stop using `exclude_none=True` for request payloads: it silently
  dropped `options.null_string` (and any other nested `None`) instead
  of just omitting a wholly-unset `options`. Now only the `options`
  key itself is dropped when `None`, so explicit `None` values inside
  it (e.g. `null_string`) are still sent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Match discovery_configs.py's convention: a malformed server response
(missing id) raises DataMasqueApiError with the triggering response
attached, not the bare DataMasqueException, so callers catching
DataMasqueApiError for API-shape problems catch this too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

2 participants