Config tables and self.config
The Config tables screen and self.config as two views of the same files - what you save is what services read.
The Config tables screen and self.config are two views of the same files - the configuration files in your project's config/user-conf directory. The screen is where a person edits them, self.config is where a service reads them, and nothing stands between the two but the file itself.
The file name is the key
A file is read in code under its name up to the first dot - statuses.ini is self.config.statuses, partners.ini is self.config.partners. The listing on the screen shows each file under that same name, so what you see in the dashboard is literally the expression you write in a service.
Nested sections
Sections nest with additional brackets - [[ ]] inside [ ], [[[ ]]] inside [[ ]], as deep as you need - and each level of brackets is another level of dot access in code. A file that keeps one courier per section, with that courier's services as subsections, reads exactly the way it is written:
# config/user-conf/couriers.ini
[EXPRESS_POST]
[[domestic]]
cutoff_hour = 16
max_weight_kg = 30
[[international]]
cutoff_hour = 12
max_weight_kg = 20
[NORDIC_PARCEL]
[[domestic]]
cutoff_hour = 17
max_weight_kg = 25
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class GetCutoff(Service):
def handle(self):
domestic = self.config.couriers.EXPRESS_POST.domestic
cutoff_hour = domestic.cutoff_hour
max_weight_kg = domestic.max_weight_kg
self.logger.info(f'Domestic cutoff is {cutoff_hour}:00, up to {max_weight_kg} kg')
Values arrive as their Python types - cutoff_hour above is an int, not a string - so the code computes with them directly.
Sections are dictionaries
Every level - the file, each section, each subsection - behaves like a Python dictionary, with all the dict methods. This is what makes a config table more than a place to look up one value at a time. A service that reports on every courier in the file does not know their names in advance and does not need to:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class ReportCouriers(Service):
def handle(self):
couriers = self.config.couriers
for courier_name, services in couriers.items():
service_names = ', '.join(services.keys())
self.logger.info(f'{courier_name} offers: {service_names}')
Membership checks work the same way, which is how a service tells a configured courier from an unknown one - and adding a courier is a change to the file, not to this code:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class CheckCourier(Service):
def handle(self):
courier = self.request.payload['courier']
if courier not in self.config.couriers:
self.response.payload = {'is_known': False}
return
services = self.config.couriers[courier]
self.response.payload = {'is_known': True, 'services': list(services.keys())}
.keys(), .values(), .items(), in, square-bracket access - if a dict does it, a section does it.
A table in a file or a lookup in code
The same mapping can live in code or in a file, and the difference shows the moment something changes. First, as a dictionary inside a service:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
status_map = {
'NORDIC_MOBILE': {
'ACTIVE': 'ACTIVE',
'BARRED': 'SUSPENDED',
'CLOSED': 'DEACTIVATED',
},
}
class ProcessStatus(Service):
def handle(self):
partner = self.request.payload['partner']
code = self.request.payload['status']
partner_map = status_map[partner]
status = partner_map[code]
self.logger.info(f'`{code}` from `{partner}` is `{status}` here')
Now the same mapping as a config table:
# config/user-conf/partners.ini
[NORDIC_MOBILE]
ACTIVE = ACTIVE
BARRED = SUSPENDED
CLOSED = DEACTIVATED
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class ProcessStatus(Service):
def handle(self):
partner = self.request.payload['partner']
code = self.request.payload['status']
status = self.config.partners.translate(source=partner, code=code)
self.logger.info(f'`{code}` from `{partner}` is `{status}` here')
With the dictionary in code, a new partner is a code change - someone edits the module, the change goes through review and deployment, and until then the partner does not exist. Only a developer can make it, and nothing in the dashboard shows the mapping is there at all.
With the config table, a new partner is a new section in one file - added on the Config tables screen, tried out in the Translate column before it is saved, and live on every server the moment it is. The service does not change, because it never knew the partners by name in the first place. The translate and validate methods build on this same idea.
Saving makes it live
Saving on the screen writes the file, and the servers reload it in RAM within a couple of seconds - every server, no restarts, no redeployments. This is the same behavior as saving the file with any other tool, because the dashboard is just one editor among many - your own editor working on the mounted project directory produces exactly the same result. A service reading self.config always sees the current contents.
What the screen does not touch
Two things you will not meet in self.config:
env.iniholds environment variables and is never shown on the screen.yamlfiles are edited on the screen like any other, but they are not configuration files in this sense - they never appear inself.config