Skip to content

bug: Allow optional mapping#101

Merged
minitriga merged 2 commits intomainfrom
bkr-optional-mapping
Oct 22, 2025
Merged

bug: Allow optional mapping#101
minitriga merged 2 commits intomainfrom
bkr-optional-mapping

Conversation

@BeArchiTek
Copy link
Contributor

@BeArchiTek BeArchiTek commented Oct 22, 2025

Right now, if you are trying to map a static reference to a model not present in the schema_mapping of the Adapter, it will be unable to find the model, therefore be unable to use it.

To bypass that, we want to be able to add schema_mapping without mapping towards a source model.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Adapters now gracefully skip schema mapping elements without defined mappings, preventing processing failures.
  • Improvements

    • Enhanced data model definitions with expanded public attributes and fields across multiple example adapters.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Walkthrough

This pull request makes the mapping field optional throughout the codebase and adds consistent guards to skip unmapped schema elements during model loading. The core library changes include making SchemaMappingModel.mapping nullable and updating all adapter implementations (ACI, Generic REST API, IPFabric, Nautobot, Netbox, Prometheus, and Slurp) with early-exit logic when elements lack a mapping. Example projects are enhanced with expanded model class definitions, updated base class resolution via PluginLoader, and refined public attribute declarations across generated sync models, affecting how model metadata is exposed for serialization and validation.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "bug: Allow optional mapping" is concise and directly captures the main change across the entire changeset. The title clearly communicates that the PR makes the mapping field optional in schema mapping configurations. The changes consistently support this objective: the core modification makes the mapping field in SchemaMappingModel optional by changing its type to str | None with a default of None, and all adapter implementations add guards to skip processing elements without a mapping. The title provides sufficient context for someone scanning the pull request history to understand the primary purpose without being vague or misleading.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bkr-optional-mapping

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
infrahub_sync/adapters/slurpitsync.py (1)

178-180: Consider refactoring duplicate guard logic and using structured logging.

This guard pattern is duplicated across 7 adapter files with identical logic and messaging. Additionally, using print() is inconsistent with production logging best practices.

Suggestions:

  1. Extract common guard logic into a shared utility function or mixin method:
def should_skip_unmapped_element(element: SchemaMappingModel, adapter_type: str) -> bool:
    """Check if element should be skipped due to missing mapping."""
    if not element.mapping:
        logger.info("No mapping defined for '%s' in %s adapter, skipping...", element.name, adapter_type)
        return True
    return False
  1. Use structured logging instead of print():
-            if not element.mapping:
-                print(f"No mapping defined for '{element.name}', skipping...")
-                continue
+            if not element.mapping:
+                logger.info("No mapping defined for '%s', skipping...", element.name)
+                continue

This applies to all adapter files: slurpitsync.py, nautobot.py, prometheus.py, netbox.py, genericrestapi.py, and aci.py.

infrahub_sync/adapters/infrahub.py (1)

72-73: Consider using structured logging instead of print().

While the added log message provides useful visibility into successful fallback resolution, using print() is inconsistent with production logging best practices. The file doesn't currently import logging, but structured logging would be more appropriate for this diagnostic information.

Consider importing and using the logging module:

+import logging
+
+logger = logging.getLogger(__name__)
+
 def resolve_peer_node(
     ...
 ) -> InfrahubNodeSync | None:
     ...
     if not peer_node and fallback:
-        print(f"Unable to find {rel_schema.peer} [{key}] in Store - Fallback to Infrahub")
+        logger.info("Unable to find %s [%s] in Store - Fallback to Infrahub", rel_schema.peer, key)
         peer_node = client.get(id=key, kind=rel_schema.peer, populate_store=True)
         if not peer_node:
-            print(f"Unable to find {rel_schema.peer} [{key}] - Ignored")
+            logger.warning("Unable to find %s [%s] - Ignored", rel_schema.peer, key)
         else:
-            print(f"Found it with kind {peer_node.get_kind()} using Infrahub client")
+            logger.info("Found peer with kind %s using Infrahub client", peer_node.get_kind())

This would also apply to other print() statements in this function and throughout the file.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75c4183 and a864aeb.

