ez-a-sync 0.22.13__py3-none-any.whl → 0.22.15__py3-none-any.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 ez-a-sync might be problematic. Click here for more details.

Files changed (73) hide show
  1. a_sync/ENVIRONMENT_VARIABLES.py +4 -3
  2. a_sync/__init__.py +30 -12
  3. a_sync/_smart.py +132 -28
  4. a_sync/_typing.py +56 -12
  5. a_sync/a_sync/__init__.py +35 -10
  6. a_sync/a_sync/_descriptor.py +74 -26
  7. a_sync/a_sync/_flags.py +14 -6
  8. a_sync/a_sync/_helpers.py +8 -7
  9. a_sync/a_sync/_kwargs.py +3 -2
  10. a_sync/a_sync/_meta.py +120 -28
  11. a_sync/a_sync/abstract.py +102 -28
  12. a_sync/a_sync/base.py +34 -16
  13. a_sync/a_sync/config.py +47 -13
  14. a_sync/a_sync/decorator.py +239 -117
  15. a_sync/a_sync/function.py +416 -146
  16. a_sync/a_sync/method.py +197 -59
  17. a_sync/a_sync/modifiers/__init__.py +47 -5
  18. a_sync/a_sync/modifiers/cache/__init__.py +46 -17
  19. a_sync/a_sync/modifiers/cache/memory.py +86 -20
  20. a_sync/a_sync/modifiers/limiter.py +52 -22
  21. a_sync/a_sync/modifiers/manager.py +98 -16
  22. a_sync/a_sync/modifiers/semaphores.py +48 -15
  23. a_sync/a_sync/property.py +383 -82
  24. a_sync/a_sync/singleton.py +1 -0
  25. a_sync/aliases.py +0 -1
  26. a_sync/asyncio/__init__.py +4 -1
  27. a_sync/asyncio/as_completed.py +177 -49
  28. a_sync/asyncio/create_task.py +31 -17
  29. a_sync/asyncio/gather.py +72 -52
  30. a_sync/asyncio/utils.py +3 -3
  31. a_sync/exceptions.py +78 -23
  32. a_sync/executor.py +120 -71
  33. a_sync/future.py +575 -158
  34. a_sync/iter.py +110 -50
  35. a_sync/primitives/__init__.py +14 -2
  36. a_sync/primitives/_debug.py +13 -13
  37. a_sync/primitives/_loggable.py +5 -4
  38. a_sync/primitives/locks/__init__.py +5 -2
  39. a_sync/primitives/locks/counter.py +38 -36
  40. a_sync/primitives/locks/event.py +21 -7
  41. a_sync/primitives/locks/prio_semaphore.py +182 -62
  42. a_sync/primitives/locks/semaphore.py +78 -77
  43. a_sync/primitives/queue.py +560 -58
  44. a_sync/sphinx/__init__.py +0 -1
  45. a_sync/sphinx/ext.py +160 -50
  46. a_sync/task.py +262 -97
  47. a_sync/utils/__init__.py +12 -6
  48. a_sync/utils/iterators.py +127 -43
  49. {ez_a_sync-0.22.13.dist-info → ez_a_sync-0.22.15.dist-info}/METADATA +1 -1
  50. ez_a_sync-0.22.15.dist-info/RECORD +74 -0
  51. {ez_a_sync-0.22.13.dist-info → ez_a_sync-0.22.15.dist-info}/WHEEL +1 -1
  52. tests/conftest.py +1 -2
  53. tests/executor.py +112 -9
  54. tests/fixtures.py +61 -32
  55. tests/test_abstract.py +7 -4
  56. tests/test_as_completed.py +54 -21
  57. tests/test_base.py +66 -17
  58. tests/test_cache.py +31 -15
  59. tests/test_decorator.py +54 -28
  60. tests/test_executor.py +8 -13
  61. tests/test_future.py +45 -8
  62. tests/test_gather.py +8 -2
  63. tests/test_helpers.py +2 -0
  64. tests/test_iter.py +55 -13
  65. tests/test_limiter.py +5 -3
  66. tests/test_meta.py +23 -9
  67. tests/test_modified.py +4 -1
  68. tests/test_semaphore.py +15 -8
  69. tests/test_singleton.py +15 -10
  70. tests/test_task.py +126 -28
  71. ez_a_sync-0.22.13.dist-info/RECORD +0 -74
  72. {ez_a_sync-0.22.13.dist-info → ez_a_sync-0.22.15.dist-info}/LICENSE.txt +0 -0
  73. {ez_a_sync-0.22.13.dist-info → ez_a_sync-0.22.15.dist-info}/top_level.txt +0 -0
