zerograd 0.2.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.
Files changed (47) hide show
  1. zerograd/__init__.py +48 -0
  2. zerograd/_http.py +86 -0
  3. zerograd/_progress.py +49 -0
  4. zerograd/_remote.py +594 -0
  5. zerograd/_ui.py +267 -0
  6. zerograd/_version.py +1 -0
  7. zerograd/client.py +59 -0
  8. zerograd/curve.py +239 -0
  9. zerograd/errors.py +70 -0
  10. zerograd/estimators.py +152 -0
  11. zerograd/local/__init__.py +5 -0
  12. zerograd/local/_errors.py +10 -0
  13. zerograd/local/championship.py +482 -0
  14. zerograd/local/engine.py +728 -0
  15. zerograd/local/preprocess.py +273 -0
  16. zerograd/local/threshold.py +91 -0
  17. zerograd/local/validation.py +156 -0
  18. zerograd/models/__init__.py +34 -0
  19. zerograd/models/_vendor/__init__.py +1 -0
  20. zerograd/models/_vendor/fomo0d/LICENSE +21 -0
  21. zerograd/models/_vendor/fomo0d/__init__.py +1 -0
  22. zerograd/models/_vendor/fomo0d/encoders.py +279 -0
  23. zerograd/models/_vendor/fomo0d/layer.py +249 -0
  24. zerograd/models/_vendor/fomo0d/transformer.py +369 -0
  25. zerograd/models/_vendor/fomo0d/utils.py +342 -0
  26. zerograd/models/_vendor/zeus/LICENSE +176 -0
  27. zerograd/models/_vendor/zeus/NOTICE +1 -0
  28. zerograd/models/_vendor/zeus/__init__.py +1 -0
  29. zerograd/models/_vendor/zeus/encoders.py +21 -0
  30. zerograd/models/_vendor/zeus/layer.py +150 -0
  31. zerograd/models/_vendor/zeus/model_utils.py +42 -0
  32. zerograd/models/_vendor/zeus/zeus.py +164 -0
  33. zerograd/models/catalog.py +237 -0
  34. zerograd/models/commercial.py +267 -0
  35. zerograd/models/noncommercial.py +221 -0
  36. zerograd/models/registry.py +272 -0
  37. zerograd/models/unsupervised.py +234 -0
  38. zerograd/privacy.py +308 -0
  39. zerograd/unsupervised/__init__.py +27 -0
  40. zerograd/unsupervised/chain.py +134 -0
  41. zerograd/unsupervised/table.py +157 -0
  42. zerograd/unsupervised/tasks.py +618 -0
  43. zerograd-0.2.0.dist-info/METADATA +395 -0
  44. zerograd-0.2.0.dist-info/RECORD +47 -0
  45. zerograd-0.2.0.dist-info/WHEEL +4 -0
  46. zerograd-0.2.0.dist-info/licenses/LICENSE +201 -0
  47. zerograd-0.2.0.dist-info/licenses/NOTICE +29 -0
