xoscar 0.4.6__cp311-cp311-macosx_10_9_x86_64.whl → 0.6.0__cp311-cp311-macosx_10_9_x86_64.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.

Potentially problematic release.


This version of xoscar might be problematic. Click here for more details.

Binary file
xoscar/_utils.pyx CHANGED
@@ -208,28 +208,32 @@ def create_actor_ref(*args, **kwargs):
208
208
 
209
209
  address = to_str(kwargs.pop('address', None))
210
210
  uid = kwargs.pop('uid', None)
211
+ proxy_addresses = kwargs.pop("proxy_addresses", None)
211
212
 
212
213
  if kwargs:
213
214
  raise ValueError('Only `address` or `uid` keywords are supported')
214
215
 
215
- if len(args) == 2:
216
+ if 2 <= len(args) <= 3:
216
217
  if address:
217
218
  raise ValueError('address has been specified')
218
219
  address = to_str(args[0])
219
220
  uid = args[1]
221
+ if len(args) == 3:
222
+ proxy_addresses = args[2]
220
223
  elif len(args) == 1:
221
224
  tp0 = type(args[0])
222
225
  if tp0 is ActorRef or tp0 is LocalActorRef:
223
226
  existing_ref = <ActorRef>(args[0])
224
227
  uid = existing_ref.uid
225
228
  address = to_str(address or existing_ref.address)
229
+ proxy_addresses = existing_ref.proxy_addresses
226
230
  else:
227
231
  uid = args[0]
228
232
 
229
233
  if uid is None:
230
234
  raise ValueError('Actor uid should be provided')
231
235
 
232
- return ActorRef(address, uid)
236
+ return ActorRef(address, uid, proxy_addresses=proxy_addresses)
233
237
 
234
238
 
235
239
  cdef class Timer:
@@ -13,7 +13,7 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- from .base import Channel, Client, Server
16
+ from .base import Channel, ChannelType, Client, Server
17
17
  from .core import gen_local_address, get_client_type, get_server_type
18
18
  from .dummy import DummyChannel, DummyClient, DummyServer
19
19
  from .socket import (
@@ -315,7 +315,11 @@ class SocketClient(Client):
315
315
  except asyncio.TimeoutError:
316
316
  raise ConnectionError("connect timeout")
317
317
  channel = SocketChannel(
318
- reader, writer, local_address=local_address, dest_address=dest_address
318
+ reader,
319
+ writer,
320
+ local_address=local_address,
321
+ dest_address=dest_address,
322
+ channel_type=ChannelType.remote,
319
323
  )
320
324
  return SocketClient(local_address, dest_address, channel)
321
325
 
@@ -431,6 +435,10 @@ class UnixSocketClient(Client):
431
435
  "Cannot connect unix socket due to file not exists"
432
436
  )
433
437
  channel = SocketChannel(
434
- reader, writer, local_address=local_address, dest_address=dest_address
438
+ reader,
439
+ writer,
440
+ local_address=local_address,
441
+ dest_address=dest_address,
442
+ channel_type=ChannelType.ipc,
435
443
  )
436
444
  return UnixSocketClient(local_address, dest_address, channel)
xoscar/backends/config.py CHANGED
@@ -33,6 +33,8 @@ class ActorPoolConfig:
33
33
  self._conf["metrics"] = dict()
34
34
  if "comm" not in self._conf:
35
35
  self._conf["comm"] = dict()
36
+ if "proxy" not in self._conf:
37
+ self._conf["proxy"] = dict()
36
38
 
37
39
  @property
38
40
  def n_pool(self):
@@ -143,3 +145,13 @@ class ActorPoolConfig:
143
145
 
144
146
  def get_comm_config(self) -> dict:
145
147
  return self._conf["comm"]
148
+
149
+ def get_proxy_config(self) -> dict:
150
+ return self._conf["proxy"]
151
+
152
+ def add_proxy_config(self, proxy_config: dict[str, str] | None):
153
+ if proxy_config:
154
+ self._conf["proxy"].update(proxy_config)
155
+
156
+ def remove_proxy(self, from_addr: str):
157
+ del self._conf["proxy"][from_addr]
@@ -72,15 +72,24 @@ class IndigenActorContext(BaseActorContext):
72
72
  self._caller.cancel_tasks()
73
73
 
74
74
  async def _call(
75
- self, address: str, message: _MessageBase, wait: bool = True
75
+ self,
76
+ address: str,
77
+ message: _MessageBase,
78
+ wait: bool = True,
79
+ proxy_addresses: list[str] | None = None,
76
80
  ) -> Union[ResultMessage, ErrorMessage, asyncio.Future]:
77
81
  return await self._caller.call(
78
- Router.get_instance_or_empty(), address, message, wait=wait
82
+ Router.get_instance_or_empty(),
83
+ address,
84
+ message,
85
+ wait=wait,
86
+ proxy_addresses=proxy_addresses,
79
87
  )
80
88
 
81
89
  async def _call_with_client(
82
90
  self, client: Client, message: _MessageBase, wait: bool = True
83
91
  ) -> Union[ResultMessage, ErrorMessage, asyncio.Future]:
92
+ # NOTE: used by copyto, cannot support proxy
84
93
  return await self._caller.call_with_client(client, message, wait)
85
94
 
86
95
  async def _call_send_buffers(
@@ -146,7 +155,12 @@ class IndigenActorContext(BaseActorContext):
146
155
  message = HasActorMessage(
147
156
  new_message_id(), actor_ref, protocol=DEFAULT_PROTOCOL
148
157
  )
149
- future = await self._call(actor_ref.address, message, wait=False)
158
+ future = await self._call(
159
+ actor_ref.address,
160
+ message,
161
+ wait=False,
162
+ proxy_addresses=actor_ref.proxy_addresses,
163
+ )
150
164
  result = await self._wait(future, actor_ref.address, message) # type: ignore
151
165
  return self._process_result_message(result)
152
166
 
@@ -154,7 +168,12 @@ class IndigenActorContext(BaseActorContext):
154
168
  message = DestroyActorMessage(
155
169
  new_message_id(), actor_ref, protocol=DEFAULT_PROTOCOL
156
170
  )
157
- future = await self._call(actor_ref.address, message, wait=False)
171
+ future = await self._call(
172
+ actor_ref.address,
173
+ message,
174
+ wait=False,
175
+ proxy_addresses=actor_ref.proxy_addresses,
176
+ )
158
177
  result = await self._wait(future, actor_ref.address, message) # type: ignore
159
178
  return self._process_result_message(result)
160
179
 
@@ -168,7 +187,7 @@ class IndigenActorContext(BaseActorContext):
168
187
  protocol=DEFAULT_PROTOCOL,
169
188
  )
170
189
  main_address = self._process_result_message(
171
- await self._call(actor_ref.address, control_message) # type: ignore
190
+ await self._call(actor_ref.address, control_message, proxy_addresses=actor_ref.proxy_addresses) # type: ignore
172
191
  )
173
192
  real_actor_ref = await self.actor_ref(actor_ref)
174
193
  if real_actor_ref.address == main_address:
@@ -182,7 +201,9 @@ class IndigenActorContext(BaseActorContext):
182
201
  protocol=DEFAULT_PROTOCOL,
183
202
  )
184
203
  # stop server
185
- result = await self._call(main_address, stop_message)
204
+ result = await self._call(
205
+ main_address, stop_message, proxy_addresses=actor_ref.proxy_addresses
206
+ )
186
207
  return self._process_result_message(result) # type: ignore
