Skip to content

Feat: Add configurable discovery - #18

Open
ClassicMMT wants to merge 6 commits into
mainfrom
add-configurable-discovery
Open

Feat: Add configurable discovery#18
ClassicMMT wants to merge 6 commits into
mainfrom
add-configurable-discovery

Conversation

@ClassicMMT

Copy link
Copy Markdown

No description provided.

@ClassicMMT

ClassicMMT commented Jul 17, 2026

Copy link
Copy Markdown
Author

Requires dm-python SDP PR and dm-python configurable discovery PR to be merged and dm-python==1.1.8 published on PyPI.

Note to self: once dm-python==1.1.8 is published, need to update uv.lock.

@ClassicMMT
ClassicMMT force-pushed the add-configurable-discovery branch from 425fe39 to 43946b9 Compare July 17, 2026 04:14
@ClassicMMT ClassicMMT self-assigned this Jul 19, 2026
@ClassicMMT ClassicMMT changed the title Add configurable discovery Feat: Add configurable discovery Jul 19, 2026
@ClassicMMT
ClassicMMT force-pushed the add-configurable-discovery branch from 2ae01e0 to d69ef28 Compare July 19, 2026 23:37
@ClassicMMT
ClassicMMT requested review from kanewilliams and kw-datamasque and removed request for kanewilliams July 20, 2026 19:54
@ClassicMMT
ClassicMMT force-pushed the add-configurable-discovery branch 2 times, most recently from 5bb10f2 to 5acaca4 Compare July 30, 2026 21:43
@ClassicMMT
ClassicMMT marked this pull request as ready for review July 30, 2026 21:45
- Add support for datamasque-python 1.2.1
- Add a status command to rulesets, libraries, discover configs, and
  discover libraries
- Refuse YAML of 60 KiB+ in rulesets/discover configs validate
- Rework discover libraries for updated library model
- Fix rulesets generate and connections update --password
- Drop click dependency
- Update changelog
- Report server reason when library deletion is rejected
- Add validation to discover configs/libraries
- Abort on empty YAML directly
- Abort with not-found when a run has no discovery output
@ClassicMMT
ClassicMMT force-pushed the add-configurable-discovery branch from 67b4915 to fdb5852 Compare July 31, 2026 01:55
@@ -0,0 +1,328 @@
"""Live-instance tests for configurable discovery."""

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.

Nit:

  1. Could we rename this file to test_discovery_configs.py, since (from what I see?) it tests discovery_configs.py and discovery_config_libraries.py but not discovery.py

  2. After renaming, we will be able to remove this docstring since the file is self-explanatory.

Comment on lines +78 to +79
named = [c for c in client.list_discovery_configs() if c.name == name]
matches = [c for c in named if c.config_type is expected_type]

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.

Nit: Could we use explicit variable names: named_configs and matched_configs rather than named and matches - makes it easier to review and spot bugs.

Creates a temporary config to trigger server-side validation,
then deletes it. Reports any validation errors.

Note that configs over 60 KB validate asynchronously and cannot be validated here.

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.

Everywhere else says KiB

Comment on lines +24 to +35
def _create_returning(
is_valid: ValidationStatus | None,
validation_errors: list[SimpleNamespace] | None = None,
) -> Callable[[object], object]:

def fake_create(rs: object) -> object:
rs.id = 99 # type: ignore[attr-defined]
rs.is_valid = is_valid # type: ignore[attr-defined]
rs.validation_errors = validation_errors or [] # type: ignore[attr-defined]
return rs

return fake_create

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.

(Claude)

The name reads as "create returning ... what?" and Callable[[object], object] hides what actually flows through here, which is what forces the three # type: ignore[attr-defined] below.

  • The clone of this helper in test_discovery_configs.py is typed Callable[[DiscoveryConfig], DiscoveryConfig] and needs no ignores; typing this one against Ruleset drops all three.
  • A third copy lives in test_discovery_config_libraries.py, so the fake is spelled three ways across three files.

Suggestion: rename to _fake_create_with_validation, type it Callable[[Ruleset], Ruleset].

For bonus points, share one helper across the three test files.

Comment on lines +475 to +484
@patch(f"{MODULE}.get_client")
def test_status_valid_exits_0(mock_get_client: MagicMock, runner: CliRunner) -> None:
client = MagicMock()
mock_get_client.return_value = client
client.list_rulesets.return_value = [_listed_ruleset(ValidationStatus.valid)]

result = runner.invoke(app, ["rulesets", "status", "demo", "--json"])

assert result.exit_code == 0
assert '"status": "valid"' in result.stdout

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.

Can we use pytest.mark.parametrize for this and the 2 functions below?

Comment thread README.md
Comment on lines +237 to +243
dm discover configs list [--type database|file] # List configs
dm discover configs get <name> [--type database] [--yaml] # Show details or raw YAML
dm discover configs defaults [--type database|file] -o cfg.yaml # Built-in default as a starting point
dm discover configs create --name <n> --type database -f cfg.yaml # Create/update from YAML
dm discover configs delete <name> [--type database] # Delete a config
dm discover configs validate -f cfg.yaml --type database # Validate against server (YAML under 60 KiB)
dm discover configs status <name> [--type database] # Validation status; poll after creating YAML of 60 KiB+

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.

Should we be using [--type database|file] consistently?

Comment thread README.md

#### Discovery config libraries

Libraries are untyped — the same library can be imported by both database and file discovery configs.

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
Libraries are untyped — the same library can be imported by both database and file discovery configs.
The same library can be imported by both database and file discovery configs.

return str(match.id)


def _resolve_discovery_config_id(

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.

Within this function could we use reduce code by using DM-Python's get_discovery_config_by_name(), which finds discovery configs matching the given name and type?


OK = 0
ERROR = 1
USAGE = 2

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.

I wasn't sure what USAGE means and why it was not part of EXIT_CODE_BY_ERROR.

It seems to be an error message emitted by Typer for command-line usage errors and is emitted before any code actually runs. Could we add a comment saying that please?



def abort_api_error(prefix: str, exc: DataMasqueApiError, *, conflict_hint: str | None = None) -> NoReturn:
"""Abort with the admin server's own explanation of a failed request."""

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
"""Abort with the admin server's own explanation of a failed request."""
"""Abort with DataMasque's explanation of a failed request."""

@kw-datamasque

Copy link
Copy Markdown
Collaborator

I like this idea of abort_api_error() which shows DataMasque's errors instead letting Typer handle errors itself.

However it may be easy to forget to add it in specific CLI commands - claude has found a few above. Preferably we would use these new abort_...() functions consistently across all the commands.

Could we create a top-level safety net, does something like the following work? (claude)

  def main() -> None:
      try:
          app()
      except DataMasqueApiError as exc:
          abort_api_error("Request failed", exc)
      except DataMasqueTransportError as exc:
          abort(str(exc), code=ErrorCode.TRANSPORT_ERROR)

Apart from consistently using abort_...() family and the UTF-8 issue above, most of the other comments are minor and this will be a good addition to the DataMasque CLI

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