jararaca 0.3.12a2__tar.gz → 0.3.12a3__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.

Files changed (85) hide show
  1. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/PKG-INFO +1 -1
  2. jararaca-0.3.12a3/docs/interceptors.md +210 -0
  3. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/pyproject.toml +1 -1
  4. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/scheduler/beat_worker.py +14 -6
  5. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/LICENSE +0 -0
  6. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/README.md +0 -0
  7. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/docs/CNAME +0 -0
  8. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/docs/architecture.md +0 -0
  9. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg +0 -0
  10. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.webp +0 -0
  11. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/docs/assets/tracing_example.png +0 -0
  12. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/docs/index.md +0 -0
  13. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/docs/messagebus.md +0 -0
  14. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/docs/retry.md +0 -0
  15. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/docs/scheduler.md +0 -0
  16. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/docs/stylesheets/custom.css +0 -0
  17. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/docs/websocket.md +0 -0
  18. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/__init__.py +0 -0
  19. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/__main__.py +0 -0
  20. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/broker_backend/__init__.py +0 -0
  21. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/broker_backend/mapper.py +0 -0
  22. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/broker_backend/redis_broker_backend.py +0 -0
  23. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/cli.py +0 -0
  24. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/common/__init__.py +0 -0
  25. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/core/__init__.py +0 -0
  26. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/core/providers.py +0 -0
  27. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/core/uow.py +0 -0
  28. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/di.py +0 -0
  29. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/files/entity.py.mako +0 -0
  30. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/lifecycle.py +0 -0
  31. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/messagebus/__init__.py +0 -0
  32. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/messagebus/bus_message_controller.py +0 -0
  33. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/messagebus/consumers/__init__.py +0 -0
  34. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/messagebus/decorators.py +0 -0
  35. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/messagebus/interceptors/__init__.py +0 -0
  36. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +0 -0
  37. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/messagebus/interceptors/publisher_interceptor.py +0 -0
  38. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/messagebus/message.py +0 -0
  39. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/messagebus/publisher.py +0 -0
  40. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/messagebus/worker.py +0 -0
  41. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/microservice.py +0 -0
  42. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/observability/decorators.py +0 -0
  43. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/observability/interceptor.py +0 -0
  44. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/observability/providers/__init__.py +0 -0
  45. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/observability/providers/otel.py +0 -0
  46. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/persistence/base.py +0 -0
  47. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/persistence/exports.py +0 -0
  48. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/persistence/interceptors/__init__.py +0 -0
  49. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/persistence/interceptors/aiosqa_interceptor.py +0 -0
  50. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/persistence/session.py +0 -0
  51. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/persistence/sort_filter.py +0 -0
  52. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/persistence/utilities.py +0 -0
  53. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/presentation/__init__.py +0 -0
  54. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/presentation/decorators.py +0 -0
  55. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/presentation/hooks.py +0 -0
  56. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/presentation/http_microservice.py +0 -0
  57. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/presentation/server.py +0 -0
  58. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/presentation/websocket/__init__.py +0 -0
  59. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/presentation/websocket/base_types.py +0 -0
  60. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/presentation/websocket/context.py +0 -0
  61. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/presentation/websocket/decorators.py +0 -0
  62. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/presentation/websocket/redis.py +0 -0
  63. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/presentation/websocket/types.py +0 -0
  64. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/presentation/websocket/websocket_interceptor.py +0 -0
  65. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/py.typed +0 -0
  66. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/reflect/__init__.py +0 -0
  67. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/reflect/controller_inspect.py +0 -0
  68. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/reflect/metadata.py +0 -0
  69. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/rpc/__init__.py +0 -0
  70. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/rpc/http/__init__.py +0 -0
  71. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/rpc/http/backends/__init__.py +0 -0
  72. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/rpc/http/backends/httpx.py +0 -0
  73. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/rpc/http/backends/otel.py +0 -0
  74. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/rpc/http/decorators.py +0 -0
  75. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/rpc/http/httpx.py +0 -0
  76. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/scheduler/__init__.py +0 -0
  77. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/scheduler/decorators.py +0 -0
  78. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/scheduler/types.py +0 -0
  79. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/tools/app_config/__init__.py +0 -0
  80. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/tools/app_config/decorators.py +0 -0
  81. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/tools/app_config/interceptor.py +0 -0
  82. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/tools/typescript/interface_parser.py +0 -0
  83. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/utils/__init__.py +0 -0
  84. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/utils/rabbitmq_utils.py +0 -0
  85. {jararaca-0.3.12a2 → jararaca-0.3.12a3}/src/jararaca/utils/retry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jararaca
