Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion src/tether/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,31 @@
"SUPPORTED_MODEL_TYPES",
"UNSUPPORTED_MODEL_MESSAGE",
"load_fixtures",
# Customer SDK surface — re-exported from tether.client so a bare
# `pip install fastcrest-tether` gives `from tether import TetherClient`.
# httpx is a base dep, so this is cheap (no torch); lazy-loaded below.
"TetherClient",
"TetherAsyncClient",
"TetherClientError",
"TetherAuthError",
"TetherServerDegradedError",
"TetherServerNotReadyError",
"TetherValidationError",
"encode_image",
]

# Names re-exported from tether.client (lazy — see __getattr__).
_CLIENT_EXPORTS = frozenset({
"TetherClient",
"TetherAsyncClient",
"TetherClientError",
"TetherAuthError",
"TetherServerDegradedError",
"TetherServerNotReadyError",
"TetherValidationError",
"encode_image",
})


# ─── ORT-TRT EP first-class support (v0.7) ──────────────────────────────────
# ORT-TRT EP needs libnvinfer.so.10 (from the `tensorrt` pip pkg) + CUDA libs
Expand Down Expand Up @@ -125,7 +148,6 @@ def _eager_dlopen_nvidia_libs() -> None:
cached handle. No-op on macOS/Windows or when libs don't exist.
"""
import ctypes
import glob
import os
import sys

Expand Down Expand Up @@ -193,4 +215,7 @@ def __getattr__(name: str):
if name == "load_fixtures":
from tether.fixtures import load_fixtures
return load_fixtures
if name in _CLIENT_EXPORTS:
import tether.client as _client
return getattr(_client, name)
raise AttributeError(f"module 'tether' has no attribute {name!r}")
38 changes: 38 additions & 0 deletions src/tether/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,41 @@
encode_image,
)

class ReflexClient(TetherClient):
"""Deprecated alias for :class:`TetherClient`. Removed in v0.14.0.

Kept so pre-rename code (`from tether.client import ReflexClient`) keeps
working through the v0.13.x compat window, matching the `reflex` import
shim's removal schedule.
"""

def __init__(self, *args, **kwargs):
import warnings

warnings.warn(
"ReflexClient is deprecated; use TetherClient. "
"The alias is removed in tether v0.14.0.",
DeprecationWarning,
stacklevel=2,
)
super().__init__(*args, **kwargs)


class ReflexAsyncClient(TetherAsyncClient):
"""Deprecated alias for :class:`TetherAsyncClient`. Removed in v0.14.0."""

def __init__(self, *args, **kwargs):
import warnings

warnings.warn(
"ReflexAsyncClient is deprecated; use TetherAsyncClient. "
"The alias is removed in tether v0.14.0.",
DeprecationWarning,
stacklevel=2,
)
super().__init__(*args, **kwargs)


__all__ = [
"TetherClient",
"TetherAsyncClient",
Expand All @@ -51,4 +86,7 @@
"TetherServerNotReadyError",
"TetherValidationError",
"encode_image",
# Deprecated rename-compat aliases (removed v0.14.0).
"ReflexClient",
"ReflexAsyncClient",
]
14 changes: 12 additions & 2 deletions src/tether/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ def encode_image(image: Any, jpeg_quality: int = 85) -> str:
if len(image) >= 8 and image[:8] == b"\x89PNG\r\n\x1a\n":
return base64.b64encode(image).decode("ascii")
return base64.b64encode(image).decode("ascii")
# The only remaining valid inputs are numpy.ndarray and PIL.Image, both of
# which need Pillow. Reject obviously-unsupported types (dict, int, list…)
# BEFORE requiring Pillow, so the error names the real problem rather than
# blaming a missing optional dep on a caller who passed garbage.
_cls = type(image)
_image_like = (
_cls.__module__.split(".")[0] in ("PIL", "numpy")
or hasattr(image, "__array_interface__")
or (hasattr(image, "save") and hasattr(image, "size"))
)
if not _image_like:
raise TetherClientError(f"unsupported image type: {_cls.__name__}")
try:
from PIL import Image as PILImage
except ImportError:
Expand Down Expand Up @@ -211,7 +223,6 @@ def close(self) -> None:
# ---- Internals -------------------------------------------------------

def _request_with_retry(self, method: str, path: str, **kw) -> httpx.Response:
last_exc: TetherClientError | None = None
attempt = 0
backoff = self.initial_backoff_s
while True:
Expand Down Expand Up @@ -244,7 +255,6 @@ def _request_with_retry(self, method: str, path: str, **kw) -> httpx.Response:
)
time.sleep(wait)
attempt += 1
last_exc = err

# ---- Public surface --------------------------------------------------

Expand Down
Loading