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.
- {chutils-2.7.2 → chutils-2.7.3}/PKG-INFO +3 -2
- {chutils-2.7.2 → chutils-2.7.3}/README.md +2 -1
- {chutils-2.7.2 → chutils-2.7.3}/pyproject.toml +1 -1
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cli_utils.py +35 -3
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/dev/ast_indexer.py +53 -5
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/dev/models.py +5 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/env.py +4 -1
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/logger/core.py +10 -0
- {chutils-2.7.2 → chutils-2.7.3}/LICENSE +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/__init__.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/__init__.pyi +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cache/__init__.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cache/base.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cache/decorator.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cache/in_memory.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cache/utils.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cli.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/cli_booster.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/__init__.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/base.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/config.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/dev.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/init.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/paths.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/secrets.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/template.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/utils.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/commands/validate.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/GEMINI.md +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/__init__.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/core.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/diagnostics.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/generator.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/getters.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/manager.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/providers.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/schema.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/utils.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/config/watcher.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/context.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/decorators.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/dev/__init__.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/exceptions.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/features.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/fs.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/lifecycle.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/logger/GEMINI.md +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/logger/__init__.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/logger/formatters.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/logger/handlers.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/logger/masking.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/secret_manager/GEMINI.md +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/secret_manager/__init__.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/secret_manager/core.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/secret_manager/providers.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/testing/__init__.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/testing/fixtures.py +0 -0
- {chutils-2.7.2 → chutils-2.7.3}/src/chutils/time.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
264
|
+
dec_name = dec.id
|
|
253
265
|
elif isinstance(dec, ast.Attribute) and isinstance(dec.value, ast.Name):
|
|
254
|
-
|
|
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
|
-
|
|
269
|
+
dec_name = dec.func.id
|
|
258
270
|
elif isinstance(dec.func, ast.Attribute) and isinstance(dec.func.value, ast.Name):
|
|
259
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|