chutils 2.5.0__tar.gz → 2.6.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.
@@ -1,3 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: chutils
3
+ Version: 2.6.0
4
+ Summary: Набор простых и удобных утилит для Python, который избавляет от рутины при работе с конфигурацией и логированием в новых проектах.
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Author: Chu4hel
8
+ Author-email: sergeiivanov636@gmail.com
9
+ Requires-Python: >=3.9, !=3.9.0, !=3.9.1
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Provides-Extra: full
17
+ Provides-Extra: json
18
+ Provides-Extra: pydantic
19
+ Provides-Extra: watch
20
+ Requires-Dist: keyring (>=25.7.0)
21
+ Requires-Dist: pydantic (>=2.13.4) ; extra == "full"
22
+ Requires-Dist: pydantic (>=2.13.4,<3.0.0) ; extra == "pydantic"
23
+ Requires-Dist: python-dotenv (>=1.2.1) ; python_full_version < "3.10.0"
24
+ Requires-Dist: python-dotenv (>=1.2.2) ; python_full_version >= "3.10.0"
25
+ Requires-Dist: python-json-logger (>=3.2.1) ; extra == "full"
26
+ Requires-Dist: python-json-logger (>=3.2.1) ; extra == "json"
27
+ Requires-Dist: pyyaml (>=6.0.3)
28
+ Requires-Dist: watchdog (>=6.0.0) ; extra == "full"
29
+ Requires-Dist: watchdog (>=6.0.0) ; extra == "watch"
30
+ Description-Content-Type: text/markdown
31
+
1
32
  [Русская версия](docs/README_RU.md)
2
33
 
3
34
  # chutils: Stop the Routine!
@@ -34,9 +65,28 @@ Every time you start a new project, you have to solve the same tasks:
34
65
  box. It returns a custom logger with additional debug levels (`devdebug`, `mediumdebug`).
35
66
  - **🔒 Secure Secret Storage:** The `secret_manager` module provides a simple interface for saving and retrieving secrets
36
67
  via the system `keyring`, with a fallback to `.env` files.
68
+ - **🔄 Hot-Reload:** Support for automatic configuration reloading on file changes without restart (requires
69
+ `pip install chutils[watch]`).
37
70
  - **⚡ Async Ready:** Most core functions have asynchronous versions (prefixed with `a`) for non-blocking execution.
38
71
  - **🚀 Ready to Use:** Just install and use.
39
72
 
73
+ ## Command Line Interface (CLI)
74
+
75
+ The library provides a `chutils` console command for convenient secret management without writing code.
76
+
77
+ ### Secret Management
78
+
79
+ ```bash
80
+ # Save a secret to the system storage (keyring)
81
+ chutils secrets set MY_API_KEY "super-secret-value"
82
+
83
+ # Delete a secret
84
+ chutils secrets delete MY_API_KEY
85
+
86
+ # Explicitly specify service name
87
+ chutils secrets set DB_PASSWORD "12345" --service my_custom_app
88
+ ```
89
+
40
90
  ## Installation
41
91
 
42
92
  ```bash
@@ -76,13 +126,60 @@ Each example focuses on a specific task.
76
126
  db_port = get_config_int("Database", "port", fallback=5432)
77
127
  ```
78
128
 
129
+ #### Validation via Pydantic (Optional)
130
+
131
+ For strict typing and validation, you can use Pydantic models (requires `pip install chutils[pydantic]`):
132
+
133
+ ```python
134
+ from pydantic import BaseModel
135
+ from chutils import get_config
136
+
137
+ class AppConfig(BaseModel):
138
+ app_name: str
139
+ version: str
140
+
141
+ # Returns an instance of AppConfig
142
+ cfg = get_config(model=AppConfig)
143
+ print(cfg.app_name)
144
+ ```
145
+
146
+ You can also validate specific sections:
147
+ ```python
148
+ from chutils import get_config_section
149
+ db_cfg = get_config_section("Database", model=MyDbModel)
150
+ ```
151
+
79
152
  #### Overriding Configuration with Local Files (`config.local.yml`)
80
153
 
