hypha-rpc 0.20.92__tar.gz → 0.20.93__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.93}/PKG-INFO +1 -1
  2. hypha_rpc-0.20.93/hypha_rpc/VERSION +3 -0
  3. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/http_client.py +21 -5
  4. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc.egg-info/PKG-INFO +1 -1
  5. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc.egg-info/SOURCES.txt +1 -0
  6. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/pyproject.toml +1 -1
  7. hypha_rpc-0.20.93/tests/test_http_rpc.py +422 -0
  8. hypha_rpc-0.20.92/hypha_rpc/VERSION +0 -3
  9. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/MANIFEST.in +0 -0
  10. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/README.md +0 -0
  11. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/__init__.py +0 -0
  12. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/pyodide_sse.py +0 -0
  13. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/pyodide_websocket.py +0 -0
  14. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/rpc.py +0 -0
  15. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/sync.py +0 -0
  16. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/utils/__init__.py +0 -0
  17. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/utils/launch.py +0 -0
  18. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/utils/mcp.py +0 -0
  19. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/utils/pydantic.py +0 -0
  20. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/utils/schema.py +0 -0
  21. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/utils/serve.py +0 -0
  22. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/webrtc_client.py +0 -0
  23. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc/websocket_client.py +0 -0
  24. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc.egg-info/dependency_links.txt +0 -0
  25. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc.egg-info/requires.txt +0 -0
  26. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/hypha_rpc.egg-info/top_level.txt +0 -0
  27. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/setup.cfg +0 -0
  28. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/tests/test_mcp.py +0 -0
  29. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/tests/test_reconnection_runner.py +0 -0
  30. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/tests/test_reconnection_stability.py +0 -0
  31. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/tests/test_schema.py +0 -0
  32. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/tests/test_server_compatibility.py +0 -0
  33. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/tests/test_utils.py +0 -0
  34. {hypha_rpc-0.20.92 → hypha_rpc-0.20.93}/tests/test_websocket_rpc.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.93
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.93"
3
+ }
@@ -174,15 +174,26 @@ class HTTPStreamingRPCConnection:
174
174
  elif self._ssl is not None:
175
175
  verify = self._ssl
176
176
 
177
+ # Try to enable HTTP/2 if h2 is available
178
+ try:
179
+ import h2 # noqa
180
+ http2_enabled = True
181
+ logger.info("HTTP/2 enabled for improved performance")
182
+ except ImportError:
183
+ http2_enabled = False
184
+ logger.debug("HTTP/2 not available (install httpx[http2] for better performance)")
185
+
177
186
  return httpx.AsyncClient(
178
187
  timeout=httpx.Timeout(self._timeout, connect=30.0),
179
188
  verify=verify,
180
- # Connection pooling for better performance with many requests
189
+ # Optimized connection pooling for high-performance RPC
181
190
  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
191
+ max_connections=200, # Max total connections (increased for parallel requests)
192
+ max_keepalive_connections=50, # More reusable connections (up from 20)
193
+ keepalive_expiry=300.0, # Keep connections alive longer (5 minutes)
185
194
  ),
195
+ # Enable HTTP/2 for better multiplexing if available
196
+ http2=http2_enabled,
186
197
  )
187
198
 
188
199
  async def open(self):
@@ -418,7 +429,11 @@ class HTTPStreamingRPCConnection:
418
429
  self._handle_message(data)
419
430
 
420
431
  async def emit_message(self, data: bytes):
421
- """Send a message to the server via HTTP POST."""
432
+ """Send a message to the server via HTTP POST.
433
+
434
+ Uses optimized connection pooling with keep-alive for better performance.
435
+ HTTP client automatically handles efficient transfer for all payload sizes.
436
+ """
422
437
  if self._closed:
423
438
  raise ConnectionError("Connection is closed")
424
439
 
@@ -430,6 +445,7 @@ class HTTPStreamingRPCConnection:
430
445
  params = {"client_id": self._client_id}
431
446
 
432
447
  try:
