jararaca 0.3.9__py3-none-any.whl → 0.3.11__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 jararaca might be problematic. Click here for more details.
- jararaca/__init__.py +76 -5
- jararaca/cli.py +460 -116
- jararaca/core/uow.py +17 -12
- jararaca/messagebus/decorators.py +33 -30
- jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +30 -2
- jararaca/messagebus/interceptors/publisher_interceptor.py +7 -3
- jararaca/messagebus/publisher.py +14 -6
- jararaca/messagebus/worker.py +1102 -88
- jararaca/microservice.py +137 -34
- jararaca/observability/decorators.py +7 -3
- jararaca/observability/interceptor.py +4 -2
- jararaca/observability/providers/otel.py +14 -10
- jararaca/persistence/base.py +2 -1
- jararaca/persistence/interceptors/aiosqa_interceptor.py +167 -16
- jararaca/persistence/utilities.py +32 -20
- jararaca/presentation/decorators.py +96 -10
- jararaca/presentation/server.py +31 -4
- jararaca/presentation/websocket/context.py +30 -4
- jararaca/presentation/websocket/types.py +2 -2
- jararaca/presentation/websocket/websocket_interceptor.py +28 -4
- jararaca/reflect/__init__.py +0 -0
- jararaca/reflect/controller_inspect.py +75 -0
- jararaca/{tools → reflect}/metadata.py +25 -5
- jararaca/scheduler/{scheduler_v2.py → beat_worker.py} +49 -53
- jararaca/scheduler/decorators.py +55 -20
- jararaca/tools/app_config/interceptor.py +4 -2
- jararaca/utils/rabbitmq_utils.py +259 -5
- jararaca/utils/retry.py +141 -0
- {jararaca-0.3.9.dist-info → jararaca-0.3.11.dist-info}/METADATA +2 -1
- {jararaca-0.3.9.dist-info → jararaca-0.3.11.dist-info}/RECORD +33 -32
- {jararaca-0.3.9.dist-info → jararaca-0.3.11.dist-info}/WHEEL +1 -1
- jararaca/messagebus/worker_v2.py +0 -617
- jararaca/scheduler/scheduler.py +0 -161
- {jararaca-0.3.9.dist-info → jararaca-0.3.11.dist-info}/LICENSE +0 -0
- {jararaca-0.3.9.dist-info → jararaca-0.3.11.dist-info}/entry_points.txt +0 -0
|
@@ -213,7 +213,7 @@ class CRUDOperations(Generic[IDENTIFIABLE_T]):
|
|
|
213
213
|
# region PaginatedFilter
|
|
214
214
|
class PaginatedFilter(BaseModel):
|
|
215
215
|
page: Annotated[int, Field(gt=-1)] = 0
|
|
216
|
-
page_size: int = 10
|
|
216
|
+
page_size: Annotated[int, Field(gt=0)] = 10
|
|
217
217
|
sort_models: list[SortModel] = []
|
|
218
218
|
filter_models: list[FilterModel] = []
|
|
219
219
|
|
|
@@ -307,51 +307,63 @@ class QueryOperations(Generic[QUERY_FILTER_T, QUERY_ENTITY_T]):
|
|
|
307
307
|
Callable[[Select[Tuple[QUERY_ENTITY_T]]], Select[Tuple[QUERY_ENTITY_T]]]
|
|
308
308
|
] = [],
|
|
309
309
|
) -> "Paginated[QUERY_ENTITY_T]":
|
|
310
|
+
"""
|
|
311
|
+
Executes a query with the provided filter and interceptors.
|
|
312
|
+
Args:
|
|
313
|
+
filter: The filter to apply to the query.
|
|
314
|
+
interceptors: A list of functions that can modify the query before execution.
|
|
315
|
+
Returns:
|
|
316
|
+
Paginated[QUERY_ENTITY_T]: A paginated result containing the items and metadata.
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
tier_one_filtered_query = self.generate_filtered_query(
|
|
320
|
+
filter, select(self.entity_type)
|
|
321
|
+
)
|
|
310
322
|
|
|
311
|
-
|
|
323
|
+
tier_two_filtered_query = reduce(
|
|
312
324
|
lambda query, interceptor: interceptor(query),
|
|
313
325
|
interceptors,
|
|
314
|
-
|
|
326
|
+
tier_one_filtered_query,
|
|
315
327
|
)
|
|
316
328
|
|
|
317
329
|
if self.sort_rule_applier:
|
|
318
|
-
|
|
319
|
-
|
|
330
|
+
tier_two_filtered_query = (
|
|
331
|
+
self.sort_rule_applier.create_query_for_sorting_list(
|
|
332
|
+
tier_two_filtered_query, filter.sort_models
|
|
333
|
+
)
|
|
320
334
|
)
|
|
321
335
|
|
|
322
336
|
if self.filter_rule_applier:
|
|
323
|
-
|
|
324
|
-
|
|
337
|
+
tier_two_filtered_query = (
|
|
338
|
+
self.filter_rule_applier.create_query_for_filter_list(
|
|
339
|
+
tier_two_filtered_query, filter.filter_models
|
|
340
|
+
)
|
|
325
341
|
)
|
|
326
342
|
|
|
327
343
|
unpaginated_total = (
|
|
328
344
|
await self.session.execute(
|
|
329
|
-
select(func.count()).select_from(
|
|
345
|
+
select(func.count()).select_from(tier_two_filtered_query.subquery())
|
|
330
346
|
)
|
|
331
347
|
).scalar_one()
|
|
332
348
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
paginated_query = filtered_query.limit(filter.page_size).offset(
|
|
349
|
+
paginated_query = tier_two_filtered_query.limit(filter.page_size).offset(
|
|
336
350
|
(filter.page) * filter.page_size
|
|
337
351
|
)
|
|
338
352
|
|
|
339
|
-
|
|
353
|
+
paginated_total = (
|
|
340
354
|
await self.session.execute(
|
|
341
355
|
select(func.count()).select_from(paginated_query.subquery())
|
|
342
356
|
)
|
|
343
357
|
).scalar_one()
|
|
344
358
|
|
|
359
|
+
result = await self.session.execute(paginated_query)
|
|
360
|
+
result_scalars = list(self.judge_unique(result).scalars().all())
|
|
361
|
+
|
|
345
362
|
return Paginated(
|
|
346
|
-
items=
|
|
347
|
-
|
|
348
|
-
for e in self.judge_unique(
|
|
349
|
-
await self.session.execute(paginated_query)
|
|
350
|
-
).scalars()
|
|
351
|
-
],
|
|
352
|
-
total=filtered_total,
|
|
363
|
+
items=result_scalars,
|
|
364
|
+
total=paginated_total,
|
|
353
365
|
unpaginated_total=unpaginated_total,
|
|
354
|
-
total_pages=int(
|
|
366
|
+
total_pages=int(unpaginated_total / filter.page_size) + 1,
|
|
355
367
|
)
|
|
356
368
|
|
|
357
369
|
def judge_unique(
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
from
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from contextvars import ContextVar
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Any, Awaitable, Callable, Literal, Protocol, TypeVar, cast
|
|
3
5
|
|
|
4
6
|
from fastapi import APIRouter
|
|
5
7
|
from fastapi import Depends as DependsF
|
|
@@ -9,6 +11,10 @@ from fastapi.params import Depends
|
|
|
9
11
|
from jararaca.lifecycle import AppLifecycle
|
|
10
12
|
from jararaca.presentation.http_microservice import HttpMiddleware
|
|
11
13
|
from jararaca.presentation.websocket.decorators import WebSocketEndpoint
|
|
14
|
+
from jararaca.reflect.controller_inspect import (
|
|
15
|
+
ControllerMemberReflect,
|
|
16
|
+
inspect_controller,
|
|
17
|
+
)
|
|
12
18
|
|
|
13
19
|
DECORATED_FUNC = TypeVar("DECORATED_FUNC", bound=Callable[..., Any])
|
|
14
20
|
DECORATED_CLASS = TypeVar("DECORATED_CLASS", bound=Any)
|
|
@@ -73,15 +79,15 @@ class RestController:
|
|
|
73
79
|
**(self.options or {}),
|
|
74
80
|
)
|
|
75
81
|
|
|
76
|
-
members =
|
|
82
|
+
controller, members = inspect_controller(cls)
|
|
77
83
|
|
|
78
84
|
router_members = [
|
|
79
|
-
(name, mapping)
|
|
80
|
-
for name, member in members
|
|
85
|
+
(name, mapping, member)
|
|
86
|
+
for name, member in members.items()
|
|
81
87
|
if (
|
|
82
88
|
mapping := (
|
|
83
|
-
HttpMapping.get_http_mapping(member)
|
|
84
|
-
or WebSocketEndpoint.get(member)
|
|
89
|
+
HttpMapping.get_http_mapping(member.member_function)
|
|
90
|
+
or WebSocketEndpoint.get(member.member_function)
|
|
85
91
|
)
|
|
86
92
|
)
|
|
87
93
|
is not None
|
|
@@ -89,7 +95,7 @@ class RestController:
|
|
|
89
95
|
|
|
90
96
|
router_members.sort(key=lambda x: x[1].order)
|
|
91
97
|
|
|
92
|
-
for name, mapping in router_members:
|
|
98
|
+
for name, mapping, member in router_members:
|
|
93
99
|
route_dependencies: list[Depends] = []
|
|
94
100
|
for middlewares_by_hook in UseMiddleware.get_middlewares(
|
|
95
101
|
getattr(instance, name)
|
|
@@ -104,12 +110,18 @@ class RestController:
|
|
|
104
110
|
):
|
|
105
111
|
route_dependencies.append(DependsF(dependency.dependency))
|
|
106
112
|
|
|
113
|
+
instance_method = getattr(instance, name)
|
|
114
|
+
instance_method = wraps_with_attributes(
|
|
115
|
+
instance_method,
|
|
116
|
+
controller_member_reflect=member,
|
|
117
|
+
)
|
|
118
|
+
|
|
107
119
|
if isinstance(mapping, HttpMapping):
|
|
108
120
|
try:
|
|
109
121
|
router.add_api_route(
|
|
110
122
|
methods=[mapping.method],
|
|
111
123
|
path=mapping.path,
|
|
112
|
-
endpoint=
|
|
124
|
+
endpoint=instance_method,
|
|
113
125
|
dependencies=route_dependencies,
|
|
114
126
|
**(mapping.options or {}),
|
|
115
127
|
)
|
|
@@ -120,7 +132,7 @@ class RestController:
|
|
|
120
132
|
else:
|
|
121
133
|
router.add_api_websocket_route(
|
|
122
134
|
path=mapping.path,
|
|
123
|
-
endpoint=
|
|
135
|
+
endpoint=instance_method,
|
|
124
136
|
dependencies=route_dependencies,
|
|
125
137
|
**(mapping.options or {}),
|
|
126
138
|
)
|
|
@@ -299,3 +311,77 @@ class UseDependency:
|
|
|
299
311
|
@staticmethod
|
|
300
312
|
def get_dependencies(subject: DECORATED_FUNC) -> list["UseDependency"]:
|
|
301
313
|
return getattr(subject, UseDependency.__DEPENDENCY_ATTR__, [])
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def wraps_with_member_data(
|
|
317
|
+
controller_member: ControllerMemberReflect, func: Callable[..., Awaitable[Any]]
|
|
318
|
+
) -> Callable[..., Any]:
|
|
319
|
+
"""
|
|
320
|
+
A decorator that wraps a function and preserves its metadata.
|
|
321
|
+
This is useful for preserving metadata when using decorators.
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
@wraps(func)
|
|
325
|
+
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
326
|
+
|
|
327
|
+
with providing_controller_member(
|
|
328
|
+
controller_member=controller_member,
|
|
329
|
+
):
|
|
330
|
+
|
|
331
|
+
return await func(*args, **kwargs)
|
|
332
|
+
|
|
333
|
+
# Copy metadata from the original function to the wrapper
|
|
334
|
+
# for attr in dir(func):
|
|
335
|
+
# if not attr.startswith("__"):
|
|
336
|
+
# setattr(wrapper, attr, getattr(func, attr))
|
|
337
|
+
|
|
338
|
+
return wrapper
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
controller_member_ctxvar = ContextVar[ControllerMemberReflect](
|
|
342
|
+
"controller_member_ctxvar"
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@contextmanager
|
|
347
|
+
def providing_controller_member(
|
|
348
|
+
controller_member: ControllerMemberReflect,
|
|
349
|
+
) -> Any:
|
|
350
|
+
"""
|
|
351
|
+
Context manager to provide the controller member metadata.
|
|
352
|
+
This is used to preserve the metadata of the controller member
|
|
353
|
+
when using decorators.
|
|
354
|
+
"""
|
|
355
|
+
token = controller_member_ctxvar.set(controller_member)
|
|
356
|
+
try:
|
|
357
|
+
yield
|
|
358
|
+
finally:
|
|
359
|
+
controller_member_ctxvar.reset(token)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def use_controller_member() -> ControllerMemberReflect:
|
|
363
|
+
"""
|
|
364
|
+
Get the current controller member metadata.
|
|
365
|
+
This is used to access the metadata of the controller member
|
|
366
|
+
when using decorators.
|
|
367
|
+
"""
|
|
368
|
+
return controller_member_ctxvar.get()
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def wraps_with_attributes(
|
|
372
|
+
func: Callable[..., Awaitable[Any]], **attributes: Any
|
|
373
|
+
) -> Callable[..., Awaitable[Any]]:
|
|
374
|
+
"""
|
|
375
|
+
A decorator that wraps a function and preserves its attributes.
|
|
376
|
+
This is useful for preserving attributes when using decorators.
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
@wraps(func)
|
|
380
|
+
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
381
|
+
return await func(*args, **kwargs)
|
|
382
|
+
|
|
383
|
+
# Copy attributes from the original function to the wrapper
|
|
384
|
+
for key, value in attributes.items():
|
|
385
|
+
setattr(wrapper, key, value)
|
|
386
|
+
|
|
387
|
+
return wrapper
|
jararaca/presentation/server.py
CHANGED
|
@@ -7,9 +7,14 @@ from starlette.types import ASGIApp
|
|
|
7
7
|
from jararaca.core.uow import UnitOfWorkContextProvider
|
|
8
8
|
from jararaca.di import Container
|
|
9
9
|
from jararaca.lifecycle import AppLifecycle
|
|
10
|
-
from jararaca.microservice import
|
|
10
|
+
from jararaca.microservice import (
|
|
11
|
+
AppTransactionContext,
|
|
12
|
+
HttpTransactionData,
|
|
13
|
+
WebSocketTransactionData,
|
|
14
|
+
)
|
|
11
15
|
from jararaca.presentation.decorators import RestController
|
|
12
16
|
from jararaca.presentation.http_microservice import HttpMicroservice
|
|
17
|
+
from jararaca.reflect.controller_inspect import ControllerMemberReflect
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
class HttpAppLifecycle:
|
|
@@ -79,10 +84,32 @@ class HttpUowContextProviderDependency:
|
|
|
79
84
|
async def __call__(
|
|
80
85
|
self, websocket: WebSocket = None, request: Request = None # type: ignore
|
|
81
86
|
) -> AsyncGenerator[None, None]:
|
|
87
|
+
if request:
|
|
88
|
+
endpoint = request.scope["endpoint"]
|
|
89
|
+
elif websocket:
|
|
90
|
+
endpoint = websocket.scope["endpoint"]
|
|
91
|
+
else:
|
|
92
|
+
raise ValueError("Either request or websocket must be provided.")
|
|
93
|
+
|
|
94
|
+
member = getattr(endpoint, "controller_member_reflect", None)
|
|
95
|
+
|
|
96
|
+
if member is None:
|
|
97
|
+
raise ValueError("The endpoint does not have a controller member reflect.")
|
|
98
|
+
|
|
99
|
+
assert isinstance(member, ControllerMemberReflect), (
|
|
100
|
+
"Expected endpoint.controller_member_reflect to be of type "
|
|
101
|
+
"ControllerMemberReflect, but got: {}".format(type(member))
|
|
102
|
+
)
|
|
103
|
+
|
|
82
104
|
async with self.uow_provider(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
105
|
+
AppTransactionContext(
|
|
106
|
+
controller_member_reflect=member,
|
|
107
|
+
transaction_data=(
|
|
108
|
+
HttpTransactionData(request=request)
|
|
109
|
+
if request
|
|
110
|
+
else WebSocketTransactionData(websocket=websocket)
|
|
111
|
+
),
|
|
112
|
+
)
|
|
86
113
|
):
|
|
87
114
|
yield
|
|
88
115
|
|
|
@@ -16,12 +16,12 @@ class WebSocketConnectionManager(Protocol):
|
|
|
16
16
|
async def remove_websocket(self, websocket: WebSocket) -> None: ...
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
_ws_conn_manager_ctx = ContextVar[WebSocketConnectionManager]("ws_manage_ctx")
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def use_ws_manager() -> WebSocketConnectionManager:
|
|
23
23
|
try:
|
|
24
|
-
return
|
|
24
|
+
return _ws_conn_manager_ctx.get()
|
|
25
25
|
except LookupError:
|
|
26
26
|
raise RuntimeError("No WebSocketConnectionManager found")
|
|
27
27
|
|
|
@@ -30,9 +30,35 @@ def use_ws_manager() -> WebSocketConnectionManager:
|
|
|
30
30
|
def provide_ws_manager(
|
|
31
31
|
ws_manager: WebSocketConnectionManager,
|
|
32
32
|
) -> Generator[None, None, None]:
|
|
33
|
-
token =
|
|
33
|
+
token = _ws_conn_manager_ctx.set(ws_manager)
|
|
34
34
|
try:
|
|
35
35
|
yield
|
|
36
36
|
finally:
|
|
37
37
|
with suppress(ValueError):
|
|
38
|
-
|
|
38
|
+
_ws_conn_manager_ctx.reset(token)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class WebSocketMessageSender(Protocol):
|
|
42
|
+
async def send(self, rooms: list[str], message: WebSocketMessageBase) -> None: ...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_ws_msg_sender_ctx = ContextVar[WebSocketMessageSender]("ws_msg_sender_ctx")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def use_ws_message_sender() -> WebSocketMessageSender:
|
|
49
|
+
try:
|
|
50
|
+
return _ws_msg_sender_ctx.get()
|
|
51
|
+
except LookupError:
|
|
52
|
+
raise RuntimeError("No WebSocketMessageSender found")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@contextmanager
|
|
56
|
+
def provide_ws_message_sender(
|
|
57
|
+
ws_message_sender: WebSocketMessageSender,
|
|
58
|
+
) -> Generator[None, None, None]:
|
|
59
|
+
token = _ws_msg_sender_ctx.set(ws_message_sender)
|
|
60
|
+
try:
|
|
61
|
+
yield
|
|
62
|
+
finally:
|
|
63
|
+
with suppress(ValueError):
|
|
64
|
+
_ws_msg_sender_ctx.reset(token)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from jararaca.presentation.websocket.base_types import WebSocketMessageBase
|
|
2
|
-
from jararaca.presentation.websocket.context import
|
|
2
|
+
from jararaca.presentation.websocket.context import use_ws_message_sender
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class WebSocketMessage(WebSocketMessageBase):
|
|
6
6
|
|
|
7
7
|
async def send(self, *rooms: str) -> None:
|
|
8
|
-
await
|
|
8
|
+
await use_ws_message_sender().send(list(rooms), self)
|
|
@@ -13,9 +13,9 @@ from pydantic import BaseModel
|
|
|
13
13
|
from jararaca.core.uow import UnitOfWorkContextProvider
|
|
14
14
|
from jararaca.di import Container
|
|
15
15
|
from jararaca.microservice import (
|
|
16
|
-
AppContext,
|
|
17
16
|
AppInterceptor,
|
|
18
17
|
AppInterceptorWithLifecycle,
|
|
18
|
+
AppTransactionContext,
|
|
19
19
|
Microservice,
|
|
20
20
|
)
|
|
21
21
|
from jararaca.presentation.decorators import (
|
|
@@ -25,7 +25,9 @@ from jararaca.presentation.decorators import (
|
|
|
25
25
|
)
|
|
26
26
|
from jararaca.presentation.websocket.context import (
|
|
27
27
|
WebSocketConnectionManager,
|
|
28
|
+
WebSocketMessageSender,
|
|
28
29
|
provide_ws_manager,
|
|
30
|
+
provide_ws_message_sender,
|
|
29
31
|
)
|
|
30
32
|
from jararaca.presentation.websocket.decorators import (
|
|
31
33
|
INHERITS_WS_MESSAGE,
|
|
@@ -127,9 +129,23 @@ class WebSocketConnectionManagerImpl(WebSocketConnectionManager):
|
|
|
127
129
|
# async def setup_consumer(self, websocket: WebSocket) -> None: ...
|
|
128
130
|
|
|
129
131
|
|
|
132
|
+
class StagingWebSocketMessageSender(WebSocketMessageSender):
|
|
133
|
+
|
|
134
|
+
def __init__(self, connection: WebSocketConnectionManager) -> None:
|
|
135
|
+
self.connection = connection
|
|
136
|
+
self.staged_messages: list[tuple[list[str], WebSocketMessageBase]] = []
|
|
137
|
+
|
|
138
|
+
async def send(self, rooms: list[str], message: WebSocketMessageBase) -> None:
|
|
139
|
+
self.staged_messages.append((rooms, message))
|
|
140
|
+
|
|
141
|
+
async def flush(self) -> None:
|
|
142
|
+
for rooms, message in self.staged_messages:
|
|
143
|
+
await self.connection.send(rooms, message)
|
|
144
|
+
self.staged_messages.clear()
|
|
145
|
+
|
|
146
|
+
|
|
130
147
|
class WebSocketInterceptor(AppInterceptor, AppInterceptorWithLifecycle):
|
|
131
148
|
"""
|
|
132
|
-
@Deprecated
|
|
133
149
|
WebSocketInterceptor is responsible for managing WebSocket connections and
|
|
134
150
|
intercepting WebSocket requests within the application. It integrates with
|
|
135
151
|
the application's lifecycle and provides a router for WebSocket endpoints.
|
|
@@ -169,10 +185,18 @@ class WebSocketInterceptor(AppInterceptor, AppInterceptorWithLifecycle):
|
|
|
169
185
|
self.shutdown_event.set()
|
|
170
186
|
|
|
171
187
|
@asynccontextmanager
|
|
172
|
-
async def intercept(
|
|
188
|
+
async def intercept(
|
|
189
|
+
self, app_context: AppTransactionContext
|
|
190
|
+
) -> AsyncGenerator[None, None]:
|
|
173
191
|
|
|
174
|
-
|
|
192
|
+
staging_ws_messages_sender = StagingWebSocketMessageSender(
|
|
193
|
+
self.connection_manager
|
|
194
|
+
)
|
|
195
|
+
with provide_ws_manager(self.connection_manager), provide_ws_message_sender(
|
|
196
|
+
staging_ws_messages_sender
|
|
197
|
+
):
|
|
175
198
|
yield
|
|
199
|
+
await staging_ws_messages_sender.flush()
|
|
176
200
|
|
|
177
201
|
# def __wrap_with_uow_context_provider(
|
|
178
202
|
# self, uow: UnitOfWorkContextProvider, func: Callable[..., Any]
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Any, Callable, Mapping, Tuple, Type
|
|
4
|
+
|
|
5
|
+
from frozendict import frozendict
|
|
6
|
+
|
|
7
|
+
from jararaca.reflect.metadata import ControllerInstanceMetadata, SetMetadata
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class ControllerReflect:
|
|
12
|
+
|
|
13
|
+
controller_class: Type[Any]
|
|
14
|
+
metadata: Mapping[str, ControllerInstanceMetadata]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class ControllerMemberReflect:
|
|
19
|
+
controller_reflect: ControllerReflect
|
|
20
|
+
member_function: Callable[..., Any]
|
|
21
|
+
metadata: Mapping[str, ControllerInstanceMetadata]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def inspect_controller(
|
|
25
|
+
controller: Type[Any],
|
|
26
|
+
) -> Tuple[ControllerReflect, Mapping[str, ControllerMemberReflect]]:
|
|
27
|
+
"""
|
|
28
|
+
Inspect a controller class to extract its metadata and member functions.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
controller (Type[Any]): The controller class to inspect.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Tuple[ControllerReflect, list[ControllerMemberReflect]]: A tuple containing the controller reflect and a list of member reflects.
|
|
35
|
+
"""
|
|
36
|
+
controller_metadata_list = SetMetadata.get(controller)
|
|
37
|
+
|
|
38
|
+
controller_metadata_map = frozendict(
|
|
39
|
+
{
|
|
40
|
+
metadata.key: ControllerInstanceMetadata(
|
|
41
|
+
value=metadata.value, inherited=False
|
|
42
|
+
)
|
|
43
|
+
for metadata in controller_metadata_list
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
controller_reflect = ControllerReflect(
|
|
48
|
+
controller_class=controller, metadata=controller_metadata_map
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
members = {
|
|
52
|
+
name: ControllerMemberReflect(
|
|
53
|
+
controller_reflect=controller_reflect,
|
|
54
|
+
member_function=member,
|
|
55
|
+
metadata=frozendict(
|
|
56
|
+
{
|
|
57
|
+
**{
|
|
58
|
+
key: ControllerInstanceMetadata(
|
|
59
|
+
value=value.value, inherited=True
|
|
60
|
+
)
|
|
61
|
+
for key, value in controller_metadata_map.items()
|
|
62
|
+
},
|
|
63
|
+
**{
|
|
64
|
+
metadata.key: ControllerInstanceMetadata(
|
|
65
|
+
value=metadata.value, inherited=False
|
|
66
|
+
)
|
|
67
|
+
for metadata in SetMetadata.get(member)
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
for name, member in inspect.getmembers(controller, predicate=inspect.isfunction)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return controller_reflect, members
|
|
@@ -1,19 +1,39 @@
|
|
|
1
1
|
from contextlib import contextmanager, suppress
|
|
2
2
|
from contextvars import ContextVar
|
|
3
|
-
from
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Awaitable, Callable, Mapping, TypeVar, Union, cast
|
|
4
5
|
|
|
5
6
|
DECORATED = TypeVar("DECORATED", bound=Union[Callable[..., Awaitable[Any]], type])
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
@dataclass
|
|
10
|
+
class ControllerInstanceMetadata:
|
|
11
|
+
value: Any
|
|
12
|
+
inherited: bool
|
|
9
13
|
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
metadata_context: ContextVar[Mapping[str, ControllerInstanceMetadata]] = ContextVar(
|
|
16
|
+
"metadata_context"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_metadata(key: str) -> ControllerInstanceMetadata | None:
|
|
12
21
|
return metadata_context.get({}).get(key)
|
|
13
22
|
|
|
14
23
|
|
|
24
|
+
def get_metadata_value(key: str, default: Any | None = None) -> Any:
|
|
25
|
+
metadata = get_metadata(key)
|
|
26
|
+
if metadata is None:
|
|
27
|
+
return default
|
|
28
|
+
return metadata.value
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_all_metadata() -> Mapping[str, ControllerInstanceMetadata]:
|
|
32
|
+
return metadata_context.get({})
|
|
33
|
+
|
|
34
|
+
|
|
15
35
|
@contextmanager
|
|
16
|
-
def provide_metadata(metadata:
|
|
36
|
+
def provide_metadata(metadata: Mapping[str, ControllerInstanceMetadata]) -> Any:
|
|
17
37
|
|
|
18
38
|
current_metadata = metadata_context.get({})
|
|
19
39
|
|
|
@@ -39,7 +59,7 @@ class SetMetadata:
|
|
|
39
59
|
setattr(cls, SetMetadata.METATADA_LIST, metadata_list)
|
|
40
60
|
|
|
41
61
|
@staticmethod
|
|
42
|
-
def get(cls:
|
|
62
|
+
def get(cls: DECORATED) -> "list[SetMetadata]":
|
|
43
63
|
return cast(list[SetMetadata], getattr(cls, SetMetadata.METATADA_LIST, []))
|
|
44
64
|
|
|
45
65
|
def __call__(self, cls: DECORATED) -> DECORATED:
|