Skip to content

Connecting to Devices

Most function blocks exist to interact with network equipment. The neops SDK makes this remarkably concise through its proxy/plugin architecture -- you declare what you need, and the platform figures out how to connect.


Three lines to a live device

Inside any function block that runs on a device, connecting looks like this:

with VersionProxy.connect(device, fallback_to_default=True) as proxy:
    version_info = proxy.get_version()

No connection library imports. No platform-specific branching. The same code works against Cisco IOS, FRR, Junos, or any other platform that has a registered plugin.


Define a proxy

A proxy is a thin class that declares which capabilities you need. You create one by inheriting from ConnectionProxy plus one or more capability interfaces:

class VersionProxy(ConnectionProxy, DeviceInfoCapability):
    pass

That is the entire class — no methods to implement. By inheriting DeviceInfoCapability you tell the SDK: "I need a plugin that can answer device info questions." This capability provides:

  • get_version() — returns a dict with vendor, model, serial, and software release

When you call this on the proxy, the SDK delegates to whichever plugin was resolved for the device's platform. Need a command the built-in capabilities don't cover? Use get_raw_connection() to access the underlying library directly (see Best Practices).

Note

You can compose multiple capabilities into a single proxy. Need device info and interface stats? Inherit both interfaces and the resolver picks a plugin that satisfies all of them.


Use it in a function block

Here is a complete function block that retrieves the software version from whatever device the workflow targets:

class ShowVersion(FunctionBlock[ShowVersionParams, ShowVersionResult]):
    async def run(self, params: ShowVersionParams, context: WorkflowContext) -> FunctionBlockResult[ShowVersionResult]:
        del params
        device = context.device
        if device is None:
            return FunctionBlockResult(message="No device in context.", success=False, data=None)

        with VersionProxy.connect(device, fallback_to_default=True) as proxy:
            version_info = proxy.get_version()

        return FunctionBlockResult(
            message="Version retrieved successfully.",
            success=True,
            data=ShowVersionResult(version_info=version_info),
        )

    async def acquire(self, params: ShowVersionParams) -> FunctionBlockAcquireResult:
        del params
        return FunctionBlockAcquireResult(message="No resources required.", success=True, acquires=None)

The important bits:

  • context.device gives you the device entity from the workflow context, complete with IP, credentials, and platform metadata. When neops runs your function block in production, it populates this automatically. During testing, you provide mock device data (as you will see in the next section).
  • VersionProxy.connect(device, fallback_to_default=True) is a context manager (like with open(file) — it opens the connection automatically and closes it when the with block exits).
  • fallback_to_default=True tells the resolver to use the platform's default connection plugin if no exact match is found. This is the right default for most use cases.
  • proxy.get_version() delegates to whichever plugin was resolved -- scrapli for IOS, netmiko for legacy gear, HTTP for API-driven platforms.

How the resolver works

When you call connect(), the SDK walks a short decision path:

  1. Look at the device's platform (e.g. ios, junos, frr).
  2. Find registered plugins that match that platform and implement all the capabilities your proxy requires.
  3. Pick the best match (or fall back to a default plugin if allowed).
  4. Create the underlying connection (SSH, NETCONF, REST -- whatever the plugin uses) and return the proxy.

Your code never sees the library-level details. If someone later swaps the IOS plugin from scrapli to netmiko, your function block keeps working without a single line changed.

What happens under the hood

The proxy system has three layers:

  • BaseConnection wraps a third-party library (scrapli, netmiko, httpx, ncclient) and owns the raw connect/disconnect lifecycle.
  • ConnectionPlugin is a platform-aware factory that creates the base connection and implements capability methods like get_version().
  • ConnectionProxy is what your code uses. It declares required capabilities via inheritance, resolves the right plugin at connect time, and delegates every method call to that plugin.

When you call proxy.get_version(), the proxy forwards the call to the resolved plugin's get_version(). If the plugin does not implement a method, the proxy raises NotImplementedForThisPlatform with a clear error message.

For the full deep dive, see the Device Connections section.


Next steps

You have a function block that talks to real devices. Now make sure it works before deploying -- continue to Testing to write your first test case.