Open-AutoTools 0.0.3rc5__py3-none-any.whl → 0.0.4rc1__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.
Files changed (44) hide show
  1. autotools/autocaps/commands.py +3 -7
  2. autotools/autocaps/core.py +5 -4
  3. autotools/autoip/commands.py +6 -11
  4. autotools/autoip/core.py +151 -200
  5. autotools/autolower/commands.py +3 -7
  6. autotools/autolower/core.py +4 -3
  7. autotools/autopassword/commands.py +27 -33
  8. autotools/autopassword/core.py +32 -73
  9. autotools/autotest/__init__.py +2 -0
  10. autotools/autotest/commands.py +205 -0
  11. autotools/cli.py +123 -62
  12. autotools/utils/commands.py +13 -0
  13. autotools/utils/loading.py +14 -6
  14. autotools/utils/performance.py +392 -0
  15. autotools/utils/updates.py +30 -22
  16. autotools/utils/version.py +69 -63
  17. open_autotools-0.0.4rc1.dist-info/METADATA +103 -0
  18. open_autotools-0.0.4rc1.dist-info/RECORD +28 -0
  19. {Open_AutoTools-0.0.3rc5.dist-info → open_autotools-0.0.4rc1.dist-info}/WHEEL +1 -1
  20. {Open_AutoTools-0.0.3rc5.dist-info → open_autotools-0.0.4rc1.dist-info}/entry_points.txt +0 -3
  21. Open_AutoTools-0.0.3rc5.dist-info/METADATA +0 -317
  22. Open_AutoTools-0.0.3rc5.dist-info/RECORD +0 -44
  23. autotools/autocaps/tests/__init__.py +0 -1
  24. autotools/autocaps/tests/test_autocaps_core.py +0 -45
  25. autotools/autocaps/tests/test_autocaps_integration.py +0 -46
  26. autotools/autodownload/__init__.py +0 -0
  27. autotools/autodownload/commands.py +0 -38
  28. autotools/autodownload/core.py +0 -433
  29. autotools/autoip/tests/__init__.py +0 -1
  30. autotools/autoip/tests/test_autoip_core.py +0 -72
  31. autotools/autoip/tests/test_autoip_integration.py +0 -92
  32. autotools/autolower/tests/__init__.py +0 -1
  33. autotools/autolower/tests/test_autolower_core.py +0 -45
  34. autotools/autolower/tests/test_autolower_integration.py +0 -46
  35. autotools/autospell/__init__.py +0 -3
  36. autotools/autospell/commands.py +0 -123
  37. autotools/autospell/core.py +0 -222
  38. autotools/autotranslate/__init__.py +0 -3
  39. autotools/autotranslate/commands.py +0 -42
  40. autotools/autotranslate/core.py +0 -52
  41. autotools/test/__init__.py +0 -3
  42. autotools/test/commands.py +0 -118
  43. {Open_AutoTools-0.0.3rc5.dist-info → open_autotools-0.0.4rc1.dist-info/licenses}/LICENSE +0 -0
  44. {Open_AutoTools-0.0.3rc5.dist-info → open_autotools-0.0.4rc1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,392 @@
1
+ import os
2
+ import gc
3
+ import sys
4
+ import time
5
+ import click
6
+ import resource
7
+ import tracemalloc
8
+ from contextlib import contextmanager
9
+ from typing import Dict, List, Tuple, Optional
10
+
11
+ try:
12
+ import psutil
13
+ PSUTIL_AVAILABLE = True
14
+ except ImportError:
15
+ PSUTIL_AVAILABLE = False
16
+
17
+ # GLOBAL FLAG TO ENABLE/DISABLE PERFORMANCE METRICS
18
+ ENABLE_PERFORMANCE_METRICS = False
19
+ if os.getenv('AUTOTOOLS_DISABLE_PERF', '').lower() in ('1', 'true', 'yes'): ENABLE_PERFORMANCE_METRICS = False
20
+
21
+ # FLAG TO ENABLE/DISABLE TRACEMALLOC (CAN BE SLOW IN PRODUCTION)
22
+ # ENABLE BY DEFAULT IN TEST ENVIRONMENTS (DETECTED BY PRESENCE OF PYTEST OR TEST IN SYS.ARGV)
23
+ _ENV_TRACEMALLOC = os.getenv('AUTOTOOLS_ENABLE_TRACEMALLOC', '').lower() in ('1', 'true', 'yes')
24
+ _IS_TEST_ENV = 'pytest' in sys.modules or any('test' in arg.lower() or 'pytest' in arg.lower() for arg in sys.argv)
25
+ ENABLE_TRACEMALLOC = _ENV_TRACEMALLOC or _IS_TEST_ENV
26
+
27
+ # PERFORMANCE METRICS COLLECTOR
28
+ class PerformanceMetrics:
29
+ def __init__(self):
30
+ self.reset()
31
+ self.steps: List[Tuple[str, float]] = []
32
+ self._step_start: Optional[float] = None
33
+ self._current_step: Optional[str] = None
34
+
35
+ def reset(self):
36
+ # TIMING METRICS
37
+ self.startup_start = None
38
+ self.startup_end = None
39
+ self.command_start = None
40
+ self.command_end = None
41
+ self.process_start = None
42
+ self.process_end = None
43
+
44
+ # CPU METRICS
45
+ self.cpu_user_start = None
46
+ self.cpu_sys_start = None
47
+ self.cpu_user_end = None
48
+ self.cpu_sys_end = None
49
+
50
+ # MEMORY METRICS
51
+ self.rss_start = None
52
+ self.rss_peak = None
53
+
54
+ # ALLOCATION TRACKING
55
+ self.tracemalloc_started = False
56
+ self.alloc_start = None
57
+ self.alloc_end = None
58
+
59
+ # GARBAGE COLLECTION METRICS
60
+ self.gc_start_stats = None
61
+ self.gc_end_stats = None
62
+
63
+ # FILESYSTEM I/O METRICS
64
+ self.fs_read_start = None
65
+ self.fs_write_start = None
66
+ self.fs_read_end = None
67
+ self.fs_write_end = None
68
+ self.fs_ops_start = None
69
+ self.fs_ops_end = None
70
+
71
+ # STEP TRACKING
72
+ self.steps = []
73
+ self._step_start = None
74
+ self._current_step = None
75
+
76
+ # STARTS PROCESS-LEVEL METRICS TRACKING
77
+ def start_process(self):
78
+ self.process_start = time.perf_counter()
79
+ self._record_cpu_start()
80
+ self._record_rss_start()
81
+ self._record_fs_start()
82
+
83
+ # STARTS STARTUP PHASE TRACKING
84
+ def start_startup(self):
85
+ self.startup_start = time.perf_counter()
86
+ if tracemalloc.is_tracing() and not self.tracemalloc_started:
87
+ self.tracemalloc_started = True
88
+ elif ENABLE_TRACEMALLOC and not self.tracemalloc_started:
89
+ tracemalloc.start(1)
90
+ self.tracemalloc_started = True
91
+
92
+ if self.tracemalloc_started and tracemalloc.is_tracing():
93
+ self.alloc_start = tracemalloc.take_snapshot()
94
+ self.gc_start_stats = self._get_gc_stats()
95
+
96
+ # ENDS STARTUP PHASE TRACKING
97
+ def end_startup(self):
98
+ self.startup_end = time.perf_counter()
99
+
100
+ # STARTS COMMAND EXECUTION TRACKING
101
+ def start_command(self):
102
+ self.command_start = time.perf_counter()
103
+
104
+ # ENDS COMMAND EXECUTION TRACKING
105
+ def end_command(self):
106
+ self.command_end = time.perf_counter()
107
+
108
+ # ENDS PROCESS-LEVEL METRICS TRACKING
109
+ def end_process(self):
110
+ self.process_end = time.perf_counter()
111
+ self._record_cpu_end()
112
+ self._record_rss_end()
113
+ self._record_fs_end()
114
+ if self.tracemalloc_started and tracemalloc.is_tracing():
115
+ self.alloc_end = tracemalloc.take_snapshot()
116
+ tracemalloc.stop()
117
+ self.gc_end_stats = self._get_gc_stats()
118
+
119
+ # STARTS TRACKING A NAMED STEP
120
+ def step_start(self, name: str):
121
+ if self._current_step:
122
+ self.step_end()
123
+ self._current_step = name
124
+ self._step_start = time.perf_counter()
125
+
126
+ # ENDS TRACKING THE CURRENT STEP
127
+ def step_end(self):
128
+ if self._current_step and self._step_start:
129
+ duration = time.perf_counter() - self._step_start
130
+ self.steps.append((self._current_step, duration))
131
+ self._current_step = None
132
+ self._step_start = None
133
+
134
+ # RECORDS CPU USAGE AT START
135
+ def _record_cpu_start(self):
136
+ usage = resource.getrusage(resource.RUSAGE_SELF)
137
+ self.cpu_user_start = usage.ru_utime
138
+ self.cpu_sys_start = usage.ru_stime
139
+
140
+ # RECORDS CPU USAGE AT END
141
+ def _record_cpu_end(self):
142
+ usage = resource.getrusage(resource.RUSAGE_SELF)
143
+ self.cpu_user_end = usage.ru_utime
144
+ self.cpu_sys_end = usage.ru_stime
145
+
146
+ # RECORDS MEMORY USAGE AT START
147
+ def _record_rss_start(self):
148
+ if PSUTIL_AVAILABLE:
149
+ process = psutil.Process()
150
+ self.rss_start = process.memory_info().rss / (1024 * 1024) # MB
151
+ else:
152
+ usage = resource.getrusage(resource.RUSAGE_SELF)
153
+ self.rss_start = usage.ru_maxrss / 1024 # MB (LINUX) OR KB (MACOS)
154
+ if sys.platform == 'darwin':
155
+ self.rss_start = self.rss_start / 1024 # CONVERT KB TO MB ON MACOS
156
+
157
+ # RECORDS MEMORY USAGE AT END
158
+ def _record_rss_end(self):
159
+ if PSUTIL_AVAILABLE:
160
+ process = psutil.Process()
161
+ mem_info = process.memory_info()
162
+ self.rss_peak = mem_info.rss / (1024 * 1024) # MB
163
+
164
+ try:
165
+ if hasattr(process, 'memory_info_ex'):
166
+ mem_ext = process.memory_info_ex()
167
+ if hasattr(mem_ext, 'peak_wss'):
168
+ self.rss_peak = max(self.rss_peak, mem_ext.peak_wss / (1024 * 1024))
169
+ except Exception:
170
+ pass
171
+ else:
172
+ usage = resource.getrusage(resource.RUSAGE_SELF)
173
+ rss_current = usage.ru_maxrss / 1024
174
+ if sys.platform == 'darwin':
175
+ rss_current = rss_current / 1024
176
+ self.rss_peak = max(self.rss_start, rss_current) if self.rss_start else rss_current
177
+
178
+ # RECORDS FILESYSTEM I/O AT START
179
+ def _record_fs_start(self):
180
+ if PSUTIL_AVAILABLE:
181
+ try:
182
+ process = psutil.Process()
183
+ io_counters = process.io_counters()
184
+ self.fs_read_start = io_counters.read_bytes
185
+ self.fs_write_start = io_counters.write_bytes
186
+ self.fs_ops_start = getattr(io_counters, 'read_count', 0) + getattr(io_counters, 'write_count', 0)
187
+ except (AttributeError, psutil.AccessDenied):
188
+ self.fs_read_start = 0
189
+ self.fs_write_start = 0
190
+ self.fs_ops_start = 0
191
+ else:
192
+ self.fs_read_start = 0
193
+ self.fs_write_start = 0
194
+ self.fs_ops_start = 0
195
+
196
+ # RECORDS FILESYSTEM I/O AT END
197
+ def _record_fs_end(self):
198
+ if PSUTIL_AVAILABLE:
199
+ try:
200
+ process = psutil.Process()
201
+ io_counters = process.io_counters()
202
+ self.fs_read_end = io_counters.read_bytes
203
+ self.fs_write_end = io_counters.write_bytes
204
+ self.fs_ops_end = getattr(io_counters, 'read_count', 0) + getattr(io_counters, 'write_count', 0)
205
+ except (AttributeError, psutil.AccessDenied):
206
+ self.fs_read_end = self.fs_read_start
207
+ self.fs_write_end = self.fs_write_start
208
+ self.fs_ops_end = self.fs_ops_start
209
+ else:
210
+ self.fs_read_end = self.fs_read_start
211
+ self.fs_write_end = self.fs_write_start
212
+ self.fs_ops_end = self.fs_ops_start
213
+
214
+ # GETS CURRENT GARBAGE COLLECTION STATISTICS
215
+ def _get_gc_stats(self) -> List[Dict]:
216
+ return gc.get_stats()
217
+
218
+ # CALCULATES DURATION METRICS IN MILLISECONDS
219
+ def _calculate_durations(self) -> Tuple[float, float, float]:
220
+ total_duration_ms = (self.process_end - self.process_start) * 1000 if self.process_end and self.process_start else 0
221
+ startup_duration_ms = (self.startup_end - self.startup_start) * 1000 if self.startup_end and self.startup_start else 0
222
+ command_duration_ms = (self.command_end - self.command_start) * 1000 if self.command_end and self.command_start else 0
223
+ return total_duration_ms, startup_duration_ms, command_duration_ms
224
+
225
+ # CALCULATES CPU TIME METRICS IN MILLISECONDS
226
+ def _calculate_cpu_time(self) -> Tuple[float, float, float]:
227
+ cpu_user_ms = (self.cpu_user_end - self.cpu_user_start) * 1000 if self.cpu_user_end and self.cpu_user_start else 0
228
+ cpu_sys_ms = (self.cpu_sys_end - self.cpu_sys_start) * 1000 if self.cpu_sys_end and self.cpu_sys_start else 0
229
+ cpu_time_total_ms = cpu_user_ms + cpu_sys_ms
230
+ return cpu_time_total_ms, cpu_user_ms, cpu_sys_ms
231
+
232
+ # CALCULATES TOTAL MEMORY ALLOCATIONS IN MB
233
+ def _calculate_allocations(self) -> float:
234
+ alloc_mb_total = 0
235
+ if self.alloc_start and self.alloc_end:
236
+ diff = self.alloc_end.compare_to(self.alloc_start, 'lineno')
237
+ alloc_bytes = sum(stat.size_diff for stat in diff if stat.size_diff > 0)
238
+ alloc_mb_total = alloc_bytes / (1024 * 1024)
239
+ return alloc_mb_total
240
+
241
+ # CALCULATES GARBAGE COLLECTION METRICS
242
+ def _calculate_gc_stats(self) -> Tuple[float, int]:
243
+ gc_pause_total_ms = 0
244
+ gc_collections_count = 0
245
+ if self.gc_start_stats and self.gc_end_stats:
246
+ end_stats = gc.get_stats()
247
+ start_stats = self.gc_start_stats
248
+
249
+ if len(start_stats) == len(end_stats):
250
+ for start_stat, end_stat in zip(start_stats, end_stats):
251
+ start_collections = start_stat.get('collections', 0)
252
+ end_collections = end_stat.get('collections', 0)
253
+ gc_collections_count += max(0, end_collections - start_collections)
254
+
255
+ start_time = start_stat.get('total_time', 0)
256
+ end_time = end_stat.get('total_time', 0)
257
+ gc_pause_total_ms += max(0, (end_time - start_time) * 1000) # CONVERT TO MS
258
+
259
+ return gc_pause_total_ms, gc_collections_count
260
+
261
+ # CALCULATES FILESYSTEM I/O METRICS
262
+ def _calculate_fs_io(self) -> Tuple[int, int, int]:
263
+ fs_bytes_read_total = self.fs_read_end - self.fs_read_start if self.fs_read_end and self.fs_read_start else 0
264
+ fs_bytes_written_total = self.fs_write_end - self.fs_write_start if self.fs_write_end and self.fs_write_start else 0
265
+ fs_ops_count = self.fs_ops_end - self.fs_ops_start if self.fs_ops_end and self.fs_ops_start else 0
266
+ return fs_bytes_read_total, fs_bytes_written_total, fs_ops_count
267
+
268
+ # CALCULATES AND RETURNS ALL PERFORMANCE METRICS AS A DICTIONARY
269
+ def get_metrics(self) -> Dict:
270
+ if self._current_step: self.step_end()
271
+
272
+ # CALCULATE ALL METRICS
273
+ total_duration_ms, startup_duration_ms, command_duration_ms = self._calculate_durations()
274
+ cpu_time_total_ms, cpu_user_ms, cpu_sys_ms = self._calculate_cpu_time()
275
+ alloc_mb_total = self._calculate_allocations()
276
+ gc_pause_total_ms, gc_collections_count = self._calculate_gc_stats()
277
+ fs_bytes_read_total, fs_bytes_written_total, fs_ops_count = self._calculate_fs_io()
278
+
279
+ # MEMORY
280
+ rss_mb_peak = self.rss_peak if self.rss_peak else 0
281
+
282
+ # TOP SLOWEST STEPS
283
+ top_slowest_steps = sorted(self.steps, key=lambda x: x[1], reverse=True)[:5]
284
+ top_slowest_steps_formatted = [
285
+ {'step': name, 'duration_ms': duration * 1000}
286
+ for name, duration in top_slowest_steps
287
+ ]
288
+
289
+ return {
290
+ 'total_duration_ms': round(total_duration_ms, 2),
291
+ 'startup_duration_ms': round(startup_duration_ms, 2),
292
+ 'command_duration_ms': round(command_duration_ms, 2),
293
+ 'top_slowest_steps': top_slowest_steps_formatted,
294
+ 'cpu_time_total_ms': round(cpu_time_total_ms, 2),
295
+ 'cpu_user_ms': round(cpu_user_ms, 2),
296
+ 'cpu_sys_ms': round(cpu_sys_ms, 2),
297
+ 'rss_mb_peak': round(rss_mb_peak, 2),
298
+ 'alloc_mb_total': round(alloc_mb_total, 2),
299
+ 'gc_pause_total_ms': round(gc_pause_total_ms, 2),
300
+ 'gc_collections_count': gc_collections_count,
301
+ 'fs_bytes_read_total': fs_bytes_read_total,
302
+ 'fs_bytes_written_total': fs_bytes_written_total,
303
+ 'fs_ops_count': fs_ops_count
304
+ }
305
+
306
+ # GLOBAL METRICS INSTANCE
307
+ _metrics = PerformanceMetrics()
308
+
309
+ # CONTEXT MANAGER FOR TRACKING NAMED STEPS
310
+ @contextmanager
311
+ def track_step(name: str):
312
+ _metrics.step_start(name)
313
+ try:
314
+ yield
315
+ finally:
316
+ _metrics.step_end()
317
+
318
+ # CHECKS IF PERFORMANCE METRICS SHOULD BE ENABLED
319
+ def should_enable_metrics(ctx) -> bool:
320
+ current = ctx
321
+ while current:
322
+ if current.params.get('perf', False): return True
323
+ current = getattr(current, 'parent', None)
324
+
325
+ if not ENABLE_PERFORMANCE_METRICS: return False
326
+
327
+ module = sys.modules.get('autotools')
328
+ module_file = getattr(module, '__file__', '') or ''
329
+ is_dev = module and 'site-packages' not in module_file.lower()
330
+
331
+ return is_dev
332
+
333
+ # DISPLAYS PERFORMANCE METRICS IN A FORMATTED WAY
334
+ def display_metrics(metrics: Dict):
335
+ click.echo(click.style("\n" + "="*60, fg='cyan'))
336
+ click.echo(click.style("PERFORMANCE METRICS", fg='cyan', bold=True))
337
+ click.echo(click.style("="*60, fg='cyan'))
338
+
339
+ # DURATION METRICS
340
+ click.echo(click.style("\nDURATION METRICS:", fg='yellow', bold=True))
341
+ click.echo(f" Total Duration: {metrics['total_duration_ms']:.2f} ms")
342
+ click.echo(f" Startup Duration: {metrics['startup_duration_ms']:.2f} ms")
343
+ click.echo(f" Command Duration: {metrics['command_duration_ms']:.2f} ms")
344
+
345
+ # CPU METRICS
346
+ click.echo(click.style("\nCPU METRICS:", fg='yellow', bold=True))
347
+ click.echo(f" CPU Time Total: {metrics['cpu_time_total_ms']:.2f} ms")
348
+ click.echo(f" CPU User Time: {metrics['cpu_user_ms']:.2f} ms")
349
+ click.echo(f" CPU System Time: {metrics['cpu_sys_ms']:.2f} ms")
350
+ cpu_ratio = (metrics['cpu_time_total_ms'] / metrics['total_duration_ms'] * 100) if metrics['total_duration_ms'] > 0 else 0
351
+ click.echo(f" CPU Usage Ratio: {cpu_ratio:.1f}%")
352
+
353
+ # MEMORY METRICS
354
+ click.echo(click.style("\nMEMORY METRICS:", fg='yellow', bold=True))
355
+ click.echo(f" RSS Peak: {metrics['rss_mb_peak']:.2f} MB")
356
+ click.echo(f" Allocations Total: {metrics['alloc_mb_total']:.2f} MB")
357
+
358
+ # GC METRICS
359
+ if metrics['gc_collections_count'] > 0 or metrics['gc_pause_total_ms'] > 0:
360
+ click.echo(click.style("\nGARBAGE COLLECTION METRICS:", fg='yellow', bold=True))
361
+ click.echo(f" GC Pause Total: {metrics['gc_pause_total_ms']:.2f} ms")
362
+ click.echo(f" GC Collections: {metrics['gc_collections_count']}")
363
+
364
+ # FS I/O METRICS
365
+ if metrics['fs_bytes_read_total'] > 0 or metrics['fs_bytes_written_total'] > 0:
366
+ click.echo(click.style("\nFILESYSTEM I/O METRICS:", fg='yellow', bold=True))
367
+ click.echo(f" Bytes Read: {metrics['fs_bytes_read_total']:,} bytes ({metrics['fs_bytes_read_total'] / 1024 / 1024:.2f} MB)")
368
+ click.echo(f" Bytes Written: {metrics['fs_bytes_written_total']:,} bytes ({metrics['fs_bytes_written_total'] / 1024 / 1024:.2f} MB)")
369
+ click.echo(f" FS Operations: {metrics['fs_ops_count']:,}")
370
+
371
+ # TOP SLOWEST STEPS
372
+ if metrics['top_slowest_steps']:
373
+ click.echo(click.style("\nTOP SLOWEST STEPS:", fg='yellow', bold=True))
374
+ for i, step in enumerate(metrics['top_slowest_steps'], 1): click.echo(f" {i}. {step['step']}: {step['duration_ms']:.2f} ms")
375
+
376
+ click.echo(click.style("\n" + "="*60 + "\n", fg='cyan'))
377
+
378
+ # INITIALIZES METRICS TRACKING
379
+ def init_metrics():
380
+ _metrics.reset()
381
+ _metrics.start_process()
382
+ _metrics.start_startup()
383
+
384
+ # FINALIZES AND DISPLAYS METRICS IF ENABLED
385
+ def finalize_metrics(ctx):
386
+ if should_enable_metrics(ctx):
387
+ _metrics.end_process()
388
+ metrics = _metrics.get_metrics()
389
+ display_metrics(metrics)
390
+
391
+ # GETS THE GLOBAL METRICS INSTANCE
392
+ def get_metrics() -> PerformanceMetrics: return _metrics
@@ -1,30 +1,38 @@
1
+ import os
2
+ import json
1
3
  import click
2
- import requests
3
- from importlib.metadata import version as get_version
4
+ import urllib.request
5
+ import urllib.error
6
+ from importlib.metadata import version as get_version, distribution, PackageNotFoundError
4
7
  from packaging.version import parse as parse_version
5
- import pkg_resources
6
8
 
9
+ # CHECKS PYPI FOR AVAILABLE UPDATES TO THE PACKAGE
7
10
  def check_for_updates():
8
- """CHECK IF AN UPDATE IS AVAILABLE AND RETURN UPDATE MESSAGE IF NEEDED"""
11
+ # SKIP UPDATE CHECK IN TEST ENVIRONMENT
12
+ if os.getenv('PYTEST_CURRENT_TEST') or os.getenv('CI'): return None
13
+
9
14
  try:
10
- dist = pkg_resources.get_distribution("Open-AutoTools")
11
- current_version = parse_version(dist.version)
15
+ try:
16
+ dist = distribution("Open-AutoTools")
17
+ current_version = parse_version(dist.version)
18
+ except PackageNotFoundError:
19
+ return None
12
20
 
13
21
  pypi_url = "https://pypi.org/pypi/Open-AutoTools/json"
14
- response = requests.get(pypi_url)
15
-
16
- if response.status_code == 200:
17
- data = response.json()
18
- latest_version = data["info"]["version"]
19
- latest_parsed = parse_version(latest_version)
20
-
21
- if latest_parsed > current_version:
22
- update_cmd = "pip install --upgrade Open-AutoTools"
23
- return (
24
- click.style(f"\nUpdate available: v{latest_version}", fg='red', bold=True) + "\n" +
25
- click.style(f"Run '{update_cmd}' to update", fg='red')
26
- )
27
- except Exception as e:
28
- print(f"Error checking updates: {str(e)}")
29
-
22
+ req = urllib.request.Request(pypi_url)
23
+ with urllib.request.urlopen(req, timeout=5) as response:
24
+ if response.status == 200:
25
+ data = json.loads(response.read().decode())
26
+ latest_version = data["info"]["version"]
27
+ latest_parsed = parse_version(latest_version)
28
+
29
+ if latest_parsed > current_version:
30
+ update_cmd = "pip install --upgrade Open-AutoTools"
31
+ return (
32
+ click.style(f"\nUpdate available: v{latest_version}", fg='red', bold=True) + "\n" +
33
+ click.style(f"Run '{update_cmd}' to update", fg='red')
34
+ )
35
+ except urllib.error.URLError:
36
+ pass
37
+
30
38
  return None
@@ -1,74 +1,80 @@
1
+ import os
2
+ import sys
3
+ import json
1
4
  import click
2
- from importlib.metadata import version as get_version, PackageNotFoundError
3
- import pkg_resources
4
- import requests
5
- from packaging.version import parse as parse_version
5
+ import urllib.request
6
+ import urllib.error
6
7
  from datetime import datetime
8
+ from packaging.version import parse as parse_version
9
+ from importlib.metadata import version as get_version, PackageNotFoundError
10
+
11
+ # FORMATS AND DISPLAYS RELEASE DATE
12
+ def _format_release_date(upload_time, pkg_version):
13
+ date_formats = ["%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%d %H:%M:%S"]
14
+ for date_format in date_formats:
15
+ try:
16
+ published_date = datetime.strptime(upload_time, date_format)
17
+ formatted_date = published_date.strftime("%d %B %Y at %H:%M:%S")
18
+ prefix = "Pre-Released" if "rc" in pkg_version.lower() else "Released"
19
+ click.echo(f"{prefix}: {formatted_date}")
20
+ return True
21
+ except ValueError:
22
+ continue
23
+
24
+ return False
25
+
26
+ # DISPLAYS RELEASE INFORMATION FROM PYPI DATA
27
+ def _display_release_info(data, pkg_version):
28
+ releases = data["releases"]
29
+ if pkg_version in releases and releases[pkg_version]:
30
+ try:
31
+ upload_time = releases[pkg_version][0]["upload_time"]
32
+ _format_release_date(upload_time, pkg_version)
33
+ except Exception:
34
+ pass
35
+
36
+ # CHECKS AND DISPLAYS UPDATE INFORMATION
37
+ def _check_for_updates(current_version, latest_version):
38
+ latest_parsed = parse_version(latest_version)
39
+ if latest_parsed > current_version:
40
+ update_cmd = "pip install --upgrade Open-AutoTools"
41
+ click.echo(click.style(f"\nUpdate available: v{latest_version}", fg='red', bold=True))
42
+ click.echo(click.style(f"Run '{update_cmd}' to update", fg='red'))
43
+
44
+ # FETCHES AND PROCESSES PYPI VERSION INFORMATION
45
+ def _fetch_pypi_version_info(pkg_version):
46
+ pypi_url = "https://pypi.org/pypi/Open-AutoTools/json"
47
+ try:
48
+ req = urllib.request.Request(pypi_url)
49
+ with urllib.request.urlopen(req, timeout=5) as response:
50
+ if response.status != 200:
51
+ return
52
+
53
+ data = json.loads(response.read().decode())
54
+ latest_version = data["info"]["version"]
55
+ current_version = parse_version(pkg_version)
56
+
57
+ _display_release_info(data, pkg_version)
58
+ _check_for_updates(current_version, latest_version)
59
+ except urllib.error.URLError:
60
+ pass
61
+
62
+ # PRINTS VERSION INFORMATION AND CHECKS FOR UPDATES
63
+ def print_version(ctx, value):
64
+ if not value or ctx.resilient_parsing: return
7
65
 
8
- # VERSION CALLBACK
9
- def print_version(ctx, param, value):
10
- """PRINT VERSION AND CHECK FOR UPDATES"""
11
- if not value or ctx.resilient_parsing:
12
- return
13
-
14
66
  try:
15
- # GET CURRENT VERSION
16
67
  pkg_version = get_version('Open-AutoTools')
17
68
  click.echo(f"Open-AutoTools version {pkg_version}")
18
69
 
19
- # GET DISTRIBUTION INFO
20
- dist = pkg_resources.get_distribution("Open-AutoTools")
21
- current_version = parse_version(dist.version)
70
+ module = sys.modules.get('autotools')
71
+ module_file = getattr(module, '__file__', '') or ''
72
+ if module and 'site-packages' not in module_file.lower():
73
+ click.echo(click.style("Development mode: enabled", fg='yellow', bold=True))
22
74
 
23
- # GET LATEST VERSION FROM PYPI
24
- pypi_url = "https://pypi.org/pypi/Open-AutoTools/json"
25
- response = requests.get(pypi_url)
26
-
27
- # CHECK IF RESPONSE IS SUCCESSFUL
28
- if response.status_code == 200:
29
- data = response.json()
30
- latest_version = data["info"]["version"]
31
- releases = data["releases"]
32
-
33
- # GET RELEASE DATE
34
- if pkg_version in releases and releases[pkg_version]:
35
- try:
36
- upload_time = releases[pkg_version][0]["upload_time"]
37
- for date_format in [
38
- "%Y-%m-%dT%H:%M:%S",
39
- "%Y-%m-%dT%H:%M:%S.%fZ",
40
- "%Y-%m-%d %H:%M:%S"
41
- ]:
42
- # TRY TO PARSE DATE
43
- try:
44
- published_date = datetime.strptime(upload_time, date_format)
45
- formatted_date = published_date.strftime("%d %B %Y at %H:%M:%S")
46
- # CHECK IF VERSION IS RC
47
- if "rc" in pkg_version.lower():
48
- click.echo(f"Pre-Released: {formatted_date}")
49
- else:
50
- click.echo(f"Released: {formatted_date}")
51
- break
52
- except ValueError:
53
- continue
54
- except Exception:
55
- pass # SKIP DATE IF PARSING FAILS
56
-
57
- # CHECK FOR UPDATES
58
- latest_parsed = parse_version(latest_version)
59
-
60
- # COMPARE VERSIONS AND PRINT UPDATE MESSAGE IF NEEDED
61
- if latest_parsed > current_version:
62
- update_cmd = "pip install --upgrade Open-AutoTools"
63
- click.echo(click.style(f"\nUpdate available: v{latest_version}", fg='red', bold=True))
64
- click.echo(click.style(f"Run '{update_cmd}' to update", fg='red'))
75
+ _fetch_pypi_version_info(pkg_version)
65
76
 
66
- # EXCEPTIONS FOR ERRORS
67
- except pkg_resources.DistributionNotFound:
68
- click.echo("Package distribution not found")
69
- except PackageNotFoundError:
70
- click.echo("Open-AutoTools version information not available")
71
- except Exception as e:
72
- click.echo(f"Error checking updates: {str(e)}")
77
+ except PackageNotFoundError: click.echo("Open-AutoTools version information not available")
78
+ except Exception as e: click.echo(f"Error checking updates: {str(e)}")
73
79
 
74
80
  ctx.exit()