Skip to content

Commit e6a586d

Browse files
authored
feat: ✨ Add threadsafe parameter in handler decorators
1 parent e660ffb commit e6a586d

File tree

4 files changed

+121
-100
lines changed

4 files changed

+121
-100
lines changed

cq/_core/handler.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,45 +94,57 @@ def __call__(
9494
self,
9595
input_or_handler_type: type[I],
9696
/,
97+
*,
98+
threadsafe: bool | None = ...,
9799
) -> Callable[[HandlerType[[I], O]], HandlerType[[I], O]]: ...
98100

99101
@overload
100102
def __call__(
101103
self,
102104
input_or_handler_type: HandlerType[[I], O],
103105
/,
106+
*,
107+
threadsafe: bool | None = ...,
104108
) -> HandlerType[[I], O]: ...
105109

106110
@overload
107111
def __call__(
108112
self,
109113
input_or_handler_type: None = ...,
110114
/,
115+
*,
116+
threadsafe: bool | None = ...,
111117
) -> Callable[[HandlerType[[I], O]], HandlerType[[I], O]]: ...
112118

113119
def __call__(
114120
self,
115121
input_or_handler_type: type[I] | HandlerType[[I], O] | None = None,
116122
/,
123+
*,
124+
threadsafe: bool | None = None,
117125
) -> Any:
118-
if input_or_handler_type is None:
119-
return self.__decorator
120-
121-
elif isclass(input_or_handler_type) and issubclass(
122-
input_or_handler_type,
123-
Handler,
126+
if (
127+
input_or_handler_type is not None
128+
and isclass(input_or_handler_type)
129+
and issubclass(input_or_handler_type, Handler)
124130
):
125-
return self.__decorator(input_or_handler_type)
131+
return self.__decorator(input_or_handler_type, threadsafe=threadsafe)
126132

127-
return partial(self.__decorator, input_type=input_or_handler_type) # type: ignore[arg-type]
133+
return partial(
134+
self.__decorator,
135+
input_type=input_or_handler_type, # type: ignore[arg-type]
136+
threadsafe=threadsafe,
137+
)
128138

129139
def __decorator(
130140
self,
131141
wrapped: HandlerType[[I], O],
142+
/,
132143
*,
133144
input_type: type[I] | None = None,
145+
threadsafe: bool | None = None,
134146
) -> HandlerType[[I], O]:
135-
factory = self.injection_module.make_async_factory(wrapped)
147+
factory = self.injection_module.make_async_factory(wrapped, threadsafe)
136148
input_type = input_type or _resolve_input_type(wrapped)
137149
self.manager.subscribe(input_type, factory)
138150
return wrapped

cq/middlewares/scope.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,15 @@
1717
class InjectionScopeMiddleware:
1818
scope_name: str
1919
exist_ok: bool = field(default=False, kw_only=True)
20+
threadsafe: bool | None = field(default=None, kw_only=True)
2021

2122
async def __call__(self, *args: Any, **kwargs: Any) -> MiddlewareResult[Any]:
2223
async with AsyncExitStack() as stack:
24+
context_manager = adefine_scope(self.scope_name, threadsafe=self.threadsafe)
2325
try:
24-
await stack.enter_async_context(
25-
adefine_scope(self.scope_name),
26-
)
27-
26+
await stack.enter_async_context(context_manager)
2827
except ScopeAlreadyDefinedError:
2928
if not self.exist_ok:
3029
raise
31-
30+
del context_manager
3231
yield

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ test = [
2222
[project]
2323
name = "python-cq"
2424
version = "0.0.0"
25-
description = "Lightweight CQRS library."
25+
description = "Lightweight CQRS library for async Python projects."
2626
license = "MIT"
2727
license-files = ["LICENSE"]
2828
readme = "README.md"

0 commit comments

Comments
 (0)