187
208
 
188
209
  async def actor_ref(self, *args, **kwargs):
@@ -194,7 +215,12 @@ class IndigenActorContext(BaseActorContext):
194
215
  message = ActorRefMessage(
195
216
  new_message_id(), actor_ref, protocol=DEFAULT_PROTOCOL
196
217
  )
197
- future = await self._call(actor_ref.address, message, wait=False)
218
+ future = await self._call(
219
+ actor_ref.address,
220
+ message,
221
+ wait=False,
222
+ proxy_addresses=actor_ref.proxy_addresses,
223
+ )
198
224
  result = await self._wait(future, actor_ref.address, message)
199
225
  res = self._process_result_message(result)
200
226
  if res.address != connect_addr:
@@ -225,7 +251,12 @@ class IndigenActorContext(BaseActorContext):
225
251
  actor_ref.address,
226
252
  ):
227
253
  detect_cycle_send(send_message, wait_response)
228
- future = await self._call(actor_ref.address, send_message, wait=False)
254
+ future = await self._call(
255
+ actor_ref.address,
256
+ send_message,
257
+ wait=False,
258
+ proxy_addresses=actor_ref.proxy_addresses,
259
+ )
229
260
  if wait_response:
230
261
  result = await self._wait(future, actor_ref.address, send_message) # type: ignore
231
262
  return self._process_result_message(result)
@@ -315,6 +346,8 @@ class IndigenActorContext(BaseActorContext):
315
346
  async def _get_client(self, address: str) -> Client:
316
347
  router = Router.get_instance()
317
348
  assert router is not None, "`copy_to` can only be used inside pools"
349
+ if router.get_proxy(address):
350
+ raise RuntimeError("Cannot run `copy_to` when enabling proxy")
318
351
  return await self._get_copy_to_client(router, address)
319
352
 
320
353
  async def copy_to_buffers(
xoscar/backends/core.py CHANGED
@@ -26,8 +26,15 @@ from typing import Type, Union
26
26
  from .._utils import Timer
27
27
  from ..errors import ServerClosed
28
28
  from ..profiling import get_profiling_data
29
- from .communication import Client, UCXClient
30
- from .message import DeserializeMessageFailed, ErrorMessage, ResultMessage, _MessageBase
29
+ from .communication import ChannelType, Client, UCXClient
30
+ from .message import (
31
+ DeserializeMessageFailed,
32
+ ErrorMessage,
33
+ ForwardMessage,
34
+ MessageType,
35
+ ResultMessage,
36
+ _MessageBase,
37
+ )
31
38
  from .router import Router
32
39
 
33
40
  ResultMessageType = Union[ResultMessage, ErrorMessage]
@@ -67,8 +74,15 @@ class ActorCallerThreadLocal:
67
74
  self._listen_client(client)
68
75
  return client
69
76
 
70
- async def get_client(self, router: Router, dest_address: str) -> Client:
71
- client = await router.get_client(dest_address, from_who=self)
77
+ async def get_client(
78
+ self,
79
+ router: Router,
80
+ dest_address: str,
81
+ proxy_addresses: list[str] | None = None,
82
+ ) -> Client:
83
+ client = await router.get_client(
84
+ dest_address, from_who=self, proxy_addresses=proxy_addresses
85
+ )
72
86
  self._listen_client(client)
73
87
  return client
74
88
 
@@ -191,8 +205,20 @@ class ActorCallerThreadLocal:
191
205
  dest_address: str,
192
206
  message: _MessageBase,
193
207
  wait: bool = True,
208
+ proxy_addresses: list[str] | None = None,
194
209
  ) -> ResultMessage | ErrorMessage | asyncio.Future:
195
- client = await self.get_client(router, dest_address)
210
+ client = await self.get_client(
211
+ router, dest_address, proxy_addresses=proxy_addresses
212
+ )
213
+ if (
214
+ client.channel_type == ChannelType.remote
215
+ and client.dest_address != dest_address
216
+ and message.message_type != MessageType.control
217
+ ):
218
+ # wrap message with forward message
219
+ message = ForwardMessage(
220
+ message_id=message.message_id, address=dest_address, raw_message=message
221
+ )
196
222
  return await self.call_with_client(client, message, wait)
197
223
 
198
224
  async def stop(self):
@@ -47,6 +47,7 @@ class MessageType(Enum):
47
47
  cancel = 9
48
48
  copy_to_buffers = 10
49
49
  copy_to_fileobjs = 11
50
+ forward = 12
50
51
 
51
52
 
52
53
  class ControlMessageType(Enum):
@@ -350,13 +351,14 @@ cdef class DestroyActorMessage(_MessageBase):
350
351
  cdef _MessageSerialItem serial(self):
351
352
  cdef _MessageSerialItem item = _MessageBase.serial(self)
352
353
  item.serialized += (
353
- self.actor_ref.address, self.actor_ref.uid, self.from_main
354
+ self.actor_ref.address, self.actor_ref.uid,
355
+ self.actor_ref.proxy_addresses, self.from_main
354
356
  )
355
357
  return item
356
358
 
357
359
  cdef deserial_members(self, tuple serialized, list subs):
358
360
  _MessageBase.deserial_members(self, serialized, subs)
359
- self.actor_ref = ActorRef(serialized[-3], serialized[-2])
361
+ self.actor_ref = ActorRef(serialized[-4], serialized[-3], serialized[-2])
360
362
  self.from_main = serialized[-1]
361
363
 
362
364
 
@@ -381,13 +383,13 @@ cdef class HasActorMessage(_MessageBase):
381
383
  cdef _MessageSerialItem serial(self):
382
384
  cdef _MessageSerialItem item = _MessageBase.serial(self)
383
385
  item.serialized += (
384
- self.actor_ref.address, self.actor_ref.uid
386
+ self.actor_ref.address, self.actor_ref.uid, self.actor_ref.proxy_addresses
385
387
  )
386
388
  return item
387
389
 
388
390
  cdef deserial_members(self, tuple serialized, list subs):
389
391
  _MessageBase.deserial_members(self, serialized, subs)
390
- self.actor_ref = ActorRef(serialized[-2], serialized[-1])
392
+ self.actor_ref = ActorRef(serialized[-3], serialized[-2], serialized[-1])
391
393
 
392
394
 
393
395
  cdef class ActorRefMessage(_MessageBase):
@@ -411,13 +413,13 @@ cdef class ActorRefMessage(_MessageBase):
411
413
  cdef _MessageSerialItem serial(self):
412
414
  cdef _MessageSerialItem item = _MessageBase.serial(self)
413
415
  item.serialized += (
414
- self.actor_ref.address, self.actor_ref.uid
416
+ self.actor_ref.address, self.actor_ref.uid, self.actor_ref.proxy_addresses
415
417
  )
416
418
  return item
417
419
 
418
420
  cdef deserial_members(self, tuple serialized, list subs):
419
421
  _MessageBase.deserial_members(self, serialized, subs)
420
- self.actor_ref = ActorRef(serialized[-2], serialized[-1])
422
+ self.actor_ref = ActorRef(serialized[-3], serialized[-2], serialized[-1])
421
423
 
422
424
 
423
425
  cdef class SendMessage(_MessageBase):
@@ -449,14 +451,14 @@ cdef class SendMessage(_MessageBase):
449
451
  cdef _MessageSerialItem serial(self):
