diff --git a/docs/docs/python-sdk/guides/query_data.mdx b/docs/docs/python-sdk/guides/query_data.mdx index fde6667f..74fb3ec9 100644 --- a/docs/docs/python-sdk/guides/query_data.mdx +++ b/docs/docs/python-sdk/guides/query_data.mdx @@ -527,6 +527,279 @@ 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 + +## 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. + +### 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.