configplusplus 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.
- configplusplus-0.1.0/LICENSE +21 -0
- configplusplus-0.1.0/PKG-INFO +418 -0
- configplusplus-0.1.0/README.md +390 -0
- configplusplus-0.1.0/pyproject.toml +46 -0
- configplusplus-0.1.0/src/configplusplus/__init__.py +20 -0
- configplusplus-0.1.0/src/configplusplus/base.py +138 -0
- configplusplus-0.1.0/src/configplusplus/env_loader.py +134 -0
- configplusplus-0.1.0/src/configplusplus/utils.py +118 -0
- configplusplus-0.1.0/src/configplusplus/yaml_loader.py +260 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Florian BARRE
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: configplusplus
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A powerful configuration management library with beautiful display, environment variables and YAML support
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: config,configuration,environment,yaml,settings
|
|
8
|
+
Author: Florian BARRE
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Dist: loggerplusplus (>=1.0.5)
|
|
21
|
+
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
|
|
22
|
+
Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
|
|
23
|
+
Project-URL: Documentation, https://github.com/Florian-BARRE/ConfigPlusPlus#readme
|
|
24
|
+
Project-URL: Homepage, https://github.com/Florian-BARRE/ConfigPlusPlus
|
|
25
|
+
Project-URL: Repository, https://github.com/Florian-BARRE/ConfigPlusPlus
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# ConfigPlusPlus
|
|
29
|
+
|
|
30
|
+
> Beautiful configuration management for Python with environment variables and YAML support
|
|
31
|
+
|
|
32
|
+
[](https://pypi.org/project/configplusplus/)
|
|
33
|
+
[](https://pypi.org/project/configplusplus/)
|
|
34
|
+
[](https://opensource.org/licenses/MIT)
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
✨ **Beautiful Display** - Pretty formatted configuration output with automatic grouping and secret masking
|
|
39
|
+
|
|
40
|
+
🔐 **Secret Masking** - Automatically hides sensitive values (API keys, passwords, tokens)
|
|
41
|
+
|
|
42
|
+
🌍 **Environment Variables** - Load configuration from environment variables with type casting
|
|
43
|
+
|
|
44
|
+
📄 **YAML Support** - Load configuration from YAML files with custom parsing
|
|
45
|
+
|
|
46
|
+
🎯 **Type Casting** - Automatic type conversion (str, int, float, bool, Path)
|
|
47
|
+
|
|
48
|
+
🏷️ **Static & Instance** - Support for both static class-based and instance-based configs
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install configplusplus
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or with Poetry:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
poetry add configplusplus
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
### Environment-Based Configuration
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from configplusplus import EnvConfigLoader, env
|
|
68
|
+
import pathlib
|
|
69
|
+
|
|
70
|
+
class AppConfig(EnvConfigLoader):
|
|
71
|
+
# Required variables
|
|
72
|
+
DATABASE_HOST = env("DATABASE_HOST")
|
|
73
|
+
DATABASE_PORT = env("DATABASE_PORT", cast=int)
|
|
74
|
+
|
|
75
|
+
# Optional with defaults
|
|
76
|
+
DEBUG_MODE = env("DEBUG_MODE", cast=bool, default=False)
|
|
77
|
+
|
|
78
|
+
# Paths
|
|
79
|
+
DATA_DIR = env("DATA_DIR", cast=pathlib.Path)
|
|
80
|
+
|
|
81
|
+
# Secrets (automatically masked in output)
|
|
82
|
+
SECRET_API_KEY = env("SECRET_API_KEY")
|
|
83
|
+
|
|
84
|
+
# Use as static class
|
|
85
|
+
print(AppConfig.DATABASE_HOST)
|
|
86
|
+
print(AppConfig) # Beautiful formatted output
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Output:**
|
|
90
|
+
```
|
|
91
|
+
╔════════════════════════════════════════════╗
|
|
92
|
+
║ APPCONFIG ║
|
|
93
|
+
╚════════════════════════════════════════════╝
|
|
94
|
+
|
|
95
|
+
▶ DATABASE
|
|
96
|
+
DATABASE_HOST = 'localhost'
|
|
97
|
+
DATABASE_PORT = 5432
|
|
98
|
+
|
|
99
|
+
▶ DEBUG
|
|
100
|
+
DEBUG_MODE = False
|
|
101
|
+
|
|
102
|
+
▶ DATA
|
|
103
|
+
DATA_DIR = '/var/data/myapp'
|
|
104
|
+
|
|
105
|
+
▶ SECRET
|
|
106
|
+
SECRET_API_KEY = 'sec...et (hidden)'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### YAML-Based Configuration
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from configplusplus import YamlConfigLoader
|
|
113
|
+
|
|
114
|
+
class UiConfig(YamlConfigLoader):
|
|
115
|
+
def __post_init__(self) -> None:
|
|
116
|
+
# Parse the loaded YAML data
|
|
117
|
+
self.app_name = self._raw_config["application"]["name"]
|
|
118
|
+
self.theme = self._raw_config["display"]["theme"]
|
|
119
|
+
|
|
120
|
+
# Parse nested structures
|
|
121
|
+
self.filters = [
|
|
122
|
+
FilterConfig(**f)
|
|
123
|
+
for f in self._raw_config["filters"]
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
# Instantiate with path
|
|
127
|
+
config = UiConfig("config.yaml")
|
|
128
|
+
print(config.app_name)
|
|
129
|
+
print(config) # Beautiful formatted output
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Environment Variables
|
|
133
|
+
|
|
134
|
+
### Basic Usage
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from configplusplus import env
|
|
138
|
+
|
|
139
|
+
# String (default)
|
|
140
|
+
DATABASE_HOST = env("DATABASE_HOST")
|
|
141
|
+
|
|
142
|
+
# Integer
|
|
143
|
+
DATABASE_PORT = env("DATABASE_PORT", cast=int)
|
|
144
|
+
|
|
145
|
+
# Boolean
|
|
146
|
+
DEBUG_MODE = env("DEBUG_MODE", cast=bool)
|
|
147
|
+
|
|
148
|
+
# Float
|
|
149
|
+
TEMPERATURE = env("TEMPERATURE", cast=float)
|
|
150
|
+
|
|
151
|
+
# Path
|
|
152
|
+
DATA_DIR = env("DATA_DIR", cast=pathlib.Path)
|
|
153
|
+
|
|
154
|
+
# With default value
|
|
155
|
+
TIMEOUT = env("TIMEOUT", cast=int, default=30)
|
|
156
|
+
|
|
157
|
+
# Optional (won't raise if missing)
|
|
158
|
+
OPTIONAL = env("OPTIONAL", required=False, default=None)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Boolean Casting
|
|
162
|
+
|
|
163
|
+
When `cast=bool`, these strings are considered `False`:
|
|
164
|
+
- `"false"`, `"False"`, `"FALSE"`
|
|
165
|
+
- `"0"`
|
|
166
|
+
- `"no"`, `"No"`, `"NO"`
|
|
167
|
+
- `""` (empty string)
|
|
168
|
+
|
|
169
|
+
All other values are considered `True`.
|
|
170
|
+
|
|
171
|
+
### Loading .env Files
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from configplusplus import safe_load_envs
|
|
175
|
+
|
|
176
|
+
# Load .env file with logging
|
|
177
|
+
safe_load_envs() # Loads from ".env"
|
|
178
|
+
|
|
179
|
+
# Load from custom path
|
|
180
|
+
safe_load_envs("config/.env")
|
|
181
|
+
|
|
182
|
+
# Silent loading
|
|
183
|
+
safe_load_envs(verbose=False)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## YAML Configuration
|
|
187
|
+
|
|
188
|
+
### Basic Usage
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from configplusplus import YamlConfigLoader
|
|
192
|
+
|
|
193
|
+
class MyConfig(YamlConfigLoader):
|
|
194
|
+
def __post_init__(self) -> None:
|
|
195
|
+
# Access raw YAML data
|
|
196
|
+
self.database_host = self._raw_config["database"]["host"]
|
|
197
|
+
self.database_port = self._raw_config["database"]["port"]
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Helper Methods
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
config = MyConfig("config.yaml")
|
|
204
|
+
|
|
205
|
+
# Get values with dot notation
|
|
206
|
+
host = config.get("database.host")
|
|
207
|
+
port = config.get("database.port")
|
|
208
|
+
|
|
209
|
+
# Get with default
|
|
210
|
+
timeout = config.get("api.timeout", default=30)
|
|
211
|
+
|
|
212
|
+
# Check if key exists
|
|
213
|
+
if config.has("database.host"):
|
|
214
|
+
print("Database configured")
|
|
215
|
+
|
|
216
|
+
# Convert to dictionary
|
|
217
|
+
config_dict = config.to_dict()
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Advanced Features
|
|
221
|
+
|
|
222
|
+
### Custom Validation
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
class ValidatedConfig(EnvConfigLoader):
|
|
226
|
+
DATABASE_PORT = env("DATABASE_PORT", cast=int)
|
|
227
|
+
|
|
228
|
+
@classmethod
|
|
229
|
+
def validate(cls) -> None:
|
|
230
|
+
super().validate()
|
|
231
|
+
if cls.DATABASE_PORT < 1024:
|
|
232
|
+
raise RuntimeError("DATABASE_PORT must be >= 1024")
|
|
233
|
+
|
|
234
|
+
# Validate configuration
|
|
235
|
+
ValidatedConfig.validate()
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Structured Data from YAML
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from dataclasses import dataclass
|
|
242
|
+
from typing import List
|
|
243
|
+
|
|
244
|
+
@dataclass
|
|
245
|
+
class FilterConfig:
|
|
246
|
+
name: str
|
|
247
|
+
type: str
|
|
248
|
+
enabled: bool = True
|
|
249
|
+
|
|
250
|
+
class UiConfig(YamlConfigLoader):
|
|
251
|
+
def __post_init__(self) -> None:
|
|
252
|
+
# Parse list of structured objects
|
|
253
|
+
self.filters: List[FilterConfig] = [
|
|
254
|
+
FilterConfig(**f)
|
|
255
|
+
for f in self._raw_config["filters"]
|
|
256
|
+
]
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Multiple Configuration Sources
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
# Combine environment and YAML configs
|
|
263
|
+
class AppConfig(EnvConfigLoader):
|
|
264
|
+
# From environment
|
|
265
|
+
SECRET_API_KEY = env("SECRET_API_KEY")
|
|
266
|
+
DATABASE_HOST = env("DATABASE_HOST")
|
|
267
|
+
|
|
268
|
+
# Load YAML for features
|
|
269
|
+
@classmethod
|
|
270
|
+
def load_features(cls) -> None:
|
|
271
|
+
yaml_config = YamlConfigLoader("features.yaml")
|
|
272
|
+
cls.FEATURES = yaml_config.get("features")
|
|
273
|
+
|
|
274
|
+
AppConfig.load_features()
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Secret Masking
|
|
278
|
+
|
|
279
|
+
Variables containing these keywords are automatically masked in output:
|
|
280
|
+
- `SECRET`
|
|
281
|
+
- `API_KEY`
|
|
282
|
+
- `PASSWORD`
|
|
283
|
+
- `TOKEN`
|
|
284
|
+
- `CREDENTIAL`
|
|
285
|
+
|
|
286
|
+
Example:
|
|
287
|
+
```python
|
|
288
|
+
SECRET_API_KEY = "sk_live_abc123xyz789"
|
|
289
|
+
# Output: "sk_...89 (hidden)"
|
|
290
|
+
|
|
291
|
+
PASSWORD = "short"
|
|
292
|
+
# Output: "***hidden***"
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Configuration Grouping
|
|
296
|
+
|
|
297
|
+
Configuration values are automatically grouped by prefix:
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
class AppConfig(EnvConfigLoader):
|
|
301
|
+
DATABASE_HOST = env("DATABASE_HOST")
|
|
302
|
+
DATABASE_PORT = env("DATABASE_PORT", cast=int)
|
|
303
|
+
API_ENDPOINT = env("API_ENDPOINT")
|
|
304
|
+
API_KEY = env("API_KEY")
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**Output shows grouped display:**
|
|
308
|
+
```
|
|
309
|
+
▶ DATABASE
|
|
310
|
+
DATABASE_HOST = 'localhost'
|
|
311
|
+
DATABASE_PORT = 5432
|
|
312
|
+
|
|
313
|
+
▶ API
|
|
314
|
+
API_ENDPOINT = 'https://api.example.com'
|
|
315
|
+
API_KEY = 'key...23 (hidden)'
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Real-World Examples
|
|
319
|
+
|
|
320
|
+
### FastAPI Application Config
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
from configplusplus import EnvConfigLoader, env, safe_load_envs
|
|
324
|
+
import pathlib
|
|
325
|
+
|
|
326
|
+
safe_load_envs()
|
|
327
|
+
|
|
328
|
+
class APIConfig(EnvConfigLoader):
|
|
329
|
+
# Server
|
|
330
|
+
HOST = env("HOST", default="0.0.0.0")
|
|
331
|
+
PORT = env("PORT", cast=int, default=8000)
|
|
332
|
+
|
|
333
|
+
# Database
|
|
334
|
+
DATABASE_URL = env("DATABASE_URL")
|
|
335
|
+
DATABASE_POOL_SIZE = env("DATABASE_POOL_SIZE", cast=int, default=10)
|
|
336
|
+
|
|
337
|
+
# Redis
|
|
338
|
+
REDIS_HOST = env("REDIS_HOST", default="localhost")
|
|
339
|
+
REDIS_PORT = env("REDIS_PORT", cast=int, default=6379)
|
|
340
|
+
|
|
341
|
+
# Security
|
|
342
|
+
SECRET_JWT_KEY = env("SECRET_JWT_KEY")
|
|
343
|
+
TOKEN_EXPIRE_MINUTES = env("TOKEN_EXPIRE_MINUTES", cast=int, default=60)
|
|
344
|
+
|
|
345
|
+
# Features
|
|
346
|
+
ENABLE_CORS = env("ENABLE_CORS", cast=bool, default=True)
|
|
347
|
+
ENABLE_DOCS = env("ENABLE_DOCS", cast=bool, default=False)
|
|
348
|
+
|
|
349
|
+
@classmethod
|
|
350
|
+
def validate(cls) -> None:
|
|
351
|
+
if cls.PORT < 1024 or cls.PORT > 65535:
|
|
352
|
+
raise RuntimeError("Invalid PORT")
|
|
353
|
+
|
|
354
|
+
# Use in FastAPI
|
|
355
|
+
from fastapi import FastAPI
|
|
356
|
+
|
|
357
|
+
app = FastAPI(
|
|
358
|
+
title="My API",
|
|
359
|
+
docs_url="/docs" if APIConfig.ENABLE_DOCS else None,
|
|
360
|
+
)
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Document Processing Pipeline Config
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
from configplusplus import YamlConfigLoader
|
|
367
|
+
from typing import List
|
|
368
|
+
from dataclasses import dataclass
|
|
369
|
+
|
|
370
|
+
@dataclass
|
|
371
|
+
class ProcessorConfig:
|
|
372
|
+
name: str
|
|
373
|
+
enabled: bool
|
|
374
|
+
priority: int
|
|
375
|
+
|
|
376
|
+
class PipelineConfig(YamlConfigLoader):
|
|
377
|
+
def __post_init__(self) -> None:
|
|
378
|
+
# Parse processors
|
|
379
|
+
self.processors: List[ProcessorConfig] = [
|
|
380
|
+
ProcessorConfig(**p)
|
|
381
|
+
for p in self._raw_config["processors"]
|
|
382
|
+
]
|
|
383
|
+
|
|
384
|
+
# Parse paths
|
|
385
|
+
self.input_dir = pathlib.Path(self._raw_config["paths"]["input"])
|
|
386
|
+
self.output_dir = pathlib.Path(self._raw_config["paths"]["output"])
|
|
387
|
+
|
|
388
|
+
# Parse settings
|
|
389
|
+
self.batch_size = self._raw_config["settings"]["batch_size"]
|
|
390
|
+
self.max_workers = self._raw_config["settings"]["max_workers"]
|
|
391
|
+
|
|
392
|
+
# Load configuration
|
|
393
|
+
config = PipelineConfig("pipeline.yaml")
|
|
394
|
+
|
|
395
|
+
# Use in pipeline
|
|
396
|
+
for processor in sorted(config.processors, key=lambda x: x.priority):
|
|
397
|
+
if processor.enabled:
|
|
398
|
+
print(f"Running {processor.name}")
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Documentation
|
|
402
|
+
|
|
403
|
+
- **Quick Reference**: See [REFERENCE.md](REFERENCE.md) for a cheat sheet
|
|
404
|
+
- **Detailed Guide**: See [USAGE.md](USAGE.md) for comprehensive documentation
|
|
405
|
+
- **Examples**: Check the `examples/` directory for working code samples
|
|
406
|
+
|
|
407
|
+
## Links
|
|
408
|
+
|
|
409
|
+
- **PyPI**: https://pypi.org/project/configplusplus/
|
|
410
|
+
- **GitHub**: https://github.com/Florian-BARRE/ConfigPlusPlus
|
|
411
|
+
- **Issues**: https://github.com/Florian-BARRE/ConfigPlusPlus/issues
|
|
412
|
+
|
|
413
|
+
## License
|
|
414
|
+
|
|
415
|
+
MIT License - See [LICENSE](LICENSE) file for details.
|
|
416
|
+
|
|
417
|
+
**Author**: Florian BARRE
|
|
418
|
+
|