jararaca 0.3.12a3__py3-none-any.whl → 0.3.12a5__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 CHANGED
@@ -45,26 +45,47 @@ if TYPE_CHECKING:
45
45
  )
46
46
  from jararaca.rpc.http.backends.httpx import HTTPXHttpRPCAsyncBackend
47
47
  from jararaca.rpc.http.backends.otel import TracedRequestMiddleware
48
- from jararaca.rpc.http.decorators import Body
48
+ from jararaca.rpc.http.decorators import ( # New request parameter decorators; Configuration decorators; Authentication classes; Middleware classes; Configuration classes; Exception classes
49
+ ApiKeyAuth,
50
+ AuthenticationMiddleware,
51
+ BasicAuth,
52
+ BearerTokenAuth,
53
+ Body,
54
+ CacheMiddleware,
55
+ ContentType,
56
+ )
49
57
  from jararaca.rpc.http.decorators import Delete as HttpDelete
58
+ from jararaca.rpc.http.decorators import ( # New request parameter decorators; Configuration decorators; Authentication classes; Middleware classes; Configuration classes; Exception classes
59
+ File,
60
+ FormData,
61
+ )
50
62
  from jararaca.rpc.http.decorators import Get as HttpGet
51
- from jararaca.rpc.http.decorators import (
63
+ from jararaca.rpc.http.decorators import ( # New request parameter decorators; Configuration decorators; Authentication classes; Middleware classes; Configuration classes; Exception classes
52
64
  GlobalHttpErrorHandler,
53
65
  Header,
54
66
  HttpMapping,
55
67
  HttpRpcClientBuilder,
56
68
  )
57
69
  from jararaca.rpc.http.decorators import Patch as HttpPatch
58
- from jararaca.rpc.http.decorators import PathParam
70
+ from jararaca.rpc.http.decorators import ( # New request parameter decorators; Configuration decorators; Authentication classes; Middleware classes; Configuration classes; Exception classes
71
+ PathParam,
72
+ )
59
73
  from jararaca.rpc.http.decorators import Post as HttpPost
60
74
  from jararaca.rpc.http.decorators import Put as HttpPut
61
- from jararaca.rpc.http.decorators import (
75
+ from jararaca.rpc.http.decorators import ( # New request parameter decorators; Configuration decorators; Authentication classes; Middleware classes; Configuration classes; Exception classes
62
76
  Query,
63
77
  RequestAttribute,
78
+ RequestHook,
79
+ ResponseHook,
80
+ ResponseMiddleware,
64
81
  RestClient,
82
+ Retry,
83
+ RetryConfig,
65
84
  RouteHttpErrorHandler,
66
85
  RPCRequestNetworkError,
67
86
  RPCUnhandleError,
87
+ Timeout,
88
+ TimeoutException,
68
89
  )
69
90
 
70
91
  from .core.providers import ProviderSpec, Token
@@ -81,6 +102,8 @@ if TYPE_CHECKING:
81
102
  from .messagebus.publisher import use_publisher
82
103
  from .microservice import (
83
104
  Microservice,
105
+ is_shutting_down,
106
+ request_shutdown,
84
107
  use_app_context,
85
108
  use_app_transaction_context,
86
109
  use_app_tx_ctx_data,
@@ -186,6 +209,12 @@ if TYPE_CHECKING:
186
209
  "QueryInjector",
187
210
  "HttpMicroservice",
188
211
  "use_current_container",
212
+ "use_app_context",
213
+ "use_app_transaction_context",
214
+ "use_app_tx_ctx_data",
215
+ "is_shutting_down",
216
+ "request_shutdown",
217
+ "Microservice",
189
218
  "T_BASEMODEL",
190
219
  "DatedEntity",
191
220
  "BaseEntity",
@@ -208,7 +237,6 @@ if TYPE_CHECKING:
208
237
  "MessageBusController",
209
238
  "MessageHandler",
210
239
  "ScheduledAction",
211
- "Microservice",
212
240
  "ProviderSpec",
213
241
  "Token",
214
242
  "AIOSqlAlchemySessionInterceptor",
@@ -242,9 +270,27 @@ if TYPE_CHECKING:
242
270
  "provide_ws_manager",
243
271
  "HttpRpcClientBuilder",
244
272
  "HTTPXHttpRPCAsyncBackend",
245
- "use_app_context",
246
- "use_app_transaction_context",
247
- "use_app_tx_ctx_data",
273
+ # New request parameter decorators
274
+ "FormData",
275
+ "File",
276
+ # Configuration decorators
277
+ "Timeout",
278
+ "Retry",
279
+ "ContentType",
280
+ # Authentication classes
281
+ "BearerTokenAuth",
282
+ "BasicAuth",
283
+ "ApiKeyAuth",
284
+ # Middleware classes
285
+ "CacheMiddleware",
286
+ "AuthenticationMiddleware",
287
+ "ResponseMiddleware",
288
+ "RequestHook",
289
+ "ResponseHook",
290
+ # Configuration classes
291
+ "RetryConfig",
292
+ # Exception classes
293
+ "TimeoutException",
248
294
  "AppTransactionContext",
249
295
  "AppContext",
250
296
  "ControllerMemberReflect",
@@ -440,9 +486,27 @@ _dynamic_imports: "dict[str, tuple[str, str, str | None]]" = {
440
486
  "provide_ws_manager": (__SPEC_PARENT__, "presentation.websocket.context", None),
441
487
  "HttpRpcClientBuilder": (__SPEC_PARENT__, "rpc.http.decorators", None),
442
488
  "HTTPXHttpRPCAsyncBackend": (__SPEC_PARENT__, "rpc.http.backends.httpx", None),
489
+ # New HTTP RPC classes
490
+ "FormData": (__SPEC_PARENT__, "rpc.http.decorators", None),
491
+ "File": (__SPEC_PARENT__, "rpc.http.decorators", None),
492
+ "Timeout": (__SPEC_PARENT__, "rpc.http.decorators", None),
493
+ "Retry": (__SPEC_PARENT__, "rpc.http.decorators", None),
494
+ "ContentType": (__SPEC_PARENT__, "rpc.http.decorators", None),
495
+ "BearerTokenAuth": (__SPEC_PARENT__, "rpc.http.decorators", None),
496
+ "BasicAuth": (__SPEC_PARENT__, "rpc.http.decorators", None),
497
+ "ApiKeyAuth": (__SPEC_PARENT__, "rpc.http.decorators", None),
498
+ "CacheMiddleware": (__SPEC_PARENT__, "rpc.http.decorators", None),
499
+ "AuthenticationMiddleware": (__SPEC_PARENT__, "rpc.http.decorators", None),
500
+ "ResponseMiddleware": (__SPEC_PARENT__, "rpc.http.decorators", None),
501
+ "RequestHook": (__SPEC_PARENT__, "rpc.http.decorators", None),
502
+ "ResponseHook": (__SPEC_PARENT__, "rpc.http.decorators", None),
503
+ "RetryConfig": (__SPEC_PARENT__, "rpc.http.decorators", None),
504
+ "TimeoutException": (__SPEC_PARENT__, "rpc.http.decorators", None),
443
505
  "use_app_context": (__SPEC_PARENT__, "microservice", None),
444
506
  "use_app_transaction_context": (__SPEC_PARENT__, "microservice", None),
445
507
  "use_app_tx_ctx_data": (__SPEC_PARENT__, "microservice", None),
508
+ "is_shutting_down": (__SPEC_PARENT__, "microservice", None),
509
+ "request_shutdown": (__SPEC_PARENT__, "microservice", None),
446
510
  "AppContext": (__SPEC_PARENT__, "microservice", None),
447
511
  "AppInterceptor": (__SPEC_PARENT__, "microservice", None),
448
512
  "AppTransactionContext": (__SPEC_PARENT__, "microservice", None),
@@ -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
- async with self.consumer.uow_context_provider(
836
- AppTransactionContext(
837
- controller_member_reflect=scheduled_action.controller_member,
838
- transaction_data=SchedulerTransactionData(
839
- scheduled_to=datetime.now(UTC),
840
- cron_expression=scheduled_action.spec.cron,
841
- triggered_at=datetime.now(UTC),
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
- await scheduled_action.callable(*args, **kwargs)
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
- async with self.consumer.uow_context_provider(
1137
- AppTransactionContext(
1138
- controller_member_reflect=handler_data.controller_member,
1139
- transaction_data=MessageBusTransactionData(
1140
- message=builded_message,
1141
- topic=routing_key,
1142
- ),
1143
- )
1144
- ):
1145
- ctx: AsyncContextManager[Any]
1146
- if incoming_message_spec.timeout is not None:
1147
- ctx = asyncio.timeout(incoming_message_spec.timeout)
1148
- else:
1149
- ctx = none_context()
1150
- async with ctx:
1151
- try:
1152
- with provide_bus_message_controller(
1153
- AioPikaMessageBusController(aio_pika_message)
1154
- ):
1155
- await handler(builded_message)
1156
- if not incoming_message_spec.auto_ack:
1157
- with suppress(aio_pika.MessageProcessError):
1158
- # Use channel context for acknowledgement
1159
- async with self.consumer.get_channel_ctx(self.queue_name):
1160
- await aio_pika_message.ack()
1161
- except BaseException as base_exc:
1162
- # Get message id for logging
1163
- message_id = aio_pika_message.message_id or str(uuid.uuid4())
1164
-
1165
- # Extract retry count from headers if available
1166
- headers = aio_pika_message.headers or {}
1167
- retry_count = int(str(headers.get("x-retry-count", 0)))
1168
-
1169
- # Process exception handler if configured
1170
- if incoming_message_spec.exception_handler is not None:
1171
- try:
1172
- incoming_message_spec.exception_handler(base_exc)
1173
- except Exception as nested_exc:
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 exception handler for message {message_id}: {base_exc} | {nested_exc}"
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
- # Handle rejection with retry logic
1183
- if incoming_message_spec.requeue_on_exception:
1184
- # Use our retry with backoff mechanism
1185
- await self.handle_reject_message(
1186
- aio_pika_message,
1187
- requeue=False, # Don't requeue directly, use our backoff mechanism
1188
- retry_count=retry_count,
1189
- exception=base_exc,
1190
- )
1191
- else:
1192
- # Message shouldn't be retried, reject it
1193
- await self.handle_reject_message(
1194
- aio_pika_message, requeue=False, exception=base_exc
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
- logger.info(
1211
- f"Message {message_id}#{self.queue_name} processed successfully"
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
jararaca/microservice.py CHANGED
@@ -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
- async with self.uow_provider(
105
- AppTransactionContext(
106
- controller_member_reflect=member,
107
- transaction_data=(
108
- HttpTransactionData(request=request)
109
- if request
110
- else WebSocketTransactionData(websocket=websocket)
111
- ),
112
- )
113
- ):
114
- yield
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,97 @@
1
+ # HTTP RPC Module - Complete REST Client Implementation
2
+ """
3
+ This module provides a complete REST client implementation with support for:
4
+ - HTTP method decorators (@Get, @Post, @Put, @Patch, @Delete)
5
+ - Request parameter decorators (@Query, @Header, @PathParam, @Body, @FormData, @File)
6
+ - Configuration decorators (@Timeout, @Retry, @ContentType)
7
+ - Authentication middleware (BearerTokenAuth, BasicAuth, ApiKeyAuth)
8
+ - Caching and response middleware
9
+ - Request/response hooks for customization
10
+ """
11
+
12
+ from .backends.httpx import HTTPXHttpRPCAsyncBackend
13
+ from .decorators import ( # HTTP Method decorators; Request parameter decorators; Configuration decorators; Client builder and core classes; Authentication classes; Middleware and hooks; Configuration classes; Data structures; Error handlers; Exceptions
14
+ ApiKeyAuth,
15
+ AuthenticationMiddleware,
16
+ BasicAuth,
17
+ BearerTokenAuth,
18
+ Body,
19
+ CacheMiddleware,
20
+ ContentType,
21
+ Delete,
22
+ File,
23
+ FormData,
24
+ Get,
25
+ GlobalHttpErrorHandler,
26
+ Header,
27
+ HttpMapping,
28
+ HttpRpcClientBuilder,
29
+ HttpRPCRequest,
30
+ HttpRPCResponse,
31
+ Patch,
32
+ PathParam,
33
+ Post,
34
+ Put,
35
+ Query,
36
+ RequestAttribute,
37
+ RequestHook,
38
+ ResponseHook,
39
+ ResponseMiddleware,
40
+ RestClient,
41
+ Retry,
42
+ RetryConfig,
43
+ RouteHttpErrorHandler,
44
+ RPCRequestNetworkError,
45
+ RPCUnhandleError,
46
+ Timeout,
47
+ TimeoutException,
48
+ )
49
+
50
+ __all__ = [
51
+ # HTTP Method decorators
52
+ "Get",
53
+ "Post",
54
+ "Put",
55
+ "Patch",
56
+ "Delete",
57
+ # Request parameter decorators
58
+ "Query",
59
+ "Header",
60
+ "PathParam",
61
+ "Body",
62
+ "FormData",
63
+ "File",
64
+ # Configuration decorators
65
+ "Timeout",
66
+ "Retry",
67
+ "ContentType",
68
+ # Client builder and core classes
69
+ "RestClient",
70
+ "HttpRpcClientBuilder",
71
+ "HttpMapping",
72
+ "RequestAttribute",
73
+ # Authentication classes
74
+ "BearerTokenAuth",
75
+ "BasicAuth",
76
+ "ApiKeyAuth",
77
+ "AuthenticationMiddleware",
78
+ # Middleware and hooks
79
+ "CacheMiddleware",
80
+ "ResponseMiddleware",
81
+ "RequestHook",
82
+ "ResponseHook",
83
+ # Configuration classes
84
+ "RetryConfig",
85
+ # Data structures
86
+ "HttpRPCRequest",
87
+ "HttpRPCResponse",
88
+ # Error handlers
89
+ "GlobalHttpErrorHandler",
90
+ "RouteHttpErrorHandler",
91
+ # Exceptions
92
+ "RPCRequestNetworkError",
93
+ "RPCUnhandleError",
94
+ "TimeoutException",
95
+ # Backend
96
+ "HTTPXHttpRPCAsyncBackend",
97
+ ]
@@ -0,0 +1,10 @@
1
+ # HTTP RPC Backends
2
+ """
3
+ Backend implementations for HTTP RPC clients.
4
+ """
5
+
6
+ from .httpx import HTTPXHttpRPCAsyncBackend
7
+
8
+ __all__ = [
9
+ "HTTPXHttpRPCAsyncBackend",
10
+ ]
@@ -1,3 +1,4 @@
1
+ import time
1
2
  from urllib.parse import urljoin
2
3
 
3
4
  import httpx
@@ -7,34 +8,63 @@ from jararaca.rpc.http.decorators import (
7
8
  HttpRPCRequest,
8
9
  HttpRPCResponse,
9
10
  RPCRequestNetworkError,
11
+ TimeoutException,
10
12
  )
11
13
 
12
14
 
13
15
  class HTTPXHttpRPCAsyncBackend(HttpRPCAsyncBackend):
14
16
 
15
- def __init__(self, prefix_url: str = ""):
17
+ def __init__(self, prefix_url: str = "", default_timeout: float = 30.0):
16
18
  self.prefix_url = prefix_url
19
+ self.default_timeout = default_timeout
17
20
 
18
21
  async def request(
19
22
  self,
20
23
  request: HttpRPCRequest,
21
24
  ) -> HttpRPCResponse:
22
25
 
23
- async with httpx.AsyncClient() as client:
26
+ start_time = time.time()
27
+
28
+ # Prepare timeout
29
+ timeout = (
30
+ request.timeout if request.timeout is not None else self.default_timeout
31
+ )
32
+
33
+ # Prepare request kwargs
34
+ request_kwargs = {
35
+ "method": request.method,
36
+ "url": urljoin(self.prefix_url, request.url),
37
+ "headers": request.headers,
38
+ "params": request.query_params,
39
+ "timeout": timeout,
40
+ }
41
+
42
+ # Handle different content types
43
+ if request.form_data and request.files:
44
+ # Multipart form data with files
45
+ request_kwargs["data"] = request.form_data
46
+ request_kwargs["files"] = request.files
47
+ elif request.form_data:
48
+ # Form data only
49
+ request_kwargs["data"] = request.form_data
50
+ elif request.body:
51
+ # Raw body content
52
+ request_kwargs["content"] = request.body
24
53
 
54
+ async with httpx.AsyncClient() as client:
25
55
  try:
26
- response = await client.request(
27
- method=request.method,
28
- url=urljoin(self.prefix_url, request.url),
29
- headers=request.headers,
30
- params=request.query_params,
31
- content=request.body,
32
- )
56
+ response = await client.request(**request_kwargs) # type: ignore[arg-type]
57
+
58
+ elapsed_time = time.time() - start_time
33
59
 
34
60
  return HttpRPCResponse(
35
61
  status_code=response.status_code,
36
62
  data=response.content,
63
+ headers=dict(response.headers),
64
+ elapsed_time=elapsed_time,
37
65
  )
66
+ except httpx.TimeoutException as err:
67
+ raise TimeoutException(f"Request timed out: {err}") from err
38
68
  except httpx.NetworkError as err:
39
69
  raise RPCRequestNetworkError(
40
70
  request=request, backend_request=err.request
@@ -1,11 +1,16 @@
1
+ import asyncio
1
2
  import inspect
3
+ import json
4
+ import time
2
5
  from dataclasses import dataclass
3
6
  from typing import (
4
7
  Any,
5
8
  Awaitable,
6
9
  Callable,
10
+ Dict,
7
11
  Iterable,
8
12
  Literal,
13
+ Optional,
9
14
  Protocol,
10
15
  Type,
11
16
  TypeVar,
@@ -15,6 +20,11 @@ from typing import (
15
20
  from pydantic import BaseModel
16
21
 
17
22
  DECORATED_FUNC = TypeVar("DECORATED_FUNC", bound=Callable[..., Awaitable[Any]])
23
+ DECORATED_CLASS = TypeVar("DECORATED_CLASS", bound=Any)
24
+
25
+
26
+ class TimeoutException(Exception):
27
+ """Exception raised when a request times out"""
18
28
 
19
29
 
20
30
  class HttpMapping:
@@ -95,7 +105,9 @@ class RequestAttribute:
95
105
  return []
96
106
 
97
107
  def __init__(
98
- self, attribute_type: Literal["query", "header", "body", "param"], name: str
108
+ self,
109
+ attribute_type: Literal["query", "header", "body", "param", "form", "file"],
110
+ name: str,
99
111
  ):
100
112
  self.attribute_type = attribute_type
101
113
  self.name = name
@@ -129,7 +141,105 @@ class PathParam(RequestAttribute):
129
141
  super().__init__("param", name)
130
142
 
131
143
 
132
- DECORATED_CLASS = TypeVar("DECORATED_CLASS", bound=Any)
144
+ class FormData(RequestAttribute):
145
+ """Decorator for form data parameters"""
146
+
147
+ def __init__(self, name: str):
148
+ super().__init__("form", name)
149
+
150
+
151
+ class File(RequestAttribute):
152
+ """Decorator for file upload parameters"""
153
+
154
+ def __init__(self, name: str):
155
+ super().__init__("file", name)
156
+
157
+
158
+ class Timeout:
159
+ """Decorator for setting request timeout"""
160
+
161
+ TIMEOUT_ATTR = "__request_timeout__"
162
+
163
+ def __init__(self, seconds: float):
164
+ self.seconds = seconds
165
+
166
+ def __call__(self, func: DECORATED_FUNC) -> DECORATED_FUNC:
167
+ setattr(func, self.TIMEOUT_ATTR, self)
168
+ return func
169
+
170
+ @staticmethod
171
+ def get(func: DECORATED_FUNC) -> Optional["Timeout"]:
172
+ return getattr(func, Timeout.TIMEOUT_ATTR, None)
173
+
174
+
175
+ class RetryConfig:
176
+ """Configuration for retry behavior"""
177
+
178
+ def __init__(
179
+ self,
180
+ max_attempts: int = 3,
181
+ backoff_factor: float = 1.0,
182
+ retry_on_status_codes: Optional[list[int]] = None,
183
+ ):
184
+ self.max_attempts = max_attempts
185
+ self.backoff_factor = backoff_factor
186
+ self.retry_on_status_codes = retry_on_status_codes or [500, 502, 503, 504]
187
+
188
+
189
+ class Retry:
190
+ """Decorator for retry configuration"""
191
+
192
+ RETRY_ATTR = "__request_retry__"
193
+
194
+ def __init__(self, config: RetryConfig):
195
+ self.config = config
196
+
197
+ def __call__(self, func: DECORATED_FUNC) -> DECORATED_FUNC:
198
+ setattr(func, self.RETRY_ATTR, self)
199
+ return func
200
+
201
+ @staticmethod
202
+ def get(func: DECORATED_FUNC) -> Optional["Retry"]:
203
+ return getattr(func, Retry.RETRY_ATTR, None)
204
+
205
+
206
+ class ContentType:
207
+ """Decorator for specifying content type"""
208
+
209
+ CONTENT_TYPE_ATTR = "__content_type__"
210
+
211
+ def __init__(self, content_type: str):
212
+ self.content_type = content_type
213
+
214
+ def __call__(self, func: DECORATED_FUNC) -> DECORATED_FUNC:
215
+ setattr(func, self.CONTENT_TYPE_ATTR, self)
216
+ return func
217
+
218
+ @staticmethod
219
+ def get(func: DECORATED_FUNC) -> Optional["ContentType"]:
220
+ return getattr(func, ContentType.CONTENT_TYPE_ATTR, None)
221
+
222
+
223
+ class ResponseMiddleware(Protocol):
224
+ """Protocol for response middleware"""
225
+
226
+ def on_response(
227
+ self, request: "HttpRPCRequest", response: "HttpRPCResponse"
228
+ ) -> "HttpRPCResponse": ...
229
+
230
+
231
+ class RequestHook(Protocol):
232
+ """Protocol for request hooks"""
233
+
234
+ def before_request(self, request: "HttpRPCRequest") -> "HttpRPCRequest": ...
235
+
236
+
237
+ class ResponseHook(Protocol):
238
+ """Protocol for response hooks"""
239
+
240
+ def after_response(
241
+ self, request: "HttpRPCRequest", response: "HttpRPCResponse"
242
+ ) -> "HttpRPCResponse": ...
133
243
 
134
244
 
135
245
  class RestClient:
@@ -159,9 +269,10 @@ class RestClient:
159
269
 
160
270
  @dataclass
161
271
  class HttpRPCResponse:
162
-
163
272
  status_code: int
164
273
  data: bytes
274
+ headers: Optional[Dict[str, str]] = None
275
+ elapsed_time: Optional[float] = None
165
276
 
166
277
 
167
278
  @dataclass
@@ -171,6 +282,9 @@ class HttpRPCRequest:
171
282
  headers: list[tuple[str, str]]
172
283
  query_params: dict[str, str]
173
284
  body: bytes | None
285
+ timeout: Optional[float] = None
286
+ form_data: Optional[Dict[str, Any]] = None
287
+ files: Optional[Dict[str, Any]] = None
174
288
 
175
289
 
176
290
  class RPCRequestNetworkError(Exception):
@@ -275,15 +389,130 @@ class RequestMiddleware(Protocol):
275
389
  def on_request(self, request: HttpRPCRequest) -> HttpRPCRequest: ...
276
390
 
277
391
 
392
+ class AuthenticationMiddleware(RequestMiddleware):
393
+ """Base class for authentication middleware"""
394
+
395
+ def on_request(self, request: HttpRPCRequest) -> HttpRPCRequest:
396
+ return self.add_auth(request)
397
+
398
+ def add_auth(self, request: HttpRPCRequest) -> HttpRPCRequest:
399
+ raise NotImplementedError
400
+
401
+
402
+ class BearerTokenAuth(AuthenticationMiddleware):
403
+ """Bearer token authentication middleware"""
404
+
405
+ def __init__(self, token: str):
406
+ self.token = token
407
+
408
+ def add_auth(self, request: HttpRPCRequest) -> HttpRPCRequest:
409
+ request.headers.append(("Authorization", f"Bearer {self.token}"))
410
+ return request
411
+
412
+
413
+ class BasicAuth(AuthenticationMiddleware):
414
+ """Basic authentication middleware"""
415
+
416
+ def __init__(self, username: str, password: str):
417
+ import base64
418
+
419
+ credentials = base64.b64encode(f"{username}:{password}".encode()).decode()
420
+ self.credentials = credentials
421
+
422
+ def add_auth(self, request: HttpRPCRequest) -> HttpRPCRequest:
423
+ request.headers.append(("Authorization", f"Basic {self.credentials}"))
424
+ return request
425
+
426
+
427
+ class ApiKeyAuth(AuthenticationMiddleware):
428
+ """API key authentication middleware"""
429
+
430
+ def __init__(self, api_key: str, header_name: str = "X-API-Key"):
431
+ self.api_key = api_key
432
+ self.header_name = header_name
433
+
434
+ def add_auth(self, request: HttpRPCRequest) -> HttpRPCRequest:
435
+ request.headers.append((self.header_name, self.api_key))
436
+ return request
437
+
438
+
439
+ class CacheMiddleware(RequestMiddleware):
440
+ """Simple in-memory cache middleware"""
441
+
442
+ def __init__(self, ttl_seconds: int = 300):
443
+ self.cache: Dict[str, tuple[Any, float]] = {}
444
+ self.ttl_seconds = ttl_seconds
445
+
446
+ def _cache_key(self, request: HttpRPCRequest) -> str:
447
+ """Generate cache key from request"""
448
+ key_data = {
449
+ "method": request.method,
450
+ "url": request.url,
451
+ "query_params": request.query_params,
452
+ "headers": sorted(request.headers),
453
+ }
454
+ return str(hash(json.dumps(key_data, sort_keys=True)))
455
+
456
+ def on_request(self, request: HttpRPCRequest) -> HttpRPCRequest:
457
+ # Only cache GET requests
458
+ if request.method == "GET":
459
+ cache_key = self._cache_key(request)
460
+ if cache_key in self.cache:
461
+ cached_response, timestamp = self.cache[cache_key]
462
+ if time.time() - timestamp < self.ttl_seconds:
463
+ # Return cached response (this needs to be handled in the client builder)
464
+ setattr(request, "_cached_response", cached_response)
465
+ return request
466
+
467
+
278
468
  class HttpRpcClientBuilder:
279
469
 
280
470
  def __init__(
281
471
  self,
282
472
  backend: HttpRPCAsyncBackend,
283
473
  middlewares: list[RequestMiddleware] = [],
474
+ response_middlewares: list[ResponseMiddleware] = [],
475
+ request_hooks: list[RequestHook] = [],
476
+ response_hooks: list[ResponseHook] = [],
284
477
  ):
285
478
  self._backend = backend
286
479
  self._middlewares = middlewares
480
+ self._response_middlewares = response_middlewares
481
+ self._request_hooks = request_hooks
482
+ self._response_hooks = response_hooks
483
+
484
+ async def _execute_with_retry(
485
+ self, request: HttpRPCRequest, retry_config: Optional[RetryConfig]
486
+ ) -> HttpRPCResponse:
487
+ """Execute request with retry logic"""
488
+ if not retry_config:
489
+ return await self._backend.request(request)
490
+
491
+ last_exception = None
492
+ for attempt in range(retry_config.max_attempts):
493
+ try:
494
+ response = await self._backend.request(request)
495
+
496
+ # Check if we should retry based on status code
497
+ if response.status_code in retry_config.retry_on_status_codes:
498
+ if attempt < retry_config.max_attempts - 1:
499
+ wait_time = retry_config.backoff_factor * (2**attempt)
500
+ await asyncio.sleep(wait_time)
501
+ continue
502
+
503
+ return response
504
+
505
+ except Exception as e:
506
+ last_exception = e
507
+ if attempt < retry_config.max_attempts - 1:
508
+ wait_time = retry_config.backoff_factor * (2**attempt)
509
+ await asyncio.sleep(wait_time)
510
+ continue
511
+ else:
512
+ raise
513
+
514
+ # This should never be reached, but just in case
515
+ raise last_exception or Exception("Retry failed")
287
516
 
288
517
  def build(self, cls: type[T]) -> T:
289
518
  rest_client = RestClient.get(cls)
@@ -314,10 +543,17 @@ class HttpRpcClientBuilder:
314
543
  headers: list[tuple[str, str]] = []
315
544
  query_params = {}
316
545
  body: Any = None
546
+ form_data: Dict[str, Any] = {}
547
+ files: Dict[str, Any] = {}
317
548
  compiled_path = (
318
549
  rest_client.base_path.rstrip("/") + "/" + mapping.path.lstrip("/")
319
550
  )
320
551
 
552
+ # Get decorators for this method
553
+ timeout_config = Timeout.get(method_call)
554
+ retry_config = Retry.get(method_call)
555
+ content_type_config = ContentType.get(method_call)
556
+
321
557
  for attr in request_attributes:
322
558
  if attr.attribute_type == "header":
323
559
  headers.append((attr.name, compiled_kwargs[attr.name]))
@@ -329,17 +565,33 @@ class HttpRpcClientBuilder:
329
565
  compiled_path = compiled_path.replace(
330
566
  f":{attr.name}", str(compiled_kwargs[attr.name])
331
567
  )
568
+ elif attr.attribute_type == "form":
569
+ form_data[attr.name] = compiled_kwargs[attr.name]
570
+ elif attr.attribute_type == "file":
571
+ files[attr.name] = compiled_kwargs[attr.name]
332
572
 
333
573
  body_content: bytes | None = None
334
574
 
575
+ # Handle different content types
335
576
  if body is not None:
336
577
  if isinstance(body, BaseModel):
337
578
  body_content = body.model_dump_json().encode()
338
- headers.append(("Content-Type", "application/json"))
579
+ if not content_type_config:
580
+ headers.append(("Content-Type", "application/json"))
339
581
  elif isinstance(body, bytes):
340
582
  body_content = body
583
+ elif isinstance(body, str):
584
+ body_content = body.encode()
585
+ elif isinstance(body, dict):
586
+ body_content = json.dumps(body).encode()
587
+ if not content_type_config:
588
+ headers.append(("Content-Type", "application/json"))
341
589
  else:
342
- raise ValueError("Invalid body type")
590
+ raise ValueError(f"Invalid body type: {type(body)}")
591
+
592
+ # Apply custom content type if specified
593
+ if content_type_config:
594
+ headers.append(("Content-Type", content_type_config.content_type))
343
595
 
344
596
  request = HttpRPCRequest(
345
597
  url=compiled_path,
@@ -347,12 +599,41 @@ class HttpRpcClientBuilder:
347
599
  headers=headers,
348
600
  query_params=query_params,
349
601
  body=body_content,
602
+ timeout=timeout_config.seconds if timeout_config else None,
603
+ form_data=form_data if form_data else None,
604
+ files=files if files else None,
350
605
  )
351
606
 
607
+ # Apply request hooks
608
+ for hook in self._request_hooks:
609
+ request = hook.before_request(request)
610
+
352
611
  for middleware in self._middlewares:
353
612
  request = middleware.on_request(request)
354
613
 
355
- response = await self._backend.request(request)
614
+ # Check for cached response
615
+ if hasattr(request, "_cached_response"):
616
+ response = getattr(request, "_cached_response")
617
+ else:
618
+ # Execute request with retry if configured
619
+ response = await self._execute_with_retry(
620
+ request, retry_config.config if retry_config else None
621
+ )
622
+
623
+ # Apply response middleware
624
+ for response_middleware in self._response_middlewares:
625
+ response = response_middleware.on_response(request, response)
626
+
627
+ # Apply response hooks
628
+ for response_hook in self._response_hooks:
629
+ response = response_hook.after_response(request, response)
630
+
631
+ # Cache response if using cache middleware and it's a GET request
632
+ if request.method == "GET":
633
+ for middleware in self._middlewares:
634
+ if isinstance(middleware, CacheMiddleware):
635
+ cache_key = middleware._cache_key(request)
636
+ middleware.cache[cache_key] = (response, time.time())
356
637
 
357
638
  return_type = inspect.signature(method_call).return_annotation
358
639
 
@@ -407,13 +688,28 @@ __all__ = [
407
688
  "Header",
408
689
  "Body",
409
690
  "PathParam",
691
+ "FormData",
692
+ "File",
693
+ "Timeout",
694
+ "RetryConfig",
695
+ "Retry",
696
+ "ContentType",
410
697
  "RestClient",
411
698
  "HttpRPCAsyncBackend",
412
699
  "HttpRPCRequest",
413
700
  "HttpRPCResponse",
414
701
  "RPCRequestNetworkError",
702
+ "RPCUnhandleError",
415
703
  "HttpRpcClientBuilder",
416
704
  "RequestMiddleware",
705
+ "ResponseMiddleware",
706
+ "RequestHook",
707
+ "ResponseHook",
708
+ "AuthenticationMiddleware",
709
+ "BearerTokenAuth",
710
+ "BasicAuth",
711
+ "ApiKeyAuth",
712
+ "CacheMiddleware",
417
713
  "TracedRequestMiddleware",
418
714
  "GlobalHttpErrorHandler",
419
715
  "RouteHttpErrorHandler",
@@ -406,7 +406,7 @@ export type ResponseType =
406
406
  export interface HttpBackendRequest {
407
407
  method: string;
408
408
  path: string;
409
- pathParams: { [key: string]: string };
409
+ pathParams: { [key: string]: any };
410
410
  headers: { [key: string]: string };
411
411
  query: { [key: string]: unknown };
412
412
  body: unknown;
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jararaca
3
- Version: 0.3.12a3
3
+ Version: 0.3.12a5
4
4
  Summary: A simple and fast API framework for Python
5
5
  Home-page: https://github.com/LuscasLeo/jararaca
6
6
  Author: Lucas S
@@ -1,7 +1,7 @@
1
1
  LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
2
2
  README.md,sha256=2qMM__t_MoLKZr4IY9tXjo-Jn6LKjuHMb1qbyXpgL08,3401
3
- pyproject.toml,sha256=Hiy7cJKBQIrdiYnBskiQEYruLl8LsGgsknkKCD2uSp8,2040
4
- jararaca/__init__.py,sha256=EdOPigKYraoG7I-Hl9V3XteqrbhdBS3xy1rUakh58lc,17949
3
+ pyproject.toml,sha256=sh8QoSrsND1MonDpmTcmeZ9ycDIraZF48tey-OKC6ZQ,2040
4
+ jararaca/__init__.py,sha256=qffDJwYvbPqMdlx3fi5qFBVF2ZQ89W09znjTGTe9J6I,20939
5
5
  jararaca/__main__.py,sha256=-O3vsB5lHdqNFjUtoELDF81IYFtR-DSiiFMzRaiSsv4,67
6
6
  jararaca/broker_backend/__init__.py,sha256=GzEIuHR1xzgCJD4FE3harNjoaYzxHMHoEL0_clUaC-k,3528
7
7
  jararaca/broker_backend/mapper.py,sha256=vTsi7sWpNvlga1PWPFg0rCJ5joJ0cdzykkIc2Tuvenc,696
@@ -23,8 +23,8 @@ jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py,sha256=_DEHwIH
23
23
  jararaca/messagebus/interceptors/publisher_interceptor.py,sha256=ojy1bRhqMgrkQljcGGS8cd8-8pUjL8ZHjIUkdmaAnNM,1325
24
24
  jararaca/messagebus/message.py,sha256=U6cyd2XknX8mtm0333slz5fanky2PFLWCmokAO56vvU,819
25
25
  jararaca/messagebus/publisher.py,sha256=JTkxdKbvxvDWT8nK8PVEyyX061vYYbKQMxRHXrZtcEY,2173
26
- jararaca/messagebus/worker.py,sha256=CrSIejWMGII4_JK0aH4jxdj0oBJX4hSXY0SmVa6KURA,54187
27
- jararaca/microservice.py,sha256=rRIimfeP2-wf289PKoUbk9wrSdA0ga_qWz5JNgQ5IE0,9667
26
+ jararaca/messagebus/worker.py,sha256=736rl6mBVxo-w5Y-jJe1w1ORPgVk16jlmTyVJueeAKs,55156
27
+ jararaca/microservice.py,sha256=4uQWPH1Ytxl2tg4IK-M_6T-28X89ku4ObJXJyg9CDr4,10663
28
28
  jararaca/observability/decorators.py,sha256=MOIr2PttPYYvRwEdfQZEwD5RxKHOTv8UEy9n1YQVoKw,2281
29
29
  jararaca/observability/interceptor.py,sha256=U4ZLM0f8j6Q7gMUKKnA85bnvD-Qa0ii79Qa_X8KsXAQ,1498
30
30
  jararaca/observability/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -40,7 +40,7 @@ jararaca/presentation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
40
40
  jararaca/presentation/decorators.py,sha256=dNcK1xYWhdHVBeWDa8dhqh4z8yQcPkJZTQkfgLXz6RI,11655
41
41
  jararaca/presentation/hooks.py,sha256=WBbU5DG3-MAm2Ro2YraQyYG_HENfizYfyShL2ktHi6k,1980
42
42
  jararaca/presentation/http_microservice.py,sha256=g771JosV6jTY3hQtG-HkLOo-T0e-r3b3rp1ddt99Qf0,533
43
- jararaca/presentation/server.py,sha256=5joE17nnSiwR497Nerr-vcthKL4NRBzyf1KWM8DFgwQ,4682
43
+ jararaca/presentation/server.py,sha256=Im-tei9EynKm1DO-63UIGeTJK5ZvxIhpAxbsYGn7ZW0,6244
44
44
  jararaca/presentation/websocket/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
45
  jararaca/presentation/websocket/base_types.py,sha256=AvUeeZ1TFhSiRMcYqZU1HaQNqSrcgTkC5R0ArP5dGmA,146
46
46
  jararaca/presentation/websocket/context.py,sha256=A6K5W3kqo9Hgeh1m6JiI7Cdz5SfbXcaICSVX7u1ARZo,1903
@@ -53,11 +53,11 @@ jararaca/reflect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
53
53
  jararaca/reflect/controller_inspect.py,sha256=UtV4pRIOqCoK4ogBTXQE0dyopEQ5LDFhwm-1iJvrkJc,2326
54
54
  jararaca/reflect/metadata.py,sha256=oTi0zIjCYkeBhs12PNTLc8GmzR6qWHdl3drlmamXLJo,1897
55
55
  jararaca/rpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- jararaca/rpc/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
- jararaca/rpc/http/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
- jararaca/rpc/http/backends/httpx.py,sha256=2inHnHYF5f7HiidkwlPOK7ZFpc1jBklKxvVRla9knCE,1141
56
+ jararaca/rpc/http/__init__.py,sha256=Xp7-d-cVj7EK1JloSUbSnGBfER5YwCfp7LCU6wCAf1c,2396
57
+ jararaca/rpc/http/backends/__init__.py,sha256=Q1tIj1PTjB4__qTZndGMu4IjP5lbayLbQZJ4fZXcnAk,166
58
+ jararaca/rpc/http/backends/httpx.py,sha256=nNHXBortLt0tWW1qTXMPtSuO1mkpQcNh5eplP_8zYEM,2246
59
59
  jararaca/rpc/http/backends/otel.py,sha256=Uc6CjHSCZ5hvnK1fNFv3ota5xzUFnvIl1JOpG380siA,807
60
- jararaca/rpc/http/decorators.py,sha256=oUSzgMGI8w6SoKiz3GltDbd3BWAuyY60F23cdRRNeiw,11897
60
+ jararaca/rpc/http/decorators.py,sha256=0BCcmP_jolIAB73VeXm3TKyVr3AWbzkXzUVMGqYtqSQ,22145
61
61
  jararaca/rpc/http/httpx.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
62
  jararaca/scheduler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
63
  jararaca/scheduler/beat_worker.py,sha256=i_NyovjFhhLYuUivf2mwpVC27oQ9SGTFOHZ7Ec4jv6I,12076
@@ -66,12 +66,12 @@ jararaca/scheduler/types.py,sha256=4HEQOmVIDp-BYLSzqmqSFIio1bd51WFmgFPIzPpVu04,1
66
66
  jararaca/tools/app_config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
67
  jararaca/tools/app_config/decorators.py,sha256=-ckkMZ1dswOmECdo1rFrZ15UAku--txaNXMp8fd1Ndk,941
68
68
  jararaca/tools/app_config/interceptor.py,sha256=HV8h4AxqUc_ACs5do4BSVlyxlRXzx7HqJtoVO9tfRnQ,2611
69
- jararaca/tools/typescript/interface_parser.py,sha256=Bux7DUjEQ8eAry_qZeKCIYz0aPaMO78_5wAPVErXGFo,31482
69
+ jararaca/tools/typescript/interface_parser.py,sha256=foZ_A_lkyacmAq9TU6hUFv5ZnZcPZbbxcjtzFUu1A_o,31479
70
70
  jararaca/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
71
  jararaca/utils/rabbitmq_utils.py,sha256=ytdAFUyv-OBkaVnxezuJaJoLrmN7giZgtKeet_IsMBs,10918
72
72
  jararaca/utils/retry.py,sha256=DzPX_fXUvTqej6BQ8Mt2dvLo9nNlTBm7Kx2pFZ26P2Q,4668
73
- jararaca-0.3.12a3.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
74
- jararaca-0.3.12a3.dist-info/METADATA,sha256=wspP7q-KGhpcjXzXfRPGdW14rjggwwhzGOFnhmhjgDE,4995
75
- jararaca-0.3.12a3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
76
- jararaca-0.3.12a3.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
77
- jararaca-0.3.12a3.dist-info/RECORD,,
73
+ jararaca-0.3.12a5.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
74
+ jararaca-0.3.12a5.dist-info/METADATA,sha256=1voFAjmPXB7wfDeF9-B0GSVSfzPG-wV1NR5ay_Tjkls,4995
75
+ jararaca-0.3.12a5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
76
+ jararaca-0.3.12a5.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
77
+ jararaca-0.3.12a5.dist-info/RECORD,,
pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "jararaca"
3
- version = "0.3.12a3"
3
+ version = "0.3.12a5"
4
4
  description = "A simple and fast API framework for Python"
5
5
  authors = ["Lucas S <me@luscasleo.dev>"]
6
6
  readme = "README.md"