jararaca 0.3.11a16__tar.gz → 0.3.12a0__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.
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/PKG-INFO +3 -3
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/docs/index.md +143 -20
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/docs/messagebus.md +24 -6
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/docs/scheduler.md +44 -24
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/docs/websocket.md +24 -10
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/pyproject.toml +1 -1
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/cli.py +214 -31
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/LICENSE +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/README.md +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/docs/CNAME +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/docs/architecture.md +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.webp +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/docs/assets/tracing_example.png +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/docs/retry.md +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/docs/stylesheets/custom.css +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/__main__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/broker_backend/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/broker_backend/mapper.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/broker_backend/redis_broker_backend.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/common/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/core/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/core/providers.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/core/uow.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/di.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/files/entity.py.mako +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/lifecycle.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/messagebus/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/messagebus/bus_message_controller.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/messagebus/consumers/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/messagebus/decorators.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/messagebus/interceptors/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/messagebus/interceptors/publisher_interceptor.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/messagebus/message.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/messagebus/publisher.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/messagebus/worker.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/microservice.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/observability/decorators.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/observability/interceptor.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/observability/providers/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/observability/providers/otel.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/persistence/base.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/persistence/exports.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/persistence/interceptors/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/persistence/interceptors/aiosqa_interceptor.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/persistence/session.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/persistence/sort_filter.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/persistence/utilities.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/presentation/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/presentation/decorators.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/presentation/hooks.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/presentation/http_microservice.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/presentation/server.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/presentation/websocket/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/presentation/websocket/base_types.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/presentation/websocket/context.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/presentation/websocket/decorators.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/presentation/websocket/redis.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/presentation/websocket/types.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/presentation/websocket/websocket_interceptor.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/py.typed +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/reflect/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/reflect/controller_inspect.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/reflect/metadata.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/rpc/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/rpc/http/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/rpc/http/backends/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/rpc/http/backends/httpx.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/rpc/http/backends/otel.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/rpc/http/decorators.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/rpc/http/httpx.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/scheduler/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/scheduler/beat_worker.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/scheduler/decorators.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/scheduler/types.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/tools/app_config/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/tools/app_config/decorators.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/tools/app_config/interceptor.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/tools/typescript/interface_parser.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/utils/__init__.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/utils/rabbitmq_utils.py +0 -0
- {jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/utils/retry.py +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: jararaca
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.12a0
|
|
4
4
|
Summary: A simple and fast API framework for Python
|
|
5
|
+
Home-page: https://github.com/LuscasLeo/jararaca
|
|
5
6
|
Author: Lucas S
|
|
6
7
|
Author-email: me@luscasleo.dev
|
|
7
8
|
Requires-Python: >=3.11,<4.0
|
|
8
9
|
Classifier: Programming Language :: Python :: 3
|
|
9
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
10
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
12
12
|
Provides-Extra: docs
|
|
13
13
|
Provides-Extra: http
|
|
14
14
|
Provides-Extra: opentelemetry
|
|
@@ -9,9 +9,9 @@ Jararaca is a powerful Python microservice framework that provides a comprehensi
|
|
|
9
9
|
- 📦 **Dependency Injection**: Flexible dependency injection system with interceptors
|
|
10
10
|
- 📊 **Database Integration**: SQLAlchemy integration with async support
|
|
11
11
|
- 📡 **Message Bus**: RabbitMQ integration for event-driven architecture
|
|
12
|
-
-
|
|
12
|
+
- ⚡ **Retry Mechanism**: Robust retry system with exponential backoff for resilient operations
|
|
13
13
|
- 🔍 **Query Operations**: Advanced query capabilities with pagination and filtering
|
|
14
|
-
- ⏱️ **Scheduled Tasks**:
|
|
14
|
+
- ⏱️ **Scheduled Tasks**: Distributed cron-based task scheduling with message broker integration
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
@@ -33,9 +33,27 @@ Starts a message bus worker that processes asynchronous messages from a message
|
|
|
33
33
|
|
|
34
34
|
**Options:**
|
|
35
35
|
|
|
36
|
-
- `--broker-url`: The URL for the message broker (required)
|
|
37
|
-
- `--backend-url`: The URL for the message broker backend (required)
|
|
38
|
-
- `--handlers`: Comma-separated list of handler names to listen to (optional)
|
|
36
|
+
- `--broker-url`: The URL for the message broker (required) [env: BROKER_URL]
|
|
37
|
+
- `--backend-url`: The URL for the message broker backend (required) [env: BACKEND_URL]
|
|
38
|
+
- `--handlers`: Comma-separated list of handler names to listen to (optional) [env: HANDLERS]
|
|
39
|
+
- `--reload`: Enable auto-reload when Python files change (for development) [env: RELOAD]
|
|
40
|
+
- `--src-dir`: The source directory to watch for changes when --reload is enabled (default: "src") [env: SRC_DIR]
|
|
41
|
+
|
|
42
|
+
**Environment Variables:**
|
|
43
|
+
- `APP_PATH`: The application module path
|
|
44
|
+
- All options support environment variables as indicated above
|
|
45
|
+
|
|
46
|
+
**Example with environment variables:**
|
|
47
|
+
```bash
|
|
48
|
+
export APP_PATH="app.module:app"
|
|
49
|
+
export BROKER_URL="amqp://guest:guest@localhost:5672/?exchange=jararaca&prefetch_count=1"
|
|
50
|
+
export BACKEND_URL="redis://localhost:6379"
|
|
51
|
+
export HANDLERS="send_email,process_payment"
|
|
52
|
+
export RELOAD="true"
|
|
53
|
+
jararaca worker
|
|
54
|
+
```
|
|
55
|
+
- `--reload`: Enable auto-reload when Python files change (for development)
|
|
56
|
+
- `--src-dir`: The source directory to watch for changes when --reload is enabled (default: "src")
|
|
39
57
|
|
|
40
58
|
### `server` - HTTP Server
|
|
41
59
|
|
|
@@ -43,11 +61,39 @@ Starts a message bus worker that processes asynchronous messages from a message
|
|
|
43
61
|
jararaca server APP_PATH [OPTIONS]
|
|
44
62
|
```
|
|
45
63
|
|
|
46
|
-
#### Perfer `uvicorn` for production
|
|
47
|
-
|
|
48
64
|
Starts a FastAPI HTTP server for your microservice.
|
|
49
65
|
|
|
66
|
+
**Options:**
|
|
67
|
+
|
|
68
|
+
- `--host`: Host to bind the server (default: "0.0.0.0") [env: HOST]
|
|
69
|
+
- `--port`: Port to bind the server (default: 8000) [env: PORT]
|
|
70
|
+
|
|
71
|
+
**Environment Variables:**
|
|
72
|
+
- `APP_PATH`: The application module path
|
|
73
|
+
- `HOST`: Host to bind the server
|
|
74
|
+
- `PORT`: Port to bind the server
|
|
75
|
+
|
|
76
|
+
**Example with environment variables:**
|
|
77
|
+
```bash
|
|
78
|
+
export APP_PATH="app.module:app"
|
|
79
|
+
export HOST="127.0.0.1"
|
|
80
|
+
export PORT="8080"
|
|
81
|
+
jararaca server
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### Alternative: Using `uvicorn` directly
|
|
85
|
+
|
|
86
|
+
For production environments, you can create an ASGI application and run it with uvicorn:
|
|
87
|
+
|
|
50
88
|
```python
|
|
89
|
+
from fastapi import FastAPI
|
|
90
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
91
|
+
from fastapi.types import Lifespan
|
|
92
|
+
|
|
93
|
+
from jararaca.presentation.http_microservice import HttpMicroservice
|
|
94
|
+
from jararaca.presentation.server import create_http_server
|
|
95
|
+
|
|
96
|
+
|
|
51
97
|
def fastapi_factory(lifespan: Lifespan[FastAPI]) -> FastAPI:
|
|
52
98
|
app = FastAPI(
|
|
53
99
|
lifespan=lifespan,
|
|
@@ -78,26 +124,37 @@ Then run the server with:
|
|
|
78
124
|
uvicorn app_module:asgi_app
|
|
79
125
|
```
|
|
80
126
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
**Options:**
|
|
84
|
-
|
|
85
|
-
- `--host`: Host to bind the server (default: "0.0.0.0")
|
|
86
|
-
- `--port`: Port to bind the server (default: 8000)
|
|
87
|
-
|
|
88
|
-
### `scheduler` - Task Scheduler
|
|
127
|
+
### `beat` - Task Scheduler
|
|
89
128
|
|
|
90
129
|
```bash
|
|
91
|
-
jararaca
|
|
130
|
+
jararaca beat APP_PATH [OPTIONS]
|
|
92
131
|
```
|
|
93
132
|
|
|
94
133
|
Runs scheduled tasks defined in your application using cron expressions.
|
|
95
134
|
|
|
96
135
|
**Options:**
|
|
97
136
|
|
|
98
|
-
- `--interval`: Polling interval in seconds (default: 1)
|
|
137
|
+
- `--interval`: Polling interval in seconds (default: 1) [env: INTERVAL]
|
|
138
|
+
- `--broker-url`: The URL for the message broker (required) [env: BROKER_URL]
|
|
139
|
+
- `--backend-url`: The URL for the message broker backend (required) [env: BACKEND_URL]
|
|
140
|
+
- `--actions`: Comma-separated list of action names to run (optional) [env: ACTIONS]
|
|
141
|
+
- `--reload`: Enable auto-reload when Python files change (for development) [env: RELOAD]
|
|
142
|
+
- `--src-dir`: The source directory to watch for changes when --reload is enabled (default: "src") [env: SRC_DIR]
|
|
99
143
|
|
|
100
|
-
|
|
144
|
+
**Environment Variables:**
|
|
145
|
+
- `APP_PATH`: The application module path
|
|
146
|
+
- All options support environment variables as indicated above
|
|
147
|
+
|
|
148
|
+
**Example with environment variables:**
|
|
149
|
+
```bash
|
|
150
|
+
export APP_PATH="app.module:app"
|
|
151
|
+
export INTERVAL="5"
|
|
152
|
+
export BROKER_URL="amqp://guest:guest@localhost:5672/?exchange=jararaca&prefetch_count=1"
|
|
153
|
+
export BACKEND_URL="redis://localhost:6379"
|
|
154
|
+
export ACTIONS="send_emails,process_payments"
|
|
155
|
+
export RELOAD="true"
|
|
156
|
+
jararaca beat
|
|
157
|
+
```
|
|
101
158
|
|
|
102
159
|
```bash
|
|
103
160
|
jararaca scheduler_v2 APP_PATH [OPTIONS]
|
|
@@ -121,8 +178,14 @@ Generates TypeScript interfaces from your Python models to ensure type safety be
|
|
|
121
178
|
|
|
122
179
|
**Options:**
|
|
123
180
|
|
|
124
|
-
- `--watch`: Watch for file changes and regenerate TypeScript interfaces automatically
|
|
125
|
-
- `--src-dir`: Source directory to watch for changes (default: "src")
|
|
181
|
+
- `--watch`: Watch for file changes and regenerate TypeScript interfaces automatically [env: WATCH]
|
|
182
|
+
- `--src-dir`: Source directory to watch for changes (default: "src") [env: SRC_DIR]
|
|
183
|
+
- `--stdout`: Print generated interfaces to stdout instead of writing to a file [env: STDOUT]
|
|
184
|
+
- `--post-process`: Command to run after generating the interfaces, {file} will be replaced with the output file path [env: POST_PROCESS]
|
|
185
|
+
|
|
186
|
+
**Environment Variables:**
|
|
187
|
+
- `APP_PATH`: The application module path
|
|
188
|
+
- All options support environment variables as indicated above
|
|
126
189
|
|
|
127
190
|
**Example with watch mode:**
|
|
128
191
|
|
|
@@ -132,6 +195,15 @@ jararaca gen-tsi app.module:app interfaces.ts --watch
|
|
|
132
195
|
|
|
133
196
|
This will generate the TypeScript interfaces initially and then watch for any changes to Python files in the src directory, automatically regenerating the interfaces when changes are detected. You can stop watching with Ctrl+C.
|
|
134
197
|
|
|
198
|
+
**Example with environment variables:**
|
|
199
|
+
```bash
|
|
200
|
+
export APP_PATH="app.module:app"
|
|
201
|
+
export FILE_PATH="interfaces.ts"
|
|
202
|
+
export WATCH="true"
|
|
203
|
+
export SRC_DIR="src"
|
|
204
|
+
jararaca gen-tsi
|
|
205
|
+
```
|
|
206
|
+
|
|
135
207
|
**Note:** To use the watch feature, you need to install the watchdog package:
|
|
136
208
|
|
|
137
209
|
```bash
|
|
@@ -152,6 +224,57 @@ jararaca gen-entity ENTITY_NAME FILE_PATH
|
|
|
152
224
|
|
|
153
225
|
Generates a new entity file template with proper naming conventions in different formats (snake_case, PascalCase, kebab-case).
|
|
154
226
|
|
|
227
|
+
**Environment Variables:**
|
|
228
|
+
- `ENTITY_NAME`: The name of the entity to generate
|
|
229
|
+
- `FILE_PATH`: The path where the entity file should be created
|
|
230
|
+
|
|
231
|
+
**Example:**
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# Using command line arguments
|
|
235
|
+
jararaca gen-entity User user.py
|
|
236
|
+
|
|
237
|
+
# Using environment variables
|
|
238
|
+
export ENTITY_NAME="User"
|
|
239
|
+
export FILE_PATH="user.py"
|
|
240
|
+
jararaca gen-entity
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### `declare` - Declare Message Infrastructure
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
jararaca declare APP_PATH [OPTIONS]
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Declares RabbitMQ infrastructure (exchanges and queues) for message handlers and schedulers without starting the actual consumption processes.
|
|
250
|
+
|
|
251
|
+
**Options:**
|
|
252
|
+
- `--broker-url`: Broker URL (e.g., amqp://guest:guest@localhost/) [env: BROKER_URL]
|
|
253
|
+
- `-i, --interactive-mode`: Enable interactive mode for queue declaration [env: INTERACTIVE_MODE]
|
|
254
|
+
- `-f, --force`: Force recreation by deleting existing exchanges and queues [env: FORCE]
|
|
255
|
+
|
|
256
|
+
**Environment Variables:**
|
|
257
|
+
- `APP_PATH`: The application module path
|
|
258
|
+
- `BROKER_URL`: The broker URL
|
|
259
|
+
- `INTERACTIVE_MODE`: Enable interactive mode
|
|
260
|
+
- `FORCE`: Force recreation of infrastructure
|
|
261
|
+
|
|
262
|
+
**Examples:**
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
# Declare infrastructure
|
|
266
|
+
jararaca declare myapp:app --broker-url amqp://guest:guest@localhost/
|
|
267
|
+
|
|
268
|
+
# Force recreation of queues and exchanges
|
|
269
|
+
jararaca declare myapp:app --broker-url amqp://guest:guest@localhost/ --force
|
|
270
|
+
|
|
271
|
+
# Using environment variables
|
|
272
|
+
export APP_PATH="myapp:app"
|
|
273
|
+
export BROKER_URL="amqp://guest:guest@localhost/"
|
|
274
|
+
export FORCE="true"
|
|
275
|
+
jararaca declare
|
|
276
|
+
```
|
|
277
|
+
|
|
155
278
|
## Quick Start
|
|
156
279
|
|
|
157
280
|
Here's a basic example of how to create a microservice with Jararaca:
|
|
@@ -455,12 +455,30 @@ jararaca worker APP_PATH [OPTIONS]
|
|
|
455
455
|
|
|
456
456
|
Options:
|
|
457
457
|
|
|
458
|
-
- `--url`:
|
|
459
|
-
- `--
|
|
460
|
-
- `--
|
|
461
|
-
- `--
|
|
462
|
-
- `--
|
|
463
|
-
|
|
458
|
+
- `--broker-url`: The URL for the message broker (required) [env: BROKER_URL]
|
|
459
|
+
- `--backend-url`: The URL for the message broker backend (required) [env: BACKEND_URL]
|
|
460
|
+
- `--handlers`: Comma-separated list of handler names to listen to (optional) [env: HANDLERS]
|
|
461
|
+
- `--reload`: Enable auto-reload when Python files change (for development) [env: RELOAD]
|
|
462
|
+
- `--src-dir`: The source directory to watch for changes when --reload is enabled (default: "src") [env: SRC_DIR]
|
|
463
|
+
|
|
464
|
+
Examples:
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
# Standard worker execution
|
|
468
|
+
jararaca worker myapp.main:app --broker-url "amqp://guest:guest@localhost:5672/?exchange=jararaca" --backend-url "redis://localhost:6379"
|
|
469
|
+
|
|
470
|
+
# With auto-reload for development
|
|
471
|
+
jararaca worker myapp.main:app --broker-url "amqp://guest:guest@localhost:5672/?exchange=jararaca" --backend-url "redis://localhost:6379" --reload
|
|
472
|
+
|
|
473
|
+
# Using environment variables
|
|
474
|
+
export APP_PATH="myapp.main:app"
|
|
475
|
+
export BROKER_URL="amqp://guest:guest@localhost:5672/?exchange=jararaca"
|
|
476
|
+
export BACKEND_URL="redis://localhost:6379"
|
|
477
|
+
export RELOAD="true"
|
|
478
|
+
export SRC_DIR="src"
|
|
479
|
+
export RELOAD="true"
|
|
480
|
+
jararaca worker
|
|
481
|
+
```
|
|
464
482
|
|
|
465
483
|
## Conclusion
|
|
466
484
|
|
|
@@ -12,22 +12,16 @@ The Jararaca scheduler allows you to:
|
|
|
12
12
|
- Distribute scheduled tasks across multiple instances
|
|
13
13
|
- Handle delayed message execution
|
|
14
14
|
|
|
15
|
-
The scheduler
|
|
16
|
-
1. **Basic Scheduler** - Simple scheduler for local execution
|
|
17
|
-
2. **Enhanced Scheduler (V2)** - Distributed scheduler with improved backend support and message broker integration
|
|
15
|
+
The scheduler is implemented through the BeatWorker which provides distributed task scheduling via a message broker:
|
|
18
16
|
|
|
19
17
|
```mermaid
|
|
20
18
|
graph TD
|
|
21
|
-
A[Microservice] --> B[
|
|
22
|
-
B --> C[
|
|
23
|
-
B --> D[
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
D -->
|
|
27
|
-
F --> G[Workers]
|
|
28
|
-
D --> H[Backend Store]
|
|
29
|
-
H --> I[Last Execution Time]
|
|
30
|
-
H --> J[Delayed Messages]
|
|
19
|
+
A[Microservice] --> B[BeatWorker]
|
|
20
|
+
B --> C[Message Broker]
|
|
21
|
+
B --> D[Backend Store]
|
|
22
|
+
C --> E[Message Processing]
|
|
23
|
+
D --> F[Last Execution Time]
|
|
24
|
+
D --> G[Delayed Messages]
|
|
31
25
|
```
|
|
32
26
|
|
|
33
27
|
## Using the Scheduler
|
|
@@ -73,31 +67,57 @@ Jararaca uses standard cron expressions for scheduling. Here are some examples:
|
|
|
73
67
|
- `0 0 * * 0` - Run at midnight every Sunday
|
|
74
68
|
- `0 0 1 * *` - Run at midnight on the first day of every month
|
|
75
69
|
|
|
76
|
-
##
|
|
70
|
+
## Using the BeatWorker Scheduler
|
|
77
71
|
|
|
78
|
-
The
|
|
72
|
+
The BeatWorker scheduler provides distributed task execution through a message broker:
|
|
79
73
|
|
|
80
74
|
```python
|
|
81
75
|
from jararaca import Microservice, ScheduledAction
|
|
76
|
+
from jararaca.scheduler.beat_worker import BeatWorker
|
|
82
77
|
|
|
83
78
|
app = Microservice(
|
|
84
79
|
# Your microservice configuration
|
|
85
80
|
)
|
|
86
81
|
|
|
87
82
|
# Run the scheduler
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
83
|
+
beat_worker = BeatWorker(
|
|
84
|
+
app=app,
|
|
85
|
+
interval=1,
|
|
86
|
+
backend_url="redis://localhost:6379",
|
|
87
|
+
broker_url="amqp://guest:guest@localhost:5672/?exchange=jararaca",
|
|
88
|
+
scheduled_action_names=None # Optional set of action names to run
|
|
89
|
+
)
|
|
90
|
+
beat_worker.run()
|
|
92
91
|
```
|
|
93
92
|
|
|
94
|
-
|
|
93
|
+
You can also use the CLI command to run the scheduler:
|
|
95
94
|
|
|
96
|
-
|
|
95
|
+
```bash
|
|
96
|
+
# Standard beat scheduler execution
|
|
97
|
+
jararaca beat app_module:app --interval 1 --broker-url "amqp://guest:guest@localhost:5672/?exchange=jararaca" --backend-url "redis://localhost:6379"
|
|
98
|
+
|
|
99
|
+
# With auto-reload for development (automatically restarts when Python files change)
|
|
100
|
+
jararaca beat app_module:app --interval 1 --broker-url "amqp://guest:guest@localhost:5672/?exchange=jararaca" --backend-url "redis://localhost:6379" --reload
|
|
101
|
+
|
|
102
|
+
# Using environment variables
|
|
103
|
+
export APP_PATH="app_module:app"
|
|
104
|
+
export INTERVAL="1"
|
|
105
|
+
export BROKER_URL="amqp://guest:guest@localhost:5672/?exchange=jararaca"
|
|
106
|
+
export BACKEND_URL="redis://localhost:6379"
|
|
107
|
+
export RELOAD="true"
|
|
108
|
+
export SRC_DIR="src"
|
|
109
|
+
jararaca beat
|
|
110
|
+
```
|
|
97
111
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
112
|
+
All command options support environment variables:
|
|
113
|
+
- `APP_PATH`: The application module path [required]
|
|
114
|
+
- `INTERVAL`: Polling interval in seconds [default: 1]
|
|
115
|
+
- `BROKER_URL`: The URL for the message broker [required]
|
|
116
|
+
- `BACKEND_URL`: The URL for the message broker backend [required]
|
|
117
|
+
- `ACTIONS`: Comma-separated list of action names to run [optional]
|
|
118
|
+
- `RELOAD`: Enable auto-reload when Python files change [optional]
|
|
119
|
+
- `SRC_DIR`: The source directory to watch for changes when using reload [default: "src"]
|
|
120
|
+
```
|
|
101
121
|
|
|
102
122
|
app = Microservice(
|
|
103
123
|
# Your microservice configuration
|
|
@@ -57,24 +57,38 @@ class WebSocketMessage(WebSocketMessageBase):
|
|
|
57
57
|
|
|
58
58
|
### 3. WebSocketConnectionManager
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
A Protocol that defines the interface for managing WebSocket connections:
|
|
61
61
|
|
|
62
|
+
```python
|
|
63
|
+
class WebSocketConnectionManager(Protocol):
|
|
64
|
+
async def broadcast(self, message: bytes) -> None: ...
|
|
65
|
+
async def send(self, rooms: list[str], message: WebSocketMessageBase) -> None: ...
|
|
66
|
+
async def join(self, rooms: list[str], websocket: WebSocket) -> None: ...
|
|
67
|
+
async def add_websocket(self, websocket: WebSocket) -> None: ...
|
|
68
|
+
async def remove_websocket(self, websocket: WebSocket) -> None: ...
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The WebSocketConnectionManager:
|
|
62
72
|
- Maintains a registry of active WebSocket connections
|
|
63
73
|
- Groups connections into named rooms
|
|
64
74
|
- Provides methods for broadcasting and sending targeted messages
|
|
65
75
|
|
|
66
|
-
### 4.
|
|
76
|
+
### 4. Context-based WebSocket Access
|
|
67
77
|
|
|
68
|
-
|
|
78
|
+
Jararaca uses context variables to provide access to WebSocket functionality anywhere in your application:
|
|
69
79
|
|
|
70
80
|
```python
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
77
|
-
|
|
81
|
+
from jararaca.presentation.websocket.context import use_ws_manager, use_ws_message_sender
|
|
82
|
+
|
|
83
|
+
# Send a message to specific rooms
|
|
84
|
+
async def notify_users(message_data: dict, room_id: str):
|
|
85
|
+
message = UserNotificationMessage(**message_data)
|
|
86
|
+
await use_ws_message_sender().send([room_id], message)
|
|
87
|
+
|
|
88
|
+
# Or directly from a WebSocketMessage instance
|
|
89
|
+
async def send_update(update_data: dict, room_id: str):
|
|
90
|
+
message = SystemUpdateMessage(**update_data)
|
|
91
|
+
await message.send(room_id)
|
|
78
92
|
```
|
|
79
93
|
|
|
80
94
|
### 5. RedisWebSocketConnectionBackend
|
|
@@ -7,7 +7,7 @@ import sys
|
|
|
7
7
|
import time
|
|
8
8
|
from codecs import StreamWriter
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Any
|
|
10
|
+
from typing import Any, Callable
|
|
11
11
|
from urllib.parse import parse_qs, urlparse
|
|
12
12
|
|
|
13
13
|
import aio_pika
|
|
@@ -421,25 +421,42 @@ def cli() -> None:
|
|
|
421
421
|
@click.option(
|
|
422
422
|
"--handlers",
|
|
423
423
|
type=str,
|
|
424
|
+
envvar="HANDLERS",
|
|
424
425
|
help="Comma-separated list of handler names to listen to. If not specified, all handlers will be used.",
|
|
425
426
|
)
|
|
427
|
+
@click.option(
|
|
428
|
+
"--reload",
|
|
429
|
+
is_flag=True,
|
|
430
|
+
envvar="RELOAD",
|
|
431
|
+
help="Enable auto-reload when Python files change.",
|
|
432
|
+
)
|
|
433
|
+
@click.option(
|
|
434
|
+
"--src-dir",
|
|
435
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
|
436
|
+
default="src",
|
|
437
|
+
envvar="SRC_DIR",
|
|
438
|
+
help="The source directory to watch for changes when --reload is enabled.",
|
|
439
|
+
)
|
|
426
440
|
def worker(
|
|
427
|
-
app_path: str,
|
|
441
|
+
app_path: str,
|
|
442
|
+
broker_url: str,
|
|
443
|
+
backend_url: str,
|
|
444
|
+
handlers: str | None,
|
|
445
|
+
reload: bool,
|
|
446
|
+
src_dir: str,
|
|
428
447
|
) -> None:
|
|
429
448
|
"""Start a message bus worker that processes asynchronous messages from a message queue."""
|
|
430
|
-
app = find_microservice_by_module_path(app_path)
|
|
431
|
-
|
|
432
|
-
# Parse handler names if provided
|
|
433
|
-
handler_names: set[str] | None = None
|
|
434
|
-
if handlers:
|
|
435
|
-
handler_names = {name.strip() for name in handlers.split(",") if name.strip()}
|
|
436
449
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
450
|
+
if reload:
|
|
451
|
+
process_args = {
|
|
452
|
+
"app_path": app_path,
|
|
453
|
+
"broker_url": broker_url,
|
|
454
|
+
"backend_url": backend_url,
|
|
455
|
+
"handlers": handlers,
|
|
456
|
+
}
|
|
457
|
+
run_with_reload_watcher(process_args, run_worker_process, src_dir)
|
|
458
|
+
else:
|
|
459
|
+
run_worker_process(app_path, broker_url, backend_url, handlers)
|
|
443
460
|
|
|
444
461
|
|
|
445
462
|
@cli.command()
|
|
@@ -485,51 +502,68 @@ def server(app_path: str, host: str, port: int) -> None:
|
|
|
485
502
|
@click.argument(
|
|
486
503
|
"app_path",
|
|
487
504
|
type=str,
|
|
505
|
+
envvar="APP_PATH",
|
|
488
506
|
)
|
|
489
507
|
@click.option(
|
|
490
508
|
"--interval",
|
|
491
509
|
type=int,
|
|
492
510
|
default=1,
|
|
493
511
|
required=True,
|
|
512
|
+
envvar="INTERVAL",
|
|
494
513
|
)
|
|
495
514
|
@click.option(
|
|
496
515
|
"--broker-url",
|
|
497
516
|
type=str,
|
|
498
517
|
required=True,
|
|
518
|
+
envvar="BROKER_URL",
|
|
499
519
|
)
|
|
500
520
|
@click.option(
|
|
501
521
|
"--backend-url",
|
|
502
522
|
type=str,
|
|
503
523
|
required=True,
|
|
524
|
+
envvar="BACKEND_URL",
|
|
504
525
|
)
|
|
505
526
|
@click.option(
|
|
506
527
|
"--actions",
|
|
507
528
|
type=str,
|
|
529
|
+
envvar="ACTIONS",
|
|
508
530
|
help="Comma-separated list of action names to run (only run actions with these names)",
|
|
509
531
|
)
|
|
532
|
+
@click.option(
|
|
533
|
+
"--reload",
|
|
534
|
+
is_flag=True,
|
|
535
|
+
envvar="RELOAD",
|
|
536
|
+
help="Enable auto-reload when Python files change.",
|
|
537
|
+
)
|
|
538
|
+
@click.option(
|
|
539
|
+
"--src-dir",
|
|
540
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
|
541
|
+
default="src",
|
|
542
|
+
envvar="SRC_DIR",
|
|
543
|
+
help="The source directory to watch for changes when --reload is enabled.",
|
|
544
|
+
)
|
|
510
545
|
def beat(
|
|
511
546
|
interval: int,
|
|
512
547
|
broker_url: str,
|
|
513
548
|
backend_url: str,
|
|
514
549
|
app_path: str,
|
|
515
550
|
actions: str | None = None,
|
|
551
|
+
reload: bool = False,
|
|
552
|
+
src_dir: str = "src",
|
|
516
553
|
) -> None:
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
broker_url
|
|
530
|
-
scheduled_action_names=scheduler_names,
|
|
531
|
-
)
|
|
532
|
-
beat_worker.run()
|
|
554
|
+
"""Start a scheduler that dispatches scheduled actions to workers."""
|
|
555
|
+
|
|
556
|
+
if reload:
|
|
557
|
+
process_args = {
|
|
558
|
+
"app_path": app_path,
|
|
559
|
+
"interval": interval,
|
|
560
|
+
"broker_url": broker_url,
|
|
561
|
+
"backend_url": backend_url,
|
|
562
|
+
"actions": actions,
|
|
563
|
+
}
|
|
564
|
+
run_with_reload_watcher(process_args, run_beat_process, src_dir)
|
|
565
|
+
else:
|
|
566
|
+
run_beat_process(app_path, interval, broker_url, backend_url, actions)
|
|
533
567
|
|
|
534
568
|
|
|
535
569
|
def generate_interfaces(
|
|
@@ -588,29 +622,35 @@ def generate_interfaces(
|
|
|
588
622
|
@click.argument(
|
|
589
623
|
"app_path",
|
|
590
624
|
type=str,
|
|
625
|
+
envvar="APP_PATH",
|
|
591
626
|
)
|
|
592
627
|
@click.argument(
|
|
593
628
|
"file_path",
|
|
594
629
|
type=click.Path(file_okay=True, dir_okay=False),
|
|
595
630
|
required=False,
|
|
631
|
+
envvar="FILE_PATH",
|
|
596
632
|
)
|
|
597
633
|
@click.option(
|
|
598
634
|
"--watch",
|
|
599
635
|
is_flag=True,
|
|
636
|
+
envvar="WATCH",
|
|
600
637
|
)
|
|
601
638
|
@click.option(
|
|
602
639
|
"--src-dir",
|
|
603
640
|
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
|
604
641
|
default="src",
|
|
642
|
+
envvar="SRC_DIR",
|
|
605
643
|
)
|
|
606
644
|
@click.option(
|
|
607
645
|
"--stdout",
|
|
608
646
|
is_flag=True,
|
|
647
|
+
envvar="STDOUT",
|
|
609
648
|
help="Print generated interfaces to stdout instead of writing to a file",
|
|
610
649
|
)
|
|
611
650
|
@click.option(
|
|
612
651
|
"--post-process",
|
|
613
652
|
type=str,
|
|
653
|
+
envvar="POST_PROCESS",
|
|
614
654
|
help="Command to run after generating the interfaces, {file} will be replaced with the output file path",
|
|
615
655
|
)
|
|
616
656
|
def gen_tsi(
|
|
@@ -730,10 +770,11 @@ def camel_case_to_pascal_case(name: str) -> str:
|
|
|
730
770
|
|
|
731
771
|
|
|
732
772
|
@cli.command()
|
|
733
|
-
@click.argument("entity_name", type=click.STRING)
|
|
773
|
+
@click.argument("entity_name", type=click.STRING, envvar="ENTITY_NAME")
|
|
734
774
|
@click.argument(
|
|
735
775
|
"file_path",
|
|
736
776
|
type=click.File("w"),
|
|
777
|
+
envvar="FILE_PATH",
|
|
737
778
|
)
|
|
738
779
|
def gen_entity(entity_name: str, file_path: StreamWriter) -> None:
|
|
739
780
|
|
|
@@ -769,6 +810,7 @@ def gen_entity(entity_name: str, file_path: StreamWriter) -> None:
|
|
|
769
810
|
"--interactive-mode",
|
|
770
811
|
is_flag=True,
|
|
771
812
|
default=False,
|
|
813
|
+
envvar="INTERACTIVE_MODE",
|
|
772
814
|
help="Enable interactive mode for queue declaration (confirm before deleting existing queues)",
|
|
773
815
|
)
|
|
774
816
|
@click.option(
|
|
@@ -776,6 +818,7 @@ def gen_entity(entity_name: str, file_path: StreamWriter) -> None:
|
|
|
776
818
|
"--force",
|
|
777
819
|
is_flag=True,
|
|
778
820
|
default=False,
|
|
821
|
+
envvar="FORCE",
|
|
779
822
|
help="Force recreation by deleting existing exchanges and queues before declaring them",
|
|
780
823
|
)
|
|
781
824
|
def declare(
|
|
@@ -835,3 +878,143 @@ def declare(
|
|
|
835
878
|
raise
|
|
836
879
|
|
|
837
880
|
asyncio.run(run_declarations())
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
def run_worker_process(
|
|
884
|
+
app_path: str, broker_url: str, backend_url: str, handlers: str | None
|
|
885
|
+
) -> None:
|
|
886
|
+
"""Run a worker process with the given parameters."""
|
|
887
|
+
app = find_microservice_by_module_path(app_path)
|
|
888
|
+
|
|
889
|
+
# Parse handler names if provided
|
|
890
|
+
handler_names: set[str] | None = None
|
|
891
|
+
if handlers:
|
|
892
|
+
handler_names = {name.strip() for name in handlers.split(",") if name.strip()}
|
|
893
|
+
|
|
894
|
+
click.echo(f"Starting worker for {app_path}...")
|
|
895
|
+
worker_mod.MessageBusWorker(
|
|
896
|
+
app=app,
|
|
897
|
+
broker_url=broker_url,
|
|
898
|
+
backend_url=backend_url,
|
|
899
|
+
handler_names=handler_names,
|
|
900
|
+
).start_sync()
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
def run_beat_process(
|
|
904
|
+
app_path: str, interval: int, broker_url: str, backend_url: str, actions: str | None
|
|
905
|
+
) -> None:
|
|
906
|
+
"""Run a beat scheduler process with the given parameters."""
|
|
907
|
+
app = find_microservice_by_module_path(app_path)
|
|
908
|
+
|
|
909
|
+
# Parse scheduler names if provided
|
|
910
|
+
scheduler_names: set[str] | None = None
|
|
911
|
+
if actions:
|
|
912
|
+
scheduler_names = {name.strip() for name in actions.split(",") if name.strip()}
|
|
913
|
+
|
|
914
|
+
click.echo(f"Starting beat scheduler for {app_path}...")
|
|
915
|
+
beat_worker = BeatWorker(
|
|
916
|
+
app=app,
|
|
917
|
+
interval=interval,
|
|
918
|
+
backend_url=backend_url,
|
|
919
|
+
broker_url=broker_url,
|
|
920
|
+
scheduled_action_names=scheduler_names,
|
|
921
|
+
)
|
|
922
|
+
beat_worker.run()
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
def run_with_reload_watcher(
|
|
926
|
+
process_args: dict[str, Any],
|
|
927
|
+
process_target: Callable[..., Any],
|
|
928
|
+
src_dir: str = "src",
|
|
929
|
+
) -> None:
|
|
930
|
+
"""
|
|
931
|
+
Run a process with a file watcher that will restart it when Python files change.
|
|
932
|
+
|
|
933
|
+
Args:
|
|
934
|
+
process_args: Arguments to pass to the process function
|
|
935
|
+
process_target: The function to run as the process
|
|
936
|
+
src_dir: The directory to watch for changes
|
|
937
|
+
"""
|
|
938
|
+
try:
|
|
939
|
+
from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
|
940
|
+
from watchdog.observers import Observer
|
|
941
|
+
except ImportError:
|
|
942
|
+
click.echo(
|
|
943
|
+
"Watchdog is required for reload mode. Install it with: pip install watchdog",
|
|
944
|
+
file=sys.stderr,
|
|
945
|
+
)
|
|
946
|
+
return
|
|
947
|
+
|
|
948
|
+
# Run the initial process
|
|
949
|
+
process = multiprocessing.get_context("spawn").Process(
|
|
950
|
+
target=process_target,
|
|
951
|
+
kwargs=process_args,
|
|
952
|
+
daemon=False, # Non-daemon to ensure it completes properly
|
|
953
|
+
)
|
|
954
|
+
process.start() # Set up file system event handler
|
|
955
|
+
|
|
956
|
+
class PyFileChangeHandler(FileSystemEventHandler):
|
|
957
|
+
def __init__(self) -> None:
|
|
958
|
+
self.last_modified_time = time.time()
|
|
959
|
+
self.debounce_seconds = 1.0 # Debounce to avoid multiple restarts
|
|
960
|
+
self.active_process = process
|
|
961
|
+
|
|
962
|
+
def on_modified(self, event: FileSystemEvent) -> None:
|
|
963
|
+
src_path = (
|
|
964
|
+
event.src_path
|
|
965
|
+
if isinstance(event.src_path, str)
|
|
966
|
+
else str(event.src_path)
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
# Ignore non-Python files and directories
|
|
970
|
+
if event.is_directory or not src_path.endswith(".py"):
|
|
971
|
+
return
|
|
972
|
+
|
|
973
|
+
# Debounce to avoid multiple restarts
|
|
974
|
+
current_time = time.time()
|
|
975
|
+
if current_time - self.last_modified_time < self.debounce_seconds:
|
|
976
|
+
return
|
|
977
|
+
self.last_modified_time = current_time
|
|
978
|
+
|
|
979
|
+
click.echo(f"Detected change in {src_path}")
|
|
980
|
+
click.echo("Restarting process...")
|
|
981
|
+
|
|
982
|
+
# Terminate the current process
|
|
983
|
+
if self.active_process and self.active_process.is_alive():
|
|
984
|
+
self.active_process.terminate()
|
|
985
|
+
self.active_process.join(timeout=5)
|
|
986
|
+
|
|
987
|
+
# If process doesn't terminate, kill it
|
|
988
|
+
if self.active_process.is_alive():
|
|
989
|
+
click.echo("Process did not terminate gracefully, killing it")
|
|
990
|
+
self.active_process.kill()
|
|
991
|
+
self.active_process.join()
|
|
992
|
+
|
|
993
|
+
# Create a new process
|
|
994
|
+
self.active_process = multiprocessing.get_context("spawn").Process(
|
|
995
|
+
target=process_target,
|
|
996
|
+
kwargs=process_args,
|
|
997
|
+
daemon=False,
|
|
998
|
+
)
|
|
999
|
+
self.active_process.start()
|
|
1000
|
+
|
|
1001
|
+
# Set up observer
|
|
1002
|
+
observer = Observer()
|
|
1003
|
+
observer.schedule(PyFileChangeHandler(), src_dir, recursive=True) # type: ignore[no-untyped-call]
|
|
1004
|
+
observer.start() # type: ignore[no-untyped-call]
|
|
1005
|
+
|
|
1006
|
+
click.echo(f"Watching for changes in {os.path.abspath(src_dir)}...")
|
|
1007
|
+
try:
|
|
1008
|
+
while True:
|
|
1009
|
+
time.sleep(1)
|
|
1010
|
+
except KeyboardInterrupt:
|
|
1011
|
+
observer.stop() # type: ignore[no-untyped-call]
|
|
1012
|
+
if process.is_alive():
|
|
1013
|
+
click.echo("Stopping process...")
|
|
1014
|
+
process.terminate()
|
|
1015
|
+
process.join(timeout=5)
|
|
1016
|
+
if process.is_alive():
|
|
1017
|
+
process.kill()
|
|
1018
|
+
process.join()
|
|
1019
|
+
click.echo("Reload mode stopped")
|
|
1020
|
+
observer.join()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jararaca-0.3.11a16 → jararaca-0.3.12a0}/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg
RENAMED
|
File without changes
|
{jararaca-0.3.11a16 → jararaca-0.3.12a0}/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
|
{jararaca-0.3.11a16 → jararaca-0.3.12a0}/src/jararaca/broker_backend/redis_broker_backend.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jararaca-0.3.11a16 → jararaca-0.3.12a0}/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
|
|
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
|