448
+ # httpx handles large payloads efficiently with connection pooling
433
449
  response = await self._http_client.post(
434
450
  url,
435
451
  content=data,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hypha_rpc
3
- Version: 0.20.92
3
+ Version: 0.20.93
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
@@ -22,6 +22,7 @@ hypha_rpc/utils/mcp.py
22
22
  hypha_rpc/utils/pydantic.py
23
23
  hypha_rpc/utils/schema.py
24
24
  hypha_rpc/utils/serve.py
25
+ tests/test_http_rpc.py
25
26
  tests/test_mcp.py
26
27
  tests/test_reconnection_runner.py
27
28
  tests/test_reconnection_stability.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hypha_rpc"
7
- version = "0.20.92"
7
+ version = "0.20.93"
8
8
  description = "Hypha RPC client for connecting to Hypha server for data management and AI model serving"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -0,0 +1,422 @@
1
+ """Test HTTP RPC transport for hypha-rpc standalone tests.
2
+
3
+ These tests connect to a remote Hypha server to test HTTP transport functionality.
4
+ """
5
+
6
+ import pytest
7
+ import numpy as np
8
+ import asyncio
9
+ from hypha_rpc import connect_to_server
10
+
11
+
12
+ # Use public test server - these tests require a running Hypha server
13
+ SERVER_URL = "https://hypha.aicell.io"
14
+
15
+
16
+ class TestHTTPObjectTransmission:
17
+ """Test HTTP transport with complex objects and callbacks."""
18
+
19
+ @pytest.mark.asyncio
20
+ async def test_http_numpy_array_transmission(self):
21
+ """Test transmitting numpy arrays over HTTP transport."""
22
+ # Service provider via WebSocket
23
+ ws_server = await connect_to_server({
24
+ "server_url": SERVER_URL,
25
+ "client_id": "numpy-provider-ws-test",
26
+ })
27
+
28
+ try:
29
+ workspace = ws_server.config["workspace"]
30
+
31
+ # Register service that works with numpy arrays
32
+ await ws_server.register_service({
33
+ "id": "numpy-service",
34
+ "name": "Numpy Service",
35
+ "config": {"visibility": "public"},
36
+ "process_array": lambda arr: {
37
+ "shape": list(arr.shape), # Convert to list for JSON
38
+ "dtype": str(arr.dtype),
39
+ "sum": float(np.sum(arr)),
40
+ "mean": float(np.mean(arr)),
41
+ "result_array": arr * 2, # Return modified array
42
+ },
43
+ "reshape": lambda arr, shape: np.reshape(arr, shape),
44
+ })
45
+
46
+ token = await ws_server.generate_token()
47
+
48
+ # HTTP client connects
49
+ http_server = await connect_to_server({
50
+ "server_url": SERVER_URL,
51
+ "workspace": workspace,
52
+ "client_id": "numpy-consumer-http-test",
53
+ "transport": "http",
54
+ "token": token,
55
+ })
56
+
57
+ try:
58
+ service = await http_server.get_service(f"{ws_server.config['client_id']}:numpy-service")
59
+
60
+ # Test 1: Send and receive numpy array
61
+ test_array = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32)
62
+ result = await service.process_array(test_array)
63
+
64
+ assert result["shape"] == [2, 3]
65
+ assert result["dtype"] == "float32"
66
+ assert result["sum"] == 21.0
67
+ assert result["mean"] == 3.5
68
+
69
+ result_array = result["result_array"]
70
+ assert isinstance(result_array, np.ndarray)
71
+ assert np.array_equal(result_array, test_array * 2)
72
+
73
+ # Test 2: Large array
74
+ large_array = np.random.rand(100, 100)
75
+ result2 = await service.process_array(large_array)
76
+ assert result2["shape"] == [100, 100]
77
+
78
+ # Test 3: Reshape operation
79
+ flat_array = np.arange(12)
80
+ reshaped = await service.reshape(flat_array, (3, 4))
81
+ assert reshaped.shape == (3, 4)
82
+ assert np.array_equal(reshaped, np.arange(12).reshape(3, 4))
83
+
84
+ finally:
85
+ await http_server.disconnect()
86
+
87
+ finally:
88
+ await ws_server.disconnect()
89
+
90
+ @pytest.mark.asyncio
91
+ async def test_http_nested_objects_transmission(self):
92
+ """Test transmitting nested complex objects over HTTP."""
93
+ ws_server = await connect_to_server({
94
+ "server_url": SERVER_URL,
95
+ "client_id": "nested-provider-ws-test",
96
+ })
97
+
98
+ try:
99
+ workspace = ws_server.config["workspace"]
100
+
101
+ # Service that handles nested objects
102
+ await ws_server.register_service({
103
+ "id": "nested-service",
104
+ "name": "Nested Object Service",
105
+ "config": {"visibility": "public"},
106
+ "process_nested": lambda data: {
107
+ "received_keys": list(data.keys()),
108
+ "array_sum": float(np.sum(data["array"])) if "array" in data else 0,
109
+ "nested_count": len(data.get("nested", {}).get("items", [])),
110
+ "echo": data,
111
+ },
112
+ })
113
+
114
+ token = await ws_server.generate_token()
115
+
116
+ http_server = await connect_to_server({
117
+ "server_url": SERVER_URL,
118
+ "workspace": workspace,
119
+ "client_id": "nested-consumer-http-test",
120
+ "transport": "http",
121
+ "token": token,
122
+ })
123
+
124
+ try:
125
+ service = await http_server.get_service(f"{ws_server.config['client_id']}:nested-service")
126
+
127
+ # Complex nested structure
128
+ test_data = {
129
+ "string": "test",
130
+ "number": 42,
131
+ "array": np.array([1, 2, 3, 4, 5]),
132
+ "nested": {
133
+ "items": [1, 2, 3],
134
+ "metadata": {
135
+ "name": "test_item",
136
+ "values": [10, 20, 30],
137
+ },
138
+ },
139
+ "list_of_arrays": [
140
+ np.array([1, 2]),
141
+ np.array([3, 4]),
142
+ ],
143
+ }
144
+
145
+ result = await service.process_nested(test_data)
146
+
147
+ assert set(result["received_keys"]) == set(test_data.keys())
148
+ assert result["array_sum"] == 15.0
149
+ assert result["nested_count"] == 3
150
+
151
+ # Verify echo preserves structure
152
+ echo = result["echo"]
153
+ assert echo["string"] == "test"
154
+ assert echo["number"] == 42
155
+ assert np.array_equal(echo["array"], test_data["array"])
156
+ assert echo["nested"]["metadata"]["name"] == "test_item"
157
+
158
+ finally:
159
+ await http_server.disconnect()
160
+
161
+ finally:
162
+ await ws_server.disconnect()
163
+
164
+ @pytest.mark.asyncio
165
+ async def test_http_callbacks_basic(self):
166
+ """Test basic callback functionality over HTTP transport."""
167
+ ws_server = await connect_to_server({
168
+ "server_url": SERVER_URL,
169
+ "client_id": "callback-provider-ws-test",
170
+ })
171
+
172
+ try:
173
+ workspace = ws_server.config["workspace"]
174
+
175
+ # Service that uses callbacks
176
+ async def call_multiple_times(callback, count):
177
+ results = []
178
+ for i in range(count):
179
+ result = await callback(i)
180
+ results.append(result)
181
+ return results
182
+
183
+ async def process_with_progress(data, progress_callback):
184
+ for i in range(len(data)):
185
+ await progress_callback({"step": i, "total": len(data)})
186
+ return sum(data)
187
+
188
+ await ws_server.register_service({
189
+ "id": "callback-service",
190
+ "name": "Callback Service",
191
+ "config": {"visibility": "public"},
192
+ "call_multiple_times": call_multiple_times,
193
+ "process_with_progress": process_with_progress,
194
+ })
195
+
196
+ token = await ws_server.generate_token()
197
+
198
+ http_server = await connect_to_server({
199
+ "server_url": SERVER_URL,
200
+ "workspace": workspace,
201
+ "client_id": "callback-consumer-http-test",
202
+ "transport": "http",
203
+ "token": token,
204
+ })
205
+
206
+ try:
207
+ service = await http_server.get_service(f"{ws_server.config['client_id']}:callback-service")
208
+
209
+ # Test 1: Simple callback
210
+ callback_results = []
211
+
212
+ def test_callback(value):
213
+ callback_results.append(value)
214
+ return value * 2
215
+
216
+ results = await service.call_multiple_times(test_callback, 5)
217
+ assert len(callback_results) == 5
218
+ assert callback_results == [0, 1, 2, 3, 4]
219
+ assert results == [0, 2, 4, 6, 8]
220
+
221
+ # Test 2: Progress callback
222
+ progress_updates = []
223
+
224
+ def progress_callback(info):
225
+ progress_updates.append(info)
226
+
227
+ test_data = [10, 20, 30, 40]
228
+ result = await service.process_with_progress(test_data, progress_callback)
229
+ assert result == 100
230
+ assert len(progress_updates) == 4
231
+ assert progress_updates[0] == {"step": 0, "total": 4}
232
+ assert progress_updates[-1] == {"step": 3, "total": 4}
233
+
234
+ finally:
235
+ await http_server.disconnect()
236
+
237
+ finally:
238
+ await ws_server.disconnect()
239
+
240
+ @pytest.mark.asyncio
241
+ async def test_http_async_callbacks(self):
242
+ """Test async callback functionality over HTTP transport."""
243
+ ws_server = await connect_to_server({
244
+ "server_url": SERVER_URL,
245
+ "client_id": "async-callback-provider-ws-test",
246
+ })
247
+
248
+ try:
249
+ workspace = ws_server.config["workspace"]
250
+
251
+ # Service with async callback support
252
+ async def process_async_callback(items, async_callback):
253
+ results = []
254
+ for item in items:
255
+ result = await async_callback(item)
256
+ results.append(result)
257
+ return results
258
+
259
+ await ws_server.register_service({
260
+ "id": "async-callback-service",
261
+ "name": "Async Callback Service",
262
+ "config": {"visibility": "public"},
263
+ "process_async": process_async_callback,
264
+ })
265
+
266
+ token = await ws_server.generate_token()
267
+
268
+ http_server = await connect_to_server({
269
+ "server_url": SERVER_URL,
270
+ "workspace": workspace,
271
+ "client_id": "async-callback-consumer-http-test",
272
+ "transport": "http",
273
+ "token": token,
274
+ })
275
+
276
+ try:
277
+ service = await http_server.get_service(f"{ws_server.config['client_id']}:async-callback-service")
278
+
279
+ # Async callback
280
+ async def async_transform(value):
281
+ await asyncio.sleep(0.01) # Simulate async work
282
+ return value ** 2
283
+
284
+ test_items = [1, 2, 3, 4, 5]
285
+ results = await service.process_async(test_items, async_transform)
286
+ assert results == [1, 4, 9, 16, 25]
287
+
288
+ finally:
289
+ await http_server.disconnect()
290
+
291
+ finally:
292
+ await ws_server.disconnect()
293
+
294
+ @pytest.mark.asyncio
295
+ async def test_http_callback_with_numpy(self):
296
+ """Test callbacks that pass numpy arrays over HTTP."""
297
+ ws_server = await connect_to_server({
298
+ "server_url": SERVER_URL,
299
+ "client_id": "numpy-callback-provider-ws-test",
300
+ })
301
+
302
+ try:
303
+ workspace = ws_server.config["workspace"]
304
+
305
+ # Service that sends arrays to callbacks
306
+ async def transform_batch(arrays, transform_callback):
307
+ results = []
308
+ for arr in arrays:
309
+ result = await transform_callback(arr)
310
+ results.append(result)
311
+ return results
312
+
313
+ await ws_server.register_service({
314
+ "id": "numpy-callback-service",
315
+ "name": "Numpy Callback Service",
316
+ "config": {"visibility": "public"},
317
+ "transform_batch": transform_batch,
318
+ })
319
+
320
+ token = await ws_server.generate_token()
321
+
322
+ http_server = await connect_to_server({
323
+ "server_url": SERVER_URL,
324
+ "workspace": workspace,
325
+ "client_id": "numpy-callback-consumer-http-test",
326
+ "transport": "http",
327
+ "token": token,
328
+ })
329
+
330
+ try:
331
+ service = await http_server.get_service(f"{ws_server.config['client_id']}:numpy-callback-service")
332
+
333
+ # Callback that processes numpy arrays
334
+ def array_processor(arr):
335
+ return {
336
+ "sum": float(np.sum(arr)),
337
+ "modified": arr * 3,
338
+ }
339
+
340
+ test_arrays = [
341
+ np.array([1, 2, 3]),
342
+ np.array([4, 5, 6]),
343
+ np.array([7, 8, 9]),
344
+ ]
345
+
346
+ results = await service.transform_batch(test_arrays, array_processor)
347
+
348
+ assert len(results) == 3
349
+ assert results[0]["sum"] == 6.0
350
+ assert results[1]["sum"] == 15.0
351
+ assert results[2]["sum"] == 24.0
352
+
353
+ assert np.array_equal(results[0]["modified"], np.array([3, 6, 9]))
354
+ assert np.array_equal(results[1]["modified"], np.array([12, 15, 18]))
355
+ assert np.array_equal(results[2]["modified"], np.array([21, 24, 27]))
356
+
357
+ finally:
358
+ await http_server.disconnect()
359
+
360
+ finally:
361
+ await ws_server.disconnect()
362
+
363
+ @pytest.mark.asyncio
364
+ async def test_http_binary_data_transmission(self):
365
+ """Test transmitting raw binary data over HTTP."""
366
+ ws_server = await connect_to_server({
367
+ "server_url": SERVER_URL,
368
+ "client_id": "binary-provider-ws-test",
369
+ })
370
+
371
+ try:
372
+ workspace = ws_server.config["workspace"]
373
+
374
+ # Service that handles binary data
375
+ await ws_server.register_service({
376
+ "id": "binary-service",
377
+ "name": "Binary Service",
378
+ "config": {"visibility": "public"},
379
+ "process_binary": lambda data: {
380
+ "length": len(data),
381
+ "first_bytes": data[:10],
382
+ "reversed": bytes(reversed(data)),
383
+ },
384
+ "concat_binary": lambda parts: b"".join(parts),
385
+ })
386
+
387
+ token = await ws_server.generate_token()
388
+
389
+ http_server = await connect_to_server({
390
+ "server_url": SERVER_URL,
391
+ "workspace": workspace,
392
+ "client_id": "binary-consumer-http-test",
393
+ "transport": "http",
394
+ "token": token,
395
+ })
396
+
397
+ try:
398
+ service = await http_server.get_service(f"{ws_server.config['client_id']}:binary-service")
399
+
400
+ # Test 1: Send binary data
401
+ test_data = b"Hello, World! This is binary data."
402
+ result = await service.process_binary(test_data)
403
+
404
+ assert result["length"] == len(test_data)
405
+ assert result["first_bytes"] == test_data[:10]
406
+ assert result["reversed"] == bytes(reversed(test_data))
407
+
408
+ # Test 2: Multiple binary chunks
409
+ parts = [b"Part1", b"Part2", b"Part3"]
410
+ concatenated = await service.concat_binary(parts)
411
+ assert concatenated == b"Part1Part2Part3"
412
+
413
+ finally:
414
+ await http_server.disconnect()
415
+
416
+ finally:
417
+ await ws_server.disconnect()
418
+
419
+
420
+ if __name__ == "__main__":
421
+ # Allow running tests directly
422
+ pytest.main([__file__, "-v", "-s"])
@@ -1,3 +0,0 @@
1
- {
2
- "version": "0.20.92"
3
- }
File without changes
File without changes
File without changes