a_sync/a_sync/abstract.py CHANGED
@@ -1,3 +1,14 @@
1
+ """
2
+ This module provides an abstract base class for defining asynchronous and synchronous behavior.
3
+
4
+ The ASyncABC class uses the ASyncMeta metaclass to automatically wrap its methods
5
+ with asynchronous or synchronous behavior based on flags. Subclasses must
6
+ implement the abstract methods to define the flag name, flag value, and
7
+ default mode for asynchronous or synchronous execution.
8
+
9
+ Note: It is recommended to use ASyncGenericBase for most use cases. This class
10
+ is intended for more custom implementations if necessary.
11
+ """
1
12
 
2
13
  import abc
3
14
  import functools
@@ -11,70 +22,133 @@ from a_sync.exceptions import NoFlagsFound
11
22
 
12
23
  logger = logging.getLogger(__name__)
13
24
 
25
+
14
26
  class ASyncABC(metaclass=ASyncMeta):
27
+ """Abstract Base Class for defining asynchronous and synchronous behavior.
28
+
29
+ This class uses the ASyncMeta metaclass to automatically wrap its methods
30
+ with asynchronous or synchronous behavior based on flags. Subclasses must
31
+ implement the abstract methods to define the flag name, flag value, and
32
+ default mode for asynchronous or synchronous execution.
33
+ """
15
34
 
16
35
  ##################################
17
36
  # Concrete Methods (overridable) #
18
37
  ##################################
19
38
 
20
39
  def __a_sync_should_await__(self, kwargs: dict) -> bool:
21
- """Returns a boolean that indicates whether methods of 'instance' should be called as sync or async methods."""
40
+ """Determines if methods should be called asynchronously.
41
+
42
+ This method first checks the provided keyword arguments for flags
43
+ indicating the desired execution mode. If no flags are found, it
44
+ defaults to the instance's asynchronous flag.
45
+
46
+ Args:
47
+ kwargs: A dictionary of keyword arguments to check for flags.
48
+ """
22
49
  try:
23
- # Defer to kwargs always
24
50
  return self.__a_sync_should_await_from_kwargs__(kwargs)
25
51
  except exceptions.NoFlagsFound:
26
- # No flag found in kwargs, check for a flag attribute.
27
52
  return self.__a_sync_instance_should_await__
28
53
 
29
54
  @functools.cached_property
30
55
  def __a_sync_instance_should_await__(self) -> bool:
56
+ """Indicates if the instance should default to asynchronous execution.
57
+
58
+ This property can be overridden if dynamic behavior is needed. For
59
+ instance, to allow hot-swapping of instance modes, redefine this as a
60
+ non-cached property.
31
61
  """
