# 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](https://zato.io/docs/dev/config/dashboard.html) and `self.config` are two views of the same files - the [configuration files](https://zato.io/docs/dev/examples/config.html) in your project's `config/user-conf` directory. A person edits them on the screen, a service reads them through `self.config`, and nothing stands between the two but the file itself.

## The file name is the key {#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 {#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, is accessed in code with the same structure:

```ini
# 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
```

```python
# -*- 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 {#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:

```python
# -*- 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 with `in` work on sections too, which is how a service tells a configured courier from an unknown one - and a new courier is added in the file, with this code left unchanged:

```python
# -*- 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 {#a-table-in-a-file-or-a-lookup-in-code}

The same mapping can be kept in code or in a file, and the difference becomes apparent when something changes. First, as a dictionary inside a service:

```python
# -*- 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:

```ini
# config/user-conf/partners.ini

[NORDIC_MOBILE]
ACTIVE = ACTIVE
BARRED = SUSPENDED
CLOSED = DEACTIVATED
```

```python
# -*- 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](https://zato.io/docs/dev/config/dashboard.html), tried out in the Translate column before it is saved, and in effect on every server immediately after. The service does not change, because it never knew the partners by name in the first place. The [translate and validate methods](https://zato.io/docs/dev/examples/config-tables.html) build on this same idea.

## Saving makes it live {#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. `self.config` always returns the current contents.

## What the screen does not touch {#what-the-screen-does-not-touch}

Two things you will not meet in `self.config`:

- `env.ini` holds environment variables and is never shown on the screen
- `.yaml` files are edited on the screen like any other, but they are not configuration files in this sense - they never appear in `self.config`

## Learn more {#learn-more}

- [REST integration overview](https://zato.io/integrations/rest/) - Start here to see how teams build REST integrations
- [In-depth API tutorial](https://zato.io/tutorials/main/01.html) - Master architectural patterns for production-ready integrations
- [Modern REST API tutorial](https://zato.io/tutorials/rest-api/python.html) - Design, build and secure REST APIs with Python