3
- Version: 0.3.12a2
3
+ Version: 0.3.12a3
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
@@ -0,0 +1,210 @@
1
+ # Interceptors in Jararaca
2
+
3
+ Interceptors are a powerful mechanism in Jararaca for implementing cross-cutting concerns across different execution contexts. They allow you to wrap the execution of HTTP requests, message handling, scheduled tasks, and WebSocket communications with common behaviors such as transaction management, message publishing, and WebSocket message dispatching.
4
+
5
+ ## Overview of the Interceptor System
6
+
7
+ Interceptors in Jararaca work by wrapping the execution of handlers in different contexts:
8
+
9
+ - **HTTP Controllers**: Intercept incoming HTTP requests before they reach controllers
10
+ - **Message Bus Consumers**: Intercept message processing in workers
11
+ - **Scheduled Tasks**: Intercept scheduled task execution
12
+ - **WebSocket Connections**: Manage WebSocket connections and message dispatching
13
+
14
+ Each interceptor provides a specific capability to the application through context variables, allowing your code to access these capabilities through hook functions like `use_session()`, `use_publisher()`, and `use_ws_manager()`.
15
+
16
+ ## The Atomic Layer: Transactions, Messages, and WebSockets
17
+
18
+ The true power of the interceptor system lies in how it creates an atomic layer that ensures consistency across database operations, message publishing, and WebSocket communications. This is particularly important for implementing transactional outbox patterns and ensuring consistency in distributed systems.
19
+
20
+ ```mermaid
21
+ sequenceDiagram
22
+ participant Client
23
+ participant HTTP Context
24
+ participant DB Interceptor
25
+ participant MessageBus Interceptor
26
+ participant WebSocket Interceptor
27
+ participant Database
28
+ participant MessageBroker
29
+ participant WebSocketBackend
30
+
31
+ Client->>HTTP Context: HTTP Request
32
+ Note over HTTP Context,WebSocketBackend: Transaction Boundary Begins
33
+
34
+ HTTP Context->>DB Interceptor: Begin Transaction
35
+ DB Interceptor->>Database: Begin DB Transaction
36
+
37
+ HTTP Context->>MessageBus Interceptor: Stage Messages
38
+ MessageBus Interceptor->>MessageBus Interceptor: Queue Messages
39
+
40
+ HTTP Context->>WebSocket Interceptor: Stage WebSocket Messages
41
+ WebSocket Interceptor->>WebSocket Interceptor: Queue WebSocket Messages
42
+
43
+ HTTP Context->>DB Interceptor: Commit Transaction
44
+ DB Interceptor->>Database: Commit DB Transaction
45
+ Database-->>DB Interceptor: Success
46
+
47
+ DB Interceptor-->>MessageBus Interceptor: DB Commit Success
48
+ MessageBus Interceptor->>MessageBroker: Flush Queued Messages
49
+
50
+ MessageBus Interceptor-->>WebSocket Interceptor: Messages Published
51
+ WebSocket Interceptor->>WebSocketBackend: Flush WebSocket Messages
52
+
53
+ Note over HTTP Context,WebSocketBackend: Transaction Boundary Ends
54
+
55
+ HTTP Context-->>Client: HTTP Response
56
+ ```
57
+
58
+ ### Order of Interceptors Matters
59
+
60
+ The order in which interceptors are configured in your application is critical. The typical order is:
61
+
62
+ 1. **Configuration Interceptor** - Loads and provides application configuration
63
+ 2. **Message Bus Publisher Interceptor** - Provides message publishing capabilities
64
+ 3. **Database Session Interceptor** - Provides database transaction capabilities
65
+ 4. **WebSocket Interceptor** - Provides WebSocket communication capabilities
66
+
67
+ This order ensures that:
68
+
69
+ 1. The database transaction is committed first
70
+ 2. Only after successful database commit, messages are published to the message broker
71
+ 3. Finally, WebSocket messages are dispatched to connected clients
72
+
73
+ ## Implementing Transactional Outbox Pattern
74
+
75
+ Jararaca's interceptor system makes implementing the transactional outbox pattern straightforward. This pattern ensures that database changes and message publishing are atomic:
76
+
77
+ ```python
78
+ from jararaca.persistence.session import use_session
79
+ from jararaca.messagebus.publisher import use_publisher
80
+
81
+ async def create_user(user_data: dict):
82
+ # Get the current database session from the context
83
+ session = use_session()
84
+
85
+ # Get the message publisher from the context
86
+ publisher = use_publisher()
87
+
88
+ # Create user in database
89
+ new_user = User(**user_data)
90
+ session.add(new_user)
91
+
92
+ # Stage a message to be published after successful transaction
93
+ await publisher.publish("user_created", UserCreatedEvent(id=new_user.id))
94
+
95
+ # The actual database commit and message publishing
96
+ # happens automatically when the request handling is complete,
97
+ # managed by the interceptors
98
+ ```
99
+
100
+ When this code executes:
101
+ 1. The database session interceptor ensures the user is committed to the database
102
+ 2. If the database transaction succeeds, the message bus interceptor publishes the staged message
103
+ 3. If the database transaction fails, no messages are published
104
+
105
+ ## Contexts and Interceptors
106
+
107
+ Jararaca supports different execution contexts, and interceptors behave differently in each:
108
+
109
+ ### HTTP Context
110
+
111
+ In an HTTP context, all interceptors are active:
112
+ - Database Session Interceptor manages transactions
113
+ - Message Bus Publisher Interceptor stages and flushes messages
114
+ - WebSocket Interceptor stages and flushes WebSocket messages
115
+
116
+ ### Worker Context
117
+
118
+ In a message bus worker context:
119
+ - Database Session Interceptor manages transactions
120
+ - Message Bus Publisher Interceptor stages and flushes messages
121
+ - WebSocket Interceptor stages and flushes WebSocket messages
122
+
123
+ ### Scheduler Context
124
+
125
+ In a scheduler context:
126
+ - Database Session Interceptor manages transactions
127
+ - Message Bus Publisher Interceptor stages and flushes messages
128
+ - WebSocket Interceptor stages and flushes WebSocket messages
129
+
130
+ ### WebSocket Context
131
+
132
+ In a WebSocket context:
133
+ - Database Session Interceptor manages transactions
134
+ - Message Bus Publisher is usually not active (as defined in the `intercept` method)
135
+ - WebSocket communications are managed directly
136
+
137
+ ## Code Example: Configuring Interceptors
138
+
139
+ ```python
140
+ from jararaca import Microservice
141
+ from jararaca.persistence.interceptors import AIOSqlAlchemySessionInterceptor, AIOSQAConfig
142
+ from jararaca.messagebus.interceptors import MessageBusPublisherInterceptor
143
+ from jararaca.presentation.websocket import WebSocketInterceptor
144
+
145
+ # Create the microservice
146
+ microservice = Microservice(
147
+ name="my-service",
148
+ interceptors=[
149
+ # Configuration interceptor (if any)
150
+ # ...
151
+
152
+ # Message bus interceptor
153
+ lambda config: MessageBusPublisherInterceptor(
154
+ connection_factory=message_publisher_factory,
155
+ connection_name="default",
156
+ message_scheduler=message_scheduler,
157
+ ),
158
+
159
+ # Database session interceptor
160
+ lambda config: AIOSqlAlchemySessionInterceptor(
161
+ AIOSQAConfig(
162
+ url="postgresql+asyncpg://user:password@localhost/dbname",
163
+ connection_name="default",
164
+ inject_default=True,
165
+ )
166
+ ),
167
+
168
+ # WebSocket interceptor
169
+ lambda config: WebSocketInterceptor(
170
+ backend=redis_websocket_backend,
171
+ ),
172
+ ],
173
+ # Other microservice configuration...
174
+ )
175
+ ```
176
+
177
+ ## Best Practices
178
+
179
+ 1. **Order Matters**: Always configure interceptors in the order: configuration → message bus → database → WebSocket
180
+ 2. **Transaction Boundaries**: Be aware that interceptors create implicit transaction boundaries
181
+ 3. **Error Handling**: Database errors will prevent message publishing and WebSocket dispatching
182
+ 4. **Idempotency**: Design your message handlers to be idempotent in case of retries
183
+ 5. **Context Variables**: Use the appropriate context hooks (`use_session()`, `use_publisher()`, etc.) to access the capabilities provided by interceptors
184
+
185
+ ## Advanced Usage: Custom Interceptors
186
+
187
+ You can create custom interceptors by implementing the `AppInterceptor` interface:
188
+
189
+ ```python
190
+ from contextlib import asynccontextmanager
191
+ from typing import AsyncGenerator
192
+ from jararaca.microservice import AppInterceptor, AppTransactionContext
193
+
194
+ class CustomInterceptor(AppInterceptor):
195
+ @asynccontextmanager
196
+ async def intercept(self, app_context: AppTransactionContext) -> AsyncGenerator[None, None]:
197
+ # Pre-processing logic
198
+
199
+ try:
200
+ # Let the request handler execute
201
+ yield
202
+ # Post-processing after successful execution
203
+ except Exception as e:
204
+ # Handle exceptions
205
+ raise e
206
+ ```
207
+
208
+ ## Conclusion
209
+
210
+ Jararaca's interceptor system provides a powerful way to implement cross-cutting concerns and ensure consistency across database operations, message publishing, and WebSocket communications. By properly configuring interceptors, you can implement patterns like the transactional outbox pattern with minimal effort, ensuring that your distributed system maintains consistency even in the face of failures.
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "jararaca"
3
- version = "0.3.12a2"
3
+ version = "0.3.12a3"
4
4
  description = "A simple and fast API framework for Python"
