Skip to content

Commit f8933bc

Browse files
rlmelvinclaude
andcommitted
docs: add env var configuration pattern guide
Codifies the convention that Python config constant names and environment variable names must be identical, so operators can grep the codebase with the same string they see on Azure config screens. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c72c504 commit f8933bc

1 file changed

Lines changed: 112 additions & 0 deletions

File tree

docs/env_var_config_pattern.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Environment Variable Configuration Pattern
2+
3+
This document describes the pattern for using environment variables to override
4+
application configuration values. The goal is a **single name** that appears
5+
identically in the Python config class, the Docker Compose `environment:`
6+
block, the Azure Container App configuration screen, and any `.env` files.
7+
8+
## Core rule
9+
10+
> **The Python constant name and the environment variable name MUST be
11+
> identical.**
12+
13+
When an operator sets `REASONING_EFFORT=medium` on an Azure container, they
14+
should be able to grep the codebase for that exact string and land on the
15+
config class attribute that consumes it. No prefix translation, no casing
16+
changes.
17+
18+
## How to add a new overridable config value
19+
20+
### 1. Add the class attribute with `os.getenv`
21+
22+
```python
23+
# UMLBot/config/config.py
24+
class UMLBotConfig:
25+
# <short comment explaining the setting and valid values>
26+
MY_NEW_SETTING: str = os.getenv("MY_NEW_SETTING", "default_value")
27+
```
28+
29+
- The first argument to `os.getenv` is the **same string** as the attribute name.
30+
- Always provide a sensible default so the app runs without any env vars set.
31+
- Use a type annotation on the attribute.
32+
33+
### 2. Add it to `docker-compose.yml`
34+
35+
```yaml
36+
services:
37+
umlbot:
38+
environment:
39+
MY_NEW_SETTING: ${MY_NEW_SETTING:-default_value}
40+
```
41+
42+
The `${VAR:-default}` syntax lets the host's env override the compose default,
43+
which in turn overrides the Python default. Use the **same default** in both
44+
places to avoid surprises.
45+
46+
### 3. Add it to `.env.example`
47+
48+
```bash
49+
# Description of the setting (default: default_value)
50+
# MY_NEW_SETTING=default_value
51+
```
52+
53+
Keep entries commented out so a bare `cp .env.example .env` produces a
54+
working configuration that matches the Python defaults.
55+
56+
### 4. Reference it in code
57+
58+
```python
59+
from UMLBot.config.config import UMLBotConfig
60+
61+
value = UMLBotConfig.MY_NEW_SETTING
62+
```
63+
64+
Never call `os.getenv` a second time elsewhere in the code. All env var
65+
reads are centralized in the config class.
66+
67+
## Naming conventions
68+
69+
| Scope | Convention | Example |
70+
|-------|-----------|---------|
71+
| UMLBot-specific infrastructure | `UMLBOT_` prefix | `UMLBOT_PLANTUML_JAR_PATH` |
72+
| LLM / API behaviour settings | Descriptive, no prefix | `REASONING_EFFORT` |
73+
| Cross-service URLs | `UMLBOT_` prefix | `UMLBOT_ENDPOINT` |
74+
75+
The prefix is useful when an env var might collide with another service's
76+
variable in a shared container group. For settings that are clearly
77+
domain-specific (like `REASONING_EFFORT` for OpenAI's Responses API), a prefix
78+
adds noise without reducing ambiguity.
79+
80+
Use your judgement, but when in doubt, match the name the external API or
81+
documentation already uses.
82+
83+
## Current inventory
84+
85+
| Python constant | Env var | Default | Location |
86+
|----------------|---------|---------|----------|
87+
| `UMLBOT_PLANTUML_JAR_PATH` | `UMLBOT_PLANTUML_JAR_PATH` | `/opt/plantuml/plantuml.jar` | `UMLBotConfig` |
88+
| `UMLBOT_DATA_DIR`* | `UMLBOT_DATA_DIR` | `/data` | `UMLBotConfig` |
89+
| `REASONING_EFFORT` | `REASONING_EFFORT` | `low` | `UMLBotConfig` |
90+
| `UMLBOT_ENDPOINT`** | `UMLBOT_ENDPOINT` | `http://localhost:8000` | `streamlit_app.py` |
91+
92+
\* `UMLBOT_DATA_DIR` is consumed as `DATA_DIR` in the config class today.
93+
This is a legacy inconsistency that should be aligned in a future PR.
94+
95+
\** `UMLBOT_ENDPOINT` lives in `streamlit_app.py` as `API_BASE_URL` rather
96+
than in `UMLBotConfig`. This is another legacy inconsistency to align.
97+
98+
## Anti-patterns
99+
100+
- **Different names for the same thing.** Don't name the attribute
101+
`JAR_PATH` and the env var `UMLBOT_PLANTUML_JAR_PATH`. An operator
102+
reading the Azure config screen should be able to find the code with a
103+
single search.
104+
105+
- **Scattered `os.getenv` calls.** Every env-var read should be in
106+
`UMLBotConfig` (or the equivalent config module for other projects).
107+
Duplicate reads in service code make it hard to audit what the app
108+
depends on.
109+
110+
- **Missing defaults.** If a setting doesn't have a default, the app crashes
111+
on startup when the env var is absent. If a value is truly required (like
112+
an API key), use `manage_sensitive()` instead of `os.getenv`.

0 commit comments

Comments
 (0)