Querying data in Infrahub
We can query data in 3 ways using the SDK:
- querying a single node of a given kind, based on some filters, using the
get
method - querying multiple nodes of a given kind, based on some filters, using the
filters
method - querying all the nodes of a given kind, using the
all
method - querying with a GraphQL query, using the
execute_graphql
method
Query filters
The get
and filters
query methods allow you to use filters. Filters specify on which attribute(s) or attributes of a relationship(s) the resulting node(s) should match. The available filters depend on the kind of Node you want to query and are dynamically generated from the schema.
Discovering available filters
The easiest way to discover the available filters for a kind of node is by querying the schema.
- Async
- Sync
tag_schema = await client.schema.get("BuiltinTag")
print(tag_schema.filters)
tag_schema = client.schema.get("BuiltinTag")
print(tag_schema.filters)
Attribute filters
For every attribute the following filters will be generated (replace attribute with the name of the attribute):
attribute__value
: filter for a single attribute valueattribute__values
: (list) filter for multiple attribute valuesattribute__is_visible
: (boolean) filter for theis_visible
property of an attributeattribute__is_protected
: (boolean) filter for theis_protected
property of an attributeattribute__source__id
: filter for thesource
property of an attributeattribute__owner__id
: filter for theowner
property of an attribute
Relationship filters
For every attribute of a relationship of the node, the same filters will be generated. The name of the filter will be prefixed with the relationship name followed by 2 underscores relationship__attribute__value
.
Using filters
Filters can be passed as arguments to the get
or filters
method.
- Async
- Sync
tag = await client.get(kind="BuiltinTag", name__value="RED")
tag = client.get(kind="BuiltinTag", name__value="RED")
Using multiple filters
When you pass multiple filters as argument to the get
or filters
method, they will combined in a logical AND operation. The resulting nodes of your query will match on all the filters in that case.
- Async
- Sync
tags = await client.filters(kind="BuiltinTag", name__values=["RED", "BLUE"], name__is_protected=True)
tags = client.filters(kind="BuiltinTag", name__values=["RED", "BLUE"], name__is_protected=True)
Querying a single node
You can query Infrahub for a single node of a particular kind, by using the get
method and using 1 or more filters.
- Async
- Sync
tag = await client.get(kind="BuiltinTag", name__value="RED")
tag = client.get(kind="BuiltinTag", name__value="RED")
Querying multiple nodes
You can query Infrahub for multiple nodes of a particular kind, by using the filters
method and using 1 or more filters.
- Async
- Sync
tags = client.filters(kind="BuiltinTag", name__values=["RED", "BLUE"])
tags = client.filters(kind="BuiltinTag", name__values=["RED", "BLUE"])
Querying all nodes
You can query Infrahub for all nodes of a particular kind, by using the all
method.
- Async
- Sync
tags = await client.all(kind="BuiltinTag")
tags = client.all(kind="BuiltinTag")
Querying with a GraphQL query
In some scenarios it might be more convenient to query Infrahub using a GraphQL query, rather than using the builtin mechanisms in the SDK.
An example might be finding all the devices connected to a given circuit. This would require us to execute multiple queries using the nodes provided by the SDK. However with a GraphQL query this can be achieved using only a single query.
The downside of using this method, is that it will not construct Python objects for the resulting data. Instead the SDK will return a Python dictionary containing the deserialized JSON data returned by the GraphQL API.
- Async
- Sync
query = """query {
BuiltinTag(name__values: ["RED", "BLUE"]) {
edges {
node {
name {
value
}
}
}
}
}"""
data = await client.execute_graphql(query=query)
for tag in data["BuiltinTag"]["edges"]:
print(tag["node"]["name"]["value"])
query = """query {
BuiltinTag(name__values: ["RED", "BLUE"]) {
edges {
node {
name {
value
}
}
}
}
}"""
data = client.execute_graphql(query=query)
for tag in data["BuiltinTag"]["edges"]:
print(tag["node"]["name"]["value"])
Attributes and relationships
By default, the result of a query will include attributes, relationships of cardinality one and relationships of kind Attribute or Parent. Relationships that are included in a query will be automatically initialized which means the ID, type and display name of the peers will be included in the query. But the related node itself will not be included.
To explore this in a bit more details, we are going to assume the following schema has been loaded into Infrahub
---
version: "1.0"
nodes:
- name: Device
namespace: Test
attributes:
- name: name
kind: Text
unique: true
optional: false
relationships:
- name: tags
cardinality: many
kind: Attribute
peer: BuiltinTag
optional: true
- name: site
cardinality: one
kind: Attribute
peer: TestSite
optional: false
- name: interfaces
cardinality: many
kind: Component
peer: TestInterface
optional: false
- name: Site
namespace: Test
attributes:
- name: name
kind: Text
unique: true
optional: false
relationships:
- name: device
cardinality: many
kind: Attribute
peer: TestDevice
optional: true
- name: Interface
namespace: Test
attributes:
- name: name
kind: Text
unique: true
optional: false
relationships:
- name: device
cardinality: one
kind: Parent
peer: TestDevice
optional: true
Attributes
Attributes are included by default.
- Async
- Sync
device = await client.get(kind="BuiltinTag", name__value="atl1-edge1")
print(device.name.value)
device = client.get(kind="BuiltinTag", name__value="atl1-edge1")
print(device.name.value)
Relationships of cardinality one
Relationships of cardinality one are included by default and will be initialized.
- Async
- Sync
device = await client.get(kind="TestDevice", name__value="atl1-edge1")
print(device.site.initialized) # True
print(device.site.id, device.site.display_label, device.site.typename)
print(device.site.peer) # the related node is not included
device = client.get(kind="TestDevice", name__value="atl1-edge1")
print(device.site.initialized) # True
print(device.site.id, device.site.display_label, device.site.typename)
print(device.site.peer) # the related node is not included
Relationships of kind Attribute
, Parent
are included by default and will be initialized.
- Async
- Sync
device = await client.get(kind="TestDevice", name__value="atl1-edge1")
print(device.tags.initialized) # True
print(device.tags.peers)
for tag in device.tags.peers:
print(tag.id, tag.display_label, tag.typename)
device = client.get(kind="TestDevice", name__value="atl1-edge1")
print(device.tags.initialized) # True
print(device.tags.peers)
for tag in device.tags.peers:
print(tag.id, tag.display_label, tag.typename)
Relationships of cardinality many are not included by default
Relationships of cardinality many are not included by default.
- Async
- Sync
device = await client.get(kind="TestDevice", name__value="atl1-edge1")
print(device.interfaces.initialized) # False
print(device.interfaces.peers) # empty list []
device = client.get(kind="TestDevice", name__value="atl1-edge1")
print(device.interfaces.initialized) # False
print(device.interfaces.peers) # empty list []
Including attributes and relationships
You can include attributes and relationships that are not retrieved as part of a query by default. The included relationships will be initialized and the related nodes (peers) will be initialized.
- Async
- Sync
device = await client.get(kind="TestDevice", name__value="atl1-edge1", include=["interfaces"])
print(device.interfaces.initialized) # True
print(device.interfaces.peers)
for interface in device.interfaces.peers:
print(interface.id, interface.display_label, interface.typename)
device = client.get(kind="TestDevice", name__value="atl1-edge1", include=["interfaces"])
print(device.interfaces.initialized) # True
print(device.interfaces.peers)
for interface in device.interfaces.peers:
print(interface.id, interface.display_label, interface.typename)
Excluding attribute and relationships
You can exclude attributes and relationships that are retrieved with a query by default. This can be useful if you need to optimize or speed up a particular query.
- Async
- Sync
device = await client.get(kind="TestDevice", exclude=["site"])
print(device.site) # None
device = client.get(kind="TestDevice", exclude=["site"])
print(device.site) # None
Fetching relationships manually
The fetch
method can be used to retrieve relationships that are not retrieved as part of a query. The fetch
method will initialized the relationship, if was not yet initialized and retrieve the related nodes.
- Async
- Sync
device = await client.get(kind="TestDevice", name__value="atl1-edge1")
print(device.interfaces.initialized) # False
await device.interfaces.fetch()
print(device.interfaces.initialized) # True
for interface in device.interfaces.peers:
print(interface.id, interface.display_label, interface.typename, interface.peer.name.value)
device = client.get(kind="TestDevice", name__value="atl1-edge1")
print(device.interfaces.initialized) # False
device.interfaces.fetch()
print(device.interfaces.initialized) # True
for interface in device.interfaces.peers:
print(interface.id, interface.display_label, interface.typename, interface.peer.name.value)
The fetch
method can also be used to fetch
the related nodes of a relationship that was already initialized or retrieved as part of the query.
- Async
- Sync
device = await client.get(kind="TestDevice", name__value="atl1-edge1", include=["interfaces"])
print(device.interfaces.initialized) # True
await device.interfaces.fetch()
for interface in device.interfaces.peers:
print(interface.id, interface.display_label, interface.typename, interface.peer.name.value)
device = client.get(kind="TestDevice", name__value="atl1-edge1", include=["interfaces"])
print(device.interfaces.initialized) # True
device.interfaces.fetch()
for interface in device.interfaces.peers:
print(interface.id, interface.display_label, interface.typename, interface.peer.name.value)
Prefetch relationships
Related nodes of a relationship can be retrieved, using the prefetch_relationships
argument for the different query
methods. But this requires the usage of the internal client store
. More information can be found in the Using the client store guide.
Query a node(s) in the past
To query the state of a Node in the past, you can pass the at
argument to all the query methods. The at argument accepts a str
, DateTime
or Timestamp
object as value.
Values of type str
will be parsed using the Pendulum library.
- Async
- Sync
device = await client.get(kind="TestDevice", name__value="atl1-edge1", at="10:10:10")
device = client.get(kind="TestDevice", name__value="atl1-edge1", at="10:10:10")
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.
- Async
- Sync
device = client.get(kind="TestDevice", name__value="atl1-edge1", branch="refresh-site-atl1")
device = client.get(kind="TestDevice", name__value="atl1-edge1", branch="refresh-site-atl1")