Skip to main content

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.

tag_schema = await 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 value
  • attribute__values: (list) filter for multiple attribute values
  • attribute__is_visible: (boolean) filter for the is_visible property of an attribute
  • attribute__is_protected: (boolean) filter for the is_protected property of an attribute
  • attribute__source__id: filter for the source property of an attribute
  • attribute__owner__id: filter for the owner 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.

tag = await 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.

tags = await 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.

tag = await 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.

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.

tags = await 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.

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"])

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.

device = await 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.

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

Relationships of kind Attribute, Parent are included by default and will be initialized.

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)

Relationships of cardinality many are not included by default

Relationships of cardinality many are not included by default.

device = await 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.

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)

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.

device = await 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.

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)

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.

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)

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.

device = await 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.

device = client.get(kind="TestDevice", name__value="atl1-edge1", branch="refresh-site-atl1")