SDK reference
The complete contract between a connector and the platform.
The Connector SDK turns any Python client library into a Zato connection type - an internal library your company maintains, a vendor's package or a client for a protocol used only by your own systems. You wrap the library once and services use it like any built-in type.
Use the Connector SDK to:
- Manage connections of your own types centrally, by name
- Store secrets encrypted, omitted from listings
- Access connections in services through
self.out, like every built-in type - Deploy connectors as regular Python modules, through hot-deployment and without a server restart
The contract
A connector is a subclass of Connector from zato.common.sdk. You give it a type name, declare its configuration fields and implement two methods - everything else is optional.
# -*- coding: utf-8 -*-
# Zato
from zato.common.sdk import Connector, Field
class MyConnector(Connector):
# Services access connections of this type as self.out.my_type
type = 'my_type'
# Configuration schema
host = Field.Text()
port = Field.Int(default=9950)
api_key = Field.Secret()
def create_client(self):
# Build and return the underlying client object
...
def ping(self, client):
# Confirm the connection works, raise an exception if it does not
...
Configuration fields
Fields are class attributes declared with Field types. Their values are stored with each connection definition and delivered to the connector, already resolved, through self.config.
| Field type | Description |
|---|---|
Field.Text | A text field |
Field.Int | An integer field |
Field.Bool | A boolean field |
Field.Secret | A secret, stored encrypted and omitted when definitions are read |
Each field can declare a default, for example Field.Int(default=9950), which applies when a definition does not provide the value.
Methods the platform calls
| Method | Required | Description |
|---|---|---|
create_client | Yes | Builds and returns the underlying client object, called when the connection starts |
ping | Yes | Confirms the connection is usable, called when the connection is pinged |
on_stop | No | Closes the client, called when the definition is deleted or edited |
validate | No | Checks the client is still usable before each use. The default calls ping and an exception makes the platform evict the client and reconnect |
refresh_credentials | No | Renews expired credentials. It runs when an invocation raises CredentialsExpired and the invocation is then retried once |
start_process | - | Provided by the platform. Starts and supervises a helper process |
Invocation methods are plain methods you add for each operation the remote system supports - get_customer, send or query. Services call them directly and the platform wraps each call with the behavior described below.
Invocation behavior
When a service calls an invocation method, the platform wraps the call.
A watchdog timeout caps how long the call may take. The definition's timeout field sets the cap and a single call can override it: conn.get_customer('C1', timeout=5).
validate runs before the call and, when it raises an exception, the platform evicts the client and rebuilds it before the call proceeds. Pooled connections validate at checkout instead.
An invocation that raises CredentialsExpired triggers refresh_credentials and is retried once.
An invocation that raises ConnectionLost receives the exception, and the platform rebuilds the connection in the background, with backoff.
Exceptions
zato.common.sdk defines two exceptions that client code raises to report a lost connection or expired credentials:
| Exception | Meaning and what the platform does |
|---|---|
ConnectionLost | The connection is down. The platform evicts the client and rebuilds it with backoff |
CredentialsExpired | The credentials expired. refresh_credentials runs and the call is retried once |
Pooled connections
When connections hold per-connection state - a logon handshake followed by calls that run one at a time - subclass PooledConnector instead of Connector. The platform owns a pool of connections, create_client builds one connection each time the pool grows, and invocation methods borrow one per call through get_connection:
# -*- coding: utf-8 -*-
# Zato
from zato.common.sdk import Field, PooledConnector
class MyConnector(PooledConnector):
type = 'my_type'
def create_client(self):
# Build and return one connection - the platform calls this each time the pool grows
...
def send_command(self, command):
# Borrow a connection for the duration of the with block
with self.get_connection() as conn:
return conn.send(command)
PooledConnector adds these to the contract:
| Method | Required | Description |
|---|---|---|
get_connection | - | Provided by the platform. Borrows a connection for the duration of a with block |
on_get_from_pool | No | Resets conversational state. It runs each time a call borrows a connection |
on_return_to_pool | No | Cleans up. It runs each time a with block using get_connection ends |
A definition's pool_size field caps how many connections its pool holds and connections are built lazily, as the load requires them. To see a complete connector of this kind, see handshake protocols.
Subscribing connections
When the remote side pushes messages - market data feeds, event streams - subclass SubscribingConnector. It adds one hook to the contract:
| Method | Required | Description |
|---|---|---|
on_started | No | Subscribes and replays state. It runs when the connection first starts and again after every reconnect |
The platform watches subscribing connections in the background. When one goes down, the platform reconnects with backoff and re-runs on_started, which re-establishes the subscription. To see a complete connector of this kind, see server push.
Helper processes
start_process runs a component next to the server - a Java jar, a .NET assembly, a native binary or a Python module in an interpreter of its own:
def create_client(self):
process = self.start_process(['java', '-jar', self.config.jar_path, '{port}'])
return MyClient('127.0.0.1', process.port)
Any {port} placeholder in the command is replaced with a local port allocated for the process. The returned Process object has these members:
| Member | Description |
|---|---|
pid | The process ID |
port | The local port allocated for the process |
is_running | Whether the process is still running |
stop | Stops the process |
The platform supervises the process. When the process exits unexpectedly, the platform rebuilds the whole connection with backoff, which re-runs create_client, and the process stops together with the connection it belongs to. To see a complete connector of this kind, see foreign runtimes.
Ambient attributes
Every connector instance has these attributes available:
| Attribute | Description |
|---|---|
self.name | The name of the connection definition this instance serves |
self.config | The resolved values of the fields the class declares |
self.client | The object create_client returned, once the connection has started |
self.logger | A logger writing to Zato server logs |
self.invoke | Invokes a service with a message, matching self.invoke in services |
self.publish | Publishes a message to a pub/sub topic, matching self.publish in services |
Per-tenant credentials
When one definition serves many tenants, each with credentials of their own, services resolve them at runtime with with_config - the platform keeps one client per distinct set of overrides and stops the ones that stay idle beyond the idle period:
conn = self.out.crm['My CRM'].with_config(api_key=tenant_api_key)
response = conn.get_customer(customer_id)
Each distinct set of overrides gets a client of its own, built through create_client with the overridden values in self.config, and repeated calls with the same overrides reuse it.
Runtime behavior
When your module is deployed, the platform finds the Connector subclasses in it and registers each one as a new connection type. From that moment:
- Definitions of the type can be created, edited, deleted and pinged like any other connection
- Each definition is served by one connector instance with one client, shared by all the services on a server
- Editing a definition stops the old client through
on_stopand builds a new one with the new configuration - Redeploying the module updates the type in place and the definitions that are already running keep working
- After a server restart, definitions start automatically as soon as the module is deployed again at boot
See also
| Page | What it covers |
|---|---|
| Connector SDK tutorial | Build a complete connector and call it from a service |
| Handshake protocols and pooled connections | A complete PooledConnector, from logon to service |
| Server push and subscriptions | A complete SubscribingConnector with resubscription after reconnects |
| Java, .NET and native code | A complete connector built around start_process |