81
154
  You can create a `config.local.yml` next to your main file. Values from the local file will **override**
82
155
  corresponding values from the main file. This is perfect for local development or storing sensitive data (ensure
83
156
  `*.local.*` is in your `.gitignore`).
84
157
 
85
- ### 2. Logging Setup
158
+ ### 2. Hot-Reload
159
+
160
+ You can make your application react to configuration file changes in real-time.
161
+
162
+ ```python
163
+ from chutils import start_config_watcher, on_config_change, get_config_value
164
+
165
+
166
+ def reload_logic():
167
+ print("Configuration updated!")
168
+ # Update app state here
169
+ db_url = get_config_value("Database", "url")
170
+
171
+
172
+ # Register callback
173
+ on_config_change(reload_logic)
174
+
175
+ # Start watcher (requires watchdog package)
176
+ start_config_watcher()
177
+ ```
178
+
179
+ To use this feature, install `watchdog`:
180
+ `pip install chutils[watch]`
181
+
182
+ ### 3. Logging Setup
86
183
 
87
184
  1. Configure and use the logger:
88
185
 
@@ -96,8 +193,39 @@ Each example focuses on a specific task.
96
193
  logger.devdebug("Deep debug message (level 9).")
97
194
  ```
98
195
 
196
+ #### Structured Logging (JSON)
197
+
198
+ If you need to output logs in JSON format for ELK, Splunk, or cloud logging (requires `pip install chutils[json]`):
199
+
200
+ ```python
201
+ # Via code
202
+ logger = setup_logger(json_format=True)
203
+
204
+ # Or via config in the [Logging] section
205
+ # json_format: true
206
+ ```
207
+
208
+ #### Contextual Logging (ContextVar)
209
+
210
+ You can bind metadata to the current execution context (thread or coroutine), and it will be automatically
211
+ included in all log messages.
212
+
213
+ ```python
214
+ from chutils import setup_logger, bind_context
215
+
216
+ logger = setup_logger()
217
+
218
+ # Bind request ID and user to the current context
219
+ bind_context(request_id="REQ-123", user="admin")
220
+
221
+ logger.info("Action performed")
222
+ # Text: ... [request_id=REQ-123 user=admin] Action performed
223
+ # JSON: {..., "message": "Action performed", "context": {"request_id": "REQ-123", "user": "admin"}}
224
+ ```
225
+
99
226
  #### Controlling Logging via Environment Variables
100
227
 
228
+ - `CH_LOG_JSON=true`: Forces JSON format.
101
229
  - `CH_LOG_NO_TIME=true`: Removes the date/time from the log format (for clean Docker logs).
102
230
  - `CH_LOG_NO_FILE=true`: Disables creating log files.
103
231
 
@@ -132,7 +260,8 @@ In environments like Docker or CI/CD where `keyring` is unavailable, you can sup
132
260
 
133
261
  - `get_config_value(section, key, fallback)` / `aget_config()`
134
262
  - `get_config_int`, `get_config_boolean`, `get_config_list`, `get_config_path`
135
- - `save_config_value(section, key, value)` / `asave_config_value()`
263
+ - `save_config_value(section, key, value, notify=True)` / `asave_config_value()`
264
+ - Use `notify=False` to update the file without triggering Hot-Reload callbacks.
136
265
 
137
266
  ### Logging (`chutils.logger`)
138
267
 
@@ -149,7 +278,22 @@ In environments like Docker or CI/CD where `keyring` is unavailable, you can sup
149
278
  ### Decorators (`chutils.decorators`)
150
279
 
151
280
  - `@log_function_details`: Logs arguments, execution time, and result (uses `DEVDEBUG` level).
281
+ - `@timeout(seconds, fallback)`: Limits function execution time. Supports sync/async and optional fallback.
282
+ - `@retry`: Automatically retries a function if it fails. Supports sync/async, backoff, jitter, and exception filtering.
283
+
284
+ #### Example of @retry usage:
285
+
286
+ ```python
287
+ from chutils.decorators import retry
288
+
289
+
290
+ @retry(retries=3, delay=1.0, backoff=2.0)
291
+ def fetch_data():
292
+ # Will be retried up to 3 times on any Exception
293
+ ...
294
+ ```
152
295
 
153
296
  ## License
154
297
 
155
298
  The project is distributed under the MIT License.
299
+
@@ -1,25 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: chutils
3
- Version: 2.5.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: keyring (>=25.7.0,<26.0.0)
18
- Requires-Dist: python-dotenv (==1.2.1) ; python_version < "3.10"
19
- Requires-Dist: python-dotenv (>=1.2.2,<2.0.0) ; python_version >= "3.10"
20
- Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
21
- Description-Content-Type: text/markdown
22
-
23
1
  [Русская версия](docs/README_RU.md)
24
2
 
25
3
  # chutils: Stop the Routine!
@@ -56,9 +34,28 @@ Every time you start a new project, you have to solve the same tasks:
56
34
  box. It returns a custom logger with additional debug levels (`devdebug`, `mediumdebug`).
57
35
  - **🔒 Secure Secret Storage:** The `secret_manager` module provides a simple interface for saving and retrieving secrets
58
36
  via the system `keyring`, with a fallback to `.env` files.
37
+ - **🔄 Hot-Reload:** Support for automatic configuration reloading on file changes without restart (requires
38
+ `pip install chutils[watch]`).
59
39
  - **⚡ Async Ready:** Most core functions have asynchronous versions (prefixed with `a`) for non-blocking execution.
60
40
  - **🚀 Ready to Use:** Just install and use.
61
41
 
42
+ ## Command Line Interface (CLI)
43
+
44
+ The library provides a `chutils` console command for convenient secret management without writing code.
45
+
46
+ ### Secret Management
47
+
48
+ ```bash
49
+ # Save a secret to the system storage (keyring)
50
+ chutils secrets set MY_API_KEY "super-secret-value"
51
+
52
+ # Delete a secret
53
+ chutils secrets delete MY_API_KEY
54
+
55
+ # Explicitly specify service name
56
+ chutils secrets set DB_PASSWORD "12345" --service my_custom_app
57
+ ```
58
+
62
59
  ## Installation
63
60
 
64
61
  ```bash
