chutils 2.7.2__tar.gz → 2.7.3__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.2 → chutils-2.7.3}/PKG-INFO +3 -2
  2. {chutils-2.7.2 → chutils-2.7.3}/README.md +2 -1
  3. {chutils-2.7.2 → chutils-2.7.3}/pyproject.toml +1 -1
  4. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cli_utils.py +35 -3
  5. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/dev/ast_indexer.py +53 -5
  6. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/dev/models.py +5 -0
  7. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/env.py +4 -1
  8. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/logger/core.py +10 -0
  9. {chutils-2.7.2 → chutils-2.7.3}/LICENSE +0 -0
  10. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/__init__.py +0 -0
  11. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/__init__.pyi +0 -0
  12. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cache/__init__.py +0 -0
  13. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cache/base.py +0 -0
  14. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cache/decorator.py +0 -0
  15. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cache/in_memory.py +0 -0
  16. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cache/utils.py +0 -0
  17. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cli.py +0 -0
  18. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cli_booster.py +0 -0
  19. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/__init__.py +0 -0
  20. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/base.py +0 -0
  21. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/config.py +0 -0
  22. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/dev.py +0 -0
  23. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/init.py +0 -0
  24. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/paths.py +0 -0
  25. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/secrets.py +0 -0
  26. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/template.py +0 -0
  27. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/utils.py +0 -0
  28. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/validate.py +0 -0
  29. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/GEMINI.md +0 -0
  30. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/__init__.py +0 -0
  31. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/core.py +0 -0
  32. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/diagnostics.py +0 -0
  33. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/generator.py +0 -0
  34. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/getters.py +0 -0
  35. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/manager.py +0 -0
  36. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/providers.py +0 -0
  37. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/schema.py +0 -0
  38. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/utils.py +0 -0
  39. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/watcher.py +0 -0
  40. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/context.py +0 -0
  41. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/decorators.py +0 -0
  42. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/dev/__init__.py +0 -0
  43. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/exceptions.py +0 -0
  44. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/features.py +0 -0
  45. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/fs.py +0 -0
  46. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/lifecycle.py +0 -0
  47. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/logger/GEMINI.md +0 -0
  48. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/logger/__init__.py +0 -0
  49. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/logger/formatters.py +0 -0
  50. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/logger/handlers.py +0 -0
  51. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/logger/masking.py +0 -0
  52. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/secret_manager/GEMINI.md +0 -0
  53. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/secret_manager/__init__.py +0 -0
  54. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/secret_manager/core.py +0 -0
  55. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/secret_manager/providers.py +0 -0
  56. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/testing/__init__.py +0 -0
  57. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/testing/fixtures.py +0 -0
  58. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/time.py +0 -0
  59. {chutils-2.7.2 → chutils-2.7.3}/src/chutils/tracing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chutils
3
- Version: 2.7.2
3
+ Version: 2.7.3
4
4
  Summary: Набор простых и удобных утилит для Python, который избавляет от рутины при работе с конфигурацией и логированием в новых проектах.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -83,7 +83,8 @@ Every time you start a new project, you have to solve the same tasks:
83
83
  file. It uses **lazy initialization** — no heavy operations until you actually need them.
84
84
  - **⚙️ Flexible Configuration:** Support for `YAML` and `INI` formats. Simple functions for retrieving typed data.
85
85
  - **✍️ Advanced Logger:** The `setup_logger()` function configures logging to the console and rotating files out of the
86
- box. It returns a custom logger with additional debug levels (`devdebug`, `mediumdebug`).
86
+ box. Includes **automatic secret masking** and **smart console width detection** for IDEs (PyCharm, etc.).
87
+ It returns a custom logger with additional debug levels (`devdebug`, `mediumdebug`).
87
88
  - **🔒 Secure Secret Storage:** The `secret_manager` module provides a simple interface for saving and retrieving secrets
88
89
  via the system `keyring`, with a fallback to `.env` files.