5
5
  authors = ["Lucas S <me@luscasleo.dev>"]
6
6
  readme = "README.md"
@@ -5,7 +5,6 @@ import signal
5
5
  import time
6
6
  from abc import ABC, abstractmethod
7
7
  from datetime import UTC, datetime
8
- from types import FrameType
9
8
  from typing import Any
10
9
  from urllib.parse import parse_qs
11
10
 
@@ -249,13 +248,16 @@ class BeatWorker:
249
248
 
250
249
  def run(self) -> None:
251
250
 
252
- def on_signal_received(signal: int, frame_type: FrameType | None) -> None:
253
- logger.info("Received shutdown signal")
254
- self.shutdown_event.set()
255
-
256
- signal.signal(signal.SIGINT, on_signal_received)
251
+ def on_shutdown(loop: asyncio.AbstractEventLoop) -> None:
252
+ logger.info("Shutting down - signal received")
253
+ # Schedule the shutdown to run in the event loop
254
+ asyncio.create_task(self._graceful_shutdown())
257
255
 
258
256
  with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner:
257
+ loop = runner.get_loop()
258
+ loop.add_signal_handler(signal.SIGINT, on_shutdown, loop)
259
+ # Add graceful shutdown handler for SIGTERM as well
260
+ loop.add_signal_handler(signal.SIGTERM, on_shutdown, loop)
259
261
  runner.run(self.start_scheduler())
260
262
 
261
263
  async def start_scheduler(self) -> None:
@@ -340,3 +342,9 @@ class BeatWorker:
340
342
 
341
343
  await self.backend.dispose()
342
344
  await self.broker.dispose()
345
+
346
+ async def _graceful_shutdown(self) -> None:
347
+ """Handles graceful shutdown process"""
348
+ logger.info("Initiating graceful shutdown sequence")
349
+ self.shutdown_event.set()
350
+ logger.info("Graceful shutdown completed")
File without changes
File without changes
File without changes
File without changes
File without changes