@@ -98,13 +95,60 @@ Each example focuses on a specific task.
98
95
  db_port = get_config_int("Database", "port", fallback=5432)
99
96
  ```
100
97
 
98
+ #### Validation via Pydantic (Optional)
99
+
100
+ For strict typing and validation, you can use Pydantic models (requires `pip install chutils[pydantic]`):
101
+
102
+ ```python
103
+ from pydantic import BaseModel
104
+ from chutils import get_config
105
+
106
+ class AppConfig(BaseModel):
107
+ app_name: str
108
+ version: str
109
+
110
+ # Returns an instance of AppConfig
111
+ cfg = get_config(model=AppConfig)
112
+ print(cfg.app_name)
113
+ ```
114
+
115
+ You can also validate specific sections:
116
+ ```python
117
+ from chutils import get_config_section
118
+ db_cfg = get_config_section("Database", model=MyDbModel)
119
+ ```
120
+
101
121
  #### Overriding Configuration with Local Files (`config.local.yml`)
102
122
 
103
123
  You can create a `config.local.yml` next to your main file. Values from the local file will **override**
104
124
  corresponding values from the main file. This is perfect for local development or storing sensitive data (ensure
105
125
  `*.local.*` is in your `.gitignore`).
106
126
 
107
- ### 2. Logging Setup
127
+ ### 2. Hot-Reload
128
+
129
+ You can make your application react to configuration file changes in real-time.
130
+
131
+ ```python
132
+ from chutils import start_config_watcher, on_config_change, get_config_value
133
+
134
+
135
+ def reload_logic():
136
+ print("Configuration updated!")
137
+ # Update app state here
138
+ db_url = get_config_value("Database", "url")
139
+
140
+
141
+ # Register callback
142
+ on_config_change(reload_logic)
143
+
144
+ # Start watcher (requires watchdog package)
145
+ start_config_watcher()
146
+ ```
147
+
148
+ To use this feature, install `watchdog`:
149
+ `pip install chutils[watch]`
150
+
151
+ ### 3. Logging Setup
108
152
 
109
153
  1. Configure and use the logger:
110
154
 
@@ -118,8 +162,39 @@ Each example focuses on a specific task.
118
162
  logger.devdebug("Deep debug message (level 9).")
119
163
  ```
