Spring Boot-style configuration for Python applications
SprigConfig supports recursive configuration imports, allowing you to modularize large configurations into manageable, reusable pieces.
Use the imports: directive to include other configuration files:
# application.yml
server:
port: 8080
imports:
- database.yml
- logging.yml
- features.yml
Each imported file is merged into the configuration at the point where the import appears.
Import paths are relative to the configuration directory:
config/
├── application.yml
├── database.yml
├── logging.yml
└── features/
├── auth.yml
└── caching.yml
# application.yml
imports:
- database.yml
- logging.yml
- features/auth.yml
- features/caching.yml
Import paths can omit the file extension. SprigConfig automatically appends the active format’s extension:
# With yml format active
imports:
- database # Resolves to database.yml
- logging # Resolves to logging.yml
If you specify an extension, it must match the active format:
imports:
- database.yml # OK with yml format
- database.json # Error if yml format is active
Organize related configuration in subdirectories:
imports:
- common/defaults
- security/policies
- integrations/aws
Files are processed in the order they appear in the imports: list:
imports:
- a.yml # Loaded first
- b.yml # Merged on top of a.yml
- c.yml # Merged on top of result
Later imports can override values from earlier imports.
Imported files can themselves have imports:
# application.yml
imports:
- database.yml
# database.yml
database:
driver: postgresql
imports:
- database-pools.yml
# database-pools.yml
database:
pool_size: 10
SprigConfig processes these depth-first:
database.ymldatabase-pools.yml and mergeapplication.ymlImports can appear at any level in the configuration tree, not just at the root:
# application.yml
server:
port: 8080
imports:
- server-defaults.yml
database:
imports:
- database-defaults.yml
When imports appear under a key, the imported content merges at that level:
# server-defaults.yml (imported under server:)
host: localhost
timeout: 30
ssl: false
# Result
server:
port: 8080
host: localhost
timeout: 30
ssl: false
This is different from root-level imports where content merges at the root.
# application.yml
app:
features:
imports:
- feature-flags.yml
# feature-flags.yml
new_ui: true
beta_api: false
dark_mode: true
# Result
app:
features:
new_ui: true
beta_api: false
dark_mode: true
SprigConfig detects and prevents circular imports:
# a.yml
imports:
- b.yml
# b.yml
imports:
- a.yml # Creates a cycle!
This raises ConfigLoadError with a clear message about the cycle.
The same file can be imported from different paths as long as no cycle forms:
# application.yml
imports:
- shared.yml
# application-dev.yml
imports:
- shared.yml # OK - not a cycle
# This is fine because they're parallel imports, not circular
Imports cannot escape the configuration directory:
imports:
- ../secrets.yml # Blocked
- /etc/passwd # Blocked
- ../../other-project.yml # Blocked
SprigConfig raises ConfigLoadError for any path traversal attempts.
Absolute paths are not allowed:
imports:
- /opt/config/shared.yml # Blocked
All imports must be relative to the configuration directory.
Every loaded configuration includes an import trace in its metadata:
cfg = load_config(profile="dev")
trace = cfg["sprigconfig._meta.import_trace"]
for entry in trace:
print(f"File: {entry['file']}")
print(f"Depth: {entry['depth']}")
print(f"Order: {entry['order']}")
The trace shows:
This helps debug complex import hierarchies.
# config/common/defaults.yml
logging:
format: "%(levelname)s - %(message)s"
handlers:
- console
- file
# config/application.yml
imports:
- common/defaults.yml
logging:
level: INFO
# config/application.yml
imports:
- common/base.yml
# config/application-prod.yml
imports:
- environments/production.yml
# config/application.yml
imports:
- features/auth.yml
- features/caching.yml
- features/notifications.yml
# config/application.yml
imports:
- security/defaults.yml
# config/application-prod.yml
imports:
- security/hardened.yml
Both base and profile files can have imports:
# application.yml
imports:
- common/base.yml
# application-prod.yml
imports:
- production/settings.yml
Processing order:
application.ymlapplication.ymlapplication-<profile>.ymlapplication-<profile>.ymlProfile imports are processed last, giving them final override priority.
config/
├── application.yml
├── application-dev.yml
├── application-prod.yml
├── common/
│ ├── database.yml
│ └── logging.yml
├── features/
│ ├── auth.yml
│ └── caching.yml
└── environments/
├── development.yml
└── production.yml
While SprigConfig supports deep recursion, deeply nested imports are hard to debug:
# Harder to follow
a.yml → b.yml → c.yml → d.yml → e.yml
# Easier to follow
application.yml
├── common.yml
├── features.yml
└── environment.yml
# Good: related config together
database:
imports:
- database/connection.yml
- database/pools.yml
# Less clear: scattered at root
imports:
- database-connection.yml
- database-pools.yml
- unrelated-stuff.yml
Add comments explaining the import hierarchy:
# Base configuration with common defaults
# See: common/README.md for import structure
imports:
- common/logging.yml # Logging defaults
- common/security.yml # Security policies
- features/index.yml # Feature configuration