@@ -78,13 +78,132 @@ For a more complete example, refer to the example in the repository:
7878
7979<ReferenceLink title = " Custom Adapter Example" url = " https://github.com/opsmill/infrahub-sync/tree/main/examples/custom_adapter" openInNewTab />
8080
81+ ### Adding custom Jinja filters
82+
83+ Custom adapters can provide their own Jinja filters for use in transform expressions. This is particularly useful for adapter-specific data transformations.
84+
85+ #### Implementing custom filters
86+
87+ To add custom filters to your adapter, implement the ` _add_custom_filters ` class method in your DiffSync model class:
88+
89+ ``` python
90+ from typing import Any, ClassVar
91+ from diffsync import DiffSyncModel
92+ from infrahub_sync import DiffSyncModelMixin
93+
94+ class MyCustomModel (DiffSyncModelMixin , DiffSyncModel ):
95+ # Store any data needed by filters as class variables
96+ _my_mapping: ClassVar[dict[str , str ]] = {}
97+
98+ @ classmethod
99+ def set_my_mapping (cls , mapping : dict[str , str ]) -> None :
100+ """ Set mapping data for use in filters."""
101+ cls ._my_mapping = mapping
102+
103+ @ classmethod
104+ def _add_custom_filters (cls , native_env : Any, item : dict[str , Any]) -> None :
105+ """ Add custom filters to the Jinja environment."""
106+
107+ def my_custom_filter (value : str ) -> str :
108+ """ Custom filter that transforms values using stored mapping."""
109+ return cls ._my_mapping.get(str (value), value)
110+
111+ def format_identifier (value : str ) -> str :
112+ """ Another custom filter for formatting identifiers."""
113+ return f " ID- { value.upper()} "
114+
115+ # Register filters with the Jinja environment
116+ native_env.filters[" my_custom_filter" ] = my_custom_filter
117+ native_env.filters[" format_identifier" ] = format_identifier
118+ ```
119+
120+ #### Setting up filter data
121+
122+ If your filters need data (like mappings, lookup values, etc.), initialize it in your adapter:
123+
124+ ``` python
125+ class MyCustomAdapter (DiffSyncMixin , Adapter ):
126+ def __init__ (self , target , adapter , config , * args , ** kwargs ):
127+ super ().__init__ (* args, ** kwargs)
128+ # ... other initialization
129+
130+ # Build data needed by filters
131+ my_mapping = self ._build_custom_mapping()
132+
133+ # Pass data to model class for filter use
134+ MyCustomModel.set_my_mapping(my_mapping)
135+
136+ def _build_custom_mapping (self ) -> dict[str , str ]:
137+ """ Build mapping data from your data source."""
138+ # Implementation depends on your data source
139+ return {" key1" : " value1" , " key2" : " value2" }
140+ ```
141+
142+ #### Using custom filters in configuration
143+
144+ Once implemented, use your custom filters in transform expressions:
145+
146+ ``` yaml
147+ schema_mapping :
148+ - name : MyModel
149+ mapping : " api/endpoint"
150+ fields :
151+ - name : identifier
152+ mapping : " raw_id"
153+ - name : formatted_name
154+ mapping : " name"
155+ transforms :
156+ - field : identifier
157+ expression : " {{ raw_id | my_custom_filter | format_identifier }}"
158+ - field : status
159+ expression : " {{ 'active' if enabled else 'inactive' }}"
160+ ` ` `
161+
162+ #### Filter implementation guidelines
163+
164+ 1. **Keep filters focused**: Each filter should do one specific transformation
165+ 2. **Handle edge cases**: Always provide fallback values for missing data
166+ 3. **Use class variables**: Store filter data as class variables for efficient access
167+ 4. **Document your filters**: Add Python documentation strings explaining what each filter does
168+ 5. **Test thoroughly**: Ensure filters work with various input types and edge cases
169+
170+ #### Real-world example: ACI device name filter
171+
172+ Here's how the ACI adapter implements the ` aci_device_name` filter:
173+
174+ ` ` ` python
175+ class AciModel(DiffSyncModelMixin, DiffSyncModel):
176+ _device_mapping: ClassVar[dict[str, str]] = {}
177+
178+ @classmethod
179+ def set_device_mapping(cls, device_mapping: dict[str, str]) -> None:
180+ cls._device_mapping = device_mapping
181+
182+ @classmethod
183+ def _add_custom_filters(cls, native_env: Any, item: dict[str, Any]) -> None:
184+ def aci_device_name(node_id: str) -> str:
185+ """Resolve ACI node IDs to device names."""
186+ return cls._device_mapping.get(str(node_id), node_id)
187+
188+ native_env.filters["aci_device_name"] = aci_device_name
189+ ` ` `
190+
191+ Used in configuration :
192+
193+ ` ` ` yaml
194+ transforms:
195+ - field: device
196+ expression: "{{ l1PhysIf.attributes.dn.split('/')[2].replace('node-', '') | aci_device_name }}"
197+ ` ` `
198+
81199# ## Best practices
82200
832011. **Package Structure** : Organize complex adapters as packages with `__init__.py`
842022. **Testing** : Include test data and documentation with your adapter
852033. **Configuration** : Use settings to make your adapter configurable
862044. **Error Handling** : Implement proper error handling and logging
872055. **Type Annotations** : Use type hints to make your code more maintainable
206+ 6. **Custom Filters** : Implement adapter-specific Jinja filters for complex transformations
88207
89208# ## Local adapter example
90209
0 commit comments