120
164
 
165
+ #### Structured Logging (JSON)
166
+
167
+ If you need to output logs in JSON format for ELK, Splunk, or cloud logging (requires `pip install chutils[json]`):
168
+
169
+ ```python
170
+ # Via code
171
+ logger = setup_logger(json_format=True)
172
+
173
+ # Or via config in the [Logging] section
174
+ # json_format: true
175
+ ```
176
+
177
+ #### Contextual Logging (ContextVar)
178
+
179
+ You can bind metadata to the current execution context (thread or coroutine), and it will be automatically
180
+ included in all log messages.
181
+
182
+ ```python
183
+ from chutils import setup_logger, bind_context
184
+
185
+ logger = setup_logger()
186
+
187
+ # Bind request ID and user to the current context
188
+ bind_context(request_id="REQ-123", user="admin")
189
+
190
+ logger.info("Action performed")
191
+ # Text: ... [request_id=REQ-123 user=admin] Action performed
192
+ # JSON: {..., "message": "Action performed", "context": {"request_id": "REQ-123", "user": "admin"}}
193
+ ```
194
+
121
195
  #### Controlling Logging via Environment Variables
122
196
 
197
+ - `CH_LOG_JSON=true`: Forces JSON format.
123
198
  - `CH_LOG_NO_TIME=true`: Removes the date/time from the log format (for clean Docker logs).
124
199
  - `CH_LOG_NO_FILE=true`: Disables creating log files.
125
200
 
@@ -154,7 +229,8 @@ In environments like Docker or CI/CD where `keyring` is unavailable, you can sup
154
229
 
155
230
  - `get_config_value(section, key, fallback)` / `aget_config()`
156
231
  - `get_config_int`, `get_config_boolean`, `get_config_list`, `get_config_path`
157
- - `save_config_value(section, key, value)` / `asave_config_value()`
232
+ - `save_config_value(section, key, value, notify=True)` / `asave_config_value()`
233
+ - Use `notify=False` to update the file without triggering Hot-Reload callbacks.
158
234
 
159
235
  ### Logging (`chutils.logger`)
160
236
 
@@ -171,8 +247,21 @@ In environments like Docker or CI/CD where `keyring` is unavailable, you can sup
171
247
  ### Decorators (`chutils.decorators`)
172
248
 
173
249
  - `@log_function_details`: Logs arguments, execution time, and result (uses `DEVDEBUG` level).
250
+ - `@timeout(seconds, fallback)`: Limits function execution time. Supports sync/async and optional fallback.
251
+ - `@retry`: Automatically retries a function if it fails. Supports sync/async, backoff, jitter, and exception filtering.
252
+
253
+ #### Example of @retry usage:
254
+
255
+ ```python
256
+ from chutils.decorators import retry
257
+
258
+
259
+ @retry(retries=3, delay=1.0, backoff=2.0)
260
+ def fetch_data():
261
+ # Will be retried up to 3 times on any Exception
262
+ ...
263
+ ```
174
264
 
175
265
  ## License
176
266
 
177
267
  The project is distributed under the MIT License.