89
90
  - **🚀 CLI Booster:** Turn any function into a CLI tool in seconds using the `@cli_command` decorator with automatic type
@@ -31,7 +31,8 @@ Every time you start a new project, you have to solve the same tasks:
31
31
  file. It uses **lazy initialization** — no heavy operations until you actually need them.
32
32
  - **⚙️ Flexible Configuration:** Support for `YAML` and `INI` formats. Simple functions for retrieving typed data.
33
33
  - **✍️ Advanced Logger:** The `setup_logger()` function configures logging to the console and rotating files out of the
34
- box. It returns a custom logger with additional debug levels (`devdebug`, `mediumdebug`).
34
+ box. Includes **automatic secret masking** and **smart console width detection** for IDEs (PyCharm, etc.).
35
+ It returns a custom logger with additional debug levels (`devdebug`, `mediumdebug`).
35
36
  - **🔒 Secure Secret Storage:** The `secret_manager` module provides a simple interface for saving and retrieving secrets
36
37
  via the system `keyring`, with a fallback to `.env` files.
37
38
  - **🚀 CLI Booster:** Turn any function into a CLI tool in seconds using the `@cli_command` decorator with automatic type
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "chutils"
3
- version = "2.7.2"
3
+ version = "2.7.3"
4
4
  description = "Набор простых и удобных утилит для Python, который избавляет от рутины при работе с конфигурацией и логированием в новых проектах."
5
5
  authors = [{name = "Chu4hel", email = "sergeiivanov636@gmail.com"}]
6
6
  license = "MIT"
@@ -1,6 +1,8 @@
1
+ import os
1
2
  import re
3
+ import shutil
2
4
  import sys
3
- from typing import Any
5
+ from typing import Any, Optional
4
6
 
5
7
  from .env import RICH_AVAILABLE, is_rich_enabled
6
8
 
@@ -18,6 +20,10 @@ class FallbackConsole:
18
20
  def __init__(self, stderr: bool = False):
19
21
  self._is_stderr = stderr
20
22
 
23
+ @property
24
+ def file(self):
25
+ return sys.stderr if self._is_stderr else sys.stdout
26
+
21
27
  def _strip_markup(self, text: str) -> str:
22
28
  """Удаляет простейшие теги rich типа [bold]."""
23
29
  return re.sub(r"\[/?[\w\s,=#]*\]", "", text)
@@ -56,6 +62,32 @@ class FallbackConsole:
56
62
 
57
63
  _console = None
58
64
  _err_console = None
65
+ _console_width: Optional[int] = None
66
+
67
+
68
+ def set_console_width(width: int):
69
+ """
70
+ Устанавливает ширину консоли и сбрасывает кэшированные экземпляры консолей.
71
+ """
72
+ global _console_width, _console, _err_console
73
+ _console_width = width
74
+ _console = None
75
+ _err_console = None
76
+
77
+
78
+ def _get_default_width() -> Optional[int]:
79
+ """Определяет ширину консоли по умолчанию с учетом IDE."""
80
+ if _console_width is not None:
81
+ return _console_width
82
+
83
+ # Пытаемся определить размер терминала
84
+ width, _ = shutil.get_terminal_size(fallback=(80, 24))
85
+
86
+ # Специфичное поведение для PyCharm (часто ограничивает ширину в 80 символов при запуске логов)
87
+ if os.getenv("PYCHARM_HOSTED") == "1" and width == 80:
88
+ return 140
89
+
90
+ return width
59
91
 
60
92
 
61
93
  def get_console(stderr: bool = False) -> Any:
@@ -68,7 +100,7 @@ def get_console(stderr: bool = False) -> Any:
68
100
  if _err_console is not None:
69
101
  return _err_console
70
102
  if is_rich_enabled():
71
- _err_console = Console(stderr=True)
103
+ _err_console = Console(stderr=True, width=_get_default_width())
72
104
  else:
73
105
  _err_console = FallbackConsole(stderr=True)
