Repository navigation
Release 1.24.0 - #1503
Release 1.24.0#1503danipl wants to merge 297 commits into
Conversation
Merge stable into develop
Merge develop into infrahub-develop
Switch infrahub-sdk from a static [project].version to a build-time version resolved from v* git tags via hatch-vcs (fallback: static sentinel 0.0.0.dev0). Removes version-bump churn and the recurring pyproject/uv.lock merge conflicts between stable and develop. - pyproject: dynamic version + hatch-vcs + version-file infrahub_sdk/_version.py; git_describe_command without --dirty so the version derives from committed state only - .gitignore: ignore the generated infrahub_sdk/_version.py - uv.lock: regenerated (no version recorded for the dynamic member) - release.yml: fetch tags on checkout; read version via importlib.metadata instead of `uv version --short`; add publish guards (tag-match + reject unreleased fallback) - publish-pypi.yml: fetch tags so `uv build` stamps the real tag version Port of infrahub PR 9711 + 9860 (INFP-566), adapted to the SDK (single package, v* tags, no Docker/compose/Helm). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merge develop into infrahub-develop
Merge stable into develop
Merge develop into infrahub-develop
Merge stable into develop
Merge develop into infrahub-develop
Merge stable into develop
Phase 1 (Specify) of speckit prep: spec.md + requirements checklist for the SDK X-Priority feature — client-wide default + per-request override, zero behaviour change when unconfigured, async/sync parity. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2 (Plan): plan.md, research.md, data-model.md, quickstart.md, and API + wire contracts. Approach grounded in the existing X-Infrahub-Tracker prior art — default injected into base self.headers, per-request override applied in execute_graphql/_execute_graphql_with_file only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-259) Phase 3 (Critique): dual-lens (product + engineering) review — verdict PROCEED WITH UPDATES, no blockers. Applied low-risk recommendations: - SC-002 reworded (X-Priority absent vs literal byte-for-byte) - new SC-006 (batch/blob inherit the client default) - scoped the manual-header edge case to low-level _get/_post - plan/research notes: pagination forwards priority per page; multipart sets X-Priority after the content-type pop Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…259) Phase 4 (Tasks): 35 tasks organized by user story (US1 MVP → US5 parity) plus setup, foundational, and polish. Tests included per PRD testing decisions; critique follow-ups (pagination, multipart ordering, batch/blob inheritance) captured as explicit test tasks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 5: verdict ALIGNED against the IHS-259 PRD. All FRs, SCs, user stories, edge cases, and out-of-scope items carried over; only expansions (FR-009, SC-006) and one testability clarification (SC-002). 0 remediation passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a case-insensitive Priority(str, Enum) in constants.py, re-export it from the SDK public namespace, and add a Config.priority field (auto-binding to INFRAHUB_PRIORITY, default None). Foundational prereqs T002-T004 for the X-Priority request header feature. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Inject the client-wide default priority once in BaseClient.__init__ so it rides GraphQL, multipart, and raw blob transports via the shared self.headers merge. Add unit tests covering GraphQL query/mutation, object-store blob download/upload, multipart upload, batched requests, and the always-emitted normal default, for both async and sync clients. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lock backwards compatibility (US3): an unconfigured client emits no X-Priority header across GraphQL, multipart, and blob transports, and its baseline SDK headers (content-type, X-Infrahub-Tracker) are unchanged. Both async and sync clients. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…S-259] Add a `priority: Priority | None = None` keyword to the GraphQL execute funnels (execute_graphql, _execute_graphql_with_file) and thread it through the high-level client methods (get, all, filters, create_diff, get_diff_summary, get_diff_tree) and node methods (save, create, update, delete) on both the async and sync clients. Resolution is `per_request if per_request is not None else client_default`. Per-request headers now take precedence over self.headers in the transport helpers (_post/_get/_get_streaming/_post_multipart) so an explicit override is not clobbered by the client-wide default. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… unknown [IHS-259]
Lock the Priority enum + pydantic validation for Config.priority (US4):
- unknown value ("lowe") raises ValidationError at config load (no request)
- "LOW"/"Low"/"low"/Priority.LOW all resolve to Priority.LOW (and HIGH/NORMAL)
- INFRAHUB_PRIORITY env-var path resolves case-insensitively
- default Config() leaves priority None (header omitted)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…le [IHS-259] Add a data-driven parity test encoding the resolution truth table from data-model.md (8 default x override rows), run against both the async and sync clients (16 param cases) asserting identical emitted X-Priority headers. Audited T006-T012 and T021-T024: all already parametrized over ["standard","sync"], no change needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… [IHS-259] Phase 8 polish for the X-Priority feature: - Add priority kwarg mention to get_diff_tree docstrings (async + sync) - Regenerate SDK docs (new Config.priority field in config.mdx; priority kwarg across client/node reference), also picking up pre-existing generator whitespace drift so docs-validate passes - Add changelog fragment 1151.added.md describing the Priority enum, Config.priority, and per-request priority= surface Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rge [IHS-259] The X-Priority feature flipped the transport-helper header merge so a per-request snapshot wins over the client base headers. For password-auth clients this regressed the automatic relogin retry: handle_relogin refreshes self.headers["Authorization"] to a new token, but the retry re-sent the SAME stale snapshot, which then overwrote the fresh token -> the retry used the expired token and auth failed. Introduce BaseClient._merge_request_headers, used at every transport-helper merge site (async/sync _post, _get, _get_streaming, _post_multipart). It lets per-request headers win (preserving the X-Priority override) but then re-asserts Authorization / X-INFRAHUB-KEY from self.headers so a mid-flight refreshed token always beats a stale snapshot. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… override [IHS-259] Add per-request X-Priority override tests for node.delete(), the save() update path on an existing node, and get_diff_summary()/get_diff_tree() (create_diff was already covered). All parametrized over async and sync clients. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Final report for the implement + review tail: 35/35 tasks done, 1 HIGH review finding (relogin auth-header regression) fixed inline, 3 test-gap findings closed. SDK suite 1145 passed; 8 pre-existing unrelated failures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…HS-259] Re-run docs-generate on top of develop so the committed generated docs (config.mdx priority field, client/node method priority kwarg) match the current generator and markdown lint rules. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rumdl (MD022/MD058) flagged missing blank lines around headings and tables in the auto-generated critique. Apply rumdl auto-fix so the markdown-lint CI job passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The file is gitignored on develop but was committed on this branch before that rule; untrack it while keeping the local working copy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… [IHS-259] Address review feedback: a per-request priority now rides consistently across a whole operation. - count() (both clients) accepts priority and forwards it; all(parallel=True) passes priority to its preliminary count query (previously sent at the client default while the pages used the override). - Resource-pool peer fetch after node create/update/save now inherits the operation's priority via _process_mutation_result -> RelatedNode.fetch. Tests cover count (direct + parallel all) and RelatedNode.fetch forwarding, both async and sync. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… [IHS-259] - Changelog: concise entry, no longer enumerates every method. - contracts/priority-api.md: remove the incorrect client.create(priority=) signature (it issues no request); document count/filters and the intentional exclusion; note peer-fetch inherits priority. - data-model.md: add node create() + resource-pool peer fetch to the coverage table; note client.create is n/a. - Regenerate SDK reference docs for the new count/fetch priority params. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ratio [IFC-3420] feat(ctl): add infrahubctl commands for the temporary trace sampling ratio [IFC-3420]
Adds the role of stable, develop, infrahub-release-x.y and infrahub-develop, which Infrahub branch pins each one, and the order in which the bot syncs them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(agents): describe the SDK branches and how they sync [IFC-3497]
…#1463) * feat(node): attach a held number to a number pool or detach it [IFC-3333] Number attributes have attach_to_pool() and detach_from_pool(), sent on the next save. An attribute built with both value and from_pool now sends both, so the pool tracks the given number instead of allocating a new one. A pool write is sent once: a pending flag is cleared after a successful mutation, while _from_pool stays set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(node): list a pool's allocations with their provenance [IFC-3367] Resource pool nodes have get_pool_allocations(), which returns PoolAllocation entries: the node id, kind, branch, display label, identifier and whether the pool allocated the number or a user provided it (None for IP pools). It needs Infrahub 1.12 or later. get_pool_allocated_resources() shares the paging loop but still leaves provenance out of its query, so it keeps working on older servers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(node): declare a number pool's ranges on create and upsert [IFC-3369] A relationship whose peer kind is CoreNumberPoolRange takes the peer's fields as mutation input instead of a reference, as Infrahub's NumberPoolRangeInput does. A CoreNumberPool can therefore be created with ranges given as dicts of start, end and allocation_weight, and an upsert sets an existing pool's ranges to the declared list. Before, each range was sent as an empty object, which Infrahub read as one range without a start or an end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(integration): number pool ranges, attach/detach and provenance on Infrahub 1.12 Covers creating a pool with declared ranges and setting them by upsert, attaching and detaching numbers (including a detach before the first save), the provenance get_pool_allocations reports, and the attribute's source after an attach. The class is skipped unless INFRAHUB_TESTING_IMAGE_VER is 1.12 or later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(changelog): split the from_pool fix and shorten the provenance entry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(node): move the detach comment onto the branch it describes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(node): check a detached attribute keeps its number Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Aaron McCarty <19253956+ajtmccarty@users.noreply.github.com>
update_group() returned early whenever a run tracked zero members, so it never diffed the previous membership against the empty set. A generator that legitimately produced nothing, or a repository whose last object file was removed, left every previously tracked node behind as an orphan. With delete_unused_nodes=True a run that tracks nothing now prunes an existing group; one with no group creates none, and an already-empty group is not re-saved. A context reused without finding a group has nothing to reap. The reap deletes on the tracking context's branch rather than the client's default branch, and both context-manager exits reset the client mode in a finally block so a raising update_group() cannot leave the client tracking. delete_unused() attempts every candidate and returns a ReapResult instead of raising. A failure about the member is recorded against it and the rest are still attempted; the refused members stay in the group so a later run retries them, and update_group() reports them together as TrackingGroupCleanupError, again on every run until whatever blocks the deletion is removed. A failure about the request stops the reap and is re-raised as itself rather than recorded against each remaining member. That includes a timeout and an unreachable server, and also the coded failures the GraphQL path raises as a GraphQLError: an expired or missing token, and a branch that is gone, merged, needs a rebase or is locked by a merge. The group write is attempted first, listing this run's nodes plus the refused members and the ones never reached, so an interrupted reap cannot leave the nodes the run created in no group at all. The member whose delete was interrupted is kept only when the server answered: without an answer the delete may have gone through, and a write naming a node that no longer exists is rejected. When the write fails too, the error that stopped the reap is raised with the write's failure as its cause. A member already removed by another member's cascade is tolerated through the catalogue's NodeNotFoundError, with the legacy message check kept only for servers that report no code. The async and sync contexts share the decisions around the reap and the write, keeping only their own calls. TrackingGroupCleanupError is exported from infrahub_sdk.exceptions, survives pickling, and infrahubctl renders it as a table of each member and the server's reason rather than a traceback. The changed contract of delete_unused() and the new exception type get a changed fragment of their own.
fix(tracking): reconcile tracking groups on runs that save no nodes
) Only the server's schema synchronizer assigns the pool of a NumberPool attribute. A schema file that carries the id, for example one exported from another instance, now validates with a read-only warning and the server ignores the value instead of storing an id that points to no pool. Regenerated from the Infrahub generator (tasks/backend.py). Co-authored-by: Aaron McCarty <19253956+ajtmccarty@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…t creates GitRepo copies its source directory, which keeps the source's permissions, and creates the repository as the user that runs the tests. When the Infrahub containers run as another user, they cannot read a repository copied from a private directory, such as one from pytest's tmp_path, and cannot push to any of them. With shared=True, GitRepo sets core.sharedRepository so that Git creates later files with the same access, and gives every user read and write access to the files that exist already. The default, False, keeps the current behavior. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pool (#1469) * fix(node): refuse every edit of a relationship that takes its peers as fields The mutation of such a relationship takes the fields of a peer, not a reference to one, so add(), extend() and remove() could not reach Infrahub: add() and extend() dropped the peer without an error, and remove() sent the remaining peers as references, which Infrahub refused. They now raise FeatureNotSupportedError and name the ways out that work. The relationships concerned are listed as (name, peer) pairs, so another relationship to the same kind of node stays editable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(docs): regenerate the relationship reference Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(ctl): tell --set apart on object create and object update A relationship whose mutation takes the peers' fields does reach Infrahub on a create, so object create refuses only a value that names peers by reference and lets a JSON list of their fields through. An update sends the peers the node was fetched with, so object update refuses every value and names the create that upserts the node. Both report the refusal before any change is applied, and the reason they give comes from the constant the SDK message is built from. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(spec): reproduce the ranges an object file declares being created on their own The two tests fail: loading the file sends a CoreNumberPoolRange mutation of its own next to the pool, which Infrahub refuses as overlapping the range the pool holds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(spec): send the ranges an object file declares with the pool itself A relationship whose mutation takes the peers' fields now travels in the payload of the node that declares it, in both forms an object file accepts for a list of objects. A list of references stays a list of references. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(spec): expand the peers a file's parameters expand before sending them The branch that sends the peers' fields with their node read the list under `data` as it stands, while the validation pass runs it through the processors first. A file with `parameters: {expand_range: true}` was therefore validated expanded and saved unexpanded. Both passes now read the same peers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(ctl): refuse an object that names a peer in --set The create guard read a list of objects as the fields of the peers, so an object holding an id, an hfid or a node went through and failed against the server instead of reporting the refusal. The keys that name a peer now have one definition, which the node side reads as well. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(spec): expand the peers with the parameters of the whole file The branch that sends the peers' fields read the parameters of the relationship alone, while the validation pass falls back to the parameters of the file. A file that sets expand_range for everything it holds was therefore validated expanded and saved unexpanded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(python-sdk): state that editing the peer list is not supported Describing what each save path does with an edit invited readers to rely on one of them. The guide and the docstring now name the complete list as the way to set the ranges. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(docs): regenerate the relationship reference Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(spec): shorten the comments on the inline-peer branch Each one now carries only what the code does not say: why these peers do not go through the deferred branch, and why the parameters fall back to the file's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(node): move the inline-peer rule out of the constants The table, the message and the predicate that reads them now live in a module of their own, keyed by peer kind so a kind can have more than one such relationship. The predicate loses its leading underscore: four modules in three packages read it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(ctl): give the --set refusals their own module Both guards sat in a module named for everything the object commands share, and their tests in a test module named after it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(changelog): fold the object file into the entry that adds the ranges Nothing was released without it, so it is part of what the release adds rather than a fix. The integration test says why it processes the same file twice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(spec): drop the range expansion tests on a number pool Expansion turns a start into a string, which the Number attribute of a range does not take, so the tests pinned a payload Infrahub has no reason to accept. The parameters still travel, so validating and saving read the same peers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat(testing): let GitRepo give every user access to the repository it creates
Merge stable into develop
Python 3.10 is end of life. infrahub-sdk 1.24.0 is the last release that supports Python 3.10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deprecate Python 3.10 support
…-infrahub-release-1.12 # Conflicts: # infrahub_sdk/spec/object.py
… branch in object.py develop sends cardinality-one references through _clean_reference so that HFID references to a generic peer are resolved. infrahub-release-1.12 adds a branch that passes inline peer fields in the mutation. The two handle different relationship formats, so both are kept: the ONE_REF branch takes develop's body and release's peers_are_inline_fields branch follows it. The imports keep both peers_are_inline_input and NodeSchemaAPI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge stable into develop
…ease-1.12 Merge develop into infrahub-release-1.12
Merge stable into develop
Merge develop into infrahub-release-1.12
Merge stable into develop
Merge develop into infrahub-release-1.12
Merge stable into develop
Merge develop into infrahub-release-1.12
Also removes the 14 changelog fragments that the 1.23.0 release had already used on stable, so they are not listed again under 1.24.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deploying infrahub-sdk-python with
|
| Latest commit: |
53cdfdd
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://cbce2007.infrahub-sdk-python.pages.dev |
| Branch Preview URL: | https://dp-20261109-release-sdk-1-24.infrahub-sdk-python.pages.dev |
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## stable #1503 +/- ##
==========================================
- Coverage 85.18% 83.78% -1.41%
==========================================
Files 148 157 +9
Lines 13368 15656 +2288
Branches 1975 2114 +139
==========================================
+ Hits 11388 13117 +1729
- Misses 1412 1979 +567
+ Partials 568 560 -8
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 18 files with indirect coverage changes 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
20 issues found across 181 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="pyproject.toml">
<violation number="1" location="pyproject.toml:69">
P2: Custom agent: **Detect conflicting package versions across dependency files**
This adds a second `pytest` specifier in `pyproject.toml`: the `testing` extra allows `pytest>=7.0`, while the `tests` group requires `pytest>=9.0,<9.1`. Align the specifiers to satisfy the dependency-version consistency rule.</violation>
</file>
<file name="infrahub_sdk/transfer/importer/json.py">
<violation number="1" location="infrahub_sdk/transfer/importer/json.py:199">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**
This `continue_on_error` rendering change has no regression coverage: the importer tests never enable `continue_on_error` or exercise `execute_batches`. Add assertions for empty `GraphQLError.errors` and entries without `message` so both fallback behaviors stay protected.</violation>
</file>
<file name="docs/docs/python-sdk/sdk_ref/infrahub_sdk/node/related_node.mdx">
<violation number="1" location="docs/docs/python-sdk/sdk_ref/infrahub_sdk/node/related_node.mdx:129">
P3: The new `is_loaded` Returns text is split into separate bullets, so the sentence reads as fragments when rendered. The continuation lines should be plain text, not `- ` items. This comes from the doc generator, so the fix may belong there rather than in this file.</violation>
</file>
<file name=".github/workflows/ci.yml">
<violation number="1" location=".github/workflows/ci.yml:359">
P2: The install matrix omits Python 3.15 even though the project supports it, so this new plain-wheel install check does not cover every supported interpreter. Add 3.15 to the matrix.</violation>
</file>
<file name="infrahub_sdk/exceptions/base.py">
<violation number="1" location="infrahub_sdk/exceptions/base.py:102">
P3: An explicit empty `message` is replaced by the generated default here. Use an `is not None` check, as `GraphQLError` does, so a deliberate empty message is kept. The same pattern appears in the other optional-message constructors in this file.</violation>
<violation number="2" location="infrahub_sdk/exceptions/base.py:209">
P2: Pickling and unpickling these three exceptions fails with a TypeError because their constructors take more than the message. Add a `__reduce__` that returns `(_rebuild_error, (type(self), self.args, self.__dict__))`, as `FieldNotLoadedError` already does, to `RateLimitError`, `VersionNotSupportedError`, and `ObjectValidationError`. Round-trip coverage in tests/unit/sdk/test_exceptions.py does not include them.</violation>
<violation number="3" location="infrahub_sdk/exceptions/base.py:456">
P3: This docstring is copied from `UninitializedError`, so it describes the wrong failure. Replace it with a sentence about an invalid server response.</violation>
</file>
<file name="infrahub_sdk/trace_context.py">
<violation number="1" location="infrahub_sdk/trace_context.py:19">
P2: When OTEL_PROPAGATORS is invalid, this import fails on every request, because the failed module is not cached and the `ValueError` path only caches the warning. Cache the failure result too, so the import is not re-attempted per request.</violation>
</file>
<file name="AGENTS.md">
<violation number="1" location="AGENTS.md:65">
P3: The new Branches section says Infrahub pins `python_sdk` to the matching branch, but the release flow in this PR moves the `infrahub-release-1.12` pin to the `v1.24.0` tag. Add a note that pins move to the release tag after a final release.</violation>
<violation number="2" location="AGENTS.md:65">
P3: `python_sdk` is a git submodule, so Infrahub pins a commit rather than tracking an SDK branch; this wording can make readers expect branch-following behavior. Say that the submodule points to a commit from the matching SDK branch.</violation>
</file>
<file name="infrahub_sdk/ctl/cli_commands.py">
<violation number="1" location="infrahub_sdk/ctl/cli_commands.py:247">
P3: The comment above this `except` is inaccurate. The GraphQLError clause below already prints the message when `errors` is empty, so that is not why these lookup misses are re-raised. The real reason is that `catch_exception` renders them with the `Error:` prefix, which the tests check. Rewrite the comment so a maintainer does not remove the branch on the belief that it is redundant.</violation>
</file>
<file name="docs/docs/python-sdk/topics/error_handling.mdx">
<violation number="1" location="docs/docs/python-sdk/topics/error_handling.mdx:196">
P3: This claims every fallback is logged at debug level. On the authentication path, a response with a code that has no class raises `AuthenticationError` without a log line, because the logging only runs when no code was resolved. Narrow the claim to the cases that actually log.</violation>
</file>
<file name="infrahub_sdk/node/attribute.py">
<violation number="1" location="infrahub_sdk/node/attribute.py:220">
P2: `detach_from_pool()` sets `_has_data = True`, which marks the attribute as loaded even when its value was never fetched. A later `.value` read on a node from `only` then returns `None` silently instead of raising `FieldNotLoadedError`. The detach only needs to send `from_pool: None`, so drop the `_has_data` assignment here.</violation>
</file>
<file name="infrahub_sdk/yaml.py">
<violation number="1" location="infrahub_sdk/yaml.py:180">
P2: Errors from `spec` models are reported with a path relative to `spec`, but the message omits the `spec` prefix. With a file whose top-level `kind: Object` is valid, `spec` missing `kind` reads as `kind | Field required (missing)`, which looks like the top-level field is absent. Prefix the spec-level location, for example by passing ("spec",) into `_content_error` from the `spec` callers in `spec/object.py` and `spec/menu.py`.</violation>
</file>
<file name="CHANGELOG.md">
<violation number="1" location="CHANGELOG.md:54">
P3: This release note claims to list all new catalogue exception classes but omits the exported `WorkerTimeoutError`. Add `WorkerTimeoutError` to the list so users can discover the new `WORKER_TIMEOUT` exception.</violation>
</file>
<file name="docs/docs/python-sdk/guides/installation.mdx">
<violation number="1" location="docs/docs/python-sdk/guides/installation.mdx:36">
P2: This description points users to the wrong file: the plugin reads `.infrahub.yml` as repository configuration and discovers `infrahub_tests` only in `test_*.yml`/`test_*.yaml` files. Document the test-file naming and location instead.</violation>
</file>
<file name="infrahub_sdk/query_groups.py">
<violation number="1" location="infrahub_sdk/query_groups.py:37">
P2: `WORKER_TIMEOUT` is missing from `_REQUEST_FAILURE_CODES`, so a server-side worker timeout is recorded as a refusal of the current member and cleanup continues against the remaining members. Add it to the request-level codes so the timeout stops the reap and is re-raised unchanged.</violation>
</file>
<file name="infrahub_sdk/testing/repository.py">
<violation number="1" location="infrahub_sdk/testing/repository.py:91">
P2: `core.sharedRepository` does not make future checked-out worktree files world-writable, so files added after `_share()` runs can revert to the container user's umask and remain unwritable by another Infrahub container user. Apply the shared mode whenever the worktree is updated, or use a mechanism that enforces shared permissions for future worktree files.</violation>
</file>
<file name="infrahub_sdk/ctl/troubleshooting.py">
<violation number="1" location="infrahub_sdk/ctl/troubleshooting.py:74">
P3: These new troubleshooting commands cross the Infrahub server boundary, but coverage only verifies mocked HTTP responses. Add a functional or integration test against a compatible Infrahub server to verify the endpoint contract and response handling.
(Based on your team's feedback about requiring real-server coverage for server-bound SDK and CLI behavior.) [ID: 8542b3a4-57c4-4f1c-abe8-ef576231d2c1]</violation>
</file>
<file name="docs/docs/python-sdk/sdk_ref/infrahub_sdk/node/inline_peers.mdx">
<violation number="1" location="docs/docs/python-sdk/sdk_ref/infrahub_sdk/node/inline_peers.mdx:2">
P3: This SDK reference exposes the internal `peers_are_inline_input` predicate as a public API. Remove this page, or document the supported `RelationshipManager` behavior instead; keep internal implementation helpers out of the SDK reference.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| # The bundled `pytest-infrahub` plugin and the schemas under `infrahub_sdk.testing`, for writing | ||
| # tests against your own Transforms, Queries and Checks. | ||
| testing = [ | ||
| "pytest>=7.0", |
There was a problem hiding this comment.
P2: Custom agent: Detect conflicting package versions across dependency files
This adds a second pytest specifier in pyproject.toml: the testing extra allows pytest>=7.0, while the tests group requires pytest>=9.0,<9.1. Align the specifiers to satisfy the dependency-version consistency rule.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At pyproject.toml, line 69:
<comment>This adds a second `pytest` specifier in `pyproject.toml`: the `testing` extra allows `pytest>=7.0`, while the `tests` group requires `pytest>=9.0,<9.1`. Align the specifiers to satisfy the dependency-version consistency rule.</comment>
<file context>
@@ -43,38 +52,48 @@ infrahubctl = "infrahub_sdk.ctl.cli:app"
+# The bundled `pytest-infrahub` plugin and the schemas under `infrahub_sdk.testing`, for writing
+# tests against your own Transforms, Queries and Checks.
+testing = [
+ "pytest>=7.0",
+]
+
</file context>
| "pytest>=7.0", | |
| "pytest>=9.0,<9.1", |
| if isinstance(result, GraphQLError): | ||
| # Keyed on there being server errors to render rather than on the class: a | ||
| # lookup miss is a GraphQLError too, and carries its reason in its message. | ||
| if isinstance(result, GraphQLError) and result.errors: |
There was a problem hiding this comment.
P2: Custom agent: Flag AI Slop and Fabricated Changes
This continue_on_error rendering change has no regression coverage: the importer tests never enable continue_on_error or exercise execute_batches. Add assertions for empty GraphQLError.errors and entries without message so both fallback behaviors stay protected.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At infrahub_sdk/transfer/importer/json.py, line 199:
<comment>This `continue_on_error` rendering change has no regression coverage: the importer tests never enable `continue_on_error` or exercise `execute_batches`. Add assertions for empty `GraphQLError.errors` and entries without `message` so both fallback behaviors stay protected.</comment>
<file context>
@@ -194,9 +194,13 @@ async def execute_batches(
- if isinstance(result, GraphQLError):
+ # Keyed on there being server errors to render rather than on the class: a
+ # lookup miss is a GraphQLError too, and carries its reason in its message.
+ if isinstance(result, GraphQLError) and result.errors:
error_name = type(result).__name__
- error_msgs = [err["message"] for err in result.errors]
</file context>
| install-matrix: | ||
| strategy: | ||
| matrix: | ||
| python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] |
There was a problem hiding this comment.
P2: The install matrix omits Python 3.15 even though the project supports it, so this new plain-wheel install check does not cover every supported interpreter. Add 3.15 to the matrix.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At .github/workflows/ci.yml, line 359:
<comment>The install matrix omits Python 3.15 even though the project supports it, so this new plain-wheel install check does not cover every supported interpreter. Add 3.15 to the matrix.</comment>
<file context>
@@ -310,6 +310,215 @@ jobs:
+ install-matrix:
+ strategy:
+ matrix:
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
+ if: |
+ always() && !cancelled() &&
</file context>
| python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] | |
| python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"] |
| return (self.__class__, (self.failures,)) | ||
|
|
||
|
|
||
| class VersionNotSupportedError(Error): |
There was a problem hiding this comment.
P2: Pickling and unpickling these three exceptions fails with a TypeError because their constructors take more than the message. Add a __reduce__ that returns (_rebuild_error, (type(self), self.args, self.__dict__)), as FieldNotLoadedError already does, to RateLimitError, VersionNotSupportedError, and ObjectValidationError. Round-trip coverage in tests/unit/sdk/test_exceptions.py does not include them.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At infrahub_sdk/exceptions/base.py, line 209:
<comment>Pickling and unpickling these three exceptions fails with a TypeError because their constructors take more than the message. Add a `__reduce__` that returns `(_rebuild_error, (type(self), self.args, self.__dict__))`, as `FieldNotLoadedError` already does, to `RateLimitError`, `VersionNotSupportedError`, and `ObjectValidationError`. Round-trip coverage in tests/unit/sdk/test_exceptions.py does not include them.</comment>
<file context>
@@ -0,0 +1,522 @@
+ return (self.__class__, (self.failures,))
+
+
+class VersionNotSupportedError(Error):
+ """Raised when a feature is used against an Infrahub server version that does not support it."""
+
</file context>
| return {} | ||
|
|
||
| try: | ||
| from opentelemetry import propagate # noqa: PLC0415 |
There was a problem hiding this comment.
P2: When OTEL_PROPAGATORS is invalid, this import fails on every request, because the failed module is not cached and the ValueError path only caches the warning. Cache the failure result too, so the import is not re-attempted per request.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At infrahub_sdk/trace_context.py, line 19:
<comment>When OTEL_PROPAGATORS is invalid, this import fails on every request, because the failed module is not cached and the `ValueError` path only caches the warning. Cache the failure result too, so the import is not re-attempted per request.</comment>
<file context>
@@ -0,0 +1,54 @@
+ return {}
+
+ try:
+ from opentelemetry import propagate # noqa: PLC0415
+ except ImportError:
+ return {}
</file context>
| | A server predating the catalogue, or an error with no `extensions` | `exc.code` is `None`, and the message is the one that version of the SDK has always produced | | ||
| | A payload that does not match what the catalogue declares | The generic class for the transport, with the code still readable | | ||
|
|
||
| Every fallback is logged at debug level on the `infrahub_sdk` logger, naming the code where the |
There was a problem hiding this comment.
P3: This claims every fallback is logged at debug level. On the authentication path, a response with a code that has no class raises AuthenticationError without a log line, because the logging only runs when no code was resolved. Narrow the claim to the cases that actually log.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/docs/python-sdk/topics/error_handling.mdx, line 196:
<comment>This claims every fallback is logged at debug level. On the authentication path, a response with a code that has no class raises `AuthenticationError` without a log line, because the logging only runs when no code was resolved. Narrow the claim to the cases that actually log.</comment>
<file context>
@@ -0,0 +1,212 @@
+| A server predating the catalogue, or an error with no `extensions` | `exc.code` is `None`, and the message is the one that version of the SDK has always produced |
+| A payload that does not match what the catalogue declares | The generic class for the transport, with the code still readable |
+
+Every fallback is logged at debug level on the `infrahub_sdk` logger, naming the code where the
+response carried one, so an SDK meeting a newer server is diagnosable without a debugger.
+
</file context>
| The Infrahub repository pins `python_sdk` to the branch that matches its own: `stable` to `stable`, | ||
| `release-x.y` to `infrahub-release-x.y`, `develop` to `infrahub-develop`. |
There was a problem hiding this comment.
P3: python_sdk is a git submodule, so Infrahub pins a commit rather than tracking an SDK branch; this wording can make readers expect branch-following behavior. Say that the submodule points to a commit from the matching SDK branch.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At AGENTS.md, line 65:
<comment>`python_sdk` is a git submodule, so Infrahub pins a commit rather than tracking an SDK branch; this wording can make readers expect branch-following behavior. Say that the submodule points to a commit from the matching SDK branch.</comment>
<file context>
@@ -53,6 +53,22 @@ infrahub_sdk/
+| `infrahub-release-x.y` | The SDK that ships with Infrahub `x.y`; created from `infrahub-develop` when Infrahub cuts `release-x.y` |
+| `infrahub-develop` | SDK changes that need the next Infrahub version |
+
+The Infrahub repository pins `python_sdk` to the branch that matches its own: `stable` to `stable`,
+`release-x.y` to `infrahub-release-x.y`, `develop` to `infrahub-develop`.
+
</file context>
| The Infrahub repository pins `python_sdk` to the branch that matches its own: `stable` to `stable`, | |
| `release-x.y` to `infrahub-release-x.y`, `develop` to `infrahub-develop`. | |
| The Infrahub repository's `python_sdk` submodule points to a commit from the SDK branch that matches its own: `stable` to `stable`, | |
| `release-x.y` to `infrahub-release-x.y`, `develop` to `infrahub-develop`. |
| print("some other failure:", exc.code) | ||
| ``` | ||
|
|
||
| The new classes are `AttributeConstraintViolationError`, `AttributeInvalidTypeError`, `AttributeRequiredError`, `BranchAlreadyMergedError`, `BranchNeedsRebaseError`, `MergeInProgressError`, `MergeRecoveryRequiredError`, `UndefinedError`, and `UniquenessViolationError`. Each is typed exactly as the catalogue declares the payload, so a required field is never optional and needs no guard. They all descend from `GraphQLError`, so no `except` clause stops catching what it catches today. `NODE_NOT_FOUND`, `BRANCH_NOT_FOUND` and `SCHEMA_NOT_FOUND` reach the classes the SDK already had for them, listed under Changed. |
There was a problem hiding this comment.
P3: This release note claims to list all new catalogue exception classes but omits the exported WorkerTimeoutError. Add WorkerTimeoutError to the list so users can discover the new WORKER_TIMEOUT exception.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At CHANGELOG.md, line 54:
<comment>This release note claims to list all new catalogue exception classes but omits the exported `WorkerTimeoutError`. Add `WorkerTimeoutError` to the list so users can discover the new `WORKER_TIMEOUT` exception.</comment>
<file context>
@@ -11,6 +11,192 @@ This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the chang
+ print("some other failure:", exc.code)
+ ```
+
+ The new classes are `AttributeConstraintViolationError`, `AttributeInvalidTypeError`, `AttributeRequiredError`, `BranchAlreadyMergedError`, `BranchNeedsRebaseError`, `MergeInProgressError`, `MergeRecoveryRequiredError`, `UndefinedError`, and `UniquenessViolationError`. Each is typed exactly as the catalogue declares the payload, so a required field is never optional and needs no guard. They all descend from `GraphQLError`, so no `except` clause stops catching what it catches today. `NODE_NOT_FOUND`, `BRANCH_NOT_FOUND` and `SCHEMA_NOT_FOUND` reach the classes the SDK already had for them, listed under Changed.
+
+ `exc.http_status` reads the status the envelope declares. Where the envelope omits one, a generated class supplies the status the catalogue gives its code; the three lookup-miss classes declare none, since they are also raised with no server behind them, and stay `None` there.
</file context>
| The new classes are `AttributeConstraintViolationError`, `AttributeInvalidTypeError`, `AttributeRequiredError`, `BranchAlreadyMergedError`, `BranchNeedsRebaseError`, `MergeInProgressError`, `MergeRecoveryRequiredError`, `UndefinedError`, and `UniquenessViolationError`. Each is typed exactly as the catalogue declares the payload, so a required field is never optional and needs no guard. They all descend from `GraphQLError`, so no `except` clause stops catching what it catches today. `NODE_NOT_FOUND`, `BRANCH_NOT_FOUND` and `SCHEMA_NOT_FOUND` reach the classes the SDK already had for them, listed under Changed. | |
| The new classes are `AttributeConstraintViolationError`, `AttributeInvalidTypeError`, `AttributeRequiredError`, `BranchAlreadyMergedError`, `BranchNeedsRebaseError`, `MergeInProgressError`, `MergeRecoveryRequiredError`, `UndefinedError`, `UniquenessViolationError`, and `WorkerTimeoutError`. Each is typed exactly as the catalogue declares the payload, so a required field is never optional and needs no guard. They all descend from `GraphQLError`, so no `except` clause stops catching what it catches today. `NODE_NOT_FOUND`, `BRANCH_NOT_FOUND` and `SCHEMA_NOT_FOUND` reach the classes the SDK already had for them, listed under Changed. |
| async def get_trace_sample_ratio(_: str = CONFIG_PARAM) -> None: | ||
| """Show the trace sampling ratio of the API server that answers.""" | ||
| client = initialize_client() | ||
| response = await client._get(url=f"{client.address}{TRACE_SAMPLE_RATIO_PATH}") |
There was a problem hiding this comment.
P3: These new troubleshooting commands cross the Infrahub server boundary, but coverage only verifies mocked HTTP responses. Add a functional or integration test against a compatible Infrahub server to verify the endpoint contract and response handling.
(Based on your team's feedback about requiring real-server coverage for server-bound SDK and CLI behavior.) [ID: 8542b3a4-57c4-4f1c-abe8-ef576231d2c1]
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At infrahub_sdk/ctl/troubleshooting.py, line 74:
<comment>These new troubleshooting commands cross the Infrahub server boundary, but coverage only verifies mocked HTTP responses. Add a functional or integration test against a compatible Infrahub server to verify the endpoint contract and response handling.
(Based on your team's feedback about requiring real-server coverage for server-bound SDK and CLI behavior.) [ID: 8542b3a4-57c4-4f1c-abe8-ef576231d2c1]</comment>
<file context>
@@ -0,0 +1,102 @@
+async def get_trace_sample_ratio(_: str = CONFIG_PARAM) -> None:
+ """Show the trace sampling ratio of the API server that answers."""
+ client = initialize_client()
+ response = await client._get(url=f"{client.address}{TRACE_SAMPLE_RATIO_PATH}")
+ _print_state(response)
+
</file context>
| @@ -0,0 +1,18 @@ | |||
| --- | |||
There was a problem hiding this comment.
P3: This SDK reference exposes the internal peers_are_inline_input predicate as a public API. Remove this page, or document the supported RelationshipManager behavior instead; keep internal implementation helpers out of the SDK reference.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/docs/python-sdk/sdk_ref/infrahub_sdk/node/inline_peers.mdx, line 2:
<comment>This SDK reference exposes the internal `peers_are_inline_input` predicate as a public API. Remove this page, or document the supported `RelationshipManager` behavior instead; keep internal implementation helpers out of the SDK reference.</comment>
<file context>
@@ -0,0 +1,18 @@
+---
+title: inline_peers
+sidebarTitle: inline_peers
+---
</file context>
Releases the Python SDK 1.24.0, the SDK version for Infrahub 1.12.0 (IFC-3589). Until now, only the betas
v1.24.0b0andv1.24.0b1were published.This pull request brings
infrahub-release-1.12(commit1b19c283) intostable, as the 1.23.0 release did in #1259. It adds one commit to the release branch:CHANGELOG.md, built by towncrier from 58 changelog fragments.stable(commitc7313d54), butinfrahub-release-1.12still had copies with identical text.Merge with a merge commit
Merge this pull request with a merge commit, not a squash. A squash would break the shared history between
stableandinfrahub-release-1.12.Next steps after the merge
v1.24.0and publish the GitHub release. The release workflow then publishes the package to PyPI.infrahub-release-1.12andinfrahub-develop.🤖 Generated with Claude Code