From 4d735934e088a70bb375f8b5ab4b11358228684a Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Mon, 5 Jan 2026 21:50:24 +0000 Subject: [PATCH 1/3] Add docs for metadata and ordering --- docs/docs/python-sdk/guides/query_data.mdx | 213 +++++++++++++++++++++ 1 file changed, 213 insertions(+) diff --git a/docs/docs/python-sdk/guides/query_data.mdx b/docs/docs/python-sdk/guides/query_data.mdx index fde6667f5..0bb80f79c 100644 --- a/docs/docs/python-sdk/guides/query_data.mdx +++ b/docs/docs/python-sdk/guides/query_data.mdx @@ -527,6 +527,219 @@ By default, the [meta data or properties]($(base_url)topics/metadata) of attribu +## Node metadata + +Node metadata provides information about when a node was created or last updated, and by whom. This includes timestamps and references to the accounts that made the changes. + +### Including node metadata in queries + +By default, node metadata is not included in query results. You can include it using the `include_metadata` argument of the SDK client's `all`, `filters`, or `get` method. + + + + + ```python + device = await client.get(kind="TestDevice", name__value="atl1-edge1", include_metadata=True) + ``` + + + + + ```python + device = client.get(kind="TestDevice", name__value="atl1-edge1", include_metadata=True) + ``` + + + + +### Accessing node metadata + +Once metadata is included in the query, you can access it using the `get_node_metadata()` method. The metadata object contains the following fields: + +- `created_at`: Timestamp when the node was created +- `created_by`: Reference to the account that created the node +- `updated_at`: Timestamp when the node was last updated +- `updated_by`: Reference to the account that last updated the node + + + + + ```python + device = await client.get(kind="TestDevice", name__value="atl1-edge1", include_metadata=True) + + # Get the metadata object + metadata = device.get_node_metadata() + + # Access creation metadata + print(metadata.created_at) # e.g., "2024-01-15T10:30:00Z" + print(metadata.created_by.display_label) # e.g., "admin" + + # Access update metadata + print(metadata.updated_at) # e.g., "2024-01-20T14:45:00Z" + print(metadata.updated_by.display_label) # e.g., "admin" + ``` + + + + + ```python + device = client.get(kind="TestDevice", name__value="atl1-edge1", include_metadata=True) + + # Get the metadata object + metadata = device.get_node_metadata() + + # Access creation metadata + print(metadata.created_at) # e.g., "2024-01-15T10:30:00Z" + print(metadata.created_by.display_label) # e.g., "admin" + + # Access update metadata + print(metadata.updated_at) # e.g., "2024-01-20T14:45:00Z" + print(metadata.updated_by.display_label) # e.g., "admin" + ``` + + + + +The `created_by` and `updated_by` fields are `NodeProperty` objects that include: + +- `id`: The unique identifier of the account +- `display_label`: A human-readable label for the account +- `typename`: The GraphQL type name of the account + +## Ordering query results + +You can control the order in which query results are returned using the `order` argument. This is particularly useful when you need results sorted by metadata fields like creation or update timestamps. + +### Ordering by node metadata + +Use the `Order` and `NodeMetaOrder` classes along with `OrderDirection` to specify how results should be ordered. + + + + + ```python + from infrahub_sdk.enums import OrderDirection + from infrahub_sdk.types import NodeMetaOrder, Order + + # Get devices ordered by creation time (oldest first) + devices = await client.all( + kind="TestDevice", + order=Order(node_metadata=NodeMetaOrder(created_at=OrderDirection.ASC)) + ) + + # Get devices ordered by last update time (most recent first) + devices = await client.all( + kind="TestDevice", + order=Order(node_metadata=NodeMetaOrder(updated_at=OrderDirection.DESC)) + ) + ``` + + + + + ```python + from infrahub_sdk.enums import OrderDirection + from infrahub_sdk.types import NodeMetaOrder, Order + + # Get devices ordered by creation time (oldest first) + devices = client.all( + kind="TestDevice", + order=Order(node_metadata=NodeMetaOrder(created_at=OrderDirection.ASC)) + ) + + # Get devices ordered by last update time (most recent first) + devices = client.all( + kind="TestDevice", + order=Order(node_metadata=NodeMetaOrder(updated_at=OrderDirection.DESC)) + ) + ``` + + + + +The available order directions are: + +- `OrderDirection.ASC`: Ascending order (oldest/smallest first) +- `OrderDirection.DESC`: Descending order (newest/largest first) + +:::note +You can only order by one metadata field at a time. Specifying both `created_at` and `updated_at` in the same `NodeMetaOrder` will raise a validation error, as they are mutually exclusive. +::: + +### Disabling default ordering + +For performance optimization, you can disable the default ordering behavior entirely: + + + + + ```python + from infrahub_sdk.types import Order + + # Disable ordering to improve query performance + devices = await client.all(kind="TestDevice", order=Order(disable=True)) + ``` + + + + + ```python + from infrahub_sdk.types import Order + + # Disable ordering to improve query performance + devices = client.all(kind="TestDevice", order=Order(disable=True)) + ``` + + + + +### Combining metadata and ordering + +You can include metadata and order results in the same query: + + + + + ```python + from infrahub_sdk.enums import OrderDirection + from infrahub_sdk.types import NodeMetaOrder, Order + + # Get the 10 most recently updated devices with their metadata + devices = await client.filters( + kind="TestDevice", + limit=10, + include_metadata=True, + order=Order(node_metadata=NodeMetaOrder(updated_at=OrderDirection.DESC)) + ) + + for device in devices: + metadata = device.get_node_metadata() + print(f"{device.name.value} - Last updated: {metadata.updated_at}") + ``` + + + + + ```python + from infrahub_sdk.enums import OrderDirection + from infrahub_sdk.types import NodeMetaOrder, Order + + # Get the 10 most recently updated devices with their metadata + devices = client.filters( + kind="TestDevice", + limit=10, + include_metadata=True, + order=Order(node_metadata=NodeMetaOrder(updated_at=OrderDirection.DESC)) + ) + + for device in devices: + metadata = device.get_node_metadata() + print(f"{device.name.value} - Last updated: {metadata.updated_at}") + ``` + + + + ## Query a node(s) in a different branch If you want to query a node(s) in a different branch than the default branch with which the SDK client was initiated, then you can use the `branch` argument of the query methods. From f08a9c31f4258feedf86758fdc4685aebd3bd908 Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Thu, 8 Jan 2026 09:54:09 +0000 Subject: [PATCH 2/3] Add relationship metadata --- docs/docs/python-sdk/guides/query_data.mdx | 60 ++++++++++++++++++++++ test.py | 12 +++++ 2 files changed, 72 insertions(+) create mode 100644 test.py diff --git a/docs/docs/python-sdk/guides/query_data.mdx b/docs/docs/python-sdk/guides/query_data.mdx index 0bb80f79c..74fb3ec9f 100644 --- a/docs/docs/python-sdk/guides/query_data.mdx +++ b/docs/docs/python-sdk/guides/query_data.mdx @@ -606,6 +606,66 @@ The `created_by` and `updated_by` fields are `NodeProperty` objects that include - `display_label`: A human-readable label for the account - `typename`: The GraphQL type name of the account +## Relationship metadata + +When querying with `include_metadata=True`, you can also access metadata about relationship edges themselves. This tells you when a specific relationship (the connection between two nodes) was last modified and by whom. + +### Accessing relationship metadata + +Use the `get_relationship_metadata()` method on a related node to access the relationship edge metadata. This is different from node metadata - it describes when the relationship itself was created or modified, not the connected node. + + + + + ```python + device = await client.get(kind="TestDevice", name__value="atl1-edge1", include_metadata=True) + + # For a cardinality-one relationship + rel_metadata = device.site.get_relationship_metadata() + if rel_metadata: + print(rel_metadata.updated_at) # e.g., "2024-01-17T08:00:00Z" + print(rel_metadata.updated_by.display_label) # e.g., "admin" + + # For a cardinality-many relationship + for tag in device.tags.peers: + rel_metadata = tag.get_relationship_metadata() + if rel_metadata: + print(f"Tag relationship updated at: {rel_metadata.updated_at}") + print(f"Updated by: {rel_metadata.updated_by.display_label}") + ``` + + + + + ```python + device = client.get(kind="TestDevice", name__value="atl1-edge1", include_metadata=True) + + # For a cardinality-one relationship + rel_metadata = device.site.get_relationship_metadata() + if rel_metadata: + print(rel_metadata.updated_at) # e.g., "2024-01-17T08:00:00Z" + print(rel_metadata.updated_by.display_label) # e.g., "admin" + + # For a cardinality-many relationship + for tag in device.tags.peers: + rel_metadata = tag.get_relationship_metadata() + if rel_metadata: + print(f"Tag relationship updated at: {rel_metadata.updated_at}") + print(f"Updated by: {rel_metadata.updated_by.display_label}") + ``` + + + + +The `RelationshipMetadata` object contains: + +- `updated_at`: Timestamp when the relationship was last updated +- `updated_by`: Reference to the account that last updated the relationship (a `NodeProperty` object with `id`, `display_label`, and `typename`) + +:::note +Relationship metadata tracks changes to the relationship edge itself (for example, when the relationship was created or when its properties were modified), not changes to the connected nodes. For node-level metadata, use `get_node_metadata()` on the node itself. +::: + ## Ordering query results You can control the order in which query results are returned using the `order` argument. This is particularly useful when you need results sorted by metadata fields like creation or update timestamps. diff --git a/test.py b/test.py new file mode 100644 index 000000000..7099b7841 --- /dev/null +++ b/test.py @@ -0,0 +1,12 @@ +from infrahub_sdk import InfrahubClientSync +from infrahub_sdk.types import NodeMetaOrder, Order +from infrahub_sdk.enums import OrderDirection + +client = InfrahubClientSync() + +tags = client.all("BuiltinTag", include_metadata=True, order=Order(node_metadata=NodeMetaOrder(updated_at=OrderDirection.DESC))) + +for tag in tags: + # print(f"Tag: {tag.name.value}") + metadata = tag.get_node_metadata() + print(f"{tag.name.value} - Last updated: {metadata.updated_at}") \ No newline at end of file From 0a44c59cfee1c65663af12cabe25a33ad96535b9 Mon Sep 17 00:00:00 2001 From: Alex Gittings Date: Thu, 8 Jan 2026 13:08:57 +0000 Subject: [PATCH 3/3] Remove test file --- test.py | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 test.py diff --git a/test.py b/test.py deleted file mode 100644 index 7099b7841..000000000 --- a/test.py +++ /dev/null @@ -1,12 +0,0 @@ -from infrahub_sdk import InfrahubClientSync -from infrahub_sdk.types import NodeMetaOrder, Order -from infrahub_sdk.enums import OrderDirection - -client = InfrahubClientSync() - -tags = client.all("BuiltinTag", include_metadata=True, order=Order(node_metadata=NodeMetaOrder(updated_at=OrderDirection.DESC))) - -for tag in tags: - # print(f"Tag: {tag.name.value}") - metadata = tag.get_node_metadata() - print(f"{tag.name.value} - Last updated: {metadata.updated_at}") \ No newline at end of file