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.
Files changed (98) hide show
  1. fastapi_rtk/__init__.py +39 -35
  2. fastapi_rtk/_version.py +1 -0
  3. fastapi_rtk/api/model_rest_api.py +476 -221
  4. fastapi_rtk/auth/auth.py +0 -9
  5. fastapi_rtk/backends/generic/__init__.py +6 -0
  6. fastapi_rtk/backends/generic/column.py +21 -12
  7. fastapi_rtk/backends/generic/db.py +42 -7
  8. fastapi_rtk/backends/generic/filters.py +21 -16
  9. fastapi_rtk/backends/generic/interface.py +14 -8
  10. fastapi_rtk/backends/generic/model.py +19 -11
  11. fastapi_rtk/backends/sqla/__init__.py +1 -0
  12. fastapi_rtk/backends/sqla/db.py +77 -17
  13. fastapi_rtk/backends/sqla/extensions/audit/audit.py +401 -189
  14. fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +15 -12
  15. fastapi_rtk/backends/sqla/filters.py +50 -21
  16. fastapi_rtk/backends/sqla/interface.py +96 -34
  17. fastapi_rtk/backends/sqla/model.py +56 -39
  18. fastapi_rtk/bases/__init__.py +20 -0
  19. fastapi_rtk/bases/db.py +94 -7
  20. fastapi_rtk/bases/file_manager.py +47 -3
  21. fastapi_rtk/bases/filter.py +22 -0
  22. fastapi_rtk/bases/interface.py +49 -5
  23. fastapi_rtk/bases/model.py +3 -0
  24. fastapi_rtk/bases/session.py +2 -0
  25. fastapi_rtk/cli/cli.py +62 -9
  26. fastapi_rtk/cli/commands/__init__.py +23 -0
  27. fastapi_rtk/cli/{db.py → commands/db/__init__.py} +107 -50
  28. fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/env.py +2 -3
  29. fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/env.py +10 -9
  30. fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/script.py.mako +3 -1
  31. fastapi_rtk/cli/{export.py → commands/export.py} +12 -10
  32. fastapi_rtk/cli/{security.py → commands/security.py} +73 -7
  33. fastapi_rtk/cli/commands/translate.py +299 -0
  34. fastapi_rtk/cli/decorators.py +9 -4
  35. fastapi_rtk/cli/utils.py +46 -0
  36. fastapi_rtk/config.py +41 -1
  37. fastapi_rtk/const.py +29 -1
  38. fastapi_rtk/db.py +76 -40
  39. fastapi_rtk/decorators.py +1 -1
  40. fastapi_rtk/dependencies.py +134 -62
  41. fastapi_rtk/exceptions.py +51 -1
  42. fastapi_rtk/fastapi_react_toolkit.py +186 -171
  43. fastapi_rtk/file_managers/file_manager.py +8 -6
  44. fastapi_rtk/file_managers/s3_file_manager.py +69 -33
  45. fastapi_rtk/globals.py +22 -12
  46. fastapi_rtk/lang/__init__.py +3 -0
  47. fastapi_rtk/lang/babel/__init__.py +4 -0
  48. fastapi_rtk/lang/babel/cli.py +40 -0
  49. fastapi_rtk/lang/babel/config.py +17 -0
  50. fastapi_rtk/lang/babel.cfg +1 -0
  51. fastapi_rtk/lang/lazy_text.py +120 -0
  52. fastapi_rtk/lang/messages.pot +238 -0
  53. fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  54. fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +248 -0
  55. fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  56. fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +244 -0
  57. fastapi_rtk/manager.py +355 -37
  58. fastapi_rtk/mixins.py +12 -0
  59. fastapi_rtk/routers.py +208 -72
  60. fastapi_rtk/schemas.py +142 -39
  61. fastapi_rtk/security/sqla/apis.py +39 -13
  62. fastapi_rtk/security/sqla/models.py +8 -23
  63. fastapi_rtk/security/sqla/security_manager.py +369 -11
  64. fastapi_rtk/setting.py +446 -88
  65. fastapi_rtk/types.py +94 -27
  66. fastapi_rtk/utils/__init__.py +8 -0
  67. fastapi_rtk/utils/async_task_runner.py +286 -61
  68. fastapi_rtk/utils/csv_json_converter.py +243 -40
  69. fastapi_rtk/utils/hooks.py +34 -0
  70. fastapi_rtk/utils/merge_schema.py +3 -3
  71. fastapi_rtk/utils/multiple_async_contexts.py +21 -0
  72. fastapi_rtk/utils/pydantic.py +46 -1
  73. fastapi_rtk/utils/run_utils.py +31 -1
  74. fastapi_rtk/utils/self_dependencies.py +1 -1
  75. fastapi_rtk/utils/use_default_when_none.py +1 -1
  76. fastapi_rtk/version.py +6 -1
  77. fastapi_rtk-1.0.13.dist-info/METADATA +28 -0
  78. fastapi_rtk-1.0.13.dist-info/RECORD +133 -0
  79. {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/WHEEL +1 -2
  80. fastapi_rtk/backends/gremlinpython/__init__.py +0 -108
  81. fastapi_rtk/backends/gremlinpython/column.py +0 -208
  82. fastapi_rtk/backends/gremlinpython/db.py +0 -228
  83. fastapi_rtk/backends/gremlinpython/exceptions.py +0 -34
  84. fastapi_rtk/backends/gremlinpython/filters.py +0 -461
  85. fastapi_rtk/backends/gremlinpython/interface.py +0 -734
  86. fastapi_rtk/backends/gremlinpython/model.py +0 -364
  87. fastapi_rtk/backends/gremlinpython/session.py +0 -23
  88. fastapi_rtk/cli/commands.py +0 -295
  89. fastapi_rtk-0.2.27.dist-info/METADATA +0 -23
  90. fastapi_rtk-0.2.27.dist-info/RECORD +0 -126
  91. fastapi_rtk-0.2.27.dist-info/top_level.txt +0 -1
  92. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/README +0 -0
  93. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/alembic.ini.mako +0 -0
  94. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi/script.py.mako +0 -0
  95. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/README +0 -0
  96. /fastapi_rtk/cli/{templates → commands/db/templates}/fastapi-multidb/alembic.ini.mako +0 -0
  97. {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/entry_points.txt +0 -0
  98. {fastapi_rtk-0.2.27.dist-info → fastapi_rtk-1.0.13.dist-info}/licenses/LICENSE +0 -0
@@ -1,28 +1,30 @@
1
- from pathlib import Path
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 .commands import branches as _branches
7
- from .commands import check as _check
8
- from .commands import current as _current
9
- from .commands import downgrade as _downgrade
10
- from .commands import edit as _edit
11
- from .commands import heads as _heads
12
- from .commands import history as _history
13
- from .commands import init as _init
14
- from .commands import list_templates as _list_templates
15
- from .commands import merge as _merge
16
- from .commands import migrate as _migrate
17
- from .commands import path_callback
18
- from .commands import revision as _revision
19
- from .commands import show as _show
20
- from .commands import stamp as _stamp
21
- from .commands import upgrade as _upgrade
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
- _list_templates()
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
- _init(directory, multidb, template, package)
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
- _revision(
133
- directory,
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
- _migrate(
190
- directory,
191
- message,
192
- sql,
193
- head,
194
- splice,
195
- branch_label,
196
- version_path,
197
- rev_id,
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
- _edit(directory, revision)
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
- _merge(directory, revisions, message, branch_label, rev_id)
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
- _upgrade(directory, revision, sql, tag)
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
- _downgrade(directory, revision, sql, tag)
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
- _show(directory, revision)
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
- _history(directory, rev_range, verbose, indicate_current)
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
- _heads(directory, verbose, resolve_dependencies)
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
- _branches(directory, verbose)
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
- _current(directory, verbose)
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
- _stamp(directory, revision, sql, tag, purge)
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
- _check(directory)
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
- asyncio.run(run_async_migrations())
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["default"] = metadatas["default"]
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 == "default":
42
- raise Exception("SQLALCHEMY_BINDS cannot have a key named 'default'")
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.replace("default", "")) as connection:
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
- asyncio.run(run_async_migrations())
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 = ["default"] + BINDS
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 ..api.model_rest_api import ModelRestApi
9
- from ..backends.sqla.model import metadatas
10
- from ..const import (
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 ..db import db
22
- from ..globals import g
23
- from .commands import version_callback
24
- from .const import logger
25
- from .decorators import ensure_fastapi_rtk_tables_exist
26
- from .types import (
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 .utils import json_to_markdown, run_in_current_event_loop
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 ..globals import g
6
- from .commands import cleanup as _cleanup
7
- from .commands import create_user as _create_user
8
- from .commands import export_data, version_callback
9
- from .commands import reset_password as _reset_password
10
- from .decorators import ensure_fastapi_rtk_tables_exist
11
- from .utils import run_in_current_event_loop
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()