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.
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any