Python client

Zato provides a convenience client for use by other Python applications to invoke services in a given cluster. Because Zato services can be exposed through many methods, there is no hard requirement that Python applications use the client, instead it’s rather meant to be an easy to use API that a Zato user would like to have in any case.

Python client works by invoking the services over HTTP, through plain HTTP or SOAP channels that can be secured with HTTP Basic Auth. The underlying HTTP library is requests.

CLI’s zato service invoke and the web admin both use AnyServiceInvoker.

Depending on how exactly you’d like to invoke a Zato service you will want to pick a particular client class, each documented later in the chapter. The classes are defined in the zato.client module.

AnyServiceInvoker is the only client class that can be used for executing other services asynchronously. All other classes always block waiting for the response to come though the service that is being invoked may be naturally an asynchronous one and the response will arrive very quickly.

There’s nothing preventing Zato users from creating their own clients by re-using the existing one or creating them from scratch. Clients don’t have to be limited to HTTP either, a specialized application may, for instance, define a reusable AMQP client instead.

Client classes

__init__

Each client class accepts the same set of arguments when creating an instance.

__init__(address, path, auth=None, session=None, to_bunch=False, max_response_repr=DEFAULT_MAX_RESPONSE_REPR, max_cid_repr=DEFAULT_MAX_CID_REPR, logger=None)
Parameters:
  • address (string) – Address using the format of http://host:port of a Zato server or load-balancer a service to be invoked is defined on
  • path (string) – URL path of a channel the service to be invoked is accessed through
  • auth (tuple) – Optional tuple of (username, password), in plain text, corresponding to an HTTP Basic Auth definition the channel is secured with
  • to_bunch (bool) – Can be used with AnyServiceInvoker, JSONClient and JSONSIOClient only - whether the response should be converted to a Bunch instance before it’s returned
  • max_response_repr (int) – If using a response’s __repr__ method, when it’s being printed out on screen for instance, how many characters of the response actually to output. Defaults to zato.client.DEFAULT_MAX_RESPONSE_REPR which is 2500 characters.
  • max_cid_repr (int) – Used in the same circumstances as max_response_repr. A CID has always 40 characters, however, when logging responses on screen it’s sometimes desirable to limit the numbers of characters printed on either end of a CID. For instance, if CID is K149081821097240318192876103867382447697 and max_cid_repr is 5, __repr__ will use ‘K1490..47697’ with any characters in between suppressed. max_cid_repr defaults to 5 and you can use the zato.client.CID_NO_CLIP constant to turn the clipping feature off.

AnyServiceInvoker

Invokes any service by its name or ID. This is the manner that is usually most convenient to use, however the ease of use comes at a certain price.

The way it’s implemented, a client instance invokes zato.service.invoke using BASE64 to encode/decode requests/responses to the service you actually want to invoke and all that work adds an overhead which means the performance may suffer - it’s up to you to decide whether the loss of performance, if any, is acceptable or not.

invoke

invoke(name=None, payload='', headers=None, channel='invoke', data_format='json', transport=None, id=None, to_json=True, output_repeated=ZATO_NOT_GIVEN)

Invokes a service synchronously, the client will block waiting for the response.

Parameters:
  • name (string) – Name of the service to invoke. Either name or id must be provided.
  • payload (any) – Payload to invoke the service with. May be a Python dictionary if it’s a SIO service and to_json is True.
  • headers (dict) – HTTP headers to pass in to the service invoker
  • channel (zato.common.CHANNEL) – Channel the service will be invoked with
  • data_format (zato.common.DATA_FORMAT) – Data format the service will be invoked with
  • transport (zato.common.TRANSPORT) – Transport the service will be invoked over
  • id (int) – ID of the service to invoke. Either name or id must be provided.
  • to_json (bool) – Whether payload should be converted to JSON
  • output_repeated (bool) – Whether the expected output is repeated. If left at zato.common.ZATO_NON_GIVEN, it will be assumed to be true if the service name ends in ‘list’, so that ‘my.application.customer.get-list’ will by default be assumed to return a SIO list of objects
Return type:

A Response object

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from zato.client import AnyServiceInvoker

address = 'http://localhost:17010'
path = '/zato/admin/invoke'
auth = ('admin.invoke', 'ccd77ca1ff414c3e8308e3f35d8292df')

client = AnyServiceInvoker(address, path, auth)
response = client.invoke('zato.ping')

if response.ok:
    print(response.data)
else:
    print(response.details)
$ py myclient.py
{u'pong': u'zato'}
$

invoke_async

Invokes a service asynchronously. Works exactly like invoke except that the client isn't blocked. Instead a message to invoke the service is published in Redis and client receives the CID a service has been invoked with so that its actual response can be correlated with the client request at a later time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from zato.client import AnyServiceInvoker