74
106
  return _err_console
@@ -77,7 +109,7 @@ def get_console(stderr: bool = False) -> Any:
77
109
  return _console
78
110
 
79
111
  if is_rich_enabled():
80
- _console = Console()
112
+ _console = Console(width=_get_default_width())
81
113
  else:
82
114
  _console = FallbackConsole()
83
115
  return _console
@@ -22,6 +22,8 @@ class Indexer:
22
22
  self.project_root = self.root_path
23
23
 
24
24
  self._graph_map: Dict[str, Dict[str, int]] = {} # {source: {target: weight}}
25
+ self._current_imports: Dict[str, str] = {}
26
+ """Карта импортов текущего модуля {asname: full_path}"""
25
27
  self._public_symbols = self._discover_public_api()
26
28
 
27
29
  @property
@@ -162,11 +164,13 @@ class Indexer:
162
164
  )
163
165
 
164
166
  # Анализ зависимостей
167
+ self._current_imports = {}
165
168
  if tree:
166
169
  for item in tree.body:
167
170
  if isinstance(item, ast.Import):
168
171
  for alias in item.names:
169
172
  self._record_dependency(rel_path, alias.name)
173
+ self._current_imports[alias.asname or alias.name] = alias.name
170
174
  elif isinstance(item, ast.ImportFrom):
171
175
  is_relative = item.level > 0
172
176
  base = item.module if item.module else "." * item.level
@@ -177,6 +181,9 @@ class Indexer:
177
181
  self._record_dependency(rel_path, base, force_internal=is_relative)
178
182
  continue
179
183
 
184
+ full_name = f"{base}.{alias.name}" if not is_relative else f"{base}{alias.name}"
185
+ self._current_imports[alias.asname or alias.name] = full_name
186
+
180
187
  if is_relative:
181
188
  # Для относительных импортов в тестах ожидается база ('.', '..')
182
189
  self._record_dependency(rel_path, base, force_internal=True)
@@ -224,6 +231,10 @@ class Indexer:
224
231
  ))
225
232
  return symbols
226
233
 
234
+ def _resolve_base_class(self, base_name: str) -> str:
235
+ """Разрешает имя базового класса в полный путь импорта."""
236
+ return self._current_imports.get(base_name, base_name)
237
+
227
238
  def _build_symbol(self, node: Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef], sym_type: str) -> Symbol:
228
239
  """Создает объект Symbol из узла AST."""
229
240
  docstring = ast.get_docstring(node) or ""
@@ -248,15 +259,21 @@ class Indexer:
248
259
 
249
260
  # Декораторы
250
261
  for dec in node.decorator_list:
262
+ dec_name = ""
251
263
  if isinstance(dec, ast.Name):
252
- breadcrumbs.decorators.append(dec.id)
264
+ dec_name = dec.id
253
265
  elif isinstance(dec, ast.Attribute) and isinstance(dec.value, ast.Name):
254
- breadcrumbs.decorators.append(f"{dec.value.id}.{dec.attr}")
266
+ dec_name = f"{dec.value.id}.{dec.attr}"
255
267
  elif isinstance(dec, ast.Call):
256
268
  if isinstance(dec.func, ast.Name):
257
- breadcrumbs.decorators.append(dec.func.id)
269
+ dec_name = dec.func.id
258
270
  elif isinstance(dec.func, ast.Attribute) and isinstance(dec.func.value, ast.Name):
259
- breadcrumbs.decorators.append(f"{dec.func.value.id}.{dec.func.attr}")
271
+ dec_name = f"{dec.func.value.id}.{dec.func.attr}"
272
+
273
+ if dec_name:
274
+ breadcrumbs.decorators.append(dec_name)
275
+ if dec_name == "abstractmethod" or dec_name.endswith(".abstractmethod"):
276
+ breadcrumbs.is_abstract = True
260
277
 
261
278
  # Теги из docstring (:tag:)
262
279
  tags = re.findall(r":([\w-]+):", docstring)
