chutils 2.6.0__tar.gz → 2.6.1__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chutils
3
- Version: 2.6.0
3
+ Version: 2.6.1
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.6.0"
3
+ version = "2.6.1"
4
4
  description = "Набор простых и удобных утилит для Python, который избавляет от рутины при работе с конфигурацией и логированием в новых проектах."
5
5
  authors = [{name = "Chu4hel", email = "sergeiivanov636@gmail.com"}]
6
6
  license = "MIT"
@@ -23,7 +23,7 @@ import os
23
23
  import time
24
24
  import warnings
25
25
  from pathlib import Path
26
- from typing import Any, Optional, List, Dict, TYPE_CHECKING, TypeVar, Type, overload, Union
26
+ from typing import Any, Optional, List, Dict, TYPE_CHECKING, TypeVar, Type, overload, Union, Tuple
27
27
 
28
28
  from .manager import _cm
29
29
  from .providers import get_providers
@@ -69,25 +69,30 @@ def __getattr__(name: str) -> Any:
69
69
  в новый менеджер состояния с выводом предупреждения об устаревании.
70
70
  """
71
71
  remap = {
72
- '_BASE_DIR': 'base_dir',
73
- '_CONFIG_FILE_PATH': 'config_file_path',
74
- '_paths_initialized': 'paths_initialized',
75
- '_config_object': 'config_object',
76
- '_config_loaded': 'config_loaded'
72
+ '_BASE_DIR': ('base_dir', 'get_base_dir()'),
73
+ '_CONFIG_FILE_PATH': ('config_file_path', 'get_config_file_path()'),
74
+ '_paths_initialized': ('paths_initialized', 'are_paths_initialized()'),
75
+ '_config_object': ('config_object', 'get_config()'),
76
+ '_config_loaded': ('config_loaded', 'is_config_loaded()'),
77
+ '_get_config_paths': ('get_config_paths', 'get_config_paths()')
77
78
  }
79
+ "Словарь соответствия: имя -> (атрибут в _cm, рекомендуемая публичная замена)"
78
80
 
79
81
  if name in remap:
80
- warnings.warn(
81
- f"Прямое обращение к 'chutils.config.{name}' устарело и будет удалено в будущих версиях. "
82
- f"Используйте публичный API модуля.",
83
- DeprecationWarning,
84
- stacklevel=2
85
- )
82
+ cm_attr, suggestion = remap[name]
83
+ msg = f"Прямое обращение к 'chutils.config.{name}' устарело и будет удалено в будущих версиях."
84
+ if suggestion:
85
+ msg += f" Используйте {suggestion}."
86
+ else:
87
+ msg += " Используйте публичный API модуля."
88
+
89
+ warnings.warn(msg, DeprecationWarning, stacklevel=2)
90
+
86
91
  # Если пути еще не инициализированы, инициализируем их при первом обращении
87
- if name in ['_BASE_DIR', '_CONFIG_FILE_PATH'] and not _cm.paths_initialized:
92
+ if name in ['_BASE_DIR', '_CONFIG_FILE_PATH', '_get_config_paths'] and not _cm.paths_initialized:
88
93
  _cm.initialize_paths(find_project_root)
89
94
 
90
- return getattr(_cm, remap[name])
95
+ return getattr(_cm, cm_attr)
91
96
 
92
97
  raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
93
98
 
@@ -158,6 +163,31 @@ def is_config_loaded() -> bool:
158
163
  return _cm.config_loaded
159
164
 
160
165
 
166
+ def are_paths_initialized() -> bool:
167
+ """
168
+ Проверяет, были ли инициализированы пути к проекту и файлам конфигурации.
169
+
170
+ Returns:
171
+ True, если пути определены.
172
+ """
173
+ return _cm.paths_initialized
174
+
175
+
176
+ def get_config_paths(cfg_file: Optional[str] = None) -> Tuple[Optional[str], Optional[str]]:
177
+ """
178
+ Возвращает пути к основному и локальному файлам конфигурации.
179
+
180
+ Args:
181
+ cfg_file: Опциональный путь к основному файлу.
182
+
183
+ Returns:
184
+ Кортеж (путь_к_основному, путь_к_локальному).
185
+ """
186
+ if not _cm.paths_initialized:
187
+ _cm.initialize_paths(find_project_root)
188
+ return _cm.get_config_paths(cfg_file)
189
+
190
+
161
191
  def _check_pydantic():
162
192
  """Проверяет наличие установленного пакета pydantic."""
163
193
  try:
@@ -597,6 +627,21 @@ def get_config_path(
597
627
 
598
628
  # Если путь относительный, _BASE_DIR определен и resolve_from_root включен, объединяем их
599
629
  if resolve_from_root and not path_obj.is_absolute() and base_dir:
600
- return str(Path(base_dir) / path_obj)
630
+ # Безопасное разрешение пути с проверкой на выход за пределы корня проекта (Path Traversal)
631
+ try:
632
+ base_dir_obj = Path(base_dir).resolve()
633
+ resolved_path = (base_dir_obj / path_obj).resolve()
634
+
635
+ # Проверяем, что итоговый путь находится внутри базовой директории
636
+ if not str(resolved_path).startswith(str(base_dir_obj)):
637
+ _get_logger().warning(
638
+ "Обнаружена попытка выхода за пределы корня проекта (Path Traversal). "
639
+ "Путь '%s' отклонен. Возвращено значение по умолчанию.", path_str
640
+ )
641
+ return fallback
642
+ return str(resolved_path)
643
+ except Exception as e:
644
+ _get_logger().error("Ошибка при разрешении пути '%s': %s", path_str, e)
645
+ return fallback
601
646
 
602
647
  return path_str
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes