Skip to main content

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.

What is Python Typing

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

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.

export INFRAHUB_ADDRESS=https://infrahub.example.com
infrahubctl protocols --out lib/protocols.py --sync

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

  1. Create your GraphQL queries in .gql files:

  2. 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.

  3. 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)