zerograd/__init__.py ADDED
@@ -0,0 +1,48 @@
1
+ """ZeroGrad: predictions over your tabular data with foundation models. Nothing to train.
2
+
3
+ from zerograd import ZeroGrad
4
+ zg = ZeroGrad() # local, on your machine
5
+ cap = zg.create("titanic-survival", examples=train_df, target="Survived")
6
+ cap.evaluation # every model on the same holdout
7
+ cap.predict(new_df)
8
+
9
+ zg = ZeroGrad(remote=True) # the ZeroGrad platform (ZEROGRAD_API_KEY)
10
+
11
+ scikit-learn: ZeroGradClassifier / ZeroGradRegressor. Honest evaluation: zg.cross_validate(df, target, groups=, time=).
12
+ Then cap.optimize_threshold("cost", cost_fn=20) and cap.importance(test_df).
13
+
14
+ No target? zg.anomalies(df), zg.impute(df), zg.synthesize(df) and zg.cluster(df) run locally (zerograd.unsupervised).
15
+ """
16
+
17
+ from ._remote import Capability, Pipeline, RemoteZeroGrad, Run
18
+ from ._version import __version__
19
+ from .client import ZeroGrad
20
+ from .curve import ContextCurve
21
+ from .errors import (
22
+ APIError,
23
+ AuthError,
24
+ LimitError,
25
+ NotFoundError,
26
+ RunFailedError,
27
+ UsageWarning,
28
+ ValidationError,
29
+ ZeroGradError,
30
+ )
31
+ from .estimators import ZeroGradClassifier, ZeroGradRegressor
32
+ from .local import LocalCapability, LocalZeroGrad, load
33
+ from .local.threshold import ThresholdResult
34
+ from .local.validation import CVResult
35
+ from .models import LicenseGuardError, NonCommercialModelWarning, list_models, list_tasks, model_info, register_model
36
+ from .privacy import SensitiveDataWarning, Vault, drop_sensitive, scan
37
+ from .unsupervised import AnomalyResult, ClusterResult, ImputeResult, SyntheticResult
38
+
39
+ __all__ = [
40
+ "ZeroGrad", "LocalZeroGrad", "RemoteZeroGrad", "LocalCapability", "Capability", "Run", "Pipeline", "load", "ContextCurve",
41
+ "list_models", "list_tasks", "model_info", "register_model", "NonCommercialModelWarning", "LicenseGuardError",
42
+ "ZeroGradError", "AuthError", "NotFoundError", "ValidationError", "LimitError",
43
+ "RunFailedError", "APIError", "UsageWarning",
44
+ "Vault", "scan", "drop_sensitive", "SensitiveDataWarning",
45
+ "AnomalyResult", "ImputeResult", "SyntheticResult", "ClusterResult",
46
+ "ZeroGradClassifier", "ZeroGradRegressor", "CVResult", "ThresholdResult",
47
+ "__version__",
48
+ ]
zerograd/_http.py ADDED
@@ -0,0 +1,86 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ import time
5
+ import warnings
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ from ._version import __version__
11
+ from .errors import APIError, UsageWarning, ZeroGradError, from_response
12
+
13
+ RETRY_STATUS = {429, 502, 503, 504}
14
+
15
+
16
+ class Transport:
17
+ """httpx with auth, retries (cold starts, 429, 5xx gateways) and error mapping."""
18
+
19
+ def __init__(self, base_url: str, api_key: str, timeout: float, http_client: httpx.Client | None, max_retries: int):
20
+ self.base_url = base_url.rstrip("/")
21
+ self.max_retries = max_retries
22
+ headers = {"Authorization": f"Bearer {api_key}", "User-Agent": f"zerograd-python/{__version__}"}
23
+ if http_client is not None:
24
+ http_client.headers.update(headers)
25
+ self.http = http_client
26
+ else:
27
+ self.http = httpx.Client(base_url=self.base_url, headers=headers, timeout=timeout)
28
+ self._warned = False
29
+
30
+ def request(self, method: str, path: str, *, raw: bool = False, **kwargs: Any) -> Any:
31
+ url = path if path.startswith("http") else self.base_url + path
32
+ last: Exception | None = None
33
+ for attempt in range(self.max_retries + 1):
34
+ try:
35
+ resp = self.http.request(method, url, **kwargs)
36
+ except httpx.TransportError as exc: # connection reset, cold start timeouts
37
+ last = exc
38
+ self._sleep(attempt)
39
+ continue
40
+ if resp.status_code in RETRY_STATUS and attempt < self.max_retries:
41
+ if not (resp.status_code == 429 and _code(resp) == "usage_limit"):
42
+ self._sleep(attempt, resp.headers.get("retry-after"))
43
+ continue
44
+ self._usage_warning(resp)
45
+ if resp.status_code >= 400:
46
+ try:
47
+ body = resp.json()
48
+ except ValueError:
49
+ body = resp.text
50
+ raise from_response(resp.status_code, body)
51
+ if raw:
52
+ return resp
53
+ return resp.json() if resp.content else None
54
+ raise APIError(f"Could not reach {self.base_url}: {last}") from last
55
+
56
+ def _sleep(self, attempt: int, retry_after: str | None = None) -> None:
57
+ if retry_after and retry_after.isdigit():
58
+ time.sleep(min(float(retry_after), 30))
59
+ else:
60
+ time.sleep(min(0.5 * 2**attempt, 8) + random.random() * 0.25)
61
+
62
+ def _usage_warning(self, resp: httpx.Response) -> None:
63
+ msg = resp.headers.get("x-zerograd-usage-warning")
64
+ if msg and not self._warned:
65
+ self._warned = True
66
+ warnings.warn(f"{msg} At the limit, predict raises LimitError.", UsageWarning, stacklevel=4)
67
+
68
+ def put_chunk(self, url: str, data: bytes, headers: dict[str, str]) -> httpx.Response:
69
+ """Upload chunks go to a session URL (Cloud Storage in production). Never send our API key there."""
70
+ if url.startswith(self.base_url):
71
+ return self.http.put(url, content=data, headers=headers)
72
+ for attempt in range(self.max_retries + 1):
73
+ try:
74
+ return httpx.put(url, content=data, headers=headers, timeout=300)
75
+ except httpx.TransportError:
76
+ if attempt == self.max_retries:
77
+ raise
78
+ self._sleep(attempt)
79
+ raise ZeroGradError("unreachable")
80
+
81
+
82
+ def _code(resp: httpx.Response) -> str | None:
83
+ try:
84
+ return resp.json().get("error")
85
+ except ValueError:
86
+ return None
zerograd/_progress.py ADDED
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+
6
+ class Bar:
7
+ """A one-line progress bar on stderr that also prints finished stages as ✓ lines. Works in notebooks."""
8
+
9
+ def __init__(self, enabled: bool = True, width: int = 28):
10
+ self.enabled = enabled
11
+ self.width = width
12
+ self.seen = 0
13
+ self.last = ""
14
+
15
+ def update(self, run: dict) -> None:
16
+ if not self.enabled:
17
+ return
18
+ log = run.get("log") or []
19
+ active = run.get("status") in ("queued", "running")
20
+ # Finished stages become ✓ lines; the latest one stays on the bar while the run is active.
21
+ upto = len(log) - 1 if active else len(log)
22
+ for entry in log[self.seen : upto]:
23
+ self._line(f"✓ {entry['message']}")
24
+ self.seen = max(self.seen, upto)
25
+ if active:
26
+ p = float(run.get("progress") or 0)
27
+ filled = int(round(p * self.width))
28
+ text = f"{run.get('message', '')[:60]:<60} {'█' * filled}{'░' * (self.width - filled)} {p:>4.0%}"
29
+ if text != self.last:
30
+ sys.stderr.write("\r" + text)
31
+ sys.stderr.flush()
32
+ self.last = text
33
+
34
+ def _line(self, text: str) -> None:
35
+ if self.last:
36
+ sys.stderr.write("\r" + " " * (len(self.last) + 2) + "\r")
37
+ self.last = ""
38
+ sys.stderr.write(text + "\n")
39
+ sys.stderr.flush()
40
+
41
+ def done(self, text: str | None = None) -> None:
42
+ if not self.enabled:
43
+ return
44
+ if self.last:
45
+ sys.stderr.write("\r" + " " * (len(self.last) + 2) + "\r")
46
+ self.last = ""
47
+ if text:
48
+ sys.stderr.write(text + "\n")
49
+ sys.stderr.flush()