450
452
  cdef _MessageSerialItem item = _MessageBase.serial(self)
451
453
  item.serialized += (
452
- self.actor_ref.address, self.actor_ref.uid
454
+ self.actor_ref.address, self.actor_ref.uid, self.actor_ref.proxy_addresses
453
455
  )
454
456
  item.subs = [self.content]
455
457
  return item
456
458
 
457
459
  cdef deserial_members(self, tuple serialized, list subs):
458
460
  _MessageBase.deserial_members(self, serialized, subs)
459
- self.actor_ref = ActorRef(serialized[-2], serialized[-1])
461
+ self.actor_ref = ActorRef(serialized[-3], serialized[-2], serialized[-1])
460
462
  self.content = subs[0]
461
463
 
462
464
 
@@ -533,6 +535,50 @@ cdef class CopyToFileObjectsMessage(CopyToBuffersMessage):
533
535
  message_type = MessageType.copy_to_fileobjs
534
536
 
535
537
 
538
+ cdef class ForwardMessage(_MessageBase):
539
+ message_type = MessageType.forward
540
+
541
+ cdef:
542
+ public str address
543
+ public _MessageBase raw_message
544
+
545
+ def __init__(
546
+ self,
547
+ bytes message_id = None,
548
+ str address = None,
549
+ _MessageBase raw_message = None,
550
+ int protocol = DEFAULT_PROTOCOL,
551
+ list message_trace = None,
552
+ ):
553
+ _MessageBase.__init__(
554
+ self,
555
+ message_id,
556
+ protocol=protocol,
557
+ message_trace=message_trace
558
+ )
559
+ self.address = address
560
+ self.raw_message = raw_message
561
+
562
+ cdef _MessageSerialItem serial(self):
563
+ cdef _MessageSerialItem item = _MessageBase.serial(self)
564
+ cdef _MessageSerialItem raw_message_serialized = self.raw_message.serial()
565
+ item.serialized += (self.address,)
566
+ item.serialized += raw_message_serialized.serialized
567
+ item.subs += raw_message_serialized.subs
568
+ return item
569
+
570
+ cdef deserial_members(self, tuple serialized, list subs):
571
+ # 5 is magic number that means serialized for _MessageBase
572
+ base_serialized = serialized[:5]
573
+ _MessageBase.deserial_members(self, base_serialized, [])
574
+ self.address = serialized[5]
575
+ # process raw message
576
+ tp = _message_type_to_message_cls[serialized[6]]
577
+ cdef _MessageBase raw_message = <_MessageBase>(tp())
578
+ raw_message.deserial_members(serialized[6:], subs)
579
+ self.raw_message = raw_message
580
+
581
+
536
582
  cdef dict _message_type_to_message_cls = {
537
583
  MessageType.control.value: ControlMessage,
538
584
  MessageType.result.value: ResultMessage,
@@ -545,7 +591,8 @@ cdef dict _message_type_to_message_cls = {
545
591
  MessageType.tell.value: TellMessage,
546
592
  MessageType.cancel.value: CancelMessage,
547
593
  MessageType.copy_to_buffers.value: CopyToBuffersMessage,
548
- MessageType.copy_to_fileobjs.value: CopyToFileObjectsMessage
594
+ MessageType.copy_to_fileobjs.value: CopyToFileObjectsMessage,
595
+ MessageType.forward.value: ForwardMessage,
549
596
  }
550
597
 
551
598
 
xoscar/backends/pool.py CHANGED
@@ -22,6 +22,7 @@ import itertools
22
22
  import logging
23
23
  import multiprocessing
24
24
  import os
25
+ import sys
25
26
  import threading
26
27
  import traceback
27
28
  from abc import ABC, ABCMeta, abstractmethod
@@ -62,6 +63,7 @@ from .message import (
62
63
  CreateActorMessage,
63
64
  DestroyActorMessage,
64
65
  ErrorMessage,
66
+ ForwardMessage,
65
67
  HasActorMessage,
66
68
  MessageType,
67
69
  ResultMessage,
@@ -123,6 +125,7 @@ def _register_message_handler(pool_type: Type["AbstractActorPool"]):
123
125
  (MessageType.send, pool_type.send),
124
126
  (MessageType.tell, pool_type.tell),
125
127
  (MessageType.cancel, pool_type.cancel),
128
+ (MessageType.forward, pool_type.forward),
126
129
  (MessageType.control, pool_type.handle_control_command),
127
130
  (MessageType.copy_to_buffers, pool_type.handle_copy_to_buffers_message),
128
131
  (MessageType.copy_to_fileobjs, pool_type.handle_copy_to_fileobjs_message),
@@ -311,6 +314,22 @@ class AbstractActorPool(ABC):
311
314
  result or error message
312
315
  """
313
316
 
317
+ async def forward(self, message: ForwardMessage) -> ResultMessageType:
318
+ """
319
+ Forward message
320
+
321
+ Parameters
322
+ ----------
323
+ message: ForwardMessage
324
+ Forward message.
325
+
326
+ Returns
327
+ -------
328
+ result_message
329
+ result or error message
330
+ """
331
+ return await self.call(message.address, message.raw_message)
332
+
314
333
  def _sync_pool_config(self, actor_pool_config: ActorPoolConfig):
315
334
  self._config = actor_pool_config
316
335
  # remove router from global one
@@ -443,6 +462,7 @@ class AbstractActorPool(ABC):
443
462
  gen_local_address(process_index),
444
463
  actor_pool_config.external_to_internal_address_map,
445
464
  comm_config=actor_pool_config.get_comm_config(),
465
+ proxy_config=actor_pool_config.get_proxy_config(),
446
466
  )
447
467
  kw["env"] = curr_pool_config["env"]
448
468
 
@@ -605,7 +625,8 @@ class ActorPoolBase(AbstractActorPool, metaclass=ABCMeta):
605
625
  self._actors[actor_id] = actor
606
626
  await self._run_coro(message.message_id, actor.__post_create__())
607
627
 
608
- result = ActorRef(address, actor_id)
628
+ proxies = self._router.get_proxies(address)
629
+ result = ActorRef(address, actor_id, proxy_addresses=proxies)
609
630
  # ensemble result message
610
631
  processor.result = ResultMessage(
611
632
  message.message_id, result, protocol=message.protocol
@@ -647,9 +668,10 @@ class ActorPoolBase(AbstractActorPool, metaclass=ABCMeta):
647
668
  actor_id = message.actor_ref.uid
648
669
  if actor_id not in self._actors:
649
670
  raise ActorNotExist(f"Actor {actor_id} does not exist")
671
+ proxies = self._router.get_proxies(self.external_address)
650
672
  result = ResultMessage(
651
673
  message.message_id,
652
- ActorRef(self.external_address, actor_id),
674
+ ActorRef(self.external_address, actor_id, proxy_addresses=proxies),
653
675
  protocol=message.protocol,
654
676
  )
655
677
  processor.result = result
@@ -762,6 +784,7 @@ class ActorPoolBase(AbstractActorPool, metaclass=ABCMeta):
762
784
  gen_local_address(process_index),
763
785
  actor_pool_config.external_to_internal_address_map,
764
786
  comm_config=actor_pool_config.get_comm_config(),
787
+ proxy_config=actor_pool_config.get_proxy_config(),
765
788
  )
766
789
 
767
790
  @classmethod
@@ -802,6 +825,9 @@ class ActorPoolBase(AbstractActorPool, metaclass=ABCMeta):
802
825
  with _disable_log_temporally():
803
826
  TypeDispatcher.reload_all_lazy_handlers()
804
827
 
828
+ if "PYTHONPATH" in os.environ:
829
+ sys.path.insert(0, os.environ["PYTHONPATH"])
830
+
805
831
  def handle_channel(channel):
806
832
  return pool.on_new_channel(channel)
807
833
 
@@ -1155,6 +1181,7 @@ class MainActorPoolBase(ActorPoolBase):
1155
1181
  actor_ref = message.actor_ref
1156
1182
  actor_ref.uid = to_binary(actor_ref.uid)
1157
1183
  if actor_ref.address == self.external_address and actor_ref.uid in self._actors:
1184
+ actor_ref.proxy_addresses = self._router.get_proxies(actor_ref.address)
1158
1185
  return ResultMessage(
1159
1186
  message.message_id, actor_ref, protocol=message.protocol
1160
1187
  )
@@ -1163,6 +1190,7 @@ class MainActorPoolBase(ActorPoolBase):
1163
1190
  for address, item in self._allocated_actors.items():
1164
1191
  ref = create_actor_ref(address, actor_ref.uid)
1165
1192
  if ref in item:
1193
+ ref.proxy_addresses = self._router.get_proxies(ref.address)
1166
1194
  return ResultMessage(message.message_id, ref, protocol=message.protocol)
1167
1195
 
1168
1196
  with _ErrorProcessor(
@@ -1503,6 +1531,7 @@ async def create_actor_pool(
1503
1531
  suspend_sigint: bool | None = None,
1504
1532
  use_uvloop: str | bool = "auto",
1505
1533
  logging_conf: dict | None = None,
1534
+ proxy_conf: dict | None = None,
1506
1535
  on_process_down: Callable[[MainActorPoolType, str], None] | None = None,
1507
1536
  on_process_recover: Callable[[MainActorPoolType, str], None] | None = None,
1508
1537
  extra_conf: dict | None = None,
@@ -1549,6 +1578,8 @@ async def create_actor_pool(
1549
1578
  )
1550
1579
  actor_pool_config = ActorPoolConfig()
1551
1580
  actor_pool_config.add_metric_configs(kwargs.get("metrics", {}))
1581
+ # add proxy config
1582
+ actor_pool_config.add_proxy_config(proxy_conf)
1552
1583
  # add main config
1553
1584
  process_index_gen = pool_cls.process_index_gen(address)
1554
1585
  main_process_index = next(process_index_gen)
xoscar/backends/router.py CHANGED
@@ -17,10 +17,15 @@ from __future__ import annotations
17
17
 
18
18
  import asyncio
19
19
  import threading
20
- from typing import Any, Dict, List, Optional, Type
20
+ from typing import Any, Optional, Type, Union
21
21
 
22
22
  from .communication import Client, get_client_type
23
23
 
24
+ _CACHE_KEY_TYPE = Union[
25
+ tuple[str, Any, Optional[Type[Client]]],
26
+ tuple[str, Any, Optional[Type[Client]], Optional[tuple[str, ...]]],
27
+ ]
28
+
24
29
 
25
30
  class Router:
26
31
  """
@@ -32,6 +37,7 @@ class Router:
32
37
  "_local_mapping",
33
38
  "_mapping",
34
39
  "_comm_config",
40
+ "_proxy_config",
35
41
  "_cache_local",
36
42
  )
37
43
 
@@ -56,6 +62,7 @@ class Router:
56
62
  local_address: str | None,
57
63
  mapping: dict[str, str] | None = None,
58
64
  comm_config: dict | None = None,
65
+ proxy_config: dict | None = None,
59
66
  ):
