Skip to content

Resolution, Defaults, and Ambiguity (How Plugins Are Selected)

This page explains how a ConnectionProxy selects a plugin and what happens when resolution is ambiguous or incomplete.


Inputs to plugin resolution

When you call:

proxy = MyProxy.get_connection(device, connection_type="ssh", connection_library="scrapli")

the resolver considers:

  • platform: derived from device.platform.short_name (preferred) or device.platform (string)
  • connection_type: "ssh", "api", "netconf", "restconf", …
  • connection_library: "netmiko", "scrapli", "httpx", "ncclient", …
  • required capabilities: the capability interfaces your proxy inherits from

Note: If device.platform is missing or not a string and has no usable short_name, resolution raises ConnectionValidationError.


Resolution algorithm (high level)

Step A: filter by platform / type / library

Candidates are pulled from the registry based on the provided criteria:

  • if you specify a library, it’s an exact match
  • if you omit the library, all libraries for that platform+type are candidates
  • if you omit connection_type, all plugins for the platform are candidates (rarely what you want)

Step B: filter by required capabilities

If your proxy declares required capabilities, the resolver prefers plugins that implement all of them.

Step C: if no plugin fully matches → best-match fallback

If no plugin implements all required capabilities, the resolver selects the best-match plugin (the one implementing the most required capabilities).

  • it logs a warning describing the selection, missing capabilities, and other candidates
  • the resulting proxy may still raise NotImplementedForThisPlatform if you call a missing capability method (see 33-writing-capabilities-and-proxies.md)

If multiple plugins tie for best score, the resolver raises AmbiguousPluginError.


Ambiguity is explicit

If multiple plugins qualify after filtering (and after capability filtering/best-match selection), resolution is not silent:

  • if fallback_to_default=False (default): raise AmbiguousPluginError
  • if fallback_to_default=True: try to select a configured default plugin

This is a deliberate design choice to avoid accidentally picking the wrong backend library.


Defaults (platform vs platform+type)

Plugins can register defaults via the registry decorator:

  • default_for_platform=True: default for the entire platform (used when type/library are not specified)
  • default_for_connection_type=True: default for (platform, connection_type) (used when library is not specified)

Defaults are only used when you pass fallback_to_default=True.

Default selection checks in this order:

  1. (platform, connection_type) default (only if connection_type was specified)
  2. platform default

Warning: Defaults are used to resolve ambiguity between multiple qualifying plugins after capability filtering (including best-match selection) has completed.

Defaults are not used to break best-match ties when no plugin fully matches the required capabilities. In that case, best-match ties raise AmbiguousPluginError immediately and you must resolve ambiguity explicitly.

Default override rules

Only one default can be active per scope (platform or platform+connection_type). The SDK distinguishes plugins by their module location (SDK package vs your app code):

  • User overrides SDK: If you register a default that conflicts with an SDK default, your plugin silently replaces it. A log message confirms the override.
  • SDK yields to user: If an SDK plugin tries to register a default but a user default already exists, the SDK plugin is skipped.
  • User vs user conflict: If two user-defined plugins both try to register as default for the same scope, registration fails with PluginRegistrationError.
  • SDK vs SDK conflict: Same as user vs user - raises PluginRegistrationError (indicates an SDK bug).

Detection is automatic based on module path. Any plugin defined within the SDK package is treated as an SDK plugin; plugins defined elsewhere are treated as user plugins. This means you can safely subclass SDK plugins - your subclass will still be identified as a user plugin.


What “validate_capabilities” does (and doesn’t do)

validate_capabilities=True (default) warns early when the resolved plugin doesn’t implement capability methods required by the proxy.

  • it does not block creation
  • it helps you notice that certain methods will raise at runtime

Missing capability calls raise NotImplementedForThisPlatform with rich context:

  • proxy class name
  • method name
  • interface name
  • platform / plugin / device id (when available)

Common “gotchas” (read before debugging)

  1. Omitting connection_library often creates ambiguity

If you have both Scrapli and Netmiko plugins registered for "ios"+"ssh", calling MyProxy.get_connection(device, "ssh") will usually be ambiguous unless you opted into defaults.

  1. Defaults require opt-in

You must pass fallback_to_default=True to let defaults participate.

  1. Best-match can “work” but still fail later

Best-match selection keeps workflows moving, but if you call a missing method you’ll get NotImplementedForThisPlatform. Use capability validation warnings and proxy overrides to make this predictable.

  1. Missing third-party dependencies fail at runtime

Base plugins lazy-import dependencies inside BaseConnection.connect(). Missing libraries raise ConnectionCreationError at connection time (not during module import).