qena-shared-lib 0.1.18__tar.gz → 0.1.20__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.
Files changed (64) hide show
  1. qena_shared_lib-0.1.18/README.md → qena_shared_lib-0.1.20/PKG-INFO +411 -21
  2. qena_shared_lib-0.1.18/PKG-INFO → qena_shared_lib-0.1.20/README.md +375 -51
  3. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/pyproject.toml +26 -16
  4. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/__init__.py +18 -1
  5. qena_shared_lib-0.1.20/src/qena_shared_lib/alias.py +27 -0
  6. qena_shared_lib-0.1.20/src/qena_shared_lib/cache.py +61 -0
  7. qena_shared_lib-0.1.20/src/qena_shared_lib/enums.py +8 -0
  8. qena_shared_lib-0.1.20/src/qena_shared_lib/eventbus.py +373 -0
  9. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/exceptions.py +2 -5
  10. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/http/__init__.py +23 -0
  11. qena_shared_lib-0.1.20/src/qena_shared_lib/http/_request.py +24 -0
  12. qena_shared_lib-0.1.20/src/qena_shared_lib/http/_response.py +24 -0
  13. qena_shared_lib-0.1.20/src/qena_shared_lib/mongodb.py +599 -0
  14. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/rabbitmq/__init__.py +2 -0
  15. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/rabbitmq/_listener.py +8 -32
  16. qena_shared_lib-0.1.20/src/qena_shared_lib/rabbitmq/message/__init__.py +22 -0
  17. qena_shared_lib-0.1.20/src/qena_shared_lib/rabbitmq/message/_inbound.py +13 -0
  18. qena_shared_lib-0.1.20/src/qena_shared_lib/rabbitmq/message/_outbound.py +13 -0
  19. qena_shared_lib-0.1.20/src/qena_shared_lib/redis.py +47 -0
  20. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/remotelogging/_base.py +14 -8
  21. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/remotelogging/logstash/_base.py +1 -0
  22. qena_shared_lib-0.1.20/src/qena_shared_lib/sync.py +91 -0
  23. qena_shared_lib-0.1.18/.gitignore +0 -177
  24. qena_shared_lib-0.1.18/.pre-commit-config.yaml +0 -27
  25. qena_shared_lib-0.1.18/CHANGELOG.md +0 -81
  26. qena_shared_lib-0.1.18/tests/conftest.py +0 -32
  27. qena_shared_lib-0.1.18/tests/test_application.py +0 -629
  28. qena_shared_lib-0.1.18/tests/test_background.py +0 -87
  29. qena_shared_lib-0.1.18/tests/test_dependencies.py +0 -136
  30. qena_shared_lib-0.1.18/tests/test_kafka.py +0 -1211
  31. qena_shared_lib-0.1.18/tests/test_logstash.py +0 -585
  32. qena_shared_lib-0.1.18/tests/test_rabbitmq.py +0 -3509
  33. qena_shared_lib-0.1.18/tests/test_scheduler.py +0 -111
  34. qena_shared_lib-0.1.18/tests/test_security.py +0 -1244
  35. qena_shared_lib-0.1.18/tests/utils.py +0 -5
  36. qena_shared_lib-0.1.18/uv.lock +0 -1667
  37. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/application.py +0 -0
  38. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/background.py +0 -0
  39. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/dependencies/__init__.py +0 -0
  40. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/dependencies/http.py +0 -0
  41. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/dependencies/miscellaneous.py +0 -0
  42. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/exception_handling.py +0 -0
  43. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/http/_base.py +0 -0
  44. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/http/_exception_handlers.py +0 -0
  45. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/kafka/__init__.py +0 -0
  46. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/kafka/_base.py +0 -0
  47. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/kafka/_consumer.py +0 -0
  48. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/kafka/_exception_handlers.py +0 -0
  49. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/kafka/_producer.py +0 -0
  50. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/logging.py +0 -0
  51. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/py.typed +0 -0
  52. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/rabbitmq/_base.py +0 -0
  53. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/rabbitmq/_channel.py +0 -0
  54. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/rabbitmq/_exception_handlers.py +0 -0
  55. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/rabbitmq/_pool.py +0 -0
  56. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/rabbitmq/_publisher.py +0 -0
  57. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/rabbitmq/_rpc_client.py +0 -0
  58. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/remotelogging/__init__.py +0 -0
  59. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/remotelogging/logstash/__init__.py +0 -0
  60. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/remotelogging/logstash/_http_sender.py +0 -0
  61. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/remotelogging/logstash/_tcp_sender.py +0 -0
  62. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/scheduler.py +0 -0
  63. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/security.py +0 -0
  64. {qena_shared_lib-0.1.18 → qena_shared_lib-0.1.20}/src/qena_shared_lib/utils.py +0 -0
