xoscar 0.9.0__cp312-cp312-macosx_10_13_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.
Files changed (94) hide show
  1. xoscar/__init__.py +61 -0
  2. xoscar/_utils.cpython-312-darwin.so +0 -0
  3. xoscar/_utils.pxd +36 -0
  4. xoscar/_utils.pyx +246 -0
  5. xoscar/_version.py +693 -0
  6. xoscar/aio/__init__.py +16 -0
  7. xoscar/aio/base.py +86 -0
  8. xoscar/aio/file.py +59 -0
  9. xoscar/aio/lru.py +228 -0
  10. xoscar/aio/parallelism.py +39 -0
  11. xoscar/api.py +527 -0
  12. xoscar/backend.py +67 -0
  13. xoscar/backends/__init__.py +14 -0
  14. xoscar/backends/allocate_strategy.py +160 -0
  15. xoscar/backends/communication/__init__.py +30 -0
  16. xoscar/backends/communication/base.py +315 -0
  17. xoscar/backends/communication/core.py +69 -0
  18. xoscar/backends/communication/dummy.py +253 -0
  19. xoscar/backends/communication/errors.py +20 -0
  20. xoscar/backends/communication/socket.py +444 -0
  21. xoscar/backends/communication/ucx.py +538 -0
  22. xoscar/backends/communication/utils.py +97 -0
  23. xoscar/backends/config.py +157 -0
  24. xoscar/backends/context.py +437 -0
  25. xoscar/backends/core.py +352 -0
  26. xoscar/backends/indigen/__init__.py +16 -0
  27. xoscar/backends/indigen/__main__.py +19 -0
  28. xoscar/backends/indigen/backend.py +51 -0
  29. xoscar/backends/indigen/driver.py +26 -0
  30. xoscar/backends/indigen/fate_sharing.py +221 -0
  31. xoscar/backends/indigen/pool.py +515 -0
  32. xoscar/backends/indigen/shared_memory.py +548 -0
  33. xoscar/backends/message.cpython-312-darwin.so +0 -0
  34. xoscar/backends/message.pyi +255 -0
  35. xoscar/backends/message.pyx +646 -0
  36. xoscar/backends/pool.py +1630 -0
  37. xoscar/backends/router.py +285 -0
  38. xoscar/backends/test/__init__.py +16 -0
  39. xoscar/backends/test/backend.py +38 -0
  40. xoscar/backends/test/pool.py +233 -0
  41. xoscar/batch.py +256 -0
  42. xoscar/collective/__init__.py +27 -0
  43. xoscar/collective/backend/__init__.py +13 -0
  44. xoscar/collective/backend/nccl_backend.py +160 -0
  45. xoscar/collective/common.py +102 -0
  46. xoscar/collective/core.py +737 -0
  47. xoscar/collective/process_group.py +687 -0
  48. xoscar/collective/utils.py +41 -0
  49. xoscar/collective/xoscar_pygloo.cpython-312-darwin.so +0 -0
  50. xoscar/collective/xoscar_pygloo.pyi +239 -0
  51. xoscar/constants.py +23 -0
  52. xoscar/context.cpython-312-darwin.so +0 -0
  53. xoscar/context.pxd +21 -0
  54. xoscar/context.pyx +368 -0
  55. xoscar/core.cpython-312-darwin.so +0 -0
  56. xoscar/core.pxd +51 -0
  57. xoscar/core.pyx +664 -0
  58. xoscar/debug.py +188 -0
  59. xoscar/driver.py +42 -0
  60. xoscar/errors.py +63 -0
  61. xoscar/libcpp.pxd +31 -0
  62. xoscar/metrics/__init__.py +21 -0
  63. xoscar/metrics/api.py +288 -0
  64. xoscar/metrics/backends/__init__.py +13 -0
  65. xoscar/metrics/backends/console/__init__.py +13 -0
  66. xoscar/metrics/backends/console/console_metric.py +82 -0
  67. xoscar/metrics/backends/metric.py +149 -0
  68. xoscar/metrics/backends/prometheus/__init__.py +13 -0
  69. xoscar/metrics/backends/prometheus/prometheus_metric.py +70 -0
  70. xoscar/nvutils.py +717 -0
  71. xoscar/profiling.py +260 -0
  72. xoscar/serialization/__init__.py +20 -0
  73. xoscar/serialization/aio.py +141 -0
  74. xoscar/serialization/core.cpython-312-darwin.so +0 -0
  75. xoscar/serialization/core.pxd +28 -0
  76. xoscar/serialization/core.pyi +57 -0
  77. xoscar/serialization/core.pyx +944 -0
  78. xoscar/serialization/cuda.py +111 -0
  79. xoscar/serialization/exception.py +48 -0
  80. xoscar/serialization/mlx.py +67 -0
  81. xoscar/serialization/numpy.py +82 -0
  82. xoscar/serialization/pyfury.py +37 -0
  83. xoscar/serialization/scipy.py +72 -0
  84. xoscar/serialization/torch.py +180 -0
  85. xoscar/utils.py +522 -0
  86. xoscar/virtualenv/__init__.py +34 -0
  87. xoscar/virtualenv/core.py +268 -0
  88. xoscar/virtualenv/platform.py +56 -0
  89. xoscar/virtualenv/utils.py +100 -0
  90. xoscar/virtualenv/uv.py +321 -0
  91. xoscar-0.9.0.dist-info/METADATA +230 -0
  92. xoscar-0.9.0.dist-info/RECORD +94 -0
  93. xoscar-0.9.0.dist-info/WHEEL +6 -0
  94. xoscar-0.9.0.dist-info/top_level.txt +2 -0
