fastapi-rtk 0.2.27__py3-none-any.whl → 1.0.13__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.
- fastapi_rtk/__init__.py +39 -35
- fastapi_rtk/_version.py +1 -0
- fastapi_rtk/api/model_rest_api.py +476 -221
- fastapi_rtk/auth/auth.py +0 -9
- fastapi_rtk/backends/generic/__init__.py +6 -0
- fastapi_rtk/backends/generic/column.py +21 -12
- fastapi_rtk/backends/generic/db.py +42 -7
- fastapi_rtk/backends/generic/filters.py +21 -16
- fastapi_rtk/backends/generic/interface.py +14 -8
- fastapi_rtk/backends/generic/model.py +19 -11
- fastapi_rtk/backends/sqla/__init__.py +1 -0
- fastapi_rtk/backends/sqla/db.py +77 -17
- fastapi_rtk/backends/sqla/extensions/audit/audit.py +401 -189
- fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +15 -12
- fastapi_rtk/backends/sqla/filters.py +50 -21
- fastapi_rtk/backends/sqla/interface.py +96 -34
- fastapi_rtk/backends/sqla/model.py +56 -39
- fastapi_rtk/bases/__init__.py +20 -0
- fastapi_rtk/bases/db.py +94 -7
- fastapi_rtk/bases/file_manager.py +47 -3
- fastapi_rtk/bases/filter.py +22 -0
- fastapi_rtk/bases/interface.py +49 -5
- fastapi_rtk/bases/model.py +3 -0
- fastapi_rtk/bases/session.py +2 -0
- fastapi_rtk/cli/cli.py +62 -9
- fastapi_rtk/cli/commands/__init__.py +23 -0
- fastapi_rtk/cli/{db.py → commands/db/__init__.py} +107 -50
- fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/env.py +2 -3
- fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/env.py +10 -9
- fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/script.py.mako +3 -1
- fastapi_rtk/cli/{export.py → commands/export.py} +12 -10
- fastapi_rtk/cli/{security.py → commands/security.py} +73 -7
- fastapi_rtk/cli/commands/translate.py +299 -0
- fastapi_rtk/cli/decorators.py +9 -4
- fastapi_rtk/cli/utils.py +46 -0
- fastapi_rtk/config.py +41 -1
- fastapi_rtk/const.py +29 -1
- fastapi_rtk/db.py +76 -40
- fastapi_rtk/decorators.py +1 -1
- fastapi_rtk/dependencies.py +134 -62
- fastapi_rtk/exceptions.py +51 -1
- fastapi_rtk/fastapi_react_toolkit.py +186 -171
- fastapi_rtk/file_managers/file_manager.py +8 -6
- fastapi_rtk/file_managers/s3_file_manager.py +69 -33
- fastapi_rtk/globals.py +22 -12
- fastapi_rtk/lang/__init__.py +3 -0
- fastapi_rtk/lang/babel/__init__.py +4 -0
- fastapi_rtk/lang/babel/cli.py +40 -0
- fastapi_rtk/lang/babel/config.py +17 -0
- fastapi_rtk/lang/babel.cfg +1 -0
- fastapi_rtk/lang/lazy_text.py +120 -0
- fastapi_rtk/lang/messages.pot +238 -0
- fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
- fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +248 -0
- fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
- fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +244 -0
- fastapi_rtk/manager.py +355 -37
- fastapi_rtk/mixins.py +12 -0
- fastapi_rtk/routers.py +208 -72
- fastapi_rtk/schemas.py +142 -39
- fastapi_rtk/security/sqla/apis.py +39 -13
- fastapi_rtk/security/sqla/models.py +8 -23
- fastapi_rtk/security/sqla/security_manager.py +369 -11
- fastapi_rtk/setting.py +446 -88
- fastapi_rtk/types.py +94 -27
- fastapi_rtk/utils/__init__.py +8 -0
- fastapi_rtk/utils/async_task_runner.py +286 -61
- fastapi_rtk/utils/csv_json_converter.py +243 -40
- fastapi_rtk/utils/hooks.py +34 -0
- fastapi_rtk/utils/merge_schema.py +3 -3
- fastapi_rtk/utils/multiple_async_contexts.py +21 -0
- fastapi_rtk/utils/pydantic.py +46 -1
- fastapi_rtk/utils/run_utils.py +31 -1
- fastapi_rtk/utils/self_dependencies.py +1 -1
- fastapi_rtk/utils/use_default_when_none.py +1 -1
- fastapi_rtk/version.py +6 -1
- fastapi_rtk-1.0.13.dist-info/METADATA +28 -0
- fastapi_rtk-1.0.13.dist-info/RECORD +133 -0
- {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/WHEEL +1 -2
- fastapi_rtk/backends/gremlinpython/__init__.py +0 -108
- fastapi_rtk/backends/gremlinpython/column.py +0 -208
- fastapi_rtk/backends/gremlinpython/db.py +0 -228
- fastapi_rtk/backends/gremlinpython/exceptions.py +0 -34
- fastapi_rtk/backends/gremlinpython/filters.py +0 -461
- fastapi_rtk/backends/gremlinpython/interface.py +0 -734
- fastapi_rtk/backends/gremlinpython/model.py +0 -364
- fastapi_rtk/backends/gremlinpython/session.py +0 -23
- fastapi_rtk/cli/commands.py +0 -295
- fastapi_rtk-0.2.27.dist-info/METADATA +0 -23
- fastapi_rtk-0.2.27.dist-info/RECORD +0 -126
- fastapi_rtk-0.2.27.dist-info/top_level.txt +0 -1
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/README +0 -0
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/alembic.ini.mako +0 -0
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/script.py.mako +0 -0
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/README +0 -0
- /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/alembic.ini.mako +0 -0
- {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/entry_points.txt +0 -0
- {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,25 +5,37 @@ import os
|
|
|
5
5
|
from contextlib import asynccontextmanager
|
|
6
6
|
from typing import Awaitable, Callable
|
|
7
7
|
|
|
8
|
+
import fastapi
|
|
8
9
|
import Secweb
|
|
9
10
|
import Secweb.ContentSecurityPolicy
|
|
11
|
+
import starlette.types
|
|
10
12
|
from fastapi import Depends, FastAPI, HTTPException, Request
|
|
11
13
|
from fastapi.responses import HTMLResponse, StreamingResponse
|
|
12
14
|
from fastapi.staticfiles import StaticFiles
|
|
13
15
|
from fastapi.templating import Jinja2Templates
|
|
16
|
+
from fastapi_babel import BabelMiddleware
|
|
14
17
|
from jinja2 import Environment, TemplateNotFound, select_autoescape
|
|
15
18
|
from prometheus_fastapi_instrumentator import Instrumentator
|
|
16
|
-
from sqlalchemy import and_, select
|
|
17
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
18
|
-
from sqlalchemy.orm import Session
|
|
19
19
|
from starlette.routing import _DefaultLifespan
|
|
20
20
|
|
|
21
21
|
from .api.model_rest_api import ModelRestApi
|
|
22
22
|
from .auth import Auth
|
|
23
|
-
from .
|
|
24
|
-
from .
|
|
23
|
+
from .backends.sqla.session import SQLASession
|
|
24
|
+
from .cli.commands.db import upgrade
|
|
25
|
+
from .cli.commands.translate import init_babel_cli
|
|
26
|
+
from .const import (
|
|
27
|
+
BASE_APIS,
|
|
28
|
+
DEFAULT_SECWEB_PARAMS,
|
|
29
|
+
INTERNAL_LANG_FOLDER,
|
|
30
|
+
ErrorCode,
|
|
31
|
+
logger,
|
|
32
|
+
)
|
|
25
33
|
from .db import db
|
|
26
|
-
from .dependencies import
|
|
34
|
+
from .dependencies import (
|
|
35
|
+
set_global_background_tasks,
|
|
36
|
+
set_global_request,
|
|
37
|
+
set_global_user,
|
|
38
|
+
)
|
|
27
39
|
from .globals import GlobalsMiddleware, g
|
|
28
40
|
from .middlewares import (
|
|
29
41
|
ProfilerMiddleware,
|
|
@@ -42,7 +54,7 @@ from .security.sqla.apis import (
|
|
|
42
54
|
from .security.sqla.models import Api, Permission, PermissionApi, Role
|
|
43
55
|
from .security.sqla.security_manager import SecurityManager
|
|
44
56
|
from .setting import Setting
|
|
45
|
-
from .utils import deep_merge, safe_call, smart_run
|
|
57
|
+
from .utils import deep_merge, lazy, multiple_async_contexts, safe_call, smart_run
|
|
46
58
|
from .version import __version__
|
|
47
59
|
|
|
48
60
|
__all__ = ["FastAPIReactToolkit"]
|
|
@@ -137,6 +149,7 @@ class FastAPIReactToolkit:
|
|
|
137
149
|
exclude_apis: list[BASE_APIS] = None
|
|
138
150
|
global_user_dependency = True
|
|
139
151
|
global_background_tasks = True
|
|
152
|
+
global_request = True
|
|
140
153
|
instrumentator: Instrumentator = None
|
|
141
154
|
on_startup: (
|
|
142
155
|
Callable[[FastAPI], None] | Awaitable[Callable[[FastAPI], None]] | None
|
|
@@ -148,7 +161,15 @@ class FastAPIReactToolkit:
|
|
|
148
161
|
# Public attributes
|
|
149
162
|
apis: list[ModelRestApi] = None
|
|
150
163
|
initialized = False
|
|
151
|
-
|
|
164
|
+
sm: SecurityManager = None
|
|
165
|
+
security: SecurityManager = lazy(lambda self: self.sm)
|
|
166
|
+
"""
|
|
167
|
+
Old attribute for `sm`, kept for backward compatibility.
|
|
168
|
+
"""
|
|
169
|
+
started = False
|
|
170
|
+
"""
|
|
171
|
+
Indicates whether the application has been started.
|
|
172
|
+
"""
|
|
152
173
|
|
|
153
174
|
# Private attributes
|
|
154
175
|
_mounted = False
|
|
@@ -166,6 +187,7 @@ class FastAPIReactToolkit:
|
|
|
166
187
|
exclude_apis: list[BASE_APIS] | None = None,
|
|
167
188
|
global_user_dependency: bool = True,
|
|
168
189
|
global_background_tasks: bool = True,
|
|
190
|
+
global_request: bool = True,
|
|
169
191
|
instrumentator: Instrumentator | None = None,
|
|
170
192
|
on_startup: (
|
|
171
193
|
Callable[[FastAPI], None] | Awaitable[Callable[[FastAPI], None]] | None
|
|
@@ -173,6 +195,9 @@ class FastAPIReactToolkit:
|
|
|
173
195
|
on_shutdown: (
|
|
174
196
|
Callable[[FastAPI], None] | Awaitable[Callable[[FastAPI], None]] | None
|
|
175
197
|
) = None,
|
|
198
|
+
lifespans: starlette.types.Lifespan[fastapi.applications.AppType]
|
|
199
|
+
| list[starlette.types.Lifespan[fastapi.applications.AppType]]
|
|
200
|
+
| None = None,
|
|
176
201
|
debug: bool = False,
|
|
177
202
|
):
|
|
178
203
|
"""
|
|
@@ -187,14 +212,16 @@ class FastAPIReactToolkit:
|
|
|
187
212
|
exclude_apis (list[BASE_APIS] | None, optional): List of security APIs to be excluded when initializing the FastAPI application. Defaults to None.
|
|
188
213
|
global_user_dependency (bool, optional): Whether to add the `set_global_user` dependency to the FastAPI application. This allows you to access the current user with the `g.user` object. Defaults to True.
|
|
189
214
|
global_background_tasks (bool, optional): Whether to add the `set_global_background_tasks` dependency to the FastAPI application. This allows you to access the background tasks with the `g.background_tasks` object. Defaults to True.
|
|
215
|
+
global_request (bool, optional): Whether to add the `set_global_request` dependency to the FastAPI application. This allows you to access the current request with the `g.request` object. Defaults to True.
|
|
190
216
|
instrumentator (Instrumentator | None, optional): The instrumentator to use for monitoring the FastAPI application. Defaults to `Instrumentator(**Setting.INSTRUMENTATOR_CONFIG)`.
|
|
191
217
|
on_startup (Callable[[FastAPI], None] | Awaitable[Callable[[FastAPI], None]] | None, optional): Function to call when the app is starting up. Can either be a regular function or an async function. If the function takes a `FastAPI` instance as an argument, it will be passed to the function. Defaults to None.
|
|
192
218
|
on_shutdown (Callable[[FastAPI], None] | Awaitable[Callable[[FastAPI], None]] | None, optional): Function to call when the app is shutting down. Can either be a regular function or an async function. If the function takes a `FastAPI` instance as an argument, it will be passed to the function. Defaults to None.
|
|
219
|
+
lifespans (starlette.types.Lifespan[fastapi.applications.AppType] | list[starlette.types.Lifespan[fastapi.applications.AppType]] | None, optional): Lifespan or list of lifespans to combine with the main lifespan. Defaults to None.
|
|
193
220
|
debug (bool, optional): Whether to log debug messages. Defaults to False.
|
|
194
221
|
"""
|
|
195
222
|
g.current_app = self
|
|
196
223
|
self.apis = []
|
|
197
|
-
self.
|
|
224
|
+
self.sm = SecurityManager(self)
|
|
198
225
|
|
|
199
226
|
# Database configuration
|
|
200
227
|
self.create_tables = create_tables
|
|
@@ -204,9 +231,17 @@ class FastAPIReactToolkit:
|
|
|
204
231
|
self.exclude_apis = exclude_apis or []
|
|
205
232
|
self.global_user_dependency = global_user_dependency
|
|
206
233
|
self.global_background_tasks = global_background_tasks
|
|
234
|
+
self.global_request = global_request
|
|
207
235
|
self.instrumentator = instrumentator
|
|
208
236
|
self.on_startup = on_startup
|
|
209
237
|
self.on_shutdown = on_shutdown
|
|
238
|
+
self.lifespans = (
|
|
239
|
+
lifespans
|
|
240
|
+
if isinstance(lifespans, list)
|
|
241
|
+
else [lifespans]
|
|
242
|
+
if lifespans
|
|
243
|
+
else []
|
|
244
|
+
)
|
|
210
245
|
|
|
211
246
|
if auth:
|
|
212
247
|
for key, value in auth.items():
|
|
@@ -273,6 +308,16 @@ class FastAPIReactToolkit:
|
|
|
273
308
|
self.app.router.dependencies.append(Depends(set_global_user()))
|
|
274
309
|
if self.global_background_tasks:
|
|
275
310
|
self.app.router.dependencies.append(Depends(set_global_background_tasks))
|
|
311
|
+
if self.global_request:
|
|
312
|
+
self.app.router.dependencies.append(Depends(set_global_request))
|
|
313
|
+
|
|
314
|
+
# Add the language middleware
|
|
315
|
+
try:
|
|
316
|
+
babel_cli = init_babel_cli(create_root_path_if_not_exists=False, log=False)
|
|
317
|
+
except FileNotFoundError:
|
|
318
|
+
# If the user does not have a lang folder, then use the internal one
|
|
319
|
+
babel_cli = init_babel_cli(root_path=INTERNAL_LANG_FOLDER, log=False)
|
|
320
|
+
self.app.add_middleware(BabelMiddleware, babel_configs=babel_cli.babel.config)
|
|
276
321
|
|
|
277
322
|
# Initialize the instrumentator
|
|
278
323
|
if not self.instrumentator:
|
|
@@ -375,171 +420,121 @@ class FastAPIReactToolkit:
|
|
|
375
420
|
|
|
376
421
|
async with db.session() as session:
|
|
377
422
|
logger.info("INITIALIZING DATABASE")
|
|
378
|
-
await self._insert_permissions(session)
|
|
379
|
-
await self._insert_apis(session)
|
|
380
|
-
await self._insert_roles(session)
|
|
381
|
-
|
|
382
|
-
|
|
423
|
+
permissions = await self._insert_permissions(session)
|
|
424
|
+
apis = await self._insert_apis(session)
|
|
425
|
+
roles = await self._insert_roles(session)
|
|
426
|
+
assert permissions is not None, "Permissions should not be None"
|
|
427
|
+
assert apis is not None, "APIs should not be None"
|
|
428
|
+
permission_apis = await self._associate_permission_with_api(
|
|
429
|
+
session, permissions, apis
|
|
430
|
+
)
|
|
431
|
+
assert roles is not None, "Roles should not be None"
|
|
432
|
+
assert permission_apis is not None, "PermissionApis should not be None"
|
|
433
|
+
await self._associate_role_with_permission_api(
|
|
434
|
+
session, roles, permission_apis
|
|
435
|
+
)
|
|
383
436
|
if self.cleanup:
|
|
384
|
-
await self.
|
|
437
|
+
await self.sm.cleanup()
|
|
385
438
|
logger.info("DATABASE INITIALIZED")
|
|
386
439
|
|
|
387
|
-
async def _insert_permissions(self, session:
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
result = await safe_call(session.scalars(stmt))
|
|
391
|
-
existing_permissions = [permission.name for permission in result.all()]
|
|
392
|
-
if len(new_permissions) == len(existing_permissions):
|
|
393
|
-
return
|
|
394
|
-
|
|
395
|
-
permission_objs = [
|
|
396
|
-
Permission(name=permission)
|
|
397
|
-
for permission in new_permissions
|
|
398
|
-
if permission not in existing_permissions
|
|
440
|
+
async def _insert_permissions(self, session: SQLASession):
|
|
441
|
+
permissions = self.total_permissions() + [
|
|
442
|
+
x[1] for x in self.sm.get_api_permission_tuples_from_builtin_roles()
|
|
399
443
|
]
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
session.add(permission)
|
|
403
|
-
await safe_call(session.commit())
|
|
404
|
-
|
|
405
|
-
async def _insert_apis(self, session: AsyncSession | Session):
|
|
406
|
-
new_apis = [api.__class__.__name__ for api in self.apis]
|
|
407
|
-
stmt = select(Api).where(Api.name.in_(new_apis))
|
|
408
|
-
result = await safe_call(session.scalars(stmt))
|
|
409
|
-
existing_apis = [api.name for api in result.all()]
|
|
410
|
-
if len(new_apis) == len(existing_apis):
|
|
411
|
-
return
|
|
412
|
-
|
|
413
|
-
api_objs = [Api(name=api) for api in new_apis if api not in existing_apis]
|
|
414
|
-
for api in api_objs:
|
|
415
|
-
logger.info(f"ADDING API {api}")
|
|
416
|
-
session.add(api)
|
|
417
|
-
await safe_call(session.commit())
|
|
418
|
-
|
|
419
|
-
async def _insert_roles(self, session: AsyncSession | Session):
|
|
420
|
-
new_roles = [g.admin_role, g.public_role]
|
|
421
|
-
stmt = select(Role).where(Role.name.in_(new_roles))
|
|
422
|
-
result = await safe_call(session.scalars(stmt))
|
|
423
|
-
existing_roles = [role.name for role in result.all()]
|
|
424
|
-
if len(new_roles) == len(existing_roles):
|
|
425
|
-
return
|
|
444
|
+
permissions = list(dict.fromkeys(permissions))
|
|
445
|
+
return await self.sm.create_permissions(permissions, session=session)
|
|
426
446
|
|
|
427
|
-
|
|
428
|
-
|
|
447
|
+
async def _insert_apis(self, session: SQLASession):
|
|
448
|
+
apis = [api.__class__.__name__ for api in self.apis] + [
|
|
449
|
+
x[0] for x in self.sm.get_api_permission_tuples_from_builtin_roles()
|
|
429
450
|
]
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
451
|
+
apis = list(dict.fromkeys(apis))
|
|
452
|
+
return await self.sm.create_apis(apis, session=session)
|
|
453
|
+
|
|
454
|
+
async def _insert_roles(self, session: SQLASession):
|
|
455
|
+
roles = self.sm.get_roles_from_builtin_roles()
|
|
456
|
+
if g.admin_role and g.admin_role not in roles:
|
|
457
|
+
roles.append(g.admin_role)
|
|
458
|
+
if g.public_role and g.public_role not in roles:
|
|
459
|
+
roles.append(g.public_role)
|
|
460
|
+
return await self.sm.create_roles(roles, session=session)
|
|
461
|
+
|
|
462
|
+
async def _associate_permission_with_api(
|
|
463
|
+
self,
|
|
464
|
+
session: SQLASession,
|
|
465
|
+
permissions: list[Permission],
|
|
466
|
+
apis: list[Api],
|
|
467
|
+
):
|
|
468
|
+
permission_map = {permission.name: permission for permission in permissions}
|
|
469
|
+
api_map = {api.name: api for api in apis}
|
|
470
|
+
permission_api_tuples = list[tuple[Permission, Api]]()
|
|
471
|
+
added_permission_api = set[tuple[str, str]]()
|
|
434
472
|
|
|
435
|
-
async def _associate_permission_with_api(self, session: AsyncSession | Session):
|
|
436
473
|
for api in self.apis:
|
|
437
|
-
|
|
438
|
-
if not
|
|
474
|
+
api_permissions = api.permissions
|
|
475
|
+
if not api_permissions:
|
|
439
476
|
continue
|
|
440
477
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
)
|
|
454
|
-
permission_result = await safe_call(session.scalars(permission_stmt))
|
|
455
|
-
new_permissions = permission_result.all()
|
|
456
|
-
|
|
457
|
-
if not new_permissions:
|
|
478
|
+
for permission_name in api_permissions:
|
|
479
|
+
if (permission_name, api.__class__.__name__) in added_permission_api:
|
|
480
|
+
continue
|
|
481
|
+
permission = permission_map[permission_name]
|
|
482
|
+
api_obj = api_map[api.__class__.__name__]
|
|
483
|
+
permission_api_tuples.append((permission, api_obj))
|
|
484
|
+
added_permission_api.add((permission.name, api_obj.name))
|
|
485
|
+
|
|
486
|
+
for (
|
|
487
|
+
api_name,
|
|
488
|
+
perm_name,
|
|
489
|
+
) in self.sm.get_api_permission_tuples_from_builtin_roles():
|
|
490
|
+
if (perm_name, api_name) in added_permission_api:
|
|
458
491
|
continue
|
|
492
|
+
permission = permission_map[perm_name]
|
|
493
|
+
api_obj = api_map[api_name]
|
|
494
|
+
permission_api_tuples.append((permission, api_obj))
|
|
495
|
+
added_permission_api.add((permission.name, api_obj.name))
|
|
459
496
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
)
|
|
464
|
-
logger.info(f"ASSOCIATING PERMISSION {permission} WITH API {api_obj}")
|
|
465
|
-
await safe_call(session.commit())
|
|
497
|
+
return await self.sm.associate_list_of_permission_with_api(
|
|
498
|
+
permission_api_tuples, session=session
|
|
499
|
+
)
|
|
466
500
|
|
|
467
|
-
async def
|
|
468
|
-
self,
|
|
501
|
+
async def _associate_role_with_permission_api(
|
|
502
|
+
self,
|
|
503
|
+
session: SQLASession,
|
|
504
|
+
roles: list[Role],
|
|
505
|
+
permission_apis: list[PermissionApi],
|
|
469
506
|
):
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
)
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
stmt = select(Api).where(Api.name == api_name)
|
|
503
|
-
api = await safe_call(session.scalar(stmt))
|
|
504
|
-
if not api:
|
|
505
|
-
api = Api(name=api_name)
|
|
506
|
-
logger.info(f"ADDING API {api}")
|
|
507
|
-
session.add(api)
|
|
508
|
-
|
|
509
|
-
permission_api = PermissionApi(permission=permission, api=api)
|
|
510
|
-
logger.info(f"ADDING PERMISSION-API {permission_api}")
|
|
511
|
-
session.add(permission_api)
|
|
512
|
-
|
|
513
|
-
# Associate role with permission-api
|
|
514
|
-
if role not in permission_api.roles:
|
|
515
|
-
permission_api.roles.append(role)
|
|
516
|
-
logger.info(
|
|
517
|
-
f"ASSOCIATING {role} WITH PERMISSION-API {permission_api}"
|
|
518
|
-
)
|
|
519
|
-
|
|
520
|
-
await safe_call(session.commit())
|
|
521
|
-
|
|
522
|
-
# Get admin role
|
|
523
|
-
admin_role_stmt = select(Role).where(Role.name == g.admin_role)
|
|
524
|
-
admin_role = await safe_call(session.scalar(admin_role_stmt))
|
|
525
|
-
|
|
526
|
-
if admin_role:
|
|
527
|
-
# Get list of permission-api.assoc_permission_api_id of the admin role
|
|
528
|
-
stmt = (
|
|
529
|
-
select(PermissionApi)
|
|
530
|
-
.where(~PermissionApi.roles.contains(admin_role))
|
|
531
|
-
.join(Api)
|
|
532
|
-
)
|
|
533
|
-
result = await safe_call(session.scalars(stmt))
|
|
534
|
-
existing_assoc_permission_api_roles = result.all()
|
|
535
|
-
|
|
536
|
-
# Add admin role to all permission-api objects
|
|
537
|
-
for permission_api in existing_assoc_permission_api_roles:
|
|
538
|
-
permission_api.roles.append(admin_role)
|
|
539
|
-
logger.info(
|
|
540
|
-
f"ASSOCIATING {admin_role} WITH PERMISSION-API {permission_api}"
|
|
541
|
-
)
|
|
542
|
-
await safe_call(session.commit())
|
|
507
|
+
role_map = {role.name: role for role in roles}
|
|
508
|
+
permission_api_map = {
|
|
509
|
+
(pa.permission.name, pa.api.name): pa for pa in permission_apis
|
|
510
|
+
}
|
|
511
|
+
permission_apis_to_exclude_from_admin = list[PermissionApi]()
|
|
512
|
+
role_permission_api_tuples = list[tuple[Role, PermissionApi]]()
|
|
513
|
+
|
|
514
|
+
for (
|
|
515
|
+
role_name,
|
|
516
|
+
role_api_permissions,
|
|
517
|
+
) in self.sm.get_role_and_api_permission_tuples_from_builtin_roles():
|
|
518
|
+
for api_name, permission_name in role_api_permissions:
|
|
519
|
+
role = role_map[role_name]
|
|
520
|
+
permission_api = permission_api_map[(permission_name, api_name)]
|
|
521
|
+
role_permission_api_tuples.append((role, permission_api))
|
|
522
|
+
if "|" in permission_name and role_name != g.public_role:
|
|
523
|
+
# Exclude multi-tenant permissions from admin role if not explicitly set
|
|
524
|
+
permission_apis_to_exclude_from_admin.append(permission_api)
|
|
525
|
+
|
|
526
|
+
if g.admin_role:
|
|
527
|
+
admin_role = role_map[g.admin_role]
|
|
528
|
+
for permission_api in [
|
|
529
|
+
pa
|
|
530
|
+
for pa in permission_apis
|
|
531
|
+
if pa not in permission_apis_to_exclude_from_admin
|
|
532
|
+
]:
|
|
533
|
+
role_permission_api_tuples.append((admin_role, permission_api))
|
|
534
|
+
|
|
535
|
+
await self.sm.associate_list_of_role_with_permission_api(
|
|
536
|
+
role_permission_api_tuples, session=session
|
|
537
|
+
)
|
|
543
538
|
|
|
544
539
|
def _mount_static_folder(self):
|
|
545
540
|
"""
|
|
@@ -585,7 +580,9 @@ class FastAPIReactToolkit:
|
|
|
585
580
|
},
|
|
586
581
|
)
|
|
587
582
|
except TemplateNotFound:
|
|
588
|
-
raise HTTPException(
|
|
583
|
+
raise HTTPException(
|
|
584
|
+
fastapi.status.HTTP_404_NOT_FOUND, ErrorCode.PAGE_NOT_FOUND
|
|
585
|
+
)
|
|
589
586
|
|
|
590
587
|
"""
|
|
591
588
|
-----------------------------------------
|
|
@@ -624,14 +621,6 @@ class FastAPIReactToolkit:
|
|
|
624
621
|
# Add the endpoint for the metrics
|
|
625
622
|
self.instrumentator.expose(app, **Setting.INSTRUMENTATOR_EXPOSE_CONFIG)
|
|
626
623
|
|
|
627
|
-
# Add the JS manifest route
|
|
628
|
-
self._init_js_manifest()
|
|
629
|
-
|
|
630
|
-
# Mount the static and template folders
|
|
631
|
-
self._mounted = True
|
|
632
|
-
self._mount_static_folder()
|
|
633
|
-
self._mount_template_folder()
|
|
634
|
-
|
|
635
624
|
await db.init_fastapi_rtk_tables()
|
|
636
625
|
|
|
637
626
|
if self.upgrade_db:
|
|
@@ -678,13 +667,34 @@ class FastAPIReactToolkit:
|
|
|
678
667
|
# Run when the app is shutting down
|
|
679
668
|
await db.close()
|
|
680
669
|
|
|
670
|
+
@asynccontextmanager
|
|
671
|
+
async def mount_lifespan(app: FastAPI):
|
|
672
|
+
# Mount the js manifest, static, and template folders
|
|
673
|
+
self._init_js_manifest()
|
|
674
|
+
self._mount_static_folder()
|
|
675
|
+
self._mount_template_folder()
|
|
676
|
+
self._mounted = True
|
|
677
|
+
self.started = True
|
|
678
|
+
yield
|
|
679
|
+
|
|
680
|
+
# Combine with other lifespans
|
|
681
|
+
@asynccontextmanager
|
|
682
|
+
async def combined_lifespan(app: FastAPI):
|
|
683
|
+
async with multiple_async_contexts(
|
|
684
|
+
[
|
|
685
|
+
lifespan(app)
|
|
686
|
+
for lifespan in [lifespan] + self.lifespans + [mount_lifespan]
|
|
687
|
+
]
|
|
688
|
+
):
|
|
689
|
+
yield
|
|
690
|
+
|
|
681
691
|
# Check whether lifespan is already set
|
|
682
692
|
if not isinstance(self.app.router.lifespan_context, _DefaultLifespan):
|
|
683
693
|
raise ValueError(
|
|
684
694
|
"Lifespan already set, please do not set lifespan directly in the FastAPI app"
|
|
685
695
|
)
|
|
686
696
|
|
|
687
|
-
self.app.router.lifespan_context =
|
|
697
|
+
self.app.router.lifespan_context = combined_lifespan
|
|
688
698
|
|
|
689
699
|
def _init_basic_apis(self):
|
|
690
700
|
apis = [
|
|
@@ -707,7 +717,12 @@ class FastAPIReactToolkit:
|
|
|
707
717
|
env = Environment(autoescape=select_autoescape(["html", "xml"]))
|
|
708
718
|
template_string = "window.fab_react_config = {{ react_vars |tojson }}"
|
|
709
719
|
template = env.from_string(template_string)
|
|
710
|
-
|
|
720
|
+
react_vars = Setting.FAB_REACT_CONFIG
|
|
721
|
+
if Setting.TRANSLATIONS:
|
|
722
|
+
react_vars[Setting.TRANSLATIONS_KEY] = deep_merge(
|
|
723
|
+
react_vars.get(Setting.TRANSLATIONS_KEY, {}), Setting.TRANSLATIONS
|
|
724
|
+
)
|
|
725
|
+
rendered_string = template.render(react_vars=react_vars)
|
|
711
726
|
content = rendered_string.encode("utf-8")
|
|
712
727
|
scriptfile = io.BytesIO(content)
|
|
713
728
|
return StreamingResponse(
|
|
@@ -4,7 +4,7 @@ import shutil
|
|
|
4
4
|
|
|
5
5
|
from ..bases.file_manager import AbstractFileManager
|
|
6
6
|
from ..setting import Setting
|
|
7
|
-
from ..utils import lazy, secure_filename
|
|
7
|
+
from ..utils import lazy, secure_filename, smart_run
|
|
8
8
|
|
|
9
9
|
__all__ = ["FileManager"]
|
|
10
10
|
|
|
@@ -20,6 +20,8 @@ class FileManager(AbstractFileManager):
|
|
|
20
20
|
def post_init(self):
|
|
21
21
|
if not self.base_path:
|
|
22
22
|
raise ValueError("UPLOAD_FOLDER not set in config.")
|
|
23
|
+
if not op.exists(self.base_path):
|
|
24
|
+
os.makedirs(self.base_path, self.permission)
|
|
23
25
|
|
|
24
26
|
def get_path(self, filename):
|
|
25
27
|
return op.join(self.base_path, filename)
|
|
@@ -32,20 +34,20 @@ class FileManager(AbstractFileManager):
|
|
|
32
34
|
async def stream_file(self, filename):
|
|
33
35
|
_, path = self.generate_secure_filename(filename)
|
|
34
36
|
with open(path, "rb") as f:
|
|
35
|
-
while chunk := f.read
|
|
37
|
+
while chunk := await smart_run(f.read, 8192):
|
|
36
38
|
yield chunk
|
|
37
39
|
|
|
38
40
|
def save_file(self, file_data, filename):
|
|
39
|
-
|
|
41
|
+
_, path = self.generate_secure_filename(filename)
|
|
40
42
|
with open(path, "wb") as buffer:
|
|
41
43
|
shutil.copyfileobj(file_data.file, buffer)
|
|
42
|
-
return
|
|
44
|
+
return path
|
|
43
45
|
|
|
44
46
|
def save_content_to_file(self, content, filename):
|
|
45
|
-
|
|
47
|
+
_, path = self.generate_secure_filename(filename)
|
|
46
48
|
with open(path, "wb") as buffer:
|
|
47
49
|
buffer.write(content)
|
|
48
|
-
return
|
|
50
|
+
return path
|
|
49
51
|
|
|
50
52
|
def delete_file(self, filename):
|
|
51
53
|
_, path = self.generate_secure_filename(filename)
|