chutils 2.7.3__tar.gz → 2.7.4__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.7.3 → chutils-2.7.4}/PKG-INFO +1 -1
- {chutils-2.7.3 → chutils-2.7.4}/pyproject.toml +1 -1
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/core.py +43 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/getters.py +25 -23
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/providers.py +1 -0
- {chutils-2.7.3 → chutils-2.7.4}/LICENSE +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/README.md +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/__init__.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/__init__.pyi +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cache/__init__.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cache/base.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cache/decorator.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cache/in_memory.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cache/utils.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cli.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cli_booster.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cli_utils.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/__init__.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/base.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/config.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/dev.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/init.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/paths.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/secrets.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/template.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/utils.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/validate.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/GEMINI.md +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/__init__.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/diagnostics.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/generator.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/manager.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/schema.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/utils.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/watcher.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/context.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/decorators.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/dev/__init__.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/dev/ast_indexer.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/dev/models.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/env.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/exceptions.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/features.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/fs.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/lifecycle.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/logger/GEMINI.md +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/logger/__init__.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/logger/core.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/logger/formatters.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/logger/handlers.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/logger/masking.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/secret_manager/GEMINI.md +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/secret_manager/__init__.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/secret_manager/core.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/secret_manager/providers.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/testing/__init__.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/testing/fixtures.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/time.py +0 -0
- {chutils-2.7.3 → chutils-2.7.4}/src/chutils/tracing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "chutils"
|
|
3
|
-
version = "2.7.
|
|
3
|
+
version = "2.7.4"
|
|
4
4
|
description = "Набор простых и удобных утилит для Python, который избавляет от рутины при работе с конфигурацией и логированием в новых проектах."
|
|
5
5
|
authors = [{name = "Chu4hel", email = "sergeiivanov636@gmail.com"}]
|
|
6
6
|
license = "MIT"
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import logging
|
|
10
|
+
import os
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import Any, Optional, Dict, TYPE_CHECKING, TypeVar, Type, Union, Tuple
|
|
12
13
|
|
|
@@ -131,6 +132,48 @@ def get_config(
|
|
|
131
132
|
except Exception as e:
|
|
132
133
|
logger.error("Ошибка загрузки удаленной конфигурации с %s: %s", remote_url, e)
|
|
133
134
|
|
|
135
|
+
# 5. Переменные окружения (CH_SECTION_KEY)
|
|
136
|
+
disable_env_override = os.getenv("CH_DISABLE_ENV_OVERRIDE", "").lower() in ("true", "1", "yes", "y")
|
|
137
|
+
if not disable_env_override:
|
|
138
|
+
env_overrides = {}
|
|
139
|
+
for env_key, env_value in os.environ.items():
|
|
140
|
+
if env_key.startswith("CH_") and env_key not in ("CH_ENV", "CH_DISABLE_ENV_OVERRIDE",
|
|
141
|
+
"CH_DISABLE_KEYRING_WARNING"):
|
|
142
|
+
parts = env_key[3:].split('_', 1)
|
|
143
|
+
if len(parts) == 2:
|
|
144
|
+
section, key = parts
|
|
145
|
+
section_lower = section.lower()
|
|
146
|
+
key_lower = key.lower()
|
|
147
|
+
|
|
148
|
+
# Попытка найти существующий регистр секции
|
|
149
|
+
actual_sec = section_lower
|
|
150
|
+
for existing_sec in config_data.keys():
|
|
151
|
+
if existing_sec.lower() == section_lower:
|
|
152
|
+
actual_sec = existing_sec
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
# Попытка найти существующий регистр ключа
|
|
156
|
+
actual_key = key_lower
|
|
157
|
+
if actual_sec in config_data and isinstance(config_data[actual_sec], dict):
|
|
158
|
+
for existing_key in config_data[actual_sec].keys():
|
|
159
|
+
if existing_key.lower() == key_lower:
|
|
160
|
+
actual_key = existing_key
|
|
161
|
+
break
|
|
162
|
+
|
|
163
|
+
if actual_sec not in env_overrides:
|
|
164
|
+
env_overrides[actual_sec] = {}
|
|
165
|
+
env_overrides[actual_sec][actual_key] = env_value
|
|
166
|
+
|
|
167
|
+
# Специфический ключ для secrets
|
|
168
|
+
secrets_env = os.getenv("CH_DISABLE_KEYRING_WARNING")
|
|
169
|
+
if secrets_env is not None:
|
|
170
|
+
if "secrets" not in env_overrides:
|
|
171
|
+
env_overrides["secrets"] = {}
|
|
172
|
+
env_overrides["secrets"]["disable_keyring"] = secrets_env
|
|
173
|
+
|
|
174
|
+
if env_overrides:
|
|
175
|
+
utils.deep_merge(config_data, env_overrides)
|
|
176
|
+
|
|
134
177
|
# Записываем переменные окружения в трассировку
|
|
135
178
|
if _cm.tracing_enabled:
|
|
136
179
|
_cm.trace_env_vars()
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
-
import os
|
|
10
9
|
from pathlib import Path
|
|
11
10
|
from typing import Any, Optional, List, Dict, TYPE_CHECKING, TypeVar, Type, overload, Union
|
|
12
11
|
|
|
@@ -29,9 +28,7 @@ def get_config_value(section: str, key: str, fallback: Any = None, config: Optio
|
|
|
29
28
|
Получает произвольное значение из конфигурации.
|
|
30
29
|
|
|
31
30
|
Если значение не найдено или оно пустое, возвращает `fallback`.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Также поддерживает универсальное переопределение через переменные окружения
|
|
31
|
+
Поддерживает универсальное переопределение через переменные окружения
|
|
35
32
|
по шаблону `CH_[SECTION]_[KEY]`, если не установлено `CH_DISABLE_ENV_OVERRIDE=true`.
|
|
36
33
|
|
|
37
34
|
Args:
|
|
@@ -43,27 +40,24 @@ def get_config_value(section: str, key: str, fallback: Any = None, config: Optio
|
|
|
43
40
|
Returns:
|
|
44
41
|
Значение из конфигурации или `fallback`.
|
|
45
42
|
"""
|
|
46
|
-
# 1. Проверка глобального флага отключения переопределения через ENV
|
|
47
|
-
disable_env_override = os.getenv("CH_DISABLE_ENV_OVERRIDE", "").lower() in ("true", "1", "yes", "y")
|
|
48
|
-
|
|
49
|
-
if not disable_env_override:
|
|
50
|
-
# 2. Проверка универсального переопределения CH_[SECTION]_[KEY]
|
|
51
|
-
# Используем верхний регистр для поиска в ENV согласно спецификации
|
|
52
|
-
env_key = f"CH_{section.upper()}_{key.upper()}"
|
|
53
|
-
env_value = os.getenv(env_key)
|
|
54
|
-
if env_value is not None:
|
|
55
|
-
return env_value
|
|
56
|
-
|
|
57
|
-
# Проверка переменных окружения для специфических ключей (FR3: приоритет над конфигом)
|
|
58
|
-
if section == "secrets" and key == "disable_keyring":
|
|
59
|
-
env_val = os.getenv("CH_DISABLE_KEYRING_WARNING")
|
|
60
|
-
if env_val is not None:
|
|
61
|
-
return env_val
|
|
62
|
-
|
|
63
43
|
if config is None:
|
|
64
44
|
config = get_config()
|
|
65
45
|
|
|
66
|
-
|
|
46
|
+
section_data = config.get(section)
|
|
47
|
+
if section_data is None:
|
|
48
|
+
for k, v in config.items():
|
|
49
|
+
if k.lower() == section.lower():
|
|
50
|
+
section_data = v
|
|
51
|
+
break
|
|
52
|
+
else:
|
|
53
|
+
section_data = {}
|
|
54
|
+
|
|
55
|
+
value = section_data.get(key)
|
|
56
|
+
if value is None:
|
|
57
|
+
for k, v in section_data.items():
|
|
58
|
+
if k.lower() == key.lower():
|
|
59
|
+
value = v
|
|
60
|
+
break
|
|
67
61
|
|
|
68
62
|
# Если значение не найдено или является пустой строкой, возвращаем fallback
|
|
69
63
|
if value is None or value == '':
|
|
@@ -211,7 +205,15 @@ def get_config_section(
|
|
|
211
205
|
if config is None:
|
|
212
206
|
config = get_config()
|
|
213
207
|
|
|
214
|
-
section_data = config.get(section_name
|
|
208
|
+
section_data = config.get(section_name)
|
|
209
|
+
if section_data is None:
|
|
210
|
+
# Case-insensitive fallback
|
|
211
|
+
for k, v in config.items():
|
|
212
|
+
if k.lower() == section_name.lower():
|
|
213
|
+
section_data = v
|
|
214
|
+
break
|
|
215
|
+
else:
|
|
216
|
+
section_data = fallback if fallback is not None else {}
|
|
215
217
|
|
|
216
218
|
if model is not None:
|
|
217
219
|
if not utils._check_pydantic():
|
|
@@ -166,6 +166,7 @@ class IniConfigProvider(ConfigProvider):
|
|
|
166
166
|
try:
|
|
167
167
|
with open(path, 'r', encoding='utf-8') as f:
|
|
168
168
|
parser = configparser.ConfigParser()
|
|
169
|
+
parser.optionxform = str # Сохраняем регистр ключей
|
|
169
170
|
parser.read_string(f.read())
|
|
170
171
|
flat_ini_config = {s: dict(parser.items(s)) for s in parser.sections()}
|
|
171
172
|
return self._nest_func(flat_ini_config)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|