address = 'http://localhost:17010'
path = '/zato/admin/invoke'
auth = ('admin.invoke', 'ccd77ca1ff414c3e8308e3f35d8292df')

client = AnyServiceInvoker(address, path, auth)
response = client.invoke_async('zato.ping')

if response.ok:
    print(response.data)
else:
    print(response.details)
$ py myclient.py
K159396487714757679933451979778422654591
$

JSONClient

Invokes a JSON service synchronously.

invoke

invoke(payload='', headers=None, to_json=True)
Parameters:
  • payload (string) -- Payload to invoke the service with
  • headers (dict) -- HTTP headers to invoke the service with
  • to_json (bool) -- Whether payload should be converted to JSON
Return type:

A Response object

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from zato.client import JSONClient

address = 'http://localhost:17010'
path = '/zato/json/zato.service.get-by-name'
auth = ('username', 'password')

client = JSONClient(address, path, auth)

payload = {'name':'zato.ping', 'cluster_id':1}
response = client.invoke(payload)

if response.ok:
    print(response.data)
else:
    print(response.details)
$ py myclient.py
{u'zato_env': {u'cid': u'K161911401323618049176944341958265621001',
    u'details': u'', u'result': u'ZATO_OK'},
 u'zato_service_get_by_name_response': {
    u'impl_name': u'zato.server.service.internal.Ping', u'name': u'zato.ping',
    u'may_be_deleted': False, u'id': 118L, u'is_internal': True,
    u'is_active': True, u'slow_threshold': 99999L, u'time_min_all_time': 0L,
    u'time_mean_all_time': 5.2, u'usage': 67L, u'time_last': 6L,
    u'time_max_all_time': 8L}}
$

JSONSIOClient

Synchronously invokes an SIO service using JSON.

invoke

invoke(payload='', headers=None, to_json=True)
Parameters:
  • payload (string) -- Payload to invoke the service with
  • headers (dict) -- HTTP headers to invoke the service with
  • to_json (bool) -- Whether payload should be converted to JSON
Return type:

A Response object

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from zato.client import JSONSIOClient

address = 'http://localhost:17010'
path = '/zato/json/zato.scheduler.job.get-by-name'
auth = ('username', 'password')

client = JSONSIOClient(address, path, auth)

payload = {'name':'zato.stats.aggregate-by-minute', 'cluster_id':1}
response = client.invoke(payload)

if response.ok:
    print(response.data)
else:
    print(response.details)
$ py myclient.py
{u'name': u'zato.stats.aggregate-by-minute', u'extra': u'',
 u'seconds': 60L, u'is_active': True, u'cron_definition': None,
 u'job_type': u'interval_based', u'days': None,
 u'start_date': u'2013-02-25T09:30:13', u'hours': None,
 u'service_name': u'zato.stats.aggregate-by-minute', u'service_id': 258L,
 u'weeks': None, u'repeats': None, u'minutes': None, u'id': 2L}
$

XMLClient

Synchronously invokes a service providing an XML document on input.

invoke

invoke(payload='', headers=None)
Parameters:
  • payload (string) -- XML payload to invoke the service with
  • headers (dict) -- HTTP headers to invoke the service with
Return type:

A Response object

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from zato.client import XMLClient

address = 'http://localhost:17010'
path = '/my/xml/service'
auth = ('username', 'password')

client = XMLClient(address, path, auth)

payload = '<customer><id>123</id></customer>'
response = client.invoke(payload)

if response.ok:
    print(response.data)
else:
    print(response.details)

SOAPClient

Synchronously invokes a SOAP service.

invoke

invoke(soap_action, payload=None, headers=None)
Parameters:
  • soap_action (string) -- SOAP action a channel the service is exposed through uses
  • payload (string) -- SOAP payload to invoke the service with
  • headers (dict) -- HTTP headers to invoke the service with
Return type:

A Response object

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# lxml
from lxml import etree

# Zato
from zato.client import SOAPClient

address = 'http://localhost:17010'
path = '/zato/soap'
auth = ('username', 'password')

client = SOAPClient(address, path, auth)

payload = """<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns="https://zato.io/ns/20130518">
      <soap:Body>
          <zato_service_has_wsdl_request>
              <cluster_id>1</cluster_id>
              <name>zato.outgoing.ftp.get-list</name>
          </zato_service_has_wsdl_request>
      </soap:Body>
   </soap:Envelope>"""

response = client.invoke('zato.service.has-wsdl', payload)

if response.ok:
    out = etree.tostring(response.data)
else:
    out = etree.tostring(response.details)