32
- A flag indicating whether the instance should default to asynchronous execution.
33
-
34
- You can override this if you want.
35
- If you want to be able to hotswap instance modes, you can redefine this as a non-cached property.
36
- """
37
- return _flags.negate_if_necessary(self.__a_sync_flag_name__, self.__a_sync_flag_value__)
38
-
62
+ return _flags.negate_if_necessary(
63
+ self.__a_sync_flag_name__, self.__a_sync_flag_value__
64
+ )
65
+
39
66
  def __a_sync_should_await_from_kwargs__(self, kwargs: dict) -> bool:
40
- """You can override this if you want."""
67
+ """Determines execution mode from keyword arguments.
68
+
69
+ This method can be overridden to customize how flags are extracted
70
+ from keyword arguments.
71
+
72
+ Args:
73
+ kwargs: A dictionary of keyword arguments to check for flags.
74
+
75
+ Raises:
76
+ NoFlagsFound: If no valid flags are found in the keyword arguments.
77
+ """
41
78
  if flag := _kwargs.get_flag_name(kwargs):
42
79
  return _kwargs.is_sync(flag, kwargs, pop_flag=True) # type: ignore [arg-type]
43
80
  raise NoFlagsFound("kwargs", kwargs.keys())
44
-
81
+
45
82
  @classmethod
46
83
  def __a_sync_instance_will_be_sync__(cls, args: tuple, kwargs: dict) -> bool:
47
- """You can override this if you want."""
48
- logger.debug("checking `%s.%s.__init__` signature against provided kwargs to determine a_sync mode for the new instance", cls.__module__, cls.__name__)
84
+ """Determines if a new instance will be synchronous.
85
+
86
+ This method checks the constructor's signature against provided
87
+ keyword arguments to determine the execution mode for the new instance.
88
+
89
+ Args:
90
+ args: A tuple of positional arguments for the instance.
91
+ kwargs: A dictionary of keyword arguments for the instance.
92
+ """
93
+ logger.debug(
94
+ "checking `%s.%s.__init__` signature against provided kwargs to determine a_sync mode for the new instance",
95
+ cls.__module__,
96
+ cls.__name__,
97
+ )
49
98
  if flag := _kwargs.get_flag_name(kwargs):
50
99
  sync = _kwargs.is_sync(flag, kwargs) # type: ignore [arg-type]
51
- logger.debug("kwargs indicate the new instance created with args %s %s is %ssynchronous", args, kwargs, 'a' if sync is False else '')
100
+ logger.debug(
101
+ "kwargs indicate the new instance created with args %s %s is %ssynchronous",
102
+ args,
103
+ kwargs,
104
+ "a" if sync is False else "",
105
+ )
52
106
  return sync
53
- logger.debug("No valid flags found in kwargs, checking class definition for defined default")
107
+ logger.debug(
108
+ "No valid flags found in kwargs, checking class definition for defined default"
109
+ )
54
110
  return cls.__a_sync_default_mode__() # type: ignore [arg-type]
55
111
 
56
112
  ######################################
57
113
  # Concrete Methods (non-overridable) #
58
114
  ######################################
59
-
115
+
60
116
  @property
61
117
  def __a_sync_modifiers__(self: "ASyncABC") -> ModifierKwargs:
62
- """You should not override this."""
118
+ """Retrieves modifiers for the instance.
119
+
120
+ This method should not be overridden. It returns the modifiers
121
+ associated with the instance, which are used to customize behavior.
122
+ """
63
123
  return modifiers.get_modifiers_from(self)
64
124
 
65
125
  ####################
66
126
  # Abstract Methods #
67
127
  ####################
68
128
 
69
- @abc.abstractproperty
129
+ @property
130
+ @abc.abstractmethod
70
131
  def __a_sync_flag_name__(self) -> str:
71
- pass
72
-
73
- @abc.abstractproperty
132
+ """Abstract property for the flag name.
133
+
134
+ Subclasses must implement this property to return the name of the flag
135
+ used to determine execution mode.
136
+ """
137
+
138
+ @property
139
+ @abc.abstractmethod
74
140
  def __a_sync_flag_value__(self) -> bool:
75
- pass
76
-
77
- @abc.abstractclassmethod # type: ignore [arg-type, misc]
78
- def __a_sync_default_mode__(cls) -> bool: # type: ignore [empty-body]
79
- # mypy doesnt recognize this abc member
80
- pass
141
+ """Abstract property for the flag value.
142
+
143
+ Subclasses must implement this property to return the value of the flag
144
+ indicating the default execution mode.
145
+ """
146
+
147
+ @classmethod
148
+ @abc.abstractmethod # type: ignore [arg-type, misc]
149
+ def __a_sync_default_mode__(cls) -> bool: # type: ignore [empty-body]
150
+ """Abstract class method for the default execution mode.
151
+
152
+ Subclasses must implement this method to return the default execution
153
+ mode (synchronous or asynchronous) for instances of the class.
154
+ """
a_sync/a_sync/base.py CHANGED
@@ -11,6 +11,7 @@ from a_sync.a_sync.abstract import ASyncABC
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
+
14
15
  class ASyncGenericBase(ASyncABC):
15
16
  """
16
17
  Base class for creating dual-function sync/async-capable classes without writing all your code twice.
@@ -35,7 +36,7 @@ class ASyncGenericBase(ASyncABC):
35
36
  @a_sync
36
37
  async def my_method(self):
37
38
  return await another_async_operation()
38
-
39
+
39
40
  # Synchronous usage
40
41
  obj = MyClass(sync=True)
41
42
  sync_result = obj.my_property
@@ -56,8 +57,10 @@ class ASyncGenericBase(ASyncABC):
56
57
  def __init__(self):
57
58
  if type(self) is ASyncGenericBase:
58
59
  cls_name = type(self).__name__
59
- raise NotImplementedError(f"You should not create instances of `{cls_name}` directly, you should subclass `ASyncGenericBase` instead.")
60
-
60
+ raise NotImplementedError(
61
+ f"You should not create instances of `{cls_name}` directly, you should subclass `ASyncGenericBase` instead."
62
+ )
63
+
61
64
  @functools.cached_property
62
65
  def __a_sync_flag_name__(self) -> str:
63
66
  logger.debug("checking a_sync flag for %s", self)
@@ -67,8 +70,14 @@ class ASyncGenericBase(ASyncABC):
67
70
  # We can't get the flag name from the __init__ signature,
68
71
  # but maybe the implementation sets the flag somewhere else.
69
72
  # Let's check the instance's atributes
70
- logger.debug("unable to find flag name using `%s.__init__` signature, checking for flag attributes defined on %s", self.__class__.__name__, self)
71
- present_flags = [flag for flag in _flags.VIABLE_FLAGS if hasattr(self, flag)]
73
+ logger.debug(
74
+ "unable to find flag name using `%s.__init__` signature, checking for flag attributes defined on %s",
75
+ self.__class__.__name__,
76
+ self,
77
+ )
78
+ present_flags = [
79
+ flag for flag in _flags.VIABLE_FLAGS if hasattr(self, flag)
80
+ ]
72
81
  if not present_flags:
73
82
  raise exceptions.NoFlagsFound(self) from None
74
83
  if len(present_flags) > 1:
@@ -77,7 +86,7 @@ class ASyncGenericBase(ASyncABC):
77
86
  if not isinstance(flag, str):
78
87
  raise exceptions.InvalidFlag(flag)
79
88
  return flag
80
-
89
+
81
90
  @functools.cached_property
82
91
  def __a_sync_flag_value__(self) -> bool:
83
92
  """If you wish to be able to hotswap default modes, just duplicate this def as a non-cached property."""
@@ -85,7 +94,7 @@ class ASyncGenericBase(ASyncABC):
85
94
  flag_value = getattr(self, flag)
86
95
  if not isinstance(flag_value, bool):
87
96
  raise exceptions.InvalidFlagValue(flag, flag_value)
88
- logger.debug('`%s.%s` is currently %s', self, flag, flag_value)
97
+ logger.debug("`%s.%s` is currently %s", self, flag, flag_value)
89
98
  return flag_value
90
99
 
91
100
  @classmethod # type: ignore [misc]
@@ -97,14 +106,21 @@ class ASyncGenericBase(ASyncABC):
97
106
  flag = cls.__get_a_sync_flag_name_from_class_def()
98
107
  flag_value = cls.__get_a_sync_flag_value_from_class_def(flag)
99
108
  sync = _flags.negate_if_necessary(flag, flag_value) # type: ignore [arg-type]
100
- logger.debug("`%s.%s` indicates default mode is %ssynchronous", cls, flag, 'a' if sync is False else '')
109
+ logger.debug(
110
+ "`%s.%s` indicates default mode is %ssynchronous",
111
+ cls,
112
+ flag,
113
+ "a" if sync is False else "",
114
+ )
101
115
  return sync
102
-
116
+
103
117
  @classmethod
104
118
  def __get_a_sync_flag_name_from_signature(cls) -> Optional[str]:
105
119
  logger.debug("Searching for flags defined on %s.__init__", cls)
106
120
  if cls.__name__ == "ASyncGenericBase":
107
- logger.debug("There are no flags defined on the base class, this is expected. Skipping.")
121
+ logger.debug(
122
+ "There are no flags defined on the base class, this is expected. Skipping."
123
+ )
108
124
  return None
109
125
  parameters = inspect.signature(cls.__init__).parameters
110
126
  logger.debug("parameters: %s", parameters)
@@ -115,17 +131,19 @@ class ASyncGenericBase(ASyncABC):
115
131
  logger.debug("Searching for flags defined on %s", cls)