60
67
  self._curr_external_addresses = external_addresses
61
68
  self._local_mapping = dict()
@@ -65,10 +72,11 @@ class Router:
65
72
  mapping = dict()
66
73
  self._mapping = mapping
67
74
  self._comm_config = comm_config or dict()
75
+ self._proxy_config = proxy_config or dict()
68
76
  self._cache_local = threading.local()
69
77
 
70
78
  @property
71
- def _cache(self) -> dict[tuple[str, Any, Optional[Type[Client]]], Client]:
79
+ def _cache(self) -> dict[_CACHE_KEY_TYPE, Client]:
72
80
  try:
73
81
  return self._cache_local.cache
74
82
  except AttributeError:
@@ -92,6 +100,7 @@ class Router:
92
100
  self._local_mapping.update(router._local_mapping)
93
101
  self._mapping.update(router._mapping)
94
102
  self._comm_config.update(router._comm_config)
103
+ self._proxy_config.update(router._proxy_config)
95
104
  self._cache_local = threading.local()
96
105
 
97
106
  def remove_router(self, router: "Router"):
@@ -124,14 +133,23 @@ class Router:
124
133
  external_address: str,
125
134
  from_who: Any = None,
126
135
  cached: bool = True,
136
+ proxy_addresses: list[str] | None = None,
127
137
  **kw,
128
138
  ) -> Client:
129
139
  async with self._lock:
130
- if cached and (external_address, from_who, None) in self._cache:
131
- cached_client = self._cache[external_address, from_who, None]
140
+ proxy_addrs: tuple[str, ...] | None = (
141
+ tuple(proxy_addresses) if proxy_addresses else None
142
+ )
143
+ if (
144
+ cached
145
+ and (external_address, from_who, None, proxy_addrs) in self._cache
146
+ ):
147
+ cached_client = self._cache[
148
+ external_address, from_who, None, proxy_addrs
149
+ ]
132
150
  if cached_client.closed:
133
151
  # closed before, ignore it
134
- del self._cache[external_address, from_who, None]
152
+ del self._cache[external_address, from_who, None, proxy_addrs]
135
153
  else:
136
154
  return cached_client
137
155
 
@@ -139,10 +157,22 @@ class Router:
139
157
  if address is None:
140
158
  # no inner address, just use external address
141
159
  address = external_address
160
+ # check if proxy address exists
161
+ proxy_address = proxy_addresses[-1] if proxy_addresses else None
162
+ if proxy_address is None:
163
+ proxy_address = self.get_proxy(address)
164
+ if proxy_address and proxy_address != self.external_address:
165
+ address = proxy_address
166
+ else:
167
+ if new_proxy_address := self.get_proxy(proxy_address):
168
+ address = new_proxy_address
169
+ else:
170
+ address = proxy_address
171
+
142
172
  client_type: Type[Client] = get_client_type(address)
143
173
  client = await self._create_client(client_type, address, **kw)
144
174
  if cached:
145
- self._cache[external_address, from_who, None] = client
175
+ self._cache[external_address, from_who, None, proxy_addrs] = client
146
176
  return client
147
177
 
148
178
  async def _create_client(
@@ -158,7 +188,7 @@ class Router:
158
188
 
159
189
  def _get_client_type_to_addresses(
160
190
  self, external_address: str
161
- ) -> Dict[Type[Client], str]:
191
+ ) -> dict[Type[Client], str]:
162
192
  client_type_to_addresses = dict()
163
193
  client_type_to_addresses[get_client_type(external_address)] = external_address
164
194
  if external_address in self._curr_external_addresses: # pragma: no cover
@@ -173,7 +203,7 @@ class Router:
173
203
  client_type_to_addresses[client_type] = addr # type: ignore
