Skip to content

fix(packaging): correct declared dependencies and publish the tests extra - #1277

Merged
ogenstad merged 6 commits into
infrahub-developfrom
pog-packaging-metadata-tests
Sep 2, 2026
Merged

fix(packaging): correct declared dependencies and publish the tests extra#1277
ogenstad merged 6 commits into
infrahub-developfrom
pog-packaging-metadata-tests

Conversation

@ogenstad

@ogenstad ogenstad commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Why

The unit-test matrix runs five Python versions but every job installs exactly what uv.lock pins, so the version ranges declared in pyproject.toml are never exercised. Checking them by hand turned up several that were wrong, two of which break users outright.

Goal: make the declared dependency surface match what the SDK actually needs, and add tests so it stays that way.

Non-goals: no CI jobs that resolve at the declared lower and upper bounds. That is the follow-up work, and it is what will genuinely test the ranges end to end. This PR only covers what can be checked without extra CI time.

Ref IHS-224 (part 1 of 3).

What changed

Behavioral changes:

  • pip install 'infrahub-sdk[tests]' works. The extra is documented in the installation guide but was never published, so the command warned that no such extra existed and installed nothing beyond the base package.
  • Installing against pydantic 2.0 or 2.0.2 now fails while resolving instead of succeeding and then raising SchemaError on import infrahub_sdk. Those versions reject the \_ escape in the generated schema model patterns, so they never worked.
  • anyio, typing-extensions and packaging are declared directly. All three were imported by the shipped package but only arrived as transitive dependencies, so a constrained resolution could install the SDK unusable.
  • infrahub-sdk[ctl] no longer installs numpy or mdxify, neither of which the SDK imports.
  • infrahub-sdk[all] now covers ctl and tests together, so it pulls in considerably more than before.

Implementation notes:

  • Every floor was established by installing the candidate version and exercising the import, not read off release notes. Two initial guesses were wrong: anyio.Path arrives in 3.3.0 rather than 3.0, and typing-extensions needs 4.4.0 for PEP 696 TypeVar defaults.
  • all is now ["infrahub-sdk[ctl,tests]"]. Hand-duplicating the lists is how it had already lost mdxify, and a self-reference cannot drift.
  • mdxify moves to a new docs dependency group, included in dev, so uv sync --all-groups still provides it for invoke docs-generate.

What stayed the same: no runtime code changed. This is packaging metadata, a regenerated lock, one docs note, and a new test module.

How to review

Start with the pyproject.toml diff, which is the whole substance of the change. Then tests/unit/test_packaging_metadata.py. The uv.lock diff is mechanical, and mostly shrinkage from dropping numpy and mdxify.

Two things worth extra scrutiny:

  • The tests extra shares its name with the tests dependency group while holding different contents. The name was chosen to match what the docs and README already tell people to type. uv handles the two namespaces without complaint, but the follow-up PR that splits the test groups is the natural place to rename the group.
  • The tests extra is heavy, adding roughly 66 packages including Docker, FastAPI, uvicorn and Prefect client libraries, all via infrahub-testcontainers. Someone who only wants the pytest plugin gets all of it. Splitting the container-based helpers into their own extra is worth considering in the follow-up.

How to test

uv run pytest tests/unit/test_packaging_metadata.py
uv run invoke lint-code
uv lock --locked --offline

The new tests are non-vacuous: pointing them at the previous pyproject.toml fails four of the five checks, each naming a real defect. The requires-python check passes on the old metadata too, so it is a regression guard rather than a bug finder.

Verified locally: ruff, ty and mypy clean; docs-validate exits 0 with no committed docs changed; lint-docs byte-identical to the base branch. The unit suite is 1840 passed with 2 failures, both of which reproduce identically in a pristine worktree of the base commit (macOS-only Rich wrapping of long /private/var/folders/... temp paths, in files this PR does not touch).

Impact & rollout

  • Backward compatibility: raising the pydantic floor and dropping numpy/mdxify from the extras are resolver-visible narrowings. Nothing that worked before stops working, since the removed versions could not import the SDK and the removed packages were never used, but the install-time behavior changes. This targets infrahub-develop deliberately so it ships with the next Infrahub version rather than as a patch to the current SDK line.
  • Performance: no runtime impact.
  • Config/env changes: none.
  • Deployment notes: safe to merge independently. The follow-up CI work depends on this landing first.

Checklist

  • Tests added/updated
  • Changelog entry added
  • External docs updated (if user-facing or ops-facing change)
  • Internal .md docs updated (internal knowledge and AI code tools knowledge)

Summary by cubic

Fixes the SDK packaging metadata so plain installs include the dependencies required by shipped modules and infrahub-sdk[tests] installs its documented test tools. It also rejects unsupported pydantic 2.0.x versions during resolution instead of failing on import. Addresses IHS-224.

