jararaca 0.3.12a4__tar.gz → 0.3.12a6__tar.gz
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-0.3.12a4 → jararaca-0.3.12a6}/PKG-INFO +1 -1
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/pyproject.toml +1 -1
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/__init__.py +15 -4
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/cli.py +2 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/messagebus/worker.py +102 -84
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/microservice.py +42 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/presentation/server.py +57 -11
- jararaca-0.3.12a6/src/jararaca/tools/typescript/decorators.py +51 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/tools/typescript/interface_parser.py +55 -7
- jararaca-0.3.12a6/src/jararaca/utils/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/LICENSE +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/README.md +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/CNAME +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/architecture.md +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.webp +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/assets/tracing_example.png +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/http-rpc.md +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/index.md +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/interceptors.md +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/messagebus.md +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/retry.md +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/scheduler.md +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/stylesheets/custom.css +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/websocket.md +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/__main__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/broker_backend/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/broker_backend/mapper.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/broker_backend/redis_broker_backend.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/common/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/core/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/core/providers.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/core/uow.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/di.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/files/entity.py.mako +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/lifecycle.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/messagebus/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/messagebus/bus_message_controller.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/messagebus/consumers/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/messagebus/decorators.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/messagebus/interceptors/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/messagebus/interceptors/publisher_interceptor.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/messagebus/message.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/messagebus/publisher.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/observability/decorators.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/observability/interceptor.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/observability/providers/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/observability/providers/otel.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/persistence/base.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/persistence/exports.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/persistence/interceptors/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/persistence/interceptors/aiosqa_interceptor.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/persistence/session.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/persistence/sort_filter.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/persistence/utilities.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/presentation/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/presentation/decorators.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/presentation/hooks.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/presentation/http_microservice.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/presentation/websocket/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/presentation/websocket/base_types.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/presentation/websocket/context.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/presentation/websocket/decorators.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/presentation/websocket/redis.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/presentation/websocket/types.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/presentation/websocket/websocket_interceptor.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/py.typed +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/reflect/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/reflect/controller_inspect.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/reflect/metadata.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/rpc/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/rpc/http/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/rpc/http/backends/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/rpc/http/backends/httpx.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/rpc/http/backends/otel.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/rpc/http/decorators.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/rpc/http/httpx.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/scheduler/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/scheduler/beat_worker.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/scheduler/decorators.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/scheduler/types.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/tools/app_config/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/tools/app_config/decorators.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/tools/app_config/interceptor.py +0 -0
- {jararaca-0.3.12a4/src/jararaca/utils → jararaca-0.3.12a6/src/jararaca/tools/typescript}/__init__.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/utils/rabbitmq_utils.py +0 -0
- {jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/utils/retry.py +0 -0
|
@@ -102,6 +102,8 @@ if TYPE_CHECKING:
|
|
|
102
102
|
from .messagebus.publisher import use_publisher
|
|
103
103
|
from .microservice import (
|
|
104
104
|
Microservice,
|
|
105
|
+
is_shutting_down,
|
|
106
|
+
request_shutdown,
|
|
105
107
|
use_app_context,
|
|
106
108
|
use_app_transaction_context,
|
|
107
109
|
use_app_tx_ctx_data,
|
|
@@ -158,6 +160,7 @@ if TYPE_CHECKING:
|
|
|
158
160
|
from .presentation.websocket.websocket_interceptor import WebSocketInterceptor
|
|
159
161
|
from .scheduler.decorators import ScheduledAction
|
|
160
162
|
from .tools.app_config.interceptor import AppConfigurationInterceptor
|
|
163
|
+
from .tools.typescript.decorators import MutationEndpoint, QueryEndpoint
|
|
161
164
|
|
|
162
165
|
__all__ = [
|
|
163
166
|
"SetMetadata",
|
|
@@ -207,6 +210,12 @@ if TYPE_CHECKING:
|
|
|
207
210
|
"QueryInjector",
|
|
208
211
|
"HttpMicroservice",
|
|
209
212
|
"use_current_container",
|
|
213
|
+
"use_app_context",
|
|
214
|
+
"use_app_transaction_context",
|
|
215
|
+
"use_app_tx_ctx_data",
|
|
216
|
+
"is_shutting_down",
|
|
217
|
+
"request_shutdown",
|
|
218
|
+
"Microservice",
|
|
210
219
|
"T_BASEMODEL",
|
|
211
220
|
"DatedEntity",
|
|
212
221
|
"BaseEntity",
|
|
@@ -229,7 +238,6 @@ if TYPE_CHECKING:
|
|
|
229
238
|
"MessageBusController",
|
|
230
239
|
"MessageHandler",
|
|
231
240
|
"ScheduledAction",
|
|
232
|
-
"Microservice",
|
|
233
241
|
"ProviderSpec",
|
|
234
242
|
"Token",
|
|
235
243
|
"AIOSqlAlchemySessionInterceptor",
|
|
@@ -254,6 +262,8 @@ if TYPE_CHECKING:
|
|
|
254
262
|
"MessageBusPublisherInterceptor",
|
|
255
263
|
"RedisWebSocketConnectionBackend",
|
|
256
264
|
"AppConfigurationInterceptor",
|
|
265
|
+
"QueryEndpoint",
|
|
266
|
+
"MutationEndpoint",
|
|
257
267
|
"UseMiddleware",
|
|
258
268
|
"UseDependency",
|
|
259
269
|
"GlobalHttpErrorHandler",
|
|
@@ -284,9 +294,6 @@ if TYPE_CHECKING:
|
|
|
284
294
|
"RetryConfig",
|
|
285
295
|
# Exception classes
|
|
286
296
|
"TimeoutException",
|
|
287
|
-
"use_app_context",
|
|
288
|
-
"use_app_transaction_context",
|
|
289
|
-
"use_app_tx_ctx_data",
|
|
290
297
|
"AppTransactionContext",
|
|
291
298
|
"AppContext",
|
|
292
299
|
"ControllerMemberReflect",
|
|
@@ -469,6 +476,8 @@ _dynamic_imports: "dict[str, tuple[str, str, str | None]]" = {
|
|
|
469
476
|
"tools.app_config.interceptor",
|
|
470
477
|
None,
|
|
471
478
|
),
|
|
479
|
+
"QueryEndpoint": (__SPEC_PARENT__, "tools.typescript.decorators", None),
|
|
480
|
+
"MutationEndpoint": (__SPEC_PARENT__, "tools.typescript.decorators", None),
|
|
472
481
|
"UseMiddleware": (__SPEC_PARENT__, "presentation.decorators", None),
|
|
473
482
|
"UseDependency": (__SPEC_PARENT__, "presentation.decorators", None),
|
|
474
483
|
"GlobalHttpErrorHandler": (__SPEC_PARENT__, "rpc.http.decorators", None),
|
|
@@ -501,6 +510,8 @@ _dynamic_imports: "dict[str, tuple[str, str, str | None]]" = {
|
|
|
501
510
|
"use_app_context": (__SPEC_PARENT__, "microservice", None),
|
|
502
511
|
"use_app_transaction_context": (__SPEC_PARENT__, "microservice", None),
|
|
503
512
|
"use_app_tx_ctx_data": (__SPEC_PARENT__, "microservice", None),
|
|
513
|
+
"is_shutting_down": (__SPEC_PARENT__, "microservice", None),
|
|
514
|
+
"request_shutdown": (__SPEC_PARENT__, "microservice", None),
|
|
504
515
|
"AppContext": (__SPEC_PARENT__, "microservice", None),
|
|
505
516
|
"AppInterceptor": (__SPEC_PARENT__, "microservice", None),
|
|
506
517
|
"AppTransactionContext": (__SPEC_PARENT__, "microservice", None),
|
|
@@ -5,6 +5,7 @@ import multiprocessing
|
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
7
|
import time
|
|
8
|
+
import traceback
|
|
8
9
|
from codecs import StreamWriter
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any, Callable
|
|
@@ -615,6 +616,7 @@ def generate_interfaces(
|
|
|
615
616
|
return content
|
|
616
617
|
except Exception as e:
|
|
617
618
|
click.echo(f"Error generating TypeScript interfaces: {e}", file=sys.stderr)
|
|
619
|
+
traceback.print_exc(file=sys.stderr)
|
|
618
620
|
return ""
|
|
619
621
|
|
|
620
622
|
|
|
@@ -49,6 +49,8 @@ from jararaca.microservice import (
|
|
|
49
49
|
MessageBusTransactionData,
|
|
50
50
|
Microservice,
|
|
51
51
|
SchedulerTransactionData,
|
|
52
|
+
ShutdownState,
|
|
53
|
+
provide_shutdown_state,
|
|
52
54
|
)
|
|
53
55
|
from jararaca.scheduler.decorators import ScheduledActionData
|
|
54
56
|
from jararaca.utils.rabbitmq_utils import RabbitmqUtils
|
|
@@ -129,6 +131,17 @@ class MessageBusConsumer(ABC):
|
|
|
129
131
|
"""Close all resources related to the consumer"""
|
|
130
132
|
|
|
131
133
|
|
|
134
|
+
class _WorkerShutdownState(ShutdownState):
|
|
135
|
+
def __init__(self, shutdown_event: asyncio.Event):
|
|
136
|
+
self.shutdown_event = shutdown_event
|
|
137
|
+
|
|
138
|
+
def request_shutdown(self) -> None:
|
|
139
|
+
self.shutdown_event.set()
|
|
140
|
+
|
|
141
|
+
def is_shutdown_requested(self) -> bool:
|
|
142
|
+
return self.shutdown_event.is_set()
|
|
143
|
+
|
|
144
|
+
|
|
132
145
|
class AioPikaMicroserviceConsumer(MessageBusConsumer):
|
|
133
146
|
def __init__(
|
|
134
147
|
self,
|
|
@@ -146,6 +159,7 @@ class AioPikaMicroserviceConsumer(MessageBusConsumer):
|
|
|
146
159
|
self.incoming_map: dict[str, MessageHandlerData] = {}
|
|
147
160
|
self.uow_context_provider = uow_context_provider
|
|
148
161
|
self.shutdown_event = asyncio.Event()
|
|
162
|
+
self.shutdown_state = _WorkerShutdownState(self.shutdown_event)
|
|
149
163
|
self.lock = asyncio.Lock()
|
|
150
164
|
self.tasks: set[asyncio.Task[Any]] = set()
|
|
151
165
|
self.connection: aio_pika.abc.AbstractConnection | None = None
|
|
@@ -832,18 +846,19 @@ class ScheduledMessageHandlerCallback:
|
|
|
832
846
|
args: tuple[Any, ...],
|
|
833
847
|
kwargs: dict[str, Any],
|
|
834
848
|
) -> None:
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
849
|
+
with provide_shutdown_state(self.consumer.shutdown_state):
|
|
850
|
+
async with self.consumer.uow_context_provider(
|
|
851
|
+
AppTransactionContext(
|
|
852
|
+
controller_member_reflect=scheduled_action.controller_member,
|
|
853
|
+
transaction_data=SchedulerTransactionData(
|
|
854
|
+
scheduled_to=datetime.now(UTC),
|
|
855
|
+
cron_expression=scheduled_action.spec.cron,
|
|
856
|
+
triggered_at=datetime.now(UTC),
|
|
857
|
+
),
|
|
858
|
+
)
|
|
859
|
+
):
|
|
845
860
|
|
|
846
|
-
|
|
861
|
+
await scheduled_action.callable(*args, **kwargs)
|
|
847
862
|
|
|
848
863
|
|
|
849
864
|
class MessageHandlerCallback:
|
|
@@ -1133,83 +1148,86 @@ class MessageHandlerCallback:
|
|
|
1133
1148
|
incoming_message_spec = MessageHandler.get_message_incoming(handler)
|
|
1134
1149
|
assert incoming_message_spec is not None
|
|
1135
1150
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1151
|
+
with provide_shutdown_state(self.consumer.shutdown_state):
|
|
1152
|
+
async with self.consumer.uow_context_provider(
|
|
1153
|
+
AppTransactionContext(
|
|
1154
|
+
controller_member_reflect=handler_data.controller_member,
|
|
1155
|
+
transaction_data=MessageBusTransactionData(
|
|
1156
|
+
message=builded_message,
|
|
1157
|
+
topic=routing_key,
|
|
1158
|
+
),
|
|
1159
|
+
)
|
|
1160
|
+
):
|
|
1161
|
+
ctx: AsyncContextManager[Any]
|
|
1162
|
+
if incoming_message_spec.timeout is not None:
|
|
1163
|
+
ctx = asyncio.timeout(incoming_message_spec.timeout)
|
|
1164
|
+
else:
|
|
1165
|
+
ctx = none_context()
|
|
1166
|
+
async with ctx:
|
|
1167
|
+
try:
|
|
1168
|
+
with provide_bus_message_controller(
|
|
1169
|
+
AioPikaMessageBusController(aio_pika_message)
|
|
1170
|
+
):
|
|
1171
|
+
await handler(builded_message)
|
|
1172
|
+
if not incoming_message_spec.auto_ack:
|
|
1173
|
+
with suppress(aio_pika.MessageProcessError):
|
|
1174
|
+
# Use channel context for acknowledgement
|
|
1175
|
+
async with self.consumer.get_channel_ctx(
|
|
1176
|
+
self.queue_name
|
|
1177
|
+
):
|
|
1178
|
+
await aio_pika_message.ack()
|
|
1179
|
+
except BaseException as base_exc:
|
|
1180
|
+
# Get message id for logging
|
|
1181
|
+
message_id = aio_pika_message.message_id or str(uuid.uuid4())
|
|
1182
|
+
|
|
1183
|
+
# Extract retry count from headers if available
|
|
1184
|
+
headers = aio_pika_message.headers or {}
|
|
1185
|
+
retry_count = int(str(headers.get("x-retry-count", 0)))
|
|
1186
|
+
|
|
1187
|
+
# Process exception handler if configured
|
|
1188
|
+
if incoming_message_spec.exception_handler is not None:
|
|
1189
|
+
try:
|
|
1190
|
+
incoming_message_spec.exception_handler(base_exc)
|
|
1191
|
+
except Exception as nested_exc:
|
|
1192
|
+
logger.exception(
|
|
1193
|
+
f"Error processing exception handler for message {message_id}: {base_exc} | {nested_exc}"
|
|
1194
|
+
)
|
|
1195
|
+
else:
|
|
1174
1196
|
logger.exception(
|
|
1175
|
-
f"Error processing
|
|
1197
|
+
f"Error processing message {message_id} on topic {routing_key}: {str(base_exc)}"
|
|
1176
1198
|
)
|
|
1177
|
-
else:
|
|
1178
|
-
logger.exception(
|
|
1179
|
-
f"Error processing message {message_id} on topic {routing_key}: {str(base_exc)}"
|
|
1180
|
-
)
|
|
1181
1199
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
else:
|
|
1197
|
-
# Message processed successfully, log and clean up any retry state
|
|
1198
|
-
message_id = aio_pika_message.message_id or str(uuid.uuid4())
|
|
1199
|
-
if message_id in self.retry_state:
|
|
1200
|
-
del self.retry_state[message_id]
|
|
1201
|
-
|
|
1202
|
-
# Log success with retry information if applicable
|
|
1203
|
-
headers = aio_pika_message.headers or {}
|
|
1204
|
-
if "x-retry-count" in headers:
|
|
1205
|
-
retry_count = int(str(headers.get("x-retry-count", 0)))
|
|
1206
|
-
logger.info(
|
|
1207
|
-
f"Message {message_id}#{self.queue_name} processed successfully after {retry_count} retries"
|
|
1208
|
-
)
|
|
1200
|
+
# Handle rejection with retry logic
|
|
1201
|
+
if incoming_message_spec.requeue_on_exception:
|
|
1202
|
+
# Use our retry with backoff mechanism
|
|
1203
|
+
await self.handle_reject_message(
|
|
1204
|
+
aio_pika_message,
|
|
1205
|
+
requeue=False, # Don't requeue directly, use our backoff mechanism
|
|
1206
|
+
retry_count=retry_count,
|
|
1207
|
+
exception=base_exc,
|
|
1208
|
+
)
|
|
1209
|
+
else:
|
|
1210
|
+
# Message shouldn't be retried, reject it
|
|
1211
|
+
await self.handle_reject_message(
|
|
1212
|
+
aio_pika_message, requeue=False, exception=base_exc
|
|
1213
|
+
)
|
|
1209
1214
|
else:
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1215
|
+
# Message processed successfully, log and clean up any retry state
|
|
1216
|
+
message_id = aio_pika_message.message_id or str(uuid.uuid4())
|
|
1217
|
+
if message_id in self.retry_state:
|
|
1218
|
+
del self.retry_state[message_id]
|
|
1219
|
+
|
|
1220
|
+
# Log success with retry information if applicable
|
|
1221
|
+
headers = aio_pika_message.headers or {}
|
|
1222
|
+
if "x-retry-count" in headers:
|
|
1223
|
+
retry_count = int(str(headers.get("x-retry-count", 0)))
|
|
1224
|
+
logger.info(
|
|
1225
|
+
f"Message {message_id}#{self.queue_name} processed successfully after {retry_count} retries"
|
|
1226
|
+
)
|
|
1227
|
+
else:
|
|
1228
|
+
logger.info(
|
|
1229
|
+
f"Message {message_id}#{self.queue_name} processed successfully"
|
|
1230
|
+
)
|
|
1213
1231
|
|
|
1214
1232
|
|
|
1215
1233
|
@asynccontextmanager
|
|
@@ -325,6 +325,48 @@ def provide_container(container: Container) -> Generator[None, None, None]:
|
|
|
325
325
|
current_container_ctx.reset(token)
|
|
326
326
|
|
|
327
327
|
|
|
328
|
+
class ShutdownState(Protocol):
|
|
329
|
+
|
|
330
|
+
def request_shutdown(self) -> None: ...
|
|
331
|
+
|
|
332
|
+
def is_shutdown_requested(self) -> bool: ...
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
shutdown_state_ctx = ContextVar[ShutdownState]("shutdown_state")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def is_shutting_down() -> bool:
|
|
339
|
+
"""
|
|
340
|
+
Check if the application is in the process of shutting down.
|
|
341
|
+
"""
|
|
342
|
+
return shutdown_state_ctx.get().is_shutdown_requested()
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def request_shutdown() -> None:
|
|
346
|
+
"""
|
|
347
|
+
Request the application to shut down.
|
|
348
|
+
This will set the shutdown event, allowing the application to gracefully shut down.
|
|
349
|
+
"""
|
|
350
|
+
shutdown_state_ctx.get().request_shutdown()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@contextmanager
|
|
354
|
+
def provide_shutdown_state(
|
|
355
|
+
state: ShutdownState,
|
|
356
|
+
) -> Generator[None, None, None]:
|
|
357
|
+
"""
|
|
358
|
+
Context manager to provide the shutdown state.
|
|
359
|
+
This is used to manage the shutdown event for the application.
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
token = shutdown_state_ctx.set(state)
|
|
363
|
+
try:
|
|
364
|
+
yield
|
|
365
|
+
finally:
|
|
366
|
+
with suppress(ValueError):
|
|
367
|
+
shutdown_state_ctx.reset(token)
|
|
368
|
+
|
|
369
|
+
|
|
328
370
|
__all__ = [
|
|
329
371
|
"AppTransactionContext",
|
|
330
372
|
"AppInterceptor",
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import signal
|
|
3
|
+
import threading
|
|
1
4
|
from contextlib import asynccontextmanager
|
|
5
|
+
from signal import SIGINT, SIGTERM
|
|
2
6
|
from typing import Any, AsyncGenerator
|
|
3
7
|
|
|
4
8
|
from fastapi import Depends, FastAPI, Request, WebSocket
|
|
@@ -10,7 +14,9 @@ from jararaca.lifecycle import AppLifecycle
|
|
|
10
14
|
from jararaca.microservice import (
|
|
11
15
|
AppTransactionContext,
|
|
12
16
|
HttpTransactionData,
|
|
17
|
+
ShutdownState,
|
|
13
18
|
WebSocketTransactionData,
|
|
19
|
+
provide_shutdown_state,
|
|
14
20
|
)
|
|
15
21
|
from jararaca.presentation.decorators import RestController
|
|
16
22
|
from jararaca.presentation.http_microservice import HttpMicroservice
|
|
@@ -76,10 +82,49 @@ class HttpAppLifecycle:
|
|
|
76
82
|
yield
|
|
77
83
|
|
|
78
84
|
|
|
85
|
+
class HttpShutdownState(ShutdownState):
|
|
86
|
+
def __init__(self) -> None:
|
|
87
|
+
self._requested = False
|
|
88
|
+
self.old_signal_handlers = {
|
|
89
|
+
SIGINT: signal.getsignal(SIGINT),
|
|
90
|
+
SIGTERM: signal.getsignal(SIGTERM),
|
|
91
|
+
}
|
|
92
|
+
self.thread_lock = threading.Lock()
|
|
93
|
+
|
|
94
|
+
def request_shutdown(self) -> None:
|
|
95
|
+
if not self._requested:
|
|
96
|
+
self._requested = True
|
|
97
|
+
os.kill(os.getpid(), SIGINT)
|
|
98
|
+
|
|
99
|
+
def is_shutdown_requested(self) -> bool:
|
|
100
|
+
return self._requested
|
|
101
|
+
|
|
102
|
+
def handle_signal(self, signum: int, frame: Any) -> None:
|
|
103
|
+
print(f"Received signal {signum}, initiating shutdown...")
|
|
104
|
+
if self._requested:
|
|
105
|
+
print("Shutdown already requested, ignoring signal.")
|
|
106
|
+
return
|
|
107
|
+
print("Requesting shutdown...")
|
|
108
|
+
self._requested = True
|
|
109
|
+
|
|
110
|
+
# remove the signal handler to prevent recursion
|
|
111
|
+
for sig in (SIGINT, SIGTERM):
|
|
112
|
+
if self.old_signal_handlers[sig] is not None:
|
|
113
|
+
signal.signal(sig, self.old_signal_handlers[sig])
|
|
114
|
+
|
|
115
|
+
signal.raise_signal(signum)
|
|
116
|
+
|
|
117
|
+
def setup_signal_handlers(self) -> None:
|
|
118
|
+
signal.signal(SIGINT, self.handle_signal)
|
|
119
|
+
signal.signal(SIGTERM, self.handle_signal)
|
|
120
|
+
|
|
121
|
+
|
|
79
122
|
class HttpUowContextProviderDependency:
|
|
80
123
|
|
|
81
124
|
def __init__(self, uow_provider: UnitOfWorkContextProvider) -> None:
|
|
82
125
|
self.uow_provider = uow_provider
|
|
126
|
+
self.shutdown_state = HttpShutdownState()
|
|
127
|
+
self.shutdown_state.setup_signal_handlers()
|
|
83
128
|
|
|
84
129
|
async def __call__(
|
|
85
130
|
self, websocket: WebSocket = None, request: Request = None # type: ignore
|
|
@@ -101,17 +146,18 @@ class HttpUowContextProviderDependency:
|
|
|
101
146
|
"ControllerMemberReflect, but got: {}".format(type(member))
|
|
102
147
|
)
|
|
103
148
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
149
|
+
with provide_shutdown_state(self.shutdown_state):
|
|
150
|
+
async with self.uow_provider(
|
|
151
|
+
AppTransactionContext(
|
|
152
|
+
controller_member_reflect=member,
|
|
153
|
+
transaction_data=(
|
|
154
|
+
HttpTransactionData(request=request)
|
|
155
|
+
if request
|
|
156
|
+
else WebSocketTransactionData(websocket=websocket)
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
):
|
|
160
|
+
yield
|
|
115
161
|
|
|
116
162
|
|
|
117
163
|
def create_http_server(
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from typing import Any, Callable, TypeVar
|
|
2
|
+
|
|
3
|
+
DECORATED_FUNC = TypeVar("DECORATED_FUNC", bound=Callable[..., Any])
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class QueryEndpoint:
|
|
7
|
+
"""
|
|
8
|
+
Decorator to mark a endpoint function as a query endpoint for Typescript generation.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
METADATA_KEY = "__jararaca_query_endpoint__"
|
|
12
|
+
|
|
13
|
+
def __init__(self) -> None: ...
|
|
14
|
+
|
|
15
|
+
def __call__(self, func: DECORATED_FUNC) -> DECORATED_FUNC:
|
|
16
|
+
"""
|
|
17
|
+
Decorate the function to mark it as a query endpoint.
|
|
18
|
+
"""
|
|
19
|
+
setattr(func, self.METADATA_KEY, True)
|
|
20
|
+
return func
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def is_query(func: Any) -> bool:
|
|
24
|
+
"""
|
|
25
|
+
Check if the function is marked as a query endpoint.
|
|
26
|
+
"""
|
|
27
|
+
return getattr(func, QueryEndpoint.METADATA_KEY, False)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MutationEndpoint:
|
|
31
|
+
"""
|
|
32
|
+
Decorator to mark a endpoint function as a mutation endpoint for Typescript generation.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
METADATA_KEY = "__jararaca_mutation_endpoint__"
|
|
36
|
+
|
|
37
|
+
def __init__(self) -> None: ...
|
|
38
|
+
|
|
39
|
+
def __call__(self, func: DECORATED_FUNC) -> DECORATED_FUNC:
|
|
40
|
+
"""
|
|
41
|
+
Decorate the function to mark it as a mutation endpoint.
|
|
42
|
+
"""
|
|
43
|
+
setattr(func, self.METADATA_KEY, True)
|
|
44
|
+
return func
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def is_mutation(func: Any) -> bool:
|
|
48
|
+
"""
|
|
49
|
+
Check if the function is marked as a mutation endpoint.
|
|
50
|
+
"""
|
|
51
|
+
return getattr(func, MutationEndpoint.METADATA_KEY, False)
|
|
@@ -8,7 +8,7 @@ from datetime import date, datetime, time
|
|
|
8
8
|
from decimal import Decimal
|
|
9
9
|
from enum import Enum
|
|
10
10
|
from io import StringIO
|
|
11
|
-
from types import NoneType, UnionType
|
|
11
|
+
from types import FunctionType, NoneType, UnionType
|
|
12
12
|
from typing import (
|
|
13
13
|
IO,
|
|
14
14
|
Annotated,
|
|
@@ -35,6 +35,7 @@ from jararaca.presentation.websocket.decorators import RegisterWebSocketMessage
|
|
|
35
35
|
from jararaca.presentation.websocket.websocket_interceptor import (
|
|
36
36
|
WebSocketMessageWrapper,
|
|
37
37
|
)
|
|
38
|
+
from jararaca.tools.typescript.decorators import MutationEndpoint, QueryEndpoint
|
|
38
39
|
|
|
39
40
|
CONSTANT_PATTERN = re.compile(r"^[A-Z_]+$")
|
|
40
41
|
|
|
@@ -67,6 +68,13 @@ def snake_to_camel(snake_str: str) -> str:
|
|
|
67
68
|
return components[0] + "".join(x.title() for x in components[1:])
|
|
68
69
|
|
|
69
70
|
|
|
71
|
+
def pascal_to_camel(pascal_str: str) -> str:
|
|
72
|
+
"""Convert a PascalCase string to camelCase."""
|
|
73
|
+
if not pascal_str:
|
|
74
|
+
return pascal_str
|
|
75
|
+
return pascal_str[0].lower() + pascal_str[1:]
|
|
76
|
+
|
|
77
|
+
|
|
70
78
|
def parse_literal_value(value: Any) -> str:
|
|
71
79
|
if value is None:
|
|
72
80
|
return "null"
|
|
@@ -354,12 +362,17 @@ def write_microservice_to_typescript_interface(
|
|
|
354
362
|
if rest_controller is None:
|
|
355
363
|
continue
|
|
356
364
|
|
|
357
|
-
|
|
358
|
-
|
|
365
|
+
controller_class_strio, types, hooks_strio = (
|
|
366
|
+
write_rest_controller_to_typescript_interface(
|
|
367
|
+
rest_controller,
|
|
368
|
+
controller,
|
|
369
|
+
)
|
|
359
370
|
)
|
|
360
371
|
|
|
361
372
|
mapped_types_set.update(types)
|
|
362
|
-
rest_controller_buffer.write(
|
|
373
|
+
rest_controller_buffer.write(controller_class_strio.getvalue())
|
|
374
|
+
if hooks_strio is not None:
|
|
375
|
+
rest_controller_buffer.write(hooks_strio.getvalue())
|
|
363
376
|
|
|
364
377
|
registered = RegisterWebSocketMessage.get(controller)
|
|
365
378
|
|
|
@@ -376,6 +389,7 @@ def write_microservice_to_typescript_interface(
|
|
|
376
389
|
|
|
377
390
|
final_buffer.write(
|
|
378
391
|
"""
|
|
392
|
+
import { createClassQueryHooks , createClassMutationHooks, createClassInfiniteQueryHooks } from "@jararaca/core";
|
|
379
393
|
export type WebSocketMessageMap = {
|
|
380
394
|
%s
|
|
381
395
|
}
|
|
@@ -494,11 +508,16 @@ def is_primitive(field_type: Any) -> bool:
|
|
|
494
508
|
|
|
495
509
|
def write_rest_controller_to_typescript_interface(
|
|
496
510
|
rest_controller: RestController, controller: type
|
|
497
|
-
) -> tuple[
|
|
511
|
+
) -> tuple[StringIO, set[Any], StringIO | None]:
|
|
512
|
+
|
|
513
|
+
class_name = controller.__name__
|
|
514
|
+
|
|
515
|
+
decorated_queries: list[tuple[str, FunctionType]] = []
|
|
516
|
+
decorated_mutations: list[tuple[str, FunctionType]] = []
|
|
498
517
|
|
|
499
518
|
class_buffer = StringIO()
|
|
500
519
|
|
|
501
|
-
class_buffer.write(f"export class {
|
|
520
|
+
class_buffer.write(f"export class {class_name} extends HttpService {{\n")
|
|
502
521
|
|
|
503
522
|
mapped_types: set[Any] = set()
|
|
504
523
|
|
|
@@ -514,6 +533,11 @@ def write_rest_controller_to_typescript_interface(
|
|
|
514
533
|
if return_type is None:
|
|
515
534
|
return_type = NoneType
|
|
516
535
|
|
|
536
|
+
if QueryEndpoint.is_query(member):
|
|
537
|
+
decorated_queries.append((name, member))
|
|
538
|
+
if MutationEndpoint.is_mutation(member):
|
|
539
|
+
decorated_mutations.append((name, member))
|
|
540
|
+
|
|
517
541
|
mapped_types.update(extract_all_envolved_types(return_type))
|
|
518
542
|
|
|
519
543
|
return_value_repr = get_field_type_for_ts(return_type)
|
|
@@ -588,7 +612,31 @@ def write_rest_controller_to_typescript_interface(
|
|
|
588
612
|
|
|
589
613
|
class_buffer.write("}\n")
|
|
590
614
|
|
|
591
|
-
|
|
615
|
+
controller_hooks_builder: StringIO | None = None
|
|
616
|
+
|
|
617
|
+
if decorated_queries or decorated_mutations:
|
|
618
|
+
controller_hooks_builder = StringIO()
|
|
619
|
+
controller_hooks_builder.write(
|
|
620
|
+
f"export const {pascal_to_camel(class_name)} = {{\n"
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
if decorated_queries:
|
|
624
|
+
controller_hooks_builder.write(
|
|
625
|
+
f"\t...createClassQueryHooks({class_name},\n"
|
|
626
|
+
)
|
|
627
|
+
for name, member in decorated_queries:
|
|
628
|
+
controller_hooks_builder.write(f'\t\t"{snake_to_camel(name)}",\n')
|
|
629
|
+
controller_hooks_builder.write("\t),\n")
|
|
630
|
+
if decorated_mutations:
|
|
631
|
+
controller_hooks_builder.write(
|
|
632
|
+
f"\t...createClassMutationHooks({class_name},\n"
|
|
633
|
+
)
|
|
634
|
+
for name, member in decorated_mutations:
|
|
635
|
+
controller_hooks_builder.write(f'\t\t"{snake_to_camel(name)}",\n')
|
|
636
|
+
controller_hooks_builder.write("\t),\n")
|
|
637
|
+
controller_hooks_builder.write("};\n")
|
|
638
|
+
|
|
639
|
+
return class_buffer, mapped_types, controller_hooks_builder
|
|
592
640
|
|
|
593
641
|
|
|
594
642
|
EXCLUDED_REQUESTS_TYPES = [Request, Response]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg
RENAMED
|
File without changes
|
{jararaca-0.3.12a4 → jararaca-0.3.12a6}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/persistence/interceptors/aiosqa_interceptor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jararaca-0.3.12a4 → jararaca-0.3.12a6}/src/jararaca/presentation/websocket/websocket_interceptor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jararaca-0.3.12a4/src/jararaca/utils → jararaca-0.3.12a6/src/jararaca/tools/typescript}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|