qena-shared-lib 0.1.0__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.
- qena_shared_lib/__init__.py +27 -0
- qena_shared_lib/application.py +190 -0
- qena_shared_lib/background.py +109 -0
- qena_shared_lib/dependencies/__init__.py +19 -0
- qena_shared_lib/dependencies/http.py +62 -0
- qena_shared_lib/dependencies/miscellaneous.py +35 -0
- qena_shared_lib/exception_handlers.py +165 -0
- qena_shared_lib/exceptions.py +319 -0
- qena_shared_lib/http.py +631 -0
- qena_shared_lib/logging.py +63 -0
- qena_shared_lib/logstash/__init__.py +17 -0
- qena_shared_lib/logstash/_base.py +573 -0
- qena_shared_lib/logstash/_http_sender.py +61 -0
- qena_shared_lib/logstash/_tcp_sender.py +84 -0
- qena_shared_lib/py.typed +0 -0
- qena_shared_lib/rabbitmq/__init__.py +52 -0
- qena_shared_lib/rabbitmq/_base.py +741 -0
- qena_shared_lib/rabbitmq/_channel.py +196 -0
- qena_shared_lib/rabbitmq/_exception_handlers.py +159 -0
- qena_shared_lib/rabbitmq/_exceptions.py +46 -0
- qena_shared_lib/rabbitmq/_listener.py +1292 -0
- qena_shared_lib/rabbitmq/_pool.py +74 -0
- qena_shared_lib/rabbitmq/_publisher.py +73 -0
- qena_shared_lib/rabbitmq/_rpc_client.py +286 -0
- qena_shared_lib/rabbitmq/_utils.py +18 -0
- qena_shared_lib/scheduler.py +402 -0
- qena_shared_lib/security.py +205 -0
- qena_shared_lib/utils.py +28 -0
- qena_shared_lib-0.1.0.dist-info/METADATA +473 -0
- qena_shared_lib-0.1.0.dist-info/RECORD +31 -0
- qena_shared_lib-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,473 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: qena-shared-lib
|
3
|
+
Version: 0.1.0
|
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
|
+
# Qena shared lib
|
19
|
+
|
20
|
+
A shared tools for other services. It includes.
|
21
|
+
|
22
|
+
- FastAPI app builder
|
23
|
+
- A wrapper around fastapi to make it class based.
|
24
|
+
- RabbitMQ utility class to listen, respond, publish and make rpc request.
|
25
|
+
- Logstash utility class to log message in `ecs` ( elastic common schema ).
|
26
|
+
- A simple task scheduler, to schedule task to run in specific time.
|
27
|
+
- Background task runner.
|
28
|
+
- Security tools ( password hasher, jwt, acl ).
|
29
|
+
- IOC container to manager dependencies used across fastapi, rabbitmq manager and schedule manager.
|
30
|
+
|
31
|
+
# Usage
|
32
|
+
|
33
|
+
## Http
|
34
|
+
|
35
|
+
To create fastapi app.
|
36
|
+
|
37
|
+
``` py
|
38
|
+
def main() -> FastAPI:
|
39
|
+
builder = (
|
40
|
+
Builder()
|
41
|
+
.with_title("Qena shared lib")
|
42
|
+
.with_description("A shared tools for other services.")
|
43
|
+
.with_version("0.1.0")
|
44
|
+
.with_environment(Environment.PRODUCTION)
|
45
|
+
)
|
46
|
+
|
47
|
+
app = builder.build()
|
48
|
+
|
49
|
+
return app
|
50
|
+
```
|
51
|
+
|
52
|
+
To run app
|
53
|
+
|
54
|
+
``` sh
|
55
|
+
$ uvicorn --factory main:main
|
56
|
+
```
|
57
|
+
|
58
|
+
### Lifespan
|
59
|
+
|
60
|
+
``` py
|
61
|
+
@asynccontextmanager
|
62
|
+
def lifespan(app: FastAPI):
|
63
|
+
...
|
64
|
+
|
65
|
+
yield
|
66
|
+
|
67
|
+
...
|
68
|
+
|
69
|
+
|
70
|
+
def main() -> FastAPI:
|
71
|
+
...
|
72
|
+
|
73
|
+
builder.with_lifespan(lifespan)
|
74
|
+
|
75
|
+
...
|
76
|
+
```
|
77
|
+
|
78
|
+
### Dependencies
|
79
|
+
|
80
|
+
``` py
|
81
|
+
def main() -> FastAPI:
|
82
|
+
...
|
83
|
+
|
84
|
+
builder.with_singleton(EmailService)
|
85
|
+
builder.with_transient(Database)
|
86
|
+
|
87
|
+
...
|
88
|
+
```
|
89
|
+
|
90
|
+
### Controllers
|
91
|
+
|
92
|
+
``` py
|
93
|
+
@ApiController("/users")
|
94
|
+
class UserController(ControllerBase):
|
95
|
+
|
96
|
+
def __init__(self, email_service: EmailService):
|
97
|
+
self._email_service = email_service
|
98
|
+
|
99
|
+
@post()
|
100
|
+
async def send_email(self, message: str):
|
101
|
+
await self._email_service.send(message)
|
102
|
+
|
103
|
+
|
104
|
+
def main() -> FastAPI:
|
105
|
+
...
|
106
|
+
|
107
|
+
builder.with_controllers([
|
108
|
+
UserController
|
109
|
+
])
|
110
|
+
```
|
111
|
+
|
112
|
+
### Routers
|
113
|
+
|
114
|
+
``` py
|
115
|
+
router = APIRouter(prefix="/auth")
|
116
|
+
|
117
|
+
|
118
|
+
@router.post("")
|
119
|
+
async def login(
|
120
|
+
db: Annotated[Database, DependsOn(Database)],
|
121
|
+
username: str,
|
122
|
+
password: str
|
123
|
+
):
|
124
|
+
...
|
125
|
+
|
126
|
+
|
127
|
+
def main() -> FastAPI:
|
128
|
+
...
|
129
|
+
|
130
|
+
builder.with_routers([
|
131
|
+
router
|
132
|
+
])
|
133
|
+
|
134
|
+
...
|
135
|
+
```
|
136
|
+
|
137
|
+
To enable metrics.
|
138
|
+
|
139
|
+
``` py
|
140
|
+
def main() -> FastAPI:
|
141
|
+
...
|
142
|
+
|
143
|
+
builder.with_metrics()
|
144
|
+
|
145
|
+
...
|
146
|
+
```
|
147
|
+
|
148
|
+
## Logstash
|
149
|
+
|
150
|
+
``` py
|
151
|
+
@asynccontextmanager
|
152
|
+
async def lifespan(app: FastAPI):
|
153
|
+
logstash = get_service(BaseLogstashSender)
|
154
|
+
|
155
|
+
await logstash.start()
|
156
|
+
|
157
|
+
yield
|
158
|
+
|
159
|
+
await logstash.stop()
|
160
|
+
|
161
|
+
|
162
|
+
def main() -> FastAPI:
|
163
|
+
...
|
164
|
+
|
165
|
+
logstash = HTTPSender(
|
166
|
+
service_name="qena-shared-lib",
|
167
|
+
url="http://127.0.0.1:18080",
|
168
|
+
user="logstash",
|
169
|
+
password="logstash",
|
170
|
+
)
|
171
|
+
# or
|
172
|
+
# logstash = TCPSender(
|
173
|
+
# service_name="qena-shared-lib",
|
174
|
+
# host="127.0.0.1",
|
175
|
+
# port=18090
|
176
|
+
# )
|
177
|
+
builder.with_singleton(
|
178
|
+
service=BaseLogstashSender,
|
179
|
+
instance=logstash,
|
180
|
+
)
|
181
|
+
|
182
|
+
...
|
183
|
+
|
184
|
+
|
185
|
+
@router.get("")
|
186
|
+
def log_message(
|
187
|
+
logstash: Annotated[
|
188
|
+
BaseLogstashSender,
|
189
|
+
DependsOn(BaseLogstashSender),
|
190
|
+
],
|
191
|
+
message: str,
|
192
|
+
):
|
193
|
+
logstash.info(message)
|
194
|
+
```
|
195
|
+
|
196
|
+
## Rabbitmq
|
197
|
+
|
198
|
+
To create rabbitmq connection manager.
|
199
|
+
|
200
|
+
``` py
|
201
|
+
@asynccontextmanager
|
202
|
+
async def lifespan(app: FastAPI):
|
203
|
+
rabbitmq = get_service(RabbitMqManager)
|
204
|
+
|
205
|
+
await rabbitmq.connect()
|
206
|
+
|
207
|
+
yield
|
208
|
+
|
209
|
+
rabbitmq.disconnect()
|
210
|
+
|
211
|
+
|
212
|
+
@Consumer("UserQueue")
|
213
|
+
class UserConsumer(ListenerBase):
|
214
|
+
|
215
|
+
def __init__(self, db: Database):
|
216
|
+
self._db = db
|
217
|
+
|
218
|
+
@consume()
|
219
|
+
async def store_user(self, user: User):
|
220
|
+
await self._db.save(user)
|
221
|
+
|
222
|
+
|
223
|
+
def main() -> FastAPI:
|
224
|
+
...
|
225
|
+
|
226
|
+
rabbitmq = RabbitMqManager(
|
227
|
+
listeners=[
|
228
|
+
UserConsumer
|
229
|
+
],
|
230
|
+
logstash=logstash,
|
231
|
+
container=builder.container,
|
232
|
+
)
|
233
|
+
|
234
|
+
builder.add_singleton(
|
235
|
+
service=RabbitMqManager,
|
236
|
+
instance=rabbitmq,
|
237
|
+
)
|
238
|
+
|
239
|
+
...
|
240
|
+
```
|
241
|
+
|
242
|
+
### Publisher
|
243
|
+
|
244
|
+
``` py
|
245
|
+
@router.post("")
|
246
|
+
async def store_user(
|
247
|
+
rabbitmq: Annotated[
|
248
|
+
RabbitMqManager,
|
249
|
+
DependsOne(RabbitMqManager)
|
250
|
+
],
|
251
|
+
user: User,
|
252
|
+
)
|
253
|
+
publisher = rabbitmq.publisher("UserQueue")
|
254
|
+
|
255
|
+
await publisher.publish(user)
|
256
|
+
# await publisher.publish_with_arguments(user)
|
257
|
+
```
|
258
|
+
|
259
|
+
### RPC client
|
260
|
+
|
261
|
+
``` py
|
262
|
+
@router.get("")
|
263
|
+
async def get_user(
|
264
|
+
rabbitmq: Annotated[
|
265
|
+
RabbitMqManager,
|
266
|
+
DependsOne(RabbitMqManager)
|
267
|
+
],
|
268
|
+
user_id: str,
|
269
|
+
)
|
270
|
+
rpc_client = rabbitmq.rpc_client("UserQueue")
|
271
|
+
|
272
|
+
user = await rpc_client.call(user_id)
|
273
|
+
# user = await rpc_client.call_with_arguments(user_id)
|
274
|
+
|
275
|
+
return user
|
276
|
+
```
|
277
|
+
|
278
|
+
## Scheduler
|
279
|
+
|
280
|
+
``` py
|
281
|
+
@asynccontextmanager
|
282
|
+
async def lifespan(app: FastAPI):
|
283
|
+
schedule_manager = get_service(ScheduleManager)
|
284
|
+
|
285
|
+
rabbitmq.start()
|
286
|
+
|
287
|
+
yield
|
288
|
+
|
289
|
+
schedule_manager.stop()
|
290
|
+
|
291
|
+
|
292
|
+
@Scheduler()
|
293
|
+
class TaskScheduler(SchedulerBase):
|
294
|
+
|
295
|
+
def __init__(self, db: Database)
|
296
|
+
|
297
|
+
@schedule("* * * * *")
|
298
|
+
def do_task(
|
299
|
+
self,
|
300
|
+
|
301
|
+
):
|
302
|
+
...
|
303
|
+
# or
|
304
|
+
# scheduler = Scheduler()
|
305
|
+
|
306
|
+
# @scheduler.schedule("* * * * *")
|
307
|
+
# def do_task(
|
308
|
+
# db: Annotated[Database, DependsOn(Database)]
|
309
|
+
# ):
|
310
|
+
# ...
|
311
|
+
|
312
|
+
|
313
|
+
def main() -> FastAPI:
|
314
|
+
...
|
315
|
+
|
316
|
+
schedule_manager = ScheduleManager(
|
317
|
+
schedulers=[
|
318
|
+
TaskScheduler
|
319
|
+
],
|
320
|
+
logstash=logstash,
|
321
|
+
container=builder.container
|
322
|
+
)
|
323
|
+
|
324
|
+
builder.with_singleton(
|
325
|
+
service=ScheduleManager,
|
326
|
+
instance=schedule_manager,
|
327
|
+
)
|
328
|
+
|
329
|
+
...
|
330
|
+
```
|
331
|
+
|
332
|
+
## Background
|
333
|
+
|
334
|
+
``` py
|
335
|
+
@asynccontextmanager
|
336
|
+
async def lifespan(app: FastAPI):
|
337
|
+
background = get_service(Background)
|
338
|
+
|
339
|
+
background.start()
|
340
|
+
|
341
|
+
yield
|
342
|
+
|
343
|
+
background.stop()
|
344
|
+
|
345
|
+
|
346
|
+
def main() -> FastAPI:
|
347
|
+
...
|
348
|
+
|
349
|
+
builder.with_singleton(
|
350
|
+
service=BaseLogstashSender,
|
351
|
+
instance=logstash,
|
352
|
+
)
|
353
|
+
builder.with_singleton(Background)
|
354
|
+
|
355
|
+
...
|
356
|
+
|
357
|
+
|
358
|
+
async data_processor(data: Data):
|
359
|
+
...
|
360
|
+
|
361
|
+
|
362
|
+
@router.get("")
|
363
|
+
async def process_data(
|
364
|
+
background: Annotated[
|
365
|
+
Background,
|
366
|
+
DependsOne(Background)
|
367
|
+
],
|
368
|
+
data: Data
|
369
|
+
)
|
370
|
+
background.add_task(BackgroundTask(data_processor, data))
|
371
|
+
```
|
372
|
+
|
373
|
+
## Security
|
374
|
+
|
375
|
+
### Password hasher
|
376
|
+
|
377
|
+
``` py
|
378
|
+
@ApiController("/users")
|
379
|
+
class UserController(ControllerBase):
|
380
|
+
|
381
|
+
def __init__(self, password_hasher: PasswordHasher):
|
382
|
+
self._password_hasher = password_hasher
|
383
|
+
|
384
|
+
@post()
|
385
|
+
async def signup(self, user: User):
|
386
|
+
await self._password_hasher.hash(user.password)
|
387
|
+
|
388
|
+
@post()
|
389
|
+
async def login(self, user: User):
|
390
|
+
await self._password_hasher.verify(user.password)
|
391
|
+
|
392
|
+
|
393
|
+
def main() -> FastAPI:
|
394
|
+
...
|
395
|
+
|
396
|
+
builder.with_singleton(PasswordHasher)
|
397
|
+
builder.with_controllers([
|
398
|
+
UserController
|
399
|
+
])
|
400
|
+
|
401
|
+
...
|
402
|
+
```
|
403
|
+
|
404
|
+
### JWT
|
405
|
+
|
406
|
+
``` py
|
407
|
+
@ApiController("/users")
|
408
|
+
class UserController(ControllerBase):
|
409
|
+
|
410
|
+
def __init__(
|
411
|
+
self,
|
412
|
+
|
413
|
+
...
|
414
|
+
|
415
|
+
jwt: JwtAdapter,
|
416
|
+
):
|
417
|
+
...
|
418
|
+
|
419
|
+
self._jwt = jwt
|
420
|
+
|
421
|
+
@post()
|
422
|
+
async def login(self, user: User):
|
423
|
+
payload = { ... }
|
424
|
+
|
425
|
+
await self._jwt.encode(payload)
|
426
|
+
|
427
|
+
@post
|
428
|
+
async def verifiy(self, token: str):
|
429
|
+
await self._jwt.decode(token)
|
430
|
+
|
431
|
+
|
432
|
+
def main() -> FastAPI:
|
433
|
+
...
|
434
|
+
|
435
|
+
builder.with_singleton(JwtAdapter)
|
436
|
+
builder.with_controllers([
|
437
|
+
UserController
|
438
|
+
])
|
439
|
+
|
440
|
+
...
|
441
|
+
```
|
442
|
+
|
443
|
+
### ACL
|
444
|
+
|
445
|
+
``` py
|
446
|
+
@ApiController("/users")
|
447
|
+
class UserController(ControllerBase):
|
448
|
+
|
449
|
+
@post()
|
450
|
+
async def get_user(
|
451
|
+
self,
|
452
|
+
user: Annotated[
|
453
|
+
UserInfo,
|
454
|
+
EndpointACL(
|
455
|
+
user_type="ADMIN",
|
456
|
+
persmission=[
|
457
|
+
"READ"
|
458
|
+
],
|
459
|
+
)
|
460
|
+
]
|
461
|
+
):
|
462
|
+
...
|
463
|
+
|
464
|
+
|
465
|
+
@router.get("")
|
466
|
+
async def get_users(
|
467
|
+
user: Annotated[
|
468
|
+
UserInfo,
|
469
|
+
EndpointACL("ADMIN")
|
470
|
+
]
|
471
|
+
)
|
472
|
+
...
|
473
|
+
```
|
@@ -0,0 +1,31 @@
|
|
1
|
+
qena_shared_lib/__init__.py,sha256=WokKEFaMNow6h2ZY_fyB-tiYnic-Ed6K6kyzKgg3Rlw,371
|
2
|
+
qena_shared_lib/application.py,sha256=2bpoEHW9FXRjNgBUuij2djfTLbhk01zymqp6kwasRq0,5470
|
3
|
+
qena_shared_lib/background.py,sha256=upWeJ747-4S4xsBOR-P2_oNar1YiaxCpnLhEDvnArmQ,3097
|
4
|
+
qena_shared_lib/exception_handlers.py,sha256=od_4g2Ro0HTeiIip_Saq6OCW0FMNjwLuaGevVWRPwFo,4824
|
5
|
+
qena_shared_lib/exceptions.py,sha256=cNeksC8ZavKgoqKLBik1NR-lCcdLZzITHMXww5deY5Y,6789
|
6
|
+
qena_shared_lib/http.py,sha256=90bmDfs522KxKBj2o0vO-MgmDqa3EZOfaNUFp1DuUdQ,23864
|
7
|
+
qena_shared_lib/logging.py,sha256=JL6bAmkK1BJA84rZNpvDEmrec3dogQRpSug4fj1Omkw,1618
|
8
|
+
qena_shared_lib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
qena_shared_lib/scheduler.py,sha256=kqrOpLpqaDIlbENWNkNtLQDmpuhBCvvnc8eentWh8Qk,11966
|
10
|
+
qena_shared_lib/security.py,sha256=mDozOTcazVz6QPN3Np8-IM2g9REqo_UCcQDE9HKlUEw,6005
|
11
|
+
qena_shared_lib/utils.py,sha256=bvPnjaFx7npVGSUO6IKdpiAHfUECsGRCeDW8UOjVckU,845
|
12
|
+
qena_shared_lib/dependencies/__init__.py,sha256=W12RgJbhqZ9GiSV1nLlHmpwPzvQv8t7f4JEoazM_WYg,350
|
13
|
+
qena_shared_lib/dependencies/http.py,sha256=3KopQjq05qbYROMPZjji2Zz7qQLU6_2u3qKisLrjLks,1469
|
14
|
+
qena_shared_lib/dependencies/miscellaneous.py,sha256=iGwAjatXb_JVSF13n1vdTRAgSKv19VtHo9ZbjjbkIco,753
|
15
|
+
qena_shared_lib/logstash/__init__.py,sha256=KlWFXqwPGwKM3yWnz0lTmeZymkobxFPPNeOgyfdGu4g,315
|
16
|
+
qena_shared_lib/logstash/_base.py,sha256=b_wWbdIpl9aIqGdnNI-oYuZmiiUhx5rWXIbZzURyOnU,15722
|
17
|
+
qena_shared_lib/logstash/_http_sender.py,sha256=Sq3bJOXuOafqloROGblnmyUd0RuU3zF0QLpQDYjiy-g,1765
|
18
|
+
qena_shared_lib/logstash/_tcp_sender.py,sha256=ncvc3wYxOJPkB7c_0W8vboaxMRJ_1UYGdA8LN2NXUpo,2306
|
19
|
+
qena_shared_lib/rabbitmq/__init__.py,sha256=WKAcKpRXXX9kqfHVEkyX6p2yIWzB07TMsBBWbV8nrKA,1209
|
20
|
+
qena_shared_lib/rabbitmq/_base.py,sha256=2_HYL5zHIl201H8O2Vu_R3aNC2S1SHstEU6ttHvtj3c,24224
|
21
|
+
qena_shared_lib/rabbitmq/_channel.py,sha256=R0xzZvLkeE4YWsyLhx8bPDramsTFDZIcCgfsDyFmSB4,5429
|
22
|
+
qena_shared_lib/rabbitmq/_exception_handlers.py,sha256=bhz-oteXcIEvYA-f4Mj6nFy66z-iVqk5rOARSx_8zSg,4733
|
23
|
+
qena_shared_lib/rabbitmq/_exceptions.py,sha256=Mg56Uk4eBEwTnWC91_WPS3_8d-yaqNkbb_32IpX_R-c,1208
|
24
|
+
qena_shared_lib/rabbitmq/_listener.py,sha256=1c6Qrz87hL10kaGobTZhI48v3ou88A5u371QdkyZIXA,44153
|
25
|
+
qena_shared_lib/rabbitmq/_pool.py,sha256=1_ftW0D3sPHI6v5c72oA03s05up4-1QqZ7d8icnZeNs,1951
|
26
|
+
qena_shared_lib/rabbitmq/_publisher.py,sha256=48JdpvNzFaw3Bw5LhunFxcjm3qNWz3AXKyXtcpzcohQ,2380
|
27
|
+
qena_shared_lib/rabbitmq/_rpc_client.py,sha256=E-J5PIfA1T3j1y9KoxjNTzSLdK95MbaePulNxiqGb-M,8724
|
28
|
+
qena_shared_lib/rabbitmq/_utils.py,sha256=5WZjYm4MdDrjL1dsrZuT8eKKiWfUef29_FJ262-hfSM,438
|
29
|
+
qena_shared_lib-0.1.0.dist-info/METADATA,sha256=0j068cW5vBiRC-PU30xRTayhck7EpqOZ1qlmIi-Zw7o,7809
|
30
|
+
qena_shared_lib-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
31
|
+
qena_shared_lib-0.1.0.dist-info/RECORD,,
|