jararaca 0.3.11a16__py3-none-any.whl → 0.4.0a5__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.
- README.md +121 -0
- jararaca/__init__.py +184 -12
- jararaca/__main__.py +4 -0
- jararaca/broker_backend/__init__.py +4 -0
- jararaca/broker_backend/mapper.py +4 -0
- jararaca/broker_backend/redis_broker_backend.py +9 -3
- jararaca/cli.py +272 -47
- jararaca/common/__init__.py +3 -0
- jararaca/core/__init__.py +3 -0
- jararaca/core/providers.py +4 -0
- jararaca/core/uow.py +41 -7
- jararaca/di.py +4 -0
- jararaca/files/entity.py.mako +4 -0
- jararaca/lifecycle.py +6 -2
- jararaca/messagebus/__init__.py +4 -0
- jararaca/messagebus/bus_message_controller.py +4 -0
- jararaca/messagebus/consumers/__init__.py +3 -0
- jararaca/messagebus/decorators.py +33 -67
- jararaca/messagebus/implicit_headers.py +49 -0
- jararaca/messagebus/interceptors/__init__.py +3 -0
- jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +13 -4
- jararaca/messagebus/interceptors/publisher_interceptor.py +4 -0
- jararaca/messagebus/message.py +4 -0
- jararaca/messagebus/publisher.py +6 -0
- jararaca/messagebus/worker.py +850 -383
- jararaca/microservice.py +110 -1
- jararaca/observability/constants.py +7 -0
- jararaca/observability/decorators.py +170 -13
- jararaca/observability/fastapi_exception_handler.py +37 -0
- jararaca/observability/hooks.py +109 -0
- jararaca/observability/interceptor.py +4 -0
- jararaca/observability/providers/__init__.py +3 -0
- jararaca/observability/providers/otel.py +202 -11
- jararaca/persistence/base.py +38 -2
- jararaca/persistence/exports.py +4 -0
- jararaca/persistence/interceptors/__init__.py +3 -0
- jararaca/persistence/interceptors/aiosqa_interceptor.py +86 -73
- jararaca/persistence/interceptors/constants.py +5 -0
- jararaca/persistence/interceptors/decorators.py +50 -0
- jararaca/persistence/session.py +3 -0
- jararaca/persistence/sort_filter.py +4 -0
- jararaca/persistence/utilities.py +50 -20
- jararaca/presentation/__init__.py +3 -0
- jararaca/presentation/decorators.py +88 -86
- jararaca/presentation/exceptions.py +23 -0
- jararaca/presentation/hooks.py +4 -0
- jararaca/presentation/http_microservice.py +4 -0
- jararaca/presentation/server.py +97 -45
- jararaca/presentation/websocket/__init__.py +3 -0
- jararaca/presentation/websocket/base_types.py +4 -0
- jararaca/presentation/websocket/context.py +4 -0
- jararaca/presentation/websocket/decorators.py +8 -41
- jararaca/presentation/websocket/redis.py +280 -53
- jararaca/presentation/websocket/types.py +4 -0
- jararaca/presentation/websocket/websocket_interceptor.py +46 -19
- jararaca/reflect/__init__.py +3 -0
- jararaca/reflect/controller_inspect.py +16 -10
- jararaca/reflect/decorators.py +238 -0
- jararaca/reflect/metadata.py +34 -25
- jararaca/rpc/__init__.py +3 -0
- jararaca/rpc/http/__init__.py +101 -0
- jararaca/rpc/http/backends/__init__.py +14 -0
- jararaca/rpc/http/backends/httpx.py +43 -9
- jararaca/rpc/http/backends/otel.py +4 -0
- jararaca/rpc/http/decorators.py +378 -113
- jararaca/rpc/http/httpx.py +3 -0
- jararaca/scheduler/__init__.py +3 -0
- jararaca/scheduler/beat_worker.py +521 -105
- jararaca/scheduler/decorators.py +15 -22
- jararaca/scheduler/types.py +4 -0
- jararaca/tools/app_config/__init__.py +3 -0
- jararaca/tools/app_config/decorators.py +7 -19
- jararaca/tools/app_config/interceptor.py +6 -2
- jararaca/tools/typescript/__init__.py +3 -0
- jararaca/tools/typescript/decorators.py +120 -0
- jararaca/tools/typescript/interface_parser.py +1074 -173
- jararaca/utils/__init__.py +3 -0
- jararaca/utils/rabbitmq_utils.py +65 -39
- jararaca/utils/retry.py +10 -3
- jararaca-0.4.0a5.dist-info/LICENSE +674 -0
- jararaca-0.4.0a5.dist-info/LICENSES/GPL-3.0-or-later.txt +232 -0
- {jararaca-0.3.11a16.dist-info → jararaca-0.4.0a5.dist-info}/METADATA +11 -7
- jararaca-0.4.0a5.dist-info/RECORD +88 -0
- {jararaca-0.3.11a16.dist-info → jararaca-0.4.0a5.dist-info}/WHEEL +1 -1
- pyproject.toml +131 -0
- jararaca-0.3.11a16.dist-info/RECORD +0 -74
- /jararaca-0.3.11a16.dist-info/LICENSE → /LICENSE +0 -0
- {jararaca-0.3.11a16.dist-info → jararaca-0.4.0a5.dist-info}/entry_points.txt +0 -0
|
@@ -1,21 +1,22 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
|
|
1
6
|
import inspect
|
|
2
7
|
from dataclasses import dataclass
|
|
3
|
-
from typing import Any, Awaitable, Callable
|
|
8
|
+
from typing import Any, Awaitable, Callable
|
|
4
9
|
|
|
5
|
-
from jararaca.messagebus.message import INHERITS_MESSAGE_CO
|
|
10
|
+
from jararaca.messagebus.message import INHERITS_MESSAGE_CO
|
|
6
11
|
from jararaca.reflect.controller_inspect import (
|
|
7
12
|
ControllerMemberReflect,
|
|
8
13
|
inspect_controller,
|
|
9
14
|
)
|
|
15
|
+
from jararaca.reflect.decorators import DECORATED_T, StackableDecorator
|
|
10
16
|
from jararaca.scheduler.decorators import ScheduledAction, ScheduledActionData
|
|
11
17
|
|
|
12
|
-
DECORATED_FUNC = TypeVar("DECORATED_FUNC", bound=Callable[..., Any])
|
|
13
|
-
DECORATED_T = TypeVar("DECORATED_T", bound=Any)
|
|
14
|
-
|
|
15
18
|
|
|
16
|
-
class MessageHandler(
|
|
17
|
-
|
|
18
|
-
MESSAGE_INCOMING_ATTR = "__message_incoming__"
|
|
19
|
+
class MessageHandler(StackableDecorator):
|
|
19
20
|
|
|
20
21
|
def __init__(
|
|
21
22
|
self,
|
|
@@ -23,50 +24,23 @@ class MessageHandler(Generic[INHERITS_MESSAGE_CO]):
|
|
|
23
24
|
timeout: int | None = None,
|
|
24
25
|
exception_handler: Callable[[BaseException], None] | None = None,
|
|
25
26
|
nack_on_exception: bool = False,
|
|
26
|
-
auto_ack: bool =
|
|
27
|
+
auto_ack: bool = False,
|
|
27
28
|
name: str | None = None,
|
|
28
29
|
) -> None:
|
|
29
30
|
self.message_type = message
|
|
30
31
|
|
|
31
32
|
self.timeout = timeout
|
|
32
33
|
self.exception_handler = exception_handler
|
|
33
|
-
self.
|
|
34
|
+
self.nack_on_exception = nack_on_exception
|
|
34
35
|
self.auto_ack = auto_ack
|
|
35
36
|
self.name = name
|
|
36
37
|
|
|
37
|
-
def __call__(
|
|
38
|
-
self, func: Callable[[Any, MessageOf[INHERITS_MESSAGE_CO]], Awaitable[None]]
|
|
39
|
-
) -> Callable[[Any, MessageOf[INHERITS_MESSAGE_CO]], Awaitable[None]]:
|
|
40
|
-
|
|
41
|
-
MessageHandler[Any].register(func, self)
|
|
42
|
-
|
|
43
|
-
return func
|
|
44
|
-
|
|
45
|
-
@staticmethod
|
|
46
|
-
def register(
|
|
47
|
-
func: Callable[[Any, MessageOf[INHERITS_MESSAGE_CO]], Awaitable[None]],
|
|
48
|
-
message_incoming: "MessageHandler[Any]",
|
|
49
|
-
) -> None:
|
|
50
|
-
|
|
51
|
-
setattr(func, MessageHandler.MESSAGE_INCOMING_ATTR, message_incoming)
|
|
52
|
-
|
|
53
|
-
@staticmethod
|
|
54
|
-
def get_message_incoming(
|
|
55
|
-
func: Callable[[MessageOf[Any]], Awaitable[Any]],
|
|
56
|
-
) -> "MessageHandler[Message] | None":
|
|
57
|
-
if not hasattr(func, MessageHandler.MESSAGE_INCOMING_ATTR):
|
|
58
|
-
return None
|
|
59
|
-
|
|
60
|
-
return cast(
|
|
61
|
-
MessageHandler[Message], getattr(func, MessageHandler.MESSAGE_INCOMING_ATTR)
|
|
62
|
-
)
|
|
63
|
-
|
|
64
38
|
|
|
65
39
|
@dataclass(frozen=True)
|
|
66
40
|
class MessageHandlerData:
|
|
67
41
|
message_type: type[Any]
|
|
68
|
-
spec: MessageHandler
|
|
69
|
-
instance_callable: Callable[
|
|
42
|
+
spec: MessageHandler
|
|
43
|
+
instance_callable: Callable[..., Awaitable[None]]
|
|
70
44
|
controller_member: ControllerMemberReflect
|
|
71
45
|
|
|
72
46
|
|
|
@@ -80,16 +54,22 @@ SCHEDULED_ACTION_DATA_SET = set[ScheduledActionData]
|
|
|
80
54
|
MESSAGE_HANDLER_DATA_SET = set[MessageHandlerData]
|
|
81
55
|
|
|
82
56
|
|
|
83
|
-
class MessageBusController:
|
|
57
|
+
class MessageBusController(StackableDecorator):
|
|
84
58
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
*,
|
|
62
|
+
inherit_class_decorators: bool = True,
|
|
63
|
+
inherit_methods_decorators: bool = True,
|
|
64
|
+
) -> None:
|
|
88
65
|
self.messagebus_factory: (
|
|
89
66
|
Callable[[Any], tuple[MESSAGE_HANDLER_DATA_SET, SCHEDULED_ACTION_DATA_SET]]
|
|
90
67
|
| None
|
|
91
68
|
) = None
|
|
92
69
|
|
|
70
|
+
self.inherit_class_decorators = inherit_class_decorators
|
|
71
|
+
self.inherit_methods_decorators = inherit_methods_decorators
|
|
72
|
+
|
|
93
73
|
def get_messagebus_factory(
|
|
94
74
|
self,
|
|
95
75
|
) -> Callable[
|
|
@@ -99,7 +79,7 @@ class MessageBusController:
|
|
|
99
79
|
raise Exception("MessageBus factory is not set")
|
|
100
80
|
return self.messagebus_factory
|
|
101
81
|
|
|
102
|
-
def
|
|
82
|
+
def post_decorated(self, subject: DECORATED_T) -> None:
|
|
103
83
|
|
|
104
84
|
def messagebus_factory(
|
|
105
85
|
instance: DECORATED_T,
|
|
@@ -108,13 +88,17 @@ class MessageBusController:
|
|
|
108
88
|
|
|
109
89
|
schedulers: SCHEDULED_ACTION_DATA_SET = set()
|
|
110
90
|
|
|
111
|
-
|
|
91
|
+
assert inspect.isclass(
|
|
92
|
+
subject
|
|
93
|
+
), "MessageBusController can only be applied to classes"
|
|
94
|
+
|
|
95
|
+
_, members = inspect_controller(subject)
|
|
112
96
|
|
|
113
97
|
for name, member in members.items():
|
|
114
|
-
message_handler_decoration = MessageHandler.
|
|
98
|
+
message_handler_decoration = MessageHandler.get_last(
|
|
115
99
|
member.member_function
|
|
116
100
|
)
|
|
117
|
-
scheduled_action_decoration = ScheduledAction.
|
|
101
|
+
scheduled_action_decoration = ScheduledAction.get_last(
|
|
118
102
|
member.member_function
|
|
119
103
|
)
|
|
120
104
|
|
|
@@ -123,7 +107,7 @@ class MessageBusController:
|
|
|
123
107
|
if not inspect.iscoroutinefunction(member.member_function):
|
|
124
108
|
raise Exception(
|
|
125
109
|
"Message incoming handler '%s' from '%s.%s' must be a coroutine function"
|
|
126
|
-
% (name,
|
|
110
|
+
% (name, subject.__module__, subject.__qualname__)
|
|
127
111
|
)
|
|
128
112
|
|
|
129
113
|
handlers.add(
|
|
@@ -138,7 +122,7 @@ class MessageBusController:
|
|
|
138
122
|
if not inspect.iscoroutinefunction(member.member_function):
|
|
139
123
|
raise Exception(
|
|
140
124
|
"Scheduled action handler '%s' from '%s.%s' must be a coroutine function"
|
|
141
|
-
% (name,
|
|
125
|
+
% (name, subject.__module__, subject.__qualname__)
|
|
142
126
|
)
|
|
143
127
|
instance_callable = getattr(instance, name)
|
|
144
128
|
|
|
@@ -153,21 +137,3 @@ class MessageBusController:
|
|
|
153
137
|
return handlers, schedulers
|
|
154
138
|
|
|
155
139
|
self.messagebus_factory = messagebus_factory
|
|
156
|
-
|
|
157
|
-
MessageBusController.register(cls_t, self)
|
|
158
|
-
|
|
159
|
-
return cls_t
|
|
160
|
-
|
|
161
|
-
@staticmethod
|
|
162
|
-
def register(func: type[DECORATED_T], messagebus: "MessageBusController") -> None:
|
|
163
|
-
|
|
164
|
-
setattr(func, MessageBusController.MESSAGEBUS_ATTR, messagebus)
|
|
165
|
-
|
|
166
|
-
@staticmethod
|
|
167
|
-
def get_messagebus(func: type[DECORATED_T]) -> "MessageBusController | None":
|
|
168
|
-
if not hasattr(func, MessageBusController.MESSAGEBUS_ATTR):
|
|
169
|
-
return None
|
|
170
|
-
|
|
171
|
-
return cast(
|
|
172
|
-
MessageBusController, getattr(func, MessageBusController.MESSAGEBUS_ATTR)
|
|
173
|
-
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
import decimal
|
|
7
|
+
import typing
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from contextvars import ContextVar
|
|
10
|
+
from typing import Any, Dict, Generator
|
|
11
|
+
|
|
12
|
+
FieldArray = list["FieldValue"]
|
|
13
|
+
"""A data structure for holding an array of field values."""
|
|
14
|
+
|
|
15
|
+
FieldTable = typing.Dict[str, "FieldValue"]
|
|
16
|
+
FieldValue = (
|
|
17
|
+
bool
|
|
18
|
+
| bytes
|
|
19
|
+
| bytearray
|
|
20
|
+
| decimal.Decimal
|
|
21
|
+
| FieldArray
|
|
22
|
+
| FieldTable
|
|
23
|
+
| float
|
|
24
|
+
| int
|
|
25
|
+
| None
|
|
26
|
+
| str
|
|
27
|
+
| datetime.datetime
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
ImplicitHeaders = Dict[str, FieldValue]
|
|
31
|
+
|
|
32
|
+
implicit_headers_ctx = ContextVar[ImplicitHeaders | None](
|
|
33
|
+
"implicit_headers_ctx", default=None
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def use_implicit_headers() -> ImplicitHeaders | None:
|
|
38
|
+
return implicit_headers_ctx.get()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@contextmanager
|
|
42
|
+
def provide_implicit_headers(
|
|
43
|
+
implicit_headers: ImplicitHeaders,
|
|
44
|
+
) -> Generator[None, Any, None]:
|
|
45
|
+
token = implicit_headers_ctx.set(implicit_headers)
|
|
46
|
+
try:
|
|
47
|
+
yield
|
|
48
|
+
finally:
|
|
49
|
+
implicit_headers_ctx.reset(token)
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
1
5
|
import logging
|
|
2
6
|
from contextlib import asynccontextmanager
|
|
3
7
|
from datetime import datetime, timedelta
|
|
@@ -9,6 +13,7 @@ from aio_pika.abc import AbstractConnection
|
|
|
9
13
|
from pydantic import BaseModel
|
|
10
14
|
|
|
11
15
|
from jararaca.broker_backend import MessageBrokerBackend
|
|
16
|
+
from jararaca.messagebus import implicit_headers
|
|
12
17
|
from jararaca.messagebus.interceptors.publisher_interceptor import (
|
|
13
18
|
MessageBusConnectionFactory,
|
|
14
19
|
)
|
|
@@ -41,9 +46,13 @@ class AIOPikaMessagePublisher(MessagePublisher):
|
|
|
41
46
|
if not exchange:
|
|
42
47
|
logging.warning(f"Exchange {self.exchange_name} not found")
|
|
43
48
|
return
|
|
44
|
-
routing_key = f"{topic}
|
|
49
|
+
routing_key = f"{topic}.#"
|
|
50
|
+
|
|
51
|
+
implicit_headers_data = implicit_headers.use_implicit_headers()
|
|
45
52
|
await exchange.publish(
|
|
46
|
-
aio_pika.Message(
|
|
53
|
+
aio_pika.Message(
|
|
54
|
+
body=message.model_dump_json().encode(), headers=implicit_headers_data
|
|
55
|
+
),
|
|
47
56
|
routing_key=routing_key,
|
|
48
57
|
)
|
|
49
58
|
|
|
@@ -122,7 +131,7 @@ class AIOPikaConnectionFactory(MessageBusConnectionFactory):
|
|
|
122
131
|
if connection_pool_config:
|
|
123
132
|
|
|
124
133
|
async def get_connection() -> AbstractConnection:
|
|
125
|
-
return await aio_pika.
|
|
134
|
+
return await aio_pika.connect(self.url)
|
|
126
135
|
|
|
127
136
|
self.connection_pool = aio_pika.pool.Pool[AbstractConnection](
|
|
128
137
|
get_connection,
|
|
@@ -142,7 +151,7 @@ class AIOPikaConnectionFactory(MessageBusConnectionFactory):
|
|
|
142
151
|
@asynccontextmanager
|
|
143
152
|
async def acquire_connection(self) -> AsyncGenerator[AbstractConnection, Any]:
|
|
144
153
|
if not self.connection_pool:
|
|
145
|
-
async with await aio_pika.
|
|
154
|
+
async with await aio_pika.connect(self.url) as connection:
|
|
146
155
|
yield connection
|
|
147
156
|
else:
|
|
148
157
|
|
jararaca/messagebus/message.py
CHANGED
jararaca/messagebus/publisher.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
1
5
|
from abc import ABC, abstractmethod
|
|
2
6
|
from contextlib import contextmanager, suppress
|
|
3
7
|
from contextvars import ContextVar
|
|
@@ -19,6 +23,8 @@ class IMessage(BaseModel):
|
|
|
19
23
|
|
|
20
24
|
MESSAGE_TYPE: ClassVar[Literal["task", "event"]] = "task"
|
|
21
25
|
|
|
26
|
+
MESSAGE_CATEGORY: ClassVar[str] = "uncategorized"
|
|
27
|
+
|
|
22
28
|
|
|
23
29
|
class MessagePublisher(ABC):
|
|
24
30
|
@abstractmethod
|