nginx-lens 0.3.4__py3-none-any.whl → 0.5.0__py3-none-any.whl

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.
@@ -0,0 +1,216 @@
1
+ """
2
+ Модуль для кэширования результатов DNS резолвинга.
3
+ """
4
+ import os
5
+ import json
6
+ import time
7
+ import hashlib
8
+ from typing import Optional, List, Dict
9
+ from pathlib import Path
10
+
11
+
12
+ class DNSCache:
13
+ """
14
+ Кэш для результатов DNS резолвинга.
15
+ """
16
+
17
+ def __init__(self, cache_dir: Optional[str] = None, ttl: int = 300):
18
+ """
19
+ Инициализация кэша.
20
+
21
+ Args:
22
+ cache_dir: Директория для хранения кэша (по умолчанию ~/.cache/nginx-lens/)
23
+ ttl: Время жизни кэша в секундах (по умолчанию 5 минут)
24
+ """
25
+ self.ttl = ttl
26
+
27
+ if cache_dir:
28
+ self.cache_dir = Path(cache_dir)
29
+ else:
30
+ # Используем ~/.cache/nginx-lens/ или /tmp/nginx-lens-cache/
31
+ home_cache = Path.home() / ".cache" / "nginx-lens"
32
+ tmp_cache = Path("/tmp") / "nginx-lens-cache"
33
+
34
+ # Пробуем использовать домашнюю директорию, если доступна
35
+ try:
36
+ home_cache.mkdir(parents=True, exist_ok=True)
37
+ self.cache_dir = home_cache
38
+ except (OSError, PermissionError):
39
+ # Fallback на /tmp
40
+ try:
41
+ tmp_cache.mkdir(parents=True, exist_ok=True)
42
+ self.cache_dir = tmp_cache
43
+ except (OSError, PermissionError):
44
+ # Последний fallback - текущая директория
45
+ self.cache_dir = Path.cwd() / ".nginx-lens-cache"
46
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
47
+
48
+ self.cache_file = self.cache_dir / "dns_cache.json"
49
+ self._cache: Dict[str, Dict] = {}
50
+ self._load_cache()
51
+
52
+ def _get_cache_key(self, host: str, port: str) -> str:
53
+ """
54
+ Генерирует ключ кэша для host:port.
55
+
56
+ Args:
57
+ host: Имя хоста
58
+ port: Порт
59
+
60
+ Returns:
61
+ Хеш ключ для кэша
62
+ """
63
+ key = f"{host}:{port}"
64
+ return hashlib.md5(key.encode()).hexdigest()
65
+
66
+ def _load_cache(self):
67
+ """Загружает кэш из файла."""
68
+ if self.cache_file.exists():
69
+ try:
70
+ with open(self.cache_file, 'r') as f:
71
+ self._cache = json.load(f)
72
+ except (json.JSONDecodeError, IOError):
73
+ self._cache = {}
74
+
75
+ def _save_cache(self):
76
+ """Сохраняет кэш в файл."""
77
+ try:
78
+ with open(self.cache_file, 'w') as f:
79
+ json.dump(self._cache, f)
80
+ except IOError:
81
+ # Игнорируем ошибки записи
82
+ pass
83
+
84
+ def get(self, host: str, port: str) -> Optional[List[str]]:
85
+ """
86
+ Получает результат из кэша.
87
+
88
+ Args:
89
+ host: Имя хоста
90
+ port: Порт
91
+
92
+ Returns:
93
+ Список резолвленных IP-адресов или None, если нет в кэше или истек TTL
94
+ """
95
+ key = self._get_cache_key(host, port)
96
+
97
+ if key not in self._cache:
98
+ return None
99
+
100
+ cached_data = self._cache[key]
101
+ cached_time = cached_data.get('timestamp', 0)
102
+ current_time = time.time()
103
+
104
+ # Проверяем TTL
105
+ if current_time - cached_time > self.ttl:
106
+ # Удаляем устаревшую запись
107
+ del self._cache[key]
108
+ self._save_cache()
109
+ return None
110
+
111
+ return cached_data.get('result')
112
+
113
+ def set(self, host: str, port: str, result: List[str]):
114
+ """
115
+ Сохраняет результат в кэш.
116
+
117
+ Args:
118
+ host: Имя хоста
119
+ port: Порт
120
+ result: Список резолвленных IP-адресов
121
+ """
122
+ key = self._get_cache_key(host, port)
123
+
124
+ self._cache[key] = {
125
+ 'timestamp': time.time(),
126
+ 'result': result,
127
+ 'host': host,
128
+ 'port': port
129
+ }
130
+
131
+ self._save_cache()
132
+
133
+ def clear(self):
134
+ """Очищает весь кэш."""
135
+ self._cache = {}
136
+ if self.cache_file.exists():
137
+ try:
138
+ self.cache_file.unlink()
139
+ except IOError:
140
+ pass
141
+
142
+ def get_cache_info(self) -> Dict[str, any]:
143
+ """
144
+ Возвращает информацию о кэше.
145
+
146
+ Returns:
147
+ Словарь с информацией о кэше
148
+ """
149
+ current_time = time.time()
150
+ valid_entries = 0
151
+ expired_entries = 0
152
+
153
+ for key, data in self._cache.items():
154
+ cached_time = data.get('timestamp', 0)
155
+ if current_time - cached_time <= self.ttl:
156
+ valid_entries += 1
157
+ else:
158
+ expired_entries += 1
159
+
160
+ return {
161
+ 'total_entries': len(self._cache),
162
+ 'valid_entries': valid_entries,
163
+ 'expired_entries': expired_entries,
164
+ 'cache_dir': str(self.cache_dir),
165
+ 'ttl': self.ttl
166
+ }
167
+
168
+
169
+ # Глобальный экземпляр кэша (будет инициализирован при первом использовании)
170
+ _cache_instance: Optional[DNSCache] = None
171
+ _cache_enabled = True
172
+
173
+
174
+ def get_cache(ttl: int = 300, cache_dir: Optional[str] = None) -> DNSCache:
175
+ """
176
+ Получает глобальный экземпляр кэша.
177
+
178
+ Args:
179
+ ttl: Время жизни кэша в секундах
180
+ cache_dir: Директория для кэша
181
+
182
+ Returns:
183
+ Экземпляр DNSCache
184
+ """
185
+ global _cache_instance
186
+ if _cache_instance is None:
187
+ _cache_instance = DNSCache(cache_dir=cache_dir, ttl=ttl)
188
+ elif ttl != _cache_instance.ttl:
189
+ # Обновляем TTL если изменился
190
+ _cache_instance.ttl = ttl
191
+ return _cache_instance
192
+
193
+
194
+ def clear_cache():
195
+ """Очищает глобальный кэш."""
196
+ global _cache_instance
197
+ if _cache_instance:
198
+ _cache_instance.clear()
199
+
200
+
201
+ def disable_cache():
202
+ """Отключает кэширование."""
203
+ global _cache_enabled
204
+ _cache_enabled = False
205
+
206
+
207
+ def enable_cache():
208
+ """Включает кэширование."""
209
+ global _cache_enabled
210
+ _cache_enabled = True
211
+
212
+
213
+ def is_cache_enabled() -> bool:
214
+ """Проверяет, включено ли кэширование."""
215
+ return _cache_enabled
216
+
utils/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """
2
+ Утилиты для nginx-lens.
3
+ """
4
+
utils/progress.py ADDED
@@ -0,0 +1,120 @@
1
+ """
2
+ Утилиты для отображения прогресса операций.
3
+ """
4
+ from typing import Optional, Callable, Any
5
+ from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, TimeElapsedColumn, TimeRemainingColumn, TaskID
6
+ from rich.console import Console
7
+ import signal
8
+ import sys
9
+
10
+ console = Console()
11
+
12
+
13
+ class ProgressManager:
14
+ """
15
+ Менеджер для отображения прогресса операций с поддержкой отмены.
16
+ """
17
+
18
+ def __init__(self, description: str = "Обработка", show_progress: bool = True):
19
+ """
20
+ Инициализация менеджера прогресса.
21
+
22
+ Args:
23
+ description: Описание операции
24
+ show_progress: Показывать ли прогресс-бар
25
+ """
26
+ self.description = description
27
+ self.show_progress = show_progress
28
+ self.progress: Optional[Progress] = None
29
+ self.task_id: Optional[TaskID] = None
30
+ self.cancelled = False
31
+
32
+ # Обработка сигнала прерывания
33
+ self._original_sigint = signal.signal(signal.SIGINT, self._handle_interrupt)
34
+
35
+ def _handle_interrupt(self, signum, frame):
36
+ """Обработка прерывания (Ctrl+C)."""
37
+ self.cancelled = True
38
+ if self.progress:
39
+ self.progress.stop()
40
+ console.print("\n[yellow]Операция отменена пользователем[/yellow]")
41
+ sys.exit(130) # Стандартный exit code для SIGINT
42
+
43
+ def __enter__(self):
44
+ """Вход в контекстный менеджер."""
45
+ if self.show_progress:
46
+ self.progress = Progress(
47
+ SpinnerColumn(),
48
+ TextColumn("[progress.description]{task.description}"),
49
+ BarColumn(),
50
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
51
+ TimeElapsedColumn(),
52
+ TimeRemainingColumn(),
53
+ console=console,
54
+ transient=False,
55
+ )
56
+ self.progress.start()
57
+ self.task_id = self.progress.add_task(self.description, total=None)
58
+ return self
59
+
60
+ def __exit__(self, exc_type, exc_val, exc_tb):
61
+ """Выход из контекстного менеджера."""
62
+ if self.progress:
63
+ self.progress.stop()
64
+ # Восстанавливаем оригинальный обработчик сигнала
65
+ signal.signal(signal.SIGINT, self._original_sigint)
66
+ return False
67
+
68
+ def update(self, completed: int, total: Optional[int] = None, description: Optional[str] = None):
69
+ """
70
+ Обновляет прогресс.
71
+
72
+ Args:
73
+ completed: Количество завершенных задач
74
+ total: Общее количество задач (если None, используется спиннер)
75
+ description: Описание текущей операции
76
+ """
77
+ if self.progress and self.task_id is not None:
78
+ if total is not None:
79
+ self.progress.update(self.task_id, total=total, completed=completed)
80
+ if description:
81
+ self.progress.update(self.task_id, description=description)
82
+
83
+ def advance(self, advance: int = 1):
84
+ """
85
+ Увеличивает прогресс на указанное значение.
86
+
87
+ Args:
88
+ advance: На сколько увеличить прогресс
89
+ """
90
+ if self.progress and self.task_id is not None:
91
+ self.progress.advance(self.task_id, advance)
92
+
93
+
94
+ def with_progress(
95
+ description: str,
96
+ total: Optional[int] = None,
97
+ show_progress: bool = True
98
+ ) -> Callable:
99
+ """
100
+ Декоратор для добавления прогресс-бара к функции.
101
+
102
+ Args:
103
+ description: Описание операции
104
+ total: Общее количество итераций (если известно)
105
+ show_progress: Показывать ли прогресс-бар
106
+
107
+ Returns:
108
+ Декорированная функция
109
+ """
110
+ def decorator(func: Callable) -> Callable:
111
+ def wrapper(*args, **kwargs):
112
+ with ProgressManager(description=description, show_progress=show_progress) as pm:
113
+ # Передаем менеджер прогресса в функцию
114
+ kwargs['progress_manager'] = pm
115
+ if total is not None:
116
+ pm.update(0, total=total)
117
+ return func(*args, **kwargs)
118
+ return wrapper
119
+ return decorator
120
+
@@ -1,38 +0,0 @@
1
- analyzer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- analyzer/base.py,sha256=oGKg78BfMVmuzYafc08oq9p31-jEgYolGjLkUcIdkN8,607
3
- analyzer/conflicts.py,sha256=NSNZc8e2x51K41dflSUvuwlDq-rzBXU5ITi6WfxFbfU,2796
4
- analyzer/dead_locations.py,sha256=uvMu5qBGTVi0Nn960x3WpRvTljGbQuVFivU4nfe36oY,1435
5
- analyzer/diff.py,sha256=idvXnoLzBVUYgKi_s3uDu0v2GNMV3B8aDqTROXcdQdo,1749
6
- analyzer/duplicates.py,sha256=jpy_6k-BzWxaXFt2Wb3rlulIXUEzbFe9xYRm7rWR50U,1215
7
- analyzer/empty_blocks.py,sha256=7Zu4-5I5PS3bjhH0Ppq1CvM7rMTeRIc4fHx5n5vkMIw,517
8
- analyzer/include.py,sha256=FhKR4VsogLknykjLD2N8jX9OtwxZcWik5oPpvp-_luE,2465
9
- analyzer/rewrite.py,sha256=-jSLLG1jqmGU-dXWvU6NHCW6muB8Lfro6fXX1tDCHCQ,1834
10
- analyzer/route.py,sha256=71dkmQaTrHqDTf4Up5gAsrgmgktNpXqWmxr7-0RAVtg,2370
11
- analyzer/unused.py,sha256=Ixzv0bPsw9IafblVwLiAOgugdg2dGu1MJDtuoqzPZiY,1066
12
- analyzer/warnings.py,sha256=zC36QMvegA2eQPvZ-P1eysrX_kXHx5A1MUKHKKNvG5c,5784
13
- commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- commands/analyze.py,sha256=W6begSgXNjgKJGoGeguR3WKgHPLkClWXxxpDcqvsJdc,8343
15
- commands/cli.py,sha256=brzp6xDDWIrm7ibaoT4x94hgAdBB2DVWniXoK8dRylE,782
16
- commands/diff.py,sha256=C7gRIWh6DNWHzjiQBPVTn-rZ40m2KCY75Zd6Q4URJIE,2076
17
- commands/graph.py,sha256=xB6KjXBkLmm5gII3e-5BMRGO7WeTwc7EFxRGzYgnme4,5947
18
- commands/health.py,sha256=lxrvuD-ClMLpTJ7gs7kn50fXwA4uBlHMPWA1ZxdEtAE,4167
19
- commands/include.py,sha256=5PTYG5C00-AlWfIgpQXLq9E7C9yTFSv7HrZkM5ogDps,2224
20
- commands/logs.py,sha256=RkPUdIpbO9dOVL56lemreYRuAjMjcqqMxRCKOFv2gC4,3691
21
- commands/resolve.py,sha256=MRruIH46tIelUyyrdrF70ai-tluuEJ13Jcj2nyRCSPA,2820
22
- commands/route.py,sha256=-x_71u6ENl3iO-oxK3bdE8v5eZKf4xRCydeUyXMFVrY,3163
23
- commands/syntax.py,sha256=ZWFdaL8LVv9S694wlk2aV3HJKb0OSKjw3wNgTlNvFR8,3418
24
- commands/tree.py,sha256=mDfx0Aeg1EDQSYQoJ2nJIkSd_uP7ZR7pEqy7Cw3clQ0,2161
25
- exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- exporter/graph.py,sha256=WYUrqUgCaK6KihgxAcRHaQn4oMo6b7ybC8yb_36ZIsA,3995
27
- exporter/html.py,sha256=uquEM-WvBt2aV9GshgaI3UVhYd8sD0QQ-OmuNtvYUdU,798
28
- exporter/markdown.py,sha256=_0mXQIhurGEZ0dO-eq9DbsuKNrgEDIblgtL3DAgYNo8,724
29
- nginx_lens-0.3.4.dist-info/licenses/LICENSE,sha256=g8QXKdvZZC56rU8E12vIeYF6R4jeTWOsblOnYAda3K4,1073
30
- parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- parser/nginx_parser.py,sha256=Sa9FtGAkvTqNzoehBvgLUWPJHLLIZYWH9ugSHW50X8s,3699
32
- upstream_checker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- upstream_checker/checker.py,sha256=i3L6XqUHUH5hcyLq5PXx6wOyjzEL_Z7xYCA3FffFOrU,11257
34
- nginx_lens-0.3.4.dist-info/METADATA,sha256=E7ce-3zNXvNVp7-y_TvlnmtCTIjMhid2sdqzbrpgy8U,552
35
- nginx_lens-0.3.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
36
- nginx_lens-0.3.4.dist-info/entry_points.txt,sha256=qEcecjSyLqcJjbIVlNlTpqAhPqDyaujUV5ZcBTAr3po,48
37
- nginx_lens-0.3.4.dist-info/top_level.txt,sha256=mxLJO4rZg0rbixVGhplF3fUNFs8vxDIL25ronZNvRy4,51
38
- nginx_lens-0.3.4.dist-info/RECORD,,