@@ -1,26 +1,105 @@
1
+ Metadata-Version: 2.3
2
+ Name: qena-shared-lib
3
+ Version: 0.1.20
4
+ Summary: A shared tools for other services
5
+ Requires-Dist: fastapi[all]==0.115.6
6
+ Requires-Dist: prometheus-client==0.21.1
7
+ Requires-Dist: prometheus-fastapi-instrumentator==7.0.2
8
+ Requires-Dist: punq==0.7.0
9
+ Requires-Dist: pydantic-core==2.27.1
10
+ Requires-Dist: pydantic==2.10.3
11
+ Requires-Dist: starlette==0.41.3
12
+ Requires-Dist: typing-extensions==4.12.2
13
+ Requires-Dist: aiokafka==0.12.0 ; extra == 'all'
14
+ Requires-Dist: cronsim==2.6 ; extra == 'all'
15
+ Requires-Dist: jwt==1.3.1 ; extra == 'all'
16
+ Requires-Dist: passlib[bcrypt]==1.7.4 ; extra == 'all'
17
+ Requires-Dist: pika==1.3.2 ; extra == 'all'
18
+ Requires-Dist: pymongo==4.15.3 ; extra == 'all'
19
+ Requires-Dist: redis==7.1.0 ; extra == 'all'
20
+ Requires-Dist: aiokafka==0.12.0 ; extra == 'kafka'
21
+ Requires-Dist: pymongo==4.15.3 ; extra == 'mongodb'
22
+ Requires-Dist: pika==1.3.2 ; extra == 'rabbitmq'
23
+ Requires-Dist: redis==7.1.0 ; extra == 'redis'
24
+ Requires-Dist: cronsim==2.6 ; extra == 'scheduler'
25
+ Requires-Dist: jwt==1.3.1 ; extra == 'security'
26
+ Requires-Dist: passlib[bcrypt]==1.7.4 ; extra == 'security'
27
+ Requires-Python: >=3.10
28
+ Provides-Extra: all
29
+ Provides-Extra: kafka
30
+ Provides-Extra: mongodb
31
+ Provides-Extra: rabbitmq
32
+ Provides-Extra: redis
33
+ Provides-Extra: scheduler
34
+ Provides-Extra: security
35
+ Description-Content-Type: text/markdown
36
+
1
37
  # Qena shared lib
2
38
 
3
39
  A shared tools for other services. It includes.
4
40
 
5
41
  - FastAPI app builder
6
42
  - A wrapper around fastapi to make it class based.
7
- - RabbitMQ utility class to listen, respond, publish and make rpc request.
43
+ - RabbitMQ utility class to listen , respond , publish and make rpc request.
8
44
  - Remote logging
9
45
  - Logstash utility class to log message in `ecs` ( elastic common schema ).
10
- - A simple task scheduler, to schedule task to run in specific time.
46
+ - A simple task scheduler , to schedule task to run in specific time.
11
47
  - Background task runner.
12
- - Security tools ( password hasher, jwt, acl ).
13
- - IOC container to manager dependencies used across fastapi, rabbitmq manager and schedule manager.
48
+ - Security tools ( password hasher , jwt , acl ).
49
+ - IOC container to manager dependencies used across fastapi , rabbitmq manager and schedule manager.
50
+ - Kafka producer and consumer wrapper.
51
+ - Mongodb client wrapper , with repository and index manager.
52
+ - Redis wrapper with cache and distributed lock manager.
53
+
54
+ ## Installation
14
55
 
