sourcecode 1.36.1__py3-none-any.whl → 1.36.2__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.
- sourcecode/__init__.py +1 -1
- sourcecode/cli.py +10 -1
- sourcecode/version_check.py +149 -0
- {sourcecode-1.36.1.dist-info → sourcecode-1.36.2.dist-info}/METADATA +1 -1
- {sourcecode-1.36.1.dist-info → sourcecode-1.36.2.dist-info}/RECORD +8 -7
- {sourcecode-1.36.1.dist-info → sourcecode-1.36.2.dist-info}/WHEEL +0 -0
- {sourcecode-1.36.1.dist-info → sourcecode-1.36.2.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.36.1.dist-info → sourcecode-1.36.2.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -6212,4 +6212,13 @@ def main_entry() -> None:
|
|
|
6212
6212
|
except Exception:
|
|
6213
6213
|
pass
|
|
6214
6214
|
_preprocess_argv()
|
|
6215
|
-
|
|
6215
|
+
try:
|
|
6216
|
+
app()
|
|
6217
|
+
finally:
|
|
6218
|
+
# Best-effort "new version available" nudge. Only speaks on an
|
|
6219
|
+
# interactive terminal; never blocks, raises, or affects exit status.
|
|
6220
|
+
try:
|
|
6221
|
+
from sourcecode.version_check import maybe_notify_update
|
|
6222
|
+
maybe_notify_update(__version__)
|
|
6223
|
+
except Exception:
|
|
6224
|
+
pass
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Best-effort "new version available" nudge.
|
|
2
|
+
|
|
3
|
+
Prints a single stderr line when a newer release exists on PyPI. Designed to be
|
|
4
|
+
invisible unless it has something useful to say:
|
|
5
|
+
|
|
6
|
+
* Only runs in an interactive terminal (stderr.isatty()) — never pollutes
|
|
7
|
+
piped output, MCP stdio, CI logs, or test runners.
|
|
8
|
+
* Hits the network at most once per 24h (cached in
|
|
9
|
+
~/.sourcecode/version_check.json); warm runs read the cache and are instant.
|
|
10
|
+
* Re-shows the same nudge at most ~once per 20h so it informs without nagging.
|
|
11
|
+
* Swallows every error and never blocks meaningfully (1.5s network timeout).
|
|
12
|
+
|
|
13
|
+
Disable entirely with SOURCECODE_NO_UPDATE_CHECK=1 (also off under SOURCECODE_CI).
|
|
14
|
+
The check reads PyPI only; it never touches the license in ~/.sourcecode/license.json.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from datetime import datetime, timezone
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Optional
|
|
24
|
+
|
|
25
|
+
_CACHE_DIR = Path.home() / ".sourcecode"
|
|
26
|
+
_CACHE_FILE = _CACHE_DIR / "version_check.json"
|
|
27
|
+
_PYPI_URL = "https://pypi.org/pypi/sourcecode/json"
|
|
28
|
+
_CHECK_TTL_SECONDS = 86_400 # refresh the PyPI lookup at most once per 24h
|
|
29
|
+
_NOTIFY_TTL_SECONDS = 72_000 # re-show the nudge at most every ~20h
|
|
30
|
+
_FETCH_TIMEOUT = 1.5
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _disabled() -> bool:
|
|
34
|
+
"""True when the nudge must stay silent (opt-out, CI, or non-interactive)."""
|
|
35
|
+
if os.environ.get("SOURCECODE_NO_UPDATE_CHECK"):
|
|
36
|
+
return True
|
|
37
|
+
if os.environ.get("SOURCECODE_CI"):
|
|
38
|
+
return True
|
|
39
|
+
try:
|
|
40
|
+
return not sys.stderr.isatty()
|
|
41
|
+
except Exception:
|
|
42
|
+
return True # no usable stderr -> stay silent
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _read_cache() -> dict:
|
|
46
|
+
try:
|
|
47
|
+
if _CACHE_FILE.exists():
|
|
48
|
+
return json.loads(_CACHE_FILE.read_text(encoding="utf-8"))
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
return {}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _write_cache(data: dict) -> None:
|
|
55
|
+
try:
|
|
56
|
+
_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
tmp = _CACHE_FILE.with_suffix(".tmp")
|
|
58
|
+
tmp.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
|
|
59
|
+
tmp.replace(_CACHE_FILE)
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _age_seconds(iso: Optional[str]) -> float:
|
|
65
|
+
if not iso:
|
|
66
|
+
return float("inf")
|
|
67
|
+
try:
|
|
68
|
+
ts = datetime.fromisoformat(iso)
|
|
69
|
+
if ts.tzinfo is None:
|
|
70
|
+
ts = ts.replace(tzinfo=timezone.utc)
|
|
71
|
+
return (datetime.now(timezone.utc) - ts).total_seconds()
|
|
72
|
+
except Exception:
|
|
73
|
+
return float("inf")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _fetch_latest() -> Optional[str]:
|
|
77
|
+
import urllib.request
|
|
78
|
+
try:
|
|
79
|
+
req = urllib.request.Request(_PYPI_URL, headers={"Accept": "application/json"})
|
|
80
|
+
with urllib.request.urlopen(req, timeout=_FETCH_TIMEOUT) as resp:
|
|
81
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
82
|
+
return ((data.get("info") or {}).get("version")) or None
|
|
83
|
+
except Exception:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _parse(v: str) -> tuple:
|
|
88
|
+
"""Lenient dotted-numeric parse for the fallback path (no packaging dep)."""
|
|
89
|
+
parts = []
|
|
90
|
+
for chunk in str(v).split("."):
|
|
91
|
+
num = ""
|
|
92
|
+
for ch in chunk:
|
|
93
|
+
if ch.isdigit():
|
|
94
|
+
num += ch
|
|
95
|
+
else:
|
|
96
|
+
break
|
|
97
|
+
parts.append(int(num) if num else 0)
|
|
98
|
+
return tuple(parts)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _is_newer(latest: str, current: str) -> bool:
|
|
102
|
+
try:
|
|
103
|
+
from packaging.version import parse as _vparse # type: ignore
|
|
104
|
+
return _vparse(latest) > _vparse(current)
|
|
105
|
+
except Exception:
|
|
106
|
+
return _parse(latest) > _parse(current)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def maybe_notify_update(current_version: str) -> None:
|
|
110
|
+
"""Print an upgrade nudge to stderr if PyPI has a newer release.
|
|
111
|
+
|
|
112
|
+
Best-effort and fully guarded: any failure is silently ignored. Safe to call
|
|
113
|
+
unconditionally from the CLI entry point.
|
|
114
|
+
"""
|
|
115
|
+
if _disabled():
|
|
116
|
+
return
|
|
117
|
+
try:
|
|
118
|
+
cache = _read_cache()
|
|
119
|
+
|
|
120
|
+
# Refresh the cached "latest" at most once per TTL (the only network hit).
|
|
121
|
+
if _age_seconds(cache.get("checked_at")) >= _CHECK_TTL_SECONDS:
|
|
122
|
+
latest = _fetch_latest()
|
|
123
|
+
if latest:
|
|
124
|
+
cache["latest"] = latest
|
|
125
|
+
cache["checked_at"] = datetime.now(timezone.utc).isoformat()
|
|
126
|
+
_write_cache(cache)
|
|
127
|
+
|
|
128
|
+
latest = cache.get("latest")
|
|
129
|
+
if not latest or not _is_newer(latest, current_version):
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
# Throttle: don't nag for the same version more than once per ~20h.
|
|
133
|
+
if (
|
|
134
|
+
cache.get("notified_for") == latest
|
|
135
|
+
and _age_seconds(cache.get("notified_at")) < _NOTIFY_TTL_SECONDS
|
|
136
|
+
):
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
sys.stderr.write(
|
|
140
|
+
f"\n[sourcecode] v{latest} is available (you have {current_version}). "
|
|
141
|
+
"Upgrade: pipx upgrade sourcecode (pip: pip install -U sourcecode)\n"
|
|
142
|
+
)
|
|
143
|
+
sys.stderr.flush()
|
|
144
|
+
|
|
145
|
+
cache["notified_for"] = latest
|
|
146
|
+
cache["notified_at"] = datetime.now(timezone.utc).isoformat()
|
|
147
|
+
_write_cache(cache)
|
|
148
|
+
except Exception:
|
|
149
|
+
pass
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=OwsI0itjMvrYqTYz2jWyNvxs5j-e5JKVwJ4h7_kCDEQ,103
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=liCwQmLgb5vplohy8arjYxs_HOIv5C9MjLh_gY6bc5Q,44115
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
@@ -7,7 +7,7 @@ sourcecode/cache.py,sha256=1V3vsaODAa2UBJAC0xpvxpmRdriCezQx5Q8JCcfgziE,31892
|
|
|
7
7
|
sourcecode/canonical_ir.py,sha256=c_lYTVoegg-1W2dZ34_2s3tN8L0GVx7eiDRh9ghdSD8,24178
|
|
8
8
|
sourcecode/cir_graphs.py,sha256=rZi8JV4ZrAa2WSCeyNa4JIEKQ_yZzDZTsrvVz2KfuKA,8919
|
|
9
9
|
sourcecode/classifier.py,sha256=hKzg-nQ47htqqIUzSGvYxv15cXrA3KgICTwJmdqal0o,8095
|
|
10
|
-
sourcecode/cli.py,sha256=
|
|
10
|
+
sourcecode/cli.py,sha256=oNcQu-l2maZ9Pb2fZiY4JTCojFi0C156TLGTrJBKT34,252665
|
|
11
11
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
12
12
|
sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
|
|
13
13
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
@@ -58,6 +58,7 @@ sourcecode/spring_semantic.py,sha256=O1nKSGVzlukuxLHQVuCPxc-XrcrMFxwlHA20_dmEGgM
|
|
|
58
58
|
sourcecode/spring_tx_analyzer.py,sha256=FdFcyqPp3aT9oJ-PKrnXcTA6s69wdvzG-NBm0GMGPTU,30717
|
|
59
59
|
sourcecode/summarizer.py,sha256=zgdps7yS2IktAbWe7IWz0oUcr3QIuNPRGrsScbZ4R1g,21797
|
|
60
60
|
sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
|
|
61
|
+
sourcecode/version_check.py,sha256=CHp6ZxTIfo8kyHPCBgJA1uFC0xQCoXMuuOfrW8QTL8o,4942
|
|
61
62
|
sourcecode/workspace.py,sha256=X_6NmNnitvT3_38V-JDChydo_sR68s249hLFlrQskU0,8271
|
|
62
63
|
sourcecode/detectors/__init__.py,sha256=A0AACJFF6HWf_RgatNtWu3PUzstcKtIGM9f1PoFcJug,1987
|
|
63
64
|
sourcecode/detectors/base.py,sha256=C2EqfZudQ1ITK4DID4M70nPxqoh9bl1zn_ta6XRaGWs,1168
|
|
@@ -96,8 +97,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
96
97
|
sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
|
|
97
98
|
sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
|
|
98
99
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
99
|
-
sourcecode-1.36.
|
|
100
|
-
sourcecode-1.36.
|
|
101
|
-
sourcecode-1.36.
|
|
102
|
-
sourcecode-1.36.
|
|
103
|
-
sourcecode-1.36.
|
|
100
|
+
sourcecode-1.36.2.dist-info/METADATA,sha256=JWwQcBBUaBi8XxPm9ztTXwbj-lqk1CebR-LnQPQ731A,30313
|
|
101
|
+
sourcecode-1.36.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
102
|
+
sourcecode-1.36.2.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
103
|
+
sourcecode-1.36.2.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
104
|
+
sourcecode-1.36.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|