physistool 0.1.2__tar.gz → 0.1.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: physistool
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Open-source PhysisFold client — CLI + SDK for constraint-guided all-atom protein folding via a remote (Nitro Enclave) endpoint.
5
5
  Author: MorphoComputing
6
6
  License: MIT
@@ -29,4 +29,4 @@ from .physis_client import FoldError, fold
29
29
 
30
30
  __all__ = ["fold", "FoldError"]
31
31
 
32
- __version__ = "0.1.2"
32
+ __version__ = "0.1.3"
@@ -36,7 +36,7 @@ _TIMEOUT = 15 # seconds
36
36
  # in sync with ``build/package.py`` VERSION — this is the version of the shipped
37
37
  # wheel. It is *spoofable* by design: the gate is a forced-upgrade lever, not an
38
38
  # anti-tamper check — the real enforcement lives server-side.
39
- CLIENT_VERSION = "0.1.2"
39
+ CLIENT_VERSION = "0.1.3"
40
40
 
41
41
 
42
42
  class MeteringError(Exception):
@@ -52,6 +52,25 @@ class MeteringError(Exception):
52
52
  self.detail = detail or message
53
53
 
54
54
 
55
+ class UpgradeRequiredError(MeteringError):
56
+ """The server's MIN_CLIENT_VERSION gate refused this build (HTTP 403).
57
+
58
+ Enforced server-side on the charge path (reserve) BEFORE any credit moves, so
59
+ an old or cracked client cannot fold. Carries the required + current versions
60
+ and a ready-to-print ``pip install -U`` message. Subclasses MeteringError so
61
+ existing ``except MeteringError`` sites still catch it, but callers may catch
62
+ this specifically to show a clean forced-upgrade prompt.
63
+ """
64
+
65
+ def __init__(self, min_client_version: str, your_version: str):
66
+ self.min_client_version = min_client_version
67
+ self.your_version = your_version
68
+ msg = (f"this physistool client ({your_version}) is below the minimum "
69
+ f"supported version ({min_client_version}). "
70
+ f"Upgrade with: pip install -U physistool")
71
+ super().__init__(msg, status=403, detail=msg)
72
+
73
+
55
74
  def _post(base_url: str, path: str, payload: dict) -> dict:
56
75
  url = f"{base_url}{path}"
57
76
  # client_version rides along on every call (see CLIENT_VERSION); an explicit
@@ -66,7 +85,16 @@ def _post(base_url: str, path: str, payload: dict) -> dict:
66
85
  body = resp.read().decode("utf-8")
67
86
  return json.loads(body) if body else {}
68
87
  except urllib.error.HTTPError as e:
69
- detail = _read_error_detail(e)
88
+ raw, obj = _read_error_body(e)
89
+ # Forced-upgrade gate: a 403 carrying min_client_version means this build
90
+ # is below the server's floor. Surface it as a dedicated, actionable error
91
+ # (the client prints "pip install -U") instead of a generic reserve failure.
92
+ if e.code == 403 and isinstance(obj, dict) and obj.get("min_client_version"):
93
+ raise UpgradeRequiredError(
94
+ str(obj["min_client_version"]),
95
+ str(obj.get("your_version") or CLIENT_VERSION)) from e
96
+ detail = (obj.get("error") or obj.get("detail") or raw) if isinstance(obj, dict) \
97
+ else (raw or e.reason or "unknown error")
70
98
  raise MeteringError(f"backend returned HTTP {e.code}: {detail}",
71
99
  status=e.code, detail=detail) from e
72
100
  except urllib.error.URLError as e:
@@ -79,13 +107,17 @@ def _post(base_url: str, path: str, payload: dict) -> dict:
79
107
  status=0, detail=str(e)) from e
80
108
 
81
109
 
82
- def _read_error_detail(e: "urllib.error.HTTPError") -> str:
110
+ def _read_error_body(e: "urllib.error.HTTPError") -> "tuple[str, object]":
111
+ """Read an HTTPError body once, returning (raw_text, parsed_json_or_None)."""
112
+ raw = ""
113
+ try:
114
+ raw = e.read().decode("utf-8", "replace")
115
+ except OSError:
116
+ return "", None
83
117
  try:
84
- body = e.read().decode("utf-8")
85
- obj = json.loads(body)
86
- return obj.get("error") or obj.get("detail") or body
87
- except (ValueError, OSError):
88
- return e.reason or "unknown error"
118
+ return raw, json.loads(raw)
119
+ except ValueError:
120
+ return raw, None
89
121
 
90
122
 
91
123
  # ── lifecycle calls ─────────────────────────────────────────────────────────
@@ -668,6 +668,10 @@ def fold(
668
668
  backend, api_key, credits=credits,
669
669
  idempotency_key=str(uuid.uuid4()), input_hash=input_hash,
670
670
  )
671
+ except metering_mod.UpgradeRequiredError as exc:
672
+ # Forced-upgrade gate (server MIN_CLIENT_VERSION). Not a reserve/credit
673
+ # problem — the build is too old to fold. Surface the pip command as-is.
674
+ raise FoldError(exc.detail, status=exc.status, error="please_upgrade") from exc
671
675
  except metering_mod.MeteringError as exc:
672
676
  raise FoldError(
673
677
  f"could not reserve {credits} credit(s): {exc.detail}",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: physistool
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Open-source PhysisFold client — CLI + SDK for constraint-guided all-atom protein folding via a remote (Nitro Enclave) endpoint.
5
5
  Author: MorphoComputing
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "physistool"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Open-source PhysisFold client — CLI + SDK for constraint-guided all-atom protein folding via a remote (Nitro Enclave) endpoint."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
File without changes
File without changes
File without changes