xoscar/api.py ADDED
@@ -0,0 +1,527 @@
1
+ # Copyright 2022-2023 XProbe Inc.
2
+ # derived from copyright 1999-2021 Alibaba Group Holding Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ from __future__ import annotations
17
+
18
+ import asyncio
19
+ import functools
20
+ import inspect
21
+ import logging
22
+ import threading
23
+ import uuid
24
+ from collections import defaultdict
25
+ from numbers import Number
26
+ from typing import (
27
+ TYPE_CHECKING,
28
+ Any,
29
+ Awaitable,
30
+ Dict,
31
+ Generic,
32
+ List,
33
+ Optional,
34
+ Tuple,
35
+ Type,
36
+ TypeVar,
37
+ Union,
38
+ )
39
+ from urllib.parse import urlparse
40
+
41
+ from .aio import AioFileObject
42
+ from .backend import get_backend
43
+ from .context import get_context
44
+ from .core import ActorRef, BufferRef, FileObjectRef, _Actor, _StatelessActor
45
+
46
+ if TYPE_CHECKING:
47
+ from .backends.config import ActorPoolConfig
48
+ from .backends.pool import MainActorPoolType
49
+
50
+ logger = logging.getLogger(__name__)
51
+
52
+
53
+ async def create_actor(
54
+ actor_cls: Type, *args, uid=None, address=None, **kwargs
55
+ ) -> ActorRef:
56
+ # TODO: explain default values.
57
+ """
58
+ Create an actor.
59
+
60
+ Parameters
61
+ ----------
62
+ actor_cls : Actor
63
+ Actor class.
64
+ args : tuple
65
+ Positional arguments for ``actor_cls.__init__``.
66
+ uid : identifier, default=None
67
+ Actor identifier.
68
+ address : str, default=None
69
+ Address to locate the actor.
70
+ kwargs : dict
71
+ Keyword arguments for ``actor_cls.__init__``.
72
+
73
+ Returns
74
+ -------
75
+ ActorRef
76
+ """
77
+
78
+ ctx = get_context()
79
+ return await ctx.create_actor(actor_cls, *args, uid=uid, address=address, **kwargs)
80
+
81
+
82
+ async def has_actor(actor_ref: ActorRef) -> bool:
83
+ """
84
+ Check if the given actor exists.
85
+
86
+ Parameters
87
+ ----------
88
+ actor_ref : ActorRef
89
+ Reference to an actor.
90
+
91
+ Returns
92
+ -------
93
+ bool
94
+ """
95
+ ctx = get_context()
96
+ return await ctx.has_actor(actor_ref)
97
+
98
+
99
+ async def destroy_actor(actor_ref: ActorRef):
100
+ """
101
+ Destroy an actor by its reference.
102
+
103
+ Parameters
104
+ ----------
105
+ actor_ref : ActorRef
106
+ Reference to an actor.
107
+
108
+ Returns
109
+ -------
110
+ bool
111
+ """
112
+ ctx = get_context()
113
+ return await ctx.destroy_actor(actor_ref)
114
+
115
+
116
+ async def actor_ref(*args, **kwargs) -> ActorRef:
117
+ """
118
+ Create a reference to an actor.
119
+
120
+ Returns
121
+ -------
122
+ ActorRef
123
+ """
124
+ # TODO: refine the argument list for better user experience.
125
+ ctx = get_context()
126
+ return await ctx.actor_ref(*args, **kwargs)
127
+
128
+
129
+ async def kill_actor(actor_ref: ActorRef):
130
+ # TODO: explain the meaning of 'kill'
131
+ """
132
+ Forcefully kill an actor.
133
+
134
+ It's important to note that this operation is potentially
135
+ dangerous as it may result in the termination of other
136
+ associated actors. Only proceed if you understand the
137
+ potential impact on associated actors and can handle any
138
+ resulting consequences.
139
+
140
+ Parameters
141
+ ----------
142
+ actor_ref : ActorRef
143
+ Reference to an actor.
144
+
145
+ Returns
146
+ -------
147
+ bool
148
+ """
149
+ ctx = get_context()
150
+ return await ctx.kill_actor(actor_ref)
151
+
152
+
153
+ async def create_actor_pool(
154
+ address: str, n_process: int | None = None, **kwargs
155
+ ) -> "MainActorPoolType":
156
+ # TODO: explain default values.
157
+ """
158
+ Create an actor pool.
159
+
160
+ Parameters
161
+ ----------
162
+ address: str
163
+ Address of the actor pool.
164
+ n_process: Optional[int], default=None
165
+ Number of processes.
166
+ kwargs : dict
167
+ Other keyword arguments for the actor pool.
168
+
169
+ Returns
170
+ -------
171
+ MainActorPoolType
172
+ """
173
+ if address is None:
174
+ raise ValueError("address has to be provided")
175
+ if "://" not in address:
176
+ scheme = None
177
+ else:
178
+ scheme = urlparse(address).scheme or None
179
+
180
+ return await get_backend(scheme).create_actor_pool(
181
+ address, n_process=n_process, **kwargs
182
+ )
183
+
184
+
185
+ async def wait_for(fut: Awaitable[Any], timeout: int | float | None = None) -> Any:
186
+ # asyncio.wait_for() on Xoscar actor call cannot work as expected,
187
+ # because when time out, the future will be cancelled, but an actor call will catch this error,
188
+ # and send a CancelMessage to the dest pool, if the CancelMessage cannot be processed correctly(e.g. the dest pool hangs),
189
+ # the time out will never happen. Thus this PR added a new API so that no matter the CancelMessage delivered or not,
190
+ # the timeout will happen as expected.
191
+ loop = asyncio.get_running_loop()
192
+ new_fut = loop.create_future()
193
+ task = asyncio.ensure_future(fut)
194
+
195
+ def on_done(f: asyncio.Future):
196
+ if new_fut.done():
197
+ return
198
+ if f.cancelled():
199
+ new_fut.cancel()
200
+ elif f.exception():
201
+ new_fut.set_exception(f.exception()) # type: ignore
202
+ else:
203
+ new_fut.set_result(f.result())
204
+
205
+ task.add_done_callback(on_done)
206
+
207
+ try:
208
+ return await asyncio.wait_for(new_fut, timeout)
209
+ except asyncio.TimeoutError:
210
+ if not task.done():
211
+ try:
212
+ task.cancel() # Try to cancel without waiting
213
+ except Exception:
214
+ logger.warning("Failed to cancel task", exc_info=True)
215
+ raise
216
+
217
+
218
+ def buffer_ref(address: str, buffer: Any) -> BufferRef:
219
+ """
220
+ Init buffer ref according address and buffer.
221
+
222
+ Parameters
223
+ ----------
224
+ address
225
+ The address of the buffer.
226
+ buffer
227
+ CPU / GPU buffer. Need to support for slicing and retrieving the length.
228
+
229
+ Returns
230
+ ----------
231
+ BufferRef obj.
232
+ """
233
+ ctx = get_context()
234
+ return ctx.buffer_ref(address, buffer)
235
+
236
+
237
+ def file_object_ref(address: str, fileobj: AioFileObject) -> FileObjectRef:
238
+ """
239
+ Init file object ref according to address and aio file obj.
240
+
241
+ Parameters
242
+ ----------
243
+ address
244
+ The address of the file obj.
245
+ fileobj
246
+ Aio file object.
247
+
248
+ Returns
249
+ ----------
250
+ FileObjectRef obj.
251
+ """
252
+ ctx = get_context()
253
+ return ctx.file_object_ref(address, fileobj)
254
+
255
+
256
+ async def copy_to(
257
+ local_buffers_or_fileobjs: list,
258
+ remote_refs: List[Union[BufferRef, FileObjectRef]],
259
+ block_size: Optional[int] = None,
260
+ ):
261
+ """
262
+ Copy data from local buffers to remote buffers or copy local file objects to remote file objects.
263
+
264
+ Parameters
265
+ ----------
266
+ local_buffers_or_fileobjs
267
+ Local buffers or file objects.
268
+ remote_refs
269
+ Remote buffer refs or file object refs.
270
+ block_size
271
+ Transfer block size when non-ucx
272
+ """
273
+ ctx = get_context()
274
+ return await ctx.copy_to(local_buffers_or_fileobjs, remote_refs, block_size)
275
+
276
+
277
+ async def wait_actor_pool_recovered(address: str, main_pool_address: str | None = None):
278
+ """
279
+ Wait until the specified actor pool has recovered from failure.
280
+
281
+ Parameters
282
+ ----------
283
+ address: str
284
+ Address of the actor pool.
285
+ main_pool_address: Optional[str], default=None
286
+ Address of corresponding main actor pool.
287
+
288
+ Returns
289
+ -------
290
+ """
291
+ ctx = get_context()
292
+ return await ctx.wait_actor_pool_recovered(address, main_pool_address)
293
+
294
+
295
+ async def get_pool_config(address: str) -> "ActorPoolConfig":
296
+ """
297
+ Get the configuration of specified actor pool.
298
+
299
+ Parameters
300
+ ----------
301
+ address: str
302
+ Address of the actor pool.
303
+
304
+ Returns
305
+ -------
306
+ ActorPoolConfig
307
+ """
308
+ ctx = get_context()
309
+ return await ctx.get_pool_config(address)
310
+
311
+
312
+ def setup_cluster(address_to_resources: Dict[str, Dict[str, Number]]):
313
+ scheme_to_address_resources: defaultdict[str | None, dict] = defaultdict(dict)
314
+ for address, resources in address_to_resources.items():
315
+ if address is None:
316
+ raise ValueError("address has to be provided")
317
+ if "://" not in address:
318
+ scheme = None
319
+ else:
320
+ scheme = urlparse(address).scheme or None
321
+
322
+ scheme_to_address_resources[scheme][address] = resources
323
+ for scheme, address_resources in scheme_to_address_resources.items():
324
+ get_backend(scheme).get_driver_cls().setup_cluster(address_resources)
325
+
326
+
327
+ T = TypeVar("T")
328
+
329
+
330
+ class IteratorWrapper(Generic[T]):
331
+ def __init__(self, uid: str, actor_addr: str, actor_uid: str):
332
+ self._uid = uid
333
+ self._actor_addr = actor_addr
334
+ self._actor_uid = actor_uid
335
+ self._actor_ref = None
336
+ self._gc_destroy = True
337
+
338
+ async def destroy(self):
339
+ if self._actor_ref is None:
340
+ self._actor_ref = await actor_ref(
341
+ address=self._actor_addr, uid=self._actor_uid
342
+ )
343
+ assert self._actor_ref is not None
344
+ return await self._actor_ref.__xoscar_destroy_generator__(self._uid)
345
+
346
+ def __del__(self):
347
+ # It's not a good idea to spawn a new thread and join in __del__,
348
+ # but currently it's the only way to GC the generator.
349
+ # TODO(codingl2k1): This __del__ may hangs if the program is exiting.
350
+ if self._gc_destroy:
351
+ thread = threading.Thread(
352
+ target=asyncio.run, args=(self.destroy(),), daemon=True
353
+ )
354
+ thread.start()
355
+ thread.join()
356
+
357
+ def __aiter__(self):
358
+ return self
359
+
360
+ def __getstate__(self):
361
+ # Transfer gc destroy during serialization.
362
+ state = self.__dict__.copy()
363
+ state["_gc_destroy"] = True
364
+ self._gc_destroy = False
365
+ return state
366
+
367
+ async def __anext__(self) -> T:
368
+ if self._actor_ref is None:
369
+ self._actor_ref = await actor_ref(
370
+ address=self._actor_addr, uid=self._actor_uid
371
+ )
372
+ try:
373
+ assert self._actor_ref is not None
374
+ return await self._actor_ref.__xoscar_next__(self._uid)
375
+ except Exception as e:
376
+ if "StopIteration" in str(e):
377
+ raise StopAsyncIteration
378
+ else:
379
+ raise
380
+
381
+
382
+ class AsyncActorMixin:
383
+ @classmethod
384
+ def default_uid(cls):
385
+ return cls.__name__
386
+
387
+ def __new__(cls, *args, **kwargs):
388
+ try:
389
+ return _actor_implementation[cls](*args, **kwargs)
390
+ except KeyError:
391
+ return super().__new__(cls, *args, **kwargs)
392
+
393
+ def __init__(self, *args, **kwargs) -> None:
394
+ super().__init__()
395
+ self._generators: Dict[str, IteratorWrapper] = {}
396
+
397
+ async def __post_create__(self):
398
+ """
399
+ Method called after actor creation
400
+ """
401
+ return await super().__post_create__()
402
+
403
+ async def __pre_destroy__(self):
404
+ """
405
+ Method called before actor destroy
406
+ """
407
+ return await super().__pre_destroy__()
408
+
409
+ async def __on_receive__(self, message: Tuple[Any]):
410
+ """
411
+ Handle message from other actors and dispatch them to user methods
412
+
413
+ Parameters
414
+ ----------
415
+ message : tuple
416
+ Message shall be (method_name,) + args + (kwargs,)
417
+ """
418
+ return await super().__on_receive__(message) # type: ignore
419
+
420
+ async def __xoscar_next__(self, generator_uid: str) -> Any:
421
+ """
422
+ Iter the next of generator.
423
+
424
+ Parameters
425
+ ----------
426
+ generator_uid: str
427
+ The uid of generator
428
+
429
+ Returns
430
+ -------
431
+ The next value of generator
432
+ """
433
+
434
+ def _wrapper(_gen):
435
+ try:
436
+ return next(_gen)
437
+ except StopIteration:
438
+ return stop
439
+
440
+ async def _async_wrapper(_gen):
441
+ try:
442
+ # anext is only available for Python >= 3.10
443
+ return await _gen.__anext__() # noqa: F821
444
+ except StopAsyncIteration:
445
+ return stop
446
+
447
+ if gen := self._generators.get(generator_uid):
448
+ stop = object()
449
+ try:
450
+ if inspect.isgenerator(gen):
451
+ r = await asyncio.to_thread(_wrapper, gen)
452
+ elif inspect.isasyncgen(gen):
453
+ r = await asyncio.create_task(_async_wrapper(gen))
454
+ else:
455
+ raise Exception(
456
+ f"The generator {generator_uid} should be a generator or an async generator, "
457
+ f"but a {type(gen)} is got."
458
+ )
459
+ except Exception as e:
460
+ logger.exception(
461
+ f"Destroy generator {generator_uid} due to an error encountered."
462
+ )
463
+ await self.__xoscar_destroy_generator__(generator_uid)
464
+ del gen # Avoid exception hold generator reference.
465
+ raise e
466
+ if r is stop:
467
+ await self.__xoscar_destroy_generator__(generator_uid)
468
+ del gen # Avoid exception hold generator reference.
469
+ raise Exception("StopIteration")
470
+ else:
471
+ return r
472
+ else:
473
+ raise RuntimeError(f"No iterator with id: {generator_uid}")
474
+
475
+ async def __xoscar_destroy_generator__(self, generator_uid: str):
476
+ """
477
+ Destroy the generator.
478
+
479
+ Parameters
480
+ ----------
481
+ generator_uid: str
482
+ The uid of generator
483
+ """
484
+ logger.debug("Destroy generator: %s", generator_uid)
485
+ self._generators.pop(generator_uid, None)
486
+
487
+
488
+ def generator(func):
489
+ need_to_thread = not asyncio.iscoroutinefunction(func)
490
+
491
+ @functools.wraps(func)
492
+ async def _wrapper(self, *args, **kwargs):
493
+ if need_to_thread:
494
+ r = await asyncio.to_thread(func, self, *args, **kwargs)
495
+ else:
496
+ r = await func(self, *args, **kwargs)
497
+ if inspect.isgenerator(r) or inspect.isasyncgen(r):
498
+ gen_uid = uuid.uuid1().hex
499
+ logger.debug("Create generator: %s", gen_uid)
500
+ self._generators[gen_uid] = r
501
+ return IteratorWrapper(gen_uid, self.address, self.uid)
502
+ else:
503
+ return r
504
+
505
+ return _wrapper
506
+
507
+
508
+ class Actor(AsyncActorMixin, _Actor):
509
+ pass
510
+
511
+
512
+ class StatelessActor(AsyncActorMixin, _StatelessActor):
513
+ pass
514
+
515
+
516
+ _actor_implementation: Dict[Type[Actor], Type[Actor]] = dict()
517
+
518
+
519
+ def register_actor_implementation(actor_cls: Type[Actor], impl_cls: Type[Actor]):
520
+ _actor_implementation[actor_cls] = impl_cls
521
+
522
+
523
+ def unregister_actor_implementation(actor_cls: Type[Actor]):
524
+ try:
525
+ del _actor_implementation[actor_cls]
526
+ except KeyError:
527
+ pass
xoscar/backend.py ADDED
@@ -0,0 +1,67 @@
1
+ # Copyright 2022-2023 XProbe Inc.
2
+ # derived from copyright 1999-2021 Alibaba Group Holding Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ from __future__ import annotations
17
+
18
+ from abc import ABC, abstractmethod
19
+ from typing import Dict, Type
20
+
21
+ from .context import register_backend_context
22
+ from .driver import register_backend_driver
23
+
24
+ __all__ = ["BaseActorBackend", "register_backend", "get_backend"]
25
+
26
+
27
+ class BaseActorBackend(ABC):
28
+ @staticmethod
29
+ @abstractmethod
30
+ def name():
31
+ pass
32
+
33
+ @staticmethod
34
+ @abstractmethod
35
+ def get_context_cls():
36
+ pass
37
+
38
+ @classmethod
39
+ async def create_actor_pool(
40
+ cls, address: str, n_process: int | None = None, **kwargs
41
+ ):
42
+ pass
43
+
44
+ @staticmethod
45
+ @abstractmethod
46
+ def get_driver_cls():
47
+ pass
48
+
49
+
50
+ _scheme_to_backend_cls: Dict[str, Type[BaseActorBackend]] = dict()
51
+
52
+
53
+ def register_backend(backend_cls: Type[BaseActorBackend]):
54
+ name = backend_cls.name()
55
+ if isinstance(name, (list, tuple)):
56
+ names = name
57
+ else:
58
+ names = [name]
59
+ for name in names:
60
+ _scheme_to_backend_cls[name] = backend_cls
61
+ register_backend_context(name, backend_cls.get_context_cls())
62
+ register_backend_driver(name, backend_cls.get_driver_cls())
63
+ return backend_cls
64
+
65
+
66
+ def get_backend(name):
67
+ return _scheme_to_backend_cls[name]
@@ -0,0 +1,14 @@
1
+ # Copyright 2022-2023 XProbe Inc.
2
+ # derived from copyright 1999-2021 Alibaba Group Holding Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.