15
- # Usage
56
+ It is prefered to use [astral.sh / uv](https://docs.astral.sh/uv) as a package manager.
16
57
 
17
- ## Environment variables
58
+ ``` sh
59
+ $ uv add qena-shared-lib[all]
60
+ ```
61
+ to install all extras , or specific extras `kafka` , `rabbitmq` , `scheduler` , `security` , `redis` or `mongodb`.
62
+
63
+ ## Usage
64
+
65
+ - [Environment variables](#environment-variables)
66
+ - [Http](#http)
67
+ - [Lifespan](#lifespan)
68
+ - [Dependencies](#dependencies)
69
+ - [Controllers](#controllers)
70
+ - [Routers](#routers)
71
+ - [Remote logging](#remote-logging)
72
+ - [Logstash](#logstash)
73
+ - [Rabbitmq](#rabbitmq)
74
+ - [Publisher](#publisher)
75
+ - [RPC client](#rpc-client)
76
+ - [Flow control](#flow-control)
77
+ - [Rpc reply](#rpc-reply)
78
+ - [Retry consumer](#retry-consumer)
79
+ - [Scheduler](#scheduler)
80
+ - [Background](#background)
81
+ - [Security](#security)
82
+ - [Password hasher](#password-hasher)
83
+ - [JWT](#jwt)
84
+ - [ACL](#acl)
85
+ - [Kafka](#kafka)
86
+ - [Producer](#producer)
87
+ - [Consumer](#consumer)
88
+ - [Mongodb](#mongodb)
89
+ - [Aggregation](#aggregation)
90
+ - [Index](#index)
91
+ - [Crud](#crud)
92
+ - [Redis](#redis)
93
+ - [Cache](#cache)
94
+ - [Distribute lock](#distribute-lock)
95
+
96
+ ### Environment variables
18
97
 
19
98
  - `QENA_SHARED_LIB_LOGGING_LOGGER_NAME` root logger name.
20
99
  - `QENA_SHARED_LIB_SECURITY_UNAUTHORIZED_RESPONSE_CODE` an integer response on an authorized access of resource.
21
100
  - `QENA_SHARED_LIB_SECURITY_TOKEN_HEADER` to header key for jwt token.
22
101
 
23
- ## Http
102
+ ### Http
24
103
 
25
104
  To create fastapi app.
26
105
 
@@ -160,9 +239,10 @@ def main() -> FastAPI:
160
239
  ...
161
240
  ```
162
241
 
163
- ## Remote logging
242
+ ### Remote logging
243
+
244
+ #### Logstash
164
245
 
165
- ### Logstash
166
246
  ``` py
167
247
  from qena_shared_lib.remotelogging import BaseRemoteLogSender
168
248
  from qena_shared_lib.remotelogging.logstash import HTTPSender, # TCPSender
@@ -213,7 +293,7 @@ def log_message(
213
293
  remote_logger.info(message)
214
294
  ```
215
295
 
216
- ## Rabbitmq
296
+ ### Rabbitmq
217
297
 
218
298
  To create rabbitmq connection manager.
219
299
 
@@ -261,7 +341,7 @@ def main() -> FastAPI:
261
341
  ...
262
342
  ```
263
343
 
264
- ### Publisher
344
+ #### Publisher
265
345
 
266
346
  ``` py
267
347
  @router.post("")
@@ -278,7 +358,7 @@ async def store_user(
278
358
  # await publisher.publish_as_arguments(user)
279
359
  ```
280
360
 
281
- ### RPC client
361
+ #### RPC client
282
362
 
283
363
  ``` py
284
364
  @router.get("")
@@ -297,7 +377,7 @@ async def get_user(
297
377
  return user
298
378
  ```
299
379
 
300
- ### Flow control
380
+ #### Flow control
301
381
 
302
382
  ``` py
303
383
  from qena_shared_lib.rabbitmq import ... , ListenerContext
@@ -316,7 +396,7 @@ class UserConsumer(ListenerBase):
316
396
 
317
397
  ```
318
398
 
319
- ### Rpc reply
399
+ #### Rpc reply
320
400
 
321
401
  Optionally it is possible to reply to rpc calls, through.
322
402
 
@@ -336,7 +416,7 @@ class UserWorker(ListenerBase):
336
416
  ...
337
417
  ```
338
418
 
339
- ### Retry consumer
419
+ #### Retry consumer
340
420
 
341
421
  Consumer can retry to consumer a message in an event of failure.
342
422
 
@@ -410,7 +490,7 @@ def main() -> FastAPI:
410
490
 
411
491
 
412
492
 
413
- ## Scheduler
493
+ ### Scheduler
414
494
 
415
495
  ``` py
416
496
  from qena_shared_lib.scheduler import (
@@ -471,7 +551,7 @@ def main() -> FastAPI:
471
551
  ...
472
552
  ```
473
553
 
474
- ## Background
554
+ ### Background
475
555
 
476
556
  ``` py
477
557
  from qena_shared_lib.background import Background
@@ -515,9 +595,9 @@ async def process_data(
515
595
  background.add_task(BackgroundTask(data_processor, data))
516
596
  ```
517
597
 
518
- ## Security
598
+ ### Security
519
599
 
520
- ### Password hasher
600
+ #### Password hasher
521
601
 
522
602
  ``` py
523
603
  from qena_shared_lib.security import PasswordHasher
@@ -549,7 +629,7 @@ def main() -> FastAPI:
549
629
  ...
550
630
  ```
551
631
 
552
- ### JWT
632
+ #### JWT
553
633
 
554
634
  ``` py
555
635
  from qena_shared_lib.security import JwtAdapter
@@ -591,7 +671,7 @@ def main() -> FastAPI:
591
671
  ...
592
672
  ```
593
673
 
594
- ### ACL
674
+ #### ACL
595
675
 
596
676
  ``` py
597
677
  from qena_shared_lib.security import Authorization
@@ -625,3 +705,313 @@ async def get_users(
625
705
  )
626
706
  ...
627
707
  ```
708
+
709
+ ### Kafka
710
+
711
+ ``` py
712
+ from qena_shared_lib.kafka import KafkaManager
713
+
714
+
715
+ @asynccontextmanager
716
+ async def lifespan(app: FastAPI):
717
+ kafka = get_service(KafkaManager)
718
+
719
+ await kafka.connect()
720
+
721
+ yield
722
+
723
+ await kafka.disconnect()
724
+
725
+
726
+ def main() -> FastAPI:
727
+ ...
728
+
729
+ kafka = KafkaManager(
730
+ remote_logger=...,
731
+ bootstrap_servers="127.0.0.1:9092",
732
+ )
733
+
734
+ builder.with_singleton(
735
+ service=KafkaManager,
736
+ instance=kafka,
737
+ )
738
+
739
+ ...
740
+ ```
741
+
742
+ #### Producer
743
+
744
+ ``` py
745
+ class UserService:
746
+ def __init__(self, kafka: KafkaManager):
747
+ self._kafka = kafka
748
+
749
+ async def create_user(self):
750
+ ...
751
+
752
+ async with await self._kafka.producer("user") as producer:
753
+ await producer.send(key="some_key", value=user)
754
+
755
+ ...
756
+ ```
757
+
758
+ #### Consumer
759
+
760
+ ``` py
761
+ from qena_shared_lib.kafka import (
762
+ ConsumerBase,
763
+ consume,
764
+ consumer,
765
+ )
766
+
767
+
768
+ @consumer(["user"])
769
+ class USerConsumer(ConsumerBase):
770
+ def __init__(self, user_service: UserService):
771
+ self._user_service = user_service
772
+
773
+ @consume()
774
+ async def user_created(self, key: Any | None, value: Any | None):
775
+ ...
776
+
777
+ await self._user_service.create_user(...)
778
+
779
+ ...
780
+ ```
781
+
782
+ ### Mongodb
783
+
784
+ ``` py
785
+ from qena_shared_lib.mongodb import MongoDBManager
786
+
787
+
788
+ @asynccontextmanager
789
+ async def lifespan(app: FastAPI):
790
+ db = get_service(MongoDBManager)
791
+
792
+ await db.connect()
793
+
794
+ yield
795
+
796
+ await db.disconnect()
797
+
798
+
799
+ def main() -> FastAPI:
800
+ ...
801
+
802
+ db = MongoDBManager(
803
+ connection_string="mongodb://127.0.0.1:27017",
804
+ db="userDb"
805
+ )
806
+
807
+ builder.with_singleton(
808
+ service=MongoDBManager,
809
+ instance=db
810
+ )
811
+
812
+ ...
813
+ ```
814
+
815
+ #### Crud
816
+
817
+ ``` py
818
+ from qena_shared_lib.mongodb import (
819
+ Document,
820
+ MongoDBManager,
821
+ ProjectedDocument,
822
+ RepositoryBase,
823
+ )
824
+
825
+
826
+ class User(Document):
827
+ __collection_name__ = "users"
828
+
829
+ full_name: str
830
+ phone: str
831
+
832
+
833
+ class FullNameProjectedUser(ProjectedDocument):
834
+ full_name: str
835
+
836
+
837
+ class UserRepository(RepositoryBase[User]):
838
+ pass
839
+
840
+
841
+ class UserService:
842
+ def __init__(self, user_repository: UserRepository):
843
+ self._user_repository = user_repository
844
+
845
+ async def add_user(self):
846
+ await self._user_repository.insert(
847
+ User(
848
+ full_name="user one",
849
+ phone="+251900000000"
850
+ )
851
+ )
852
+
853
+ async def get_user(self):
854
+ user = await self._user_repository.find_by_filter(
855
+ filter={"phone": "+251900000000"}
856
+ )
857
+
858
+ async def get_user_fullname(self):
859
+ user = await self._user_repository.find_by_filter(
860
+ filter={"phone": "+251900000000"}, projection=FullNameProjectedUser
861
+ )
862
+
863
+ async def update_user(self):
864
+ user = await self._user_repository.find_by_filter(
865
+ filter={"phone": "+251900000000"}, projection
866
+ )
867
+ user.phone = "+251900000001"
868
+
869
+ await user_repository.replace(user)
870
+ ```
871
+
872
+ #### Aggregation
873
+
874
+ ``` py
875
+ class AggregatedUser(AggregatedDocument):
876
+ __pipeline__ = [
877
+ {"$match": {"phone": {"$in": ["+251900000000", "+251900000001"]}}},
878
+ {"$project": {"fullName": True}},
879
+ ]
880
+
881
+ full_name: str
882
+
883
+ class UserService:
884
+ ...
885
+
886
+ async def get_user_fullnames(self):
887
+ users = user_repository.aggregate(aggregation=AggregatedUser)
888
+
889
+ ...
890
+ ```
891
+
892
+ #### Index
893
+
894
+ ``` py
895
+ from qena_shared_lib.mongodb import Document, IndexManager, IndexModel
896
+
897
+
898
+ class User(Document):
899
+ __collection_name__ = "users"
900
+ __indexes__ = [IndexModel("phone")]
901
+
902
+ full_name: str
903
+ phone: str
904
+
905
+
906
+ async def manage_indexes():
907
+ ...
908
+
909
+ index_manager = IndexManager(db=db, documents=[User])
910
+
911
+ await index_manager.create_indexes
912
+
913
+ ...
914
+
915
+ await index_manager.drop_indexes()
916
+
917
+ ...
918
+ ```
919
+
920
+ ### Redis
921
+
922
+ ``` py
923
+ from qena_shared_lib.redis import RedisManager
924
+
925
+
926
+ @asynccontextmanager
927
+ async def lifespan(app: FastAPI):
928
+ redis_manager = get_service(RedisManager)
929
+
930
+ await dredis_managerb.connect()
931
+
932
+ yield
933
+
934
+ await redis_manager.disconnect()
935
+
936
+
937
+ def main() -> FastAPI:
938
+ ...
939
+
940
+ redis_manager = RedisManager("redis://127.0.0.1:6379")
941
+
942
+ builder.with_singleton(
943
+ service=RedisManager,
944
+ instance=redis_manager
945
+ )
946
+
947
+ ...
948
+ ```
949
+
950
+ #### Cache
951
+
952
+ ``` py
953
+ from qena_shared_lib.cache import CachedObject, CacheManager
954
+
955
+
956
+ def main() -> FastAPI:
957
+ ...
958
+
959
+ cache_manager = CacheManager()
960
+
961
+ redis_manager.add(cache_manager)
962
+ builder.with_singleton(
963
+ service=CacheManager,
964
+ instance=cache_manager
965
+ )
966
+
967
+ ...
968
+
969
+
970
+ class UserCache(CachedObject):
971
+ full_name: str
972
+
973
+
974
+ class UserService:
975
+ def __init__(self, cache_manager: CacheManager):
976
+ self._cache_manager = cache_manager
977
+
978
+ async def cache_user(self):
979
+ await self._cache_manager.set(
980
+ UserCache(full_name="user one")
981
+ )
982
+
983
+ async def get_cached_user(self):
984
+ user_cache = await self._cache_manager.get(UserCache)
985
+
986
+ async def unset_cached_user(self):
987
+ await self._cache_manager.unset(UserCache)
988
+ ```
989
+
990
+ #### Distribute lock
991
+
992
+ ``` py
993
+ from qena_shared_lib.sync import DistributedLockManager
994
+
995
+
996
+ def main() -> FastAPI:
997
+ ...
998
+
999
+ distributed_lock_manager = DistributedLockManager()
1000
+
1001
+ redis_manager.add(distributed_lock_manager)
1002
+ builder.with_singleton(
1003
+ service=DistributedLockManager,
1004
+ instance=distributed_lock_manager
1005
+ )
1006
+
1007
+ ...
1008
+
1009
+
1010
+ class UserService:
1011
+ def __init__(self, distributed_lock_manager: DistributedLockManager):
1012
+ self._distributed_lock_manager = distributed_lock_manager
1013
+
1014
+ async def create_user(self):
1015
+ async with self._distributed_lock_manager("user_one_create") as _:
1016
+ ...
1017
+ ```