qena-shared-lib 0.1.7__tar.gz → 0.1.9__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.
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/.pre-commit-config.yaml +9 -1
- qena_shared_lib-0.1.7/README.md → qena_shared_lib-0.1.9/PKG-INFO +161 -11
- qena_shared_lib-0.1.7/PKG-INFO → qena_shared_lib-0.1.9/README.md +145 -28
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/pyproject.toml +15 -16
- qena_shared_lib-0.1.9/requirements.txt +55 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/application.py +2 -2
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/exception_handlers.py +69 -44
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/exceptions.py +187 -85
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/http.py +20 -20
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/logstash/_base.py +2 -2
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/rabbitmq/__init__.py +6 -6
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/rabbitmq/_base.py +20 -17
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/rabbitmq/_exception_handlers.py +58 -71
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/rabbitmq/_listener.py +109 -42
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/rabbitmq/_publisher.py +2 -1
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/rabbitmq/_rpc_client.py +13 -6
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/scheduler.py +5 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/security.py +3 -3
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/tests/test_application.py +13 -13
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/tests/test_logstash.py +50 -16
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/tests/test_rabbitmq.py +109 -32
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/tests/test_scheduler.py +3 -2
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/tests/test_security.py +20 -18
- qena_shared_lib-0.1.9/tests/utils.py +5 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/uv.lock +16 -18
- qena_shared_lib-0.1.7/requirements.txt +0 -18
- qena_shared_lib-0.1.7/src/qena_shared_lib/rabbitmq/_exceptions.py +0 -46
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/.gitignore +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/__init__.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/background.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/dependencies/__init__.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/dependencies/http.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/dependencies/miscellaneous.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/logging.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/logstash/__init__.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/logstash/_http_sender.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/logstash/_tcp_sender.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/py.typed +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/rabbitmq/_channel.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/rabbitmq/_pool.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/src/qena_shared_lib/utils.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/tests/conftest.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/tests/test_background.py +0 -0
- {qena_shared_lib-0.1.7 → qena_shared_lib-0.1.9}/tests/test_dependencies.py +0 -0
@@ -5,9 +5,17 @@ repos:
|
|
5
5
|
- id: check-yaml
|
6
6
|
- id: end-of-file-fixer
|
7
7
|
- id: trailing-whitespace
|
8
|
+
- id: check-toml
|
9
|
+
- id: name-tests-test
|
10
|
+
args: [ --pytest-test-first ]
|
11
|
+
- id: check-added-large-files
|
8
12
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
9
|
-
rev: v0.9.
|
13
|
+
rev: v0.9.9
|
10
14
|
hooks:
|
11
15
|
- id: ruff
|
12
16
|
args: [ --fix ]
|
13
17
|
- id: ruff-format
|
18
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
19
|
+
rev: v1.15.0
|
20
|
+
hooks:
|
21
|
+
- id: mypy
|
@@ -1,3 +1,19 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: qena-shared-lib
|
3
|
+
Version: 0.1.9
|
4
|
+
Summary: A shared tools for other services
|
5
|
+
Requires-Python: >=3.10
|
6
|
+
Requires-Dist: cronsim==2.6
|
7
|
+
Requires-Dist: fastapi[all]==0.115.6
|
8
|
+
Requires-Dist: httpx==0.27.2
|
9
|
+
Requires-Dist: jwt==1.3.1
|
10
|
+
Requires-Dist: passlib[bcrypt]==1.7.4
|
11
|
+
Requires-Dist: pika==1.3.2
|
12
|
+
Requires-Dist: prometheus-client==0.21.1
|
13
|
+
Requires-Dist: prometheus-fastapi-instrumentator==7.0.2
|
14
|
+
Requires-Dist: punq==0.7.0
|
15
|
+
Description-Content-Type: text/markdown
|
16
|
+
|
1
17
|
# Qena shared lib
|
2
18
|
|
3
19
|
A shared tools for other services. It includes.
|
@@ -24,6 +40,9 @@ A shared tools for other services. It includes.
|
|
24
40
|
To create fastapi app.
|
25
41
|
|
26
42
|
``` py
|
43
|
+
from qena_shared_lib.application import Builder, Environment
|
44
|
+
|
45
|
+
|
27
46
|
def main() -> FastAPI:
|
28
47
|
builder = (
|
29
48
|
Builder()
|
@@ -47,6 +66,11 @@ $ uvicorn --factory main:main
|
|
47
66
|
### Lifespan
|
48
67
|
|
49
68
|
``` py
|
69
|
+
from contextlib import asynccontextmanager
|
70
|
+
|
71
|
+
from fastapi import FastAPI
|
72
|
+
|
73
|
+
|
50
74
|
@asynccontextmanager
|
51
75
|
def lifespan(app: FastAPI):
|
52
76
|
...
|
@@ -67,6 +91,16 @@ def main() -> FastAPI:
|
|
67
91
|
### Dependencies
|
68
92
|
|
69
93
|
``` py
|
94
|
+
class EmailService:
|
95
|
+
def __init__(self):
|
96
|
+
...
|
97
|
+
|
98
|
+
|
99
|
+
class Database:
|
100
|
+
def __init__(self):
|
101
|
+
...
|
102
|
+
|
103
|
+
|
70
104
|
def main() -> FastAPI:
|
71
105
|
...
|
72
106
|
|
@@ -79,7 +113,10 @@ def main() -> FastAPI:
|
|
79
113
|
### Controllers
|
80
114
|
|
81
115
|
``` py
|
82
|
-
|
116
|
+
from qena_shared_lib.http import ControllerBase, api_controller, post
|
117
|
+
|
118
|
+
|
119
|
+
@api_controller("/users")
|
83
120
|
class UserController(ControllerBase):
|
84
121
|
|
85
122
|
def __init__(self, email_service: EmailService):
|
@@ -96,11 +133,18 @@ def main() -> FastAPI:
|
|
96
133
|
builder.with_controllers([
|
97
134
|
UserController
|
98
135
|
])
|
136
|
+
|
137
|
+
...
|
99
138
|
```
|
100
139
|
|
101
140
|
### Routers
|
102
141
|
|
103
142
|
``` py
|
143
|
+
from fastapi import APIRouter
|
144
|
+
|
145
|
+
from qena_shared_lib.dependencies.http import DependsOn
|
146
|
+
|
147
|
+
|
104
148
|
router = APIRouter(prefix="/auth")
|
105
149
|
|
106
150
|
|
@@ -137,6 +181,9 @@ def main() -> FastAPI:
|
|
137
181
|
## Logstash
|
138
182
|
|
139
183
|
``` py
|
184
|
+
from qena_shared_lib.logstash import BaseLogstashSender, HTTPSender, # TCPSender
|
185
|
+
|
186
|
+
|
140
187
|
@asynccontextmanager
|
141
188
|
async def lifespan(app: FastAPI):
|
142
189
|
logstash = get_service(BaseLogstashSender)
|
@@ -187,6 +234,9 @@ def log_message(
|
|
187
234
|
To create rabbitmq connection manager.
|
188
235
|
|
189
236
|
``` py
|
237
|
+
from qena_shared_lib.rabbitmq import ListenerBase, consume, consumer
|
238
|
+
|
239
|
+
|
190
240
|
@asynccontextmanager
|
191
241
|
async def lifespan(app: FastAPI):
|
192
242
|
rabbitmq = get_service(RabbitMqManager)
|
@@ -198,7 +248,7 @@ async def lifespan(app: FastAPI):
|
|
198
248
|
rabbitmq.disconnect()
|
199
249
|
|
200
250
|
|
201
|
-
@
|
251
|
+
@consumer("UserQueue")
|
202
252
|
class UserConsumer(ListenerBase):
|
203
253
|
|
204
254
|
def __init__(self, db: Database):
|
@@ -233,7 +283,7 @@ def main() -> FastAPI:
|
|
233
283
|
async def store_user(
|
234
284
|
rabbitmq: Annotated[
|
235
285
|
RabbitMqManager,
|
236
|
-
|
286
|
+
DependsOn(RabbitMqManager)
|
237
287
|
],
|
238
288
|
user: User,
|
239
289
|
)
|
@@ -250,7 +300,7 @@ async def store_user(
|
|
250
300
|
async def get_user(
|
251
301
|
rabbitmq: Annotated[
|
252
302
|
RabbitMqManager,
|
253
|
-
|
303
|
+
DependsOn(RabbitMqManager)
|
254
304
|
],
|
255
305
|
user_id: str,
|
256
306
|
)
|
@@ -265,7 +315,10 @@ async def get_user(
|
|
265
315
|
### Flow control
|
266
316
|
|
267
317
|
``` py
|
268
|
-
|
318
|
+
from qena_shared_lib.rabbitmq import ... , ListenerContext
|
319
|
+
|
320
|
+
|
321
|
+
@consumer("UserQueue")
|
269
322
|
class UserConsumer(ListenerBase):
|
270
323
|
|
271
324
|
@consume()
|
@@ -283,7 +336,10 @@ class UserConsumer(ListenerBase):
|
|
283
336
|
Optionally it is possible to reply to rpc calls, through.
|
284
337
|
|
285
338
|
``` py
|
286
|
-
|
339
|
+
from qena_shared_lib.rabbitmq import ... , rpc_worker
|
340
|
+
|
341
|
+
|
342
|
+
@rpc_worker("UserQueue")
|
287
343
|
class UserWorker(ListenerBase):
|
288
344
|
|
289
345
|
@execute()
|
@@ -295,10 +351,92 @@ class UserWorker(ListenerBase):
|
|
295
351
|
...
|
296
352
|
```
|
297
353
|
|
354
|
+
### Retry consumer
|
355
|
+
|
356
|
+
Consumer can retry to consumer a message in an event of failure.
|
357
|
+
|
358
|
+
``` py
|
359
|
+
from qena_shared_lib.rabbitmq import (
|
360
|
+
BackoffRetryDelay,
|
361
|
+
FixedRetryDelay,
|
362
|
+
RabbitMqManager,
|
363
|
+
RetryDelayJitter,
|
364
|
+
RetryPolicy,
|
365
|
+
)
|
366
|
+
|
367
|
+
|
368
|
+
@consumer(
|
369
|
+
queue="UserQueue",
|
370
|
+
# can be defined for consumer of specific queue
|
371
|
+
retry_policy=RetryPolicy(
|
372
|
+
exceptions=(AMQPError,),
|
373
|
+
max_retry=5,
|
374
|
+
retry_delay_strategy=FixedRetryDelay(
|
375
|
+
retry_delay=2
|
376
|
+
),
|
377
|
+
retry_delay_jitter=RetryDelayJitter(min=0.5, max=5.0),
|
378
|
+
)
|
379
|
+
)
|
380
|
+
class UserConsumer(ListenerBase):
|
381
|
+
|
382
|
+
@consume(
|
383
|
+
# for specific target
|
384
|
+
retry_policy=RetryPolicy(
|
385
|
+
exceptions=(AMQPError,),
|
386
|
+
max_retry=5,
|
387
|
+
retry_delay_strategy=FixedRetryDelay(
|
388
|
+
retry_delay=2
|
389
|
+
),
|
390
|
+
retry_delay_jitter=RetryDelayJitter(min=0.5, max=5.0),
|
391
|
+
)
|
392
|
+
)
|
393
|
+
async def store_user(self, ctx: ListenerContext, user: User):
|
394
|
+
...
|
395
|
+
|
396
|
+
await ctx.flow_control.request(10)
|
397
|
+
|
398
|
+
...
|
399
|
+
|
400
|
+
|
401
|
+
def main() -> FastAPI:
|
402
|
+
...
|
403
|
+
|
404
|
+
rabbitmq = RabbitMqManager(
|
405
|
+
logstash=logstash,
|
406
|
+
container=builder.container,
|
407
|
+
# or globally for all consumers
|
408
|
+
listener_global_retry_policy=RetryPolicy(
|
409
|
+
exceptions=(AMQPError,),
|
410
|
+
max_retry=10,
|
411
|
+
retry_delay_strategy=BackoffRetryDelay(
|
412
|
+
multiplier=1.5, min=2, max=10
|
413
|
+
),
|
414
|
+
retry_delay_jitter=RetryDelayJitter(min=0.5, max=5.0),
|
415
|
+
match_by_cause=True,
|
416
|
+
),
|
417
|
+
)
|
418
|
+
|
419
|
+
rabbitmq.include_listener(UserConsumer)
|
420
|
+
builder.add_singleton(
|
421
|
+
service=RabbitMqManager,
|
422
|
+
instance=rabbitmq,
|
423
|
+
)
|
424
|
+
```
|
425
|
+
|
426
|
+
|
298
427
|
|
299
428
|
## Scheduler
|
300
429
|
|
301
430
|
``` py
|
431
|
+
from qena_shared_lib.scheduler import (
|
432
|
+
ScheduleManager,
|
433
|
+
# Scheduler,
|
434
|
+
SchedulerBase,
|
435
|
+
schedule,
|
436
|
+
scheduler,
|
437
|
+
)
|
438
|
+
|
439
|
+
|
302
440
|
@asynccontextmanager
|
303
441
|
async def lifespan(app: FastAPI):
|
304
442
|
schedule_manager = get_service(ScheduleManager)
|
@@ -310,7 +448,7 @@ async def lifespan(app: FastAPI):
|
|
310
448
|
schedule_manager.stop()
|
311
449
|
|
312
450
|
|
313
|
-
@
|
451
|
+
@scheduler()
|
314
452
|
class TaskScheduler(SchedulerBase):
|
315
453
|
|
316
454
|
def __init__(self, db: Database)
|
@@ -351,6 +489,9 @@ def main() -> FastAPI:
|
|
351
489
|
## Background
|
352
490
|
|
353
491
|
``` py
|
492
|
+
from qena_shared_lib.background import Background
|
493
|
+
|
494
|
+
|
354
495
|
@asynccontextmanager
|
355
496
|
async def lifespan(app: FastAPI):
|
356
497
|
background = get_service(Background)
|
@@ -394,7 +535,10 @@ async def process_data(
|
|
394
535
|
### Password hasher
|
395
536
|
|
396
537
|
``` py
|
397
|
-
|
538
|
+
from qena_shared_lib.security import PasswordHasher
|
539
|
+
|
540
|
+
|
541
|
+
@api_controller("/users")
|
398
542
|
class UserController(ControllerBase):
|
399
543
|
|
400
544
|
def __init__(self, password_hasher: PasswordHasher):
|
@@ -423,6 +567,9 @@ def main() -> FastAPI:
|
|
423
567
|
### JWT
|
424
568
|
|
425
569
|
``` py
|
570
|
+
from qena_shared_lib.security import JwtAdapter
|
571
|
+
|
572
|
+
|
426
573
|
@ApiController("/users")
|
427
574
|
class UserController(ControllerBase):
|
428
575
|
|
@@ -462,7 +609,10 @@ def main() -> FastAPI:
|
|
462
609
|
### ACL
|
463
610
|
|
464
611
|
``` py
|
465
|
-
|
612
|
+
from qena_shared_lib.security import Authorization
|
613
|
+
|
614
|
+
|
615
|
+
@api_controller("/users")
|
466
616
|
class UserController(ControllerBase):
|
467
617
|
|
468
618
|
@post()
|
@@ -470,7 +620,7 @@ class UserController(ControllerBase):
|
|
470
620
|
self,
|
471
621
|
user: Annotated[
|
472
622
|
UserInfo,
|
473
|
-
|
623
|
+
Authorization(
|
474
624
|
user_type="ADMIN",
|
475
625
|
persmission=[
|
476
626
|
"READ"
|
@@ -485,7 +635,7 @@ class UserController(ControllerBase):
|
|
485
635
|
async def get_users(
|
486
636
|
user: Annotated[
|
487
637
|
UserInfo,
|
488
|
-
|
638
|
+
Authorization("ADMIN")
|
489
639
|
]
|
490
640
|
)
|
491
641
|
...
|
@@ -1,20 +1,3 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: qena-shared-lib
|
3
|
-
Version: 0.1.7
|
4
|
-
Summary: A shared tools for other services
|
5
|
-
Requires-Python: >=3.10
|
6
|
-
Requires-Dist: cronsim~=2.0
|
7
|
-
Requires-Dist: fastapi[all]~=0.115.0
|
8
|
-
Requires-Dist: httpx~=0.27.0
|
9
|
-
Requires-Dist: jwt~=1.3.0
|
10
|
-
Requires-Dist: passlib[bcrypt]~=1.7.0
|
11
|
-
Requires-Dist: pika~=1.3.0
|
12
|
-
Requires-Dist: prometheus-client~=0.21.0
|
13
|
-
Requires-Dist: prometheus-fastapi-instrumentator~=7.0.0
|
14
|
-
Requires-Dist: punq~=0.7.0
|
15
|
-
Requires-Dist: pydantic~=2.10.0
|
16
|
-
Description-Content-Type: text/markdown
|
17
|
-
|
18
1
|
# Qena shared lib
|
19
2
|
|
20
3
|
A shared tools for other services. It includes.
|
@@ -41,6 +24,9 @@ A shared tools for other services. It includes.
|
|
41
24
|
To create fastapi app.
|
42
25
|
|
43
26
|
``` py
|
27
|
+
from qena_shared_lib.application import Builder, Environment
|
28
|
+
|
29
|
+
|
44
30
|
def main() -> FastAPI:
|
45
31
|
builder = (
|
46
32
|
Builder()
|
@@ -64,6 +50,11 @@ $ uvicorn --factory main:main
|
|
64
50
|
### Lifespan
|
65
51
|
|
66
52
|
``` py
|
53
|
+
from contextlib import asynccontextmanager
|
54
|
+
|
55
|
+
from fastapi import FastAPI
|
56
|
+
|
57
|
+
|
67
58
|
@asynccontextmanager
|
68
59
|
def lifespan(app: FastAPI):
|
69
60
|
...
|
@@ -84,6 +75,16 @@ def main() -> FastAPI:
|
|
84
75
|
### Dependencies
|
85
76
|
|
86
77
|
``` py
|
78
|
+
class EmailService:
|
79
|
+
def __init__(self):
|
80
|
+
...
|
81
|
+
|
82
|
+
|
83
|
+
class Database:
|
84
|
+
def __init__(self):
|
85
|
+
...
|
86
|
+
|
87
|
+
|
87
88
|
def main() -> FastAPI:
|
88
89
|
...
|
89
90
|
|
@@ -96,7 +97,10 @@ def main() -> FastAPI:
|
|
96
97
|
### Controllers
|
97
98
|
|
98
99
|
``` py
|
99
|
-
|
100
|
+
from qena_shared_lib.http import ControllerBase, api_controller, post
|
101
|
+
|
102
|
+
|
103
|
+
@api_controller("/users")
|
100
104
|
class UserController(ControllerBase):
|
101
105
|
|
102
106
|
def __init__(self, email_service: EmailService):
|
@@ -113,11 +117,18 @@ def main() -> FastAPI:
|
|
113
117
|
builder.with_controllers([
|
114
118
|
UserController
|
115
119
|
])
|
120
|
+
|
121
|
+
...
|
116
122
|
```
|
117
123
|
|
118
124
|
### Routers
|
119
125
|
|
120
126
|
``` py
|
127
|
+
from fastapi import APIRouter
|
128
|
+
|
129
|
+
from qena_shared_lib.dependencies.http import DependsOn
|
130
|
+
|
131
|
+
|
121
132
|
router = APIRouter(prefix="/auth")
|
122
133
|
|
123
134
|
|
@@ -154,6 +165,9 @@ def main() -> FastAPI:
|
|
154
165
|
## Logstash
|
155
166
|
|
156
167
|
``` py
|
168
|
+
from qena_shared_lib.logstash import BaseLogstashSender, HTTPSender, # TCPSender
|
169
|
+
|
170
|
+
|
157
171
|
@asynccontextmanager
|
158
172
|
async def lifespan(app: FastAPI):
|
159
173
|
logstash = get_service(BaseLogstashSender)
|
@@ -204,6 +218,9 @@ def log_message(
|
|
204
218
|
To create rabbitmq connection manager.
|
205
219
|
|
206
220
|
``` py
|
221
|
+
from qena_shared_lib.rabbitmq import ListenerBase, consume, consumer
|
222
|
+
|
223
|
+
|
207
224
|
@asynccontextmanager
|
208
225
|
async def lifespan(app: FastAPI):
|
209
226
|
rabbitmq = get_service(RabbitMqManager)
|
@@ -215,7 +232,7 @@ async def lifespan(app: FastAPI):
|
|
215
232
|
rabbitmq.disconnect()
|
216
233
|
|
217
234
|
|
218
|
-
@
|
235
|
+
@consumer("UserQueue")
|
219
236
|
class UserConsumer(ListenerBase):
|
220
237
|
|
221
238
|
def __init__(self, db: Database):
|
@@ -250,7 +267,7 @@ def main() -> FastAPI:
|
|
250
267
|
async def store_user(
|
251
268
|
rabbitmq: Annotated[
|
252
269
|
RabbitMqManager,
|
253
|
-
|
270
|
+
DependsOn(RabbitMqManager)
|
254
271
|
],
|
255
272
|
user: User,
|
256
273
|
)
|
@@ -267,7 +284,7 @@ async def store_user(
|
|
267
284
|
async def get_user(
|
268
285
|
rabbitmq: Annotated[
|
269
286
|
RabbitMqManager,
|
270
|
-
|
287
|
+
DependsOn(RabbitMqManager)
|
271
288
|
],
|
272
289
|
user_id: str,
|
273
290
|
)
|
@@ -282,7 +299,10 @@ async def get_user(
|
|
282
299
|
### Flow control
|
283
300
|
|
284
301
|
``` py
|
285
|
-
|
302
|
+
from qena_shared_lib.rabbitmq import ... , ListenerContext
|
303
|
+
|
304
|
+
|
305
|
+
@consumer("UserQueue")
|
286
306
|
class UserConsumer(ListenerBase):
|
287
307
|
|
288
308
|
@consume()
|
@@ -300,7 +320,10 @@ class UserConsumer(ListenerBase):
|
|
300
320
|
Optionally it is possible to reply to rpc calls, through.
|
301
321
|
|
302
322
|
``` py
|
303
|
-
|
323
|
+
from qena_shared_lib.rabbitmq import ... , rpc_worker
|
324
|
+
|
325
|
+
|
326
|
+
@rpc_worker("UserQueue")
|
304
327
|
class UserWorker(ListenerBase):
|
305
328
|
|
306
329
|
@execute()
|
@@ -312,10 +335,92 @@ class UserWorker(ListenerBase):
|
|
312
335
|
...
|
313
336
|
```
|
314
337
|
|
338
|
+
### Retry consumer
|
339
|
+
|
340
|
+
Consumer can retry to consumer a message in an event of failure.
|
341
|
+
|
342
|
+
``` py
|
343
|
+
from qena_shared_lib.rabbitmq import (
|
344
|
+
BackoffRetryDelay,
|
345
|
+
FixedRetryDelay,
|
346
|
+
RabbitMqManager,
|
347
|
+
RetryDelayJitter,
|
348
|
+
RetryPolicy,
|
349
|
+
)
|
350
|
+
|
351
|
+
|
352
|
+
@consumer(
|
353
|
+
queue="UserQueue",
|
354
|
+
# can be defined for consumer of specific queue
|
355
|
+
retry_policy=RetryPolicy(
|
356
|
+
exceptions=(AMQPError,),
|
357
|
+
max_retry=5,
|
358
|
+
retry_delay_strategy=FixedRetryDelay(
|
359
|
+
retry_delay=2
|
360
|
+
),
|
361
|
+
retry_delay_jitter=RetryDelayJitter(min=0.5, max=5.0),
|
362
|
+
)
|
363
|
+
)
|
364
|
+
class UserConsumer(ListenerBase):
|
365
|
+
|
366
|
+
@consume(
|
367
|
+
# for specific target
|
368
|
+
retry_policy=RetryPolicy(
|
369
|
+
exceptions=(AMQPError,),
|
370
|
+
max_retry=5,
|
371
|
+
retry_delay_strategy=FixedRetryDelay(
|
372
|
+
retry_delay=2
|
373
|
+
),
|
374
|
+
retry_delay_jitter=RetryDelayJitter(min=0.5, max=5.0),
|
375
|
+
)
|
376
|
+
)
|
377
|
+
async def store_user(self, ctx: ListenerContext, user: User):
|
378
|
+
...
|
379
|
+
|
380
|
+
await ctx.flow_control.request(10)
|
381
|
+
|
382
|
+
...
|
383
|
+
|
384
|
+
|
385
|
+
def main() -> FastAPI:
|
386
|
+
...
|
387
|
+
|
388
|
+
rabbitmq = RabbitMqManager(
|
389
|
+
logstash=logstash,
|
390
|
+
container=builder.container,
|
391
|
+
# or globally for all consumers
|
392
|
+
listener_global_retry_policy=RetryPolicy(
|
393
|
+
exceptions=(AMQPError,),
|
394
|
+
max_retry=10,
|
395
|
+
retry_delay_strategy=BackoffRetryDelay(
|
396
|
+
multiplier=1.5, min=2, max=10
|
397
|
+
),
|
398
|
+
retry_delay_jitter=RetryDelayJitter(min=0.5, max=5.0),
|
399
|
+
match_by_cause=True,
|
400
|
+
),
|
401
|
+
)
|
402
|
+
|
403
|
+
rabbitmq.include_listener(UserConsumer)
|
404
|
+
builder.add_singleton(
|
405
|
+
service=RabbitMqManager,
|
406
|
+
instance=rabbitmq,
|
407
|
+
)
|
408
|
+
```
|
409
|
+
|
410
|
+
|
315
411
|
|
316
412
|
## Scheduler
|
317
413
|
|
318
414
|
``` py
|
415
|
+
from qena_shared_lib.scheduler import (
|
416
|
+
ScheduleManager,
|
417
|
+
# Scheduler,
|
418
|
+
SchedulerBase,
|
419
|
+
schedule,
|
420
|
+
scheduler,
|
421
|
+
)
|
422
|
+
|
423
|
+
|
319
424
|
@asynccontextmanager
|
320
425
|
async def lifespan(app: FastAPI):
|
321
426
|
schedule_manager = get_service(ScheduleManager)
|
@@ -327,7 +432,7 @@ async def lifespan(app: FastAPI):
|
|
327
432
|
schedule_manager.stop()
|
328
433
|
|
329
434
|
|
330
|
-
@
|
435
|
+
@scheduler()
|
331
436
|
class TaskScheduler(SchedulerBase):
|
332
437
|
|
333
438
|
def __init__(self, db: Database)
|
@@ -368,6 +473,9 @@ def main() -> FastAPI:
|
|
368
473
|
## Background
|
369
474
|
|
370
475
|
``` py
|
476
|
+
from qena_shared_lib.background import Background
|
477
|
+
|
478
|
+
|
371
479
|
@asynccontextmanager
|
372
480
|
async def lifespan(app: FastAPI):
|
373
481
|
background = get_service(Background)
|
@@ -411,7 +519,10 @@ async def process_data(
|
|
411
519
|
### Password hasher
|
412
520
|
|
413
521
|
``` py
|
414
|
-
|
522
|
+
from qena_shared_lib.security import PasswordHasher
|
523
|
+
|
524
|
+
|
525
|
+
@api_controller("/users")
|
415
526
|
class UserController(ControllerBase):
|
416
527
|
|
417
528
|
def __init__(self, password_hasher: PasswordHasher):
|
@@ -440,6 +551,9 @@ def main() -> FastAPI:
|
|
440
551
|
### JWT
|
441
552
|
|
442
553
|
``` py
|
554
|
+
from qena_shared_lib.security import JwtAdapter
|
555
|
+
|
556
|
+
|
443
557
|
@ApiController("/users")
|
444
558
|
class UserController(ControllerBase):
|
445
559
|
|
@@ -479,7 +593,10 @@ def main() -> FastAPI:
|
|
479
593
|
### ACL
|
480
594
|
|
481
595
|
``` py
|
482
|
-
|
596
|
+
from qena_shared_lib.security import Authorization
|
597
|
+
|
598
|
+
|
599
|
+
@api_controller("/users")
|
483
600
|
class UserController(ControllerBase):
|
484
601
|
|
485
602
|
@post()
|
@@ -487,7 +604,7 @@ class UserController(ControllerBase):
|
|
487
604
|
self,
|
488
605
|
user: Annotated[
|
489
606
|
UserInfo,
|
490
|
-
|
607
|
+
Authorization(
|
491
608
|
user_type="ADMIN",
|
492
609
|
persmission=[
|
493
610
|
"READ"
|
@@ -502,7 +619,7 @@ class UserController(ControllerBase):
|
|
502
619
|
async def get_users(
|
503
620
|
user: Annotated[
|
504
621
|
UserInfo,
|
505
|
-
|
622
|
+
Authorization("ADMIN")
|
506
623
|
]
|
507
624
|
)
|
508
625
|
...
|
@@ -1,20 +1,19 @@
|
|
1
1
|
[project]
|
2
2
|
name = "qena-shared-lib"
|
3
|
-
version = "0.1.
|
3
|
+
version = "0.1.9"
|
4
4
|
description = "A shared tools for other services"
|
5
5
|
readme = "README.md"
|
6
6
|
requires-python = ">=3.10"
|
7
7
|
dependencies = [
|
8
|
-
"cronsim
|
9
|
-
"fastapi[all]
|
10
|
-
"httpx
|
11
|
-
"jwt
|
12
|
-
"passlib[bcrypt]
|
13
|
-
"pika
|
14
|
-
"prometheus-client
|
15
|
-
"prometheus-fastapi-instrumentator
|
16
|
-
"punq
|
17
|
-
"pydantic~=2.10.0",
|
8
|
+
"cronsim==2.6",
|
9
|
+
"fastapi[all]==0.115.6",
|
10
|
+
"httpx==0.27.2",
|
11
|
+
"jwt==1.3.1",
|
12
|
+
"passlib[bcrypt]==1.7.4",
|
13
|
+
"pika==1.3.2",
|
14
|
+
"prometheus-client==0.21.1",
|
15
|
+
"prometheus-fastapi-instrumentator==7.0.2",
|
16
|
+
"punq==0.7.0",
|
18
17
|
]
|
19
18
|
|
20
19
|
[build-system]
|
@@ -23,11 +22,11 @@ build-backend = "hatchling.build"
|
|
23
22
|
|
24
23
|
[tool.uv]
|
25
24
|
dev-dependencies = [
|
26
|
-
"pre-commit
|
27
|
-
"pytest-asyncio
|
28
|
-
"pytest
|
29
|
-
"testcontainers
|
30
|
-
"pytest-cov
|
25
|
+
"pre-commit==4.0.1",
|
26
|
+
"pytest-asyncio==0.24.0",
|
27
|
+
"pytest==8.3.3",
|
28
|
+
"testcontainers==4.8.2",
|
29
|
+
"pytest-cov==6.0.0",
|
31
30
|
]
|
32
31
|
|
33
32
|
[tool.ruff]
|