📒 Files selected for processing (9)
  • infrahub_sync/__init__.py (1 hunks)
  • infrahub_sync/adapters/aci.py (1 hunks)
  • infrahub_sync/adapters/genericrestapi.py (1 hunks)
  • infrahub_sync/adapters/infrahub.py (1 hunks)
  • infrahub_sync/adapters/ipfabricsync.py (2 hunks)
  • infrahub_sync/adapters/nautobot.py (1 hunks)
  • infrahub_sync/adapters/netbox.py (1 hunks)
  • infrahub_sync/adapters/prometheus.py (1 hunks)
  • infrahub_sync/adapters/slurpitsync.py (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: Pull Request on main/stable
infrahub_sync/adapters/ipfabricsync.py

[error] 62-62: E303: Too many blank lines (2) before line 62. Found 1 error. 1 fixable with the --fix option. Command: poetry run ruff check .

🔇 Additional comments (6)
infrahub_sync/__init__.py (1)

48-48: LGTM! Mapping field is now optional.

This change correctly makes the mapping field optional in SchemaMappingModel, which aligns with the PR objective of allowing schema mapping entries without a source model mapping.

infrahub_sync/adapters/nautobot.py (1)

64-66: Guard implementation is correct.

The guard correctly prevents accessing element.mapping when it's None, avoiding runtime errors on line 69 where element.mapping.split(".") is called.

Note: See the comment on slurpitsync.py regarding refactoring duplicate guard logic across adapters.

infrahub_sync/adapters/prometheus.py (1)

473-475: Guard implementation is correct.

The guard correctly prevents accessing element.mapping when it's None, ensuring safe access on line 477.

Note: See the comment on slurpitsync.py regarding refactoring duplicate guard logic across adapters.

infrahub_sync/adapters/netbox.py (1)

68-70: Guard implementation is correct.

The guard correctly prevents accessing element.mapping when it's None, avoiding runtime errors on line 73 where element.mapping.split(".") is called.

Note: See the comment on slurpitsync.py regarding refactoring duplicate guard logic across adapters.

infrahub_sync/adapters/genericrestapi.py (1)

144-146: Guard implementation is correct.

The guard correctly prevents accessing element.mapping when it's None, ensuring safe access on line 149.

Note: See the comment on slurpitsync.py regarding refactoring duplicate guard logic across adapters.

infrahub_sync/adapters/ipfabricsync.py (1)

78-80: LGTM! Guard correctly implements optional mapping.

The guard properly skips processing when no mapping is defined, aligning with the PR objective to allow optional mapping entries in schema_mapping. The placement ensures safe access to element.mapping on line 81.

@cloudflare-workers-and-pages
Copy link

Deploying infrahub-sync with  Cloudflare Pages  Cloudflare Pages

Latest commit: 2fa9220
Status: ✅  Deploy successful!
Preview URL: https://bbb8b17e.infrahub-sync.pages.dev
Branch Preview URL: https://bkr-optional-mapping.infrahub-sync.pages.dev

View logs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
examples/device42_to_infrahub/genericrestapi/sync_models.py (1)

36-44: Critical: Mutable default argument causes shared state across instances.

Line 41 uses [] as the default value for tags. This list is created once at class definition and shared across all instances that don't explicitly provide a value, leading to unexpected behavior when modified.

Apply this diff to fix:

-    tags: list[str] | None = []
+    tags: list[str] | None = None

If an empty list is required when tags is None, initialize it in __init__ or __post_init__ instead.

examples/peering-manager_to_infrahub/peeringmanager/sync_models.py (1)

7-19: Consider more specific exception handling and add logging for the fallback.

The broad except Exception on line 14 silently catches all errors during plugin resolution, which could mask configuration issues or legitimate errors. Users may not realize they're using the fallback DiffSyncModel instead of the intended plugin-specific model class.

Consider this improvement:

 # Load model class dynamically at runtime (honor adapters_path, safe fallback)
 try:
     _loader = PluginLoader.from_env_and_args(adapter_paths=[])
 
     _spec = "peeringmanager"
 
     _ModelBaseClass = _loader.resolve(_spec, default_class_candidates=("Model",))
-except Exception:
+except Exception as e:
     # Fallback: use DiffSyncModel to avoid import-time failure
+    import logging
+    logging.getLogger(__name__).debug(
+        "Could not resolve model base class for '%s', falling back to DiffSyncModel: %s",
+        "peeringmanager",
+        e,
+    )
     from diffsync import DiffSyncModel as _FallbackModel
 
     _ModelBaseClass = _FallbackModel
examples/device42_to_infrahub/infrahub/sync_models.py (1)

36-44: Fix the generator to use Field(default_factory=list) instead of mutable defaults.

The mutable default [] for optional list fields is confirmed unsafe in Pydantic v2, which DiffSyncModel requires. The issue affects all auto-generated sync_models.py files (dozens across examples).

Since these files carry the header "AUTO-GENERATED FILE, DO NOT MODIFY" with regeneration command infrahub-sync generate, manual fixes will be lost on next regeneration. The root cause is in the generator code—it should emit:

tags: list[str] = Field(default_factory=list)  # instead of: tags: list[str] | None = []

This ensures each instance gets a fresh list and prevents shared mutable state across instances, which is required for Pydantic v2 compliance.

examples/netbox_to_infrahub/infrahub/sync_models.py (2)

125-136: InfraInterfaceL2L3: missing attribute in _attributes tuple.

untagged_vlan is defined but not listed in _attributes, so it won’t sync/diff.

-    _attributes = ("tagged_vlan", "tags", "l2_mode", "mac_address", "description", "interface_type", "mgmt_only")
+    _attributes = ("tagged_vlan", "untagged_vlan", "tags", "l2_mode", "mac_address", "description", "interface_type", "mgmt_only")

Note: adjust the generator to persist this.


54-55: Fix the generator to emit None instead of empty lists for mutable default list attributes.

The mutable default list antipattern is confirmed across all generated sync_models.py files. Attributes like tags, tagged_vlan, export_rt, import_rt, etc., are initialized with [], creating shared class-level lists that leak state across instances. Fix the generator to emit None as the default value instead.

Example:

-    tags: list[str] | None = []
+    tags: list[str] | None = None

This pattern affects 60+ attributes across multiple integrations: netbox_to_infrahub, slurpit_to_infrahub, peering-manager_to_infrahub, nautobot-v2_to_infrahub, device42_to_infrahub, and infrahub_to_peering-manager.

examples/nautobot-v2_to_infrahub/nautobot/sync_models.py (2)

90-102: Identifiers should not be Optional.

These fields are listed in _identifiers but typed Optional with None defaults, which can break identity/merging.

@@ class InfraDevice(_ModelBaseClass):
-    name: str | None = None
+    name: str
@@
-    organization: str | None = None
+    organization: str
@@ class InfraIPAddress(_ModelBaseClass):
-    ip_prefix: str | None = None
+    ip_prefix: str
@@ class InfraPlatform(_ModelBaseClass):
-    manufacturer: str | None = None
+    manufacturer: str
@@ class InfraPrefix(_ModelBaseClass):
-    ip_namespace: str | None = None
+    ip_namespace: str
@@ class InfraRouteTarget(_ModelBaseClass):
-    organization: str | None = None
+    organization: str

If any of these truly are optional in source, consider removing them from _identifiers instead.

Also applies to: 168-177, 204-213, 232-244, 279-286


154-155: Type/default mismatch for mgmt_only.

Optional[bool] with default False is confusing. Use a plain bool with default False, or Optional[bool] defaulting to None.

-    mgmt_only: bool | None = False
+    mgmt_only: bool = False
♻️ Duplicate comments (4)
examples/netbox_to_infrahub/netbox/sync_models.py (4)

8-18: Same loader fallback concern as infrahub models.

Catching Exception is too broad; prefer PluginLoadError. See prior diff; apply here too.


125-136: InfraInterfaceL2L3: add untagged_vlan to _attributes.

Mirror the infrahub fix; otherwise the field won’t be synced.

-    _attributes = ("tagged_vlan", "tags", "l2_mode", "mac_address", "description", "interface_type", "mgmt_only")
+    _attributes = ("tagged_vlan", "untagged_vlan", "tags", "l2_mode", "mac_address", "description", "interface_type", "mgmt_only")

54-55: Shared mutable defaults (lists).

Same issue as the infrahub file; fix in the generator (use None or per-instance list creation).

Also applies to: 69-70, 82-83, 101-102, 132-135, 164-165, 179-180, 221-224, 237-238


94-104: Optional identifier and Optional[bool] default.

Make InfraDevice.name non-optional if required; make mgmt_only a plain bool.

🧹 Nitpick comments (14)
infrahub_sync/adapters/ipfabricsync.py (1)

77-79: Guard logic correctly implements optional mapping.

The guard properly checks for missing mappings and skips processing with a clear message, preventing errors when element.mapping is used on line 80. This aligns with the PR objective to allow schema entries without source model mappings.

Note: While print() is consistent with existing logging in this file (lines 86, 90), consider migrating to structured logging (e.g., Python's logging module) for better observability in production environments.

examples/peeringdb_to_infrahub/infrahub/sync_models.py (1)

8-18: Consider narrowing the exception scope in the generator

The broad except Exception: handler on line 14 will silently catch all exceptions during base class resolution, potentially masking import errors, configuration issues, or bugs in the PluginLoader. While this ensures the module always loads, it may hide real problems that should be surfaced.

Since this is auto-generated code (lines 21-25 warn modifications will be lost), consider updating the generator to:

  1. Catch specific exceptions (e.g., PluginLoadError, ImportError)
  2. Log warnings when falling back to DiffSyncModel so users are aware of resolution failures

Note: Changes should be made to the code generator that produces this file, not to this file directly.

examples/peeringdb_to_infrahub/genericrestapi/sync_models.py (1)

8-18: Consider narrowing the exception scope in the generator

The broad except Exception: handler on line 14 will silently catch all exceptions during base class resolution, potentially masking import errors, configuration issues, or bugs in the PluginLoader. While this ensures the module always loads, it may hide real problems that should be surfaced.

Since this is auto-generated code (lines 21-25 warn modifications will be lost), consider updating the generator to:

  1. Catch specific exceptions (e.g., PluginLoadError, ImportError)
  2. Log warnings when falling back to DiffSyncModel so users are aware of resolution failures

Note: Changes should be made to the code generator that produces this file, not to this file directly. This same pattern appears in multiple generated sync_models.py files.

examples/custom_adapter/mockdb/sync_models.py (1)

7-18: Consider adding logging when fallback is triggered.

The broad except Exception: silently falls back to DiffSyncModel without any indication that the dynamic resolution failed. In production, this could mask configuration issues or plugin loading failures.

Consider adding a warning log in the except block:

 except Exception:
     # Fallback: use DiffSyncModel to avoid import-time failure
+    import logging
+    logging.getLogger(__name__).warning(
+        "Failed to dynamically load model base class from spec %r, falling back to DiffSyncModel", 
+        _spec if '_spec' in locals() else 'unknown'
+    )
     from diffsync import DiffSyncModel as _FallbackModel
 
     _ModelBaseClass = _FallbackModel

Note: The dynamic loading happens at import time, which means PluginLoader.from_env_and_args() executes when this module is first imported. This is by design for auto-generated code, but be aware it may impact startup performance or behavior in constrained environments.

examples/custom_adapter/infrahub/sync_models.py (1)

7-18: Consider adding logging when fallback is triggered.

The broad except Exception: silently falls back to DiffSyncModel without any indication that the dynamic resolution failed. This could mask configuration or loading issues in production.

Consider adding a warning log in the except block:

 except Exception:
     # Fallback: use DiffSyncModel to avoid import-time failure
+    import logging
+    logging.getLogger(__name__).warning(
+        "Failed to dynamically load model base class from spec %r, falling back to DiffSyncModel", 
+        _spec if '_spec' in locals() else 'unknown'
+    )
     from diffsync import DiffSyncModel as _FallbackModel
 
     _ModelBaseClass = _FallbackModel

Note: This file uses _spec = "infrahub" (a module name), while the mockdb version uses a file path. This difference appears intentional for different adapter resolution strategies, but worth confirming both specs are correct for their respective contexts.

examples/infrahub_to_peering-manager/infrahub/sync_models.py (1)

8-18: Narrow the exception and surface a minimal diagnostic.

Catching Exception hides real import/config errors. Prefer a narrower exception set (e.g., ImportError, AttributeError, PluginLoadError if available) and emit a one-line warning to stderr for visibility.

Example:

-try:
+try:
     _loader = PluginLoader.from_env_and_args(adapter_paths=[])
     _spec = "infrahub"
     _ModelBaseClass = _loader.resolve(_spec, default_class_candidates=("Model",))
-except Exception:
+except (ImportError, AttributeError):
     # Fallback: use DiffSyncModel to avoid import-time failure
     from diffsync import DiffSyncModel as _FallbackModel
-    _ModelBaseClass = _FallbackModel
+    _ModelBaseClass = _FallbackModel
+    # Optional: basic diagnostic without hard dependency on logging
+    import sys
+    print(f"[infrahub] Falling back to DiffSyncModel (spec={_spec})", file=sys.stderr)
examples/observium_to_infrahub/genericrestapi/sync_models.py (1)

8-18: Tighten error handling on dynamic base resolution.

Same pattern: prefer catching specific import/resolve errors and emitting a brief warning to aid debugging while keeping the safe fallback.

-except Exception:
+except (ImportError, AttributeError):
     # Fallback: use DiffSyncModel to avoid import-time failure
     from diffsync import DiffSyncModel as _FallbackModel
     _ModelBaseClass = _FallbackModel
examples/peering-manager_to_infrahub/infrahub/sync_models.py (1)

8-18: Reduce catch-all to targeted exceptions and add a minimal diagnostic.

Mirror the recommended loader handling used elsewhere: catch import/resolve errors only, and print a short notice when falling back.

examples/observium_to_infrahub/infrahub/sync_models.py (1)

8-18: Constrain the exception type for loader fallback.

Catch import/resolve failures instead of Exception to avoid masking real errors; optional one-line stderr note helps troubleshooting.

examples/netbox_to_infrahub/infrahub/sync_models.py (3)

8-18: Narrow exception handling for loader fallback.

Catching Exception hides unrelated import-time bugs. Catch PluginLoadError explicitly and re-raise others. Based on infrahub_sync/plugin_loader.py.

-from infrahub_sync.plugin_loader import PluginLoader
+from infrahub_sync.plugin_loader import PluginLoader, PluginLoadError
@@
-try:
+try:
@@
-    _ModelBaseClass = _loader.resolve(_spec, default_class_candidates=("Model",))
-except Exception:
+    _ModelBaseClass = _loader.resolve(_spec, default_class_candidates=("Model",))
+except PluginLoadError:
     # Fallback: use DiffSyncModel to avoid import-time failure
     from diffsync import DiffSyncModel as _FallbackModel
@@
-    _ModelBaseClass = _FallbackModel
+    _ModelBaseClass = _FallbackModel

94-104: Identifier typed optional: consider making required.

InfraDevice._identifiers includes "name" but it's annotated as Optional[str]. Prefer non-optional for identifiers to reflect required semantics (unless base class supports None identifiers).

If acceptable, change to:

-    name: str | None = None
+    name: str

Verify other classes for Optional identifiers. Would you like a script to scan and report these?


130-131: Boolean optional defaulted to False.

mgmt_only is Optional[bool] but default False. Make it a plain bool or default None.

-    mgmt_only: bool | None = False
+    mgmt_only: bool = False
examples/nautobot-v2_to_infrahub/nautobot/sync_models.py (2)

14-18: Narrow the exception; avoid masking real import bugs.

Catching Exception will hide unrelated errors during import. Catch plugin-resolution failures explicitly.

-from infrahub_sync.plugin_loader import PluginLoader
+from infrahub_sync.plugin_loader import PluginLoader, PluginLoadError
@@
-except Exception:
+except (PluginLoadError, ImportError):

(Keep broad catch only if you also log/emit the traceback.) Based on plugin_loader.py.


9-9: Nit: let PluginLoader read env by default.

Passing [] is equivalent to None here; simplify.

-    _loader = PluginLoader.from_env_and_args(adapter_paths=[])
+    _loader = PluginLoader.from_env_and_args()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a864aeb and 2fa9220.

📒 Files selected for processing (28)
  • examples/aci_to_infrahub/aci/__init__.py (1 hunks)
  • examples/aci_to_infrahub/aci/sync_models.py (5 hunks)
  • examples/aci_to_infrahub/infrahub/__init__.py (1 hunks)
  • examples/aci_to_infrahub/infrahub/sync_models.py (5 hunks)
  • examples/custom_adapter/custom_adapter_src/custom_adapter.py (1 hunks)
  • examples/custom_adapter/infrahub/sync_models.py (2 hunks)
  • examples/custom_adapter/mockdb/sync_models.py (2 hunks)
  • examples/device42_to_infrahub/genericrestapi/sync_models.py (4 hunks)
  • examples/device42_to_infrahub/infrahub/sync_models.py (4 hunks)
  • examples/infrahub_to_peering-manager/infrahub/sync_models.py (7 hunks)
  • examples/infrahub_to_peering-manager/peeringmanager/sync_models.py (7 hunks)
  • examples/librenms_to_infrahub/genericrestapi/sync_models.py (5 hunks)
  • examples/librenms_to_infrahub/infrahub/sync_models.py (5 hunks)
  • examples/nautobot-v2_to_infrahub/infrahub/sync_adapter.py (1 hunks)
  • examples/nautobot-v2_to_infrahub/infrahub/sync_models.py (25 hunks)
  • examples/nautobot-v2_to_infrahub/nautobot/sync_adapter.py (1 hunks)
  • examples/nautobot-v2_to_infrahub/nautobot/sync_models.py (25 hunks)
  • examples/netbox_to_infrahub/infrahub/sync_adapter.py (1 hunks)
  • examples/netbox_to_infrahub/infrahub/sync_models.py (18 hunks)
  • examples/netbox_to_infrahub/netbox/sync_adapter.py (1 hunks)
  • examples/netbox_to_infrahub/netbox/sync_models.py (18 hunks)
  • examples/observium_to_infrahub/genericrestapi/sync_models.py (4 hunks)
  • examples/observium_to_infrahub/infrahub/sync_models.py (4 hunks)
  • examples/peering-manager_to_infrahub/infrahub/sync_models.py (9 hunks)
  • examples/peering-manager_to_infrahub/peeringmanager/sync_models.py (9 hunks)
  • examples/peeringdb_to_infrahub/genericrestapi/sync_models.py (2 hunks)
  • examples/peeringdb_to_infrahub/infrahub/sync_models.py (2 hunks)
  • infrahub_sync/adapters/ipfabricsync.py (1 hunks)
✅ Files skipped from review due to trivial changes (4)
  • examples/aci_to_infrahub/infrahub/init.py
  • examples/custom_adapter/custom_adapter_src/custom_adapter.py
  • examples/aci_to_infrahub/aci/init.py
  • examples/netbox_to_infrahub/infrahub/sync_adapter.py
🧰 Additional context used
🧬 Code graph analysis (22)
examples/device42_to_infrahub/genericrestapi/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/peeringdb_to_infrahub/infrahub/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/librenms_to_infrahub/genericrestapi/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/librenms_to_infrahub/infrahub/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/netbox_to_infrahub/infrahub/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/aci_to_infrahub/infrahub/sync_models.py (1)
infrahub_sync/plugin_loader.py (2)
  • PluginLoader (46-481)
  • from_env_and_args (68-104)
examples/infrahub_to_peering-manager/infrahub/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/custom_adapter/infrahub/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/peeringdb_to_infrahub/genericrestapi/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/nautobot-v2_to_infrahub/infrahub/sync_models.py (2)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/nautobot-v2_to_infrahub/nautobot/sync_models.py (1)
  • InfraInterfaceL2L3 (137-163)
examples/peering-manager_to_infrahub/infrahub/sync_models.py (2)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/peering-manager_to_infrahub/peeringmanager/sync_models.py (2)
  • InfraAutonomousSystem (47-69)
  • InfraIXPConnection (132-154)
examples/netbox_to_infrahub/netbox/sync_adapter.py (1)
examples/netbox_to_infrahub/netbox/sync_models.py (1)
  • InfraIPAddress (109-119)
examples/custom_adapter/mockdb/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/nautobot-v2_to_infrahub/nautobot/sync_models.py (2)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/nautobot-v2_to_infrahub/infrahub/sync_models.py (1)
  • InfraInterfaceL2L3 (137-163)
examples/nautobot-v2_to_infrahub/nautobot/sync_adapter.py (1)
examples/nautobot-v2_to_infrahub/infrahub/sync_models.py (1)
  • InfraIPAddress (166-179)
examples/infrahub_to_peering-manager/peeringmanager/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/observium_to_infrahub/genericrestapi/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/device42_to_infrahub/infrahub/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/aci_to_infrahub/aci/sync_models.py (1)
infrahub_sync/plugin_loader.py (2)
  • PluginLoader (46-481)
  • from_env_and_args (68-104)
examples/peering-manager_to_infrahub/peeringmanager/sync_models.py (2)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/peering-manager_to_infrahub/infrahub/sync_models.py (2)
  • InfraAutonomousSystem (47-69)
  • InfraIXPConnection (132-154)
examples/observium_to_infrahub/infrahub/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
examples/netbox_to_infrahub/netbox/sync_models.py (1)
infrahub_sync/plugin_loader.py (1)
  • PluginLoader (46-481)
🔇 Additional comments (34)
examples/nautobot-v2_to_infrahub/nautobot/sync_adapter.py (1)

16-16: LGTM! Auto-generated import reordering.

The repositioning of InfraIPAddress after InfraInterfaceL2L3 is expected for an auto-generated file and has no functional impact. Import order does not affect runtime behavior in this context.

examples/device42_to_infrahub/genericrestapi/sync_models.py (2)

7-19: LGTM: Dynamic base class resolution with safe fallback.

The try/except block correctly attempts runtime resolution of the model base class via PluginLoader and falls back to DiffSyncModel on any exception, ensuring import-time stability.


47-55: LGTM: Description field added correctly.

The new optional description attribute uses an immutable default value and is properly declared in _attributes.

examples/peering-manager_to_infrahub/peeringmanager/sync_models.py (3)

37-44: Verify that empty _attributes is intentional for reference-only model.

The OrganizationProvider model has an empty _attributes tuple, which aligns with the PR objective of allowing schema_mapping entries without source model mapping. This appears to be a reference-only model that other models (like InfraAutonomousSystem.organization) can reference.

Please confirm that this model is intended to serve only as a static reference target without data synchronization from a source system.


50-58: LGTM! Formatting improvement enhances readability.

The multiline formatting of the _attributes tuple improves readability without changing functionality.


135-143: LGTM! Consistent formatting improvement.

The multiline formatting of the _attributes tuple maintains consistency with the formatting improvements elsewhere in the file.

examples/peeringdb_to_infrahub/infrahub/sync_models.py (2)

3-3: LGTM: Import cleanup

Removing the unused List import is appropriate since only Any is needed for the type annotations in this generated file.


26-36: LGTM: Model class definition

The model class correctly uses the dynamically resolved _ModelBaseClass and maintains proper structure with identifiers, attributes, and typed fields.

examples/peeringdb_to_infrahub/genericrestapi/sync_models.py (2)

3-3: LGTM: Import cleanup

Removing the unused List import is appropriate since only Any is needed for the type annotations in this generated file.


26-36: LGTM: Model class definition

The model class correctly uses the dynamically resolved _ModelBaseClass and maintains proper structure with identifiers, attributes, and typed fields.

examples/nautobot-v2_to_infrahub/infrahub/sync_adapter.py (1)

16-16: LGTM: Import reordering.

The reordering of InfraIPAddress in the import list has no functional impact.

examples/nautobot-v2_to_infrahub/infrahub/sync_models.py (3)

7-18: Verify that broad exception handling is appropriate.

The except Exception: block catches all exceptions during dynamic base class resolution, which could mask configuration errors, import failures, or bugs in PluginLoader. While the fallback to DiffSyncModel prevents import-time failure, consider whether specific exception types should be caught instead.

Since this is auto-generated code, verify that the code generator intentionally uses broad exception handling for robustness, or if it should be narrowed to specific exceptions like ImportError, AttributeError, or PluginLoadError.


140-149: LGTM: Improved readability.

The multi-line formatting of the _attributes tuple improves readability without changing functionality.


26-372: LGTM: Consistent model definitions.

All model classes follow a consistent pattern with proper type hints and structure. The expanded model definitions with additional fields and attributes align with the PR's goal of supporting richer schema mappings.

examples/custom_adapter/mockdb/sync_models.py (2)

1-6: LGTM: Import changes are clean.

The formatting adjustments and Any import are appropriate for the type annotations used in the model class.


26-34: LGTM: Model class definition is correct.

The InfraDevice class properly inherits from the dynamically resolved _ModelBaseClass and has appropriate type annotations.

examples/custom_adapter/infrahub/sync_models.py (2)

1-6: LGTM: Import changes are clean.

The formatting and Any import are appropriate, consistent with the companion file.


26-34: LGTM: Model class definition is correct.

The InfraDevice class properly inherits from the dynamically resolved _ModelBaseClass with appropriate type annotations.

examples/librenms_to_infrahub/genericrestapi/sync_models.py (3)

3-6: LGTM: Import and formatting changes support new models.

The addition of Any type import and formatting adjustments appropriately support the new model class definitions that follow.


7-19: Dynamic base resolution pattern works well for optional mapping use case.

The runtime resolution of _ModelBaseClass via PluginLoader appropriately supports the PR's objective of allowing models without explicit source mappings. The fallback to DiffSyncModel ensures import safety.

Note: The broad exception handler (line 14) provides robustness but could potentially mask unexpected errors during development.


37-68: LGTM: New model classes are well-structured.

The three new model classes (InfraDevice, IpamIPAddress, LocationSite) are properly structured with consistent type hints, proper identifier/attribute declarations, and support for local metadata via local_id and local_data fields.

examples/librenms_to_infrahub/infrahub/sync_models.py (3)

3-6: LGTM: Import and formatting changes support new models.

The addition of Any type import and formatting adjustments appropriately support the new model class definitions.


7-19: Dynamic base resolution correctly targets infrahub adapter.

The runtime resolution pattern is identical to the genericrestapi sync_models file, with the appropriate adapter spec set to "infrahub" (line 11). The fallback mechanism ensures import safety.


37-68: LGTM: New model classes are well-structured.

The three new model classes (InfraDevice, IpamIPAddress, LocationSite) are properly structured with consistent type hints and proper metadata field declarations, matching the pattern established in the genericrestapi sync_models file.

examples/aci_to_infrahub/infrahub/sync_models.py (2)

1-18: LGTM! Base class resolution is correct.

The dynamic base class resolution using PluginLoader is properly implemented with a safe fallback to DiffSyncModel. The imports and initialization logic follow the expected pattern.


26-84: LGTM! Generated model classes are structurally correct.

The four new model classes (DcimPhysicalInterface, LocationBuilding, LocationMetro, OrganizationCustomer) follow the correct structure and are consistent with existing patterns. All type hints, identifiers, and attributes are properly defined.

The file header confirms this is auto-generated via infrahub-sync generate, so no manual re-generation step is needed.

examples/aci_to_infrahub/aci/sync_models.py (1)

3-3: LGTM! The Any import is correctly added.

The Any type is properly used for the local_data field type hints across all model classes (lines 38, 50, 62, 73, 83).

examples/infrahub_to_peering-manager/peeringmanager/sync_models.py (1)

49-51: No changes required – code is correct.

The mutable default values (= []) in the field declarations are safe. Pydantic automatically deep-copies mutable default values for each model instance, so each instance gets its own independent list rather than sharing a reference. DiffSyncModel is based on Pydantic and uses Python typing to define attributes. The auto-generated code correctly leverages this Pydantic feature and does not introduce the shared-state bug that would occur in plain Python classes.

examples/device42_to_infrahub/infrahub/sync_models.py (2)

1-19: LGTM: Runtime loader pattern and imports.

The dynamic base class resolution using PluginLoader with a safe fallback to DiffSyncModel is well-implemented. The Any import is appropriately used for the generic local_data field.


47-55: LGTM: OrganizationTenant changes.

The addition of the optional description attribute with a None default follows Python best practices for optional fields.

examples/netbox_to_infrahub/netbox/sync_adapter.py (1)

13-13: LGTM — reorder only; no functional impact.

Also applies to: 42-43

examples/infrahub_to_peering-manager/infrahub/sync_models.py (1)

49-51: I need to inspect the actual file to determine what type of classes are being used. Let me examine the class definitions in the target file.

Based on my analysis of the repository and external documentation, I need to verify whether these classes are Pydantic BaseModel instances, as this critically affects the validity of the mutable default concern.

The review comment assumes traditional Python class behavior where mutable defaults are shared across instances. However, Pydantic automatically creates a deep copy of mutable default values when creating each instance of the model, making the pattern safe in Pydantic models.

Given that infrahub-sync is a Pydantic-based synchronization project and the sync_models.py files use type annotations with | None syntax, these classes are almost certainly Pydantic BaseModel subclasses. If that's the case, the mutable default pattern list[str] | None = [] is safe and requires no changes.

No changes recommended — manually verify class type first.

The file examples/infrahub_to_peering-manager/infrahub/sync_models.py (lines 49-51, 81, 94-96) should be inspected to confirm whether these classes inherit from pydantic.BaseModel. If they do, the mutable defaults are handled correctly and the review comment is incorrect. If they are plain Python classes or dataclasses, the comment is valid.

examples/peering-manager_to_infrahub/infrahub/sync_models.py (1)

79-81: Do not manually fix auto-generated files—they will be overwritten on next generation.

The target file examples/peering-manager_to_infrahub/infrahub/sync_models.py is explicitly marked "AUTO-GENERATED FILE, DO NOT MODIFY" and states "All modifications will be lost the next time you reexecute this command." The same applies to all 24 affected sync_models.py files across examples/.

The mutable default issue must be fixed at the source (the generation script/template for infrahub-sync generate), not in the generated files.

However, infrahub_sync/__init__.py:52 is a legitimate non-generated file that does contain an inconsistent mutable default (fields: list[SchemaMappingField] | None = []) within a Pydantic BaseModel, while sibling fields correctly use pydantic.Field(default=None).

Likely an incorrect or invalid review comment.

examples/nautobot-v2_to_infrahub/nautobot/sync_models.py (1)

70-70: Auto-generated file—review comment asks to modify code that should not be changed.

This file is auto-generated (explicitly marked "DO NOT MODIFY" in the header, line 20-22) and will be regenerated by the infrahub-sync generate command. While the mutable default anti-pattern (= []) is technically present, manually editing auto-generated output is ineffective—any changes will be lost on the next generation.

The mutable defaults issue must be fixed at the source: the code generator or template that produces these files, not in the generated output itself.

Likely an incorrect or invalid review comment.

@BeArchiTek BeArchiTek requested a review from a team October 22, 2025 13:27
Copy link
Contributor

@minitriga minitriga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn formatting. I found the change at the bottom looks good!

@minitriga minitriga merged commit 83a3898 into main Oct 22, 2025
15 checks passed
@minitriga minitriga deleted the bkr-optional-mapping branch October 22, 2025 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants