ez-a-sync 0.33.4__cp313-cp313-musllinux_1_2_i686.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.
- a_sync/ENVIRONMENT_VARIABLES.py +42 -0
- a_sync/__init__.pxd +2 -0
- a_sync/__init__.py +145 -0
- a_sync/_smart.c +22830 -0
- a_sync/_smart.cpython-313-i386-linux-musl.so +0 -0
- a_sync/_smart.pxd +2 -0
- a_sync/_smart.pyi +202 -0
- a_sync/_smart.pyx +674 -0
- a_sync/_typing.py +258 -0
- a_sync/a_sync/__init__.py +60 -0
- a_sync/a_sync/_descriptor.c +20537 -0
- a_sync/a_sync/_descriptor.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/_descriptor.pyi +33 -0
- a_sync/a_sync/_descriptor.pyx +422 -0
- a_sync/a_sync/_flags.c +6082 -0
- a_sync/a_sync/_flags.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/_flags.pxd +3 -0
- a_sync/a_sync/_flags.pyx +92 -0
- a_sync/a_sync/_helpers.c +14529 -0
- a_sync/a_sync/_helpers.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/_helpers.pxd +3 -0
- a_sync/a_sync/_helpers.pyi +10 -0
- a_sync/a_sync/_helpers.pyx +167 -0
- a_sync/a_sync/_kwargs.c +12202 -0
- a_sync/a_sync/_kwargs.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/_kwargs.pxd +2 -0
- a_sync/a_sync/_kwargs.pyx +64 -0
- a_sync/a_sync/_meta.py +210 -0
- a_sync/a_sync/abstract.c +12420 -0
- a_sync/a_sync/abstract.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/abstract.pyi +141 -0
- a_sync/a_sync/abstract.pyx +221 -0
- a_sync/a_sync/base.c +14940 -0
- a_sync/a_sync/base.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/base.pyi +60 -0
- a_sync/a_sync/base.pyx +271 -0
- a_sync/a_sync/config.py +168 -0
- a_sync/a_sync/decorator.py +651 -0
- a_sync/a_sync/flags.c +5272 -0
- a_sync/a_sync/flags.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/flags.pxd +72 -0
- a_sync/a_sync/flags.pyi +74 -0
- a_sync/a_sync/flags.pyx +72 -0
- a_sync/a_sync/function.c +37856 -0
- a_sync/a_sync/function.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/function.pxd +28 -0
- a_sync/a_sync/function.pyi +571 -0
- a_sync/a_sync/function.pyx +1381 -0
- a_sync/a_sync/method.c +29662 -0
- a_sync/a_sync/method.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/method.pxd +9 -0
- a_sync/a_sync/method.pyi +523 -0
- a_sync/a_sync/method.pyx +1023 -0
- a_sync/a_sync/modifiers/__init__.pxd +1 -0
- a_sync/a_sync/modifiers/__init__.py +101 -0
- a_sync/a_sync/modifiers/cache/__init__.py +160 -0
- a_sync/a_sync/modifiers/cache/memory.py +165 -0
- a_sync/a_sync/modifiers/limiter.py +132 -0
- a_sync/a_sync/modifiers/manager.c +16157 -0
- a_sync/a_sync/modifiers/manager.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/modifiers/manager.pxd +5 -0
- a_sync/a_sync/modifiers/manager.pyi +219 -0
- a_sync/a_sync/modifiers/manager.pyx +299 -0
- a_sync/a_sync/modifiers/semaphores.py +173 -0
- a_sync/a_sync/property.c +27268 -0
- a_sync/a_sync/property.cpython-313-i386-linux-musl.so +0 -0
- a_sync/a_sync/property.pyi +376 -0
- a_sync/a_sync/property.pyx +819 -0
- a_sync/a_sync/singleton.py +63 -0
- a_sync/aliases.py +3 -0
- a_sync/async_property/__init__.pxd +1 -0
- a_sync/async_property/__init__.py +1 -0
- a_sync/async_property/cached.c +20397 -0
- a_sync/async_property/cached.cpython-313-i386-linux-musl.so +0 -0
- a_sync/async_property/cached.pxd +10 -0
- a_sync/async_property/cached.pyi +45 -0
- a_sync/async_property/cached.pyx +178 -0
- a_sync/async_property/proxy.c +34662 -0
- a_sync/async_property/proxy.cpython-313-i386-linux-musl.so +0 -0
- a_sync/async_property/proxy.pxd +2 -0
- a_sync/async_property/proxy.pyi +124 -0
- a_sync/async_property/proxy.pyx +474 -0
- a_sync/asyncio/__init__.pxd +6 -0
- a_sync/asyncio/__init__.py +164 -0
- a_sync/asyncio/as_completed.c +18849 -0
- a_sync/asyncio/as_completed.cpython-313-i386-linux-musl.so +0 -0
- a_sync/asyncio/as_completed.pxd +8 -0
- a_sync/asyncio/as_completed.pyi +109 -0
- a_sync/asyncio/as_completed.pyx +269 -0
- a_sync/asyncio/create_task.c +15912 -0
- a_sync/asyncio/create_task.cpython-313-i386-linux-musl.so +0 -0
- a_sync/asyncio/create_task.pxd +2 -0
- a_sync/asyncio/create_task.pyi +51 -0
- a_sync/asyncio/create_task.pyx +271 -0
- a_sync/asyncio/gather.c +16687 -0
- a_sync/asyncio/gather.cpython-313-i386-linux-musl.so +0 -0
- a_sync/asyncio/gather.pyi +107 -0
- a_sync/asyncio/gather.pyx +218 -0
- a_sync/asyncio/igather.c +13080 -0
- a_sync/asyncio/igather.cpython-313-i386-linux-musl.so +0 -0
- a_sync/asyncio/igather.pxd +1 -0
- a_sync/asyncio/igather.pyi +8 -0
- a_sync/asyncio/igather.pyx +183 -0
- a_sync/asyncio/sleep.c +9601 -0
- a_sync/asyncio/sleep.cpython-313-i386-linux-musl.so +0 -0
- a_sync/asyncio/sleep.pyi +14 -0
- a_sync/asyncio/sleep.pyx +49 -0
- a_sync/debugging.c +15370 -0
- a_sync/debugging.cpython-313-i386-linux-musl.so +0 -0
- a_sync/debugging.pyi +76 -0
- a_sync/debugging.pyx +107 -0
- a_sync/exceptions.c +13320 -0
- a_sync/exceptions.cpython-313-i386-linux-musl.so +0 -0
- a_sync/exceptions.pyi +376 -0
- a_sync/exceptions.pyx +446 -0
- a_sync/executor.py +619 -0
- a_sync/functools.c +12746 -0
- a_sync/functools.cpython-313-i386-linux-musl.so +0 -0
- a_sync/functools.pxd +7 -0
- a_sync/functools.pyi +33 -0
- a_sync/functools.pyx +139 -0
- a_sync/future.py +1497 -0
- a_sync/iter.c +37279 -0
- a_sync/iter.cpython-313-i386-linux-musl.so +0 -0
- a_sync/iter.pxd +11 -0
- a_sync/iter.pyi +370 -0
- a_sync/iter.pyx +981 -0
- a_sync/primitives/__init__.pxd +1 -0
- a_sync/primitives/__init__.py +53 -0
- a_sync/primitives/_debug.c +15765 -0
- a_sync/primitives/_debug.cpython-313-i386-linux-musl.so +0 -0
- a_sync/primitives/_debug.pxd +12 -0
- a_sync/primitives/_debug.pyi +52 -0
- a_sync/primitives/_debug.pyx +223 -0
- a_sync/primitives/_loggable.c +11538 -0
- a_sync/primitives/_loggable.cpython-313-i386-linux-musl.so +0 -0
- a_sync/primitives/_loggable.pxd +4 -0
- a_sync/primitives/_loggable.pyi +66 -0
- a_sync/primitives/_loggable.pyx +102 -0
- a_sync/primitives/locks/__init__.pxd +8 -0
- a_sync/primitives/locks/__init__.py +17 -0
- a_sync/primitives/locks/counter.c +17938 -0
- a_sync/primitives/locks/counter.cpython-313-i386-linux-musl.so +0 -0
- a_sync/primitives/locks/counter.pxd +12 -0
- a_sync/primitives/locks/counter.pyi +151 -0
- a_sync/primitives/locks/counter.pyx +267 -0
- a_sync/primitives/locks/event.c +17072 -0
- a_sync/primitives/locks/event.cpython-313-i386-linux-musl.so +0 -0
- a_sync/primitives/locks/event.pxd +22 -0
- a_sync/primitives/locks/event.pyi +43 -0
- a_sync/primitives/locks/event.pyx +185 -0
- a_sync/primitives/locks/prio_semaphore.c +25635 -0
- a_sync/primitives/locks/prio_semaphore.cpython-313-i386-linux-musl.so +0 -0
- a_sync/primitives/locks/prio_semaphore.pxd +25 -0
- a_sync/primitives/locks/prio_semaphore.pyi +217 -0
- a_sync/primitives/locks/prio_semaphore.pyx +597 -0
- a_sync/primitives/locks/semaphore.c +26553 -0
- a_sync/primitives/locks/semaphore.cpython-313-i386-linux-musl.so +0 -0
- a_sync/primitives/locks/semaphore.pxd +21 -0
- a_sync/primitives/locks/semaphore.pyi +197 -0
- a_sync/primitives/locks/semaphore.pyx +454 -0
- a_sync/primitives/queue.py +1026 -0
- a_sync/py.typed +0 -0
- a_sync/sphinx/__init__.py +3 -0
- a_sync/sphinx/ext.py +289 -0
- a_sync/task.py +934 -0
- a_sync/utils/__init__.py +105 -0
- a_sync/utils/iterators.py +297 -0
- a_sync/utils/repr.c +15866 -0
- a_sync/utils/repr.cpython-313-i386-linux-musl.so +0 -0
- a_sync/utils/repr.pyi +2 -0
- a_sync/utils/repr.pyx +73 -0
- ez_a_sync-0.33.4.dist-info/METADATA +368 -0
- ez_a_sync-0.33.4.dist-info/RECORD +177 -0
- ez_a_sync-0.33.4.dist-info/WHEEL +5 -0
- ez_a_sync-0.33.4.dist-info/licenses/LICENSE.txt +17 -0
- ez_a_sync-0.33.4.dist-info/top_level.txt +1 -0
|
Binary file
|
a_sync/a_sync/base.pyi
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from a_sync._typing import *
|
|
2
|
+
import functools
|
|
3
|
+
import logging
|
|
4
|
+
from a_sync import exceptions as exceptions
|
|
5
|
+
from a_sync.a_sync.abstract import ASyncABC as ASyncABC
|
|
6
|
+
from a_sync.a_sync.flags import VIABLE_FLAGS as VIABLE_FLAGS
|
|
7
|
+
|
|
8
|
+
logger: logging.Logger
|
|
9
|
+
|
|
10
|
+
class ASyncGenericBase(ASyncABC):
|
|
11
|
+
"""
|
|
12
|
+
Base class for creating dual-function sync/async-capable classes without writing all your code twice.
|
|
13
|
+
|
|
14
|
+
This class, via its inherited metaclass :class:`~ASyncMeta', provides the foundation for creating hybrid sync/async classes. It allows methods
|
|
15
|
+
and properties to be defined once and used in both synchronous and asynchronous contexts.
|
|
16
|
+
|
|
17
|
+
The class uses the :func:`a_sync` decorator internally to create dual-mode methods and properties.
|
|
18
|
+
Subclasses should define their methods as coroutines (using `async def`) where possible, and
|
|
19
|
+
use the `@a_sync.property` or `@a_sync.cached_property` decorators for properties that need to support both modes.
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
```python
|
|
23
|
+
class MyClass(ASyncGenericBase):
|
|
24
|
+
def __init__(self, sync: bool):
|
|
25
|
+
self.sync = sync
|
|
26
|
+
|
|
27
|
+
@a_sync.property
|
|
28
|
+
async def my_property(self):
|
|
29
|
+
return await some_async_operation()
|
|
30
|
+
|
|
31
|
+
@a_sync
|
|
32
|
+
async def my_method(self):
|
|
33
|
+
return await another_async_operation()
|
|
34
|
+
|
|
35
|
+
# Synchronous usage
|
|
36
|
+
obj = MyClass(sync=True)
|
|
37
|
+
sync_result = obj.my_property
|
|
38
|
+
sync_method_result = obj.my_method()
|
|
39
|
+
|
|
40
|
+
# Asynchronous usage
|
|
41
|
+
obj = MyClass(sync=False)
|
|
42
|
+
async_result = await obj.my_property
|
|
43
|
+
async_method_result = await obj.my_method()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Note:
|
|
47
|
+
When subclassing, be aware that all async methods and properties will be
|
|
48
|
+
automatically wrapped to support both sync and async calls. This allows for
|
|
49
|
+
seamless usage in different contexts without changing the underlying implementation.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self) -> None: ...
|
|
53
|
+
@functools.cached_property
|
|
54
|
+
def __a_sync_flag_name__(self) -> str: ...
|
|
55
|
+
@functools.cached_property
|
|
56
|
+
def __a_sync_flag_value__(self) -> bool:
|
|
57
|
+
"""If you wish to be able to hotswap default modes, just duplicate this def as a non-cached property."""
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def __a_sync_default_mode__(cls) -> bool: ...
|
a_sync/a_sync/base.pyx
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# cython: boundscheck=False
|
|
2
|
+
import inspect
|
|
3
|
+
from logging import getLogger
|
|
4
|
+
|
|
5
|
+
from cpython.object cimport Py_TYPE, PyObject
|
|
6
|
+
from cpython.tuple cimport PyTuple_GET_SIZE, PyTuple_GetItem
|
|
7
|
+
|
|
8
|
+
from a_sync._typing import *
|
|
9
|
+
from a_sync.a_sync._flags cimport validate_and_negate_if_necessary, validate_flag_value
|
|
10
|
+
from a_sync.a_sync.abstract import ASyncABC
|
|
11
|
+
from a_sync.a_sync.flags cimport VIABLE_FLAGS
|
|
12
|
+
from a_sync.exceptions import ASyncFlagException, FlagNotDefined, InvalidFlag, NoFlagsFound, TooManyFlags
|
|
13
|
+
from a_sync.functools cimport cached_property_unsafe
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
cdef extern from "Python.h":
|
|
17
|
+
ctypedef struct PyTypeObject:
|
|
18
|
+
PyObject *tp_bases
|
|
19
|
+
PyObject *tp_dict
|
|
20
|
+
|
|
21
|
+
ctypedef object object_id
|
|
22
|
+
ctypedef dict[str, object] cls_init_flags
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# cdef inspect
|
|
26
|
+
cdef object signature = inspect.signature
|
|
27
|
+
cdef object _empty = inspect._empty
|
|
28
|
+
del inspect
|
|
29
|
+
|
|
30
|
+
# cdef logging
|
|
31
|
+
cdef public object logger = getLogger(__name__)
|
|
32
|
+
cdef object _logger_is_enabled = logger.isEnabledFor
|
|
33
|
+
cdef object _logger_debug = logger.debug
|
|
34
|
+
cdef object _logger_log = logger._log
|
|
35
|
+
cdef object DEBUG = 10
|
|
36
|
+
del getLogger
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
cdef object _init_ASyncABC = ASyncABC.__init__
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ASyncGenericBase(ASyncABC):
|
|
43
|
+
"""
|
|
44
|
+
Base class for creating dual-function sync/async-capable classes without writing all your code twice.
|
|
45
|
+
|
|
46
|
+
This class, via its inherited metaclass :class:`~ASyncMeta', provides the foundation for creating hybrid sync/async classes. It allows methods
|
|
47
|
+
and properties to be defined once and used in both synchronous and asynchronous contexts.
|
|
48
|
+
|
|
49
|
+
The class uses the :func:`a_sync` decorator internally to create dual-mode methods and properties.
|
|
50
|
+
Subclasses should define their methods as coroutines (using `async def`) where possible, and
|
|
51
|
+
use the `@a_sync.property` or `@a_sync.cached_property` decorators for properties that need to support both modes.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
```python
|
|
55
|
+
class MyClass(ASyncGenericBase):
|
|
56
|
+
def __init__(self, sync: bool):
|
|
57
|
+
self.sync = sync
|
|
58
|
+
|
|
59
|
+
@a_sync.property
|
|
60
|
+
async def my_property(self):
|
|
61
|
+
return await some_async_operation()
|
|
62
|
+
|
|
63
|
+
@a_sync
|
|
64
|
+
async def my_method(self):
|
|
65
|
+
return await another_async_operation()
|
|
66
|
+
|
|
67
|
+
# Synchronous usage
|
|
68
|
+
obj = MyClass(sync=True)
|
|
69
|
+
sync_result = obj.my_property
|
|
70
|
+
sync_method_result = obj.my_method()
|
|
71
|
+
|
|
72
|
+
# Asynchronous usage
|
|
73
|
+
obj = MyClass(sync=False)
|
|
74
|
+
async_result = await obj.my_property
|
|
75
|
+
async_method_result = await obj.my_method()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Note:
|
|
79
|
+
When subclassing, be aware that all async methods and properties will be
|
|
80
|
+
automatically wrapped to support both sync and async calls. This allows for
|
|
81
|
+
seamless usage in different contexts without changing the underlying implementation.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
@classmethod # type: ignore [misc]
|
|
85
|
+
def __a_sync_default_mode__(cls) -> bint: # type: ignore [override]
|
|
86
|
+
cdef str flag
|
|
87
|
+
cdef bint flag_value
|
|
88
|
+
cdef PyTypeObject *cls_ptr
|
|
89
|
+
|
|
90
|
+
cls_ptr = <PyTypeObject*>cls
|
|
91
|
+
if not _logger_is_enabled(DEBUG):
|
|
92
|
+
# we can optimize this if we dont need to log `flag` and the return value
|
|
93
|
+
try:
|
|
94
|
+
flag = _get_a_sync_flag_name_from_signature(cls_ptr, False)
|
|
95
|
+
flag_value = _a_sync_flag_default_value_from_signature(cls_ptr)
|
|
96
|
+
except NoFlagsFound:
|
|
97
|
+
flag = _get_a_sync_flag_name_from_class_def(cls_ptr)
|
|
98
|
+
flag_value = _get_a_sync_flag_value_from_class_def(cls, flag)
|
|
99
|
+
return validate_and_negate_if_necessary(flag, flag_value)
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
flag = _get_a_sync_flag_name_from_signature(cls_ptr, True)
|
|
103
|
+
flag_value = _a_sync_flag_default_value_from_signature(cls_ptr)
|
|
104
|
+
except NoFlagsFound:
|
|
105
|
+
flag = _get_a_sync_flag_name_from_class_def(cls_ptr)
|
|
106
|
+
flag_value = _get_a_sync_flag_value_from_class_def(cls, flag)
|
|
107
|
+
|
|
108
|
+
cdef bint sync = validate_and_negate_if_necessary(flag, flag_value)
|
|
109
|
+
_logger_log(
|
|
110
|
+
DEBUG,
|
|
111
|
+
"`%s.%s` indicates default mode is %ssynchronous",
|
|
112
|
+
(cls, flag, "a" if sync is False else ""),
|
|
113
|
+
)
|
|
114
|
+
return sync
|
|
115
|
+
|
|
116
|
+
def __init__(self):
|
|
117
|
+
if Py_TYPE(self) == ASyncGenericBase_ptr:
|
|
118
|
+
raise NotImplementedError(
|
|
119
|
+
"You should not create instances of `ASyncGenericBase` directly, "
|
|
120
|
+
"you should subclass `ASyncGenericBase` instead."
|
|
121
|
+
)
|
|
122
|
+
_init_ASyncABC(self)
|
|
123
|
+
|
|
124
|
+
@cached_property_unsafe
|
|
125
|
+
def __a_sync_flag_name__(self) -> str:
|
|
126
|
+
# TODO: cythonize this cache
|
|
127
|
+
cdef bint debug_logs
|
|
128
|
+
if debug_logs := _logger_is_enabled(DEBUG):
|
|
129
|
+
_logger_log(DEBUG, "checking a_sync flag for %s", (self, ))
|
|
130
|
+
try:
|
|
131
|
+
flag = _get_a_sync_flag_name_from_signature(Py_TYPE(self), debug_logs)
|
|
132
|
+
except ASyncFlagException:
|
|
133
|
+
# We can't get the flag name from the __init__ signature,
|
|
134
|
+
# but maybe the implementation sets the flag somewhere else.
|
|
135
|
+
# Let's check the instance's atributes
|
|
136
|
+
if debug_logs:
|
|
137
|
+
_logger_log(
|
|
138
|
+
DEBUG,
|
|
139
|
+
"unable to find flag name using `%s.__init__` signature, checking for flag attributes defined on %s",
|
|
140
|
+
(self.__class__.__name__, self),
|
|
141
|
+
)
|
|
142
|
+
present_flags = [flag for flag in VIABLE_FLAGS if hasattr(self, flag)]
|
|
143
|
+
if not present_flags:
|
|
144
|
+
raise NoFlagsFound(self) from None
|
|
145
|
+
if len(present_flags) > 1:
|
|
146
|
+
raise TooManyFlags(self, present_flags) from None
|
|
147
|
+
flag = present_flags[0]
|
|
148
|
+
if not isinstance(flag, str):
|
|
149
|
+
raise InvalidFlag(flag)
|
|
150
|
+
return flag
|
|
151
|
+
|
|
152
|
+
@cached_property_unsafe
|
|
153
|
+
def __a_sync_flag_value__(self) -> bint:
|
|
154
|
+
# TODO: cythonize this cache
|
|
155
|
+
"""If you wish to be able to hotswap default modes, just duplicate this def as a non-cached property."""
|
|
156
|
+
cdef str flag = self.__a_sync_flag_name__
|
|
157
|
+
flag_value = getattr(self, flag)
|
|
158
|
+
_logger_debug("`%s.%s` is currently %s", self, flag, flag_value)
|
|
159
|
+
return validate_flag_value(flag, flag_value)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
cdef PyTypeObject *ASyncGenericBase_ptr = <PyTypeObject*>ASyncGenericBase
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
cdef inline str _get_a_sync_flag_name_from_class_def(PyTypeObject *cls_ptr):
|
|
166
|
+
cdef object bases
|
|
167
|
+
cdef PyObject *bases_ptr
|
|
168
|
+
cdef PyTypeObject *base_ptr
|
|
169
|
+
cdef Py_ssize_t len_bases
|
|
170
|
+
|
|
171
|
+
if _logger_is_enabled(DEBUG):
|
|
172
|
+
_logger_log(DEBUG, "Searching for flags defined on %s", (<object>cls_ptr,))
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
return _parse_flag_name_from_dict_keys(cls_ptr, <object>cls_ptr.tp_dict)
|
|
176
|
+
except NoFlagsFound:
|
|
177
|
+
bases_ptr = cls_ptr.tp_bases # This is a tuple or NULL
|
|
178
|
+
if bases_ptr != NULL:
|
|
179
|
+
bases = <object>bases_ptr
|
|
180
|
+
len_bases = PyTuple_GET_SIZE(bases)
|
|
181
|
+
for i in range(len_bases):
|
|
182
|
+
base_ptr = <PyTypeObject*>PyTuple_GetItem(bases, i)
|
|
183
|
+
try:
|
|
184
|
+
return _get_a_sync_flag_name_from_class_def(base_ptr)
|
|
185
|
+
except NoFlagsFound:
|
|
186
|
+
pass
|
|
187
|
+
raise NoFlagsFound(<object>cls_ptr, list(<object>cls_ptr.tp_dict))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
cdef bint _a_sync_flag_default_value_from_signature(PyTypeObject *cls_ptr):
|
|
191
|
+
cdef cls_init_flags flags = _get_init_flags(cls_ptr)
|
|
192
|
+
|
|
193
|
+
if not _logger_is_enabled(DEBUG):
|
|
194
|
+
# we can optimize this much better
|
|
195
|
+
return flags[_get_a_sync_flag_name_from_signature(cls_ptr, False)].default
|
|
196
|
+
|
|
197
|
+
cdef object cls = <object>cls_ptr
|
|
198
|
+
_logger_log(
|
|
199
|
+
DEBUG, "checking `__init__` signature for default %s a_sync flag value", (cls, )
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
cdef str flag = _get_a_sync_flag_name_from_signature(cls_ptr, True)
|
|
203
|
+
cdef object flag_value = flags[flag].default
|
|
204
|
+
if flag_value is _empty: # type: ignore [attr-defined]
|
|
205
|
+
raise NotImplementedError(
|
|
206
|
+
"The implementation for 'cls' uses an arg to specify sync mode, instead of a kwarg. We are unable to proceed. I suppose we can extend the code to accept positional arg flags if necessary"
|
|
207
|
+
)
|
|
208
|
+
_logger_log(DEBUG, "%s defines %s, default value %s", (cls, flag, flag_value))
|
|
209
|
+
return flag_value
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
cdef str _get_a_sync_flag_name_from_signature(PyTypeObject *cls_ptr, bint debug_logs):
|
|
213
|
+
cdef cls_init_flags flags
|
|
214
|
+
|
|
215
|
+
if cls_ptr == ASyncGenericBase_ptr:
|
|
216
|
+
# There are no flags defined on the base class
|
|
217
|
+
return ""
|
|
218
|
+
|
|
219
|
+
# if we fail this one there's no need to check again
|
|
220
|
+
if not debug_logs:
|
|
221
|
+
# we can also skip assigning params to a var
|
|
222
|
+
return _parse_flag_name_from_dict_keys(cls_ptr, _get_init_flags(cls_ptr))
|
|
223
|
+
|
|
224
|
+
_logger_log(DEBUG, "Searching for flags defined on %s.__init__", (<object>cls_ptr, ))
|
|
225
|
+
flags = _get_init_flags(cls_ptr)
|
|
226
|
+
_logger_log(DEBUG, "flags: %s", (flags, ))
|
|
227
|
+
return _parse_flag_name_from_dict_keys(cls_ptr, flags)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
cdef inline str _parse_flag_name_from_dict_keys(PyTypeObject *cls_ptr, dict[str, object] d):
|
|
231
|
+
cdef str flag
|
|
232
|
+
cdef list[str] present_flags = [flag for flag in VIABLE_FLAGS if flag in d]
|
|
233
|
+
cdef int flags_len = len(present_flags)
|
|
234
|
+
if not flags_len:
|
|
235
|
+
cls = <object>cls_ptr
|
|
236
|
+
_logger_debug("There are no flags defined on %s", cls)
|
|
237
|
+
raise NoFlagsFound(cls, d.keys())
|
|
238
|
+
if flags_len > 1:
|
|
239
|
+
cls = <object>cls_ptr
|
|
240
|
+
_logger_debug("There are too many flags defined on %s", cls)
|
|
241
|
+
raise TooManyFlags(cls, present_flags)
|
|
242
|
+
if _logger_is_enabled(DEBUG):
|
|
243
|
+
flag = present_flags[0]
|
|
244
|
+
_logger_log(DEBUG, "found flag %s", (flag, ))
|
|
245
|
+
return flag
|
|
246
|
+
return present_flags[0]
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
cdef inline bint _get_a_sync_flag_value_from_class_def(object cls, str flag):
|
|
250
|
+
cdef object spec
|
|
251
|
+
cdef bint flag_value
|
|
252
|
+
for spec in [cls, *cls.__bases__]:
|
|
253
|
+
flag_value = spec.__dict__.get(flag)
|
|
254
|
+
if flag_value is not None:
|
|
255
|
+
return flag_value
|
|
256
|
+
raise FlagNotDefined(cls, flag)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
cdef dict[object_id, cls_init_flags] _init_flags_cache = {}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
cdef inline cls_init_flags _get_init_flags(PyTypeObject *cls_ptr):
|
|
263
|
+
cdef cls_init_flags init_flags
|
|
264
|
+
cdef object init_method = (<object>cls_ptr).__init__
|
|
265
|
+
# We keep a smaller dict if we use the id of the method instead of the class
|
|
266
|
+
cdef object_id init_method_id = id(init_method)
|
|
267
|
+
init_flags = _init_flags_cache.get(init_method_id)
|
|
268
|
+
if init_flags is None:
|
|
269
|
+
init_flags = {k: v for k, v in signature(init_method).parameters.items() if k in VIABLE_FLAGS}
|
|
270
|
+
_init_flags_cache[init_method_id] = init_flags
|
|
271
|
+
return init_flags
|
a_sync/a_sync/config.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides configuration options and default settings for the a_sync library.
|
|
3
|
+
It includes functionality for setting up executors, defining default modifiers,
|
|
4
|
+
and handling environment variable configurations.
|
|
5
|
+
|
|
6
|
+
Environment Variables:
|
|
7
|
+
:obj:`~A_SYNC_EXECUTOR_TYPE`: Specifies the type of executor to use. Valid values are
|
|
8
|
+
strings that start with 'p' for :class:`~concurrent.futures.ProcessPoolExecutor`
|
|
9
|
+
(e.g., 'processes') or 't' for :class:`~concurrent.futures.ThreadPoolExecutor`
|
|
10
|
+
(e.g., 'threads'). Defaults to 'threads'.
|
|
11
|
+
:obj:`~A_SYNC_EXECUTOR_VALUE`: Specifies the number of workers for the executor.
|
|
12
|
+
Defaults to 8.
|
|
13
|
+
:obj:`~A_SYNC_DEFAULT_MODE`: Sets the default mode for a_sync functions if not specified.
|
|
14
|
+
:obj:`~A_SYNC_CACHE_TYPE`: Sets the default cache type. If not specified, defaults to None.
|
|
15
|
+
:obj:`~A_SYNC_CACHE_TYPED`: Boolean flag to determine if cache keys should consider types.
|
|
16
|
+
:obj:`~A_SYNC_RAM_CACHE_MAXSIZE`: Sets the maximum size for the RAM cache. Defaults to -1.
|
|
17
|
+
:obj:`~A_SYNC_RAM_CACHE_TTL`: Sets the time-to-live for cache entries. If not specified,
|
|
18
|
+
defaults to 0, which is then checked against `null_modifiers["ram_cache_ttl"]`
|
|
19
|
+
to potentially set it to `None`, meaning cache entries do not expire by default.
|
|
20
|
+
:obj:`~A_SYNC_RUNS_PER_MINUTE`: Sets the rate limit for function execution.
|
|
21
|
+
:obj:`~A_SYNC_SEMAPHORE`: Sets the semaphore limit for function execution.
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
To set the executor type to use threads with 4 workers, set the environment variables:
|
|
25
|
+
|
|
26
|
+
.. code-block:: bash
|
|
27
|
+
|
|
28
|
+
export A_SYNC_EXECUTOR_TYPE=threads
|
|
29
|
+
export A_SYNC_EXECUTOR_VALUE=4
|
|
30
|
+
|
|
31
|
+
To configure caching with a maximum size of 100 and a TTL of 60 seconds:
|
|
32
|
+
|
|
33
|
+
.. code-block:: bash
|
|
34
|
+
|
|
35
|
+
export A_SYNC_CACHE_TYPE=memory
|
|
36
|
+
export A_SYNC_RAM_CACHE_MAXSIZE=100
|
|
37
|
+
export A_SYNC_RAM_CACHE_TTL=60
|
|
38
|
+
|
|
39
|
+
TODO: explain how and where these values are used
|
|
40
|
+
|
|
41
|
+
See Also:
|
|
42
|
+
- :mod:`concurrent.futures`: For more details on executors.
|
|
43
|
+
- :mod:`functools`: For caching mechanisms.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
import functools
|
|
47
|
+
import os
|
|
48
|
+
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
|
|
49
|
+
from concurrent.futures._base import Executor
|
|
50
|
+
|
|
51
|
+
from a_sync._typing import *
|
|
52
|
+
|
|
53
|
+
EXECUTOR_TYPE = os.environ.get("A_SYNC_EXECUTOR_TYPE", "threads")
|
|
54
|
+
"""Specifies the type of executor to use.
|
|
55
|
+
|
|
56
|
+
Valid values are strings that start with 'p' for :class:`~concurrent.futures.ProcessPoolExecutor`
|
|
57
|
+
(e.g., 'processes') or 't' for :class:`~concurrent.futures.ThreadPoolExecutor` (e.g., 'threads').
|
|
58
|
+
Defaults to 'threads'.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
EXECUTOR_VALUE = int(os.environ.get("A_SYNC_EXECUTOR_VALUE", 8))
|
|
62
|
+
"""Specifies the number of workers for the executor. Defaults to 8."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@functools.lru_cache(maxsize=1)
|
|
66
|
+
def get_default_executor() -> Executor:
|
|
67
|
+
"""Get the default executor based on the EXECUTOR_TYPE environment variable.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
An instance of either :class:`~concurrent.futures.ProcessPoolExecutor`
|
|
71
|
+
or :class:`~concurrent.futures.ThreadPoolExecutor`.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ValueError: If an invalid EXECUTOR_TYPE is specified. Valid values are
|
|
75
|
+
strings that start with 'p' for :class:`~concurrent.futures.ProcessPoolExecutor`
|
|
76
|
+
or 't' for :class:`~concurrent.futures.ThreadPoolExecutor`.
|
|
77
|
+
|
|
78
|
+
Examples:
|
|
79
|
+
>>> import os
|
|
80
|
+
>>> os.environ["A_SYNC_EXECUTOR_TYPE"] = "threads"
|
|
81
|
+
>>> executor = get_default_executor()
|
|
82
|
+
>>> isinstance(executor, ThreadPoolExecutor)
|
|
83
|
+
True
|
|
84
|
+
|
|
85
|
+
>>> os.environ["A_SYNC_EXECUTOR_TYPE"] = "processes"
|
|
86
|
+
>>> executor = get_default_executor()
|
|
87
|
+
>>> isinstance(executor, ProcessPoolExecutor)
|
|
88
|
+
True
|
|
89
|
+
"""
|
|
90
|
+
if EXECUTOR_TYPE.lower().startswith("p"): # p, P, proc, Processes, etc
|
|
91
|
+
return ProcessPoolExecutor(EXECUTOR_VALUE)
|
|
92
|
+
elif EXECUTOR_TYPE.lower().startswith("t"): # t, T, thread, THREADS, etc
|
|
93
|
+
return ThreadPoolExecutor(EXECUTOR_VALUE)
|
|
94
|
+
raise ValueError("Invalid value for A_SYNC_EXECUTOR_TYPE. Please use 'threads' or 'processes'.")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
default_sync_executor = get_default_executor()
|
|
98
|
+
|
|
99
|
+
null_modifiers = ModifierKwargs(
|
|
100
|
+
default=None,
|
|
101
|
+
cache_type=None,
|
|
102
|
+
cache_typed=False,
|
|
103
|
+
ram_cache_maxsize=-1,
|
|
104
|
+
ram_cache_ttl=None,
|
|
105
|
+
runs_per_minute=None,
|
|
106
|
+
semaphore=None,
|
|
107
|
+
executor=default_sync_executor,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
#####################
|
|
111
|
+
# Default Modifiers #
|
|
112
|
+
#####################
|
|
113
|
+
|
|
114
|
+
# User configurable default modifiers to be applied to any a_sync decorated function if you do not specify kwarg values for each modifier.
|
|
115
|
+
|
|
116
|
+
DEFAULT_MODE: DefaultMode = os.environ.get("A_SYNC_DEFAULT_MODE") # type: ignore [assignment]
|
|
117
|
+
"""Sets the default mode for a_sync functions if not specified."""
|
|
118
|
+
|
|
119
|
+
CACHE_TYPE: CacheType = (
|
|
120
|
+
typ
|
|
121
|
+
if (typ := os.environ.get("A_SYNC_CACHE_TYPE", "").lower())
|
|
122
|
+
else null_modifiers["cache_type"]
|
|
123
|
+
)
|
|
124
|
+
"""Sets the default cache type. If not specified, defaults to None."""
|
|
125
|
+
|
|
126
|
+
CACHE_TYPED = bool(os.environ.get("A_SYNC_CACHE_TYPED"))
|
|
127
|
+
"""Boolean flag to determine if cache keys should consider types."""
|
|
128
|
+
|
|
129
|
+
RAM_CACHE_MAXSIZE = int(os.environ.get("A_SYNC_RAM_CACHE_MAXSIZE", -1))
|
|
130
|
+
"""
|
|
131
|
+
Sets the maximum size for the RAM cache. Defaults to -1.
|
|
132
|
+
# TODO: explain what -1 does
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
RAM_CACHE_TTL = (
|
|
136
|
+
ttl
|
|
137
|
+
if (ttl := float(os.environ.get("A_SYNC_RAM_CACHE_TTL", 0)))
|
|
138
|
+
else null_modifiers["ram_cache_ttl"]
|
|
139
|
+
)
|
|
140
|
+
"""
|
|
141
|
+
Sets the time-to-live for cache entries. If not specified, defaults to 0, which is then checked against
|
|
142
|
+
`null_modifiers["ram_cache_ttl"]` to potentially set it to `None`, meaning cache entries do not expire
|
|
143
|
+
by default.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
RUNS_PER_MINUTE = (
|
|
148
|
+
rpm
|
|
149
|
+
if (rpm := int(os.environ.get("A_SYNC_RUNS_PER_MINUTE", 0)))
|
|
150
|
+
else null_modifiers["runs_per_minute"]
|
|
151
|
+
)
|
|
152
|
+
"""Sets the rate limit for function execution."""
|
|
153
|
+
|
|
154
|
+
SEMAPHORE = (
|
|
155
|
+
rpm if (rpm := int(os.environ.get("A_SYNC_SEMAPHORE", 0))) else null_modifiers["semaphore"]
|
|
156
|
+
)
|
|
157
|
+
"""Sets the semaphore limit for function execution."""
|
|
158
|
+
|
|
159
|
+
user_set_default_modifiers = ModifierKwargs(
|
|
160
|
+
default=DEFAULT_MODE,
|
|
161
|
+
cache_type=CACHE_TYPE,
|
|
162
|
+
cache_typed=CACHE_TYPED,
|
|
163
|
+
ram_cache_maxsize=RAM_CACHE_MAXSIZE,
|
|
164
|
+
ram_cache_ttl=RAM_CACHE_TTL,
|
|
165
|
+
runs_per_minute=RUNS_PER_MINUTE,
|
|
166
|
+
semaphore=SEMAPHORE,
|
|
167
|
+
executor=default_sync_executor,
|
|
168
|
+
)
|