Skip to content

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

Open
ogenstad wants to merge 3 commits into
infrahub-developfrom
pog-packaging-metadata-tests
Open

fix(packaging): correct declared dependencies and publish the tests extra#1277
ogenstad wants to merge 3 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

Aligns packaging metadata with actual imports and publishes the tests extra so installs are reliable. Old: installs could miss needed deps or accept pydantic 2.0/2.0.2 and crash, and infrahub-sdk[tests] did nothing; New: direct deps are declared, those versions are rejected during resolution, the tests extra installs, and the metadata tests were fixed to treat tomllib as stdlib on Python 3.10 to avoid false positives. Ref IHS-224.

Dependencies

  • 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 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 declared-vs-imported parity.

Migration

  • 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 1700088. 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.16%           
=================================================
  Files                   147      147           
  Lines                 13047    13047           
  Branches               1930     1930           
=================================================
  Hits                  10981    10981           
  Misses                 1503     1503           
  Partials                563      563           
Flag Coverage Δ
integration-tests 39.02% <ø> (+<0.01%) ⬆️
python-3.10 57.00% <ø> (+0.01%) ⬆️
python-3.11 56.98% <ø> (-0.02%) ⬇️
python-3.12 56.98% <ø> (-0.02%) ⬇️
python-3.13 57.00% <ø> (ø)
python-3.14 57.00% <ø> (ø)
python-filler-3.12 23.68% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 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.

4 issues 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>

<file name="tests/unit/test_packaging_metadata.py">

<violation number="1" location="tests/unit/test_packaging_metadata.py:50">
P3: The docstring says this normalises per PEP 503, but consecutive separators are not collapsed (each '_'/'.'/ '-' becomes its own '-'), so names that PEP 503 treats as equal compare unequal here. Currently harmless because both comparison sides use the same helper, but the claim is misleading and a future dist name with doubled separators would silently fail to match. Collapse runs with a regex to be faithful to PEP 503.</violation>

<violation number="2" location="tests/unit/test_packaging_metadata.py:140">
P2: `test_imports_in_shipped_package_are_declared` aggregates `declared` across all extras, so it can't detect base-wheel modules that import extra-only packages. For example, `infrahub_sdk/transfer/importer/json.py` top-level imports `rich` (ctl-only) and `infrahub_sdk/template/__init__.py` imports `jinja2` (ctl-only), yet a plain `pip install infrahub-sdk` installs neither, and this test still passes. The docstring's guarantee ('anything the wheel imports must be installable') isn't actually enforced for base installs. Consider checking the set of distributions a module is importable with (base deps only) for modules that ship with no extra, or verifying base-install imports against base dependencies rather than the extra union.</violation>
</file>

<file name="changelog/+dependency-lower-bounds.changed.md">

<violation number="1" location="changelog/+dependency-lower-bounds.changed.md:5">
P3: The changelog groups `packaging` with `anyio`/`typing-extensions` as base dependency fixes that keep a minimal install usable, but `packaging` is declared only in the `tests` extra and imported only by `infrahub_sdk/testing/docker.py`, not by the base SDK. A plain `pip install infrahub-sdk` still does not install `packaging`. Clarify that `packaging` is a direct requirement of the `tests` extra (for the testing helpers), separate from the base-level `anyio`/`typing-extensions` additions.</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>


def test_imports_in_shipped_package_are_declared() -> None:
"""Anything the wheel imports must be installable, not left to arrive as a transitive dependency."""
declared = _declared_distributions()

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: test_imports_in_shipped_package_are_declared aggregates declared across all extras, so it can't detect base-wheel modules that import extra-only packages. For example, infrahub_sdk/transfer/importer/json.py top-level imports rich (ctl-only) and infrahub_sdk/template/__init__.py imports jinja2 (ctl-only), yet a plain pip install infrahub-sdk installs neither, and this test still passes. The docstring's guarantee ('anything the wheel imports must be installable') isn't actually enforced for base installs. Consider checking the set of distributions a module is importable with (base deps only) for modules that ship with no extra, or verifying base-install imports against base dependencies rather than the extra union.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/unit/test_packaging_metadata.py, line 140:

<comment>`test_imports_in_shipped_package_are_declared` aggregates `declared` across all extras, so it can't detect base-wheel modules that import extra-only packages. For example, `infrahub_sdk/transfer/importer/json.py` top-level imports `rich` (ctl-only) and `infrahub_sdk/template/__init__.py` imports `jinja2` (ctl-only), yet a plain `pip install infrahub-sdk` installs neither, and this test still passes. The docstring's guarantee ('anything the wheel imports must be installable') isn't actually enforced for base installs. Consider checking the set of distributions a module is importable with (base deps only) for modules that ship with no extra, or verifying base-install imports against base dependencies rather than the extra union.</comment>

<file context>
@@ -0,0 +1,178 @@
+
+def test_imports_in_shipped_package_are_declared() -> None:
+    """Anything the wheel imports must be installable, not left to arrive as a transitive dependency."""
+    declared = _declared_distributions()
+    undeclared = {
+        distribution: source
</file context>

}


def _normalize(name: str) -> str:

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.

P3: The docstring says this normalises per PEP 503, but consecutive separators are not collapsed (each '_'/'.'/ '-' becomes its own '-'), so names that PEP 503 treats as equal compare unequal here. Currently harmless because both comparison sides use the same helper, but the claim is misleading and a future dist name with doubled separators would silently fail to match. Collapse runs with a regex to be faithful to PEP 503.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/unit/test_packaging_metadata.py, line 50:

<comment>The docstring says this normalises per PEP 503, but consecutive separators are not collapsed (each '_'/'.'/ '-' becomes its own '-'), so names that PEP 503 treats as equal compare unequal here. Currently harmless because both comparison sides use the same helper, but the claim is misleading and a future dist name with doubled separators would silently fail to match. Collapse runs with a regex to be faithful to PEP 503.</comment>

<file context>
@@ -0,0 +1,178 @@
+}
+
+
+def _normalize(name: str) -> str:
+    """Normalise a distribution name per PEP 503 so `ruamel.yaml` and `Jinja2` compare cleanly."""
+    return "".join("-" if character in "-_." else character for character in name.lower())
</file context>


If you pin `pydantic` to 2.0, 2.0.1 or 2.0.2, installing the SDK now fails while resolving dependencies. Those versions never actually worked: `import infrahub_sdk` raised a `SchemaError` on them, because their regex engine rejects a pattern used by the schema models. Pin `pydantic>=2.0.3` to resolve it.

`anyio`, `typing-extensions` and `packaging` are also now installed as direct requirements. The SDK has always imported them but relied on other packages to pull them in, so a minimal or heavily constrained environment could end up with the SDK installed and unusable. No action is needed, installs simply become reliable.

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.

P3: The changelog groups packaging with anyio/typing-extensions as base dependency fixes that keep a minimal install usable, but packaging is declared only in the tests extra and imported only by infrahub_sdk/testing/docker.py, not by the base SDK. A plain pip install infrahub-sdk still does not install packaging. Clarify that packaging is a direct requirement of the tests extra (for the testing helpers), separate from the base-level anyio/typing-extensions additions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At changelog/+dependency-lower-bounds.changed.md, line 5:

<comment>The changelog groups `packaging` with `anyio`/`typing-extensions` as base dependency fixes that keep a minimal install usable, but `packaging` is declared only in the `tests` extra and imported only by `infrahub_sdk/testing/docker.py`, not by the base SDK. A plain `pip install infrahub-sdk` still does not install `packaging`. Clarify that `packaging` is a direct requirement of the `tests` extra (for the testing helpers), separate from the base-level `anyio`/`typing-extensions` additions.</comment>

<file context>
@@ -0,0 +1,5 @@
+
+If you pin `pydantic` to 2.0, 2.0.1 or 2.0.2, installing the SDK now fails while resolving dependencies. Those versions never actually worked: `import infrahub_sdk` raised a `SchemaError` on them, because their regex engine rejects a pattern used by the schema models. Pin `pydantic>=2.0.3` to resolve it.
+
+`anyio`, `typing-extensions` and `packaging` are also now installed as direct requirements. The SDK has always imported them but relied on other packages to pull them in, so a minimal or heavily constrained environment could end up with the SDK installed and unusable. No action is needed, installs simply become reliable.
</file context>

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: 1700088
Status: ✅  Deploy successful!
Preview URL: https://0170e75e.infrahub-sdk-python.pages.dev
Branch Preview URL: https://pog-packaging-metadata-tests.infrahub-sdk-python.pages.dev

View logs

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