Config tables
Validate incoming codes and translate between the code sets of external systems.
A config table is an .ini file in config/user-conf that holds either the list of codes a service accepts or the mapping from an external system's codes to that list. Config tables are configuration files and a service reads them through self.config. A change to a file is applied on all servers without a restart, and the files are editable in the dashboard.
A config table provides two methods. validate returns whether a value is in the accepted list. translate returns the accepted code that an external system's code corresponds to, or, when a target system is given, the code that the target system uses for the same value.
Code lists and mapping tables
A file with a section named [codes] is a code list. Each key is an accepted code and each value is its description:
# config/user-conf/statuses.ini
[codes]
ACTIVE = Active and billable
SUSPENDED = Suspended by billing
DEACTIVATED = Deactivated, number released
A file without a [codes] section is a mapping table. Each section is named after an external system, each key is a code that system sends and each value is the accepted code it corresponds to:
# config/user-conf/partners.ini
[NORDIC_MOBILE]
ACTIVE = ACTIVE
BARRED = SUSPENDED
CLOSED = DEACTIVATED
[ALPINE_TELECOM]
IN_SERVICE = ACTIVE
NON_PAYMENT = SUSPENDED
CANCELLED = DEACTIVATED
[codes] is the only reserved section name. Every other section name identifies an external system.
Validating a value
validate returns True when the value is a key of the [codes] section and False otherwise:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class UpdateSubscriberStatus(Service):
def handle(self):
status = self.request.payload['status']
if not self.config.statuses.validate(status):
self.logger.warning('Status `%s` is not an accepted value', status)
return
description = self.config.statuses.codes[status]
self.logger.info('Status `%s` accepted: %s', status, description)
The [codes] section is a configuration section like any other, so the description of a code is read directly from self.config.statuses.codes.
Translating a value
translate takes the external system the value came from and the value itself, and returns the accepted code:
self.config.partnersis the mapping table to read.sourceis the section of that table, which identifies the external system.codeis the key to look up in that section.
The call returns SUSPENDED.
The codes parameter names a code list that the result is checked against before it is returned. With codes='statuses', a value that the mapping table produces but that statuses.ini does not list is not returned:
status = self.config.partners.translate(source='ALPINE_TELECOM', code='NON_PAYMENT', codes='statuses')
translate returns None in three cases:
- The mapping table has no section named in
source. - The section has no key named in
code. codesis given and the translated value is not in that code list.
A result of None is not an error. How to handle it is up to the service's own code.
Translating to a target system
The target parameter names a second section of the same mapping table. The value is looked up among the values of that section and the key that holds it is returned:
status = self.config.partners.translate(source='ALPINE_TELECOM', code='NON_PAYMENT', target='NORDIC_MOBILE')
NON_PAYMENTis looked up in[ALPINE_TELECOM]and resolves toSUSPENDED.SUSPENDEDis looked up among the values of[NORDIC_MOBILE].- The key that holds it,
BARRED, is returned.
Both systems are sections of one file, so each mapping is configured once. The section that is read forwards for one system is read backwards for another.
translate returns None when the mapping table has no section named in target and when no key of that section holds the value, in addition to the three cases listed above.
A value under more than one target code
A target section may hold the same value under more than one key:
SUSPENDED is held under both BARRED and FROZEN. There is no single code to return, so translate raises AmbiguousTarget:
# -*- coding: utf-8 -*-
# Zato
from zato.common.user_config import AmbiguousTarget
from zato.server.service import Service
class ForwardSubscriberStatus(Service):
def handle(self):
try:
status = self.config.partners.translate(
source='ALPINE_TELECOM', code='NON_PAYMENT', target='NORDIC_MOBILE')
except AmbiguousTarget as e:
self.logger.warning('Cannot translate: %s', e)
return
self.logger.info('Target status is `%s`', status)
The exception message names the file, the target section, the value and the keys that hold it:
There are two resolutions. The target section is changed to hold the value under a single key, or the service reads self.config.partners.NORDIC_MOBILE directly and selects the key itself. The dashboard shows every key a value is held under, so the duplicate is visible while the file is edited.
Processing a status update end to end
The service below receives a subscriber status from a roaming partner, translates it to an accepted status from statuses.ini and forwards the result to a billing system over a REST connection. The billing system receives accepted codes only:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class SyncSubscriberStatus(Service):
def handle(self):
partner = self.request.payload['partner_id']
subscriber_id = self.request.payload['subscriber_id']
partner_status = self.request.payload['status']
status = self.config.partners.translate(source=partner, code=partner_status, codes='statuses')
if status is None:
self.logger.warning('No mapping for code `%s` from source `%s`', partner_status, partner)
self.response.payload = {'is_accepted': False, 'received_status': partner_status}
return
conn = self.rest['Billing API']
_ = conn.post({'subscriber_id': subscriber_id, 'status': status})
self.response.payload = {'is_accepted': True, 'status': status}
The incoming value is kept in partner_status and the translated value in status, so the original value is available for the log message and the response payload.
A partner that renames its states, or a new partner, is a change to partners.ini and to no service.
Healthcare - LOINC observation codes
A measurement in an HL7 v2 message is identified by whatever observation code the sending application uses. The accepted vocabulary is LOINC, so the LOINC codes in use are the code list and each sending application is a section of the mapping table:
# config/user-conf/loinc.ini
[codes]
8302-2 = Body height
29463-7 = Body weight
39156-5 = Body mass index
8867-4 = Heart rate
# config/user-conf/observations.ini
[WELLNESS_APP]
HEIGHT-CM = 8302-2
WEIGHT-KG = 29463-7
BMI = 39156-5
PULSE-BPM = 8867-4
[CHECKUP_KIOSK]
BODY-HT = 8302-2
BODY-WT = 29463-7
In the service, the sending application from MSH-3 is the source and the observation identifier from OBX-3 is the code:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class RecordObservation(Service):
def handle(self):
message = self.request.input
sending_application = message.msh.sending_application
local_code = message.obx.observation_identifier.identifier
loinc_code = self.config.observations.translate(
source=sending_application, code=local_code, codes='loinc')
if loinc_code is None:
self.logger.warning('Code `%s` from `%s` has no LOINC code', local_code, sending_application)
return
loinc_name = self.config.loinc.codes[loinc_code]
self.logger.info('Code `%s` is LOINC `%s` (%s)', local_code, loinc_code, loinc_name)
A new sending application is a new section in observations.ini. No service is changed or redeployed.
Editing config tables in the dashboard
Config tables are edited in the dashboard under Services > Config tables, and Config tables in the dashboard describes the editor. Saving a file applies it on all servers without a restart.

See also
| Feature | What it does |
|---|---|
| Config files | Configuration files in config/user-conf |
| Config tables in the dashboard | The dashboard editor for config tables |
| Messages | Build the payloads the translated codes go into |