Skip to content

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

Draft
ogenstad wants to merge 6 commits into
infrahub-developfrom
pog-packaging-metadata-tests
Draft

fix(packaging): correct declared dependencies and publish the tests extra#1277
ogenstad wants to merge 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 packaging metadata so a plain install imports everything the SDK ships with, and publishes the tests extra. Old: a plain install failed importing template/, spec/, transfer/ and protocols_generator/, pydantic 2.0/2.0.2 crashed on import, and infrahub-sdk[tests] did nothing; New: those dependencies ship with the SDK, the broken pydantic versions fail during resolution, and the tests extra installs. Ref IHS-224.

Dependencies

  • Move Jinja2, PyYAML and rich into core dependencies so a base install can import template/, spec/, transfer/ and protocols_generator/, and drop the rich upper bound.
  • Raise the floor to pydantic>=2.0.3 (2.0 and 2.0.2 now fail during resolution).
  • Declare anyio>=3.3.0, typing-extensions>=4.4.0, and packaging>=21.0 as direct requirements.
  • Publish the tests extra with infrahub-testcontainers>=1.7.3, pytest>=7.0, and packaging>=21.0.
  • Make all aggregate the other extras via infrahub-sdk[ctl,tests] to prevent drift.
  • Drop numpy and mdxify from ctl; move mdxify to a docs dependency group included in dev.
  • Add unit tests that enforce lower bounds, extras aggregation, and that each module only imports what its own install provides.

Migration

  • A plain pip install infrahub-sdk now installs Jinja2, PyYAML and rich (19→26 packages).
  • If you pin pydantic to 2.0.x, raise it to >=2.0.3.
  • If you relied on infrahub-sdk[ctl] to install numpy or mdxify, declare them in your project.
  • Expect infrahub-sdk[all] to install more; use infrahub-sdk[ctl] if you don’t need the testing tools.

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

Review in cubic

…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.
@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             84.16%   84.21%   +0.04%     
====================================================
  Files                   147      148       +1     
  Lines                 13047    13081      +34     
  Branches               1930     1936       +6     
====================================================
+ Hits                  10981    11016      +35     
+ Misses                 1503     1499       -4     
- Partials                563      566       +3     
Flag Coverage Δ
integration-tests 39.06% <ø> (+0.04%) ⬆️
python-3.10 57.12% <ø> (+0.13%) ⬆️
python-3.11 57.12% <ø> (+0.11%) ⬆️
python-3.12 57.10% <ø> (+0.10%) ⬆️
python-3.13 57.10% <ø> (+0.10%) ⬆️
python-3.14 57.10% <ø> (+0.10%) ⬆️
python-filler-3.12 23.62% <ø> (-0.07%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 5 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
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.
@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: ffb9351
Status: ✅  Deploy successful!
Preview URL: https://b85de03f.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
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.

@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
…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.
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.

1 participant