@@ -267,7 +284,7 @@ class Indexer:
267
284
  if "heavy" in breadcrumbs.tags:
268
285
  breadcrumbs.is_heavy = True
269
286
 
270
- return Symbol(
287
+ symbol = Symbol(
271
288
  name=node.name,
272
289
  type=sym_type,
273
290
  signature=signature,
@@ -277,3 +294,34 @@ class Indexer:
277
294
  line_number=node.lineno,
278
295
  layer=self._get_layer(node.name, docstring)
279
296
  )
297
+
298
+ if isinstance(node, ast.ClassDef):
299
+ # Извлекаем базы
300
+ for base in node.bases:
301
+ if isinstance(base, ast.Name):
302
+ symbol.bases.append(self._resolve_base_class(base.id))
303
+ elif isinstance(base, ast.Attribute):
304
+ # Случай типа pydantic.BaseModel
305
+ parts = []
306
+ curr = base
307
+ while isinstance(curr, ast.Attribute):
308
+ parts.append(curr.attr)
309
+ curr = curr.value
310
+ if isinstance(curr, ast.Name):
311
+ parts.append(curr.id)
312
+ symbol.bases.append(".".join(reversed(parts)))
313
+
314
+ # Извлекаем методы
315
+ for item in node.body:
316
+ if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
317
+ # Фильтрация: оставляем публичные, защищенные (_) и __init__.
318
+ # Отбрасываем остальные dunder-методы (__dunder__) и приватные (__private).
319
+ name = item.name
320
+ if name.startswith("__") and name != "__init__":
321
+ continue
322
+
323
+ method_symbol = self._build_symbol(item, "method")
324
+
325
+ symbol.children.append(method_symbol)
326
+
327
+ return symbol
@@ -12,6 +12,7 @@ class Breadcrumbs(BaseModel):
12
12
  is_async: bool = False
13
13
  is_thread_safe: bool = False
14
14
  is_heavy: bool = False
15
+ is_abstract: bool = False
15
16
  decorators: List[str] = Field(default_factory=list)
16
17
  tags: List[str] = Field(default_factory=list)
17
18
  custom_metadata: Dict[str, Any] = Field(default_factory=dict)
@@ -29,6 +30,10 @@ class Symbol(BaseModel):
29
30
  docstring: Optional[str] = None
30
31
  breadcrumbs: Breadcrumbs = Field(default_factory=Breadcrumbs)
31
32
  line_number: int = 0
33
+ bases: List[str] = Field(default_factory=list)
34
+ """Базовые классы (для классов)"""
35
+ children: List["Symbol"] = Field(default_factory=list)
36
+ """Вложенные символы (например, методы класса)"""
32
37
 
33
38
 
34
39
  class Node(BaseModel):
@@ -6,7 +6,10 @@ import os
6
6
 
7
7
  def _is_installed(package_name: str) -> bool:
8
8
  """Проверяет наличие пакета в системе без его импорта."""
9
- return importlib.util.find_spec(package_name) is not None
9
+ try:
10
+ return importlib.util.find_spec(package_name) is not None
11
+ except (ImportError, ModuleNotFoundError):
12
+ return False
10
13
 
11
14
 
12
15
  RICH_AVAILABLE = _is_installed("rich")
@@ -264,6 +264,16 @@ def setup_logger(
264
264
 
265
265
  final_logger_settings = {**default_settings, **specific_settings}
266
266
 
267
+ # --- Настройка ширины консоли ---
268
+ cli_settings = cfg.get('CLI', {})
269
+ config_width = cli_settings.get('console_width')
270
+ if config_width is not None:
271
+ try:
272
+ from chutils.cli_utils import set_console_width
273
+ set_console_width(int(config_width))
274
+ except (ValueError, TypeError, ImportError):
275
+ pass
276
+
267
277
  # --- Определение флага асинхронности ---
268
278
  if use_async is not None:
269
279
  final_use_async = use_async
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes