atdd 0.2.1__py3-none-any.whl → 0.2.3__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.
atdd/__main__.py CHANGED
@@ -1,4 +1,4 @@
1
- from .cli import main
1
+ from .cli import cli
2
2
 
3
3
  if __name__ == "__main__":
4
- main()
4
+ raise SystemExit(cli())
atdd/cli.py CHANGED
@@ -41,6 +41,7 @@ from atdd.coach.commands.registry import RegistryUpdater
41
41
  from atdd.coach.commands.initializer import ProjectInitializer
42
42
  from atdd.coach.commands.session import SessionManager
43
43
  from atdd.coach.commands.sync import AgentConfigSync
44
+ from atdd.version_check import print_update_notice
44
45
 
45
46
 
46
47
  class ATDDCoach:
@@ -400,5 +401,14 @@ Phase descriptions:
400
401
  return 0
401
402
 
402
403
 
404
+ def cli() -> int:
405
+ """CLI entry point with version check."""
406
+ try:
407
+ result = main()
408
+ finally:
409
+ print_update_notice()
410
+ return result
411
+
412
+
403
413
  if __name__ == "__main__":
404
- sys.exit(main())
414
+ sys.exit(cli())
atdd/version_check.py ADDED
@@ -0,0 +1,126 @@
1
+ """
2
+ Version check for ATDD CLI.
3
+
4
+ Checks PyPI for newer versions and notifies users. Uses a cached check
5
+ to avoid adding latency to every command.
6
+
7
+ Cache location: ~/.atdd/version_cache.json
8
+ Disable: Set ATDD_NO_UPDATE_CHECK=1 environment variable
9
+ """
10
+ import json
11
+ import os
12
+ import sys
13
+ import time
14
+ from pathlib import Path
15
+ from typing import Optional, Tuple
16
+ from urllib.request import urlopen
17
+ from urllib.error import URLError
18
+
19
+ from atdd import __version__
20
+
21
+ # Check once per day (86400 seconds)
22
+ CHECK_INTERVAL = 86400
23
+ CACHE_DIR = Path.home() / ".atdd"
24
+ CACHE_FILE = CACHE_DIR / "version_cache.json"
25
+ PYPI_URL = "https://pypi.org/pypi/atdd/json"
26
+
27
+
28
+ def _parse_version(version: str) -> Tuple[int, ...]:
29
+ """Parse version string into tuple for comparison."""
30
+ try:
31
+ return tuple(int(x) for x in version.split(".")[:3])
32
+ except (ValueError, AttributeError):
33
+ return (0, 0, 0)
34
+
35
+
36
+ def _is_newer(latest: str, current: str) -> bool:
37
+ """Check if latest version is newer than current."""
38
+ return _parse_version(latest) > _parse_version(current)
39
+
40
+
41
+ def _load_cache() -> dict:
42
+ """Load version cache from disk."""
43
+ try:
44
+ if CACHE_FILE.exists():
45
+ with open(CACHE_FILE) as f:
46
+ return json.load(f)
47
+ except (json.JSONDecodeError, OSError):
48
+ pass
49
+ return {}
50
+
51
+
52
+ def _save_cache(data: dict) -> None:
53
+ """Save version cache to disk."""
54
+ try:
55
+ CACHE_DIR.mkdir(parents=True, exist_ok=True)
56
+ with open(CACHE_FILE, "w") as f:
57
+ json.dump(data, f)
58
+ except OSError:
59
+ pass # Silently fail if we can't write cache
60
+
61
+
62
+ def _fetch_latest_version() -> Optional[str]:
63
+ """Fetch latest version from PyPI."""
64
+ try:
65
+ with urlopen(PYPI_URL, timeout=2) as response:
66
+ data = json.loads(response.read().decode())
67
+ return data.get("info", {}).get("version")
68
+ except (URLError, json.JSONDecodeError, OSError, TimeoutError):
69
+ return None
70
+
71
+
72
+ def check_for_updates() -> Optional[str]:
73
+ """
74
+ Check for updates if cache is stale.
75
+
76
+ Returns:
77
+ Message to display if update available, None otherwise.
78
+ """
79
+ # Respect disable flag
80
+ if os.environ.get("ATDD_NO_UPDATE_CHECK", "").lower() in ("1", "true", "yes"):
81
+ return None
82
+
83
+ # Skip if running in development (version 0.0.0)
84
+ if __version__ == "0.0.0":
85
+ return None
86
+
87
+ cache = _load_cache()
88
+ now = time.time()
89
+ last_check = cache.get("last_check", 0)
90
+ cached_latest = cache.get("latest_version")
91
+
92
+ # Check if cache is fresh
93
+ if now - last_check < CHECK_INTERVAL and cached_latest:
94
+ latest = cached_latest
95
+ else:
96
+ # Fetch from PyPI
97
+ latest = _fetch_latest_version()
98
+ if latest:
99
+ _save_cache({
100
+ "last_check": now,
101
+ "latest_version": latest,
102
+ })
103
+ elif cached_latest:
104
+ # Use cached version if fetch failed
105
+ latest = cached_latest
106
+ else:
107
+ return None
108
+
109
+ # Compare versions
110
+ if latest and _is_newer(latest, __version__):
111
+ return (
112
+ f"\nA new version of atdd is available: {__version__} → {latest}\n"
113
+ f"Run `pip install --upgrade atdd` to update."
114
+ )
115
+
116
+ return None
117
+
118
+
119
+ def print_update_notice() -> None:
120
+ """Print update notice to stderr if available."""
121
+ try:
122
+ notice = check_for_updates()
123
+ if notice:
124
+ print(notice, file=sys.stderr)
125
+ except Exception:
126
+ pass # Never fail the main command due to version check
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atdd
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: ATDD Platform - Acceptance Test Driven Development toolkit
5
5
  License: MIT
6
6
  Requires-Python: >=3.10
@@ -1,7 +1,8 @@
1
1
  atdd/__init__.py,sha256=-S8i9OahH-t9FJkPn6nprxipnjVum3rLeVsCS74T6eY,156
2
- atdd/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
3
- atdd/cli.py,sha256=POp59pszeoxh5KKJKWxkr0mrWv7-xlg1AWXhONEnipU,13149
2
+ atdd/__main__.py,sha256=B0sXDQLjFN9GowTlXo4NMWwPZPjDsrT8Frq7DnbdOD8,77
3
+ atdd/cli.py,sha256=GHMIPRR1tDVN72Tt3vmQ1dJrow-FeOw429mOHZLrS-c,13359
4
4
  atdd/conftest.py,sha256=Fj3kIhCETbj2QBCIjySBgdS3stKNRZcZzKTJr7A4LaQ,5300
5
+ atdd/version_check.py,sha256=B9MbbxO_sJrEC3fxFJhTlOIkLLTRQCDO1_8ec1KvuWY,3540
5
6
  atdd/coach/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
7
  atdd/coach/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
8
  atdd/coach/commands/add_persistence_metadata.py,sha256=xAdeO9k53CIGTBhzNQbWenuzeoWKZimDBR9xBWaA3NU,6887
@@ -176,9 +177,9 @@ atdd/tester/validators/test_red_supabase_layer_structure.py,sha256=26cnzPZAwSFy0
176
177
  atdd/tester/validators/test_telemetry_structure.py,sha256=hIUnU2WU-8PNIg9EVHe2fnUdIQKIOUm5AWEtCBUXLVk,22467
177
178
  atdd/tester/validators/test_typescript_test_naming.py,sha256=E-TyGv_GVlTfsbyuxrtv9sOWSZS_QcpH6rrJFbWoeeU,11280
178
179
  atdd/tester/validators/test_typescript_test_structure.py,sha256=eV89SD1RaKtchBZupqhnJmaruoROosf3LwB4Fwe4UJI,2612
179
- atdd-0.2.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
180
- atdd-0.2.1.dist-info/METADATA,sha256=ePniZ2i1mFTZaHz4dQrXpsxYxq0o0QWUD3K2HA5qS-o,5215
181
- atdd-0.2.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
182
- atdd-0.2.1.dist-info/entry_points.txt,sha256=z0s90AA3SVM-A2hXQiOw-V3B_ygwqdgeib4-NbKTMyQ,39
183
- atdd-0.2.1.dist-info/top_level.txt,sha256=VKkf6Uiyrm4RS6ULCGM-v8AzYN8K2yg8SMqwJLoO-xs,5
184
- atdd-0.2.1.dist-info/RECORD,,
180
+ atdd-0.2.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
181
+ atdd-0.2.3.dist-info/METADATA,sha256=7KkLUARDmdmL1PZ-9rw4JbTf7umDEsq4chYxHkJrujg,5215
182
+ atdd-0.2.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
183
+ atdd-0.2.3.dist-info/entry_points.txt,sha256=-C3yrA1WQQfN3iuGmSzPapA5cKVBEYU5Q1HUffSJTbY,38
184
+ atdd-0.2.3.dist-info/top_level.txt,sha256=VKkf6Uiyrm4RS6ULCGM-v8AzYN8K2yg8SMqwJLoO-xs,5
185
+ atdd-0.2.3.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ atdd = atdd.cli:cli
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- atdd = atdd.cli:main
File without changes