dataknobs-config 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dataknobs_config-0.1.0/CHANGELOG.md +40 -0
- dataknobs_config-0.1.0/PKG-INFO +423 -0
- dataknobs_config-0.1.0/README.md +397 -0
- dataknobs_config-0.1.0/docs/DESIGN_PLAN.md +165 -0
- dataknobs_config-0.1.0/docs/PROGRESS_CHECKLIST.md +70 -0
- dataknobs_config-0.1.0/pyproject.toml +55 -0
- dataknobs_config-0.1.0/setup.cfg +4 -0
- dataknobs_config-0.1.0/src/dataknobs_config/__init__.py +24 -0
- dataknobs_config-0.1.0/src/dataknobs_config/builders.py +248 -0
- dataknobs_config-0.1.0/src/dataknobs_config/config.py +560 -0
- dataknobs_config-0.1.0/src/dataknobs_config/environment.py +213 -0
- dataknobs_config-0.1.0/src/dataknobs_config/exceptions.py +37 -0
- dataknobs_config-0.1.0/src/dataknobs_config/references.py +170 -0
- dataknobs_config-0.1.0/src/dataknobs_config/settings.py +210 -0
- dataknobs_config-0.1.0/src/dataknobs_config.egg-info/PKG-INFO +423 -0
- dataknobs_config-0.1.0/src/dataknobs_config.egg-info/SOURCES.txt +28 -0
- dataknobs_config-0.1.0/src/dataknobs_config.egg-info/dependency_links.txt +1 -0
- dataknobs_config-0.1.0/src/dataknobs_config.egg-info/requires.txt +9 -0
- dataknobs_config-0.1.0/src/dataknobs_config.egg-info/top_level.txt +1 -0
- dataknobs_config-0.1.0/tests/conftest.py +72 -0
- dataknobs_config-0.1.0/tests/fixtures/atomic_db.yaml +6 -0
- dataknobs_config-0.1.0/tests/fixtures/test_config.json +24 -0
- dataknobs_config-0.1.0/tests/fixtures/test_config.yaml +33 -0
- dataknobs_config-0.1.0/tests/test_builders.py +259 -0
- dataknobs_config-0.1.0/tests/test_config.py +323 -0
- dataknobs_config-0.1.0/tests/test_environment.py +203 -0
- dataknobs_config-0.1.0/tests/test_lazy_factory.py +260 -0
- dataknobs_config-0.1.0/tests/test_path_resolution.py +264 -0
- dataknobs_config-0.1.0/tests/test_references.py +217 -0
- dataknobs_config-0.1.0/tests/test_regex_path_resolution.py +238 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the dataknobs-config package will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2025-01-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial release of dataknobs-config package
|
|
12
|
+
- Core `Config` class for managing modular configurations
|
|
13
|
+
- Support for YAML and JSON file formats
|
|
14
|
+
- Atomic configuration management with type/name-based access
|
|
15
|
+
- String reference system (`xref:`) for cross-referencing configurations
|
|
16
|
+
- Environment variable override system with bash-compatible naming
|
|
17
|
+
- Global settings and defaults management
|
|
18
|
+
- Path resolution for relative paths in configurations
|
|
19
|
+
- Object construction support with class instantiation and factory patterns
|
|
20
|
+
- Object caching for improved performance
|
|
21
|
+
- Comprehensive test suite with 91% code coverage
|
|
22
|
+
- Full type annotations with mypy support
|
|
23
|
+
- Detailed documentation and usage examples
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
- **Modular Design**: Organize configurations by type with atomic units
|
|
27
|
+
- **File Loading**: Load from YAML, JSON, or Python dictionaries
|
|
28
|
+
- **Cross-References**: Link configurations using `xref:type[name]` syntax
|
|
29
|
+
- **Environment Overrides**: Override any config value via environment variables
|
|
30
|
+
- **Path Resolution**: Automatic resolution of relative paths
|
|
31
|
+
- **Object Building**: Optional object construction from configurations
|
|
32
|
+
- **Settings Management**: Global and type-specific defaults
|
|
33
|
+
- **Extensible**: Clean interfaces for custom builders and factories
|
|
34
|
+
|
|
35
|
+
### Technical Details
|
|
36
|
+
- Python 3.8+ support
|
|
37
|
+
- Dependency: PyYAML >= 6.0
|
|
38
|
+
- Development dependencies include pytest, mypy, ruff, and types-PyYAML
|
|
39
|
+
- Follows PEP 8 style guidelines
|
|
40
|
+
- 100% type annotated codebase
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dataknobs-config
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Modular configuration system with composable settings and environment variable overrides
|
|
5
|
+
Author-email: KBS Labs <info@kbs-labs.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.8
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: pyyaml>=6.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
22
|
+
Requires-Dist: black>=22.0; extra == "dev"
|
|
23
|
+
Requires-Dist: flake8>=5.0; extra == "dev"
|
|
24
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
25
|
+
Requires-Dist: types-PyYAML>=6.0; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# DataKnobs Config
|
|
28
|
+
|
|
29
|
+
A modular, reusable configuration system for composable settings with environment variable overrides, file loading, and optional object construction helpers.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **Modular Configuration**: Organize configurations by type with atomic configuration units
|
|
34
|
+
- **Multiple Input Formats**: Load from YAML, JSON files, or Python dictionaries
|
|
35
|
+
- **Composable**: Reference other configurations and compose complex setups
|
|
36
|
+
- **Environment Overrides**: Override any configuration value via environment variables
|
|
37
|
+
- **Path Resolution**: Automatically resolve relative paths to absolute
|
|
38
|
+
- **Object Construction**: Optional helpers to build objects from configurations
|
|
39
|
+
- **Defaults Management**: Global and type-specific default values
|
|
40
|
+
- **Caching**: Cache constructed objects for efficiency
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install dataknobs-config
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from dataknobs_config import Config
|
|
52
|
+
|
|
53
|
+
# Load from dictionary
|
|
54
|
+
config = Config({
|
|
55
|
+
"database": [
|
|
56
|
+
{"name": "primary", "host": "localhost", "port": 5432},
|
|
57
|
+
{"name": "secondary", "host": "backup.local", "port": 5433}
|
|
58
|
+
],
|
|
59
|
+
"cache": [
|
|
60
|
+
{"name": "redis", "host": "localhost", "port": 6379}
|
|
61
|
+
]
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
# Access configurations
|
|
65
|
+
primary_db = config.get("database", "primary")
|
|
66
|
+
print(primary_db["host"]) # localhost
|
|
67
|
+
|
|
68
|
+
# Load from file
|
|
69
|
+
config = Config.from_file("config.yaml")
|
|
70
|
+
|
|
71
|
+
# Load from multiple sources
|
|
72
|
+
config = Config("base.yaml", "overrides.json", {"extra": [...]})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Core Concepts
|
|
76
|
+
|
|
77
|
+
### Atomic Configurations
|
|
78
|
+
|
|
79
|
+
Each configuration is an "atomic" unit - a dictionary of settings for a single object:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
{
|
|
83
|
+
"name": "primary", # Optional, auto-generated if not provided
|
|
84
|
+
"type": "database", # Optional, inferred from parent key
|
|
85
|
+
"host": "localhost",
|
|
86
|
+
"port": 5432,
|
|
87
|
+
# ... any other attributes
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Configuration Structure
|
|
92
|
+
|
|
93
|
+
Internally, configurations are organized by type:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
{
|
|
97
|
+
"database": [ # Type name
|
|
98
|
+
{...}, # Atomic config 1
|
|
99
|
+
{...} # Atomic config 2
|
|
100
|
+
],
|
|
101
|
+
"cache": [
|
|
102
|
+
{...} # Atomic config
|
|
103
|
+
],
|
|
104
|
+
"settings": { # Special type for global settings
|
|
105
|
+
"config_root": "/app/config",
|
|
106
|
+
"default_timeout": 30
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## String References (xref)
|
|
112
|
+
|
|
113
|
+
Reference other configurations using the xref format:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
config = Config({
|
|
117
|
+
"database": [
|
|
118
|
+
{"name": "primary", "host": "db.example.com"}
|
|
119
|
+
],
|
|
120
|
+
"api": [
|
|
121
|
+
{
|
|
122
|
+
"name": "main",
|
|
123
|
+
"database": "xref:database[primary]" # Reference
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
# Resolve references
|
|
129
|
+
api = config.resolve_reference("xref:api[main]")
|
|
130
|
+
print(api["database"]["host"]) # db.example.com
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Reference Formats
|
|
134
|
+
|
|
135
|
+
- `xref:type[name]` - Reference by name
|
|
136
|
+
- `xref:type[0]` - Reference by index
|
|
137
|
+
- `xref:type[-1]` - Reference last item
|
|
138
|
+
- `xref:type` - Reference first/only item
|
|
139
|
+
|
|
140
|
+
## Environment Variable Overrides
|
|
141
|
+
|
|
142
|
+
Override any configuration value using environment variables:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
export DATAKNOBS_DATABASE__PRIMARY__HOST=prod.example.com
|
|
146
|
+
export DATAKNOBS_DATABASE__PRIMARY__PORT=5433
|
|
147
|
+
export DATAKNOBS_CACHE__REDIS__TTL=7200
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
config = Config({
|
|
152
|
+
"database": [{"name": "primary", "host": "localhost", "port": 5432}],
|
|
153
|
+
"cache": [{"name": "redis", "ttl": 3600}]
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
# Environment variables automatically override values
|
|
157
|
+
db = config.get("database", "primary")
|
|
158
|
+
print(db["host"]) # prod.example.com
|
|
159
|
+
print(db["port"]) # 5433 (converted to int)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Environment Variable Format
|
|
163
|
+
|
|
164
|
+
- Pattern: `DATAKNOBS_<TYPE>__<NAME_OR_INDEX>__<ATTRIBUTE>`
|
|
165
|
+
- Nested attributes: `DATAKNOBS_DATABASE__0__CONNECTION__TIMEOUT`
|
|
166
|
+
- Automatic type conversion for integers, floats, and booleans
|
|
167
|
+
|
|
168
|
+
## File References
|
|
169
|
+
|
|
170
|
+
Reference external configuration files using the `@` prefix:
|
|
171
|
+
|
|
172
|
+
```yaml
|
|
173
|
+
# main.yaml
|
|
174
|
+
database:
|
|
175
|
+
- "@database/primary.yaml" # Load from file
|
|
176
|
+
- "@database/secondary.yaml"
|
|
177
|
+
|
|
178
|
+
settings:
|
|
179
|
+
config_root: /app/config # Base path for relative references
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Global Settings and Defaults
|
|
183
|
+
|
|
184
|
+
Configure global settings and defaults in the special `settings` section:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
config = Config({
|
|
188
|
+
"database": [{"name": "db1"}],
|
|
189
|
+
"settings": {
|
|
190
|
+
# Paths
|
|
191
|
+
"config_root": "/app/config", # Base path for "@"-prefixed config file references
|
|
192
|
+
"global_root": "/app", # Base for path resolution (settings.path_resolution_attributes)
|
|
193
|
+
"database.global_root": "/app/db", # Type-specific base for path resolution
|
|
194
|
+
|
|
195
|
+
# Path resolution (supports exact names and regex patterns)
|
|
196
|
+
"path_resolution_attributes": [
|
|
197
|
+
"config_path", # Exact match for all types
|
|
198
|
+
"database.data_dir", # Exact match for database type only
|
|
199
|
+
"/.*_path$/", # Regex: all attributes ending with "_path"
|
|
200
|
+
"cache./.*_dir$/" # Regex: cache type attributes ending with "_dir"
|
|
201
|
+
],
|
|
202
|
+
|
|
203
|
+
# Defaults
|
|
204
|
+
"default_timeout": 30, # Global default
|
|
205
|
+
"database.default_pool_size": 10 # Type-specific default
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Path Resolution
|
|
211
|
+
|
|
212
|
+
Automatically resolve relative paths to absolute:
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
config = Config({
|
|
216
|
+
"database": [{
|
|
217
|
+
"name": "db1",
|
|
218
|
+
"data_dir": "./data", # Relative path
|
|
219
|
+
"backup_dir": "/abs/path" # Absolute path unchanged
|
|
220
|
+
}],
|
|
221
|
+
"settings": {
|
|
222
|
+
"global_root": "/app", # Base for path resolution
|
|
223
|
+
"path_resolution_attributes": ["data_dir", "backup_dir"]
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
db = config.get("database", "db1")
|
|
228
|
+
print(db["data_dir"]) # /app/data (resolved)
|
|
229
|
+
print(db["backup_dir"]) # /abs/path (unchanged)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Object Construction (Optional)
|
|
233
|
+
|
|
234
|
+
Build objects directly from configurations:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
# Using class attribute
|
|
238
|
+
config = Config({
|
|
239
|
+
"database": [{
|
|
240
|
+
"name": "primary",
|
|
241
|
+
"class": "myapp.database.PostgreSQL",
|
|
242
|
+
"host": "localhost",
|
|
243
|
+
"port": 5432
|
|
244
|
+
}]
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
# Build object
|
|
248
|
+
db = config.build_object("xref:database[primary]")
|
|
249
|
+
# Returns instance of myapp.database.PostgreSQL
|
|
250
|
+
|
|
251
|
+
# Using factory pattern
|
|
252
|
+
config = Config({
|
|
253
|
+
"cache": [{
|
|
254
|
+
"name": "redis",
|
|
255
|
+
"factory": "myapp.cache.CacheFactory",
|
|
256
|
+
"type": "redis",
|
|
257
|
+
"host": "localhost"
|
|
258
|
+
}]
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
cache = config.build_object("xref:cache[redis]")
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Implementing Configurable Classes
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
from dataknobs_config import ConfigurableBase
|
|
268
|
+
|
|
269
|
+
class MyDatabase(ConfigurableBase):
|
|
270
|
+
def __init__(self, host, port, **kwargs):
|
|
271
|
+
self.host = host
|
|
272
|
+
self.port = port
|
|
273
|
+
|
|
274
|
+
@classmethod
|
|
275
|
+
def from_config(cls, config):
|
|
276
|
+
# Custom configuration logic
|
|
277
|
+
return cls(**config)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Implementing Factories
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
from dataknobs_config import FactoryBase
|
|
284
|
+
|
|
285
|
+
class DatabaseFactory(FactoryBase):
|
|
286
|
+
def create(self, **config):
|
|
287
|
+
db_type = config.pop("type", "postgresql")
|
|
288
|
+
if db_type == "postgresql":
|
|
289
|
+
return PostgreSQL(**config)
|
|
290
|
+
elif db_type == "mysql":
|
|
291
|
+
return MySQL(**config)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Lazy Factory Access
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
# Configuration with factory
|
|
298
|
+
config = Config({
|
|
299
|
+
"database": [{
|
|
300
|
+
"name": "primary",
|
|
301
|
+
"factory": "myapp.db.DatabaseFactory",
|
|
302
|
+
"type": "postgresql",
|
|
303
|
+
"host": "localhost"
|
|
304
|
+
}]
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
# Get the factory instance (cached)
|
|
308
|
+
factory = config.get_factory("database", "primary")
|
|
309
|
+
db1 = factory.create(database="app1")
|
|
310
|
+
db2 = factory.create(database="app2")
|
|
311
|
+
|
|
312
|
+
# Or get an instance directly
|
|
313
|
+
db = config.get_instance("database", "primary", database="myapp")
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## API Reference
|
|
317
|
+
|
|
318
|
+
### Config Class
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
class Config:
|
|
322
|
+
def __init__(self, *sources, use_env=True)
|
|
323
|
+
def from_file(cls, path) -> Config
|
|
324
|
+
def from_dict(cls, data) -> Config
|
|
325
|
+
|
|
326
|
+
# Access
|
|
327
|
+
def get_types() -> List[str]
|
|
328
|
+
def get_count(type_name: str) -> int
|
|
329
|
+
def get_names(type_name: str) -> List[str]
|
|
330
|
+
def get(type_name: str, name_or_index: Union[str, int] = 0) -> dict
|
|
331
|
+
def set(type_name: str, name_or_index: Union[str, int], config: dict)
|
|
332
|
+
|
|
333
|
+
# References
|
|
334
|
+
def resolve_reference(ref: str) -> dict
|
|
335
|
+
def build_reference(type_name: str, name_or_index: Union[str, int]) -> str
|
|
336
|
+
|
|
337
|
+
# Merging
|
|
338
|
+
def merge(other: Config, precedence: str = "first")
|
|
339
|
+
|
|
340
|
+
# Export
|
|
341
|
+
def to_dict() -> dict
|
|
342
|
+
def to_file(path: Path, format: str = None)
|
|
343
|
+
|
|
344
|
+
# Object Construction
|
|
345
|
+
def build_object(ref: str, cache: bool = True, **kwargs) -> Any
|
|
346
|
+
def clear_object_cache(ref: str = None)
|
|
347
|
+
|
|
348
|
+
# Lazy Factory Access
|
|
349
|
+
def get_factory(type_name: str, name_or_index: Union[str, int] = 0) -> Any
|
|
350
|
+
def get_instance(type_name: str, name_or_index: Union[str, int] = 0, **kwargs) -> Any
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Examples
|
|
354
|
+
|
|
355
|
+
### Multi-Environment Configuration
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
# base.yaml
|
|
359
|
+
database:
|
|
360
|
+
- name: primary
|
|
361
|
+
host: localhost
|
|
362
|
+
port: 5432
|
|
363
|
+
|
|
364
|
+
# production.yaml
|
|
365
|
+
database:
|
|
366
|
+
- name: primary
|
|
367
|
+
host: prod.db.example.com
|
|
368
|
+
pool_size: 50
|
|
369
|
+
|
|
370
|
+
# Load with overrides
|
|
371
|
+
config = Config("base.yaml", "production.yaml")
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Service Discovery Integration
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
config = Config({
|
|
378
|
+
"services": [
|
|
379
|
+
{"name": "auth", "url": "http://auth:8000"},
|
|
380
|
+
{"name": "api", "url": "http://api:8080"}
|
|
381
|
+
],
|
|
382
|
+
"app": [{
|
|
383
|
+
"name": "main",
|
|
384
|
+
"auth_service": "xref:services[auth]",
|
|
385
|
+
"api_service": "xref:services[api]"
|
|
386
|
+
}]
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
app = config.resolve_reference("xref:app[main]")
|
|
390
|
+
# app["auth_service"]["url"] = "http://auth:8000"
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Dynamic Configuration with Environment
|
|
394
|
+
|
|
395
|
+
```python
|
|
396
|
+
# Development: export DATAKNOBS_DATABASE__PRIMARY__HOST=localhost
|
|
397
|
+
# Production: export DATAKNOBS_DATABASE__PRIMARY__HOST=prod.db.aws.com
|
|
398
|
+
|
|
399
|
+
config = Config.from_file("config.yaml")
|
|
400
|
+
db = config.get("database", "primary")
|
|
401
|
+
# Automatically uses environment-appropriate host
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Best Practices
|
|
405
|
+
|
|
406
|
+
1. **Use Type Organization**: Group related configurations by type
|
|
407
|
+
2. **Leverage Defaults**: Define common values in settings to avoid repetition
|
|
408
|
+
3. **Environment Overrides**: Use for deployment-specific values (hosts, ports, credentials)
|
|
409
|
+
4. **File References**: Split large configurations into manageable files
|
|
410
|
+
5. **Path Resolution**: Use relative paths in configs for portability
|
|
411
|
+
6. **Object Caching**: Enable caching for expensive object construction
|
|
412
|
+
|
|
413
|
+
## Testing
|
|
414
|
+
|
|
415
|
+
Run tests with pytest:
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
pytest tests/
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## License
|
|
422
|
+
|
|
423
|
+
MIT License - see LICENSE file for details.
|