Repository navigation
Conversation
Spec, plan, research, data model, public API contract, quickstart, critique, task breakdown and spec/ask alignment check for the `only` parameter on get/all/filters and fetch(), `is_loaded`, and the three new error types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add FieldNotLoadedError, FieldNotFoundError and FieldSelectionConflictError to infrahub_sdk.exceptions, each with Error as its only base class and its arguments kept as attributes. Add the private module infrahub_sdk/node/_only.py with the checks and field lists that queries using only will share: the conflict check with include and exclude, the name check for a kind and its implementing kinds, the check that every peer kind has every name, the expansion of a kind's human_friendly_id into the fields to request, and the frozen marker for nodes built from such a query. The module takes schemas already looked up and does no I/O. Baseline before this change: tests/unit/sdk 1657 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FieldSelectionConflictError takes no arguments, so comparing its attributes compared two empty dictionaries and could not fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every attribute, cardinality-one relationship and relationship manager built for a node is bound to it and records whether the node's data contained the field. is_loaded reports that record, any assignment since, or a node without an id; it never raises and reads stay unchanged. RelationshipManager.peers is now a property over the stored list; the setter marks the manager as loaded without changing initialized. Internal reads used when saving go through the private storage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add private query methods on InfrahubNode and InfrahubNodeSync that request id, display_label and __typename plus the fields named in only, without the node's hfid. Named relationships are references, and get their peers' default fields with prefetch_relationships. Hierarchy fields are requested only when named. hfid is replaced by the fields of each kind's HFID. For a generic kind, fields that only some implementing kinds have go in aliased ...on Kind blocks, read from the query's branch. The decision of which field goes where lives in _only.py, so both node classes share it. The public generate_query_data methods do not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the query text Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A relationship that a query with only requests just for the HFID now keeps the reference plus the peer attributes the HFID needs, with or without prefetch_relationships. Only relationships and hierarchy fields named in only get their peers' default fields, so the query no longer grows when the HFID peer kind gains a field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
get, all and filters on both clients take only, the list of fields to fetch. Combining it with include or exclude raises FieldSelectionConflictError before any request, and a name that is not a field of the kind raises FieldNotFoundError before the data query. Nodes from a query with only, and the peers built with them, record the only list. Peers are built only for the relationships named in only when prefetch_relationships is set (parent and children for the hierarchy fields) and for the relationships the HFID needs, reading the node's data with aliases removed so relationships that only an implementing kind has are included. Each node keeps its HFID peers, so its HFID does not depend on the store. The HFID is calculated with a private path reader that treats a field the node's data did not contain as missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… only did not fetch Reads of an attribute value, a cardinality-one relationship's peer and identifiers, and a cardinality-many relationship's peer list now raise FieldNotLoadedError on a node returned by a query with only, or on a peer returned with it, when the response did not contain the field. The error names the kind, the field, the only list and, for a peer, the relationship. Nodes without the marker read exactly as before. Saving, upserting, updating, the HFID, the store, mutation-result processing and repr keep working on these nodes; the update mutation carries the id and the changed fields only. The Raises sections of get_path_value, get_flat_value, matches_local_checksum, download_file and upload_if_changed name the new error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RelatedNode.fetch(only=...) passes only to get. When the cached peer was fetched with only and its HFID cannot be calculated, hfid and hfid_str return the HFID the reference carried. RelationshipManager.fetch(only=...) checks the names against the declared peer kind before any request, reloads a relationship that is not loaded (on a node from only with only=[name] and populate_store=False, which also works for ancestors and descendants), checks the names against every actual peer kind, then runs one filters call per peer kind with only. Without only, both fetch methods behave as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rahub Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a section on only to the query guide, fetch(only=...) under fetching relationships manually, a sentence on nodes from only in the store guide, and a changelog fragment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…errors - Compute the field plan once per filters call and pass it to the query builder, which no longer looks up the implementing schemas, re-checks the names or takes fragment. The plan carries the only list, and each kind's fields carry that kind's schema. - Mark the nodes and peers of a query with only from one node method, with the markers and peer plan built once per kind. - Reject only given as a string with a TypeError. - Make FieldNotLoadedError, FieldNotFoundError and FieldSelectionConflictError survive pickle and copy. - Stop suggesting fetch(only=[field]) on a peer, which would replace the peer with one that carries that field alone. - Raise again when a relationship name does not match a field. - Correct the docstrings of fetch, is_loaded and get, and regenerate the reference pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Make the device relationship of the example interface schema mandatory, as a human-friendly ID through a relationship requires. - Say that hfid can add a hierarchy field to the query, that download_file(skip_if_unchanged=True) raises only for an existing dest file, and that fetch(only=...) on a relationship of cardinality many sends a count query and then one query per page for each peer kind. - State which store entries a node from a query with only replaces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Move the schema, ids, mocked responses and client helpers that several test modules use to tests/helpers/only_data.py, so no test module imports another one. - Define the only_clients fixture once in the sdk conftest, with one schema that holds every kind the tests use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Test the reload of a relationship before the check of each peer kind in its own test, from a shelter whose animals were not fetched, and assert the reload request; a mocked edge may no longer hold fields the query with only did not request. - Test that add, extend and remove on a relationship not fetched of a node from only raise UninitializedError. - Test that unknown names in include and exclude are ignored. - Test the HFID of a default node over every path form against its path value. - Test RelatedNode.fetch with an unknown name in only. - Read hfid_str and peer_hfids_str from fetched peers, so the fetched cases expect real strings. - Add integration tests of RelationshipManager.fetch with only on a person's animals, and of all with only and prefetch_relationships. - Remove helper tests that repeated other cases or asserted constants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the TypeError raised when only is a string to the Raises sections of get, all and filters on both clients and of the four fetch methods, and regenerate the reference pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nodes that do not come from a query with only resolve their HFID with the public path lookup again, so a path without a property on an attribute the data did not contain gives the same result as before this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d fields Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## infrahub-develop #1423 +/- ##
====================================================
+ Coverage 86.86% 87.36% +0.50%
====================================================
Files 152 153 +1
Lines 14813 15221 +408
Branches 1982 2056 +74
====================================================
+ Hits 12867 13298 +431
+ Misses 1382 1367 -15
+ Partials 564 556 -8
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 2 files with indirect coverage changes 🚀 New features to boost your workflow:
|
The HFID of a node from a query with only is read with the fields the node fetched; check every path form, including fields it did not fetch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deploying infrahub-sdk-python with
|
| Latest commit: |
6d00aaa
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://0c02088c.infrahub-sdk-python.pages.dev |
| Branch Preview URL: | https://ds-infp-532-only-field-selec.infrahub-sdk-python.pages.dev |
- A hierarchy field that the kind also declares as a relationship now uses the declared peer kind. An HFID that reads an attribute only the parent kind has, such as parent__code__value, now gets that attribute. A peer attribute that the peer kind lacks raises FieldNotFoundError instead of being left out of the query. - Every node resolves its HFID with one reader, which skips only the fields that a node from only did not fetch. The HFID of a node from a default query no longer raises when its peer in the store came from a query with only. - The default query wraps hierarchy fields with the same helper as the query with only. - fetch() passes only to the client only when it is set, so client subclasses written before only keep working. - Relationship managers read their node from self.node instead of a second field, and all() no longer repeats the conflict check that filters() runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elds Update the contract, the data model and the spec for the declared peer kind of hierarchy fields, the new FieldNotFoundError case and the single HFID reader. Add a section to the implementation report that lists the changes in 909a293. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s it A relationship manager always has a node, so the test that checks field objects built without a node covers attributes and cardinality-one relationships; managers on a node without an id are covered by the new-node test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SDK calculates node.hfid from the node's fields and never reads the server's hfid, which is why naming hfid in only fetches those fields. State that in the HFID docstrings, test it, and shorten the description of the only argument on get, all and filters. The reference pages are regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
||
| return data | ||
|
|
||
| async def _generate_only_query_data( |
There was a problem hiding this comment.
Follow-up refactoring: build the only query with the default builder (generate_query_data / generate_query_data_node) instead of this separate builder and its sync copy.
What it needs first:
- A private way for the default builder to request a relationship without fetching its peer's fields. Today
includedoes both. No new public parameter. node.hfidfrom the server'shfidfor nodes fromonly, since the default builder can't request "a reference plus one peer attribute".- Fixes for three gaps in the default builder:
_process_hierarchical_fieldspasses the node'sexcludeto the hierarchy peers;- hierarchy fields are not processed on hierarchical generics;
- the fragment loop drops fields that an implementing kind inherits from another generic.
A local probe built the query both ways for every case in tests/unit/sdk/test_node_only_query.py: 20 of 32 matched; the rest differ for the reasons above.
The reload in RelationshipManager.fetch and the query building in _build_filters_query each repeated a call in two branches that differed only in a few arguments. Build the arguments first and call once, in the async and the sync code. The requests are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| - **FR-002**: For the queried node, a query with `only` MUST fetch `id`, `display_label` and `__typename`, plus the named attributes and relationships. The server's `hfid` field MUST NOT be fetched for the queried node. | ||
| - **FR-003**: A relationship named in `only` MUST return its peers as references (`id`, `hfid`, `display_label`, `__typename`), as a relationship in the default set does today. | ||
| - **FR-004**: With `prefetch_relationships=True`, the peers of the relationships named in `only` MUST come back with their default fields. Relationships not named in `only` MUST NOT be queried. | ||
| - **FR-005**: In `only`, `id` and `display_label` MUST be accepted and change nothing. `hfid` MUST be replaced by the fields that make up the kind's HFID, including one field of a peer for an HFID component that goes through a relationship. Those fields MUST be readable like named fields, and those peers MUST go to the client store as peers of a relationship named in `include` do. For a generic kind, the HFID of each implementing kind applies to the nodes of that kind. For a kind without an HFID, naming `hfid` MUST change nothing. |
There was a problem hiding this comment.
The main reason to have this only parameter is for the sake of performance to avoid over fetching and only collect what we need. The benefits of this is a) less data on the wire to transfer b) less work for the infrahub server.
It seems like this choice could be sensible for schemas that only have attribute local components that compose the hfid of a node. However if the hfid has a part tied to a remote node through a relationship that lookup is a lot heavier for the backend than an extra attribute lookup (as the hfid is).
Say we have a sample schema:
- name: Character
namespace: Rpg
label: "Character"
icon: "mdi:shield-person"
default_filter: name__value
generate_template: true
order_by:
- name__value
display_label: "{{ name__value }}"
human_friendly_id: [name__value, profession__name__value]
#uniqueness_constraints: [[name__value]]
#display_labels: null
# display_label: "name__value"
inherit_from:
- LineageOwner
- LineageSource
- CoreArtifactTarget
- RpgEntity
attributes:
- name: name
description: The name of the character
kind: Text
unique: true
- name: description
description: Brief description
kind: Text
optional: true
relationships:
- name: profession
peer: RpgClass
optional: false
cardinality: one
kind: AttributeThis is just a snippet but the relevant part is the hfid that contains a related peer.
If we start the SDK with INFRAHUB_ECHO_GRAPHQL_QUERIES=true we get something like this:
>>> client.all(kind="RpgCharacter", only=["hfid"])
query All_RpgCharacter ($offset: Int!, $limit: Int!) {
RpgCharacter(offset: $offset, limit: $limit) {
count
edges {
node {
id
display_label
__typename
name {
value
}
profession {
node {
id
hfid
display_label
__typename
name {
value
}
}
}
}
}
}
}It could be that this decision was to avoid querying for an extra attribute, but the cost is instead that we need to include a relationship to the mix, then by default we also search for the hfid and display_label on the remote peer.
Typically you'd see this on Interface nodes where the hfid would combine the local attribute "name" with a relationship to a Device that also has an attribute called "Name". One of the reasons we introduced the hfid "attribute" was to avoid a dynamic lookup where the device would be evaluated as part of the query. As such I think this has to change.
On a node not from only, fetch() on ancestors or descendants raised ValueError before any request, because the reload removed the relationship's name from a list of schema fields that never contains it. Build the exclude list without that name instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use a TypedDict for the shared builder arguments in _build_filters_query, so mypy and ty check their names and types again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Check the query and the stored peer when parent declares a concrete kind and prefetch_relationships is set, and rename the test that compares the prefetch with the default query to say it holds when the generic is the declared peer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Infrahub's CI passed against this branch at commit 907694f, in opsmill/infrahub#11108: a draft on The jobs that ran include the backend unit tests (Python 3.12, 3.13 and 3.14), the component, functional, integration and Docker integration tests, the end-to-end tests and the benchmarks. The three commits pushed after 907694f (4a2ddfb, cf442ef and 9b5f70d) change |
- hfid in only now adds the server's hfid field to the queried node instead of the fields the HFID is made of, so the query no longer follows a relationship to read a peer attribute for an HFID part. - A node from that query returns the server's value from hfid, hfid_str and the get_human_friendly_id methods, None when it is empty or null, and the store indexes the node by it. Without hfid in only, the HFID is still calculated from the fetched fields. Nodes from default queries and peers keep calculating theirs. - Remove the HFID expansion, the HFID peers kept on a node, and the FieldNotFoundError raised for an HFID peer attribute that the peer kind lacks. Hierarchy fields keep using the peer kind that the kind declares for them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The spec, research, plan, data model, contract and quickstart now say that hfid in only requests the HFID the server stores, and no longer describe the HFID fields, the HFID peers or the declared peer kind of hierarchy fields. Research R5 gives the reason, with links to the server code and the review thread. - The alignment check records the owner decision of 2026-10-16, and the implementation report lists the change. - The query guide describes hfid in only as it now works, and the store guide says when a lookup by HFID returns the earlier copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…anges A node from a query with only that named hfid returns the HFID the server stored. After a local change to an attribute or relationship the HFID is built from, that value no longer matches the node, so the HFID is calculated from the fetched fields instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…70d (do not merge) Merges develop at 11fe17d and moves python_sdk to the current head of opsmill/infrahub-sdk-python#1423, to run Infrahub CI against the commits pushed after 907694f. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The contract and the query guide say again that parent and children use the peer kind that the kind declares for them, or the hierarchy generic when it declares none; the stored-HFID change keeps that behaviour. Research R4 and R15 now say so too. - The implementation report points at the rebased commits, records the change that stops using the stored HFID after a local edit, and gives the test results on the rebased branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Why
Developers who write integrations, scripts and generators with the SDK cannot ask for only some fields of a node. Every
get(),all()andfilters()query fetches the default set: every attribute and most relationships.includeadds to that set andexcluderemoves from it, so a query fetches more than the code reads and grows each time someone adds a field to the schema.Goal: a developer names the fields they need, gets those plus the fields the SDK needs, and the query stays the same when the schema grows. Adds
onlyin the API.Jira: INFP-532
-> Reviewed locally using claudes'
/code-review highNotes to reviewer:
DO NOT REVIEW THE CODE
hfidinonlyfollows hownode.hfidalready works: the SDK calculates it from the node's fields and never reads the server'shfid. So naminghfidfetches the fields it needs. Nodes not fromonlyare unchanged. Details: spec.md (FR-005).is_loadedlets code check whether a field was fetched before reading it.** On a node fromonly, reading an unfetched field raisesFieldNotLoadedError. Before this PR, a field missing from the response and a field set tonullboth read asNone.is_loadedworks on every node, including afterexclude, and never raises.Existing bugs found during end-to-end testing
These came up while testing this pull request against Infrahub 1.11.5. None of them is caused by this pull request.
ancestors.fetch()anddescendants.fetch()raiseValueErrorwhen the relationship was not loaded.node_metadataon an emptyparentfails on the server.save()then fails.