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
179 # File handler settings
180 # Two configuration styles are supported:
181 # - New style (recommended): file_path: ./logs/kstlib.log
182 # - Legacy style: log_path + log_dir + log_name (for backward compatibility)
183 # The new style takes priority if file_path is defined.
184 file:
185 level: WARNING # Log level: TRACE | DEBUG | INFO | SUCCESS | WARNING | ERROR | CRITICAL
186 datefmt: "%Y-%m-%d %H:%M:%S"
187 format: "[%(asctime)s | %(levelname)-8s] ::: PID %(process)d / TID %(thread)d ::: %(message)s"
188 # file_path: ./logs/kstlib.log # New style (recommended)
189 # auto_create_dir: true # New style auto-create
190 log_path: "./" # Legacy style (kept for backward compatibility)
191 log_dir: "logs"
192 log_name: "kstlib.log"
193 log_dir_auto_create: true
194
195 # File rotation settings
196 rotation:
197 when: midnight # midnight | S | M | H | D | W0-W6
198 interval: 1
199 backup_count: 7
200
201 presets:
202 dev:
203 output: console
204 console:
205 level: DEBUG
206 show_path: true
207 tracebacks_show_locals: true
208 icons:
209 show: true
210
211 prod:
212 output: file
213 file:
214 level: INFO
215 icons:
216 show: false
217
218 # debug: deeper-than-dev preset for investigation sessions.
219 # Persists to file (output: both) so logs survive the terminal and can be
220 # grepped or shared after the fact, and emits at TRACE so HTTP traces and
221 # detailed diagnostics are captured. Use 'dev' for everyday iteration
222 # (console-only, DEBUG); use 'debug' when actively investigating an issue.
223 debug:
224 output: both
225 console:
226 level: TRACE
227 show_path: true
228 tracebacks_show_locals: true
229 file:
230 level: TRACE
231 format: "[%(asctime)s | %(levelname)-8s] ::: PID %(process)d / TID %(thread)d ::: [%(filename)s:%(lineno)d %(funcName)s] %(message)s"
232 icons:
233 show: true
234
235 trace:
236 output: both
237 console:
238 level: TRACE
239 show_path: true
240 tracebacks_show_locals: true
241 file:
242 level: TRACE
243 format: "[%(asctime)s | %(levelname)-8s] ::: PID %(process)d / TID %(thread)d ::: [%(filename)s:%(lineno)d %(funcName)s] %(message)s"
244 icons:
245 show: true
246
247 # Mail trace preset - verbose SMTP/SSL debugging to dedicated file
248 # Usage: LogManager(preset="trace_mail") or logger.preset: trace_mail
249 trace_mail:
250 output: both
251 console:
252 level: WARNING # Keep console quiet
253 file:
254 level: TRACE
255 file_path: ./logs/mail-trace.log
256 auto_create_dir: true
257 icons:
258 show: true
259
260###########################################################################################
261## UI helpers configuration
262###########################################################################################
263ui:
264 panels:
265 defaults:
266 panel:
267 # border_style supports any Rich color/style (e.g. "blue", "bold green")
268 border_style: "bright_blue"
269 title_align: "left"
270 subtitle_align: "left"
271 padding: [1, 2]
272 expand: true
273 highlight: false
274 # https://rich.readthedocs.io/en/stable/appendix/box.html#appendix-box
275 box: "ROUNDED"
276 content:
277 box: "SIMPLE"
278 expand: true
279 show_header: false
280 key_label: "Key"
281 value_label: "Value"
282 key_style: "bold white"
283 value_style: null
284 header_style: "bold"
285 pad_edge: false
286 sort_keys: false
287 use_markup: true
288 use_pretty: true
289 pretty_indent: 2
290 presets:
291 info:
292 panel:
293 border_style: "cyan"
294 title: "Information"
295 icon: "📘"
296 success:
297 panel:
298 border_style: "sea_green3"
299 title: "Success"
300 icon: "✅"
301 warning:
302 panel:
303 border_style: "orange3"
304 title: "Warning"
305 icon: "🔔"
306 error:
307 panel:
308 border_style: "red3"
309 title: "Error"
310 icon: "❌"
311 summary:
312 panel:
313 border_style: "light_steel_blue1"
314 title: "Execution Summary"
315 icon: "📝"
316 content:
317 sort_keys: true
318 key_style: "bold orchid2"
319 value_style: "dim white"
320 tables:
321 defaults:
322 table:
323 title: null
324 caption: null
325 box: "SIMPLE"
326 show_header: true
327 header_style: "bold cyan"
328 show_lines: false
329 row_styles: null
330 expand: true
331 pad_edge: false
332 highlight: false
333 columns:
334 - header: "Key"
335 key: "key"
336 justify: "left"
337 style: "bold white"
338 overflow: "fold"
339 no_wrap: false
340 - header: "Value"
341 key: "value"
342 justify: "left"
343 style: null
344 overflow: "fold"
345 no_wrap: false
346 presets:
347 inventory:
348 table:
349 title: "Inventory"
350 box: "SIMPLE_HEAVY"
351 show_lines: true
352 header_style: "bold yellow"
353 columns:
354 - header: "Component"
355 key: "component"
356 style: "bold"
357 width: 18
358 - header: "Version"
359 key: "version"
360 style: "cyan"
361 width: 12
362 - header: "Status"
363 key: "status"
364 justify: "center"
365 style: "bold"
366 width: 10
367 metrics:
368 table:
369 title: "Metrics"
370 box: "SIMPLE_HEAD"
371 header_style: "bold green"
372 columns:
373 - header: "Metric"
374 key: "metric"
375 style: "bold"
376 - header: "Value"
377 key: "value"
378 justify: "right"
379 spinners:
380 defaults:
381 # Spinner character style: BRAILLE | DOTS | LINE | ARROW | BLOCKS | CIRCLE | SQUARE | MOON | CLOCK
382 style: "BRAILLE"
383 # Position relative to message: before | after
384 position: "before"
385 # Animation type: spin | bounce | color_wave
386 animation_type: "spin"
387 # Seconds between animation frames
388 interval: 0.08
389 # Rich style for spinner character
390 spinner_style: "cyan"
391 # Rich style for message text (null = default)
392 text_style: null
393 # Character shown on success
394 done_character: "✓"
395 done_style: "green"
396 # Character shown on failure
397 fail_character: "✗"
398 fail_style: "red"
399 presets:
400 minimal:
401 style: "LINE"
402 spinner_style: "dim white"
403 interval: 0.1
404 fancy:
405 style: "BRAILLE"
406 spinner_style: "bold cyan"
407 interval: 0.06
408 blocks:
409 style: "BLOCKS"
410 spinner_style: "blue"
411 interval: 0.05
412 bounce:
413 animation_type: "bounce"
414 spinner_style: "yellow"
415 interval: 0.08
416 color_wave:
417 animation_type: "color_wave"
418 interval: 0.1
419
420###########################################################################################
421## Mail configuration
422###########################################################################################
423mail:
424 # Default named preset used when MailBuilder() is instantiated without
425 # transport= or preset=. Must match a key under mail.presets below.
426 # Leave null to force callers to pass transport= or preset= explicitly.
427 default: null
428
429 # Anti-spam throttle (kill switch). Enforced before the transport on
430 # every MailBuilder.send(), including indirect calls via @mail.notify.
431 #
432 # Cascade (highest priority first):
433 # 1. mail.presets.<name>.throttle.<key> (preset-level override)
434 # 2. mail.throttle.<key> (this section, mail-wide default)
435 # 3. Code defaults (rate=20, per=60.0, on_exceed=raise)
436 #
437 # Each key cascades independently: a preset can override only ``rate``
438 # while keeping the mail-wide ``per`` and ``on_exceed``.
439 #
440 # Modes:
441 # - raise (default): emits WARNING [SECURITY] then raises
442 # MailThrottledError. The caller decides how to back off.
443 # - warn: emits WARNING [SECURITY] then drops the mail silently
444 # (returns the built message without sending).
445 # - drop (silent) is INTENTIONALLY REJECTED at init: a security
446 # event must never be silent (kstlib logging convention).
447 #
448 # Singleton: a single MailThrottle is shared across all builders that
449 # use the same preset, including snapshots taken by the @notify
450 # decorator. This prevents bypass via creating many builder instances.
451 #
452 # Hard limits enforced in code (see kstlib.limits):
453 # - rate: 1 to 1000 mails per period
454 # - per: 1.0 to 86400.0 seconds (1 day)
455 # - on_exceed: "raise" or "warn"
456 throttle:
457 enabled: true
458 rate: 20
459 per: 60.0
460 on_exceed: raise
461
462 # SSL/TLS configuration for mail transports.
463 #
464 # Values here override the root ``ssl:`` section (bottom of this file)
465 # for mail only, and are themselves overridden by ``ssl_verify`` and
466 # ``ssl_ca_bundle`` keys set inside an individual preset. Each key
467 # cascades independently: you can set ``verify: false`` here and still
468 # provide ``ssl_ca_bundle`` at the preset level.
469 #
470 # Security: setting ``verify: false`` at any level emits a WARNING log
471 # at transport build time. Prefer ``ca_bundle: /path/to/private-ca.pem``
472 # for internal PKI rather than disabling verification outright.
473 ssl:
474 verify: true
475 ca_bundle: null
476
477 # Named transport presets. Each preset declares a "transport" field
478 # (smtp or resend) and backend-specific parameters. Define your own
479 # presets here and reference them via MailBuilder(preset="name").
480 presets: {}
481 # Example presets:
482 #
483 # corporate:
484 # transport: smtp
485 # host: smtp-secure.corp.local
486 # port: 25
487 # login: svc_mail
488 # password: "secret"
489 # starttls: false
490 # ssl: false
491 # timeout: 30
492 # # Optional SSL overrides for this preset (highest priority in the cascade):
493 # ssl_verify: false
494 # ssl_ca_bundle: /etc/ssl/certs/corp-ca.pem
495 # # Optional envelope defaults (sender / reply_to only, never to/cc/bcc):
496 # defaults:
497 # sender: "Service Notifications <notify@corp.local>"
498 # reply_to: "Service Notifications <notify@corp.local>"
499 # # Optional throttle override (highest priority, see mail.throttle above):
500 # throttle:
501 # rate: 5 # corporate is more restrictive than mail-wide default
502 # per: 60.0
503 # on_exceed: warn # operational critical, prefer drop+log over raise
504 #
505 # transactional:
506 # transport: resend
507 # api_key: re_xxxxxxxxxxxxx
508 # timeout: 30
509 #
510 # local:
511 # transport: smtp
512 # host: localhost
513 # port: 1025
514 # starttls: false
515
516 # Attachment and message limits
517 limits:
518 # Maximum size for a single attachment
519 # Accepts: bytes (int) or human-readable string ("25M", "10 MiB")
520 # Hard limit enforced in code: 25 MiB
521 max_attachment_size: "25M"
522 # Maximum number of attachments per message
523 # Hard limit enforced in code: 50
524 max_attachments: 20
525
526 filesystem:
527 attachments_root: "~/.cache/kstlib/mail/attachments"
528 inline_root: "~/.cache/kstlib/mail/inline"
529 templates_root: "~/.cache/kstlib/mail/templates"
530 allow_external_attachments: false
531 allow_external_templates: false
532 auto_create_roots: true
533 enforce_permissions: true
534 max_permission_octal: 448 # 0o700
535
536###########################################################################################
537## Secrets configuration
538###########################################################################################
539secrets:
540 name: "default"
541 providers:
542 - name: environment
543 settings:
544 prefix: "KSTLIB"
545 delimiter: "__"
546 - name: keyring
547 settings:
548 service: "kstlib"
549 sops:
550 # Path to the encrypted secrets file (set to null to disable by default)
551 path: null
552 # Override the sops executable if it is not on PATH
553 binary: "sops"
554 # autodetect | json | yaml | text
555 format: "auto"
556 # Maximum cached decrypted files (LRU eviction)
557 # Hard limit enforced in code: 256
558 max_cache_entries: 64
559
560###########################################################################################
561## Authentication configuration (OAuth2/OIDC)
562###########################################################################################
563auth:
564 # Default provider to use when none specified
565 default_provider: null
566
567 # Token storage backend: "memory" (dev/testing), "file" (persistent), or "sops" (encrypted)
568 token_storage: "memory"
569
570 # OIDC discovery document cache TTL (seconds)
571 discovery_ttl: 3600
572
573 # TRACE level HTTP logging settings
574 trace:
575 # Pretty-print JSON bodies in TRACE logs (indent with 2 spaces)
576 pretty: true
577 # Maximum body length before truncation (chars)
578 # TRACE = debug mode, show full body by default
579 # Hard limit enforced in code: 10000 (10KB)
580 max_body_length: 10000
581
582 # Local callback server for authorization code flow
583 callback_server:
584 host: "127.0.0.1"
585 port: 8400
586 # Port range to try if primary port is busy (optional)
587 port_range: null # e.g., [8400, 8410]
588 # Timeout waiting for callback (seconds)
589 # Hard limit enforced in code: 600 (10 minutes)
590 timeout: 120
591
592 # Status display settings (kstlib auth status)
593 status:
594 # Access token considered "expiring soon" when remaining time < threshold
595 # Hard limits enforced in code: min 60s, max 3600s (1 hour)
596 expiring_soon_threshold: 120 # seconds (2 minutes)
597 # Refresh token considered "expiring soon" when remaining time < threshold
598 # Hard limits enforced in code: min 60s, max 172800s (48 hours)
599 # Typically higher since refresh tokens can live days/weeks/months
600 refresh_expiring_soon_threshold: 600 # seconds (10 minutes)
601 # Timezone for displaying timestamps: "local" or "utc"
602 display_timezone: "local"
603
604 # Token storage configuration per backend
605 storage:
606 file:
607 directory: "~/.config/kstlib/auth/tokens"
608 sops:
609 directory: "~/.config/kstlib/auth/tokens"
610
611 # Named providers (empty by default, users define their own)
612 providers: {}
613 # Example provider configuration:
614 # providers:
615 # corporate:
616 # type: "oidc" # oauth2 | oidc
617 #
618 # # OIDC Discovery modes:
619 # # - Auto: only issuer provided, endpoints auto-discovered
620 # # - Hybrid: issuer + some explicit endpoints (explicit wins)
621 # # - Manual: no issuer, all endpoints explicit (no discovery)
622 # issuer: "https://idp.corp.local/realms/main"
623 #
624 # client_id: "my-app"
625 # # Secret can be inline or SOPS reference
626 # client_secret: null # or "sops://secrets/auth.yaml#corporate.client_secret"
627 # scopes:
628 # - openid
629 # - profile
630 # - email
631 #
632 # # PKCE enabled by default (recommended for all clients)
633 # pkce: true
634 #
635 # # Optional endpoint overrides (auto-discovered if issuer provided)
636 # authorization_endpoint: null
637 # token_endpoint: null
638 # userinfo_endpoint: null
639 # jwks_uri: null
640 #
641 # # Custom HTTP headers sent with all IDP requests
642 # # Useful for load balancer validation, tenant routing, etc.
643 # headers: {}
644 # # Example:
645 # # headers:
646 # # Host: "idp.corp.local"
647 # # X-Tenant-Id: "corp"
648 #
649 # # Provider-specific token storage (overrides global)
650 # token_storage: null # "memory" | "file" | "sops"
651
652###########################################################################################
653## Utilities configuration
654###########################################################################################
655utilities:
656 secure_delete:
657 method: "auto"
658 passes: 3
659 zero_last_pass: true
660 chunk_size: 1048576 # 1 MiB
661
662###########################################################################################
663## Resilience configuration
664###########################################################################################
665resilience:
666 # --- Core components (used by WebSocket trading bots) ---
667
668 heartbeat:
669 # Seconds between heartbeats
670 # Hard limits enforced in code: min 1s, max 300s (5 minutes)
671 interval: 10
672
673 watchdog:
674 # Seconds of inactivity before triggering timeout callback
675 # Hard limits enforced in code: min 1s, max 3600s (1 hour)
676 timeout: 30
677
678 # --- Advanced components (for REST API calls, order placement) ---
679 # Note: WebSocket connections have built-in reconnection logic.
680 # These are useful for REST API resilience (e.g., placing orders, account queries).
681
682 shutdown:
683 # GracefulShutdown: Orderly cleanup on SIGTERM/SIGINT with prioritized callbacks.
684 # Use case: Ensure open orders are cancelled, positions closed before exit.
685 # Total timeout for all cleanup callbacks (seconds)
686 # Hard limits enforced in code: min 5s, max 300s (5 minutes)
687 timeout: 30
688 # Exit code when timeout exceeded
689 force_exit_code: 1
690
691 circuit_breaker:
692 # CircuitBreaker: Fail-fast pattern for external service calls.
693 # Use case: REST API calls (order placement, account info) - after N failures,
694 # stop calling the failing endpoint and fail immediately until recovery.
695 # Failures before opening circuit
696 # Hard limits enforced in code: min 1, max 100
697 max_failures: 5
698 # Cooldown before attempting recovery (seconds)
699 # Hard limits enforced in code: min 1s, max 3600s (1 hour)
700 reset_timeout: 60
701 # Calls allowed in half-open state for testing
702 # Hard limits enforced in code: min 1, max 10
703 half_open_max_calls: 1
704
705###########################################################################################
706## Database configuration
707###########################################################################################
708db:
709 pool:
710 # Minimum connections to maintain in pool (0 = lazy pool, on-demand)
711 # Hard limits enforced in code: min 0, max 10
712 min_size: 1
713 # Maximum connections allowed in pool
714 # Hard limits enforced in code: min 1, max 100
715 max_size: 10
716 # Timeout for acquiring a connection (seconds)
717 # Hard limits enforced in code: min 1.0, max 300.0 (5 minutes)
718 acquire_timeout: 30.0
719 retry:
720 # Retry attempts on connection failure
721 # Hard limits enforced in code: min 1, max 10
722 max_attempts: 3
723 # Delay between retries (seconds)
724 # Hard limits enforced in code: min 0.1, max 60.0
725 delay: 0.5
726
727 # SQLCipher encryption (opt-in, requires: pip install kstlib[db-crypto])
728 # System deps: libsqlcipher-dev (Debian/Ubuntu), sqlcipher (macOS/brew)
729 cipher:
730 # Enable SQLCipher encryption (default: false)
731 enabled: false
732 # Key source: env | sops | passphrase
733 # - env: Read from environment variable (key_env)
734 # - sops: Read from SOPS-encrypted file (sops_path + sops_key)
735 # - passphrase: Direct passphrase (ONLY for development/testing)
736 key_source: env
737 # Environment variable containing the encryption key
738 key_env: "KSTLIB_DB_KEY"
739 # SOPS configuration (when key_source: sops)
740 sops_path: null
741 sops_key: "db_key"
742 # Direct passphrase (NEVER use in production)
743 passphrase: null
744
745###########################################################################################
746## Credentials configuration (multi-source credential resolution)
747###########################################################################################
748credentials:
749 # Credentials are named entries that can be referenced by rapi services.
750 # Supported types: env, file, sops, provider
751 #
752 # Examples:
753 #
754 # # Type: env - from environment variable
755 # github:
756 # type: env
757 # var: "GITHUB_TOKEN"
758 #
759 # # Type: env - key+secret pair from environment
760 # kraken_env:
761 # type: env
762 # var_key: "KRAKEN_API_KEY"
763 # var_secret: "KRAKEN_API_SECRET"
764 #
765 # # Type: file - from JSON/YAML file with jq-like path extraction
766 # azure_cli:
767 # type: file
768 # path: "~/.azure/msal_token_cache.json"
769 # token_path: ".AccessToken.secret"
770 #
771 # # Type: file - key+secret from file fields
772 # api_file:
773 # type: file
774 # path: "~/.config/api_keys.json"
775 # key_field: "api_key"
776 # secret_field: "api_secret"
777 #
778 # # Type: sops - from SOPS-encrypted file
779 # kraken_prod:
780 # type: sops
781 # path: "secrets/kraken.sops.json"
782 # key_field: "api_key"
783 # secret_field: "api_secret"
784 #
785 # # Type: provider - from kstlib.auth provider (OAuth2/OIDC)
786 # corporate:
787 # type: provider
788 # provider: "corporate"
789
790###########################################################################################
791## REST API configuration (config-driven HTTP client)
792###########################################################################################
793rapi:
794 # Hard limits enforced in code for deep defense:
795 # - timeout: min 1s, max 300s (5 minutes)
796 # - max_response_size: max 100M
797 # - max_retries: min 0, max 10
798 # - retry_delay: min 0.1s, max 60s
799 # - retry_backoff: min 1.0, max 5.0
800 limits:
801 timeout: 30 # Request timeout in seconds
802 max_response_size: "10M" # Maximum response body size
803 max_retries: 3 # Retry attempts on failure
804 retry_delay: 1.0 # Initial delay between retries (seconds)
805 retry_backoff: 2.0 # Exponential backoff multiplier
806
807 # Safeguard configuration for dangerous HTTP methods
808 # Endpoints using these methods MUST define a safeguard string
809 # to prevent accidental destructive operations
810 safeguard:
811 # HTTP methods that require a safeguard to be defined on endpoints
812 # Default: DELETE and PUT (most destructive operations)
813 # Set to empty list [] to disable safeguard requirements
814 required_methods:
815 - DELETE
816 - PUT
817
818 # Pretty-print settings for CLI output
819 # Controls formatting of JSON and XML responses in terminal
820 pretty_render:
821 # JSON indentation (spaces). Set to null or 0 to disable pretty-printing.
822 json: 2
823 # XML pretty-print. Set to true to enable formatted XML output.
824 xml: true
825
826 # API services and their endpoints
827 # Define your own APIs here or use external *.rapi.yml files
828 api: {}
829
830 # Example: Azure Resource Manager API
831 # azure:
832 # base_url: "https://management.azure.com"
833 # credentials: azure_cli # Reference to credentials section
834 # auth_type: bearer
835 # headers:
836 # X-Custom-Header: "service-value"
837 # endpoints:
838 # list_subscriptions:
839 # path: "/subscriptions"
840 # query:
841 # api-version: "2020-01-01"
842 # headers:
843 # X-Request-ID: "{request_id}"
844
845###########################################################################################
846## Alerts configuration (multi-channel alerting)
847###########################################################################################
848alerts:
849 # Hard limits enforced in code for deep defense:
850 # - throttle.rate: min 1, max 1000 alerts per period
851 # - throttle.per: min 1.0, max 86400.0 seconds (1 day)
852 # - throttle.burst: min 1, max rate value
853
854 # Default throttle settings (anti-spam protection)
855 throttle:
856 rate: 10 # Maximum alerts per period
857 per: 60.0 # Period duration in seconds (1 minute)
858 burst: 5 # Initial burst capacity
859
860 # Default channel settings
861 channels:
862 # Timeout for sending alerts (seconds)
863 # Hard limits enforced in code: min 1.0, max 120.0
864 timeout: 30.0
865 # Retry attempts on delivery failure
866 # Hard limits enforced in code: min 0, max 5
867 max_retries: 2
868
869 presets:
870 dev:
871 throttle:
872 rate: 100 # More lenient for development
873 per: 60.0
874 burst: 20
875 channels:
876 timeout: 10.0
877 max_retries: 0
878
879 prod:
880 throttle:
881 rate: 10 # Strict rate limiting
882 per: 60.0
883 burst: 3
884 channels:
885 timeout: 30.0
886 max_retries: 3
887
888 critical_only:
889 throttle:
890 rate: 5 # Very strict for critical-only channels
891 per: 300.0 # 5 minutes
892 burst: 2
893
894###########################################################################################
895## Metrics configuration
896###########################################################################################
897metrics:
898 # Enable colored output
899 colors: true
900
901 # Output destination: stderr | stdout
902 output: stderr
903
904 # Default behavior for @metrics decorator (can be overridden per-call)
905 defaults:
906 time: true # Track execution time
907 memory: true # Track peak memory (tracemalloc)
908 step: false # Enable step numbering
909
910 # Step format string
911 # Variables: {n} (step number), {title}, {function}, {module}, {file}, {line}
912 step_format: "[STEP {n}] {title}"
913
914 # Lap format string (for Stopwatch)
915 # Variables: {n} (lap number), {name}
916 lap_format: "[LAP {n}] {name}"
917
918 # Title format (auto-generated when no custom title provided)
919 # Variables: {function}, {module}, {file}, {line}
920 title_format: "{function} [dim green]({file}:{line})[/dim green]"
921
922 # Time display precision (decimal places for seconds)
923 time_precision: 3
924
925 # Thresholds for color warnings
926 thresholds:
927 time_warn: 5 # Warn color if >= 5 seconds
928 time_crit: 30 # Critical color if >= 30 seconds
929 memory_warn: 100000000 # Warn color if >= 100 MB
930 memory_crit: 500000000 # Critical color if >= 500 MB
931
932 # Icons (set to "" to disable)
933 icons:
934 time: "⏱"
935 memory: "🧠"
936 peak: "Peak:" # Text after memory icon
937
938 # Color theme (Rich style names)
939 # See: https://rich.readthedocs.io/en/stable/appendix/colors.html
940 theme:
941 label: "bold green"
942 title: "bold white"
943 text: "white"
944 muted: "dim"
945 table_header: "bold cyan"
946 time_ok: "cyan"
947 time_warn: "orange3"
948 time_crit: "bold red"
949 memory_ok: "rosy_brown"
950 memory_warn: "orange3"
951 memory_crit: "bold red"
952 step_number: "dim"
953 separator: "dim white"
954
955 # Summary display style: table | simple
956 summary_style: table
957
958 # Show percentage of total time in summaries
959 show_percentages: true
960
961 # Print metrics to stderr by default
962 print_results: true
963
964###########################################################################################
965## WebSocket configuration (proactive connection control)
966###########################################################################################
967websocket:
968 # Ping/Pong heartbeat settings
969 ping:
970 # Seconds between ping frames
971 # Hard limits: [5, 60] - values outside bounds will be clamped
972 interval: 20
973 # Seconds to wait for pong response
974 # Hard limits: [5, 30]
975 timeout: 10
976
977 # Connection settings
978 connection:
979 # Timeout for initial connection (seconds)
980 # Hard limits: [5, 120]
981 timeout: 30
982
983 # Reconnection behavior
984 reconnect:
985 # Initial delay between reconnect attempts (seconds)
986 # Hard limits: [0, 300] - 0 = immediate reconnect allowed
987 delay: 1.0
988 # Maximum delay for exponential backoff (seconds)
989 # Hard limits: [1, 600]
990 max_delay: 60.0
991 # Maximum consecutive reconnection attempts
992 # Hard limits: [0, 100] - 0 = no retry
993 max_attempts: 10
994
995 # Message queue settings
996 queue:
997 # Maximum messages in queue (0 = unlimited)
998 # Hard limits: [0, 10000]
999 size: 1000
1000
1001 # Proactive control settings (KEY FEATURE)
1002 proactive:
1003 # Seconds between should_disconnect callback checks
1004 # Hard limits: [1, 60]
1005 disconnect_check_interval: 10.0
1006 # Seconds between should_reconnect callback checks
1007 # Hard limits: [0.5, 60]
1008 reconnect_check_interval: 5.0
1009 # Disconnect X seconds before 24h limit (Binance, etc.)
1010 # Hard limits: [60, 3600] - at least 1min, max 1h
1011 disconnect_margin: 300.0
1012
1013 # Presets for common use cases
1014 presets:
1015 trading:
1016 ping: { interval: 15, timeout: 10 }
1017 reconnect: { delay: 0.5, max_delay: 30.0, max_attempts: 20 }
1018 proactive: { disconnect_check_interval: 5.0, reconnect_check_interval: 2.0 }
1019 monitoring:
1020 ping: { interval: 30, timeout: 15 }
1021 reconnect: { delay: 5.0, max_delay: 120.0, max_attempts: 50 }
1022 proactive: { disconnect_check_interval: 30.0, reconnect_check_interval: 10.0 }
1023
1024###########################################################################################
1025## Pipeline configuration (declarative workflow execution)
1026###########################################################################################
1027pipeline:
1028 # Default step timeout in seconds
1029 # Hard limits enforced in code: min 1s, max 3600s (1 hour)
1030 default_timeout: 300
1031
1032 # Error handling policy: fail_fast | continue
1033 # fail_fast: abort pipeline on first step failure (run on_failure steps)
1034 # continue: execute all remaining steps even after failure
1035 on_error: fail_fast
1036
1037 # Named pipelines (user-defined)
1038 pipelines: {}
1039 # Example pipeline configuration:
1040 # morning-monitoring:
1041 # steps:
1042 # - name: build_logs
1043 # type: shell
1044 # command: |
1045 # for host in server1 server2; do
1046 # ssh $host "systemctl status viya"
1047 # done
1048 # timeout: 300
1049 # - name: process_data
1050 # type: python
1051 # module: my.analytics.runner
1052 # args: ["--output", "report.html"]
1053 # - name: notify
1054 # type: callable
1055 # callable: my.alerts:send_summary
1056 # when: always
1057 # - name: cleanup
1058 # type: shell
1059 # command: "rm -f /tmp/pipeline_*.tmp"
1060 # when: on_failure
1061
1062# ============================================================================
1063# OPS - Session Management Configuration
1064# ============================================================================
1065# Config-driven session management for persistent processes (bots, services).
1066# Supports tmux (local dev) and container (Podman/Docker) backends.
1067#
1068# Hard limits enforced:
1069# - Session name: max 64 chars, alphanumeric + underscore + hyphen
1070# - Image name: max 256 chars, valid OCI format
1071# - Volumes: max 20, no path traversal
1072# - Ports: max 50, range 1-65535
1073# - Env vars: max 100, key max 128 chars, value max 32KB
1074# - Command: max 4096 chars, dangerous patterns blocked
1075# ============================================================================
1076ops:
1077 # Default backend when not specified (tmux | container)
1078 default_backend: tmux
1079
1080 # Tmux binary path (default: tmux)
1081 tmux_binary: tmux
1082
1083 # Container runtime (podman | docker | null for auto-detect)
1084 container_runtime: null
1085
1086 # Pre-defined sessions (config-driven)
1087 # Sessions can be started with: kstlib ops start <name>
1088 sessions: {}
1089 # Example session configuration:
1090 # mybot:
1091 # backend: tmux # Override default_backend
1092 # command: "python -m mybot.main" # Command to run
1093 # working_dir: "/opt/mybot" # Working directory
1094 # env: # Environment variables
1095 # BOT_ENV: production
1096 # LOG_LEVEL: INFO
1097 #
1098 # mybot-prod:
1099 # backend: container
1100 # image: "mybot:latest" # Container image (required)
1101 # volumes: # Volume mounts (host:container[:ro|:rw])
1102 # - "./data:/app/data"
1103 # - "./logs:/app/logs:rw"
1104 # ports: # Port mappings (host:container[/tcp|/udp])
1105 # - "8080:80"
1106 # log_volume: "./logs:/app/logs" # Persistent logs for post-mortem
1107
1108###########################################################################################
1109## SSL/TLS configuration (global settings for all HTTP clients)
1110###########################################################################################
1111ssl:
1112 # Enable SSL certificate verification (default: true)
1113 # Set to false ONLY for development with self-signed certificates
1114 # WARNING: Disabling verification exposes you to MITM attacks
1115 verify: true
1116
1117 # Custom CA bundle path for corporate PKI or self-signed certificates
1118 # If provided, ssl_verify is implicitly true
1119 # Accepts: null (use system CAs), or path to PEM file
1120 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 enhancement on the backlog (tracked as
feat-config-paths-relative-to-yaml). 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.