178
-
@@ -0,0 +1,70 @@
1
+ [project]
2
+ name = "chutils"
3
+ version = "2.6.0"
4
+ description = "Набор простых и удобных утилит для Python, который избавляет от рутины при работе с конфигурацией и логированием в новых проектах."
5
+ authors = [{name = "Chu4hel", email = "sergeiivanov636@gmail.com"}]
6
+ license = "MIT"
7
+ readme = "README.md"
8
+ requires-python = ">=3.9, !=3.9.0, !=3.9.1"
9
+ dependencies = [
10
+ "keyring (>=25.7.0)",
11
+ "pyyaml (>=6.0.3)",
12
+ "python-dotenv (>=1.2.1) ; python_full_version < '3.10'",
13
+ "python-dotenv (>=1.2.2) ; python_full_version >= '3.10'"
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ pydantic = ["pydantic (>=2.13.4,<3.0.0)"]
18
+ json = ["python-json-logger (>=3.2.1)"]
19
+ watch = ["watchdog (>=6.0.0)"]
20
+ full = [
21
+ "pydantic (>=2.13.4)",
22
+ "python-json-logger (>=3.2.1)",
23
+ "watchdog (>=6.0.0)"
24
+ ]
25
+
26
+ [project.scripts]
27
+ chutils = "chutils.cli:main"
28
+
29
+ [tool.poetry]
30
+ packages = [{ include = "chutils", from = "src" }]
31
+ exclude = [
32
+ "tests/",
33
+ "site/",
34
+ "examples/",
35
+ "docs/",
36
+ ".github/",
37
+ ".idea/",
38
+ "__pycache__/",
39
+ "*.pyc",
40
+ "*.log",
41
+ "*.egg-info/",
42
+ ".vscode/",
43
+ ".ruff_cache/",
44
+ "coverage.xml",
45
+ ".coverage",
46
+ "PUBLISHING.md",
47
+ "project.txt",
48
+ "changelog.txt",
49
+ ]
50
+
51
+ [tool.poetry.group.dev.dependencies]
52
+ pytest = { version = "^9.0.3", python = ">=3.10" }
53
+ pytest-mock = "^3.15.1"
54
+ pyfakefs = { version = "^6.2.0", python = ">=3.10" }
55
+ mkdocs = "^1.6.1"
56
+ mkdocs-material = "^9.7.5"
57
+ mkdocstrings = { version = "^1.0.4", python = ">=3.10" }
58
+ mkdocstrings-python = { version = "^2.0.3", python = ">=3.10" }
59
+ pytest-cov = "^7.1.0"
60
+ pytest-asyncio = { version = "^1.3.0", python = ">=3.10" }
61
+ ruff = "^0.15.12"
62
+
63
+ [build-system]
64
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
65
+ build-backend = "poetry.core.masonry.api"
66
+
67
+ [tool.pytest.ini_options]
68
+ pythonpath = [
69
+ "src"
70
+ ]
@@ -36,10 +36,6 @@ import os
36
36
 
37
37
  from . import config
38
38
  from . import logger
39
-
40
- # --- Импорт публичных функций и классов ---
41
- # Явно импортируем все, что должно быть доступно пользователю напрямую из пакета chutils.
42
-
43
39
  from .config import (
44
40
  get_config,
45
41
  get_config_value,
@@ -50,11 +46,20 @@ from .config import (
50
46
  get_config_section,
51
47
  get_config_path,
52
48
  aget_config,
53
- asave_config_value
49
+ save_config_value,
50
+ asave_config_value,
51
+ start_config_watcher,
52
+ stop_config_watcher,
53
+ on_config_change,
54
+ )
55
+ from .context import bind_context, unbind_context, clear_context
56
+ from .decorators import log_function_details, retry, timeout
57
+ from .logger import (
58
+ setup_logger,
59
+ ChutilsLogger,
60
+ SafeTimedRotatingFileHandler
54
61
  )
55
- from .logger import setup_logger, ChutilsLogger, SafeTimedRotatingFileHandler
56
62
  from .secret_manager import SecretManager
57
- from .decorators import log_function_details
58
63
 
59
64
 
60
65
  def init(base_dir: str):
@@ -74,10 +79,9 @@ def init(base_dir: str):
74
79
  if not os.path.isdir(base_dir):
75
80
  raise ValueError(f"Указанная директория base_dir не существует или не является директорией: {base_dir}")
76
81
 
77
- # Вручную устанавливаем базовую директорию. Модуль config сам найдет
78
- # нужный файл (yml или ini) при первом обращении.
79
- config._BASE_DIR = base_dir
80
- config._paths_initialized = True
82
+ # Вручную устанавливаем базовую директорию через менеджер состояний.
83
+ config._cm.base_dir = base_dir
84
+ config._cm.paths_initialized = True
81
85
 
82
86
  print(f"Пакет chutils вручную инициализирован с базовой директорией: {base_dir}")
83
87
 
@@ -99,16 +103,25 @@ __all__ = [
99
103
  'get_config_section',
100
104
  'get_config_path',
101
105
  'aget_config',
106
+ "save_config_value",
102
107
  'asave_config_value',
108
+ 'start_config_watcher',
109
+ 'stop_config_watcher',
110
+ 'on_config_change',
103
111
 
104
112
  # Функции и классы из модуля logger
105
113
  'setup_logger',
106
114
  'ChutilsLogger',
107
115
  'SafeTimedRotatingFileHandler',
116
+ 'bind_context',
117
+ 'unbind_context',
118
+ 'clear_context',
108
119
 
109
120
  # Классы из модуля secret_manager
110
121
  'SecretManager',
111
122
 
112
123
  # Декораторы
113
124
  'log_function_details',
125
+ 'retry',
126
+ 'timeout',
114
127
  ]
@@ -0,0 +1,71 @@
1
+ import argparse
2
+ import sys
3
+
4
+ from chutils import config
5
+ from chutils.secret_manager import SecretManager
6
+
7
+
8
+ def handle_secrets_set(args: argparse.Namespace):
9
+ """Обработчик команды сохранения секрета."""
10
+ service_name = args.service or config.get_config_value("Secrets", "service_name", "")
11
+ sm = SecretManager(service_name)
12
+
13
+ if sm.save_secret(args.key, args.value):
14
+ print(f"[OK] Секрет '{args.key}' успешно сохранен в системном хранилище.")
15
+ else:
16
+ print(f"[ERROR] Не удалось сохранить секрет '{args.key}'. Проверьте доступность keyring.")
17
+ sys.exit(1)
18
+
19
+
20
+ def handle_secrets_delete(args: argparse.Namespace):
21
+ """Обработчик команды удаления секрета."""
22
+ service_name = args.service or config.get_config_value("Secrets", "service_name", "")
23
+ sm = SecretManager(service_name)
24
+
25
+ if sm.delete_secret(args.key):
26
+ print(f"[OK] Секрет '{args.key}' успешно удален.")
27
+ else:
28
+ print(f"[ERROR] Не удалось удалить секрет '{args.key}' или он не существовал.")
29
+ sys.exit(1)
30
+
31
+
32
+ def main():
33
+ """Точка входа в CLI."""
34
+ parser = argparse.ArgumentParser(
35
+ prog="chutils",
36
+ description="Набор утилит chutils для командной строки."
37
+ )
38
+ subparsers = parser.add_subparsers(dest="command", help="Команды")
39
+
40
+ # Секция секретов
41
+ secrets_parser = subparsers.add_parser("secrets", help="Управление секретами")
42
+ secrets_subparsers = secrets_parser.add_subparsers(dest="subcommand", help="Действия с секретами")
43
+
44
+ # secrets set <key> <value>
45
+ set_parser = secrets_subparsers.add_parser("set", help="Сохранить секрет")
46
+ set_parser.add_argument("key", help="Имя ключа")
47
+ set_parser.add_argument("value", help="Значение секрета")
48
+ set_parser.add_argument("-s", "--service", help="Имя сервиса (service_name) для keyring")
49
+
50
+ # secrets delete <key>
51
+ delete_parser = secrets_subparsers.add_parser("delete", help="Удалить секрет")
52
+ delete_parser.add_argument("key", help="Имя ключа")
53
+ delete_parser.add_argument("-s", "--service", help="Имя сервиса (service_name) для keyring")
54
+
55
+ args = parser.parse_args()
56
+
57
+ if args.command == "secrets":
58
+ if args.subcommand == "set":
59
+ handle_secrets_set(args)
60
+ elif args.subcommand == "delete":
61
+ handle_secrets_delete(args)
62
+ else:
63
+ secrets_parser.print_help()
64
+ else:
65
+ parser.print_help()
66
+
67
+ sys.exit(0)
68
+
69
+
70
+ if __name__ == "__main__":
71
+ main()