jararaca 0.2.37a11__py3-none-any.whl → 0.2.37a12__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.
- jararaca/cli.py +78 -4
- jararaca/messagebus/worker.py +11 -11
- jararaca-0.2.37a12.dist-info/METADATA +154 -0
- {jararaca-0.2.37a11.dist-info → jararaca-0.2.37a12.dist-info}/RECORD +7 -10
- {jararaca-0.2.37a11.dist-info → jararaca-0.2.37a12.dist-info}/WHEEL +1 -1
- README.md +0 -243
- jararaca-0.2.37a11.dist-info/LICENSE +0 -674
- jararaca-0.2.37a11.dist-info/METADATA +0 -278
- pyproject.toml +0 -77
- /LICENSE → /jararaca-0.2.37a12.dist-info/LICENSE +0 -0
- {jararaca-0.2.37a11.dist-info → jararaca-0.2.37a12.dist-info}/entry_points.txt +0 -0
jararaca/cli.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
import importlib.resources
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
3
6
|
from codecs import StreamWriter
|
|
4
7
|
from typing import Any
|
|
5
8
|
from urllib.parse import urlparse, urlunsplit
|
|
@@ -208,12 +211,83 @@ def scheduler(
|
|
|
208
211
|
"file_path",
|
|
209
212
|
type=click.File("w"),
|
|
210
213
|
)
|
|
211
|
-
|
|
212
|
-
|
|
214
|
+
@click.option(
|
|
215
|
+
"--watch",
|
|
216
|
+
is_flag=True,
|
|
217
|
+
help="Watch for file changes and regenerate TypeScript interfaces",
|
|
218
|
+
)
|
|
219
|
+
@click.option(
|
|
220
|
+
"--src-dir",
|
|
221
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
|
222
|
+
default="src",
|
|
223
|
+
help="Source directory to watch for changes (default: src)",
|
|
224
|
+
)
|
|
225
|
+
def gen_tsi(app_path: str, file_path: StreamWriter, watch: bool, src_dir: str) -> None:
|
|
226
|
+
"""Generate TypeScript interfaces from a Python microservice."""
|
|
227
|
+
|
|
228
|
+
# Generate typescript interfaces
|
|
229
|
+
def generate_interfaces() -> None:
|
|
230
|
+
try:
|
|
231
|
+
app = find_microservice_by_module_path(app_path)
|
|
232
|
+
content = write_microservice_to_typescript_interface(app)
|
|
233
|
+
|
|
234
|
+
# Save current position
|
|
235
|
+
file_path.tell()
|
|
236
|
+
|
|
237
|
+
# Reset file to beginning
|
|
238
|
+
file_path.seek(0)
|
|
239
|
+
file_path.truncate()
|
|
240
|
+
|
|
241
|
+
# Write new content
|
|
242
|
+
file_path.write(content)
|
|
243
|
+
file_path.flush()
|
|
213
244
|
|
|
214
|
-
|
|
245
|
+
print(f"Generated TypeScript interfaces at {time.strftime('%H:%M:%S')}")
|
|
246
|
+
except Exception as e:
|
|
247
|
+
print(f"Error generating TypeScript interfaces: {e}", file=sys.stderr)
|
|
215
248
|
|
|
216
|
-
|
|
249
|
+
# Initial generation
|
|
250
|
+
generate_interfaces()
|
|
251
|
+
|
|
252
|
+
# If watch mode is not enabled, exit
|
|
253
|
+
if not watch:
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
|
258
|
+
from watchdog.observers import Observer
|
|
259
|
+
except ImportError:
|
|
260
|
+
print(
|
|
261
|
+
"Watchdog is required for watch mode. Install it with: pip install watchdog",
|
|
262
|
+
file=sys.stderr,
|
|
263
|
+
)
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
# Set up file system event handler
|
|
267
|
+
class PyFileChangeHandler(FileSystemEventHandler):
|
|
268
|
+
def on_modified(self, event: FileSystemEvent) -> None:
|
|
269
|
+
src_path = (
|
|
270
|
+
event.src_path
|
|
271
|
+
if isinstance(event.src_path, str)
|
|
272
|
+
else str(event.src_path)
|
|
273
|
+
)
|
|
274
|
+
if not event.is_directory and src_path.endswith(".py"):
|
|
275
|
+
print(f"File changed: {src_path}")
|
|
276
|
+
generate_interfaces()
|
|
277
|
+
|
|
278
|
+
# Set up observer
|
|
279
|
+
observer = Observer()
|
|
280
|
+
observer.schedule(PyFileChangeHandler(), src_dir, recursive=True)
|
|
281
|
+
observer.start()
|
|
282
|
+
|
|
283
|
+
print(f"Watching for changes in {os.path.abspath(src_dir)}...")
|
|
284
|
+
try:
|
|
285
|
+
while True:
|
|
286
|
+
time.sleep(1)
|
|
287
|
+
except KeyboardInterrupt:
|
|
288
|
+
observer.stop()
|
|
289
|
+
print("Watch mode stopped")
|
|
290
|
+
observer.join()
|
|
217
291
|
|
|
218
292
|
|
|
219
293
|
def camel_case_to_snake_case(name: str) -> str:
|
jararaca/messagebus/worker.py
CHANGED
|
@@ -205,17 +205,17 @@ class MessageHandlerCallback:
|
|
|
205
205
|
self, aio_pika_message: aio_pika.abc.AbstractIncomingMessage
|
|
206
206
|
) -> None:
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
routing_key = self.queue_name
|
|
209
209
|
|
|
210
|
-
if
|
|
210
|
+
if routing_key is None:
|
|
211
211
|
logger.warning("No topic found for message")
|
|
212
212
|
await self.handle_reject_message(aio_pika_message)
|
|
213
213
|
return
|
|
214
214
|
|
|
215
|
-
handler_data = self.consumer.incoming_map.get(
|
|
215
|
+
handler_data = self.consumer.incoming_map.get(routing_key)
|
|
216
216
|
|
|
217
217
|
if handler_data is None:
|
|
218
|
-
logger.warning("No handler found for topic '%s'" %
|
|
218
|
+
logger.warning("No handler found for topic '%s'" % routing_key)
|
|
219
219
|
await self.handle_reject_message(aio_pika_message)
|
|
220
220
|
|
|
221
221
|
return
|
|
@@ -227,7 +227,7 @@ class MessageHandlerCallback:
|
|
|
227
227
|
if len(sig.parameters) != 1:
|
|
228
228
|
logger.warning(
|
|
229
229
|
"Handler for topic '%s' must have exactly one parameter which is MessageOf[T extends Message]"
|
|
230
|
-
%
|
|
230
|
+
% routing_key
|
|
231
231
|
)
|
|
232
232
|
return
|
|
233
233
|
|
|
@@ -238,14 +238,14 @@ class MessageHandlerCallback:
|
|
|
238
238
|
if param_origin is not MessageOf:
|
|
239
239
|
logger.warning(
|
|
240
240
|
"Handler for topic '%s' must have exactly one parameter of type Message"
|
|
241
|
-
%
|
|
241
|
+
% routing_key
|
|
242
242
|
)
|
|
243
243
|
return
|
|
244
244
|
|
|
245
245
|
if len(parameter.annotation.__args__) != 1:
|
|
246
246
|
logger.warning(
|
|
247
247
|
"Handler for topic '%s' must have exactly one parameter of type Message"
|
|
248
|
-
%
|
|
248
|
+
% routing_key
|
|
249
249
|
)
|
|
250
250
|
return
|
|
251
251
|
|
|
@@ -253,8 +253,8 @@ class MessageHandlerCallback:
|
|
|
253
253
|
|
|
254
254
|
if not issubclass(message_type, BaseModel):
|
|
255
255
|
logger.warning(
|
|
256
|
-
"Handler for topic '%s' must have exactly one parameter of type
|
|
257
|
-
%
|
|
256
|
+
"Handler for topic '%s' must have exactly one parameter of type MessageOf[BaseModel]"
|
|
257
|
+
% routing_key
|
|
258
258
|
)
|
|
259
259
|
return
|
|
260
260
|
|
|
@@ -266,7 +266,7 @@ class MessageHandlerCallback:
|
|
|
266
266
|
async with self.consumer.uow_context_provider(
|
|
267
267
|
MessageBusAppContext(
|
|
268
268
|
message=builded_message,
|
|
269
|
-
topic=
|
|
269
|
+
topic=routing_key,
|
|
270
270
|
)
|
|
271
271
|
):
|
|
272
272
|
ctx: AsyncContextManager[Any]
|
|
@@ -293,7 +293,7 @@ class MessageHandlerCallback:
|
|
|
293
293
|
)
|
|
294
294
|
else:
|
|
295
295
|
logger.exception(
|
|
296
|
-
f"Error processing message on topic {
|
|
296
|
+
f"Error processing message on topic {routing_key}"
|
|
297
297
|
)
|
|
298
298
|
if incoming_message_spec.requeue_on_exception:
|
|
299
299
|
await self.handle_reject_message(aio_pika_message, requeue=True)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: jararaca
|
|
3
|
+
Version: 0.2.37a12
|
|
4
|
+
Summary: A simple and fast API framework for Python
|
|
5
|
+
Author: Lucas S
|
|
6
|
+
Author-email: me@luscasleo.dev
|
|
7
|
+
Requires-Python: >=3.11,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Provides-Extra: docs
|
|
13
|
+
Provides-Extra: http
|
|
14
|
+
Provides-Extra: opentelemetry
|
|
15
|
+
Requires-Dist: aio-pika (>=9.4.3,<10.0.0)
|
|
16
|
+
Requires-Dist: croniter (>=3.0.3,<4.0.0)
|
|
17
|
+
Requires-Dist: fastapi (>=0.113.0,<0.114.0)
|
|
18
|
+
Requires-Dist: mako (>=1.3.5,<2.0.0)
|
|
19
|
+
Requires-Dist: opentelemetry-api (>=1.27.0,<2.0.0) ; extra == "opentelemetry"
|
|
20
|
+
Requires-Dist: opentelemetry-distro (>=0.49b2,<0.50) ; extra == "opentelemetry"
|
|
21
|
+
Requires-Dist: opentelemetry-exporter-otlp (>=1.27.0,<2.0.0) ; extra == "opentelemetry"
|
|
22
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http (>=1.27.0,<2.0.0) ; extra == "opentelemetry"
|
|
23
|
+
Requires-Dist: opentelemetry-sdk (>=1.27.0,<2.0.0) ; extra == "opentelemetry"
|
|
24
|
+
Requires-Dist: redis (>=5.0.8,<6.0.0)
|
|
25
|
+
Requires-Dist: sqlalchemy (>=2.0.34,<3.0.0)
|
|
26
|
+
Requires-Dist: types-croniter (>=3.0.3.20240731,<4.0.0.0)
|
|
27
|
+
Requires-Dist: types-redis (>=4.6.0.20240903,<5.0.0.0)
|
|
28
|
+
Requires-Dist: uvicorn (>=0.30.6,<0.31.0)
|
|
29
|
+
Requires-Dist: uvloop (>=0.20.0,<0.21.0)
|
|
30
|
+
Requires-Dist: websockets (>=13.0.1,<14.0.0)
|
|
31
|
+
Project-URL: Repository, https://github.com/LuscasLeo/jararaca
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
<img src="https://raw.githubusercontent.com/LuscasLeo/jararaca/main/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg" alt="Jararaca Logo" width="250" float="right">
|
|
35
|
+
|
|
36
|
+
# Jararaca Microservice Framework
|
|
37
|
+
|
|
38
|
+
## Overview
|
|
39
|
+
|
|
40
|
+
Jararaca is an async-first microservice framework designed to simplify the development of distributed systems. It provides a comprehensive set of tools for building robust, scalable, and maintainable microservices with a focus on developer experience and type safety.
|
|
41
|
+
|
|
42
|
+
## Key Features
|
|
43
|
+
|
|
44
|
+
### REST API Development
|
|
45
|
+
- Easy-to-use interfaces for building REST APIs
|
|
46
|
+
- Automatic request/response validation
|
|
47
|
+
- Type-safe endpoints with FastAPI integration
|
|
48
|
+
- Automatic OpenAPI documentation generation
|
|
49
|
+
|
|
50
|
+
### Message Bus Integration
|
|
51
|
+
- Topic-based message bus for event-driven architecture
|
|
52
|
+
- Support for both worker and publisher patterns
|
|
53
|
+
- Built-in message serialization and deserialization
|
|
54
|
+
- Easy integration with AIO Pika for RabbitMQ
|
|
55
|
+
|
|
56
|
+
### Distributed WebSocket
|
|
57
|
+
- Room-based WebSocket communication
|
|
58
|
+
- Distributed broadcasting across multiple backend instances
|
|
59
|
+
- Automatic message synchronization between instances
|
|
60
|
+
- Built-in connection management and room handling
|
|
61
|
+
|
|
62
|
+
### Task Scheduling
|
|
63
|
+
- Cron-based task scheduling
|
|
64
|
+
- Support for overlapping and non-overlapping tasks
|
|
65
|
+
- Distributed task execution
|
|
66
|
+
- Easy integration with message bus for task distribution
|
|
67
|
+
|
|
68
|
+
### TypeScript Integration
|
|
69
|
+
- Automatic TypeScript interface generation
|
|
70
|
+
- Command-line tool for generating TypeScript types
|
|
71
|
+
- Support for REST endpoints, WebSocket events, and message bus payloads
|
|
72
|
+
- Type-safe frontend-backend communication
|
|
73
|
+
|
|
74
|
+
### Hexagonal Architecture
|
|
75
|
+
- Clear separation of concerns
|
|
76
|
+
- Business logic isolation from infrastructure
|
|
77
|
+
- Easy testing and maintainability
|
|
78
|
+
- Dependency injection for flexible component management
|
|
79
|
+
|
|
80
|
+
### Observability
|
|
81
|
+
- Built-in OpenTelemetry integration
|
|
82
|
+
- Distributed tracing support
|
|
83
|
+
- Logging and metrics collection
|
|
84
|
+
- Performance monitoring capabilities
|
|
85
|
+
|
|
86
|
+
## Quick Start
|
|
87
|
+
|
|
88
|
+
### Installation
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install jararaca
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Basic Usage
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from jararaca import Microservice, create_http_server
|
|
98
|
+
from jararaca.presentation.http_microservice import HttpMicroservice
|
|
99
|
+
|
|
100
|
+
# Define your microservice
|
|
101
|
+
app = Microservice(
|
|
102
|
+
providers=[
|
|
103
|
+
# Add your providers here
|
|
104
|
+
],
|
|
105
|
+
controllers=[
|
|
106
|
+
# Add your controllers here
|
|
107
|
+
],
|
|
108
|
+
interceptors=[
|
|
109
|
+
# Add your interceptors here
|
|
110
|
+
],
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Create HTTP server
|
|
114
|
+
http_app = HttpMicroservice(app)
|
|
115
|
+
web_app = create_http_server(app)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Running the Service
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Run as HTTP server
|
|
122
|
+
jararaca server app:http_app
|
|
123
|
+
|
|
124
|
+
# Run as message bus worker
|
|
125
|
+
jararaca worker app:app
|
|
126
|
+
|
|
127
|
+
# Run as scheduler
|
|
128
|
+
jararaca scheduler app:app
|
|
129
|
+
|
|
130
|
+
# Generate TypeScript interfaces
|
|
131
|
+
jararaca gen-tsi app.main:app app.ts
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Documentation
|
|
135
|
+
|
|
136
|
+
For detailed documentation, please visit our [documentation site](https://luscasleo.github.io/jararaca/).
|
|
137
|
+
|
|
138
|
+
## Examples
|
|
139
|
+
|
|
140
|
+
Check out the [examples directory](examples/) for complete working examples of:
|
|
141
|
+
- REST API implementation
|
|
142
|
+
- WebSocket usage
|
|
143
|
+
- Message bus integration
|
|
144
|
+
- Task scheduling
|
|
145
|
+
- TypeScript interface generation
|
|
146
|
+
|
|
147
|
+
## Contributing
|
|
148
|
+
|
|
149
|
+
Contributions are welcome! Please read our [contributing guidelines](.github/CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
154
|
+
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
2
|
-
README.md,sha256=mte30I-ZEJJp-Oax-OganNgl6G9GaCZPL6JVFAvZGz4,7034
|
|
3
|
-
pyproject.toml,sha256=6qmtrXrdw7bJZOL_-RGgWJtkCmz7b42Xbc-NwubeGTQ,1840
|
|
4
1
|
jararaca/__init__.py,sha256=VBrN25GHJ3gDG95CcJWe3dmGcA-X2agzOCIBbjzc1Iw,15312
|
|
5
2
|
jararaca/__main__.py,sha256=-O3vsB5lHdqNFjUtoELDF81IYFtR-DSiiFMzRaiSsv4,67
|
|
6
|
-
jararaca/cli.py,sha256=
|
|
3
|
+
jararaca/cli.py,sha256=K6df8fqEc4Qj-yGz5hmO3pSgFWxcLP4iciOP4LHida4,7979
|
|
7
4
|
jararaca/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
5
|
jararaca/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
6
|
jararaca/core/providers.py,sha256=wktH84FK7c1s2wNq-fudf1uMfi3CQBR0neU2czJ_L0U,434
|
|
@@ -18,7 +15,7 @@ jararaca/messagebus/interceptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
|
|
18
15
|
jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py,sha256=BPH5wOlj_CyHtJ7W4NWF2h0gYMwzOPNzFhGADk618N4,4373
|
|
19
16
|
jararaca/messagebus/publisher.py,sha256=5ay9Znwybqt981OOykdWkFisSvGiTeTpPXDFLMnaiqg,1109
|
|
20
17
|
jararaca/messagebus/types.py,sha256=iYLyLxWqOHkDadxyMqQPWy3itLNQfvD6oQe8jcq9nzo,887
|
|
21
|
-
jararaca/messagebus/worker.py,sha256=
|
|
18
|
+
jararaca/messagebus/worker.py,sha256=_X2Ctj7bI9XjCMvQMy8jM3dEE3CGnqgrU_B3lTGfmmQ,13605
|
|
22
19
|
jararaca/microservice.py,sha256=1TvDKVMMREH27Ly8eTEheMmSfro4_Az_JKM_NdDvrgc,6636
|
|
23
20
|
jararaca/observability/decorators.py,sha256=XffBinFXdiNkY6eo8_1nkr_GapM0RUGBg0aicBIelag,2220
|
|
24
21
|
jararaca/observability/interceptor.py,sha256=GHkuGKFWftN7MDjvYeGFGEPnuJETNhtxRK6yuPrCrpU,1462
|
|
@@ -59,8 +56,8 @@ jararaca/tools/app_config/decorators.py,sha256=-ckkMZ1dswOmECdo1rFrZ15UAku--txaN
|
|
|
59
56
|
jararaca/tools/app_config/interceptor.py,sha256=nfFZiS80hrbnL7-XEYrwmp2rwaVYBqxvqu3Y-6o_ov4,2575
|
|
60
57
|
jararaca/tools/metadata.py,sha256=7nlCDYgItNybentPSSCc2MLqN7IpBd0VyQzfjfQycVI,1402
|
|
61
58
|
jararaca/tools/typescript/interface_parser.py,sha256=4SHt094P-QawMFHSyMCiujQf8Niw7xACIO1RHBM8-w4,29192
|
|
62
|
-
jararaca-0.2.
|
|
63
|
-
jararaca-0.2.
|
|
64
|
-
jararaca-0.2.
|
|
65
|
-
jararaca-0.2.
|
|
66
|
-
jararaca-0.2.
|
|
59
|
+
jararaca-0.2.37a12.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
60
|
+
jararaca-0.2.37a12.dist-info/METADATA,sha256=W8aHUpqo5Q5iGBwX0BE4CS9MfEEMN97vM-YRuCJ2URM,4873
|
|
61
|
+
jararaca-0.2.37a12.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
|
62
|
+
jararaca-0.2.37a12.dist-info/entry_points.txt,sha256=WIh3aIvz8LwUJZIDfs4EeH3VoFyCGEk7cWJurW38q0I,45
|
|
63
|
+
jararaca-0.2.37a12.dist-info/RECORD,,
|
README.md
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
<img src="https://raw.githubusercontent.com/LuscasLeo/jararaca/main/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg" alt="README.md" width="250" float="right">
|
|
2
|
-
|
|
3
|
-
# Jararaca Microservice Framework
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
Jararaca is a aio-first microservice framework that provides a set of tools to build and deploy microservices in a simple and clear way.
|
|
8
|
-
|
|
9
|
-
## Features
|
|
10
|
-
|
|
11
|
-
### Hexagonal Architecture
|
|
12
|
-
|
|
13
|
-
The framework is based on the hexagonal architecture, which allows you to separate the business logic from the infrastructure, making the code more testable and maintainable.
|
|
14
|
-
|
|
15
|
-
### Dependency Injection
|
|
16
|
-
|
|
17
|
-
The framework uses the dependency injection pattern to manage the dependencies between the components of the application.
|
|
18
|
-
|
|
19
|
-
```py
|
|
20
|
-
app = Microservice(
|
|
21
|
-
providers=[
|
|
22
|
-
ProviderSpec(
|
|
23
|
-
provide=Token(AuthConfig, "AUTH_CONFIG"),
|
|
24
|
-
use_value=AuthConfig(
|
|
25
|
-
secret="secret",
|
|
26
|
-
identity_refresh_token_expires_delta_seconds=60 * 60 * 24 * 30,
|
|
27
|
-
identity_token_expires_delta_seconds=60 * 60,
|
|
28
|
-
),
|
|
29
|
-
),
|
|
30
|
-
ProviderSpec(
|
|
31
|
-
provide=Token(AppConfig, "APP_CONFIG"),
|
|
32
|
-
use_factory=AppConfig.provider,
|
|
33
|
-
),
|
|
34
|
-
ProviderSpec(
|
|
35
|
-
provide=TokenBlackListService,
|
|
36
|
-
use_value=InMemoryTokenBlackListService(),
|
|
37
|
-
),
|
|
38
|
-
],
|
|
39
|
-
)
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Web Server Port
|
|
43
|
-
|
|
44
|
-
The framework provides a web server that listens on a specific port and routes the requests to the appropriate handler. It uses [FastAPI](https://fastapi.tiangolo.com/) as the web framework.
|
|
45
|
-
|
|
46
|
-
```py
|
|
47
|
-
@Delete("/{task_id}")
|
|
48
|
-
async def delete_task(self, task_id: TaskId) -> None:
|
|
49
|
-
await self.tasks_crud.delete_by_id(task_id)
|
|
50
|
-
|
|
51
|
-
await use_ws_manager().broadcast(("Task %s deleted" % task_id).encode())
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Message Bus
|
|
55
|
-
|
|
56
|
-
The framework provides a topic-based message bus that allows you to send messages between the components of the application. It uses [AIO Pika](https://aio-pika.readthedocs.io/) as the message broker worker and publisher.
|
|
57
|
-
|
|
58
|
-
```py
|
|
59
|
-
@IncomingHandler("task")
|
|
60
|
-
async def process_task(self, message: Message[Identifiable[TaskSchema]]) -> None:
|
|
61
|
-
name = generate_random_name()
|
|
62
|
-
now = asyncio.get_event_loop().time()
|
|
63
|
-
print("Processing task: ", name)
|
|
64
|
-
|
|
65
|
-
task = message.payload()
|
|
66
|
-
|
|
67
|
-
print("Received task: ", task)
|
|
68
|
-
await asyncio.sleep(random.randint(1, 5))
|
|
69
|
-
|
|
70
|
-
await use_publisher().publish(task, topic="task")
|
|
71
|
-
|
|
72
|
-
then = asyncio.get_event_loop().time()
|
|
73
|
-
print("Task Finished: ", name, " Time: ", then - now)
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Distributed Websocket
|
|
77
|
-
|
|
78
|
-
You can setup a room-based websocket server that allows you to send messages to a specific room or broadcast messages to all connected clients. All backend instances communicates with each other using a pub/sub mechanism (such as Redis).
|
|
79
|
-
|
|
80
|
-
```py
|
|
81
|
-
@WebSocketEndpoint("/ws")
|
|
82
|
-
async def ws_endpoint(self, websocket: WebSocket) -> None:
|
|
83
|
-
await websocket.accept()
|
|
84
|
-
counter.increment()
|
|
85
|
-
await use_ws_manager().add_websocket(websocket)
|
|
86
|
-
await use_ws_manager().join(["tasks"], websocket)
|
|
87
|
-
await use_ws_manager().broadcast(
|
|
88
|
-
("New Connection (%d) from %s" % (counter.count, self.hostname)).encode()
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
print("New Connection (%d)" % counter.count)
|
|
92
|
-
|
|
93
|
-
while True:
|
|
94
|
-
try:
|
|
95
|
-
await websocket.receive_text()
|
|
96
|
-
except WebSocketDisconnect:
|
|
97
|
-
counter.decrement()
|
|
98
|
-
await use_ws_manager().remove_websocket(websocket)
|
|
99
|
-
|
|
100
|
-
await use_ws_manager().broadcast(
|
|
101
|
-
(
|
|
102
|
-
"Connection Closed (%d) from %s"
|
|
103
|
-
% (counter.count, self.hostname)
|
|
104
|
-
).encode()
|
|
105
|
-
)
|
|
106
|
-
print("Connection Closed (%d)" % counter.count)
|
|
107
|
-
break
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### Scheduled Routine
|
|
111
|
-
|
|
112
|
-
You can setup a scheduled routine that runs a specific task at a specific time or interval.
|
|
113
|
-
|
|
114
|
-
```py
|
|
115
|
-
...
|
|
116
|
-
@ScheduledAction("* * * * * */3", allow_overlap=False)
|
|
117
|
-
async def scheduled_task(self) -> None:
|
|
118
|
-
print("Scheduled Task at ", asyncio.get_event_loop().time())
|
|
119
|
-
|
|
120
|
-
print("sleeping")
|
|
121
|
-
await asyncio.sleep(5)
|
|
122
|
-
|
|
123
|
-
await use_publisher().publish(
|
|
124
|
-
message=Identifiable(
|
|
125
|
-
id=uuid4(),
|
|
126
|
-
data=TaskSchema(name=generate_random_name()),
|
|
127
|
-
),
|
|
128
|
-
topic="task",
|
|
129
|
-
)
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### Observability
|
|
133
|
-
|
|
134
|
-
You can setup Observability Interceptors for logs, traces and metric collection with [OpenTelemetry](https://opentelemetry.io/docs)-based Protocols
|
|
135
|
-
|
|
136
|
-
```python
|
|
137
|
-
class HelloService:
|
|
138
|
-
def __init__(
|
|
139
|
-
self,
|
|
140
|
-
hello_rpc: Annotated[HelloRPC, Token(HelloRPC, "HELLO_RPC")],
|
|
141
|
-
):
|
|
142
|
-
self.hello_rpc = hello_rpc
|
|
143
|
-
|
|
144
|
-
@TracedFunc("ping") # Decorator for tracing
|
|
145
|
-
async def ping(self) -> HelloResponse:
|
|
146
|
-
return await self.hello_rpc.ping()
|
|
147
|
-
|
|
148
|
-
@TracedFunc("hello-service")
|
|
149
|
-
async def hello(
|
|
150
|
-
self,
|
|
151
|
-
gather: bool,
|
|
152
|
-
) -> HelloResponse:
|
|
153
|
-
now = asyncio.get_event_loop().time()
|
|
154
|
-
if gather:
|
|
155
|
-
await asyncio.gather(*[self.random_await(a) for a in range(10)])
|
|
156
|
-
else:
|
|
157
|
-
for a in range(10):
|
|
158
|
-
await self.random_await(a)
|
|
159
|
-
return HelloResponse(
|
|
160
|
-
message="Elapsed time: {}".format(asyncio.get_event_loop().time() - now)
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
@TracedFunc("random-await")
|
|
164
|
-
async def random_await(self, index: int) -> None:
|
|
165
|
-
logger.info("Random await %s", index, extra={"index": index})
|
|
166
|
-
await asyncio.sleep(random.randint(1, 3))
|
|
167
|
-
logger.info("Random await %s done", index, extra={"index": index})
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
## Installation
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
pip install jararaca
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## Usage
|
|
177
|
-
|
|
178
|
-
### Create a Microservice
|
|
179
|
-
|
|
180
|
-
```python
|
|
181
|
-
# app.py
|
|
182
|
-
|
|
183
|
-
from jararaca import Microservice, create_http_server, create_messagebus_worker
|
|
184
|
-
from jararaca.presentation.http_microservice import HttpMicroservice
|
|
185
|
-
|
|
186
|
-
app = Microservice(
|
|
187
|
-
providers=[
|
|
188
|
-
ProviderSpec(
|
|
189
|
-
provide=Token[AppConfig],
|
|
190
|
-
use_factory=AppConfig.provider,
|
|
191
|
-
)
|
|
192
|
-
],
|
|
193
|
-
controllers=[TasksController],
|
|
194
|
-
interceptors=[
|
|
195
|
-
AIOSqlAlchemySessionInterceptor(
|
|
196
|
-
AIOSQAConfig(
|
|
197
|
-
connection_name="default",
|
|
198
|
-
url="sqlite+aiosqlite:///db.sqlite3",
|
|
199
|
-
)
|
|
200
|
-
),
|
|
201
|
-
],
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
# App for specific Http Configuration Context
|
|
206
|
-
http_app = HttpMicroservice(app)
|
|
207
|
-
|
|
208
|
-
web_app = create_http_server(app)
|
|
209
|
-
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### Run as a Web Server
|
|
213
|
-
|
|
214
|
-
```bash
|
|
215
|
-
uvicorn app:web_app --reload
|
|
216
|
-
# or
|
|
217
|
-
jararaca server app:app
|
|
218
|
-
# or
|
|
219
|
-
jararaca server app:http_app
|
|
220
|
-
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### Run as a Message Bus Worker
|
|
224
|
-
|
|
225
|
-
```bash
|
|
226
|
-
jararaca worker app:app
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### Run as a scheduled routine
|
|
230
|
-
|
|
231
|
-
```bash
|
|
232
|
-
jararaca scheduler app:app
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
### Generate Typescript intefaces from microservice app controllers
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
jararaca gen-tsi app.main:app app.ts
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### Documentation
|
|
242
|
-
|
|
243
|
-
Documentation is under construction [here](https://luscasleo.github.io/jararaca/).
|