116
132
  try:
117
133
  return cls.__parse_flag_name_from_list(cls.__dict__) # type: ignore [arg-type]
118
- # idk why __dict__ doesn't type check as a dict
134
+ # idk why __dict__ doesn't type check as a dict
119
135
  except exceptions.NoFlagsFound:
120
136
  for base in cls.__bases__:
121
137
  with suppress(exceptions.NoFlagsFound):
122
- return cls.__parse_flag_name_from_list(base.__dict__) # type: ignore [arg-type]
123
- # idk why __dict__ doesn't type check as a dict
138
+ return cls.__parse_flag_name_from_list(base.__dict__) # type: ignore [arg-type]
139
+ # idk why __dict__ doesn't type check as a dict
124
140
  raise exceptions.NoFlagsFound(cls, list(cls.__dict__.keys()))
125
141
 
126
142
  @classmethod # type: ignore [misc]
127
143
  def __a_sync_flag_default_value_from_signature(cls) -> bool:
128
- logger.debug("checking `__init__` signature for default %s a_sync flag value", cls)
144
+ logger.debug(
145
+ "checking `__init__` signature for default %s a_sync flag value", cls
146
+ )
129
147
  signature = inspect.signature(cls.__init__)
130
148
  flag = cls.__get_a_sync_flag_name_from_signature()
131
149
  flag_value = signature.parameters[flag].default
@@ -133,7 +151,7 @@ class ASyncGenericBase(ASyncABC):
133
151
  raise NotImplementedError(
134
152
  "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"
135
153
  )
136
- logger.debug('%s defines %s, default value %s', cls, flag, flag_value)
154
+ logger.debug("%s defines %s, default value %s", cls, flag, flag_value)
137
155
  return flag_value
138
156
 
139
157
  @classmethod
@@ -154,4 +172,4 @@ class ASyncGenericBase(ASyncABC):
154
172
  raise exceptions.TooManyFlags(cls, present_flags)
155
173
  flag = present_flags[0]
156
174
  logger.debug("found flag %s", flag)
157
- return flag
175
+ return flag
a_sync/a_sync/config.py CHANGED
@@ -1,9 +1,23 @@
1
1
  """
2
- Configuration module for a_sync library.
2
+ Configuration module for the a_sync library.
3
3
 
4
4
  This module provides configuration options and default settings for the a_sync library.
5
5
  It includes functionality for setting up executors, defining default modifiers,
6
6
  and handling environment variable configurations.
7
+
8
+ Environment Variables:
9
+ A_SYNC_EXECUTOR_TYPE: Specifies the type of executor to use. Valid values are
10
+ strings that start with 'p' for ProcessPoolExecutor (e.g., 'processes')
11
+ or 't' for ThreadPoolExecutor (e.g., 'threads'). Defaults to 'threads'.
12
+ A_SYNC_EXECUTOR_VALUE: Specifies the number of workers for the executor.
13
+ Defaults to 8.
14
+ A_SYNC_DEFAULT_MODE: Sets the default mode for a_sync functions if not specified.
15
+ A_SYNC_CACHE_TYPE: Sets the default cache type. If not specified, defaults to None.
16
+ A_SYNC_CACHE_TYPED: Boolean flag to determine if cache keys should consider types.
17
+ A_SYNC_RAM_CACHE_MAXSIZE: Sets the maximum size for the RAM cache. Defaults to -1.
18
+ A_SYNC_RAM_CACHE_TTL: Sets the time-to-live for cache entries. Defaults to None.
19
+ A_SYNC_RUNS_PER_MINUTE: Sets the rate limit for function execution.
20
+ A_SYNC_SEMAPHORE: Sets the semaphore limit for function execution.
7
21
  """
8
22
 
9
23
  import functools
@@ -16,22 +30,26 @@ from a_sync._typing import *
16
30
  EXECUTOR_TYPE = os.environ.get("A_SYNC_EXECUTOR_TYPE", "threads")
17
31
  EXECUTOR_VALUE = int(os.environ.get("A_SYNC_EXECUTOR_VALUE", 8))
18
32
 
33
+
19
34
  @functools.lru_cache(maxsize=1)
20
35
  def get_default_executor() -> Executor:
21
- """
22
- Get the default executor based on the :obj:`EXECUTOR_TYPE` environment variable.
36
+ """Get the default executor based on the EXECUTOR_TYPE environment variable.
23
37
 
24
38
  Returns:
25
- Executor: An instance of either ProcessPoolExecutor or ThreadPoolExecutor.
39
+ An instance of either ProcessPoolExecutor or ThreadPoolExecutor.
26
40
 
27
41
  Raises:
28
- :class:`ValueError`: If an invalid EXECUTOR_TYPE is specified.
42
+ ValueError: If an invalid EXECUTOR_TYPE is specified. Valid values are
43
+ strings that start with 'p' for ProcessPoolExecutor or 't' for ThreadPoolExecutor.
29
44
  """
30
- if EXECUTOR_TYPE.lower().startswith('p'): # p, P, proc, Processes, etc
45
+ if EXECUTOR_TYPE.lower().startswith("p"): # p, P, proc, Processes, etc
31
46
  return ProcessPoolExecutor(EXECUTOR_VALUE)
32
- elif EXECUTOR_TYPE.lower().startswith('t'): # t, T, thread, THREADS, etc
47
+ elif EXECUTOR_TYPE.lower().startswith("t"): # t, T, thread, THREADS, etc
33
48
  return ThreadPoolExecutor(EXECUTOR_VALUE)
34
- raise ValueError("Invalid value for A_SYNC_EXECUTOR_TYPE. Please use 'threads' or 'processes'.")
49
+ raise ValueError(
50
+ "Invalid value for A_SYNC_EXECUTOR_TYPE. Please use 'threads' or 'processes'."
51
+ )
52
+
35
53
 
36
54
  default_sync_executor = get_default_executor()
37
55
 
@@ -53,13 +71,29 @@ null_modifiers = ModifierKwargs(
53
71
  # User configurable default modifiers to be applied to any a_sync decorated function if you do not specify kwarg values for each modifier.
54
72
 
55
73
  DEFAULT_MODE: DefaultMode = os.environ.get("A_SYNC_DEFAULT_MODE") # type: ignore [assignment]
56
- CACHE_TYPE: CacheType = typ if (typ := os.environ.get("A_SYNC_CACHE_TYPE", "").lower()) else null_modifiers['cache_type']
74
+ CACHE_TYPE: CacheType = (
75
+ typ
76
+ if (typ := os.environ.get("A_SYNC_CACHE_TYPE", "").lower())
77
+ else null_modifiers["cache_type"]
78
+ )
57
79
  CACHE_TYPED = bool(os.environ.get("A_SYNC_CACHE_TYPED"))
58
- RAM_CACHE_MAXSIZE = int(os.environ.get("A_SYNC_RAM_CACHE_MAXSIZE", -1))
59
- RAM_CACHE_TTL = ttl if (ttl := float(os.environ.get("A_SYNC_RAM_CACHE_TTL", 0))) else null_modifiers['ram_cache_ttl']
80
+ RAM_CACHE_MAXSIZE = int(os.environ.get("A_SYNC_RAM_CACHE_MAXSIZE", -1))
81
+ RAM_CACHE_TTL = (
82
+ ttl
83
+ if (ttl := float(os.environ.get("A_SYNC_RAM_CACHE_TTL", 0)))
84
+ else null_modifiers["ram_cache_ttl"]
85
+ )
60
86
 
61
- RUNS_PER_MINUTE = rpm if (rpm := int(os.environ.get("A_SYNC_RUNS_PER_MINUTE", 0))) else null_modifiers['runs_per_minute']
62
- SEMAPHORE = rpm if (rpm := int(os.environ.get("A_SYNC_SEMAPHORE", 0))) else null_modifiers['semaphore']
87
+ RUNS_PER_MINUTE = (
88
+ rpm
89
+ if (rpm := int(os.environ.get("A_SYNC_RUNS_PER_MINUTE", 0)))
90
+ else null_modifiers["runs_per_minute"]
91
+ )
92
+ SEMAPHORE = (
93
+ rpm
94
+ if (rpm := int(os.environ.get("A_SYNC_SEMAPHORE", 0)))
95
+ else null_modifiers["semaphore"]
96
+ )
63
97
 
64
98
  user_set_default_modifiers = ModifierKwargs(
65
99
  default=DEFAULT_MODE,