chutils 2.2.1__tar.gz → 2.4.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.
- chutils-2.4.0/PKG-INFO +418 -0
- chutils-2.4.0/README.md +396 -0
- {chutils-2.2.1 → chutils-2.4.0}/pyproject.toml +3 -2
- {chutils-2.2.1 → chutils-2.4.0}/src/chutils/__init__.py +9 -2
- {chutils-2.2.1 → chutils-2.4.0}/src/chutils/config.py +133 -9
- {chutils-2.2.1 → chutils-2.4.0}/src/chutils/logger.py +183 -68
- {chutils-2.2.1 → chutils-2.4.0}/src/chutils/secret_manager.py +43 -0
- chutils-2.2.1/PKG-INFO +0 -388
- chutils-2.2.1/README.md +0 -364
- {chutils-2.2.1 → chutils-2.4.0}/LICENSE +0 -0
- {chutils-2.2.1 → chutils-2.4.0}/src/chutils/decorators.py +0 -0
chutils-2.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chutils
|
|
3
|
+
Version: 2.4.0
|
|
4
|
+
Summary: Набор простых и удобных утилит для Python, который избавляет от рутины при работе с конфигурацией и логированием в новых проектах.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Chu4hel
|
|
8
|
+
Author-email: sergeiivanov636@gmail.com
|
|
9
|
+
Requires-Python: >=3.9, !=2.7.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*, !=3.8.*
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Dist: dotenv (>=0.9.9,<0.10.0)
|
|
18
|
+
Requires-Dist: keyring (>=25.7.0,<26.0.0)
|
|
19
|
+
Requires-Dist: python-dotenv (>=1.2.1,<2.0.0)
|
|
20
|
+
Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
[Русская версия](docs/README_RU.md)
|
|
24
|
+
|
|
25
|
+
# chutils: Stop the Routine!
|
|
26
|
+
|
|
27
|
+
[](https://opensource.org/licenses/MIT)
|
|
28
|
+
[](https://www.python.org/downloads/)
|
|
29
|
+
[](https://badge.fury.io/py/chutils)
|
|
30
|
+
[](https://Chu4hel.github.io/chutils/)
|
|
31
|
+
|
|
32
|
+
**chutils** is a set of simple utilities for Python designed to eliminate the repetitive setup of configuration,
|
|
33
|
+
logging, and secrets in your projects.
|
|
34
|
+
|
|
35
|
+
Start a new project and focus on what matters, not the routine.
|
|
36
|
+
|
|
37
|
+
Full documentation is available on [our website](https://Chu4hel.github.io/chutils/) (currently in Russian).
|
|
38
|
+
|
|
39
|
+
## The Problem
|
|
40
|
+
|
|
41
|
+
Every time you start a new project, you have to solve the same tasks:
|
|
42
|
+
|
|
43
|
+
- How to conveniently read settings from a configuration file?
|
|
44
|
+
- How to configure logging to write messages to both the console and a file with daily rotation?
|
|
45
|
+
- How to securely store API keys without hardcoding them in the code?
|
|
46
|
+
- How to make it all work "out of the box" without manually defining paths?
|
|
47
|
+
|
|
48
|
+
**chutils** offers ready-made solutions for all these problems.
|
|
49
|
+
|
|
50
|
+
## Key Features
|
|
51
|
+
|
|
52
|
+
- **✨ Zero Configuration:** The library **automatically** finds your project root and the `config.yml` or `config.ini`
|
|
53
|
+
file. If the file is not found, safe defaults are used.
|
|
54
|
+
- **⚙️ Flexible Configuration:** Support for `YAML` and `INI` formats. Simple functions for retrieving typed data.
|
|
55
|
+
- **✍️ Advanced Logger:** The `setup_logger()` function configures logging to the console and rotating files out of the
|
|
56
|
+
box. It returns a custom logger with additional debug levels (`devdebug`, `mediumdebug`).
|
|
57
|
+
- **🔒 Secure Secret Storage:** The `secret_manager` module provides a simple interface for saving and retrieving secrets
|
|
58
|
+
via the system `keyring`, with a fallback to `.env` files.
|
|
59
|
+
- **🚀 Ready to Use:** Just install and use.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
poetry add chutils
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or using pip:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install chutils
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For development, clone the repository and install in editable mode:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
git clone https://github.com/Chu4hel/chutils.git
|
|
77
|
+
cd chutils
|
|
78
|
+
pip install -e .
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Examples
|
|
82
|
+
|
|
83
|
+
In the [`/examples`](./examples/) folder, you will find ready-to-run scripts demonstrating the library's key features.
|
|
84
|
+
Each example focuses on a specific task.
|
|
85
|
+
|
|
86
|
+
## Quick Start
|
|
87
|
+
|
|
88
|
+
### 1. Working with Configuration
|
|
89
|
+
|
|
90
|
+
1. (Optional) Create a `config.yml` file in your project root. If you skip this, the library will use defaults:
|
|
91
|
+
|
|
92
|
+
```yaml
|
|
93
|
+
# config.yml
|
|
94
|
+
Database:
|
|
95
|
+
host: localhost
|
|
96
|
+
port: 5432
|
|
97
|
+
user: my_user
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
2. Get values in your code:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# main.py
|
|
104
|
+
from chutils import get_config_value, get_config_int
|
|
105
|
+
|
|
106
|
+
db_host = get_config_value("Database", "host", fallback="127.0.0.1")
|
|
107
|
+
db_port = get_config_int("Database", "port", fallback=5433)
|
|
108
|
+
|
|
109
|
+
print(f"Connecting to DB at: {db_host}:{db_port}")
|
|
110
|
+
# Output: Connecting to DB at: localhost:5432
|
|
111
|
+
```
|
|
112
|
+
`chutils` will automatically find `config.yml` and read the data.
|
|
113
|
+
|
|
114
|
+
#### Overriding Configuration with Local Files (`config.local.yml`)
|
|
115
|
+
|
|
116
|
+
You can create a local configuration file (e.g., `config.local.yml` or `config.local.ini`) next to your main file (
|
|
117
|
+
`config.yml` or `config.ini`). Values from the local file will **override** corresponding values from the main file.
|
|
118
|
+
This is useful for:
|
|
119
|
+
- Storing sensitive data that should not be committed to version control (add `config.local.yml` to `.gitignore`).
|
|
120
|
+
- Overriding settings for local development without changing the main file.
|
|
121
|
+
|
|
122
|
+
**Example:**
|
|
123
|
+
If `config.yml` contains:
|
|
124
|
+
```yaml
|
|
125
|
+
# config.yml
|
|
126
|
+
Database:
|
|
127
|
+
host: production_db.com
|
|
128
|
+
port: 5432
|
|
129
|
+
App:
|
|
130
|
+
debug: false
|
|
131
|
+
```
|
|
132
|
+
And `config.local.yml` contains:
|
|
133
|
+
```yaml
|
|
134
|
+
# config.local.yml
|
|
135
|
+
Database:
|
|
136
|
+
host: localhost
|
|
137
|
+
App:
|
|
138
|
+
debug: true
|
|
139
|
+
developer_mode: true
|
|
140
|
+
```
|
|
141
|
+
Then `get_config()` will return:
|
|
142
|
+
```yaml
|
|
143
|
+
Database:
|
|
144
|
+
host: localhost # Overridden by local file
|
|
145
|
+
port: 5432 # From main file
|
|
146
|
+
App:
|
|
147
|
+
debug: true # Overridden by local file
|
|
148
|
+
developer_mode: true # Added from local file
|
|
149
|
+
```
|
|
150
|
+
**Important:** Ensure you add `config.local.yml` (or `config.local.ini`) to your `.gitignore`.
|
|
151
|
+
|
|
152
|
+
### 2. Logging Setup
|
|
153
|
+
|
|
154
|
+
1. Add a `Logging` section to your `config.yml` (optional):
|
|
155
|
+
|
|
156
|
+
```yaml
|
|
157
|
+
# config.yml
|
|
158
|
+
Logging:
|
|
159
|
+
log_level: DEBUG
|
|
160
|
+
log_file_name: my_app.log
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
2. Use the logger:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
# main.py
|
|
167
|
+
from chutils import setup_logger, ChutilsLogger
|
|
168
|
+
|
|
169
|
+
# Configure logger. It automatically reads settings from config.
|
|
170
|
+
logger: ChutilsLogger = setup_logger()
|
|
171
|
+
|
|
172
|
+
logger.info("Application started.")
|
|
173
|
+
logger.debug("This is a debug message.")
|
|
174
|
+
# Output to console and writes to file logs/my_app.log
|
|
175
|
+
```
|
|
176
|
+
The `logs` folder will be created automatically.
|
|
177
|
+
|
|
178
|
+
You can also specify the log filename directly when calling `setup_logger`, overriding the config:
|
|
179
|
+
```python
|
|
180
|
+
# main.py
|
|
181
|
+
from chutils import setup_logger, ChutilsLogger
|
|
182
|
+
|
|
183
|
+
# Logger will write to custom.log, ignoring log_file_name from config.yml
|
|
184
|
+
logger: ChutilsLogger = setup_logger(log_file_name="custom.log")
|
|
185
|
+
|
|
186
|
+
logger.info("Message in a custom file.")
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Creating Multiple Loggers
|
|
190
|
+
|
|
191
|
+
You can create different loggers for different parts of your application by passing a unique name to `setup_logger`.
|
|
192
|
+
This helps filter and separate logs.
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
# main.py
|
|
196
|
+
from chutils import setup_logger
|
|
197
|
+
|
|
198
|
+
# Main app logger will write to main_app.log
|
|
199
|
+
main_logger = setup_logger("main_app", log_file_name="main_app.log")
|
|
200
|
+
# Logger for the database module will write to database.log
|
|
201
|
+
db_logger = setup_logger("database", log_file_name="database.log")
|
|
202
|
+
|
|
203
|
+
main_logger.info("Application started.")
|
|
204
|
+
db_logger.debug("Initializing DB connection...")
|
|
205
|
+
```
|
|
206
|
+
See [`/examples/05_different_log_levels.py`](./examples/05_different_log_levels.py) for a detailed example.
|
|
207
|
+
|
|
208
|
+
#### Configuring Multiple Loggers via File
|
|
209
|
+
|
|
210
|
+
You can centrally manage settings for different loggers using the `config_section_name` parameter.
|
|
211
|
+
|
|
212
|
+
1. **Add sections to `config.yml`**:
|
|
213
|
+
The `[Logging]` section is used for defaults. Other sections can be used for specific loggers.
|
|
214
|
+
```yaml
|
|
215
|
+
# config.yml
|
|
216
|
+
Logging:
|
|
217
|
+
log_level: INFO
|
|
218
|
+
rotation_type: time
|
|
219
|
+
compress: true
|
|
220
|
+
|
|
221
|
+
AuditLogger:
|
|
222
|
+
log_level: DEBUG
|
|
223
|
+
log_file_name: "audit.log"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
2. **Use `config_section_name` in code**:
|
|
227
|
+
```python
|
|
228
|
+
# main.py
|
|
229
|
+
from chutils import setup_logger
|
|
230
|
+
|
|
231
|
+
# This logger takes settings from [Logging]
|
|
232
|
+
main_logger = setup_logger("main")
|
|
233
|
+
main_logger.info("Message from main logger.")
|
|
234
|
+
|
|
235
|
+
# This logger takes settings from [AuditLogger], overriding defaults
|
|
236
|
+
audit_logger = setup_logger("audit", config_section_name="AuditLogger")
|
|
237
|
+
audit_logger.debug("Detailed audit message.")
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### 3. Secret Management
|
|
241
|
+
|
|
242
|
+
`SecretManager` looks for secrets in the following order:
|
|
243
|
+
|
|
244
|
+
1. **System Storage (`keyring`)**: The most secure method.
|
|
245
|
+
2. **`.env` File**: If the secret is not found in `keyring`, the manager looks in the `.env` file in the project root.
|
|
246
|
+
3. **Environment Variables**: If not found there either, it checks OS environment variables.
|
|
247
|
+
|
|
248
|
+
#### Method 1: Keyring (Recommended)
|
|
249
|
+
|
|
250
|
+
1. Initialize `SecretManager` and save your secret. **Do this once.**
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
# setup_secrets.py
|
|
254
|
+
from chutils import SecretManager
|
|
255
|
+
|
|
256
|
+
secrets = SecretManager("my_awesome_app")
|
|
257
|
+
secrets.save_secret("DB_PASSWORD", "MySuperSecretDbPassword123!")
|
|
258
|
+
print("DB password saved to system storage!")
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
2. Retrieve the secret in your main code without exposing it:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
# main.py
|
|
265
|
+
from chutils import SecretManager, get_config_value
|
|
266
|
+
|
|
267
|
+
secrets = SecretManager("my_awesome_app")
|
|
268
|
+
db_user = get_config_value("Database", "user")
|
|
269
|
+
|
|
270
|
+
# Get password from secure storage
|
|
271
|
+
db_password = secrets.get_secret("DB_PASSWORD")
|
|
272
|
+
|
|
273
|
+
if db_password:
|
|
274
|
+
print(f"Password retrieved for user {db_user}.")
|
|
275
|
+
else:
|
|
276
|
+
print("Password not found!")
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Method 2: .env File (Useful for Docker and CI/CD)
|
|
280
|
+
|
|
281
|
+
1. Create a `.env` file in your project root:
|
|
282
|
+
```dotenv
|
|
283
|
+
# .env
|
|
284
|
+
DB_PASSWORD="AnotherSecretPassword"
|
|
285
|
+
API_KEY="abcdef123456"
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
2. `SecretManager` automatically finds this file and reads variables if not found in `keyring`.
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
# main.py
|
|
292
|
+
from chutils import SecretManager
|
|
293
|
+
|
|
294
|
+
secrets = SecretManager("my_awesome_app")
|
|
295
|
+
|
|
296
|
+
# This secret will be taken from .env if not in keyring
|
|
297
|
+
api_key = secrets.get_secret("API_KEY")
|
|
298
|
+
print(f"Found API key: {api_key}")
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Comprehensive Example
|
|
302
|
+
|
|
303
|
+
This example shows how all `chutils` components work together.
|
|
304
|
+
|
|
305
|
+
1. **`config.yml`:**
|
|
306
|
+
```yaml
|
|
307
|
+
API:
|
|
308
|
+
base_url: https://api.example.com
|
|
309
|
+
|
|
310
|
+
Database:
|
|
311
|
+
host: localhost
|
|
312
|
+
port: 5432
|
|
313
|
+
user: my_user
|
|
314
|
+
|
|
315
|
+
Logging:
|
|
316
|
+
log_level: INFO
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
2. **`main.py`:**
|
|
320
|
+
```python
|
|
321
|
+
# main.py
|
|
322
|
+
from chutils import get_config_value, setup_logger, SecretManager, ChutilsLogger
|
|
323
|
+
|
|
324
|
+
# 1. Setup logger. It automatically reads settings from config.
|
|
325
|
+
logger: ChutilsLogger = setup_logger()
|
|
326
|
+
|
|
327
|
+
# 2. Initialize secret manager for our app.
|
|
328
|
+
secrets = SecretManager("my_awesome_app")
|
|
329
|
+
|
|
330
|
+
def setup_credentials():
|
|
331
|
+
"""Function to save password initially if missing."""
|
|
332
|
+
db_user = get_config_value("Database", "user")
|
|
333
|
+
password_key = f"{db_user}_password"
|
|
334
|
+
|
|
335
|
+
if not secrets.get_secret(password_key):
|
|
336
|
+
logger.info("DB password not found. Saving new one...")
|
|
337
|
+
secrets.save_secret(password_key, "MySuperSecretDbPassword123!")
|
|
338
|
+
logger.info("DB password saved to system storage.")
|
|
339
|
+
|
|
340
|
+
def connect_to_db():
|
|
341
|
+
"""Example DB connection using config and secrets."""
|
|
342
|
+
db_host = get_config_value("Database", "host")
|
|
343
|
+
db_user = get_config_value("Database", "user")
|
|
344
|
+
db_password = secrets.get_secret(f"{db_user}_password")
|
|
345
|
+
|
|
346
|
+
if not db_password:
|
|
347
|
+
logger.error("Failed to retrieve DB password!")
|
|
348
|
+
return
|
|
349
|
+
|
|
350
|
+
logger.info(f"Connecting to {db_host} as {db_user}...")
|
|
351
|
+
# ... connection logic ...
|
|
352
|
+
logger.info("Connected successfully!")
|
|
353
|
+
|
|
354
|
+
def main():
|
|
355
|
+
logger.info("App started.")
|
|
356
|
+
setup_credentials()
|
|
357
|
+
connect_to_db()
|
|
358
|
+
logger.info("App finished.")
|
|
359
|
+
|
|
360
|
+
if __name__ == "__main__":
|
|
361
|
+
main()
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## API
|
|
365
|
+
|
|
366
|
+
### Configuration (`chutils.config`)
|
|
367
|
+
|
|
368
|
+
- `get_config_value(section, key, fallback="")`: Get a value.
|
|
369
|
+
- `get_config_int(section, key, fallback=0)`: Get an integer.
|
|
370
|
+
- `get_config_boolean(section, key, fallback=False)`: Get a boolean.
|
|
371
|
+
- `get_config_list(section, key, fallback=[])`: Get a list.
|
|
372
|
+
- `get_config_section(section)`: Get the entire section as a dictionary.
|
|
373
|
+
- `save_config_value(section, key, value)`: Save a value. Works for `.yml` and `.ini`.
|
|
374
|
+
**Note**: comments and formatting are lost when saving to `.yml`. They are preserved for `.ini`.
|
|
375
|
+
|
|
376
|
+
### Logging (`chutils.logger`)
|
|
377
|
+
|
|
378
|
+
- `setup_logger(name='app_logger', log_level_str='')`: Configures and returns a `ChutilsLogger` instance.
|
|
379
|
+
- `logger.mediumdebug("message")`: Log with level 15.
|
|
380
|
+
- `logger.devdebug("message")`: Log with level 9.
|
|
381
|
+
|
|
382
|
+
### Secret Management (`chutils.secret_manager`)
|
|
383
|
+
|
|
384
|
+
- `SecretManager(service_name, prefix="Chutils_")`: Creates a manager isolated by service name.
|
|
385
|
+
- `secrets.save_secret(key, value)`: Saves a secret.
|
|
386
|
+
- `secrets.get_secret(key)`: Retrieves a secret.
|
|
387
|
+
- `secrets.delete_secret(key)`: Deletes a secret.
|
|
388
|
+
|
|
389
|
+
### Decorators (`chutils.decorators`)
|
|
390
|
+
|
|
391
|
+
- `log_function_details`: Decorator for logging function call details (arguments, execution time, result).
|
|
392
|
+
|
|
393
|
+
### Manual Initialization (`chutils.init`)
|
|
394
|
+
|
|
395
|
+
In 99% of cases, you **will not need this**. But if automation fails, you can manually specify the project path once at
|
|
396
|
+
the very beginning:
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
import chutils
|
|
400
|
+
|
|
401
|
+
chutils.init(base_dir="/path/to/my/project/root")
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Note on `secret_manager` (Keyring)
|
|
405
|
+
|
|
406
|
+
The `SecretManager` module uses the `keyring` library to securely store secrets in system storage.
|
|
407
|
+
|
|
408
|
+
- On **Windows** and **macOS**, this works "out of the box".
|
|
409
|
+
- **Linux Requirements**: Secure `keyring` operation on Linux requires an installed and configured backend (secret
|
|
410
|
+
storage), such as `GNOME Keyring` (Seahorse) or `KWallet`. On servers or minimal builds, you may need to install this
|
|
411
|
+
manually.
|
|
412
|
+
See the [official `keyring` documentation](https://keyring.readthedocs.io/en/latest/) for details.
|
|
413
|
+
- **Mobile OS**: This module is **not intended** for use on mobile operating systems (Android, iOS). `keyring` will
|
|
414
|
+
likely not find system storage and may use an **insecure** method to store your secrets.
|
|
415
|
+
|
|
416
|
+
## License
|
|
417
|
+
|
|
418
|
+
The project is distributed under the MIT License.
|