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.
Files changed (59) hide show
  1. {chutils-2.7.3 → chutils-2.7.4}/PKG-INFO +1 -1
  2. {chutils-2.7.3 → chutils-2.7.4}/pyproject.toml +1 -1
  3. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/core.py +43 -0
  4. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/getters.py +25 -23
  5. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/providers.py +1 -0
  6. {chutils-2.7.3 → chutils-2.7.4}/LICENSE +0 -0
  7. {chutils-2.7.3 → chutils-2.7.4}/README.md +0 -0
  8. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/__init__.py +0 -0
  9. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/__init__.pyi +0 -0
  10. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cache/__init__.py +0 -0
  11. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cache/base.py +0 -0
  12. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cache/decorator.py +0 -0
  13. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cache/in_memory.py +0 -0
  14. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cache/utils.py +0 -0
  15. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cli.py +0 -0
  16. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cli_booster.py +0 -0
  17. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/cli_utils.py +0 -0
  18. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/__init__.py +0 -0
  19. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/base.py +0 -0
  20. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/config.py +0 -0
  21. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/dev.py +0 -0
  22. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/init.py +0 -0
  23. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/paths.py +0 -0
  24. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/secrets.py +0 -0
  25. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/template.py +0 -0
  26. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/utils.py +0 -0
  27. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/commands/validate.py +0 -0
  28. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/GEMINI.md +0 -0
  29. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/__init__.py +0 -0
  30. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/diagnostics.py +0 -0
  31. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/generator.py +0 -0
  32. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/manager.py +0 -0
  33. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/schema.py +0 -0
  34. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/utils.py +0 -0
  35. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/config/watcher.py +0 -0
  36. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/context.py +0 -0
  37. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/decorators.py +0 -0
  38. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/dev/__init__.py +0 -0
  39. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/dev/ast_indexer.py +0 -0
  40. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/dev/models.py +0 -0
  41. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/env.py +0 -0
  42. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/exceptions.py +0 -0
  43. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/features.py +0 -0
  44. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/fs.py +0 -0
  45. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/lifecycle.py +0 -0
  46. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/logger/GEMINI.md +0 -0
  47. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/logger/__init__.py +0 -0
  48. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/logger/core.py +0 -0
  49. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/logger/formatters.py +0 -0
  50. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/logger/handlers.py +0 -0
  51. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/logger/masking.py +0 -0
  52. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/secret_manager/GEMINI.md +0 -0
  53. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/secret_manager/__init__.py +0 -0
  54. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/secret_manager/core.py +0 -0
  55. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/secret_manager/providers.py +0 -0
  56. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/testing/__init__.py +0 -0
  57. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/testing/fixtures.py +0 -0
  58. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/time.py +0 -0
  59. {chutils-2.7.3 → chutils-2.7.4}/src/chutils/tracing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chutils
3
- Version: 2.7.3
3
+ Version: 2.7.4
4
4
  Summary: Набор простых и удобных утилит для Python, который избавляет от рутины при работе с конфигурацией и логированием в новых проектах.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "chutils"
3
- version = "2.7.3"
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
- Для ключа `disable_keyring` в секции `secrets` проверяет переменную окружения.
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
- value = config.get(section, {}).get(key)
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, fallback if fallback is not None else {})
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