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
|
@@ -1,28 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
import os
|
|
2
2
|
from typing import Annotated, Union
|
|
3
3
|
|
|
4
|
+
import alembic
|
|
5
|
+
import alembic.command
|
|
6
|
+
import alembic.config
|
|
4
7
|
import typer
|
|
5
8
|
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
from .commands import version_callback
|
|
23
|
-
from .decorators import ensure_fastapi_rtk_tables_exist
|
|
9
|
+
from ...cli import app
|
|
10
|
+
from ...decorators import ensure_fastapi_rtk_tables_exist
|
|
11
|
+
from .. import version_callback
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Config(alembic.config.Config):
|
|
15
|
+
def __init__(self, *args, **kwargs):
|
|
16
|
+
self.template_directory = kwargs.pop("template_directory", None)
|
|
17
|
+
super().__init__(*args, **kwargs)
|
|
18
|
+
|
|
19
|
+
def get_template_directory(self):
|
|
20
|
+
if self.template_directory:
|
|
21
|
+
return self.template_directory
|
|
22
|
+
package_dir = os.path.abspath(os.path.dirname(__file__))
|
|
23
|
+
return os.path.join(package_dir, "templates")
|
|
24
|
+
|
|
24
25
|
|
|
25
26
|
db_app = typer.Typer(rich_markup_mode="rich")
|
|
27
|
+
app.add_typer(db_app, name="db")
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
@db_app.callback()
|
|
@@ -45,7 +47,14 @@ def callback(
|
|
|
45
47
|
@db_app.command()
|
|
46
48
|
def list_templates():
|
|
47
49
|
"""List available templates."""
|
|
48
|
-
|
|
50
|
+
config = Config()
|
|
51
|
+
config.print_stdout("Available templates:\n")
|
|
52
|
+
for tempname in sorted(os.listdir(config.get_template_directory())):
|
|
53
|
+
with open(
|
|
54
|
+
os.path.join(config.get_template_directory(), tempname, "README")
|
|
55
|
+
) as readme:
|
|
56
|
+
synopsis = next(readme).strip()
|
|
57
|
+
config.print_stdout("%s - %s", tempname, synopsis)
|
|
49
58
|
|
|
50
59
|
|
|
51
60
|
@db_app.command()
|
|
@@ -76,7 +85,17 @@ def init(
|
|
|
76
85
|
] = False,
|
|
77
86
|
):
|
|
78
87
|
"""Creates a new migration repository"""
|
|
79
|
-
|
|
88
|
+
template_directory = None
|
|
89
|
+
if "/" in template or "\\" in template:
|
|
90
|
+
template_directory, template = os.path.split(template)
|
|
91
|
+
config = Config(template_directory=template_directory)
|
|
92
|
+
config.set_main_option("script_location", directory)
|
|
93
|
+
config.config_file_name = os.path.join(directory, "alembic.ini")
|
|
94
|
+
# TODO: Fix this
|
|
95
|
+
# config = current_app.extensions["migrate"].migrate.call_configure_callbacks(config)
|
|
96
|
+
if multidb and template == "fastapi":
|
|
97
|
+
template = "fastapi-multidb"
|
|
98
|
+
alembic.command.init(config, directory, template=template, package=package)
|
|
80
99
|
|
|
81
100
|
|
|
82
101
|
@db_app.command()
|
|
@@ -129,16 +148,21 @@ def revision(
|
|
|
129
148
|
] = None,
|
|
130
149
|
):
|
|
131
150
|
"""Create a new revision file."""
|
|
132
|
-
|
|
133
|
-
|
|
151
|
+
opts = ["autogenerate"] if autogenerate else None
|
|
152
|
+
config = Config()
|
|
153
|
+
config.set_main_option("script_location", directory)
|
|
154
|
+
config.cmd_opts = opts
|
|
155
|
+
# config = current_app.extensions["migrate"].migrate.get_config(directory, opts=opts)
|
|
156
|
+
alembic.command.revision(
|
|
157
|
+
config,
|
|
134
158
|
message,
|
|
135
|
-
autogenerate,
|
|
136
|
-
sql,
|
|
137
|
-
head,
|
|
138
|
-
splice,
|
|
139
|
-
branch_label,
|
|
140
|
-
version_path,
|
|
141
|
-
rev_id,
|
|
159
|
+
autogenerate=autogenerate,
|
|
160
|
+
sql=sql,
|
|
161
|
+
head=head,
|
|
162
|
+
splice=splice,
|
|
163
|
+
branch_label=branch_label,
|
|
164
|
+
version_path=version_path,
|
|
165
|
+
rev_id=rev_id,
|
|
142
166
|
)
|
|
143
167
|
|
|
144
168
|
|
|
@@ -186,15 +210,16 @@ def migrate(
|
|
|
186
210
|
] = None,
|
|
187
211
|
):
|
|
188
212
|
"""Alias for `revision` command with --autogenerate."""
|
|
189
|
-
|
|
190
|
-
directory,
|
|
191
|
-
message,
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
213
|
+
return revision(
|
|
214
|
+
directory=directory,
|
|
215
|
+
message=message,
|
|
216
|
+
autogenerate=True,
|
|
217
|
+
sql=sql,
|
|
218
|
+
head=head,
|
|
219
|
+
splice=splice,
|
|
220
|
+
branch_label=branch_label,
|
|
221
|
+
version_path=version_path,
|
|
222
|
+
rev_id=rev_id,
|
|
198
223
|
)
|
|
199
224
|
|
|
200
225
|
|
|
@@ -208,7 +233,9 @@ def edit(
|
|
|
208
233
|
] = "current",
|
|
209
234
|
):
|
|
210
235
|
"""Edit the revision."""
|
|
211
|
-
|
|
236
|
+
config = Config()
|
|
237
|
+
config.set_main_option("script_location", directory)
|
|
238
|
+
alembic.command.edit(config, revision)
|
|
212
239
|
|
|
213
240
|
|
|
214
241
|
@db_app.command()
|
|
@@ -234,7 +261,15 @@ def merge(
|
|
|
234
261
|
] = None,
|
|
235
262
|
):
|
|
236
263
|
"""Merge two revisions together."""
|
|
237
|
-
|
|
264
|
+
config = Config()
|
|
265
|
+
config.set_main_option("script_location", directory)
|
|
266
|
+
alembic.command.merge(
|
|
267
|
+
config,
|
|
268
|
+
revisions,
|
|
269
|
+
message=message,
|
|
270
|
+
branch_label=branch_label,
|
|
271
|
+
rev_id=rev_id,
|
|
272
|
+
)
|
|
238
273
|
|
|
239
274
|
|
|
240
275
|
@db_app.command()
|
|
@@ -259,7 +294,9 @@ def upgrade(
|
|
|
259
294
|
] = None,
|
|
260
295
|
):
|
|
261
296
|
"""Upgrade to a later version."""
|
|
262
|
-
|
|
297
|
+
config = Config()
|
|
298
|
+
config.set_main_option("script_location", directory)
|
|
299
|
+
alembic.command.upgrade(config, revision, sql=sql, tag=tag)
|
|
263
300
|
|
|
264
301
|
|
|
265
302
|
@db_app.command()
|
|
@@ -284,7 +321,9 @@ def downgrade(
|
|
|
284
321
|
] = None,
|
|
285
322
|
):
|
|
286
323
|
"""Downgrade to a previous version."""
|
|
287
|
-
|
|
324
|
+
config = Config()
|
|
325
|
+
config.set_main_option("script_location", directory)
|
|
326
|
+
alembic.command.downgrade(config, revision, sql=sql, tag=tag)
|
|
288
327
|
|
|
289
328
|
|
|
290
329
|
@db_app.command()
|
|
@@ -295,7 +334,9 @@ def show(
|
|
|
295
334
|
revision: Annotated[str, typer.Option(help="Target revision to show.")] = "head",
|
|
296
335
|
):
|
|
297
336
|
"""Show the revision."""
|
|
298
|
-
|
|
337
|
+
config = Config()
|
|
338
|
+
config.set_main_option("script_location", directory)
|
|
339
|
+
alembic.command.show(config, revision)
|
|
299
340
|
|
|
300
341
|
|
|
301
342
|
@db_app.command()
|
|
@@ -320,7 +361,11 @@ def history(
|
|
|
320
361
|
] = False,
|
|
321
362
|
):
|
|
322
363
|
"""List changeset scripts in chronological order."""
|
|
323
|
-
|
|
364
|
+
config = Config()
|
|
365
|
+
config.set_main_option("script_location", directory)
|
|
366
|
+
alembic.command.history(
|
|
367
|
+
config, rev_range, verbose=verbose, indicate_current=indicate_current
|
|
368
|
+
)
|
|
324
369
|
|
|
325
370
|
|
|
326
371
|
@db_app.command()
|
|
@@ -342,7 +387,11 @@ def heads(
|
|
|
342
387
|
] = False,
|
|
343
388
|
):
|
|
344
389
|
"""Show current available heads in the script directory."""
|
|
345
|
-
|
|
390
|
+
config = Config()
|
|
391
|
+
config.set_main_option("script_location", directory)
|
|
392
|
+
alembic.command.heads(
|
|
393
|
+
config, verbose=verbose, resolve_dependencies=resolve_dependencies
|
|
394
|
+
)
|
|
346
395
|
|
|
347
396
|
|
|
348
397
|
@db_app.command()
|
|
@@ -358,7 +407,9 @@ def branches(
|
|
|
358
407
|
] = False,
|
|
359
408
|
):
|
|
360
409
|
"""Show current branch points in the script directory."""
|
|
361
|
-
|
|
410
|
+
config = Config()
|
|
411
|
+
config.set_main_option("script_location", directory)
|
|
412
|
+
alembic.command.branches(config, verbose=verbose)
|
|
362
413
|
|
|
363
414
|
|
|
364
415
|
@db_app.command()
|
|
@@ -374,7 +425,9 @@ def current(
|
|
|
374
425
|
] = False,
|
|
375
426
|
):
|
|
376
427
|
"""Display the current revision for each database."""
|
|
377
|
-
|
|
428
|
+
config = Config()
|
|
429
|
+
config.set_main_option("script_location", directory)
|
|
430
|
+
alembic.command.current(config, verbose=verbose)
|
|
378
431
|
|
|
379
432
|
|
|
380
433
|
@db_app.command()
|
|
@@ -403,7 +456,9 @@ def stamp(
|
|
|
403
456
|
] = False,
|
|
404
457
|
):
|
|
405
458
|
"""Stamp the revision table with the given revision."""
|
|
406
|
-
|
|
459
|
+
config = Config()
|
|
460
|
+
config.set_main_option("script_location", directory)
|
|
461
|
+
alembic.command.stamp(config, revision, sql=sql, tag=tag, purge=purge)
|
|
407
462
|
|
|
408
463
|
|
|
409
464
|
@db_app.command()
|
|
@@ -413,4 +468,6 @@ def check(
|
|
|
413
468
|
] = "migrations",
|
|
414
469
|
):
|
|
415
470
|
"""Check the current revision."""
|
|
416
|
-
|
|
471
|
+
config = Config()
|
|
472
|
+
config.set_main_option("script_location", directory)
|
|
473
|
+
alembic.command.check(config)
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import logging
|
|
3
2
|
from logging.config import fileConfig
|
|
4
3
|
|
|
5
4
|
from alembic import context
|
|
6
|
-
from fastapi_rtk import Setting, metadata
|
|
5
|
+
from fastapi_rtk import Setting, metadata, smart_run_sync
|
|
7
6
|
from fastapi_rtk.const import FASTAPI_RTK_TABLES
|
|
8
7
|
from fastapi_rtk.db import DatabaseSessionManager
|
|
9
8
|
from sqlalchemy import Connection
|
|
@@ -114,7 +113,7 @@ async def run_async_migrations() -> None:
|
|
|
114
113
|
def run_migrations_online() -> None:
|
|
115
114
|
"""Run migrations in 'online' mode."""
|
|
116
115
|
|
|
117
|
-
|
|
116
|
+
smart_run_sync(run_async_migrations)
|
|
118
117
|
|
|
119
118
|
|
|
120
119
|
if context.is_offline_mode():
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import logging
|
|
3
2
|
from logging.config import fileConfig
|
|
4
3
|
|
|
5
4
|
from alembic import context
|
|
6
|
-
from fastapi_rtk import Setting, metadatas
|
|
7
|
-
from fastapi_rtk.const import FASTAPI_RTK_TABLES
|
|
5
|
+
from fastapi_rtk import Setting, metadatas, smart_run_sync
|
|
6
|
+
from fastapi_rtk.const import DEFAULT_METADATA_KEY, FASTAPI_RTK_TABLES
|
|
8
7
|
from fastapi_rtk.db import DatabaseSessionManager
|
|
9
8
|
from sqlalchemy import Connection, MetaData
|
|
10
9
|
|
|
@@ -29,17 +28,19 @@ logger = logging.getLogger("alembic.env")
|
|
|
29
28
|
# 'URL1':mymodel.metadata1,
|
|
30
29
|
# 'engine2':mymodel.metadata2
|
|
31
30
|
# }
|
|
32
|
-
target_metadata =
|
|
31
|
+
target_metadata = dict[str, MetaData]()
|
|
33
32
|
|
|
34
33
|
MAIN_DATABASE = Setting.SQLALCHEMY_DATABASE_URI
|
|
35
34
|
if not MAIN_DATABASE:
|
|
36
35
|
raise Exception("SQLALCHEMY_DATABASE_URI not set in config")
|
|
37
|
-
target_metadata[
|
|
36
|
+
target_metadata[DEFAULT_METADATA_KEY] = metadatas[DEFAULT_METADATA_KEY]
|
|
38
37
|
|
|
39
38
|
BINDS = Setting.SQLALCHEMY_BINDS or {}
|
|
40
39
|
for name in BINDS.keys():
|
|
41
|
-
if name ==
|
|
42
|
-
raise Exception(
|
|
40
|
+
if name == DEFAULT_METADATA_KEY:
|
|
41
|
+
raise Exception(
|
|
42
|
+
f"SQLALCHEMY_BINDS cannot have a key named '{DEFAULT_METADATA_KEY}'"
|
|
43
|
+
)
|
|
43
44
|
target_metadata[name] = metadatas[name]
|
|
44
45
|
|
|
45
46
|
# other values from the config, defined by the needs of env.py,
|
|
@@ -158,7 +159,7 @@ async def run_async_migrations() -> None:
|
|
|
158
159
|
|
|
159
160
|
for name, metadata in target_metadata.items():
|
|
160
161
|
logger.info("Migrating database %s" % name)
|
|
161
|
-
async with db.connect(name
|
|
162
|
+
async with db.connect(name) as connection:
|
|
162
163
|
if isinstance(connection, Connection):
|
|
163
164
|
do_run_migrations(
|
|
164
165
|
connection, f"{name}_upgrades", f"{name}_downgrades", metadata, name
|
|
@@ -221,7 +222,7 @@ async def run_async_migrations() -> None:
|
|
|
221
222
|
def run_migrations_online() -> None:
|
|
222
223
|
"""Run migrations in 'online' mode."""
|
|
223
224
|
|
|
224
|
-
|
|
225
|
+
smart_run_sync(run_async_migrations)
|
|
225
226
|
|
|
226
227
|
|
|
227
228
|
if context.is_offline_mode():
|
|
@@ -12,6 +12,7 @@ from typing import Sequence, Union
|
|
|
12
12
|
|
|
13
13
|
from alembic import op
|
|
14
14
|
import sqlalchemy as sa
|
|
15
|
+
import fastapi_rtk.types
|
|
15
16
|
${imports if imports else ""}
|
|
16
17
|
|
|
17
18
|
# revision identifiers, used by Alembic.
|
|
@@ -30,13 +31,14 @@ def downgrade(engine_name: str) -> None:
|
|
|
30
31
|
|
|
31
32
|
<%
|
|
32
33
|
from fastapi_rtk import Setting
|
|
34
|
+
from fastapi_rtk.const import DEFAULT_METADATA_KEY
|
|
33
35
|
bind_names = []
|
|
34
36
|
|
|
35
37
|
MAIN_DATABASE = Setting.SQLALCHEMY_DATABASE_URI
|
|
36
38
|
BINDS = Setting.SQLALCHEMY_BINDS
|
|
37
39
|
BINDS = list(BINDS.keys())
|
|
38
40
|
|
|
39
|
-
db_names = [
|
|
41
|
+
db_names = [DEFAULT_METADATA_KEY] + BINDS
|
|
40
42
|
%>
|
|
41
43
|
|
|
42
44
|
## generate an "upgrade_<xyz>() / downgrade_<xyz>()" function
|
|
@@ -5,9 +5,9 @@ import typing
|
|
|
5
5
|
import jsonschema2md
|
|
6
6
|
import typer
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from
|
|
8
|
+
from ...api.model_rest_api import ModelRestApi
|
|
9
|
+
from ...backends.sqla.model import metadatas
|
|
10
|
+
from ...const import (
|
|
11
11
|
ACCESSTOKEN_TABLE,
|
|
12
12
|
API_TABLE,
|
|
13
13
|
ASSOC_PERMISSION_API_ROLE_TABLE,
|
|
@@ -18,12 +18,12 @@ from ..const import (
|
|
|
18
18
|
ROLE_TABLE,
|
|
19
19
|
USER_TABLE,
|
|
20
20
|
)
|
|
21
|
-
from
|
|
22
|
-
from
|
|
23
|
-
from
|
|
24
|
-
from
|
|
25
|
-
from
|
|
26
|
-
from
|
|
21
|
+
from ...db import db
|
|
22
|
+
from ...globals import g
|
|
23
|
+
from ..cli import app
|
|
24
|
+
from ..const import logger
|
|
25
|
+
from ..decorators import ensure_fastapi_rtk_tables_exist
|
|
26
|
+
from ..types import (
|
|
27
27
|
APIDataDocs,
|
|
28
28
|
APIDocs,
|
|
29
29
|
APIMetadataDocs,
|
|
@@ -34,9 +34,11 @@ from .types import (
|
|
|
34
34
|
DBTableDocs,
|
|
35
35
|
TableDocumentation,
|
|
36
36
|
)
|
|
37
|
-
from
|
|
37
|
+
from ..utils import json_to_markdown, run_in_current_event_loop
|
|
38
|
+
from . import version_callback
|
|
38
39
|
|
|
39
40
|
export_app = typer.Typer(rich_markup_mode="rich")
|
|
41
|
+
app.add_typer(export_app, name="export")
|
|
40
42
|
|
|
41
43
|
table_documentation: collections.defaultdict[str, TableDocumentation] = (
|
|
42
44
|
collections.defaultdict(lambda: TableDocumentation(description="", columns={}))
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
import typing
|
|
1
2
|
from typing import Annotated, Union
|
|
2
3
|
|
|
4
|
+
import sqlalchemy
|
|
3
5
|
import typer
|
|
4
6
|
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from .
|
|
8
|
-
from .
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
from
|
|
7
|
+
from ...db import db
|
|
8
|
+
from ...globals import g
|
|
9
|
+
from ...security.sqla.models import Role
|
|
10
|
+
from ...utils.run_utils import safe_call
|
|
11
|
+
from ..cli import app
|
|
12
|
+
from ..decorators import ensure_fastapi_rtk_tables_exist
|
|
13
|
+
from ..utils import run_in_current_event_loop
|
|
14
|
+
from . import version_callback
|
|
12
15
|
|
|
13
16
|
security_app = typer.Typer(rich_markup_mode="rich")
|
|
17
|
+
app.add_typer(security_app, name="security")
|
|
14
18
|
|
|
15
19
|
|
|
16
20
|
@security_app.callback()
|
|
@@ -225,3 +229,65 @@ def cleanup():
|
|
|
225
229
|
run_in_current_event_loop(_cleanup())
|
|
226
230
|
|
|
227
231
|
typer.echo("Cleanup complete.")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
async def _check_roles(name: str, create: bool = False):
|
|
235
|
+
async with db.session() as session:
|
|
236
|
+
stmt = sqlalchemy.select(Role).where(Role.name == name)
|
|
237
|
+
role = (await safe_call(session.execute(stmt))).scalar_one_or_none()
|
|
238
|
+
if not role:
|
|
239
|
+
if not create:
|
|
240
|
+
raise Exception(f"Role {name} does not exist")
|
|
241
|
+
await g.current_app.sm.create_role(name=name, session=session)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
async def _create_user(
|
|
245
|
+
username: str,
|
|
246
|
+
email: str,
|
|
247
|
+
password: str,
|
|
248
|
+
first_name: str = "",
|
|
249
|
+
last_name: str = "",
|
|
250
|
+
role: str = "",
|
|
251
|
+
create_role: bool = False,
|
|
252
|
+
):
|
|
253
|
+
"""
|
|
254
|
+
Create an admin user.
|
|
255
|
+
"""
|
|
256
|
+
if role:
|
|
257
|
+
await _check_roles(role, create=create_role)
|
|
258
|
+
return await g.current_app.sm.create_user(
|
|
259
|
+
email=email,
|
|
260
|
+
username=username,
|
|
261
|
+
password=password,
|
|
262
|
+
first_name=first_name,
|
|
263
|
+
last_name=last_name,
|
|
264
|
+
roles=[role] if role else None,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
async def _reset_password(email_or_username: str, password: str):
|
|
269
|
+
"""
|
|
270
|
+
Reset user password.
|
|
271
|
+
"""
|
|
272
|
+
user = await g.current_app.sm.get_user(email_or_username)
|
|
273
|
+
return await g.current_app.sm.reset_password(user, password)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
async def export_data(
|
|
277
|
+
file_path: str,
|
|
278
|
+
data: typing.Literal["users", "roles"],
|
|
279
|
+
type: typing.Literal["json", "csv"] = "json",
|
|
280
|
+
):
|
|
281
|
+
"""
|
|
282
|
+
Export data.
|
|
283
|
+
"""
|
|
284
|
+
data = await g.current_app.sm.export_data(data, type)
|
|
285
|
+
with open(file_path, "w") as f:
|
|
286
|
+
f.write(data)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
async def _cleanup():
|
|
290
|
+
"""
|
|
291
|
+
Cleanup unused permissions from apis and roles.
|
|
292
|
+
"""
|
|
293
|
+
await g.current_app.sm.cleanup()
|