Configuration¶
Flexible configuration management with multi-format support, cascading search, and type-checked access.
TL;DR¶
from kstlib.config import ConfigLoader
# Load with auto-discovery (recommended)
config = ConfigLoader().config
# Access with dot notation
print(config.app.name)
print(config.database.host)
# Export default config to customize
kstlib config export --out kstlib.conf.yml
Key Features¶
Multi-format support: YAML, TOML, JSON, and INI
Cascading search: Automatic discovery across multiple locations
Include system: Compose configs from multiple files
Deep merge: Intelligent merging of nested configurations
Dot notation: Easy access to nested values via Box
Type safety: Full type hints for IDE support
Quick Start¶
# kstlib.conf.yml
app:
name: "My Application"
debug: true
database:
host: "localhost"
port: 5432
from kstlib.config import load_from_file
# 1. Load from specific file
config = load_from_file("kstlib.conf.yml")
# 2. Or use auto-discovery
from kstlib.config import ConfigLoader
config = ConfigLoader().config
# 3. Access values with dot notation
print(config.app.name) # "My Application"
print(config.database.port) # 5432
How It Works¶
Loading Strategies¶
Cascading mode (recommended) searches multiple locations in order:
config = ConfigLoader().config
Search order (priority from highest to lowest):
Current working directory (
./kstlib.conf.yml)User’s home directory (
~/kstlib.conf.yml)User’s config directory (
~/.config/kstlib.conf.yml)System-wide config dirs via
platformdirs.site_config_dir:Linux:
/etc/xdg/kstlib/plus every entry in$XDG_CONFIG_DIRSmacOS:
/Library/Application Support/kstlib/Windows:
%PROGRAMDATA%/kstlib/
Package defaults (lowest priority)
System-wide entries are merged silently. If a file does not exist at a given
location, it is skipped without warning or error. This lets operators drop a
shared kstlib.conf.yml in /etc/xdg/kstlib/ (or the platform equivalent)
while users override individual keys in their home directory.
See System-Wide Configuration below for the
full per-OS defaults and the rules that apply when XDG_CONFIG_DIRS lists
several directories.
Direct mode loads from a specific file:
config = load_from_file("path/to/config.yml")
Environment variable mode loads from a path in an env var:
# Uses CONFIG_PATH env var by default
config = ConfigLoader(auto_source="env").config
# Or specify a different env var name
config = ConfigLoader(auto_source="env", auto_env_var="MYAPP_CONFIG_FILE").config
System-Wide Configuration¶
System-wide configuration lets operators ship shared defaults (corporate
endpoints, logging targets, TLS settings, audit rules, …) from a location
that every user of a machine inherits automatically. Users keep their own
~/.config/kstlib.conf.yml and the cascade merges the two without any
manual wiring.
Under the hood, kstlib delegates path discovery to
platformdirs.site_config_dir, so the
behavior matches every other well-behaved XDG-aware tool.
Default paths per OS¶
Platform |
Default system config path |
|---|---|
Linux / BSD |
|
macOS |
|
Windows |
|
Note
On Windows and macOS, writing to these paths usually requires administrator or root privileges. That is intentional: system-wide config is meant to be provisioned by an operator or a configuration management tool (Ansible, Puppet, Chef, Intune, …), not hand-edited by end users.
Linux: XDG_CONFIG_DIRS semantics¶
On Linux and other XDG-compliant Unices, kstlib honors the standard
XDG Base Directory Specification.
The XDG_CONFIG_DIRS environment variable takes a colon-separated list
of directories, ordered from highest to lowest priority:
# /etc/corp/kstlib wins over /etc/xdg/kstlib for common keys
export XDG_CONFIG_DIRS=/etc/corp:/etc/xdg
If XDG_CONFIG_DIRS is unset, kstlib falls back to the single default
/etc/xdg/kstlib/.
Each directory in the list is probed for kstlib.conf.yml. kstlib then
deep-merges every file it finds, so keys defined in the higher-priority
directory override identical keys in the lower-priority ones, while
non-conflicting keys are unioned.
Tip
You can inspect what kstlib will probe on your machine:
python -c "import platformdirs; print(platformdirs.site_config_dir('kstlib', appauthor=False, multipath=True))"
Full cascade at a glance¶
From lowest to highest priority (later entries override earlier ones):
1. Package defaults (shipped inside kstlib)
2. System config dirs:
- $XDG_CONFIG_DIRS entries (Linux, if set) <- lowest system priority
- /etc/xdg/kstlib (Linux default, if XDG unset)
- /Library/Application Support/kstlib (macOS)
- %PROGRAMDATA%\kstlib (Windows) <- highest system priority
3. ~/.config/kstlib.conf.yml
4. ~/kstlib.conf.yml
5. ./kstlib.conf.yml (cwd) <- highest overall
6. Runtime kwargs (supersede every file source)
Missing files at any level are skipped silently. This is the whole point: deployments can pre-provision a system file, and machines that do not have one simply fall through to the next layer.
Example: corporate baseline + user overrides¶
# /etc/xdg/kstlib/kstlib.conf.yml (shipped by IT)
logger:
defaults:
output: file
rotation: daily
alerts:
channels:
slack:
webhook_url: https://hooks.slack.com/services/OPS/CORP/XXXXX
# ~/.config/kstlib.conf.yml (written by the developer)
logger:
defaults:
level: DEBUG # Adds to the corporate baseline
The effective configuration for this user is:
logger:
defaults:
output: file # from system
rotation: daily # from system
level: DEBUG # from user (added)
alerts:
channels:
slack:
webhook_url: https://hooks.slack.com/services/OPS/CORP/XXXXX # from system
The developer cannot accidentally lose the corporate Slack webhook or the file-rotation policy, but they can still opt into DEBUG locally.
Opting out¶
System-wide config is always active in cascading mode, but you can bypass it entirely by using direct mode:
from kstlib.config import load_from_file
# Only this file is loaded - no cascade, no system dirs
config = load_from_file("/opt/myapp/isolated.yml")
Or by scoping the loader to an explicit file:
from kstlib.config import ConfigLoader
loader = ConfigLoader(auto_source="file", auto_path="./test.yml")
Include System¶
Compose configurations from multiple files:
# main.yml
include:
- database.toml
- features.json
app:
name: "My App"
Deep merge behavior:
Nested dictionaries are recursively merged
Lists are replaced (not merged)
Later values override earlier ones
Warning
Override priority matters! Values are merged left-to-right with later sources overwriting earlier ones:
package defaults → user config file → includes → kwargs
This means a value in your config file will override package defaults, and
kwargs passed at runtime will override everything else.
Example: If package defaults set app.debug: false and your config file has
app.debug: true, the final value is true. If you then pass debug=False as
a kwarg, it becomes False again.
Supported Formats¶
Format |
Extensions |
Notes |
|---|---|---|
YAML |
|
Recommended, supports comments |
TOML |
|
Good for hierarchical data |
JSON |
|
Strict, no comments |
INI |
|
Legacy support |
Caching¶
Config is cached after first load:
from kstlib.config import get_config, clear_config
config = get_config() # Cached config (fast)
config = get_config(max_age=0) # Force reload
clear_config() # Clear cache entirely
Interactive usage (Jupyter / REPL)¶
The config singleton is intentionally cached: services that run for hours should not re-read the YAML files on every access. In interactive sessions though, you often edit the config and want the change to take effect immediately, without restarting the kernel.
Warning
If you edit a kstlib.conf.yml file (for example
/etc/xdg/kstlib/kstlib.conf.yml, ~/.config/kstlib.conf.yml, or the one
in your current working directory) while a Python session is running, call
reload_config() to force a refresh. Without this, the singleton cache
keeps the old values and get_config() will continue to return the stale
Box.
reload_config() is the explicit, discoverable alias for “flush the cache
and re-read from disk”. It is equivalent to clear_config() followed by
get_config(), but expresses the intent in a single call.
from kstlib.config import reload_config
# ... you just edited ~/.config/kstlib.conf.yml in another window ...
cfg = reload_config()
print(cfg.mail.default) # reflects the edit
It is also available at the top level, consistent with get_config and
clear_config:
import kstlib
cfg = kstlib.reload_config()
When to use which:
Call |
Purpose |
|---|---|
|
One-shot refresh in interactive work. Clearest intent. |
|
Same behaviour, but the intent is hidden in a kwarg. |
|
Only flushes the cache. The next |
Note
Known issue fixed in 2.3.1: on kstlib 2.3.0, importing kstlib.mail as
the very first kstlib symbol in a fresh Python process (for example
right after Restart Kernel in Jupyter) could raise ImportError due
to a circular import between kstlib.limits and kstlib.config.loader.
Affected versions: 2.3.0 only. Workaround for users still on 2.3.0:
import kstlib.config before the first from kstlib.mail import ....
Upgrading to 2.3.1 or later removes the need for the workaround.
Configuration¶
CLI Export¶
Bootstrap configuration files from package defaults:
# Export full default config
kstlib config export --out kstlib.conf.yml
# Export specific section
kstlib config export --section secrets --out secrets.yml
# Preview to stdout
kstlib config export --stdout
Environment-Based Structure¶
Recommended project layout:
myapp/
├── config/
│ ├── base.yml # Defaults (committed)
│ ├── development.yml # Dev overrides
│ ├── production.yml # Prod overrides
│ └── secrets.yml # Local secrets (gitignored)
└── src/
# config/base.yml
app:
name: "My Application"
debug: false
log_level: INFO
database:
pool_size: 10
timeout: 30
# config/development.yml
include: base.yml
app:
debug: true
log_level: DEBUG
database:
host: localhost
Strict Format Mode¶
Enforce format consistency (all includes must match parent format):
config = load_from_file("config.yml", strict_format=True)
Default Configuration¶
The package ships with sensible defaults. Export to customize:
kstlib config export --out kstlib.conf.yml
Note
Partial override only: You do not need to copy the entire default configuration. The system deep-merges your config with package defaults, so you only specify what you want to change:
# Minimal user config - only override what you need
logger:
defaults:
output: file # Everything else uses package defaults
cache:
default_strategy: lru
This keeps your config clean and maintainable. For larger projects, you can also
split your config into multiple files using the include: directive.
View default configuration
1# Default configuration for kstlib
2# This file is used to set default values for the kstlib library.
3
4###########################################################################################
5## Internal kstlib behavior (opt-in switches controlling library-side features)
6###########################################################################################
7kstlib:
8 # Internal logging activation
9 # ---------------------------
10 # Controls whether kstlib's own log records (from kstlib.auth, kstlib.mail,
11 # kstlib.rapi, ...) are emitted when a consumer never calls init_logging()
12 # explicitly. Disabled by default so applications embedding kstlib stay
13 # silent unless they opt in.
14 #
15 # When enabled, the first call to get_logger() reads this section and
16 # triggers init_logging(preset=...) transparently. Any error (missing
17 # config file, parse failure, etc.) is swallowed silently so kstlib can
18 # never break the host application through this cascade.
19 logging:
20 enabled: false # Opt-in: set to true to activate internal kstlib logs
21 preset: prod # Preset used by auto-init: dev, prod, debug, trace, ...
22 # Unknown presets fall back to "prod" with a one-line
23 # stderr notice listing available names.
24
25 # Per-logger level override (optional, default = no filtering).
26 #
27 # When set, each entry calls logging.getLogger(name).setLevel(level)
28 # right after the kstlib root logger is registered. Useful to silence
29 # the noise of a specific sub-package (e.g. RAPI config loader emitting
30 # 1300+ DEBUG lines per startup) without dropping the level globally.
31 #
32 # Cascade (lowest to highest priority):
33 # 1. kstlib.logging.modules (this section, global default)
34 # 2. logger.presets.<active>.modules (preset-specific override)
35 # 3. CLI flag --log-module (repeatable, REPLACES YAML;
36 # three syntaxes, see below)
37 # Steps 1 and 2 are merged key-by-key; preset wins on shared keys.
38 #
39 # Convention: every kstlib logger follows kstlib.<sub-package>[.<module>],
40 # mirroring the layout of src/kstlib/. Examples:
41 # kstlib.rapi, kstlib.rapi.client, kstlib.rapi.config
42 # kstlib.transform, kstlib.transform.chain
43 #
44 # Available sub-packages:
45 # kstlib.alerts, kstlib.auth, kstlib.cache, kstlib.cli, kstlib.config,
46 # kstlib.db, kstlib.helpers, kstlib.logging, kstlib.mail, kstlib.metrics,
47 # kstlib.monitoring, kstlib.ops, kstlib.pipeline, kstlib.rapi,
48 # kstlib.resilience, kstlib.secrets, kstlib.secure, kstlib.ssl,
49 # kstlib.transform, kstlib.ui, kstlib.utils, kstlib.websocket
50 #
51 # For the full module list, browse src/kstlib/ or the Sphinx
52 # "Logging introspection guide".
53 #
54 # Supported levels (see Sphinx "Logging introspection guide"):
55 # TRACE, DEBUG, INFO, SUCCESS, WARNING, ERROR, CRITICAL
56 # Names are case-insensitive. Invalid entries are skipped with a
57 # WARNING (and WARNING [SECURITY] for names not starting with kstlib.).
58 #
59 # Defaults: kstlib.rapi.config and kstlib.config.loader are the
60 # most verbose loggers at startup (1300+ DEBUG/TRACE lines for
61 # rapi.config on a typical Viya install, large cascade trace for
62 # config.loader when many includes are present). Both muted to
63 # WARNING by default; raise them locally when you need to debug.
64 #
65 # Module mutes are PERSISTENT by design: -v/-vv/-vvv and --log-level
66 # only adjust the root handler level. They do NOT reset these
67 # mutes, so noisy modules stay quiet even under -vvv.
68 #
69 # Override surfaces (priority increasing):
70 # - user kstlib.conf.yml: modules: {kstlib.rapi.config: DEBUG}
71 # (deep merge with these defaults, override per-module)
72 # - preset-specific: presets.<name>.modules: {...}
73 # - CLI --log-module: per-invocation override, three syntaxes
74 # (1) classic, fully-qualified
75 # --log-module kstlib.rapi.config=TRACE
76 # (2) classic, prefix omitted (kstlib. auto-prepended)
77 # --log-module rapi.config=TRACE
78 # (3) inverse, level groups module list
79 # --log-module DEBUG=foo.bar,baz.qux
80 modules:
81 kstlib.rapi.config: WARNING
82 kstlib.config.loader: WARNING
83
84###########################################################################################
85## Datetime formatting (global settings for timestamp display)
86###########################################################################################
87datetime:
88 # Format string for timestamps (pendulum format tokens)
89 # See: https://pendulum.eustace.io/docs/#tokens
90 # Common formats:
91 # - "YYYY-MM-DD HH:mm:ss" (ISO-like, default)
92 # - "DD/MM/YYYY HH:mm:ss" (European)
93 # - "MM/DD/YYYY hh:mm:ss A" (US with AM/PM)
94 # - "ddd D MMM YYYY HH:mm" (Human: "Mon 29 Jan 2026 15:30")
95 # Hard limit: max 64 chars, alphanumeric + common punctuation only
96 format: "YYYY-MM-DD HH:mm:ss"
97
98 # Timezone for display: "local" (system timezone) or IANA timezone name
99 # Examples: "local", "UTC", "Europe/Paris", "America/New_York"
100 # Hard limit: max 64 chars, validated against pendulum timezones
101 timezone: "local"
102
103###########################################################################################
104## Cache configuration
105###########################################################################################
106cache:
107 # Default caching strategy (ttl | lru | memoize | file)
108 default_strategy: ttl
109
110 # TTL (Time-To-Live) cache settings
111 ttl:
112 default_seconds: 300 # 5 minutes
113 max_entries: 1000 # Maximum number of cached entries
114 cleanup_interval: 60 # Cleanup expired entries every 60s
115
116 # LRU (Least Recently Used) cache settings
117 lru:
118 maxsize: 128 # Maximum cache size
119 typed: false # Separate cache for different argument types
120
121 # File-based cache settings
122 file:
123 enabled: true
124 cache_dir: ".cache" # Directory for cache files
125 check_mtime: true # Invalidate cache on file modification
126 serializer: json # json (default) | pickle | auto
127 # Maximum cache file size (prevents OOM on corrupted files)
128 # Accepts: bytes (int) or human-readable string ("100M", "50 MiB")
129 # Hard limit enforced in code: 100 MiB
130 max_file_size: "50M"
131
132 # Async cache support
133 async_support:
134 enabled: true
135 executor_workers: 4 # ThreadPoolExecutor workers for sync functions
136
137 # Metrics and monitoring
138 metrics:
139 enabled: false # Track cache hits/misses (opt-in)
140 log_stats: false # Log statistics periodically
141 stats_interval: 300 # Log stats every 5 minutes
142
143###########################################################################################
144## Logging configuration
145###########################################################################################
146logger:
147 defaults:
148 output: console # console | file | both
149
150 # Color theme for Rich console output
151 theme:
152 trace: "medium_purple4 on dark_olive_green1"
153 debug: "black on deep_sky_blue1"
154 info: "sky_blue1"
155 success: "black on sea_green3"
156 warning: "bold white on salmon1"
157 error: "bold white on deep_pink2"
158 critical: "blink bold white on red3"
159
160 # Icons for each log level
161 icons:
162 show: true
163 trace: "🔬"
164 debug: "🔎"
165 info: "📄"
166 success: "✅"
167 warning: "🚨"
168 error: "❌"
169 critical: "💀"
170
171 # Console handler settings
172 console:
173 level: WARNING # Log level: TRACE | DEBUG | INFO | SUCCESS | WARNING | ERROR | CRITICAL
174 datefmt: "%Y-%m-%d %H:%M:%S"
175 format: "::: PID %(process)d / TID %(thread)d ::: %(message)s"
176 show_path: true
177 tracebacks_show_locals: true
178 # Console routing: stdout (default) | stderr. Route to stderr when the CLI
179 # emits data/JSON on stdout, so logs do not corrupt the piped output
180 # (Unix data vs diagnostics separation). Invalid values raise ConfigError.
181 stream: stdout
182
183 # File handler settings
184 # Two configuration styles are supported:
185 # - New style (recommended): file_path: ./logs/kstlib.log
186 # - Legacy style: log_path + log_dir + log_name (for backward compatibility)
187 # The new style takes priority if file_path is defined.
188 file:
189 level: WARNING # Log level: TRACE | DEBUG | INFO | SUCCESS | WARNING | ERROR | CRITICAL
190 datefmt: "%Y-%m-%d %H:%M:%S"
191 format: "[%(asctime)s | %(levelname)-8s] ::: PID %(process)d / TID %(thread)d ::: %(message)s"
192 # file_path: ./logs/kstlib.log # New style (recommended)
193 # auto_create_dir: true # New style auto-create
194 log_path: "./" # Legacy style (kept for backward compatibility)
195 log_dir: "logs"
196 log_name: "kstlib.log"
197 log_dir_auto_create: true
198
199 # File rotation settings
200 rotation:
201 when: midnight # midnight | S | M | H | D | W0-W6
202 interval: 1
203 backup_count: 7
204
205 presets:
206 dev:
207 output: console
208 console:
209 level: DEBUG
210 show_path: true
211 tracebacks_show_locals: true
212 icons:
213 show: true
214
215 prod:
216 output: file
217 file:
218 level: INFO
219 icons:
220 show: false
221
222 # debug: deeper-than-dev preset for investigation sessions.
223 # Persists to file (output: both) so logs survive the terminal and can be
224 # grepped or shared after the fact, and emits at TRACE so HTTP traces and
225 # detailed diagnostics are captured. Use 'dev' for everyday iteration
226 # (console-only, DEBUG); use 'debug' when actively investigating an issue.
227 debug:
228 output: both
229 console:
230 level: TRACE
231 show_path: true
232 tracebacks_show_locals: true
233 file:
234 level: TRACE
235 format: "[%(asctime)s | %(levelname)-8s] ::: PID %(process)d / TID %(thread)d ::: [%(filename)s:%(lineno)d %(funcName)s] %(message)s"
236 icons:
237 show: true
238
239 trace:
240 output: both
241 console:
242 level: TRACE
243 show_path: true
244 tracebacks_show_locals: true
245 file:
246 level: TRACE
247 format: "[%(asctime)s | %(levelname)-8s] ::: PID %(process)d / TID %(thread)d ::: [%(filename)s:%(lineno)d %(funcName)s] %(message)s"
248 icons:
249 show: true
250
251 # Mail trace preset - verbose SMTP/SSL debugging to dedicated file
252 # Usage: LogManager(preset="trace_mail") or logger.preset: trace_mail
253 trace_mail:
254 output: both
255 console:
256 level: WARNING # Keep console quiet
257 file:
258 level: TRACE
259 file_path: ./logs/mail-trace.log
260 auto_create_dir: true
261 icons:
262 show: true
263
264###########################################################################################
265## UI helpers configuration
266###########################################################################################
267ui:
268 panels:
269 defaults:
270 panel:
271 # border_style supports any Rich color/style (e.g. "blue", "bold green")
272 border_style: "bright_blue"
273 title_align: "left"
274 subtitle_align: "left"
275 padding: [1, 2]
276 expand: true
277 highlight: false
278 # https://rich.readthedocs.io/en/stable/appendix/box.html#appendix-box
279 box: "ROUNDED"
280 content:
281 box: "SIMPLE"
282 expand: true
283 show_header: false
284 key_label: "Key"
285 value_label: "Value"
286 key_style: "bold white"
287 value_style: null
288 header_style: "bold"
289 pad_edge: false
290 sort_keys: false
291 use_markup: true
292 use_pretty: true
293 pretty_indent: 2
294 presets:
295 info:
296 panel:
297 border_style: "cyan"
298 title: "Information"
299 icon: "📘"
300 success:
301 panel:
302 border_style: "sea_green3"
303 title: "Success"
304 icon: "✅"
305 warning:
306 panel:
307 border_style: "orange3"
308 title: "Warning"
309 icon: "🔔"
310 error:
311 panel:
312 border_style: "red3"
313 title: "Error"
314 icon: "❌"
315 summary:
316 panel:
317 border_style: "light_steel_blue1"
318 title: "Execution Summary"
319 icon: "📝"
320 content:
321 sort_keys: true
322 key_style: "bold orchid2"
323 value_style: "dim white"
324 tables:
325 defaults:
326 table:
327 title: null
328 caption: null
329 box: "SIMPLE"
330 show_header: true
331 header_style: "bold cyan"
332 show_lines: false
333 row_styles: null
334 expand: true
335 pad_edge: false
336 highlight: false
337 columns:
338 - header: "Key"
339 key: "key"
340 justify: "left"
341 style: "bold white"
342 overflow: "fold"
343 no_wrap: false
344 - header: "Value"
345 key: "value"
346 justify: "left"
347 style: null
348 overflow: "fold"
349 no_wrap: false
350 presets:
351 inventory:
352 table:
353 title: "Inventory"
354 box: "SIMPLE_HEAVY"
355 show_lines: true
356 header_style: "bold yellow"
357 columns:
358 - header: "Component"
359 key: "component"
360 style: "bold"
361 width: 18
362 - header: "Version"
363 key: "version"
364 style: "cyan"
365 width: 12
366 - header: "Status"
367 key: "status"
368 justify: "center"
369 style: "bold"
370 width: 10
371 metrics:
372 table:
373 title: "Metrics"
374 box: "SIMPLE_HEAD"
375 header_style: "bold green"
376 columns:
377 - header: "Metric"
378 key: "metric"
379 style: "bold"
380 - header: "Value"
381 key: "value"
382 justify: "right"
383 spinners:
384 defaults:
385 # Spinner character style: BRAILLE | DOTS | LINE | ARROW | BLOCKS | CIRCLE | SQUARE | MOON | CLOCK
386 style: "BRAILLE"
387 # Position relative to message: before | after
388 position: "before"
389 # Animation type: spin | bounce | color_wave
390 animation_type: "spin"
391 # Seconds between animation frames
392 interval: 0.08
393 # Rich style for spinner character
394 spinner_style: "cyan"
395 # Rich style for message text (null = default)
396 text_style: null
397 # Character shown on success
398 done_character: "✓"
399 done_style: "green"
400 # Character shown on failure
401 fail_character: "✗"
402 fail_style: "red"
403 presets:
404 minimal:
405 style: "LINE"
406 spinner_style: "dim white"
407 interval: 0.1
408 fancy:
409 style: "BRAILLE"
410 spinner_style: "bold cyan"
411 interval: 0.06
412 blocks:
413 style: "BLOCKS"
414 spinner_style: "blue"
415 interval: 0.05
416 bounce:
417 animation_type: "bounce"
418 spinner_style: "yellow"
419 interval: 0.08
420 color_wave:
421 animation_type: "color_wave"
422 interval: 0.1
423
424###########################################################################################
425## Mail configuration
426###########################################################################################
427mail:
428 # Default named preset used when MailBuilder() is instantiated without
429 # transport= or preset=. Must match a key under mail.presets below.
430 # Leave null to force callers to pass transport= or preset= explicitly.
431 default: null
432
433 # Anti-spam throttle (kill switch). Enforced before the transport on
434 # every MailBuilder.send(), including indirect calls via @mail.notify.
435 #
436 # Cascade (highest priority first):
437 # 1. mail.presets.<name>.throttle.<key> (preset-level override)
438 # 2. mail.throttle.<key> (this section, mail-wide default)
439 # 3. Code defaults (rate=20, per=60.0, on_exceed=raise)
440 #
441 # Each key cascades independently: a preset can override only ``rate``
442 # while keeping the mail-wide ``per`` and ``on_exceed``.
443 #
444 # Modes:
445 # - raise (default): emits WARNING [SECURITY] then raises
446 # MailThrottledError. The caller decides how to back off.
447 # - warn: emits WARNING [SECURITY] then drops the mail silently
448 # (returns the built message without sending).
449 # - drop (silent) is INTENTIONALLY REJECTED at init: a security
450 # event must never be silent (kstlib logging convention).
451 #
452 # Singleton: a single MailThrottle is shared across all builders that
453 # use the same preset, including snapshots taken by the @notify
454 # decorator. This prevents bypass via creating many builder instances.
455 #
456 # Hard limits enforced in code (see kstlib.limits):
457 # - rate: 1 to 1000 mails per period
458 # - per: 1.0 to 86400.0 seconds (1 day)
459 # - on_exceed: "raise" or "warn"
460 throttle:
461 enabled: true
462 rate: 20
463 per: 60.0
464 on_exceed: raise
465
466 # SSL/TLS configuration for mail transports.
467 #
468 # Values here override the root ``ssl:`` section (bottom of this file)
469 # for mail only, and are themselves overridden by ``ssl_verify`` and
470 # ``ssl_ca_bundle`` keys set inside an individual preset. Each key
471 # cascades independently: you can set ``verify: false`` here and still
472 # provide ``ssl_ca_bundle`` at the preset level.
473 #
474 # Security: setting ``verify: false`` at any level emits a WARNING log
475 # at transport build time. Prefer ``ca_bundle: /path/to/private-ca.pem``
476 # for internal PKI rather than disabling verification outright.
477 ssl:
478 verify: true
479 ca_bundle: null
480
481 # Named transport presets. Each preset declares a "transport" field
482 # (smtp or resend) and backend-specific parameters. Define your own
483 # presets here and reference them via MailBuilder(preset="name").
484 presets: {}
485 # Example presets:
486 #
487 # corporate:
488 # transport: smtp
489 # host: smtp-secure.corp.local
490 # port: 25
491 # login: svc_mail
492 # password: "secret"
493 # starttls: false
494 # ssl: false
495 # timeout: 30
496 # # Optional SSL overrides for this preset (highest priority in the cascade):
497 # ssl_verify: false
498 # ssl_ca_bundle: /etc/ssl/certs/corp-ca.pem
499 # # Optional envelope defaults (sender / reply_to only, never to/cc/bcc):
500 # defaults:
501 # sender: "Service Notifications <notify@corp.local>"
502 # reply_to: "Service Notifications <notify@corp.local>"
503 # # Optional throttle override (highest priority, see mail.throttle above):
504 # throttle:
505 # rate: 5 # corporate is more restrictive than mail-wide default
506 # per: 60.0
507 # on_exceed: warn # operational critical, prefer drop+log over raise
508 #
509 # transactional:
510 # transport: resend
511 # api_key: re_xxxxxxxxxxxxx
512 # timeout: 30
513 #
514 # local:
515 # transport: smtp
516 # host: localhost
517 # port: 1025
518 # starttls: false
519
520 # Attachment and message limits
521 limits:
522 # Maximum size for a single attachment
523 # Accepts: bytes (int) or human-readable string ("25M", "10 MiB")
524 # Hard limit enforced in code: 25 MiB
525 max_attachment_size: "25M"
526 # Maximum number of attachments per message
527 # Hard limit enforced in code: 50
528 max_attachments: 20
529
530 filesystem:
531 attachments_root: "~/.cache/kstlib/mail/attachments"
532 inline_root: "~/.cache/kstlib/mail/inline"
533 templates_root: "~/.cache/kstlib/mail/templates"
534 allow_external_attachments: false
535 allow_external_templates: false
536 auto_create_roots: true
537 enforce_permissions: true
538 max_permission_octal: 448 # 0o700
539
540###########################################################################################
541## Secrets configuration
542###########################################################################################
543secrets:
544 name: "default"
545 providers:
546 - name: environment
547 settings:
548 prefix: "KSTLIB"
549 delimiter: "__"
550 - name: keyring
551 settings:
552 service: "kstlib"
553 sops:
554 # Path to the encrypted secrets file (set to null to disable by default)
555 path: null
556 # Override the sops executable if it is not on PATH
557 binary: "sops"
558 # autodetect | json | yaml | text
559 format: "auto"
560 # Maximum cached decrypted files (LRU eviction)
561 # Hard limit enforced in code: 256
562 max_cache_entries: 64
563
564###########################################################################################
565## Authentication configuration (OAuth2/OIDC)
566###########################################################################################
567auth:
568 # Default provider to use when none specified
569 default_provider: null
570
571 # Token storage backend: "memory" (dev/testing), "file" (persistent), or "sops" (encrypted)
572 token_storage: "memory"
573
574 # OIDC discovery document cache TTL (seconds)
575 discovery_ttl: 3600
576
577 # TRACE level HTTP logging settings
578 trace:
579 # Pretty-print JSON bodies in TRACE logs (indent with 2 spaces)
580 pretty: true
581 # Maximum body length before truncation (chars)
582 # TRACE = debug mode, show full body by default
583 # Hard limit enforced in code: 10000 (10KB)
584 max_body_length: 10000
585 # Additional body keys to redact in HTTP traces, added on top of the
586 # built-in floor of OAuth2/OIDC credentials (the floor can never be
587 # disabled here). Entries containing "*", "?" or "[seq]" are
588 # case-insensitive fnmatch patterns. Warning: a broad pattern like
589 # "*token*" also masks
590 # non-secret fields such as "token_type". Niche-profile credentials
591 # (CIBA, UMA, OpenID for Verifiable Presentations, ACE, ...) belong here
592 # rather than in the built-in floor.
593 extra_sensitive_keys: []
594
595 # Local callback server for authorization code flow
596 callback_server:
597 host: "127.0.0.1"
598 port: 8400
599 # Port range to try if primary port is busy (optional)
600 port_range: null # e.g., [8400, 8410]
601 # Timeout waiting for callback (seconds)
602 # Hard limit enforced in code: 600 (10 minutes)
603 timeout: 120
604
605 # Status display settings (kstlib auth status)
606 status:
607 # Access token considered "expiring soon" when remaining time < threshold
608 # Hard limits enforced in code: min 60s, max 3600s (1 hour)
609 expiring_soon_threshold: 120 # seconds (2 minutes)
610 # Refresh token considered "expiring soon" when remaining time < threshold
611 # Hard limits enforced in code: min 60s, max 172800s (48 hours)
612 # Typically higher since refresh tokens can live days/weeks/months
613 refresh_expiring_soon_threshold: 600 # seconds (10 minutes)
614 # Timezone for displaying timestamps: "local" or "utc"
615 display_timezone: "local"
616
617 # Token storage configuration per backend
618 storage:
619 file:
620 directory: "~/.config/kstlib/auth/tokens"
621 sops:
622 directory: "~/.config/kstlib/auth/tokens"
623
624 # Named providers (empty by default, users define their own)
625 providers: {}
626 # Example provider configuration:
627 # providers:
628 # corporate:
629 # type: "oidc" # oauth2 | oidc
630 #
631 # # OIDC Discovery modes:
632 # # - Auto: only issuer provided, endpoints auto-discovered
633 # # - Hybrid: issuer + some explicit endpoints (explicit wins)
634 # # - Manual: no issuer, all endpoints explicit (no discovery)
635 # issuer: "https://idp.corp.local/realms/main"
636 #
637 # client_id: "my-app"
638 # # Secret can be inline or SOPS reference
639 # client_secret: null # or "sops://secrets/auth.yaml#corporate.client_secret"
640 # scopes:
641 # - openid
642 # - profile
643 # - email
644 #
645 # # PKCE enabled by default (recommended for all clients)
646 # pkce: true
647 #
648 # # Optional endpoint overrides (auto-discovered if issuer provided)
649 # authorization_endpoint: null
650 # token_endpoint: null
651 # userinfo_endpoint: null
652 # jwks_uri: null
653 #
654 # # Custom HTTP headers sent with all IDP requests
655 # # Useful for load balancer validation, tenant routing, etc.
656 # headers: {}
657 # # Example:
658 # # headers:
659 # # Host: "idp.corp.local"
660 # # X-Tenant-Id: "corp"
661 #
662 # # Provider-specific token storage (overrides global)
663 # token_storage: null # "memory" | "file" | "sops"
664
665###########################################################################################
666## Utilities configuration
667###########################################################################################
668utilities:
669 secure_delete:
670 method: "auto"
671 passes: 3
672 zero_last_pass: true
673 chunk_size: 1048576 # 1 MiB
674
675###########################################################################################
676## Resilience configuration
677###########################################################################################
678resilience:
679 # --- Core components (used by WebSocket trading bots) ---
680
681 heartbeat:
682 # Seconds between heartbeats
683 # Hard limits enforced in code: min 1s, max 300s (5 minutes)
684 interval: 10
685
686 watchdog:
687 # Seconds of inactivity before triggering timeout callback
688 # Hard limits enforced in code: min 1s, max 3600s (1 hour)
689 timeout: 30
690
691 # --- Advanced components (for REST API calls, order placement) ---
692 # Note: WebSocket connections have built-in reconnection logic.
693 # These are useful for REST API resilience (e.g., placing orders, account queries).
694
695 shutdown:
696 # GracefulShutdown: Orderly cleanup on SIGTERM/SIGINT with prioritized callbacks.
697 # Use case: Ensure open orders are cancelled, positions closed before exit.
698 # Total timeout for all cleanup callbacks (seconds)
699 # Hard limits enforced in code: min 5s, max 300s (5 minutes)
700 timeout: 30
701 # Exit code when timeout exceeded
702 force_exit_code: 1
703
704 circuit_breaker:
705 # CircuitBreaker: Fail-fast pattern for external service calls.
706 # Use case: REST API calls (order placement, account info) - after N failures,
707 # stop calling the failing endpoint and fail immediately until recovery.
708 # Failures before opening circuit
709 # Hard limits enforced in code: min 1, max 100
710 max_failures: 5
711 # Cooldown before attempting recovery (seconds)
712 # Hard limits enforced in code: min 1s, max 3600s (1 hour)
713 reset_timeout: 60
714 # Calls allowed in half-open state for testing
715 # Hard limits enforced in code: min 1, max 10
716 half_open_max_calls: 1
717
718###########################################################################################
719## Database configuration
720###########################################################################################
721db:
722 pool:
723 # Minimum connections to maintain in pool (0 = lazy pool, on-demand)
724 # Hard limits enforced in code: min 0, max 10
725 min_size: 1
726 # Maximum connections allowed in pool
727 # Hard limits enforced in code: min 1, max 100
728 max_size: 10
729 # Timeout for acquiring a connection (seconds)
730 # Hard limits enforced in code: min 1.0, max 300.0 (5 minutes)
731 acquire_timeout: 30.0
732 retry:
733 # Retry attempts on connection failure
734 # Hard limits enforced in code: min 1, max 10
735 max_attempts: 3
736 # Delay between retries (seconds)
737 # Hard limits enforced in code: min 0.1, max 60.0
738 delay: 0.5
739
740 # SQLCipher encryption (opt-in, requires: pip install kstlib[db-crypto])
741 # System deps: libsqlcipher-dev (Debian/Ubuntu), sqlcipher (macOS/brew)
742 cipher:
743 # Enable SQLCipher encryption (default: false)
744 enabled: false
745 # Key source: env | sops | passphrase
746 # - env: Read from environment variable (key_env)
747 # - sops: Read from SOPS-encrypted file (sops_path + sops_key)
748 # - passphrase: Direct passphrase (ONLY for development/testing)
749 key_source: env
750 # Environment variable containing the encryption key
751 key_env: "KSTLIB_DB_KEY"
752 # SOPS configuration (when key_source: sops)
753 sops_path: null
754 sops_key: "db_key"
755 # Direct passphrase (NEVER use in production)
756 passphrase: null
757
758###########################################################################################
759## Credentials configuration (multi-source credential resolution)
760###########################################################################################
761credentials:
762 # Credentials are named entries that can be referenced by rapi services.
763 # Supported types: env, file, sops, provider
764 #
765 # Examples:
766 #
767 # # Type: env - from environment variable
768 # github:
769 # type: env
770 # var: "GITHUB_TOKEN"
771 #
772 # # Type: env - key+secret pair from environment
773 # kraken_env:
774 # type: env
775 # var_key: "KRAKEN_API_KEY"
776 # var_secret: "KRAKEN_API_SECRET"
777 #
778 # # Type: file - from JSON/YAML file with jq-like path extraction
779 # azure_cli:
780 # type: file
781 # path: "~/.azure/msal_token_cache.json"
782 # token_path: ".AccessToken.secret"
783 #
784 # # Type: file - key+secret from file fields
785 # api_file:
786 # type: file
787 # path: "~/.config/api_keys.json"
788 # key_field: "api_key"
789 # secret_field: "api_secret"
790 #
791 # # Type: sops - from SOPS-encrypted file
792 # kraken_prod:
793 # type: sops
794 # path: "secrets/kraken.sops.json"
795 # key_field: "api_key"
796 # secret_field: "api_secret"
797 #
798 # # Type: provider - from kstlib.auth provider (OAuth2/OIDC)
799 # corporate:
800 # type: provider
801 # provider: "corporate"
802
803###########################################################################################
804## REST API configuration (config-driven HTTP client)
805###########################################################################################
806rapi:
807 # Hard limits enforced in code for deep defense:
808 # - timeout: min 1s, max 300s (5 minutes)
809 # - max_response_size: max 100M
810 # - max_retries: min 0, max 10
811 # - retry_delay: min 0.1s, max 60s
812 # - retry_backoff: min 1.0, max 5.0
813 limits:
814 timeout: 30 # Request timeout in seconds
815 max_response_size: "10M" # Maximum response body size
816 max_retries: 3 # Retry attempts on failure
817 retry_delay: 1.0 # Initial delay between retries (seconds)
818 retry_backoff: 2.0 # Exponential backoff multiplier
819
820 # Safeguard configuration for dangerous HTTP methods
821 # Endpoints using these methods MUST define a safeguard string
822 # to prevent accidental destructive operations
823 safeguard:
824 # HTTP methods that require a safeguard to be defined on endpoints
825 # Default: DELETE and PUT (most destructive operations)
826 # Set to empty list [] to disable safeguard requirements
827 required_methods:
828 - DELETE
829 - PUT
830
831 # Pretty-print settings for CLI output
832 # Controls formatting of JSON and XML responses in terminal
833 pretty_render:
834 # JSON indentation (spaces). Set to null or 0 to disable pretty-printing.
835 json: 2
836 # XML pretty-print. Set to true to enable formatted XML output.
837 xml: true
838
839 # Heuristic field-extraction defaults (collection shapes: HAL, SAS Viya, ...).
840 # Back the .id / .ids / .count response accessors when no per-endpoint
841 # extract: directive applies. Override any of the 3 lists here (a per-endpoint
842 # extract: still wins). Hardened on load: max 32 entries per list; id_keys and
843 # count_keys are field names (max 64 chars); ids_paths are JMESPath expressions
844 # (max 512 chars) that must compile. An invalid override is ignored with a
845 # [SECURITY] warning, leaving that list disabled.
846 extraction:
847 # Field names tried in order to resolve the single-resource .id accessor.
848 id_keys: ["id", "uri", "name", "key", "objectId"]
849 # JMESPath expressions tried in order to resolve the .ids list accessor.
850 ids_paths: ["items[*].id", "members[*].id", "results[*].id", "data[*].id", "value[*].id"]
851 # Field names tried in order to resolve the .count accessor.
852 count_keys: ["count", "total", "totalCount"]
853
854 # API services and their endpoints
855 # Define your own APIs here or use external *.rapi.yml files
856 api: {}
857
858 # Example: Azure Resource Manager API
859 # azure:
860 # base_url: "https://management.azure.com"
861 # credentials: azure_cli # Reference to credentials section
862 # auth_type: bearer
863 # headers:
864 # X-Custom-Header: "service-value"
865 # endpoints:
866 # list_subscriptions:
867 # path: "/subscriptions"
868 # query:
869 # api-version: "2020-01-01"
870 # headers:
871 # X-Request-ID: "{request_id}"
872
873###########################################################################################
874## Alerts configuration (multi-channel alerting)
875###########################################################################################
876alerts:
877 # Hard limits enforced in code for deep defense:
878 # - throttle.rate: min 1, max 1000 alerts per period
879 # - throttle.per: min 1.0, max 86400.0 seconds (1 day)
880 # - throttle.burst: min 1, max rate value
881
882 # Default throttle settings (anti-spam protection)
883 throttle:
884 rate: 10 # Maximum alerts per period
885 per: 60.0 # Period duration in seconds (1 minute)
886 burst: 5 # Initial burst capacity
887
888 # Default channel settings
889 channels:
890 # Timeout for sending alerts (seconds)
891 # Hard limits enforced in code: min 1.0, max 120.0
892 timeout: 30.0
893 # Retry attempts on delivery failure
894 # Hard limits enforced in code: min 0, max 5
895 max_retries: 2
896
897 presets:
898 dev:
899 throttle:
900 rate: 100 # More lenient for development
901 per: 60.0
902 burst: 20
903 channels:
904 timeout: 10.0
905 max_retries: 0
906
907 prod:
908 throttle:
909 rate: 10 # Strict rate limiting
910 per: 60.0
911 burst: 3
912 channels:
913 timeout: 30.0
914 max_retries: 3
915
916 critical_only:
917 throttle:
918 rate: 5 # Very strict for critical-only channels
919 per: 300.0 # 5 minutes
920 burst: 2
921
922###########################################################################################
923## Metrics configuration
924###########################################################################################
925metrics:
926 # Enable colored output
927 colors: true
928
929 # Output destination: stderr | stdout
930 output: stderr
931
932 # Default behavior for @metrics decorator (can be overridden per-call)
933 defaults:
934 time: true # Track execution time
935 memory: true # Track peak memory (tracemalloc)
936 step: false # Enable step numbering
937
938 # Step format string
939 # Variables: {n} (step number), {title}, {function}, {module}, {file}, {line}
940 step_format: "[STEP {n}] {title}"
941
942 # Lap format string (for Stopwatch)
943 # Variables: {n} (lap number), {name}
944 lap_format: "[LAP {n}] {name}"
945
946 # Title format (auto-generated when no custom title provided)
947 # Variables: {function}, {module}, {file}, {line}
948 title_format: "{function} [dim green]({file}:{line})[/dim green]"
949
950 # Time display precision (decimal places for seconds)
951 time_precision: 3
952
953 # Thresholds for color warnings
954 thresholds:
955 time_warn: 5 # Warn color if >= 5 seconds
956 time_crit: 30 # Critical color if >= 30 seconds
957 memory_warn: 100000000 # Warn color if >= 100 MB
958 memory_crit: 500000000 # Critical color if >= 500 MB
959
960 # Icons (set to "" to disable)
961 icons:
962 time: "⏱"
963 memory: "🧠"
964 peak: "Peak:" # Text after memory icon
965
966 # Color theme (Rich style names)
967 # See: https://rich.readthedocs.io/en/stable/appendix/colors.html
968 theme:
969 label: "bold green"
970 title: "bold white"
971 text: "white"
972 muted: "dim"
973 table_header: "bold cyan"
974 time_ok: "cyan"
975 time_warn: "orange3"
976 time_crit: "bold red"
977 memory_ok: "rosy_brown"
978 memory_warn: "orange3"
979 memory_crit: "bold red"
980 step_number: "dim"
981 separator: "dim white"
982
983 # Summary display style: table | simple
984 summary_style: table
985
986 # Show percentage of total time in summaries
987 show_percentages: true
988
989 # Print metrics to stderr by default
990 print_results: true
991
992###########################################################################################
993## WebSocket configuration (proactive connection control)
994###########################################################################################
995websocket:
996 # Ping/Pong heartbeat settings
997 ping:
998 # Seconds between ping frames
999 # Hard limits: [5, 60] - values outside bounds will be clamped
1000 interval: 20
1001 # Seconds to wait for pong response
1002 # Hard limits: [5, 30]
1003 timeout: 10
1004
1005 # Connection settings
1006 connection:
1007 # Timeout for initial connection (seconds)
1008 # Hard limits: [5, 120]
1009 timeout: 30
1010
1011 # Reconnection behavior
1012 reconnect:
1013 # Initial delay between reconnect attempts (seconds)
1014 # Hard limits: [0, 300] - 0 = immediate reconnect allowed
1015 delay: 1.0
1016 # Maximum delay for exponential backoff (seconds)
1017 # Hard limits: [1, 600]
1018 max_delay: 60.0
1019 # Maximum consecutive reconnection attempts
1020 # Hard limits: [0, 100] - 0 = no retry
1021 max_attempts: 10
1022
1023 # Message queue settings
1024 queue:
1025 # Maximum messages in queue (0 = unlimited)
1026 # Hard limits: [0, 10000]
1027 size: 1000
1028
1029 # Proactive control settings (KEY FEATURE)
1030 proactive:
1031 # Seconds between should_disconnect callback checks
1032 # Hard limits: [1, 60]
1033 disconnect_check_interval: 10.0
1034 # Seconds between should_reconnect callback checks
1035 # Hard limits: [0.5, 60]
1036 reconnect_check_interval: 5.0
1037 # Disconnect X seconds before 24h limit (Binance, etc.)
1038 # Hard limits: [60, 3600] - at least 1min, max 1h
1039 disconnect_margin: 300.0
1040
1041 # Presets for common use cases
1042 presets:
1043 trading:
1044 ping: { interval: 15, timeout: 10 }
1045 reconnect: { delay: 0.5, max_delay: 30.0, max_attempts: 20 }
1046 proactive: { disconnect_check_interval: 5.0, reconnect_check_interval: 2.0 }
1047 monitoring:
1048 ping: { interval: 30, timeout: 15 }
1049 reconnect: { delay: 5.0, max_delay: 120.0, max_attempts: 50 }
1050 proactive: { disconnect_check_interval: 30.0, reconnect_check_interval: 10.0 }
1051
1052###########################################################################################
1053## Pipeline configuration (declarative workflow execution)
1054###########################################################################################
1055pipeline:
1056 # Default step timeout in seconds
1057 # Hard limits enforced in code: min 1s, max 3600s (1 hour)
1058 default_timeout: 300
1059
1060 # Error handling policy: fail_fast | continue
1061 # fail_fast: abort pipeline on first step failure (run on_failure steps)
1062 # continue: execute all remaining steps even after failure
1063 on_error: fail_fast
1064
1065 # Named pipelines (user-defined)
1066 pipelines: {}
1067 # Example pipeline configuration:
1068 # morning-monitoring:
1069 # steps:
1070 # - name: build_logs
1071 # type: shell
1072 # command: |
1073 # for host in server1 server2; do
1074 # ssh $host "systemctl status viya"
1075 # done
1076 # timeout: 300
1077 # - name: process_data
1078 # type: python
1079 # module: my.analytics.runner
1080 # args: ["--output", "report.html"]
1081 # - name: notify
1082 # type: callable
1083 # callable: my.alerts:send_summary
1084 # when: always
1085 # - name: cleanup
1086 # type: shell
1087 # command: "rm -f /tmp/pipeline_*.tmp"
1088 # when: on_failure
1089
1090# ============================================================================
1091# OPS - Session Management Configuration
1092# ============================================================================
1093# Config-driven session management for persistent processes (bots, services).
1094# Supports tmux (local dev) and container (Podman/Docker) backends.
1095#
1096# Hard limits enforced:
1097# - Session name: max 64 chars, alphanumeric + underscore + hyphen
1098# - Image name: max 256 chars, valid OCI format
1099# - Volumes: max 20, no path traversal
1100# - Ports: max 50, range 1-65535
1101# - Env vars: max 100, key max 128 chars, value max 32KB
1102# - Command: max 4096 chars, dangerous patterns blocked
1103# ============================================================================
1104ops:
1105 # Default backend when not specified (tmux | container)
1106 default_backend: tmux
1107
1108 # Tmux binary path (default: tmux)
1109 tmux_binary: tmux
1110
1111 # Container runtime (podman | docker | null for auto-detect)
1112 container_runtime: null
1113
1114 # Pre-defined sessions (config-driven)
1115 # Sessions can be started with: kstlib ops start <name>
1116 sessions: {}
1117 # Example session configuration:
1118 # mybot:
1119 # backend: tmux # Override default_backend
1120 # command: "python -m mybot.main" # Command to run
1121 # working_dir: "/opt/mybot" # Working directory
1122 # env: # Environment variables
1123 # BOT_ENV: production
1124 # LOG_LEVEL: INFO
1125 #
1126 # mybot-prod:
1127 # backend: container
1128 # image: "mybot:latest" # Container image (required)
1129 # volumes: # Volume mounts (host:container[:ro|:rw])
1130 # - "./data:/app/data"
1131 # - "./logs:/app/logs:rw"
1132 # ports: # Port mappings (host:container[/tcp|/udp])
1133 # - "8080:80"
1134 # log_volume: "./logs:/app/logs" # Persistent logs for post-mortem
1135
1136###########################################################################################
1137## SSL/TLS configuration (global settings for all HTTP clients)
1138###########################################################################################
1139ssl:
1140 # Enable SSL certificate verification (default: true)
1141 # Set to false ONLY for development with self-signed certificates
1142 # WARNING: Disabling verification exposes you to MITM attacks
1143 verify: true
1144
1145 # Custom CA bundle path for corporate PKI or self-signed certificates
1146 # If provided, ssl_verify is implicitly true
1147 # Accepts: null (use system CAs), or path to PEM file
1148 ca_bundle: null
Common Patterns¶
Development vs Production¶
import os
from kstlib.config import load_from_file
env = os.getenv("APP_ENV", "development")
config = load_from_file(f"config/{env}.yml")
Override from environment¶
# Load base config, then override specific values
config = ConfigLoader().config
# Override at runtime (config is a Box, so this works)
if os.getenv("DEBUG"):
config.app.debug = True
Testing with isolated config¶
from pathlib import Path
from kstlib.config import clear_config, load_from_file
def test_custom_config(tmp_path: Path):
config_file = tmp_path / "test.yml"
config_file.write_text("""
app:
debug: true
""")
clear_config() # Isolate from other tests
config = load_from_file(config_file)
assert config.app.debug is True
Advanced: AutoDiscoveryConfig¶
Bundle discovery settings into a reusable object:
from pathlib import Path
from kstlib.config import ConfigLoader
from kstlib.config.loader import AutoDiscoveryConfig
auto = AutoDiscoveryConfig(
enabled=True,
source="file",
filename="kstlib.conf.yml",
env_var="APP_CONFIG",
path=Path("/srv/kstlib/prod.yml"),
)
loader = ConfigLoader(auto=auto)
config = loader.config
Troubleshooting¶
ConfigFileNotFoundError¶
File doesn’t exist at the specified path:
from kstlib.config import load_from_file
from kstlib.exceptions import ConfigFileNotFoundError
try:
config = load_from_file("config.yml")
except ConfigFileNotFoundError:
# Fall back to defaults or create config
config = bootstrap_defaults()
ConfigFormatError¶
Invalid syntax or parse error in config file:
from kstlib.exceptions import ConfigFormatError
try:
config = load_from_file("config.yml")
except ConfigFormatError as exc:
raise SystemExit(f"Invalid configuration: {exc}")
ConfigCircularIncludeError¶
Include loop detected (A includes B, B includes A):
# This will fail
# a.yml includes b.yml, b.yml includes a.yml
Fix: Review your include chain and remove the circular dependency.
Config not updating after file change¶
Config is cached by default. In interactive sessions, force a reload with
reload_config():
from kstlib.config import reload_config
config = reload_config() # Flush cache + reload from disk
See Interactive usage (Jupyter / REPL) for the full discussion and alternatives.
Environment variable not found¶
When using auto_source="env", ensure the variable is set:
export CONFIG_PATH=/path/to/config.yml
# This fails if CONFIG_PATH is not set
config = ConfigLoader(auto_source="env").config
API Reference¶
Full autodoc: Configuration Loader
Function |
Description |
|---|---|
|
Main loader class with auto-discovery |
|
Load from specific file |
|
Get cached config (singleton) |
|
Clear the config cache |
|
Flush cache + reload from disk (Jupyter/REPL) |
Path resolution in configuration¶
When a configuration value is a filesystem path, for example
ssl_ca_bundle, attachments_root, credentials.path, or
logging.handlers.file.path, kstlib resolves it with Python’s standard
path logic:
Absolute paths are used as-is:
/etc/ssl/certs/corp-ca.pemTilde-prefixed paths are expanded to the user’s home:
~/ca-bundles/corp.pembecomes/home/alice/ca-bundles/corp.pemRelative paths are resolved against the current working directory of the Python process, NOT the directory of the YAML file that declared the path.
Why this matters¶
Point 3 is a common source of surprise, especially in interactive environments such as Jupyter:
You have
ssl_ca_bundle: ./corp-ca.pemin your config, withcorp-ca.pemsitting next to your notebook file.Your JupyterHub launches the kernel with
cwd=/home/alice, which does not contain the file.kstlib raises
MailConfigurationError: ssl_ca_bundle path does not exist: ./corp-ca.pem.
The same trap applies to scripts launched by cron, systemd units, container entry points, or any process whose cwd does not match the directory holding the YAML file.
Recommended practice¶
Always use absolute paths or home-expanded paths in your YAML:
mail:
presets:
corporate:
ssl_ca_bundle: /etc/ssl/certs/corp-ca.pem # absolute
# or
ssl_ca_bundle: ~/.config/kstlib/corp-ca.pem # home-expanded
Absolute paths are unambiguous across Jupyter, scripts, scheduled jobs, and container processes.
Debugging a relative path¶
If you must use a relative path, verify the Python process cwd:
import os
print(f"Process cwd: {os.getcwd()}")
The relative path is resolved against this value. If the printed cwd
differs from where your YAML and its referenced files sit, the path
will not resolve. Either os.chdir(...) before the import, or switch
to an absolute path.
Future direction¶
Resolving relative paths against the YAML file that declared them is a candidate future enhancement. Implementing it requires kstlib to track, for every configuration value, the file it originated from, which is a substantial refactor of the loader. For now, use absolute paths to avoid ambiguity across different execution contexts.