REST adapter
The RESTAdapter base class - declarative API calls with response mapping and no boilerplate.
The REST adapter is a base class for services that call external REST APIs declaratively - a subclass of RESTAdapter names its connection and HTTP method as class attributes and implements no handle method. The adapter manages the connection, authentication, request serialization, response parsing and retries.
Basic usage
Subclass RESTAdapter and set conn_name to the name of an outgoing REST connection - this is the only required attribute. The adapter invokes the configured endpoint when the service runs and the response becomes self.response.payload:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import RESTAdapter
class GetFlight(RESTAdapter):
""" Retrieves flight data from the scheduling system.
"""
name = 'airport.adapter.get-flight'
conn_name = 'flight.ops'
The default method is GET. For POST, PUT, DELETE or another HTTP method, set the method attribute:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import RESTAdapter
class UpdateRunwayStatus(RESTAdapter):
""" Updates runway availability status.
"""
name = 'airport.adapter.update-runway-status'
conn_name = 'runway.ops'
method = 'POST'
An adapter is used in two ways:
- Invoke it from another service: an orchestration service calls the adapter through
self.invoke, combines its result with data from other sources and returns one response - each adapter stays focused on one external system - Expose it on a REST channel: external clients call your endpoint, the adapter forwards the request to the external system, transforms the response and returns it, with no code beyond the adapter class
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class GetFlightStatus(Service):
name = 'demo.rest.get-flight-status'
def handle(self) -> 'None':
flight = self.invoke('airport.adapter.get-flight', id=self.request.input.flight_id)
self.response.payload = flight
Dynamic configuration
When request parameters depend on input data or configuration, override the matching get_* method - the adapter calls it on each invocation and uses what it returns:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import RESTAdapter
# The airport the weather API reports on when the caller names none
_default_airport = 'BIKF'
class GetWeatherData(RESTAdapter):
""" Fetches weather data for airport operations.
"""
name = 'airport.adapter.get-weather-data'
conn_name = 'weather.api'
def get_headers(self):
airport_code = self.request.input.get('airport_code', _default_airport)
return {
'X-Request-ID': self.cid,
'X-Airport-Code': airport_code,
}
def get_query_string(self, params):
params['unit'] = 'metric'
return params
Response mapping with models
The adapter deserializes JSON responses into data models. Set the model attribute to your Model class and the adapter parses the response into an instance of it, with type-safe access to every field:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Model, RESTAdapter
class AircraftModel(Model):
id: int
registration: str
type_code: str
class GetAircraft(RESTAdapter):
name = 'airport.adapter.get-aircraft'
conn_name = 'fleet.api'
model = AircraftModel
Custom response transformation
When the external format differs from your own - other field names, codes or structures - implement map_response. The method receives the parsed response data, already converted to your model class when one is set, and returns your canonical model:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Model, RESTAdapter
class ExternalFlightData(Model):
flight_id: int
carrier: str
dep_time_utc: str
status_code: str
class Flight(Model):
id: int
airline: str
departure_time: str
status: str
class GetFlightDetails(RESTAdapter):
name = 'airport.adapter.get-flight-details'
input = 'id'
output = Flight
has_query_string_id = True
conn_name = 'Flight.Schedule'
model = ExternalFlightData
def map_response(self, data:'ExternalFlightData') -> 'Flight':
# Configuration translates the external codes ..
status = self.config.airport.status_codes[data.status_code]
airline = self.config.airport.carriers[data.carrier]
# .. and the canonical model carries the translated values.
out = Flight()
out.id = data.flight_id
out.airline = airline
out.departure_time = data.dep_time_utc
out.status = status
return out
Path parameters
When the outgoing connection's URL contains placeholders like /terminals/{terminal_id}/gates/{gate_id}, implement get_path_params to return the values. The adapter substitutes them into the URL, and parameters the path does not use become query string parameters:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import RESTAdapter
class GetGateAssignment(RESTAdapter):
name = 'airport.adapter.get-gate-assignment'
input = 'terminal_id', 'gate_id'
conn_name = 'Gate.Management'
method = 'GET'
def get_path_params(self, params):
return {
'terminal_id': self.request.input.terminal_id,
'gate_id': self.request.input.gate_id,
}
Request data
For POST, PUT and PATCH requests, implement get_request to return the request body - the adapter serializes dicts and Model objects to JSON:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import RESTAdapter
class SubmitCargoManifest(RESTAdapter):
name = 'airport.adapter.submit-cargo-manifest'
input = 'manifest'
conn_name = 'Cargo.System'
method = 'POST'
def get_request(self):
return self.request.input.manifest
Base adapters
When several adapters share configuration - the same authentication, headers or conventions - move it into a base class the others inherit from. When the external system changes its authentication, you update the base class and every adapter follows:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import RESTAdapter
class BaseAirportAdapter(RESTAdapter):
sec_def_name = 'Airport.OAuth.Token'
class GetRunway(BaseAirportAdapter):
name = 'airport.adapter.get-runway'
conn_name = 'Airfield.Runways'
input = 'id'
has_query_string_id = True
class GetTaxiway(BaseAirportAdapter):
name = 'airport.adapter.get-taxiway'
conn_name = 'Airfield.Taxiways'
input = 'id'
has_query_string_id = True
Invoke adapter services
Adapter services are regular Zato services - other services invoke them through self.invoke, with input parameters arriving through the standard self.request.input. The calling service receives the mapped result directly:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class SyncGateAssignments(Service):
""" Assigns available gates to flights that have none.
"""
name = 'demo.rest.sync-gate-assignments'
def handle(self) -> 'None':
flight_list = self.invoke('airport.adapter.get-flight-list')
gate_list = self.invoke('airport.adapter.get-gate-list', status='available')
for flight in flight_list:
if flight.gate_id not in gate_list:
self.invoke('airport.adapter.assign-gate', flight_id=flight.id)
Adapter services can also be exposed on REST channels, so external clients receive a transformed view of an external API directly.
All configuration options
Connection and method
| Name | Default | Description |
|---|---|---|
| conn_name | '' | Name of the outgoing REST connection to use |
| method | 'GET' | HTTP method (GET, POST, PUT, DELETE, PATCH, etc.) |
| get_conn_name | None | Callable returning the connection name dynamically |
| get_method | None | Callable returning the HTTP method dynamically |
Request data
| Name | Default | Description |
|---|---|---|
| get_request | None | Callable returning the request body data |
| get_query_string | None | Callable returning query string parameters as a dict |
| get_path_params | None | Callable returning path parameters as a dict |
| get_headers | None | Callable returning HTTP headers as a dict |
| has_query_string_id | False | If True, automatically adds an 'id' parameter to query string from input |
| query_string_id_param | None | Custom name for the query string ID parameter (default: 'id') |
| has_json_id | False | If True, uses JSON ID parameter |
| json_id_param | None | Custom name for the JSON ID parameter |
Authentication
| Name | Default | Description |
|---|---|---|
| sec_def_name | None | Name of the security definition to use |
| auth_scopes | '' | OAuth scopes to request |
| get_sec_def_name | None | Callable returning the security definition name dynamically |
| get_auth_scopes | None | Callable returning auth scopes dynamically |
| get_auth_bearer | None | Callable returning a bearer token to add to the Authorization header |
Response handling
| Name | Default | Description |
|---|---|---|
| model | None | A Model class to map the response to |
| map_response | None | Callable to transform the response data |
| log_response | False | Whether to log the response |
| needs_raw_response | False | If True, returns a RESTAdapterResponse with both parsed data and raw response |
Retry configuration
| Name | Default | Description |
|---|---|---|
| max_retries | None | How many times a failed invocation is retried, a failure being a timeout, a connection error or an HTTP 429 response. The effective default is 0, i.e. no retries. |
| retry_sleep_time | None | How many seconds to sleep before the first retry. The effective default is 2. |
| retry_backoff_multiplier | None | Each retry sleeps this many times longer than the previous one, up to 8 seconds per a single sleep. The effective default is 2. |
| retry_backoff_threshold | None | A cap on the total time spent sleeping between retries, in seconds - once reached, no more retries take place. It is not a retry count. The effective default is 60. |
When an adapter leaves these attributes as None, the retry config of the underlying outgoing connection applies - the one set in the Dashboard under "More options" or through enmasse. Only when the connection has no retry config of its own do the defaults above take effect. Setting any of the attributes in an adapter subclass overrides both the connection's config and the defaults. To learn how the settings work, with examples, and how a Retry-After header is honoured, see retries.
See also
| Page | What it covers |
|---|---|
| Outgoing connections | The connections adapters name in conn_name |
| Data mapping | The transformation patterns map_response implements |
| Calling REST APIs | Invoking connections directly, without an adapter |
| REST channels | Exposing adapter services to external clients |