Skip to content

Commit afa8f28

Browse files
committed
update docs
1 parent 43bff66 commit afa8f28

2 files changed

Lines changed: 64 additions & 2 deletions

File tree

AGENTS.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ FourcatModule (common/lib/fourcat_module.py)
6161

6262
### Database Schema (key tables in `backend/database.sql`)
6363
- `settings` — key-value config store (name, value, tag).
64+
- `settings_declarations` — which module last declared each setting, and when it was last seen declared. A setting in `settings` with no row here is one nothing currently declares; that is not enough to remove it, since its extension may merely be uninstalled or disabled.
65+
- `settings_archive` — settings removed from `settings` are moved here rather than deleted, so removal is reversible.
6466
- `jobs` — job queue (jobtype, remote_id, timestamps, interval for recurring).
6567
- `datasets` — all datasets (key, type, key_parent for chaining, parameters as JSON, result_file, status, progress, etc.).
6668
- `datasets_owners` — many-to-many user-dataset ownership.
@@ -113,7 +115,9 @@ Invocation (the plain `docker compose` command uses `docker-compose.yml`):
113115
## Configuration
114116
- **INI-based** (`config/config.ini`): Primary runtime config, read by `ConfigManager`. Docker's `docker_setup.py` syncs environment variables into this file.
115117
- **Database**: The `settings` table stores runtime-configurable settings (name/value/tag). `ConfigManager` reads from both INI and database, with memcached caching.
116-
- **Module config**: Module-defined `config` dicts (on worker classes) are collected by `ModuleCollector` at startup and cached to `config/module_config.bin`.
118+
- **Module config**: Module-defined `config` dicts (on worker classes) are collected by `ModuleCollector` at startup and cached to `config/module_config.bin`, with which module declared what in `config/module_config_provenance.bin` alongside it. The two files are deliberately separate: the first is merged straight into the config definition by every reader, including a front-end container that may still be running older code, so its shape is frozen — do not combine them. **Because these are caches written at start-up, a change to a module's `config` needs a back-end restart before it is visible.**
119+
- **Setting declarations**: a module may not redeclare a setting core already defines, or use a namespace reserved for core (`privileges.`, `flask.`, `4cat.`, `path.`, `datasources.`, `extensions.`, `logging.`). Such declarations are refused, not merged, because a definition controls a setting's `global` flag, `default` and `type`. See `ModuleCollector.collect_module_config()`.
120+
- **Start-up order**: `config.ensure_database()` runs *before* `ModuleCollector` in `backend/bootstrap.py`, because the collector reads `extensions.enabled` and `datasources.expiration` from the database. It therefore only ever sees the previous boot's module cache. Anything that needs the current module set — such as recording declarations — has to be a separate pass after the collector, not folded into `ensure_database()`.
117121
- **Legacy**: `config.py` in the repo root contains some legacy constants. Prefer `ConfigManager` / `config.ini` patterns.
118122
- **Extensions**: Installed under `config/extensions/`. Each extension can include its own `requirements.txt` (auto-installed by `setup.py`). Enabled/disabled via `extensions.enabled` setting.
119123

config/extensions/README.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,62 @@ folder. An extension containing both processors and a data source could look lik
3737
│ ├─ search_my_datasource.py
3838
```
3939

40-
In this scenario, `my_extension` would be a git repository within which all other files are contained.
40+
In this scenario, `my_extension` would be a git repository within which all other files are contained.
41+
42+
## Settings
43+
An extension can add its own settings to 4CAT's settings panel. Declare them in a `config` dictionary on the worker or
44+
processor class:
45+
46+
```python
47+
class MyProcessor(BasicProcessor):
48+
type = "my-processor"
49+
50+
config = {
51+
"my_extension.api_key": {
52+
"type": UserInput.OPTION_TEXT,
53+
"default": "",
54+
"help": "API key",
55+
"tooltip": "Get one from the service's developer console"
56+
}
57+
}
58+
```
59+
60+
Read them with `self.config.get("my_extension.api_key")`.
61+
62+
Note that the `config` must be on the class, not on a data source's `__init__.py` - a data source's settings belong on
63+
its search or import worker, which 4CAT collects along with every other worker.
64+
65+
### Naming
66+
Put your settings under a prefix of your own. The part before the first `.` decides which tab they appear under, so
67+
related settings sharing a prefix are grouped together. For new extensions, `extensions.<your extension id>.` is
68+
recommended, since it cannot collide with anything else.
69+
70+
Some namespaces are reserved for 4CAT itself, and a setting declared in one of them will be **refused**: `privileges.`,
71+
`flask.`, `4cat.`, `path.`, `datasources.`, `extensions.` and `logging.`. This is so that an extension cannot claim a
72+
name 4CAT might use in a future version, and thereby take over its definition.
73+
74+
A setting that a 4CAT release already declares is also refused - 4CAT's own definition always wins. Where two extensions
75+
declare the same setting, the first one wins, and which is 'first' does not depend on the machine. Refusals are written
76+
to the 4CAT log, so if a setting of yours does not show up, look there first.
77+
78+
Several classes sharing a base class that declares `config` is fine: the setting is registered once, and inheriting it
79+
is not treated as a collision.
80+
81+
### Settings that your code writes
82+
If a setting holds something your worker maintains rather than something an administrator sets - a cache timestamp, a
83+
list of options fetched from an API - mark it `"indirect": True`. It will then be kept out of the settings form, so it
84+
cannot be edited or overwritten by someone saving that page.
85+
86+
### What happens to settings when an extension goes away
87+
4CAT does not delete an extension's settings. If your extension is switched off or uninstalled, its stored values are
88+
kept, and enabling or re-installing it restores the previous configuration instead of reverting to defaults. While the
89+
extension is not loaded, its settings are listed under 'unused settings' in the control panel, marked as kept on
90+
purpose.
91+
92+
This means you do not need to worry about an administrator losing their configuration across an upgrade that briefly
93+
removes your extension. It also means that renaming one of your settings leaves the old value behind; 4CAT will not
94+
clean that up for you, because it cannot tell a rename from an uninstall.
95+
96+
### Applying changes
97+
Declarations are read once, when the 4CAT back-end starts, and cached. After adding, renaming or removing a setting in
98+
your code, restart 4CAT before expecting the change in the interface.

0 commit comments

Comments
 (0)