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:
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.devicegives 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 (likewith open(file)— it opens the connection automatically and closes it when thewithblock exits).fallback_to_default=Truetells 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:
- Look at the device's platform (e.g.
ios,junos,frr). - Find registered plugins that match that platform and implement all the capabilities your proxy requires.
- Pick the best match (or fall back to a default plugin if allowed).
- 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:
BaseConnectionwraps a third-party library (scrapli, netmiko, httpx, ncclient) and owns the raw connect/disconnect lifecycle.ConnectionPluginis a platform-aware factory that creates the base connection and implements capability methods likeget_version().ConnectionProxyis 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.