# Connector SDK tutorial

Wrap a Python client as a connection type that services call by name.

With the [Connector SDK](https://zato.io/docs/sdk/index.html), you wrap a Python client library once and services use it as a named connection type, with its definitions managed centrally and its [API key](https://zato.io/docs/security/api-keys.html) stored encrypted.

This page shows how to wrap a client for an internal CRM gateway - the protocol is one line of text per request and one line per response - and how to call it from a service through `self.out.crm`.

## The connector module {#the-connector-module}

The whole connector is one Python module - the client class and the connector class that wraps it. When the client comes from a library that is already installed, the module contains only the connector class.

```python
# -*- coding: utf-8 -*-

# stdlib
import socket

# Zato
from zato.common.sdk import Connector, Field

class CRMClient:
    """ A client for a CRM gateway that uses a line protocol - each request is one line of text
    and the gateway answers with one line too. A new connection is opened per request, which makes
    the client safe to share between concurrent calls.
    """
    def __init__(self, host:'str', port:'int', api_key:'str') -> 'None':
        self.host = host
        self.port = port
        self.api_key = api_key

    def send(self, data:'str') -> 'str':

        # Connect for the duration of one request ..
        with socket.create_connection((self.host, self.port)) as conn:

            # .. send the request line, prefixed with the key the gateway expects ..
            request = f'{self.api_key} {data}\n'
            conn.sendall(request.encode('utf8'))

            # .. and read the response line back.
            with conn.makefile('r', encoding='utf8') as reader:
                response = reader.readline()

        return response.strip()

class CRMConnector(Connector):
    """ Wraps the CRM gateway's client as a connection type that services access through self.out.crm.
    """
    type = 'crm'

    # Configuration schema
    host = Field.Text()
    port = Field.Int(default=9950)
    api_key = Field.Secret()

    def create_client(self) -> 'CRMClient':
        return CRMClient(self.config.host, self.config.port, self.config.api_key)

    def ping(self, client:'CRMClient') -> 'None':
        client.send('ping')

    def on_stop(self, client:'CRMClient') -> 'None':
        self.logger.info('CRM client for `%s` stopped', self.name)

    def get_customer(self, customer_id:'str') -> 'str':
        return self.client.send(f'get-customer {customer_id}')
```

`type = 'crm'` is the one field every connector declares. The platform derives the rest from it - services access connections of this type through `self.out.crm`, the registered type name is `outconn-crm` and the enmasse key is `custom_crm`.

`host`, `port` and `api_key` declare the configuration schema. Each definition of the `crm` type sets its own values for them, and `api_key`, declared as `Field.Secret`, is stored encrypted.

`create_client` and `ping` are the two methods the platform requires. `on_stop` is optional - it runs when a definition is deleted or edited, and a client uses it to flush its buffers and close its sockets. This client opens a connection per request, so its `on_stop` only logs the stop.

Services call `get_customer` and any other invocation methods you add - one for each operation the remote system supports.

## Deploy the connector {#deploy-the-connector}

Copy the module to your [hot-deployment directory](https://zato.io/tutorials/devops/deployment.html), like any other Python code in Zato. The server registers the new type and logs the registration:

```text
Registered connector type `outconn-crm` (CRMConnector)
```

Redeploying the module later updates the type in place - definitions that are already running keep working.

## Create a definition {#create-a-definition}

Create definitions of the new type in an [enmasse](https://zato.io/docs/admin/enmasse.html) file, under the `custom_crm` key - `custom_` followed by the connector's type:

```yaml
custom_crm:
  - name: My CRM
    host: 10.152.81.19
    port: 9950
    api_key: my-api-key
```

`host`, `port` and `api_key` are the fields the connector declared - every definition of the `crm` type includes them, and `api_key` is encrypted before it is stored.

Deploy the enmasse file like the rest of your configuration, as described in the [DevOps blueprint](https://zato.io/tutorials/devops/deployment.html). The connection starts as soon as the file is imported, without a server restart.

## Call the connection from a service {#call-the-connection-from-a-service}

Services reach the connection through `self.out.crm`, by the name the definition was created with:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class GetCustomer(Service):
    """ Returns one customer from the CRM gateway.
    """
    name = 'demo.crm.get-customer'

    input = 'customer_id'

    def handle(self) -> 'None':
        conn = self.out.crm['My CRM']
        response = conn.get_customer(self.request.input.customer_id)
        self.response.payload = response
```

`self.out.crm['My CRM']` returns the connector instance, so `conn.get_customer` is the method the connector defined, with the client already built and configured underneath.

## Lifecycle {#lifecycle}

Editing the definition - changing its fields in the YAML and deploying the file again - stops the current client through `on_stop` and builds a new one with the new configuration. Deleting the definition also stops the client through `on_stop` and removes it from the server. After a server restart, the definition starts automatically once the connector module is deployed at boot.

The connector is complete - services call the CRM gateway through a named connection and the platform manages the client's lifecycle.

## See also {#see-also}

- [SDK reference](https://zato.io/docs/sdk/reference.html) - Field types, lifecycle methods and the behavior of every invocation
- [Handshake protocols and pooled connections](https://zato.io/docs/sdk/stateful-sockets.html) - Clients that log on first, pooled by the platform
- [Server push and subscriptions](https://zato.io/docs/sdk/subscriptions.html) - Deliver pushed messages to services and topics