174
204
  return client_type_to_addresses
175
205
 
176
- def get_all_client_types(self, external_address: str) -> List[Type[Client]]:
206
+ def get_all_client_types(self, external_address: str) -> list[Type[Client]]:
177
207
  return list(self._get_client_type_to_addresses(external_address))
178
208
 
179
209
  async def get_client_via_type(
@@ -205,3 +235,51 @@ class Router:
205
235
  if cached:
206
236
  self._cache[external_address, from_who, client_type] = client
207
237
  return client
238
+
239
+ def get_proxy(self, from_addr: str) -> str | None:
240
+ """
241
+ Get proxy address that sent to.
242
+
243
+ Some patterns can be supported:
244
+
245
+ 1. Direct address mapping, e.g. mapping 127.0.0.1:12345 -> 127.0.0.1:12346
246
+ The message will be sent to 127.0.0.1:12346 as forward one.
247
+ 2. Host match, e.g. mapping 127.0.0.1 -> 127.0.0.1:12346
248
+ All the messages that match the host, e.g. 127.0.0.1:12345 and 127.0.0.1:12347
249
+ will be sent to 127.0.0.1:12346 as forward one.
250
+ 3. Wildcard, e.g. mapping * -> 127.0.0.1:12346
251
+ All the messages will be sent to 127.0.0.1:12346 as forward one.
252
+ """
253
+
254
+ host = from_addr.split(":", 1)[0]
255
+
256
+ proxy_map = self._proxy_config
257
+ addr = proxy_map.get(from_addr)
258
+ if addr and addr != from_addr:
259
+ return addr
260
+ addr = proxy_map.get(host)
261
+ if addr and addr != from_addr:
262
+ return addr
263
+ addr = proxy_map.get("*")
264
+ if addr and addr != from_addr:
265
+ return addr
266
+ return None
267
+
268
+ def get_proxies(self, from_addr: str) -> list[str] | None:
269
+ """
270
+ Get all proxies
271
+
272
+ e.g. Proxy mapping {'a': 'b', 'b': 'c'}
273
+ get_proxies('a') will return ['b', 'c']
274
+ """
275
+
276
+ proxies: list[str] = []
277
+ while True:
278
+ proxy = self.get_proxy(from_addr)
279
+ if not proxies and not proxy:
280
+ return None
281
+ elif not proxy:
282
+ return proxies
283
+ else:
284
+ proxies.append(proxy)
285
+ from_addr = proxy
Binary file
Binary file
xoscar/core.pxd CHANGED
@@ -18,6 +18,7 @@ cdef class ActorRef:
18
18
  cdef object __weakref__
19
19
  cdef public str address
20
20
  cdef public object uid
21
+ cdef public list proxy_addresses
21
22
  cdef dict _methods
22
23
 
23
24
 
xoscar/core.pyx CHANGED
@@ -95,7 +95,7 @@ cpdef create_local_actor_ref(address, uid):
95
95
  return None
96
96
 
97
97
 
98
- cpdef create_actor_ref(address, uid):
98
+ cpdef create_actor_ref(address, uid, list proxy_addresses = None):
99
99
  """
100
100
  Create an actor reference.
101
101
  TODO(fyrestone): Remove the create_actor_ref in _utils.pyx
@@ -105,18 +105,20 @@ cpdef create_actor_ref(address, uid):
105
105
  ActorRef or LocalActorRef
106
106
  """
107
107
  actor = _get_local_actor(address, uid)
108
- return ActorRef(address, uid) if actor is None else LocalActorRef(actor)
108
+ return ActorRef(address, uid, proxy_addresses=proxy_addresses) \
109
+ if actor is None else LocalActorRef(actor)
109
110
 
110
111
 
111
112
  cdef class ActorRef:
112
113
  """
113
114
  Reference of an Actor at user side
114
115
  """
115
- def __init__(self, str address, object uid):
116
+ def __init__(self, str address, object uid, list proxy_addresses = None):
116
117
  if isinstance(uid, str):
117
118
  uid = uid.encode()
118
119
  self.uid = uid
119
120
  self.address = address
121
+ self.proxy_addresses = proxy_addresses
120
122
  self._methods = dict()
121
123
 
122
124
  def destroy(self, object callback=None):
@@ -124,7 +126,7 @@ cdef class ActorRef:
124
126
  return ctx.destroy_actor(self)
125
127
 
126
128
  def __reduce__(self):
127
- return create_actor_ref, (self.address, self.uid)
129
+ return create_actor_ref, (self.address, self.uid, self.proxy_addresses)
128
130
 
129
131
  def __getattr__(self, item):
130
132
  if item.startswith('_') and item not in ["__xoscar_next__", "__xoscar_destroy_generator__"]:
@@ -146,7 +148,11 @@ cdef class ActorRef:
146
148
  return False
147
149
 
148
150
  def __repr__(self):
149
- return 'ActorRef(uid={!r}, address={!r})'.format(self.uid, self.address)
151
+ if not self.proxy_addresses:
152
+ return 'ActorRef(uid={!r}, address={!r})'.format(self.uid, self.address)
153
+ else:
154
+ return (f"ActorRef(uid={self.uid}, address={self.address}, "
155
+ f"proxy_addresses={self.proxy_addresses})")
150
156
 
151
157
 
152
158
  cdef class _DelayedArgument:
@@ -13,8 +13,8 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- from . import cuda, exception, numpy, scipy
16
+ from . import cuda, exception, mlx, numpy, scipy
17
17
  from .aio import AioDeserializer, AioSerializer
18
18
  from .core import Serializer, deserialize, serialize, serialize_with_spawn
19
19
 
20
- del cuda, numpy, scipy, exception
20
+ del cuda, numpy, scipy, mlx, exception
@@ -0,0 +1,63 @@
1
+ # Copyright 2022-2025 XProbe Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from typing import Any, List
16
+
17
+ import numpy as np
18
+
19
+ from ..utils import lazy_import
20
+ from .core import Serializer, buffered
21
+
22
+ mx = lazy_import("mlx.core")
23
+
24
+
25
+ dtype_map = {
26
+ "b": np.int8,
27
+ "B": np.uint8,
28
+ "h": np.int16,
29
+ "H": np.uint16,
30
+ "i": np.int32,
31
+ "I": np.uint32,
32
+ "q": np.int64,
33
+ "Q": np.uint64,
34
+ "e": np.float16,
35
+ "f": np.float32,
36
+ "d": np.float64,
37
+ }
38
+
39
+
40
+ class MLXSerislizer(Serializer):
41
+ @buffered
42
+ def serial(self, obj: "mx.array", context: dict): # type: ignore
43
+ mv = memoryview(obj)
44
+ header = dict(shape=mv.shape, format=mv.format)
45
+ if not mv.c_contiguous:
46
+ mv = memoryview(bytes(mv))
47
+ return (header,), [mv], True
48
+
49
+ def deserial(self, serialized: tuple, context: dict, subs: List[Any]):
50
+ header = serialized[0]
51
+ shape, format = header["shape"], header["format"]
52
+ mv = memoryview(subs[0])
53
+ if mv.format != format:
54
+ dtype = dtype_map.get(format, np.uint8)
55
+ np_arr = np.frombuffer(mv, dtype=dtype).reshape(shape) # parse
56
+ mv = memoryview(np_arr) # recreate memoryview
57
+ elif mv.shape != shape:
58
+ mv = mv.cast(format, shape) # cast directly
59
+ return mx.array(mv)
60
+
61
+
62
+ if mx is not None:
63
+ MLXSerislizer.register(mx.array)
@@ -0,0 +1,34 @@
1
+ # Copyright 2022-2025 XProbe Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from pathlib import Path
18
+
19
+ from .core import VirtualEnvManager
20
+ from .uv import UVVirtualEnvManager
21
+
22
+ _name_to_managers = {"uv": UVVirtualEnvManager}
23
+
24
+
25
+ def get_virtual_env_manager(env_name: str, env_path: str | Path) -> VirtualEnvManager:
26
+ try:
27
+ manager_cls = _name_to_managers[env_name]
28
+ except KeyError:
29
+ raise ValueError(
30
+ f"Unknown virtualenv manager {env_name}, available: {list(_name_to_managers)}"
31
+ )
32
+
33
+ path = Path(env_path)
34
+ return manager_cls(path)
@@ -0,0 +1,48 @@
1
+ # Copyright 2022-2025 XProbe Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ from abc import ABC, abstractmethod
18
+ from pathlib import Path
19
+
20
+
21
+ class VirtualEnvManager(ABC):
22
+ @classmethod
23
+ @abstractmethod
24
+ def is_available(cls):
25
+ pass
26
+
27
+ def __init__(self, env_path: Path):
28
+ self.env_path = env_path.resolve()
29
+
30
+ @abstractmethod
31
+ def create_env(self, python_path: Path | None = None) -> None:
32
+ pass
33
+
34
+ @abstractmethod
35
+ def install_packages(self, packages: list[str], **kwargs):
36
+ pass
37
+
38
+ @abstractmethod
39
+ def cancel_install(self):
40
+ pass
41
+
42
+ @abstractmethod
43
+ def get_lib_path(self) -> str:
44
+ pass
45
+
46
+ @abstractmethod
47
+ def remove_env(self):
48
+ pass
@@ -0,0 +1,85 @@
1
+ # Copyright 2022-2025 XProbe Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import shutil
18
+ import subprocess
19
+ import sysconfig
20
+ from pathlib import Path
21
+ from typing import Optional
22
+
23
+ from .core import VirtualEnvManager
24
+
25
+
26
+ class UVVirtualEnvManager(VirtualEnvManager):
27
+ def __init__(self, env_path: Path):
28
+ super().__init__(env_path)
29
+ self._install_process: Optional[subprocess.Popen] = None
30
+
31
+ @classmethod
32
+ def is_available(cls):
33
+ return shutil.which("uv") is not None
34
+
35
+ def create_env(self, python_path: Path | None = None) -> None:
36
+ cmd = ["uv", "venv", str(self.env_path)]
37
+ if python_path:
38
+ cmd += ["--python", str(python_path)]
39
+ subprocess.run(cmd, check=True)
40
+
41
+ def install_packages(self, packages: list[str], **kwargs):
42
+ """
43
+ Install packages into the virtual environment using uv.
44
+ Supports pip-compatible kwargs: index_url, extra_index_url, find_links.
45
+ """
46
+ if not packages:
47
+ return
48
+
49
+ cmd = ["uv", "pip", "install", "-p", str(self.env_path)] + packages
50
+
51
+ # Handle known pip-related kwargs
52
+ if "index_url" in kwargs and kwargs["index_url"]:
53
+ cmd += ["-i", kwargs["index_url"]]
54
+ if "extra_index_url" in kwargs and kwargs["extra_index_url"]:
55
+ cmd += ["--extra-index-url", kwargs["extra_index_url"]]
56
+ if "find_links" in kwargs and kwargs["find_links"]:
57
+ cmd += ["-f", kwargs["find_links"]]
58
+ if "trusted_host" in kwargs and kwargs["trusted_host"]:
59
+ cmd += ["--trusted-host", kwargs["trusted_host"]]
60
+
61
+ self._install_process = subprocess.Popen(
62
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
63
+ )
64
+
65
+ stdout, stderr = self._install_process.communicate()
66
+ returncode = self._install_process.returncode
67
+
68
+ self._install_process = None # install finished, clear reference
69
+
70
+ if returncode != 0:
71
+ raise subprocess.CalledProcessError(
72
+ returncode, cmd, output=stdout, stderr=stderr
73
+ )
74
+
75
+ def cancel_install(self):
76
+ if self._install_process and self._install_process.poll() is None:
77
+ self._install_process.terminate()
78
+ self._install_process.wait()
79
+
80
+ def get_lib_path(self) -> str:
81
+ return sysconfig.get_path("purelib", vars={"base": str(self.env_path)})
82
+
83
+ def remove_env(self):
84
+ if self.env_path.exists():
85
+ shutil.rmtree(self.env_path, ignore_errors=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xoscar
3
- Version: 0.4.6
3
+ Version: 0.6.0
4
4
  Summary: Python actor framework for heterogeneous computing.
5
5
  Home-page: http://github.com/xorbitsai/xoscar
6
6
  Author: Qin Xuye
@@ -40,6 +40,7 @@ Requires-Dist: pydata-sphinx-theme>=0.3.0; extra == "dev"
40
40
  Requires-Dist: sphinx-intl>=0.9.9; extra == "dev"
41
41
  Requires-Dist: flake8>=3.8.0; extra == "dev"
42
42
  Requires-Dist: black; extra == "dev"
43
+ Requires-Dist: uv; extra == "dev"
43
44
  Provides-Extra: doc
44
45
  Requires-Dist: ipython>=6.5.0; extra == "doc"
45
46
  Requires-Dist: sphinx; extra == "doc"
@@ -1,11 +1,11 @@
1
- xoscar-0.4.6.dist-info/RECORD,,
2
- xoscar-0.4.6.dist-info/WHEEL,sha256=k313prM1litdYY0m9CIWjnJcoMut0fJadB3MrPSqgmk,111
3
- xoscar-0.4.6.dist-info/top_level.txt,sha256=vYlqqY4Nys8Thm1hePIuUv8eQePdULVWMmt7lXtX_ZA,21
4
- xoscar-0.4.6.dist-info/METADATA,sha256=mDlEVxaFePvqmKl3aoY9-bymXVVOYdX_Tw4S6qgSzso,9042
5
- xoscar/_utils.pyx,sha256=UR1FtYXAYKIdEWR9HulEpMbSOrkQWi6xGz63d4IQmG0,7059
6
- xoscar/_utils.cpython-311-darwin.so,sha256=XiApTXvkc5tIdZ0hZnZ_JEyP8wtRypazUMVn0HgQIqY,160440
1
+ xoscar-0.6.0.dist-info/RECORD,,
2
+ xoscar-0.6.0.dist-info/WHEEL,sha256=k313prM1litdYY0m9CIWjnJcoMut0fJadB3MrPSqgmk,111
3
+ xoscar-0.6.0.dist-info/top_level.txt,sha256=vYlqqY4Nys8Thm1hePIuUv8eQePdULVWMmt7lXtX_ZA,21
4
+ xoscar-0.6.0.dist-info/METADATA,sha256=PxvcFM64ACvq8BuPaniMOGyosrQHR0u7aBcnnBtW-1w,9076
5
+ xoscar/_utils.pyx,sha256=6iqO4eTwEI-n9i39n_TKz7MWqbytMRnVNubdJ5egL6o,7279
6
+ xoscar/_utils.cpython-311-darwin.so,sha256=bMlh04ta_hffWVlZEZAvbXDytYcx7parPe0TV_YDLD8,160504
7
7
  xoscar/backend.py,sha256=is436OPkZfSpQXaoqTRVta5eoye_pp45RFgCstAk2hU,1850
8
- xoscar/core.pxd,sha256=4lBq8J0kjcXcsGuvN7Kv4xcL5liHwTTFWlqyK7XAEnw,1280
8
+ xoscar/core.pxd,sha256=I_C2ka7XryyGnnAVXUVm8xfS1gtIrCs6X-9rswgOcUU,1317
9
9
  xoscar/_version.py,sha256=ClSPrUjgGRGHIkVMQV9XQnkQ-n0akJMnq_rh819nqFE,23719
10
10
  xoscar/context.pxd,sha256=qKa0OyDPZtVymftSh447m-RzFZgmz8rGqQBa7qlauvc,725
11
11
  xoscar/batch.py,sha256=DpArS0L3WYJ_HVPG-6hSYEwoAFY1mY2-mlC4Jp5M_Dw,7872
@@ -14,13 +14,13 @@ xoscar/constants.py,sha256=QHHSREw6uWBBjQDCFqlNfTvBZgniJPGy42KSIsR8Fqw,787
14
14
  xoscar/__init__.py,sha256=0zX8kKaio3ZIrlzB79WybcravMJw1OxPWjDspTgJFyQ,1608
15
15
  xoscar/api.py,sha256=3hztPoOxg8A_mlhWyWgVP7FMXG0PATA1TP4Rbaj7A-g,13327
16
16
  xoscar/utils.py,sha256=jUw6OICZUPBbmS1b3GE4vLctJf6fCKXrYtLtBuK-Oqc,16483
17
- xoscar/context.cpython-311-darwin.so,sha256=p41AIg9Z3P03eW_UPtyn2NkXyvqFQDomy6NBbvWZr5E,200528
17
+ xoscar/context.cpython-311-darwin.so,sha256=ksMUpJRO4zpArJBmKIJVFAZ32yCFLTU7pjxLz6AEymw,200528
18
18
  xoscar/debug.py,sha256=9Z8SgE2WaKYQcyDo-5-DxEJQ533v7kWjrvCd28pSx3E,5069
19
19
  xoscar/libcpp.pxd,sha256=DJqBxLFOKL4iRr9Kale5UH3rbvPRD1x5bTSOPHFpz9I,1147
20
20
  xoscar/context.pyx,sha256=8CdgPnWcE9eOp3N600WgDQ03MCi8P73eUOGcfV7Zksg,10942
21
- xoscar/core.cpython-311-darwin.so,sha256=V-sPRIjOvYQhyCtDMMUWVqPDgA9tEdoCDpGq6ysc4Vo,448248
21
+ xoscar/core.cpython-311-darwin.so,sha256=4UrBtmGiYC1Eu6dLV6sWbsV-0dSy4qcfZ9qYO0cZPsc,448624
22
22
  xoscar/errors.py,sha256=wBlQOKsXf0Fc4skN39tDie0YZT-VIAuLNRgoDl2pZcA,1241
23
- xoscar/core.pyx,sha256=Aqc2i8Fetsd5wRAPF4kL0ddnBZn3E2HRNCvup79BbQc,21730
23
+ xoscar/core.pyx,sha256=phN-yYV0A0QI8WFi2jCu0nc4CnShTepfDi0V7ZrLYPY,22092
24
24
  xoscar/driver.py,sha256=498fowtJr6b3FE8FIOA_Tc1Vwx88nfZw7p0FxrML0h4,1372
25
25
  xoscar/profiling.py,sha256=BC5OF0HzSaXv8V7w-y-B8r5gV5DgxHFoTEIF6jCMioQ,8015
26
26
  xoscar/_utils.pxd,sha256=5KYAL3jfPdejsHnrGGT2s--ZUX5SXznQWpHVSno429k,1157
@@ -32,7 +32,7 @@ xoscar/metrics/backends/prometheus/__init__.py,sha256=h_JgzSqV5lP6vQ6XX_17kE4IY4
32
32
  xoscar/metrics/backends/prometheus/prometheus_metric.py,sha256=MxoMvVrg0pOkKpkjJ0PcAuEaaEJR2FZljmPrLjQ1-oc,2050
33
33
  xoscar/metrics/backends/console/console_metric.py,sha256=y5CCtH33j3AqI5_Uhwi4mgOcAhyhb4cWv_YvR6fxcbQ,2082
34
34
  xoscar/metrics/backends/console/__init__.py,sha256=h_JgzSqV5lP6vQ6XX_17kE4IY4BRnvKta_7VLQAL1ms,581
35
- xoscar/collective/xoscar_pygloo.cpython-311-darwin.so,sha256=GDnzxf6DDlC9FQmWahPxmm5kfRTFLY3OmQ5FF7mYfhc,1263648
35
+ xoscar/collective/xoscar_pygloo.cpython-311-darwin.so,sha256=GbywLuDQ3Meh-xf-jyyDGvrziZ_-IzDQ7_y1h4aLPxM,1264248
36
36
  xoscar/collective/__init__.py,sha256=XsClIkO_3Jd8GDifYuAbZCmJLAo9ZqGvnjUn9iuogmU,774
37
37
  xoscar/collective/core.py,sha256=NVR-7Iaq3aDPCN6fgXcq9Ew6uFEszRwxYqmUG9FLcws,23502
38
38
  xoscar/collective/common.py,sha256=INAnISbfnRicbbbDHTqbSr9ITb89ZphH5BUkSpEdXXU,3561
@@ -41,22 +41,23 @@ xoscar/collective/process_group.py,sha256=zy7LcIFnEcmrcxuECI89v0bQlUbSqQMkVyBw46
41
41
  xoscar/serialization/exception.py,sha256=Jy8Lsk0z-VJyEUaWeuZIwkmxqaoB-nLKMa1D15Cl4js,1634
42
42
  xoscar/serialization/pyfury.py,sha256=sifOnVMYoS82PzZEkzkfxesmMHei23k5UAUUKUyoOYQ,1163
43
43
  xoscar/serialization/core.pxd,sha256=k4RoJgX5E5LGs4jdCQ7vvcn26MabXbrWoWhkO49X6YI,985
44
- xoscar/serialization/__init__.py,sha256=5Y_C3cYbQJIZ09LRjeCf-jrkLma7mfN8I5bznHrdsbg,846
44
+ xoscar/serialization/__init__.py,sha256=v76XC2OQLp-Yk4_U3_IVguEylMeyRw1UrkU_DPDMh0U,856
45
45
  xoscar/serialization/numpy.py,sha256=5Kem87CvpJmzUMp3QHk4WeHU30FoQWTJJP2SwIcaQG0,2919
46
46
  xoscar/serialization/cuda.py,sha256=iFUEnN4SiquBIhyieyOrfw3TnKnW-tU_vYgqOxO_DrA,3758
47
47
  xoscar/serialization/scipy.py,sha256=yOEi0NB8cqQ6e2UnCZ1w006RsB7T725tIL-DM_hNcsU,2482
48
48
  xoscar/serialization/aio.py,sha256=5DySPgDxU43ec7_5Ct44-Oqt7YNSJBfuf8VdQgQlChA,4731
49
- xoscar/serialization/core.cpython-311-darwin.so,sha256=KAZ30WXjzAPdRtulq6mE6e8gcAE7vqcmCU7Lre_rQI0,408048
49
+ xoscar/serialization/core.cpython-311-darwin.so,sha256=2aWKMxJgeOYYHHeGk8PB4_DbE0LHqXeEczzXe5Vb07c,408048
50
50
  xoscar/serialization/core.pyx,sha256=bjR-zXGm9qersk7kYPzpjpMIxDl_Auur4BCubRfKmfA,29626
51
- xoscar/backends/message.cpython-311-darwin.so,sha256=9pKG1GAK9_luBhnIk1pYC3cNGVtmrQxgmxIBQZxEXNc,366672
52
- xoscar/backends/config.py,sha256=EG26f0GwX_f4dAhwTW77RBjiK9h8R_3JrD-rBF1bAq8,4984
51
+ xoscar/serialization/mlx.py,sha256=N_cvbTUBKc14XWYsPIMz4kDstyRN1DNhb4BVRgnQm8Y,1872
52
+ xoscar/backends/message.cpython-311-darwin.so,sha256=DGmJXGFyuuimhL6SvIFZjUq9BtjvLjqqIrxoRGMm3fA,387512
53
+ xoscar/backends/config.py,sha256=4tZMiXAMMS8qQ4SX_LjONLtSQVfZTx3m-IK3EqbkYdk,5375
53
54
  xoscar/backends/allocate_strategy.py,sha256=tC1Nbq2tJohahUwd-zoRYHEDX65wyuX8tmeY45uWj_w,4845
54
55
  xoscar/backends/__init__.py,sha256=VHEBQcUWM5bj027W8EUf9PiJUAP7JoMrRw3Tsvy5ySw,643
55
- xoscar/backends/core.py,sha256=rXJ73IC5lgERXCWvVrDEEyGAILlwVJs7XIWBCFUEVCc,10166
56
- xoscar/backends/context.py,sha256=Vr_PibRxYCDQ_gYK7r-BOlw9TXw8VQbFsVTH7K7mHPk,15470
57
- xoscar/backends/router.py,sha256=mhSvM5KVfV882jricVcpyxAqHEvhS4zL6ivczC6fOTE,7746
58
- xoscar/backends/message.pyx,sha256=uyzilPc_7SqNwGUL4U-Zbfqku8bfZyRW_Lt_S3I_LEU,17930
59
- xoscar/backends/pool.py,sha256=prpyQzJMp5ujFHaSnyhltlSFsnTxXh9D0pPzU6CCCb4,59864
56
+ xoscar/backends/core.py,sha256=EH-fHlV9x3bnruEHaUtGYO7osKLfLJ4AQHtuzA_mr2g,10857
57
+ xoscar/backends/context.py,sha256=XfDPG2eDhAhE6hWBEkEsHTnyyOYN9R3houlMjAL7BFw,16329
58
+ xoscar/backends/router.py,sha256=MVl5naz-FYf-Wla7XRn3kRxOpWV0SjKDsKNluifVA8M,10532
59
+ xoscar/backends/message.pyx,sha256=krGVtZ1YDaZX8yWhaNHwZiudQooLvcGlw6x3Sq7jxjE,19685
60
+ xoscar/backends/pool.py,sha256=OezOhvvXAV3TpODhLHmJVgqCfowb3aA_fWZKPodm8bE,61003
60
61
  xoscar/backends/indigen/backend.py,sha256=znl_fZzWGEtLH8hZ9j9Kkf0fva25jEem2_KO7I1RVvc,1612
61
62
  xoscar/backends/indigen/__init__.py,sha256=tKHP5ClzedBRBpZsLRVErR3EUNbbDm4CY4u0rCFJr44,685
62
63
  xoscar/backends/indigen/driver.py,sha256=VGzkacYKykegW5qhCuhx01gdgBZEKJjNIyfNCnA6Nm8,952
@@ -65,11 +66,11 @@ xoscar/backends/test/backend.py,sha256=nv9WFhH5Bbq4Q1HB9yfpciZBaeHT4IQAtzugBWESr
65
66
  xoscar/backends/test/__init__.py,sha256=j2ZfD6prD9WjUxRUDC7Eq5Z7N7TkL6fFr59oNyc_vY4,682
66
67
  xoscar/backends/test/pool.py,sha256=TW4X6J-92Pti66103poQBNDBznX6CBD3RLOc_zixjTo,7257
67
68
  xoscar/backends/communication/ucx.py,sha256=_Dp9Ld2MWIa1txSGMnmfYwJDT0esxS-GOd2FQ4BdHiM,19960
68
- xoscar/backends/communication/__init__.py,sha256=tB05BlK63iWQnfJgRzKt4mFKRtmWUki5hUGSZQwAotc,1050
69
+ xoscar/backends/communication/__init__.py,sha256=oFIg83Ga93-AhrG52TE85Z2LgpGZu1RCgQu1RWi62zQ,1063
69
70
  xoscar/backends/communication/core.py,sha256=sJeE3foRIqVPXldzYpFKHDSsabfAIFBU4JuXY4OyklY,2130
70
71
  xoscar/backends/communication/utils.py,sha256=AmovE-hmWLXNCPwHafYuaRjOk8m42BUyT3XBqfXQRVI,3664
71
72
  xoscar/backends/communication/errors.py,sha256=V3CdBe2xX9Rwv32f2dH2Msc84yaUhlyerZ42-739o1Q,723
72
- xoscar/backends/communication/socket.py,sha256=_1tuBZrSmdEC6c6QIj_7JQh23ruIIQPwySDMcrndzwA,14267
73
+ xoscar/backends/communication/socket.py,sha256=6Pf9RJWHuvobjZ1eAU6bUuGY7PzYga3Vyc4PAKF-k2M,14428
73
74
  xoscar/backends/communication/dummy.py,sha256=6kLkxjNk4xTQ-IlNZD6cftNCx5UsGOur2jk7ikrNUCg,8157
74
75
  xoscar/backends/communication/base.py,sha256=0P4Tr35GSWpRp394e9jVWUUoKKa-gIk177eYPw1BnSU,7421
75
76
  xoscar/aio/__init__.py,sha256=kViDKR_kJe59VQViHITKEfBcIgN4ZJblUyd8zl0E3ZI,675
@@ -77,3 +78,6 @@ xoscar/aio/file.py,sha256=PBtkLp-Q7XtYl-zk00s18TtgIrkNr60J3Itf66ctO1o,1486
77
78
  xoscar/aio/lru.py,sha256=rpXCqSLtPV5xnWtd6uDwQQFGgIPEgvmWEQDkPNUx9cM,6311
78
79
  xoscar/aio/parallelism.py,sha256=VSsjk8wP-Bw7tLeUsTyLVNgp91thjxEfE3pCrw_vF5Q,1293
79
80
  xoscar/aio/base.py,sha256=9j0f1piwfE5R5GIvV212vSD03ixdaeSzSSsO2kxJZVE,2249
81
+ xoscar/virtualenv/__init__.py,sha256=65t9_X1DvbanNjFy366SiiWZrRTpa9SXWMXPmqayE-4,1117
82
+ xoscar/virtualenv/core.py,sha256=YMN6yHoNeEc8ecbJMbZkKeWKUABK1mUZ_OYOjcbRqWs,1263
83
+ xoscar/virtualenv/uv.py,sha256=wP0wVgWkncABpXnNRVPuOPaeIP7fy3c0LRVrKBFtqU8,3042
File without changes