hypha-rpc 0.20.93__tar.gz → 0.20.94__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.
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/PKG-INFO +1 -1
- hypha_rpc-0.20.94/hypha_rpc/VERSION +3 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/__init__.py +18 -7
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/http_client.py +25 -12
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/rpc.py +231 -136
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/utils/__init__.py +15 -9
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/websocket_client.py +42 -8
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc.egg-info/PKG-INFO +1 -1
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/pyproject.toml +1 -1
- hypha_rpc-0.20.94/tests/test_http_rpc.py +728 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/tests/test_reconnection_runner.py +57 -46
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/tests/test_reconnection_stability.py +296 -194
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/tests/test_websocket_rpc.py +859 -627
- hypha_rpc-0.20.93/hypha_rpc/VERSION +0 -3
- hypha_rpc-0.20.93/tests/test_http_rpc.py +0 -422
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/MANIFEST.in +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/README.md +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/pyodide_sse.py +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/pyodide_websocket.py +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/sync.py +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/utils/launch.py +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/utils/mcp.py +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/utils/pydantic.py +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/utils/schema.py +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/utils/serve.py +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc/webrtc_client.py +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc.egg-info/SOURCES.txt +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc.egg-info/dependency_links.txt +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc.egg-info/requires.txt +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/hypha_rpc.egg-info/top_level.txt +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/setup.cfg +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/tests/test_mcp.py +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/tests/test_schema.py +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/tests/test_server_compatibility.py +0 -0
- {hypha_rpc-0.20.93 → hypha_rpc-0.20.94}/tests/test_utils.py +0 -0
|
@@ -28,11 +28,12 @@ from .http_client import HTTPStreamingRPCConnection
|
|
|
28
28
|
with open(os.path.join(os.path.dirname(__file__), "VERSION"), "r") as f:
|
|
29
29
|
__version__ = json.load(f)["version"]
|
|
30
30
|
|
|
31
|
+
|
|
31
32
|
def is_user_defined_class_instance(obj):
|
|
32
33
|
return (
|
|
33
|
-
not isinstance(obj, type)
|
|
34
|
-
hasattr(obj, "__class__")
|
|
35
|
-
obj.__class__.__module__ != "builtins"
|
|
34
|
+
not isinstance(obj, type) # not a class itself
|
|
35
|
+
and hasattr(obj, "__class__")
|
|
36
|
+
and obj.__class__.__module__ != "builtins" # not a built-in type
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
|
|
@@ -41,23 +42,34 @@ class API(ObjectProxy):
|
|
|
41
42
|
super().__init__(*args, **kwargs)
|
|
42
43
|
self._registry = {}
|
|
43
44
|
self._export_handler = self._default_export_handler
|
|
44
|
-
|
|
45
|
+
|
|
45
46
|
async def _register_services(self, obj, config=None, **kwargs):
|
|
46
47
|
if not os.environ.get("HYPHA_SERVER_URL"):
|
|
47
48
|
try:
|
|
48
49
|
from dotenv import load_dotenv, find_dotenv
|
|
50
|
+
|
|
49
51
|
load_dotenv(dotenv_path=find_dotenv(usecwd=True))
|
|
50
52
|
# use info from .env file
|
|
51
53
|
print("✅ Loaded connection configuration from .env file.")
|
|
52
54
|
except ImportError:
|
|
53
|
-
print(
|
|
55
|
+
print(
|
|
56
|
+
"❌ Missing environment variables. Set HYPHA_SERVER_URL, HYPHA_TOKEN, HYPHA_WORKSPACE",
|
|
57
|
+
file=sys.stderr,
|
|
58
|
+
)
|
|
54
59
|
sys.exit(1)
|
|
55
60
|
SERVER_URL = os.environ.get("HYPHA_SERVER_URL")
|
|
56
61
|
TOKEN = os.environ.get("HYPHA_TOKEN")
|
|
57
62
|
CLIENT_ID = os.environ.get("HYPHA_CLIENT_ID")
|
|
58
63
|
WORKSPACE = os.environ.get("HYPHA_WORKSPACE")
|
|
59
64
|
|
|
60
|
-
server = await connect_to_server(
|
|
65
|
+
server = await connect_to_server(
|
|
66
|
+
{
|
|
67
|
+
"client_id": CLIENT_ID,
|
|
68
|
+
"server_url": SERVER_URL,
|
|
69
|
+
"token": TOKEN,
|
|
70
|
+
"workspace": WORKSPACE,
|
|
71
|
+
}
|
|
72
|
+
)
|
|
61
73
|
# If obj is a class, instantiate it
|
|
62
74
|
if isinstance(obj, type):
|
|
63
75
|
obj = obj()
|
|
@@ -96,7 +108,6 @@ class API(ObjectProxy):
|
|
|
96
108
|
asyncio.create_task(self._register_services(obj, config, **kwargs))
|
|
97
109
|
else:
|
|
98
110
|
asyncio.run(self._register_services(obj, config, **kwargs))
|
|
99
|
-
|
|
100
111
|
|
|
101
112
|
def set_export_handler(self, handler):
|
|
102
113
|
self._export_handler = handler
|
|
@@ -134,7 +134,9 @@ class HTTPStreamingRPCConnection:
|
|
|
134
134
|
if response.status_code == 200:
|
|
135
135
|
logger.debug("Token refresh requested successfully")
|
|
136
136
|
else:
|
|
137
|
-
logger.warning(
|
|
137
|
+
logger.warning(
|
|
138
|
+
f"Token refresh request failed: {response.status_code}"
|
|
139
|
+
)
|
|
138
140
|
except Exception as e:
|
|
139
141
|
logger.warning(f"Failed to send refresh token request: {e}")
|
|
140
142
|
|
|
@@ -177,11 +179,14 @@ class HTTPStreamingRPCConnection:
|
|
|
177
179
|
# Try to enable HTTP/2 if h2 is available
|
|
178
180
|
try:
|
|
179
181
|
import h2 # noqa
|
|
182
|
+
|
|
180
183
|
http2_enabled = True
|
|
181
184
|
logger.info("HTTP/2 enabled for improved performance")
|
|
182
185
|
except ImportError:
|
|
183
186
|
http2_enabled = False
|
|
184
|
-
logger.debug(
|
|
187
|
+
logger.debug(
|
|
188
|
+
"HTTP/2 not available (install httpx[http2] for better performance)"
|
|
189
|
+
)
|
|
185
190
|
|
|
186
191
|
return httpx.AsyncClient(
|
|
187
192
|
timeout=httpx.Timeout(self._timeout, connect=30.0),
|
|
@@ -198,14 +203,16 @@ class HTTPStreamingRPCConnection:
|
|
|
198
203
|
|
|
199
204
|
async def open(self):
|
|
200
205
|
"""Open the streaming connection."""
|
|
201
|
-
logger.info(
|
|
206
|
+
logger.info(
|
|
207
|
+
f"Opening HTTP streaming connection to {self._server_url} (format={self._format})"
|
|
208
|
+
)
|
|
202
209
|
|
|
203
210
|
if self._http_client is None:
|
|
204
211
|
self._http_client = await self._create_http_client()
|
|
205
212
|
|
|
206
|
-
# Build stream URL
|
|
207
|
-
|
|
208
|
-
stream_url = f"{self._server_url}/{
|
|
213
|
+
# Build stream URL - workspace is part of path, default to "public" for anonymous
|
|
214
|
+
ws = self._workspace or "public"
|
|
215
|
+
stream_url = f"{self._server_url}/{ws}/rpc"
|
|
209
216
|
params = {"client_id": self._client_id}
|
|
210
217
|
if self._format == "msgpack":
|
|
211
218
|
params["format"] = "msgpack"
|
|
@@ -357,15 +364,15 @@ class HTTPStreamingRPCConnection:
|
|
|
357
364
|
# Process complete frames from buffer
|
|
358
365
|
while len(buffer) >= 4:
|
|
359
366
|
# Read 4-byte length prefix (big-endian)
|
|
360
|
-
length = int.from_bytes(buffer[:4],
|
|
367
|
+
length = int.from_bytes(buffer[:4], "big")
|
|
361
368
|
|
|
362
369
|
if len(buffer) < 4 + length:
|
|
363
370
|
# Incomplete frame, wait for more data
|
|
364
371
|
break
|
|
365
372
|
|
|
366
373
|
# Extract the frame
|
|
367
|
-
frame_data = buffer[4:4 + length]
|
|
368
|
-
buffer = buffer[4 + length:]
|
|
374
|
+
frame_data = buffer[4 : 4 + length]
|
|
375
|
+
buffer = buffer[4 + length :]
|
|
369
376
|
|
|
370
377
|
try:
|
|
371
378
|
# For msgpack, first check if it's a control message
|
|
@@ -440,8 +447,9 @@ class HTTPStreamingRPCConnection:
|
|
|
440
447
|
if self._http_client is None:
|
|
441
448
|
self._http_client = await self._create_http_client()
|
|
442
449
|
|
|
443
|
-
workspace
|
|
444
|
-
|
|
450
|
+
# Build POST URL - workspace is part of path (must be set after connection)
|
|
451
|
+
ws = self._workspace or "public"
|
|
452
|
+
url = f"{self._server_url}/{ws}/rpc"
|
|
445
453
|
params = {"client_id": self._client_id}
|
|
446
454
|
|
|
447
455
|
try:
|
|
@@ -454,7 +462,9 @@ class HTTPStreamingRPCConnection:
|
|
|
454
462
|
)
|
|
455
463
|
|
|
456
464
|
if response.status_code != 200:
|
|
457
|
-
error =
|
|
465
|
+
error = (
|
|
466
|
+
response.json() if response.content else {"detail": "Unknown error"}
|
|
467
|
+
)
|
|
458
468
|
raise ConnectionError(f"POST failed: {error.get('detail', error)}")
|
|
459
469
|
|
|
460
470
|
except httpx.TimeoutException:
|
|
@@ -528,6 +538,7 @@ def connect_to_server_http(config=None, **kwargs):
|
|
|
528
538
|
ServerContextManager that can be used as async context manager
|
|
529
539
|
"""
|
|
530
540
|
from .websocket_client import connect_to_server
|
|
541
|
+
|
|
531
542
|
config = config or {}
|
|
532
543
|
config.update(kwargs)
|
|
533
544
|
config["transport"] = "http"
|
|
@@ -625,6 +636,7 @@ async def _connect_to_server_http(config: dict):
|
|
|
625
636
|
|
|
626
637
|
# Handle force-exit from manager
|
|
627
638
|
if connection.manager_id:
|
|
639
|
+
|
|
628
640
|
async def handle_disconnect(message):
|
|
629
641
|
if message.get("from") == "*/" + connection.manager_id:
|
|
630
642
|
logger.info(f"Disconnecting from server: {message.get('reason')}")
|
|
@@ -642,6 +654,7 @@ def get_remote_service_http(service_uri: str, config=None, **kwargs):
|
|
|
642
654
|
For a unified interface, use get_remote_service with transport="http" instead.
|
|
643
655
|
"""
|
|
644
656
|
from .websocket_client import get_remote_service
|
|
657
|
+
|
|
645
658
|
config = config or {}
|
|
646
659
|
config.update(kwargs)
|
|
647
660
|
config["transport"] = "http"
|