# SDK reference

The complete contract between a connector and the platform.

The [Connector SDK](https://zato.io/docs/sdk/index.html) exposes any Python client library as 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](https://zato.io/tutorials/devops/deployment.html) and without a server restart

## The contract {#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.

```python
# -*- 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 {#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 {#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 {#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 {#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 {#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 keeps a pool of connections, `create_client` builds one connection each time the pool grows, and invocation methods borrow one per call through `get_connection`:

```python
# -*- 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](https://zato.io/docs/sdk/stateful-sockets.html).

## Subscribing connections {#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 monitors 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](https://zato.io/docs/sdk/subscriptions.html).

## Helper processes {#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](https://zato.io/docs/sdk/python-cannot-run-in-server.html):

```python
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](https://zato.io/docs/sdk/foreign-runtimes.html).

## Ambient attributes {#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 {#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:

```python
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 {#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_stop` and 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 {#see-also}

- [Connector SDK tutorial](https://zato.io/docs/sdk/tutorial.html) - Build a complete connector and call it from a service
- [Handshake protocols and pooled connections](https://zato.io/docs/sdk/stateful-sockets.html) - A complete PooledConnector, from logon to service
- [Server push and subscriptions](https://zato.io/docs/sdk/subscriptions.html) - A complete SubscribingConnector with resubscription after reconnects
- [Java, .NET and native code](https://zato.io/docs/sdk/foreign-runtimes.html) - A complete connector built around start\_process
