From 0f15d43a4032fc3a94304d35ad8c575fe9e79e97 Mon Sep 17 00:00:00 2001 From: Daniel Yudelevich Date: Wed, 23 Sep 2026 17:30:15 -0700 Subject: [PATCH 1/3] Fix CLI import crash on Typer 0.27 and release 0.4.1 A fresh install of discolike-cli 0.4.0 fails on every command with ImportError: cannot import name 'Abort' from 'typer._click.exceptions'. Typer 0.27 moved Abort to typer.exceptions. CI never saw it because it tests against uv.lock, which pins Typer 0.26.8, while the published constraint (typer>=0.12) let users resolve 0.27.2. Import the public typer.Abort instead. The remaining imports still reach into Typer's vendored Click (typer._click), which only exists from 0.26 and is private, so bound the dependency to >=0.26,<0.28 rather than let the next minor break installs the same way. --- CHANGELOG.md | 4 ++++ packages/discolike-cli/pyproject.toml | 6 +++--- packages/discolike-cli/src/discolike_cli/main.py | 2 +- packages/discolike/src/discolike/_version.py | 2 +- packages/discolike/tests/test_package.py | 2 +- uv.lock | 4 ++-- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9283b6b..ba66e6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.4.1 (2026-09-23) + +- CLI: fix `ImportError: cannot import name 'Abort' from 'typer._click.exceptions'` on every command in a fresh 0.4.0 install. Typer 0.27 moved `Abort`; the CLI now imports the public `typer.Abort` and requires `typer>=0.26,<0.28`, since it still relies on Typer's vendored Click for its error envelope and help formatting. + ## 0.4.0 (2026-09-23) - SDK: `validate_icp` can run without an LLM key. Pass `integration_id=NATIVE_ICP_ENGINE` (exported from `discolike`, the string `"native-icp"`) to score with DiscoLike's own ICP-fit model instead of your BYOK LLM: no LLM cost, no LLM key, and no web search on that run. The same sentinel works on `discogen.process` for a prompt that already carries the validation structure. Two new errors are specific to it: a 400 `ValidationError` when the ICP text does not yield a Mandatory / Reject if / Nice-to-have prompt, and a 503 `ServerError` when no ICP-fit engine is available. Task lifecycle, polling and statuses are unchanged. diff --git a/packages/discolike-cli/pyproject.toml b/packages/discolike-cli/pyproject.toml index 656b253..acc587b 100644 --- a/packages/discolike-cli/pyproject.toml +++ b/packages/discolike-cli/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "discolike-cli" -version = "0.4.0" +version = "0.4.1" description = "Official CLI for the DiscoLike API" readme = "README.md" license = "MIT" @@ -12,8 +12,8 @@ license-files = ["LICENSE"] requires-python = ">=3.10" authors = [{ name = "DiscoLike", email = "support@discolike.com" }] dependencies = [ - "discolike==0.4.0", - "typer>=0.12", + "discolike==0.4.1", + "typer>=0.26,<0.28", "rich>=13.0", ] keywords = ["discolike", "cli", "business-data", "enrichment"] diff --git a/packages/discolike-cli/src/discolike_cli/main.py b/packages/discolike-cli/src/discolike_cli/main.py index 42457bd..6270907 100644 --- a/packages/discolike-cli/src/discolike_cli/main.py +++ b/packages/discolike-cli/src/discolike_cli/main.py @@ -5,7 +5,7 @@ from typing import Any import typer -from typer._click.exceptions import Abort +from typer import Abort from typer._click.exceptions import ClickException from typer._click.exceptions import NoArgsIsHelpError from typer._click.exceptions import UsageError diff --git a/packages/discolike/src/discolike/_version.py b/packages/discolike/src/discolike/_version.py index 6a9beea..3d26edf 100644 --- a/packages/discolike/src/discolike/_version.py +++ b/packages/discolike/src/discolike/_version.py @@ -1 +1 @@ -__version__ = "0.4.0" +__version__ = "0.4.1" diff --git a/packages/discolike/tests/test_package.py b/packages/discolike/tests/test_package.py index 5700221..2bba95f 100644 --- a/packages/discolike/tests/test_package.py +++ b/packages/discolike/tests/test_package.py @@ -2,4 +2,4 @@ def test_version() -> None: - assert discolike.__version__ == "0.4.0" + assert discolike.__version__ == "0.4.1" diff --git a/uv.lock b/uv.lock index cb4f963..fdf1a38 100644 --- a/uv.lock +++ b/uv.lock @@ -374,7 +374,7 @@ dev = [ [[package]] name = "discolike-cli" -version = "0.4.0" +version = "0.4.1" source = { editable = "packages/discolike-cli" } dependencies = [ { name = "discolike" }, @@ -395,7 +395,7 @@ dev = [ requires-dist = [ { name = "discolike", editable = "packages/discolike" }, { name = "rich", specifier = ">=13.0" }, - { name = "typer", specifier = ">=0.12" }, + { name = "typer", specifier = ">=0.26,<0.28" }, ] [package.metadata.requires-dev] From 980398c7c614f0dcf0090cb644ddb0a579b798db Mon Sep 17 00:00:00 2001 From: Daniel Yudelevich Date: Wed, 23 Sep 2026 17:35:04 -0700 Subject: [PATCH 2/3] Test the dependency ranges users resolve, not just uv.lock Every CI job installed exactly what uv.lock pins, so the Typer 0.27 break that shipped in 0.4.0 was invisible: users resolve fresh from the pyproject ranges. The new test-resolution job runs the suite and a wheel install at both ends of those ranges. Running it locally found two more problems. Typer 0.26.0 drops the env-var API key in auth status, so the floor is 0.26.1. Two entrypoint tests pinned Typer's exact error wording, which 0.27 changed; they now check the flag or argument name the envelope must carry. --- .github/workflows/ci.yml | 22 +++++++++++++++++++ CHANGELOG.md | 2 +- packages/discolike-cli/pyproject.toml | 2 +- .../discolike-cli/tests/test_entrypoint.py | 6 ++--- uv.lock | 2 +- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67f1e9e..d9b1697 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,28 @@ jobs: - run: uv run pytest packages/discolike/tests -q - run: uv run pytest packages/discolike-cli/tests -q + # uv.lock pins one version of every dependency; users resolve fresh from the + # pyproject ranges. Test both ends of those ranges and the built wheels. + test-resolution: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - resolution: highest + python-version: '3.14' + - resolution: lowest-direct + python-version: '3.10' + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + with: + python-version: ${{ matrix.python-version }} + - run: uv sync --all-packages --upgrade --resolution ${{ matrix.resolution }} + - run: uv run --no-sync pytest packages/discolike/tests -q + - run: uv run --no-sync pytest packages/discolike-cli/tests -q + - run: uv build --all-packages -o dist + - run: uv run --no-project --isolated --resolution ${{ matrix.resolution }} --find-links dist --with dist/discolike_cli-*.whl discolike --version + # 3.15 is still prerelease; non-blocking until 3.15.0 final, when it moves # into the matrix above and gets a classifier. test-prerelease: diff --git a/CHANGELOG.md b/CHANGELOG.md index ba66e6d..396de75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 0.4.1 (2026-09-23) -- CLI: fix `ImportError: cannot import name 'Abort' from 'typer._click.exceptions'` on every command in a fresh 0.4.0 install. Typer 0.27 moved `Abort`; the CLI now imports the public `typer.Abort` and requires `typer>=0.26,<0.28`, since it still relies on Typer's vendored Click for its error envelope and help formatting. +- CLI: fix `ImportError: cannot import name 'Abort' from 'typer._click.exceptions'` on every command in a fresh 0.4.0 install. Typer 0.27 moved `Abort`; the CLI now imports the public `typer.Abort` and requires `typer>=0.26.1,<0.28`, since it still relies on Typer's vendored Click for its error envelope and help formatting. ## 0.4.0 (2026-09-23) diff --git a/packages/discolike-cli/pyproject.toml b/packages/discolike-cli/pyproject.toml index acc587b..5202784 100644 --- a/packages/discolike-cli/pyproject.toml +++ b/packages/discolike-cli/pyproject.toml @@ -13,7 +13,7 @@ requires-python = ">=3.10" authors = [{ name = "DiscoLike", email = "support@discolike.com" }] dependencies = [ "discolike==0.4.1", - "typer>=0.26,<0.28", + "typer>=0.26.1,<0.28", "rich>=13.0", ] keywords = ["discolike", "cli", "business-data", "enrichment"] diff --git a/packages/discolike-cli/tests/test_entrypoint.py b/packages/discolike-cli/tests/test_entrypoint.py index 452d96a..0e471f9 100644 --- a/packages/discolike-cli/tests/test_entrypoint.py +++ b/packages/discolike-cli/tests/test_entrypoint.py @@ -22,8 +22,8 @@ def _run(argv: list[str], capsys: pytest.CaptureFixture[str]) -> tuple[int, str, ("argv", "fragment"), [ (["count", "--nope"], "--nope"), - (["discover", "--max-records", "abc"], "not a valid integer"), - (["discogen", "status"], "TASK_ID"), + (["discover", "--max-records", "abc"], "--max-records"), + (["discogen", "status"], "task_id"), (["auth", "login", "--method", "bogus"], "--method"), ], ) @@ -37,7 +37,7 @@ def test_parser_errors_use_the_json_envelope( assert payload["code"] == "validation_error" assert payload["error"] == "ValidationError" assert payload["exit_code"] == 2 - assert fragment in payload["message"] + assert fragment.lower() in payload["message"].lower() def test_help_still_renders(capsys: pytest.CaptureFixture[str]) -> None: diff --git a/uv.lock b/uv.lock index fdf1a38..c07afc0 100644 --- a/uv.lock +++ b/uv.lock @@ -395,7 +395,7 @@ dev = [ requires-dist = [ { name = "discolike", editable = "packages/discolike" }, { name = "rich", specifier = ">=13.0" }, - { name = "typer", specifier = ">=0.26,<0.28" }, + { name = "typer", specifier = ">=0.26.1,<0.28" }, ] [package.metadata.requires-dev] From 62838c3a720a52353295800efdeda8d4367e5ae8 Mon Sep 17 00:00:00 2001 From: Daniel Yudelevich Date: Wed, 23 Sep 2026 17:37:26 -0700 Subject: [PATCH 3/3] Pin the cli extra to the matching 0.4.1 CLI discolike[cli]==0.4.1 required discolike-cli==0.4.0, which requires discolike==0.4.0, so the extra could not be installed. --- packages/discolike/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/discolike/pyproject.toml b/packages/discolike/pyproject.toml index 80784f7..17b53d8 100644 --- a/packages/discolike/pyproject.toml +++ b/packages/discolike/pyproject.toml @@ -34,7 +34,7 @@ classifiers = [ ] [project.optional-dependencies] -cli = ["discolike-cli==0.4.0"] +cli = ["discolike-cli==0.4.1"] [project.urls] Homepage = "https://www.discolike.com"