Skip to content
Merged
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
26 changes: 24 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ on:
push:
branches: [main]
pull_request:
branches: [main]

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lint:
name: Lint (ruff)
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -34,6 +33,10 @@ jobs:
- name: ruff format --check
run: uv run ruff format --check src tests

# Fails when `src` imports a package `pyproject.toml` does not declare.
- name: deptry
run: uv run deptry src

typecheck:
name: Typecheck (mypy)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -89,3 +92,22 @@ jobs:
name: test-results-py${{ matrix.python-version }}
path: report.xml
retention-days: 7

install-smoke:
name: Fresh install smoke test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Build
run: |
rm -rf dist
uv build

- name: Install wheel into a clean venv and run dm
run: ./scripts/installation_smoke_test.sh
7 changes: 6 additions & 1 deletion .github/workflows/release-testpypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@ jobs:
echo "::notice title=TestPyPI version::${VERSION}"

- name: Build
run: uv build
run: |
rm -rf dist
uv build

- name: Validate distributions
run: uvx twine check dist/*

- name: Fresh install smoke test
run: ./scripts/installation_smoke_test.sh

- name: Upload distributions
uses: actions/upload-artifact@v4
with:
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,15 @@ jobs:
fi

- name: Build
run: uv build
run: |
rm -rf dist
uv build

- name: Validate distributions
run: uvx twine check dist/*

- name: Fresh install smoke test
run: ./scripts/installation_smoke_test.sh

- name: Upload distributions
uses: actions/upload-artifact@v4
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## v1.5.1

### Added
- `dm discover schema-results` notes when a run holds Safe Data Preview results.
The table cannot show them, so the note points to `--json`.

### Fixed
- A fresh install no longer fails when `typer` drops a package `dm` imports.
`rich` is now a declared dependency, and `typer` requires 0.16.0 or later.

## v1.5.0

### Added
Expand Down
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.PHONY: install lint format mypy test test-integration test-integration-local check build release-patch release-minor release-major
.PHONY: install lint format mypy test test-integration test-integration-local check build smoke release-patch release-minor release-major

install:
uv sync

lint:
uv run ruff check src/ tests/
uv run deptry src/

format:
uv run ruff format src/ tests/
Expand All @@ -25,14 +26,20 @@ test-integration:
test-integration-local:
@eval "$$(python3 scripts/active_profile_env.py)" && uv run pytest -m integration

check: lint format-check mypy test
check: lint format-check mypy test smoke

format-check:
uv run ruff format --check src/ tests/

build:
uv build

# Build the wheel and install it into a clean venv, as a user's machine would.
smoke:
rm -rf dist
uv build
./scripts/installation_smoke_test.sh

# Bump version, commit, tag, push — CI publishes automatically.
# Usage: make release-patch (0.1.0 → 0.1.1)
# make release-minor (0.1.0 → 0.2.0)
Expand Down
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "datamasque-cli"
version = "1.5.0"
version = "1.5.1"
description = "Official command-line interface for the DataMasque data-masking platform."
authors = [
{ name = "DataMasque Ltd" },
Expand All @@ -10,8 +10,9 @@ license = "Apache-2.0"
license-files = ["LICENSE"]
requires-python = ">=3.11"
dependencies = [
"typer>=0.15.0",
"tomli-w>=1.0.0",
"typer>=0.16.0,<1",
"rich>=13.8",
"tomli-w>=1.0.0,<2",
"datamasque-python>=1.2.3,<2",
"pydantic>=2.5,<3",
]
Expand Down Expand Up @@ -46,6 +47,7 @@ dev = [
"ruff>=0.9",
"mypy>=1.10",
"types-requests>=2.31",
"deptry>=0.25.1",
]

[tool.ruff]
Expand Down
30 changes: 30 additions & 0 deletions scripts/installation_smoke_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Tests that the built wheel installs into a clean venv and `dm` runs,
# resolving from PyPI as a user does rather than from `uv.lock`,
# which pins transitive packages and so hides undeclared imports.
#
# Does not test:
# - imports inside functions (deptry catches those)
# - platforms or Pythons other than Linux and 3.11
# - behaviour beyond startup
set -euo pipefail

VENV="$(mktemp -d)/smoke"

uv venv --python 3.11 "$VENV"
uv pip install --python "$VENV/bin/python" dist/*.whl
uv pip check --python "$VENV/bin/python"

# `dm --help` only imports what the entry path touches,
# so a module used by a single subcommand needs importing directly.
"$VENV/bin/python" -c '
import importlib
import pkgutil

import datamasque_cli

for module in pkgutil.walk_packages(datamasque_cli.__path__, "datamasque_cli."):
importlib.import_module(module.name)
'

"$VENV/bin/dm" --help
4 changes: 3 additions & 1 deletion src/datamasque_cli/commands/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
require_id_or_abort,
)
from datamasque_cli.fileio import write_bytes_or_abort, write_text_or_abort
from datamasque_cli.output import print_json, print_success, render_output, should_emit_json
from datamasque_cli.output import print_info, print_json, print_success, render_output, should_emit_json

app = typer.Typer(help="Data discovery operations.", no_args_is_help=True)
app.add_typer(discovery_configs.app, name="configs")
Expand Down Expand Up @@ -237,6 +237,8 @@ def schema_results(
columns=["id", "schema", "table", "column", "data_type", "matches", "constraint"],
title=f"Schema Discovery: Run {run_id}",
)
if not should_emit_json(is_json) and any(row["safe_data_preview"] for row in data):
print_info("Safe Data Preview results are not shown in the table. Use --json to view them.")


@app.command("sdd-report")
Expand Down
29 changes: 29 additions & 0 deletions tests/commands/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,35 @@ def test_schema_results_includes_safe_data_preview_in_json(mock_get_client: Magi
assert "safe_data_preview" not in table.stdout


@pytest.mark.parametrize("has_safe_data_preview", [True, False])
@patch(f"{MODULE}.get_client")
def test_schema_results_hints_at_json_only_when_safe_data_preview_present(
mock_get_client: MagicMock, runner: CliRunner, has_safe_data_preview: bool
) -> None:
client = MagicMock()
mock_get_client.return_value = client
client.list_schema_discovery_results.return_value = [
SimpleNamespace(
id=1,
column="author",
table="books",
schema_name="public",
data=SimpleNamespace(
data_type="varchar",
discovery_matches=[SimpleNamespace(label="name")],
constraint="",
safe_data_preview=_make_string_preview() if has_safe_data_preview else None,
),
),
]

result = runner.invoke(app, ["discover", "schema-results", "42"])

assert result.exit_code == 0
assert ("Safe Data Preview results are not shown" in result.stderr) is has_safe_data_preview
assert ("--json" in result.stderr) is has_safe_data_preview


# -- configurable-discovery run triggers ----------------------------------


Expand Down
Loading
Loading