qena-shared-lib 0.1.0__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.0/.gitignore +176 -0
- qena_shared_lib-0.1.0/.pre-commit-config.yaml +13 -0
- qena_shared_lib-0.1.0/PKG-INFO +473 -0
- qena_shared_lib-0.1.0/README.md +456 -0
- qena_shared_lib-0.1.0/pyproject.toml +75 -0
- qena_shared_lib-0.1.0/requirements.txt +18 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/__init__.py +27 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/application.py +190 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/background.py +109 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/dependencies/__init__.py +19 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/dependencies/http.py +62 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/dependencies/miscellaneous.py +35 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/exception_handlers.py +165 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/exceptions.py +319 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/http.py +631 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/logging.py +63 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/logstash/__init__.py +17 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/logstash/_base.py +573 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/logstash/_http_sender.py +61 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/logstash/_tcp_sender.py +84 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/py.typed +0 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/rabbitmq/__init__.py +52 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/rabbitmq/_base.py +741 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/rabbitmq/_channel.py +196 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/rabbitmq/_exception_handlers.py +159 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/rabbitmq/_exceptions.py +46 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/rabbitmq/_listener.py +1292 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/rabbitmq/_pool.py +74 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/rabbitmq/_publisher.py +73 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/rabbitmq/_rpc_client.py +286 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/rabbitmq/_utils.py +18 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/scheduler.py +402 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/security.py +205 -0
- qena_shared_lib-0.1.0/src/qena_shared_lib/utils.py +28 -0
- qena_shared_lib-0.1.0/tests/conftest.py +23 -0
- qena_shared_lib-0.1.0/tests/test_application.py +631 -0
- qena_shared_lib-0.1.0/tests/test_background.py +87 -0
- qena_shared_lib-0.1.0/tests/test_dependencies.py +132 -0
- qena_shared_lib-0.1.0/tests/test_logstash.py +536 -0
- qena_shared_lib-0.1.0/tests/test_rabbitmq.py +2876 -0
- qena_shared_lib-0.1.0/tests/test_scheduler.py +103 -0
- qena_shared_lib-0.1.0/tests/test_security.py +985 -0
- qena_shared_lib-0.1.0/uv.lock +1542 -0
@@ -0,0 +1,176 @@
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
2
|
+
__pycache__/
|
3
|
+
*.py[cod]
|
4
|
+
*$py.class
|
5
|
+
|
6
|
+
# C extensions
|
7
|
+
*.so
|
8
|
+
|
9
|
+
# Distribution / packaging
|
10
|
+
.Python
|
11
|
+
build/
|
12
|
+
develop-eggs/
|
13
|
+
dist/
|
14
|
+
downloads/
|
15
|
+
eggs/
|
16
|
+
.eggs/
|
17
|
+
lib/
|
18
|
+
lib64/
|
19
|
+
parts/
|
20
|
+
sdist/
|
21
|
+
var/
|
22
|
+
wheels/
|
23
|
+
share/python-wheels/
|
24
|
+
*.egg-info/
|
25
|
+
.installed.cfg
|
26
|
+
*.egg
|
27
|
+
MANIFEST
|
28
|
+
|
29
|
+
# PyInstaller
|
30
|
+
# Usually these files are written by a python script from a template
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
32
|
+
*.manifest
|
33
|
+
*.spec
|
34
|
+
|
35
|
+
# Installer logs
|
36
|
+
pip-log.txt
|
37
|
+
pip-delete-this-directory.txt
|
38
|
+
|
39
|
+
# Unit test / coverage reports
|
40
|
+
htmlcov/
|
41
|
+
.tox/
|
42
|
+
.nox/
|
43
|
+
.coverage
|
44
|
+
.coverage.*
|
45
|
+
.cache
|
46
|
+
nosetests.xml
|
47
|
+
coverage.xml
|
48
|
+
*.cover
|
49
|
+
*.py,cover
|
50
|
+
.hypothesis/
|
51
|
+
.pytest_cache/
|
52
|
+
cover/
|
53
|
+
|
54
|
+
# Translations
|
55
|
+
*.mo
|
56
|
+
*.pot
|
57
|
+
|
58
|
+
# Django stuff:
|
59
|
+
*.log
|
60
|
+
local_settings.py
|
61
|
+
db.sqlite3
|
62
|
+
db.sqlite3-journal
|
63
|
+
|
64
|
+
# Flask stuff:
|
65
|
+
instance/
|
66
|
+
.webassets-cache
|
67
|
+
|
68
|
+
# Scrapy stuff:
|
69
|
+
.scrapy
|
70
|
+
|
71
|
+
# Sphinx documentation
|
72
|
+
docs/_build/
|
73
|
+
|
74
|
+
# PyBuilder
|
75
|
+
.pybuilder/
|
76
|
+
target/
|
77
|
+
|
78
|
+
# Jupyter Notebook
|
79
|
+
.ipynb_checkpoints
|
80
|
+
|
81
|
+
# IPython
|
82
|
+
profile_default/
|
83
|
+
ipython_config.py
|
84
|
+
|
85
|
+
# pyenv
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
88
|
+
# .python-version
|
89
|
+
|
90
|
+
# pipenv
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
94
|
+
# install all needed dependencies.
|
95
|
+
#Pipfile.lock
|
96
|
+
|
97
|
+
# UV
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
100
|
+
# commonly ignored for libraries.
|
101
|
+
#uv.lock
|
102
|
+
|
103
|
+
# poetry
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
106
|
+
# commonly ignored for libraries.
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
108
|
+
#poetry.lock
|
109
|
+
|
110
|
+
# pdm
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
112
|
+
#pdm.lock
|
113
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
114
|
+
# in version control.
|
115
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
116
|
+
.pdm.toml
|
117
|
+
.pdm-python
|
118
|
+
.pdm-build/
|
119
|
+
|
120
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
121
|
+
__pypackages__/
|
122
|
+
|
123
|
+
# Celery stuff
|
124
|
+
celerybeat-schedule
|
125
|
+
celerybeat.pid
|
126
|
+
|
127
|
+
# SageMath parsed files
|
128
|
+
*.sage.py
|
129
|
+
|
130
|
+
# Environments
|
131
|
+
.env
|
132
|
+
.venv
|
133
|
+
env/
|
134
|
+
venv/
|
135
|
+
ENV/
|
136
|
+
env.bak/
|
137
|
+
venv.bak/
|
138
|
+
|
139
|
+
# Spyder project settings
|
140
|
+
.spyderproject
|
141
|
+
.spyproject
|
142
|
+
|
143
|
+
# Rope project settings
|
144
|
+
.ropeproject
|
145
|
+
|
146
|
+
# mkdocs documentation
|
147
|
+
/site
|
148
|
+
|
149
|
+
# mypy
|
150
|
+
.mypy_cache/
|
151
|
+
.dmypy.json
|
152
|
+
dmypy.json
|
153
|
+
|
154
|
+
# Pyre type checker
|
155
|
+
.pyre/
|
156
|
+
|
157
|
+
# pytype static type analyzer
|
158
|
+
.pytype/
|
159
|
+
|
160
|
+
# Cython debug symbols
|
161
|
+
cython_debug/
|
162
|
+
|
163
|
+
# PyCharm
|
164
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
165
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
166
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
167
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
168
|
+
.idea/
|
169
|
+
|
170
|
+
# PyPI configuration file
|
171
|
+
.pypirc
|
172
|
+
|
173
|
+
# additional
|
174
|
+
.ruff_cache
|
175
|
+
.vscode
|
176
|
+
scheduler.lock
|
@@ -0,0 +1,13 @@
|
|
1
|
+
repos:
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
3
|
+
rev: v5.0.0
|
4
|
+
hooks:
|
5
|
+
- id: check-yaml
|
6
|
+
- id: end-of-file-fixer
|
7
|
+
- id: trailing-whitespace
|
8
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
9
|
+
rev: v0.9.2
|
10
|
+
hooks:
|
11
|
+
- id: ruff
|
12
|
+
args: [ --fix ]
|
13
|
+
- id: ruff-format
|
@@ -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
|
+
```
|