Packaging

  • Moves Jinja2, PyYAML, rich, anyio, and typing-extensions into core dependencies and removes rich’s upper bound.
  • Adds the tests extra with infrahub-testcontainers, pytest, and packaging.
  • Makes all aggregate ctl and tests, removes unused numpy and mdxify from ctl, and moves mdxify to the docs dependency group.
  • Plain installs grow from 19 to 26 packages; tests adds roughly 61 packages, and projects pinning pydantic 2.0–2.0.2 must upgrade.

Validation

  • Adds packaging tests for dependency lower bounds, extras aggregation, import coverage, and Python classifiers.
  • The import checks distinguish load-time, deferred, and type-checking imports and handle tomllib on Python 3.10.
  • Updates the installation guide, changelog, and lockfile.

Written for commit aa5dcc7. Summary will update on new commits.

Review in cubic

@ogenstad
ogenstad requested a review from a team as a code owner August 25, 2026 12:04
@ogenstad ogenstad added the type/tech-debt Item we know we need to improve way it is implemented label Aug 25, 2026
@github-actions github-actions Bot added the type/documentation Improvements or additions to documentation label Aug 25, 2026
@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

@@                 Coverage Diff                  @@
##           infrahub-develop    #1277      +/-   ##
====================================================
- Coverage             85.55%   85.33%   -0.23%     
====================================================
  Files                   148      148              
  Lines                 14282    14085     -197     
  Branches               1953     1936      -17     
====================================================
- Hits                  12219    12019     -200     
- Misses                 1496     1499       +3     
  Partials                567      567              
