Overview
This guide explains how to use Python's type system effectively with the Infrahub SDK, focusing on the use of Protocols for type-safe development.
Python typing allows you to specify the expected data types of variables, function arguments, and return values to improve code clarity and catch bugs early.
# Basic type hints
def percentage(num1: int, num2: int) -> float:
return (num1 / num2) * 100
Leveraging Python protocols
The Python SDK for Infrahub has been designed to automatically work with any schemas loaded into Infrahub. Internally, the Python SDK generates dynamic Python representations of your schemas.
While this approach improves code readability, it presents challenges with type checking because each object has a different signature based on your schema.
Without protocols
In the example below, type checkers like Mypy will typically complain about blue_tag.description.value
because description
is a dynamic parameter generated by the SDK.
# Type checker cannot verify the existence of 'description'
blue_tag = client.get("BuiltinTag", name__value="blue") # blue_tag is of type InfrahubNode or InfrahubNodeSync
blue_tag.description.value = "The blue tag" # Mypy: error: "InfrahubNode" has no attribute "description"
blue_tag.save()
With protocols
To provide strict type checking while maintaining platform extensibility, the Python SDK integrates with Python Protocols.
For all core and internal models, the protocols are included in the SDK under infrahub_sdk.protocols
.
Whenever you need to specify the kind of object you're working with as a string, you can use the corresponding protocol instead.
from infrahub_sdk.protocols import BuiltinTag
# Type checker can now verify all attributes
blue_tag = client.get(BuiltinTag, name__value="blue") # blue_tag is of type BuiltinTag
blue_tag.description.value = "The blue tag" # No type errors
blue_tag.save()
Python Protocols, introduced in PEP 544, define a set of method and property signatures that a class must implement to be considered a match, enabling structural subtyping (also known as "duck typing" with static checks). They allow you to specify behavior without requiring inheritance, making code more flexible and type-safe.
More information about Python Protocols can be found here
Generating custom protocols based on your schema
You can generate Python Protocols for your own models using the infrahubctl protocols
command. This supports both synchronous and asynchronous Python code.
It's possible to provide the schema from a local directory or from an existing Infrahub Instance.
- Existing Infrahub Instance
- Local Directory
export INFRAHUB_ADDRESS=https://infrahub.example.com
infrahubctl protocols --out lib/protocols.py --sync
infrahubctl protocols --schemas schemas/tag.schema.yml --out lib/protocols.py
When using a local directory, Protocols for Profiles and Object Templates won't be generated.
Using custom protocols
After generation, you can import and use your custom protocols as describe below.
from lib.protocols import MyOwnObject
# Use your custom protocol
my_object = client.get(MyOwnObject, name__value="example")
if you don't have your own Python module, it's possible to use relative path by having the
procotols.py
in the same directory as your script/transform/generator
Generating Pydantic models from GraphQL queries
When working with GraphQL queries, you can generate type-safe Pydantic models that correspond to your query return types. This provides excellent type safety and IDE support for your GraphQL operations.
Why use generated return types?
Generated Pydantic models from GraphQL queries offer several important benefits:
- Type Safety: Catch type errors at development time instead of runtime
- IDE Support: Get autocomplete, type hints, and better IntelliSense in your IDE
- Documentation: Generated models serve as living documentation of your GraphQL API
- Validation: Automatic validation of query responses against the expected schema
Generating return types
Use the infrahubctl graphql generate-return-types
command to create Pydantic models from your GraphQL queries:
# Generate models for queries in current directory
infrahubctl graphql generate-return-types
# Generate models for specific query files
infrahubctl graphql generate-return-types queries/get_devices.gql
You can also export the GraphQL schema first using the
infrahubctl graphql export-schema
command:
Example workflow
-
Create your GraphQL queries in
.gql
files: -
Generate the Pydantic models:
infrahubctl graphql generate-return-types queries/
The command will generate the Python file per query based on the name of the query.
-
Use the generated models in your Python code
from .queries.get_devices import GetDevicesQuery
response = await client.execute_graphql(query=MY_QUERY)
data = GetDevicesQuery(**response)