hypha-rpc 0.20.92__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.
Files changed (34) hide show
  1. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/PKG-INFO +1 -1
  2. hypha_rpc-0.20.94/hypha_rpc/VERSION +3 -0
  3. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/__init__.py +18 -7
  4. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/http_client.py +45 -16
  5. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/rpc.py +231 -136
  6. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/utils/__init__.py +15 -9
  7. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/websocket_client.py +42 -8
  8. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc.egg-info/PKG-INFO +1 -1
  9. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc.egg-info/SOURCES.txt +1 -0
  10. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/pyproject.toml +1 -1
  11. hypha_rpc-0.20.94/tests/test_http_rpc.py +728 -0
  12. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/tests/test_reconnection_runner.py +57 -46
  13. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/tests/test_reconnection_stability.py +296 -194
  14. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/tests/test_websocket_rpc.py +859 -627
  15. hypha_rpc-0.20.92/hypha_rpc/VERSION +0 -3
  16. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/MANIFEST.in +0 -0
  17. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/README.md +0 -0
  18. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/pyodide_sse.py +0 -0
  19. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/pyodide_websocket.py +0 -0
  20. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/sync.py +0 -0
  21. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/utils/launch.py +0 -0
  22. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/utils/mcp.py +0 -0
  23. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/utils/pydantic.py +0 -0
  24. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/utils/schema.py +0 -0
  25. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/utils/serve.py +0 -0
  26. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc/webrtc_client.py +0 -0
  27. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc.egg-info/dependency_links.txt +0 -0
  28. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc.egg-info/requires.txt +0 -0
  29. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/hypha_rpc.egg-info/top_level.txt +0 -0
  30. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/setup.cfg +0 -0
  31. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/tests/test_mcp.py +0 -0
  32. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/tests/test_schema.py +0 -0
  33. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/tests/test_server_compatibility.py +0 -0
  34. {hypha_rpc-0.20.92 → hypha_rpc-0.20.94}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypha_rpc
3
- Version: 0.20.92
3
+ Version: 0.20.94
4
4
  Summary: Hypha RPC client for connecting to Hypha server for data management and AI model serving
5
5
  Author-email: Wei Ouyang <oeway007@gmail.com>
6
6
  Requires-Python: >=3.9
@@ -0,0 +1,3 @@
1
+ {
2
+ "version": "0.20.94"
3
+ }
@@ -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) and # not a class itself
34
- hasattr(obj, "__class__") and
35
- obj.__class__.__module__ != "builtins" # not a built-in type
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("❌ Missing environment variables. Set HYPHA_SERVER_URL, HYPHA_TOKEN, HYPHA_WORKSPACE", file=sys.stderr)
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({"client_id": CLIENT_ID, "server_url": SERVER_URL, "token": TOKEN, "workspace": WORKSPACE})
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(f"Token refresh request failed: {response.status_code}")
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
 
@@ -174,27 +176,43 @@ class HTTPStreamingRPCConnection:
174
176
  elif self._ssl is not None:
175
177
  verify = self._ssl
176
178
 
179
+ # Try to enable HTTP/2 if h2 is available
180
+ try:
181
+ import h2 # noqa
182
+
183
+ http2_enabled = True
184
+ logger.info("HTTP/2 enabled for improved performance")
185
+ except ImportError:
186
+ http2_enabled = False
187
+ logger.debug(
188
+ "HTTP/2 not available (install httpx[http2] for better performance)"
189
+ )
190
+
177
191
  return httpx.AsyncClient(
178
192
  timeout=httpx.Timeout(self._timeout, connect=30.0),
179
193
  verify=verify,
180
- # Connection pooling for better performance with many requests
194
+ # Optimized connection pooling for high-performance RPC
181
195
  limits=httpx.Limits(
182
- max_connections=100, # Max total connections
183
- max_keepalive_connections=20, # Keep-alive connections for reuse
184
- keepalive_expiry=30.0, # Keep connections alive for 30 seconds
196
+ max_connections=200, # Max total connections (increased for parallel requests)
197
+ max_keepalive_connections=50, # More reusable connections (up from 20)
198
+ keepalive_expiry=300.0, # Keep connections alive longer (5 minutes)
185
199
  ),
200
+ # Enable HTTP/2 for better multiplexing if available
201
+ http2=http2_enabled,
186
202
  )
187
203
 
188
204
  async def open(self):
189
205
  """Open the streaming connection."""
190
- logger.info(f"Opening HTTP streaming connection to {self._server_url} (format={self._format})")
206
+ logger.info(
207
+ f"Opening HTTP streaming connection to {self._server_url} (format={self._format})"
208
+ )
191
209
 
192
210
  if self._http_client is None:
193
211
  self._http_client = await self._create_http_client()
194
212
 
195
- # Build stream URL
196
- workspace = self._workspace or "public"
197
- stream_url = f"{self._server_url}/{workspace}/rpc"
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"
198
216
  params = {"client_id": self._client_id}
199
217
  if self._format == "msgpack":
200
218
  params["format"] = "msgpack"
@@ -346,15 +364,15 @@ class HTTPStreamingRPCConnection:
346
364
  # Process complete frames from buffer
347
365
  while len(buffer) >= 4:
348
366
  # Read 4-byte length prefix (big-endian)
349
- length = int.from_bytes(buffer[:4], 'big')
367
+ length = int.from_bytes(buffer[:4], "big")
350
368
 
351
369
  if len(buffer) < 4 + length:
352
370
  # Incomplete frame, wait for more data
353
371
  break
354
372
 
355
373
  # Extract the frame
356
- frame_data = buffer[4:4 + length]
357
- buffer = buffer[4 + length:]
374
+ frame_data = buffer[4 : 4 + length]
375
+ buffer = buffer[4 + length :]
358
376
 
359
377
  try:
360
378
  # For msgpack, first check if it's a control message
@@ -418,18 +436,24 @@ class HTTPStreamingRPCConnection:
418
436
  self._handle_message(data)
419
437
 
420
438
  async def emit_message(self, data: bytes):
421
- """Send a message to the server via HTTP POST."""
439
+ """Send a message to the server via HTTP POST.
440
+
441
+ Uses optimized connection pooling with keep-alive for better performance.
442
+ HTTP client automatically handles efficient transfer for all payload sizes.
443
+ """
422
444
  if self._closed:
423
445
  raise ConnectionError("Connection is closed")
424
446
 
425
447
  if self._http_client is None:
426
448
  self._http_client = await self._create_http_client()
427
449
 
428
- workspace = self._workspace or "public"
429
- url = f"{self._server_url}/{workspace}/rpc"
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"
430
453
  params = {"client_id": self._client_id}
431
454
 
432
455
  try:
456
+ # httpx handles large payloads efficiently with connection pooling
433
457
  response = await self._http_client.post(
434
458
  url,
435
459
  content=data,
@@ -438,7 +462,9 @@ class HTTPStreamingRPCConnection:
438
462
  )
439
463
 
440
464
  if response.status_code != 200:
441
- error = response.json() if response.content else {"detail": "Unknown error"}
465
+ error = (
466
+ response.json() if response.content else {"detail": "Unknown error"}
467
+ )
442
468
  raise ConnectionError(f"POST failed: {error.get('detail', error)}")
443
469
 
444
470
  except httpx.TimeoutException:
@@ -512,6 +538,7 @@ def connect_to_server_http(config=None, **kwargs):
512
538
  ServerContextManager that can be used as async context manager
513
539
  """
514
540
  from .websocket_client import connect_to_server
541
+
515
542
  config = config or {}
516
543
  config.update(kwargs)
517
544
  config["transport"] = "http"
@@ -609,6 +636,7 @@ async def _connect_to_server_http(config: dict):
609
636
 
610
637
  # Handle force-exit from manager
611
638
  if connection.manager_id:
639
+
612
640
  async def handle_disconnect(message):
613
641
  if message.get("from") == "*/" + connection.manager_id:
614
642
  logger.info(f"Disconnecting from server: {message.get('reason')}")
@@ -626,6 +654,7 @@ def get_remote_service_http(service_uri: str, config=None, **kwargs):
626
654
  For a unified interface, use get_remote_service with transport="http" instead.
627
655
  """
628
656
  from .websocket_client import get_remote_service
657
+
629
658
  config = config or {}
630
659
  config.update(kwargs)
631
660
  config["transport"] = "http"