Building Custom Local Adapters
Relevant source files
The following files were used as context for generating this wiki page:
Building custom local adapters allows you to connect Infrahub Sync to external systems that do not have built-in connectors. Custom adapters define how to read data from a source and how to write data to a destination by implementing a specific contract with the potenda sync engine. This module provides the flexibility to handle bespoke SDKs, proprietary protocols, or specific REST API patterns not covered by the generic REST adapter.
The architecture relies on two primary classes: an Adapter class for orchestrating communication and a Model class that defines the data structure and mutation logic. These classes leverage mixins provided by infrahub-sync to integrate with the declarative schema_mapping defined in the project configuration.
Sources: dev/guides/adding-an-adapter.md:1-17, dev/knowledge/sync-architecture.md:1-12
Adapter Architecture and Components
Custom adapters integrate into the Infrahub Sync ecosystem by inheriting from both diffsync base classes and infrahub-sync mixins. The engine uses these classes to load objects into an in-memory store for comparison and reconciliation.
Class Hierarchy
Custom adapters must follow a specific inheritance order to ensure mixin methods take precedence over base class methods.
The diagram shows the required inheritance structure where infrahub-sync mixins are declared before diffsync bases.
Sources: dev/knowledge/adapter-anatomy.md:10-30, dev/guidelines/writing-an-adapter.md:8-15
Core Components
| Component | Responsibility |
|---|---|
| Adapter Class | Manages client initialization, authentication, and orchestrates the loading of records from the external system. |
| Model Class | Defines the structure of the data and implements mutation methods (create, update, delete) for destination roles. |
| model_loader | Fetches raw records, applies filters/transforms, and converts them to the DiffSync format. |
| obj_to_diffsync | A helper method that maps raw source fields to the destination model fields based on the schema mapping. |
Sources: dev/knowledge/adapter-anatomy.md:32-45, examples/custom_adapter/custom_adapter_src/custom_adapter.py:148-185
Implementation Workflow
Implementing a custom adapter involves defining the logic to fetch data and mapping it to the internal synchronization engine.
1. Adapter Initialization
You initialize the adapter class by providing the target role ("source" or "destination"), the adapter settings from the configuration, and the overall sync configuration.
class MysystemAdapter(DiffSyncMixin, Adapter):
type = "MySystem"
def __init__(self, target, adapter, config, *args, **kwargs):
super().__init__(*args, **kwargs)
self.target = target
self.config = config
self.settings = adapter.settings or {}
self.client = self._create_client(self.settings)
Sources: dev/guides/adding-an-adapter.md:40-52, examples/custom_adapter/custom_adapter_src/custom_adapter.py:91-109
2. Record Loading (model_loader)
The model_loader method fetches data for a specific model name. If the adapter acts as a source, it must filter and transform records using the model mixin before adding them to the store.
The flowchart illustrates the sequence of operations within model_loader during the synchronization load phase.
Sources: dev/knowledge/adapter-anatomy.md:35-43, examples/custom_adapter/custom_adapter_src/custom_adapter.py:111-146
3. Data Transformation (obj_to_diffsync)
The conversion from an external object to a DiffSync-compatible dictionary must handle three types of field mappings:
- Static: Assigns a literal value defined in the YAML config.
- Mapping: Extracts a value from a specific key in the source record.
- Reference: Resolves a link to another object using the peer object's
unique_idstored in the adapter.
Sources: dev/knowledge/schema-mapping.md:27-42, examples/custom_adapter/custom_adapter_src/custom_adapter.py:148-185
Configuration and Loading
Custom adapters are registered in the config.yml file by providing a filesystem path or a dotted Python import path.
source:
name: mysystem
adapter: ./path/to/my_adapter.py:MysystemAdapter
settings:
url: "https://api.example.com"
Sources: dev/guides/adding-an-adapter.md:82-95, dev/knowledge/adapter-anatomy.md:92-100
Loading Mechanics
Potenda uses a plugin loader to resolve custom adapter classes. The generator reads the schema_mapping to render the model classes from templates, which are then wired to the adapter instance at runtime.
This diagram shows how the system resolves and prepares custom adapters before starting the synchronization process.
Sources: dev/knowledge/sync-architecture.md:40-52, dev/knowledge/adapter-anatomy.md:92-100
Development Guidelines
To maintain consistency and safety, custom adapters must adhere to the following rules:
- Mixin Order: Always declare
DiffSyncMixinbeforeAdapter. - Logging: Use
structlogfor structured logging. Never log secrets, tokens, or credentials. - Error Handling: Raise specific exceptions for authentication failures (401/403) or timeouts instead of broad
except Exceptionblocks. - Credentials: Resolve credentials from environment variables first, using the
settingsdictionary as a fallback. - Optional Dependencies: Import external SDKs as optional dependencies with
# ty: ignore[unresolved-import].
Sources: dev/guidelines/writing-an-adapter.md:8-63, dev/guides/adding-an-adapter.md:54-57
Custom local adapters serve as the primary extension point for integrating disparate infrastructure systems. By implementing the mandated DiffSync contract, developers ensure that their bespoke integrations benefit from the core engine's idempotency, dependency ordering, and dry-run capabilities.