print(out)
$ py myclient.py
<zato_service_has_wsdl_response xmlns="https://zato.io/ns/20130518"
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <zato_env>
      <cid>K031150888300645689530407544411404011007</cid>
      <result>ZATO_OK</result>
    </zato_env>
    <item>
      <service_id>15</service_id>
      <has_wsdl>false</has_wsdl>
    </item>
  </zato_service_has_wsdl_response>
$

SOAPSIOClient

Synchronously invokes an SIO service using SOAP.

invoke

invoke(soap_action, payload=None, headers=None)
Parameters:
  • soap_action (string) -- SOAP action a channel the service is exposed through uses
  • payload (string) -- SOAP payload to invoke the service with
  • headers (dict) -- HTTP headers to invoke the service with
Return type:

A Response object

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# lxml
from lxml import etree

# Zato
from zato.client import SOAPSIOClient

address = 'http://localhost:17010'
path = '/zato/soap'
auth = ('username', 'password')

client = SOAPSIOClient(address, path, auth)

payload = """<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns="https://zato.io/ns/20130518">
      <soap:Body>
          <zato_service_has_wsdl_request>
              <cluster_id>1</cluster_id>
              <name>zato.outgoing.ftp.get-list</name>
          </zato_service_has_wsdl_request>
      </soap:Body>
   </soap:Envelope>"""

response = client.invoke('zato.service.has-wsdl', payload)

if response.ok:
    out = etree.tostring(response.data)
else:
    out = etree.tostring(response.details)

print(out)
<zato_service_has_wsdl_response xmlns="https://zato.io/ns/20130518"
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <zato_env>
      <cid>K015583100463424928664896214562918540979</cid>
      <result>ZATO_OK</result>
    </zato_env>
    <item>
      <service_id>15</service_id>
      <has_wsdl>false</has_wsdl>
    </item>
  </zato_service_has_wsdl_response>

RawDataClient

A pass-through client which doesn't parse anything and doesn't expect input and output data to be in any particular data format either.

invoke

invoke(payload='', headers=None)
Parameters:
  • payload (string) -- Payload to invoke the service with
  • headers (dict) -- HTTP headers to invoke the service with
Return type:

A Response object

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from zato.client import RawDataClient

address = 'http://localhost:17010'
path = '/my/service'
auth = ('username', 'password')

client = RawDataClient(address, path, auth)

payload = 'My payload'
response = client.invoke(payload)

if response.ok:
    print(response.data)
else:
    print(response.details)

Response object

The result of invoking a service, no matter if in a blocking way or asynchronously, is always a Response object which provides access to the underlying data a service produced along with a couple of useful attributes.

Depending on the client class used, the response attributes may hold data of different types.

Name Datatype Description
inner requests.Response The inner HTTP response as returned by the requests library
ok boolean

Whether the invocation succeeded. How it's calculated depends on the client class.

has_data boolean

If data a service produced is available. Will be set to True only if ok is True. Otherwise, if ok is not True, details will contain error information.

Note however that both ok and data can be True if a service was invoked with no problems yet it didn't return any output.

data (depends)

Service response in format depending on the client class used. Will be available only if ok and has_data are True.

details (depends)

Error details in format depending on the client class used. Will be available if ok is not True.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# stdlib
import logging

# Zato
from zato.client import JSONClient

logging.basicConfig(level=logging.INFO, format='%(message)s')

address = 'http://localhost:17010'
path = '/zato/ping'
auth = ('username', 'password')

client = JSONClient(address, path, auth)
response = client.invoke()

logging.info(response.ok)
logging.info(response.inner.status_code)
logging.info(response.has_data)
logging.info(response.details)
logging.info(response.data)
$ py myclient.py
True
200
True
None
{u'zato_env': {u'cid': u'K172368768454740187781748699048481406944',
  u'details': u'', u'result': u'ZATO_OK'}, u'zato_ping_response':
    {u'pong': u'zato'}}
$

Each Response object has a helpful string representation that may at times come in handy.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from zato.client import JSONClient

address = 'http://localhost:17010'
path = '/zato/json/zato.service.get-by-name'
auth = ('username', 'password')

client = JSONClient(address, path, auth)

payload = {'name':'zato.ping', 'cluster_id':1}
response = client.invoke(payload)

print(response)
$ py myclient.py
<JSONResponse at 0x27fa290 ok:[True] inner.status_code:[200]
  cid:[K0610..15505], inner.text:[{"zato_env": {"details": "", "result": "ZATO_OK",
    "cid": "K061088476838453632321746870188938415505"},
      "zato_service_get_by_name_response": {
        "impl_name": "zato.server.service.internal.Ping",
        "name": "zato.ping", "may_be_deleted": false, "is_internal": true,
        "is_active": true, "slow_threshold": 99999, "time_min_all_time": 0,
        "time_mean_all_time": 3.3, "usage": 70, "time_max_all_time": 8,
        "time_last": 3, "id": 118}}]>
$