Flag Coverage Δ
integration-tests 43.40% <ø> (-0.60%) ⬇️
python-3.10 60.17% <ø> (-0.55%) ⬇️
python-3.11 60.16% <ø> (-0.58%) ⬇️
python-3.12 60.16% <ø> (-0.58%) ⬇️
python-3.13 60.17% <ø> (-0.57%) ⬇️
python-3.14 60.16% <ø> (-0.57%) ⬇️
python-filler-3.12 21.93% <ø> (+0.30%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="pyproject.toml">

<violation number="1" location="pyproject.toml:66">
P2: Custom agent: **Detect conflicting package versions across dependency files**

The new `tests` extra declares `pytest>=7.0`, but the same file's `tests` dependency group declares `pytest>=9.0,<9.1`. Align these pytest specifiers or otherwise avoid declaring conflicting versions in the same dependency metadata.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread pyproject.toml
tests = [
"infrahub-testcontainers>=1.7.3",
"packaging>=21.0",
"pytest>=7.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Custom agent: Detect conflicting package versions across dependency files

The new tests extra declares pytest>=7.0, but the same file's tests dependency group declares pytest>=9.0,<9.1. Align these pytest specifiers or otherwise avoid declaring conflicting versions in the same dependency metadata.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pyproject.toml, line 66:

<comment>The new `tests` extra declares `pytest>=7.0`, but the same file's `tests` dependency group declares `pytest>=9.0,<9.1`. Align these pytest specifiers or otherwise avoid declaring conflicting versions in the same dependency metadata.</comment>

<file context>
@@ -43,30 +47,28 @@ infrahubctl = "infrahub_sdk.ctl.cli:app"
+tests = [
+    "infrahub-testcontainers>=1.7.3",
+    "packaging>=21.0",
+    "pytest>=7.0",
+]
+
</file context>

Comment thread tests/unit/test_packaging_metadata.py Outdated
Comment thread tests/unit/test_packaging_metadata.py
Comment thread changelog/+dependency-lower-bounds.changed.md Outdated
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 25, 2026

Copy link
Copy Markdown

Deploying infrahub-sdk-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: aa5dcc7
Status: ✅  Deploy successful!
Preview URL: https://ea82a98f.infrahub-sdk-python.pages.dev
Branch Preview URL: https://pog-packaging-metadata-tests.infrahub-sdk-python.pages.dev

View logs

@ogenstad
ogenstad marked this pull request as draft August 26, 2026 07:49

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 7 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread tests/unit/test_packaging_metadata.py Outdated
Comment thread pyproject.toml Outdated
@ogenstad
ogenstad marked this pull request as ready for review August 28, 2026 07:41

@gmazoyer gmazoyer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good, just wonder how all those multi-line changelog record will look like once combined in a release note.

…xtra

The declared dependency surface had drifted from what the SDK actually
needs. pydantic>=2.0.0 admitted 2.0 and 2.0.2, on which `import
infrahub_sdk` raises SchemaError because their regex engine rejects the
`\_` escape in the generated schema model patterns. anyio,
typing-extensions and packaging were imported by the shipped package but
only ever arrived as transitive dependencies of httpx, pydantic and the
test tooling, so a constrained resolution could install the SDK unusable.

The `tests` extra is described in the installation guide but was never
published, so `pip install 'infrahub-sdk[tests]'` warned and installed
nothing beyond the base package. It now exists and carries what
`infrahub_sdk.testing` and the bundled pytest plugin import. `all` becomes
self-referential so it cannot drift from the extras it aggregates, which is
how it had already lost mdxify.

numpy and mdxify were declared but never imported. pyarrow declares numpy
itself on the releases that need it, and mdxify only builds the docs, so it
moves to a docs dependency group.

Each floor was established by installing the candidate version and
exercising the import rather than read off release notes: anyio.Path
appears in 3.3.0 rather than 3.0, and typing-extensions needs 4.4.0 for
PEP 696 TypeVar defaults.

New unit tests over the packaging metadata keep the declared and imported
dependency sets in agreement.
The extra is newly installable, so readers will reach it for the first time.
It pulls in the container tooling behind `infrahub_sdk.testing`, which is a
lot more than the `ctl` extra beside it.
The import scan classified modules with the running interpreter's
`sys.stdlib_module_names`. `tomllib` only joined the standard library in
3.11, so on 3.10 the guarded `import tomllib` in ctl/config.py looked like an
undeclared third-party package and failed the check.

Exempt it by name rather than skipping imports nested under a
`sys.version_info` guard: the `tomli` backport sits in the same else branch
and is a real declared dependency that must stay verified.
Jinja2, PyYAML and rich sat in the `ctl` extra, but `template/`, `spec/`,
`transfer/` and `protocols_generator/` import them at module level. On a plain
`pip install infrahub-sdk` those modules raised ModuleNotFoundError: 11 of
them, including `infrahub_sdk.template`, which renders Transforms and is
nothing to do with the CLI. rich is not merely presentation there either, its
Traceback/Frame/Syntax types are carried in the Jinja error model.

Moving the three to the core dependencies takes the non-ctl modules that fail
to import on a base install from 20 to 9, and the remaining 9 legitimately
need an extra: seven want pytest, and async_typer and graphql/plugin.py want
the CLI dependencies. It also means the `tests` extra provides a working
pytest environment, which it did not: the bundled plugin imports yaml and
jinja2, so pytest could not start at all.

A base install grows from 19 to 26 packages. ruamel.yaml stays in `ctl`, where
its round-trip mode is only used to preserve comments in `schema format`.
The import check compared against the union of every extra, so a module in the
base wheel could import a package only `ctl` installed and still pass. That is
exactly how `infrahub_sdk.template` came to be broken on a plain install.

Each shipped module is now checked against the requirements its own surface
implies: base modules against the core dependencies, `pytest_plugin/` and
`testing/` against the `tests` extra, and `ctl/` plus the two modules only
reachable from it against `ctl`. Against the previous metadata this reports 36
violations.

Only imports that run at load time count. An import inside a function is the
sanctioned way to reach for an extra, as the JSON importer does for pyarrow,
and flagging it would punish the correct pattern.

Also collapse separator runs in the name normaliser so it matches PEP 503 as
its docstring claims.
…ports

Moving rich into the core dependencies also promoted its `<14` cap from CLI
users to everyone, which would conflict with any project already on a newer
rich. The cap has no recorded cause: it arrived with the UV conversion, a
mechanical commit that postdates rich 14.0, and nothing needs it. The unit
suite passes against rich 13.9.4, 14.0.0, 14.2.0 and 15.0.0, and
`Traceback._guess_lexer`, the one private API in use, behaves the same on all
four. The lock stays on 13.9.4; only the accepted range widens.

The import scan also counted `if TYPE_CHECKING:` blocks as running at import
time, because it unwound every module-level `if` without reading the guard.
Nothing triggered it yet, but annotating a base module against a ctl-only
package is precisely a type-checking import, so the check would have rejected
the correct pattern. The `else` branch of such a guard is a runtime fallback
and still counts.

Import classification now has direct tests, since the distinction between
import-time, deferred and type-checking-only is subtle enough to regress
quietly. Reverting the guard fix fails two of them.
@ogenstad
ogenstad force-pushed the pog-packaging-metadata-tests branch from ffb9351 to aa5dcc7 Compare September 2, 2026 08:12
@ogenstad
ogenstad merged commit 956c830 into infrahub-develop Sep 2, 2026
36 of 37 checks passed
@ogenstad
ogenstad deleted the pog-packaging-metadata-tests branch September 2, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/documentation Improvements or additions to documentation type/tech-debt Item we know we need to improve way it is implemented

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants