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,364 +0,0 @@
1
- import collections
2
- import json
3
- import typing
4
-
5
- import fastapi
6
- import gremlin_python.process.graph_traversal
7
- import gremlin_python.process.traversal
8
- import janusgraph_python.process.traversal
9
-
10
- from ...const import logger
11
- from ...setting import Setting
12
- from ...utils import lazy
13
- from ..generic.exceptions import MultipleColumnsException
14
- from ..generic.model import GenericModel, Mapper
15
- from .column import GremlinColumn, GremlinRelationship
16
- from .exceptions import GremlinMissingLabelException, LKMultipleException
17
-
18
- __all__ = ["GremlinModel"]
19
-
20
- UNSPECIFIED_LABEL = "__unspecified_label_"
21
-
22
- try:
23
- import janusgraph_python
24
-
25
- janusgraph_python_exists = True
26
- except ImportError:
27
- janusgraph_python_exists = False
28
-
29
-
30
- class GremlinMapper(Mapper[GremlinColumn | GremlinRelationship]):
31
- lk: str | None = None
32
- """
33
- The label key of the model. Used to set the label from the database to the model. Defaults to `label`.
34
- """
35
-
36
-
37
- class GremlinModel(GenericModel):
38
- """
39
- A base class for Gremlin models, which are used to represent vertices in a Gremlin graph.
40
-
41
- ## Example:
42
-
43
- ```python
44
- from fastapi_rtk import GremlinModel, GremlinColumn, GremlinRelationship, GremlinInterface, ModelRestApi
45
- from app import toolkit # Assuming you have a `toolkit` instance set up for your FastAPI application.
46
-
47
- class User(GremlinModel):
48
- __label__ = "Person"
49
-
50
- id = GremlinColumn(str, primary_key=True) # Primary key of the model, typically the vertex ID in the graph.
51
- label = GremlinColumn(str, label_key=True, default="Person") # Label of the vertex. Label from the graph will be stored here.
52
- firstname = GremlinColumn(str, default="") # A property in the model, which will be stored as a property in the vertex.
53
- type = GremlinColumn(str, default="user") # A type of the model, which can be used to filter vertices in the graph.
54
-
55
- tasks = GremlinRelationship(
56
- list,
57
- name="related", # The name of the edge in the graph.
58
- properties={"type": "task"}, # Additional properties to filter the edge.
59
- uselist=True, # Whether the relationship is a list of related vertices.
60
- obj="Task", # The model class that this relationship points to. If given, the related object can be added or edited.
61
- obj_properties={"type": "task"}, # Additional properties to filter the related vertex.
62
- )
63
-
64
- friends = GremlinRelationship(
65
- list,
66
- name="related",
67
- properties={"type": "friend"},
68
- uselist=True,
69
- with_edge=True, # If True, the column will return a dictionary with two keys: "edge" and the property name. The "edge" key will contain the edge properties. If `obj` is set, the related object needs to have an `edge` property to hold the edge properties.
70
- direction="both", # The direction of the relationship. Use "in" for incoming relationships, "out" for outgoing relationships, or "both" for both directions.
71
- obj_properties={"type": "user"}, # Additional properties to filter the related vertex.
72
- ) # Without `obj`, frontend will see the column as a simple dictionary.
73
-
74
- class Task(GremlinModel):
75
- __label__ = "Task"
76
-
77
- ... # Similar to User, but represents a task in the graph.
78
-
79
- class UserApi(ModelRestApi):
80
- resource_name = "users" # The name of the API resource.
81
- datamodel = GremlinInterface(User) # The data model for the API, which is the User model.
82
-
83
- ... # Additional API methods and configurations.
84
-
85
- toolkit.add_api(UserApi) # Register the API with the toolkit.
86
- ```
87
- """
88
-
89
- __ignore_init__ = True
90
- __model_callbacks__: dict[
91
- str, list[typing.Callable[[typing.Type["GremlinModel"]], None]]
92
- ] = collections.defaultdict(list)
93
-
94
- __mapper__: GremlinMapper = lazy(lambda: GremlinMapper(), only_instance=False)
95
- """
96
- A mapper that contains the primary key, properties, and columns of the model.
97
- """
98
-
99
- def __init_subclass__(cls):
100
- if cls.__label__ is None:
101
- if not cls.__ignore_label__:
102
- raise GremlinMissingLabelException(
103
- f"Gremlin model '{cls.__name__}' must have a label defined. If you want to ignore this, set '__ignore_label__ = True' in the class definition."
104
- )
105
- else:
106
- logger.info(
107
- f"Gremlin model '{cls.__name__}' does not have a label defined. This will result in query of vertices without any filtering on label."
108
- )
109
-
110
- if cls.__name__ in GremlinModel.__model_callbacks__:
111
- for callback in GremlinModel.__model_callbacks__[cls.__name__]:
112
- callback(cls)
113
- del GremlinModel.__model_callbacks__[cls.__name__]
114
-
115
- # Do not ignore the init for subclasses of GremlinModel
116
- cls.__ignore_init__ = False
117
- super().__init_subclass__()
118
- cls.__mapper__.columns = [
119
- x
120
- for x in cls.__mapper__.columns
121
- if not isinstance(cls.__mapper__.properties[x], GremlinRelationship)
122
- ]
123
- for key, value in cls.__mapper__.properties.items():
124
- if value.label_key:
125
- if cls.__mapper__.lk and cls.__mapper__.lk != key:
126
- raise LKMultipleException(
127
- cls.__name__, "Only one label key is allowed"
128
- )
129
- cls.__mapper__.lk = key
130
-
131
- __label__: str | None = None
132
- """
133
- The label of the model, used to filter vertices in Gremlin queries. If not set, the model will not be filtered by label, which may lead to unexpected results.
134
- """
135
- __properties__: dict[str, typing.Any] | None = None
136
- """
137
- Properties of the model, used to filter vertices in Gremlin queries.
138
- """
139
- __ignore_label__ = False
140
- """
141
- If set to True, the model will not require a label. This is useful for models that do not represent a specific vertex type, or when the label is not relevant for the model's functionality.
142
- """
143
- __session__: gremlin_python.process.graph_traversal.GraphTraversalSource = None
144
- """
145
- The session used for executing Gremlin queries. This is typically set by the interface.
146
- """
147
- __data__: dict[str, typing.Any] = None
148
- """
149
- The data of the model instance, typically set during initialization. You can get the whole data from the database by accessing this attribute.
150
- """
151
- __reserved_letters__ = lazy(lambda: Setting.JANUSGRAPH_RESERVED_LETTERS)
152
- """
153
- A list of reserved letters that cannot be used in the primary key of the model. This is to avoid conflicts with JanusGraph's internal representation of vertices and edges. Only used if `janusgraph_python` is available.
154
- """
155
- __janusgraph_python_exists__ = lazy(
156
- lambda: janusgraph_python_exists, only_instance=False
157
- )
158
- """
159
- A boolean indicating whether the `janusgraph_python` package is available.
160
- """
161
-
162
- def __init__(self, __table__=None, **kwargs):
163
- self.__table__ = __table__
164
- super().__init__(**kwargs)
165
- self.__data__ = kwargs
166
-
167
- @classmethod
168
- def from_gremlinpython(
169
- cls,
170
- data: dict[str, typing.Any],
171
- /,
172
- preserve_as_list: list[str] | None = None,
173
- ):
174
- """
175
- Create an instance of the model from a dictionary typically returned by a Gremlin query.
176
-
177
- Args:
178
- data (dict[str, typing.Any]): The dictionary to parse, typically from a Gremlin query with valueMap.
179
- preserve_as_list (list[str] | None, optional): A list of keys that should be preserved as lists even if they contain a single item. If None, all keys are treated normally. Defaults to None.
180
-
181
- Returns:
182
- GremlinModel: An instance of the model with properties set from the parsed data.
183
- """
184
- params = {"id_key": cls.__mapper__.pk, "preserve_as_list": preserve_as_list}
185
- if cls.__mapper__.lk:
186
- params["label_key"] = cls.__mapper__.lk
187
- parsed_data = cls.parse_from_gremlinpython(data, **params)
188
- for key in parsed_data.keys():
189
- column = cls.__mapper__.properties.get(key)
190
- if (
191
- not column
192
- or not isinstance(column, GremlinRelationship)
193
- or not column.obj
194
- ):
195
- continue
196
- value = parsed_data[key]
197
- if not value:
198
- continue
199
- if column.uselist:
200
- value = [column.obj.from_gremlinpython(v) for v in value]
201
- else:
202
- value = column.obj.from_gremlinpython(value)
203
- parsed_data[key] = value
204
- return cls(__table__=True, **parsed_data)
205
-
206
- def to_gremlinpython(self):
207
- """
208
- Convert the model instance to a Gremlin Python compatible dictionary.
209
-
210
- Returns:
211
- dict: A dictionary representation of the model instance suitable for Gremlin Python, with keys for the primary key and label, and other properties as they are.
212
- """
213
- return self.parse_to_gremlinpython(
214
- self.to_json(with_id=False, with_name=False),
215
- id_key=self.__mapper__.pk,
216
- label_key=self.__mapper__.lk,
217
- )
218
-
219
- def to_json(self, *, with_id=True, with_name=True):
220
- """
221
- Convert the model instance to a JSON-compatible dictionary.
222
-
223
- - Properties that are not set will be excluded from the output.
224
- - Properties that are instances of `GremlinModel` will be converted to their JSON representation.
225
- - Properties that are lists or tuples will have their items converted to JSON representations if they are instances of `GremlinModel`.
226
-
227
- Args:
228
- with_id (bool, optional): Whether to include the `id_` field in the output. Defaults to True.
229
- with_name (bool, optional): Whether to include the `name_` field in the output. Defaults to True.
230
-
231
- Returns:
232
- dict: A dictionary representation of the model instance, with keys for each property and the `id_` and `name_` fields if specified.
233
- """
234
- data = dict[str, typing.Any]()
235
- cls = self.__class__
236
- for key in self.__mapper__.properties.keys():
237
- if not getattr(cls, key).is_column_set(self):
238
- continue
239
- value = getattr(self, key)
240
- if isinstance(value, GremlinModel):
241
- value = value.to_json(with_id=with_id, with_name=with_name)
242
- elif isinstance(value, (list, tuple)):
243
- value = [
244
- v.to_json(with_id=with_id, with_name=with_name)
245
- if isinstance(v, GremlinModel)
246
- else v
247
- for v in value
248
- ]
249
- data[key] = value
250
- if with_id:
251
- data["id_"] = self.id_
252
- if with_name:
253
- data["name_"] = self.name_
254
- return data
255
-
256
- @staticmethod
257
- def parse_from_gremlinpython(
258
- data: dict[str, typing.Any],
259
- /,
260
- id_key="id",
261
- label_key="label",
262
- preserve_as_list: list[str] | None = None,
263
- ):
264
- """
265
- Parse a dictionary from Gremlin Python into a dictionary with specific keys.
266
-
267
- Args:
268
- data (dict[str, typing.Any]): The dictionary to parse, typically from a Gremlin query with valueMap.
269
- id_key (str, optional): The key for the primary key in the parsed dictionary. Defaults to "id".
270
- label_key (str, optional): The key for the label in the parsed dictionary. Defaults to "label".
271
- preserve_as_list (list[str] | None, optional): A list of keys that should be preserved as lists even if they contain a single item. If None, all keys are treated normally. Defaults to None.
272
-
273
- Returns:
274
- dict: A dictionary with keys for the primary key and label, and other properties as they are.
275
- """
276
- parsed = dict[str, typing.Any]()
277
- for key, value in data.items():
278
- if (
279
- (isinstance(value, list) or isinstance(value, tuple))
280
- and len(value) == 1
281
- and (preserve_as_list is None or key not in preserve_as_list)
282
- ):
283
- value = value[0]
284
-
285
- # A dictionary means it is coming from the database directly, and not a stringified JSON.
286
- if isinstance(value, dict):
287
- value = GremlinModel.parse_from_gremlinpython(value)
288
- elif isinstance(
289
- value, janusgraph_python.process.traversal.RelationIdentifier
290
- ):
291
- parsed["source"] = value.out_vertex_id
292
- parsed["target"] = value.in_vertex_id
293
- value = str(value)
294
-
295
- try:
296
- value = json.loads(value)
297
- except json.JSONDecodeError:
298
- pass
299
- except TypeError:
300
- pass
301
- if key == gremlin_python.process.traversal.T.id:
302
- parsed[id_key] = value
303
- elif key == gremlin_python.process.traversal.T.label:
304
- parsed[label_key] = value
305
- else:
306
- parsed[key] = value
307
- return parsed
308
-
309
- @staticmethod
310
- def parse_to_gremlinpython(
311
- data: dict[str, typing.Any], /, id_key="id", label_key="label"
312
- ):
313
- """
314
- Convert a dictionary to a Gremlin Python compatible format.
315
-
316
- Args:
317
- data (dict[str, typing.Any]): The dictionary to convert.
318
- id_key (str): The key for the primary key in Gremlin Python.
319
- label_key (str): The key for the label in Gremlin Python.
320
-
321
- Returns:
322
- dict: A dictionary representation suitable for Gremlin Python.
323
- """
324
- result = dict[str, typing.Any]()
325
- for key, value in data.items():
326
- if isinstance(value, (list, tuple, dict)):
327
- value = json.dumps(value)
328
- if key == id_key:
329
- key = gremlin_python.process.traversal.T.id
330
- elif key == label_key:
331
- key = gremlin_python.process.traversal.T.label
332
- result[key] = value
333
- return result
334
-
335
- @staticmethod
336
- def _register_model_callback(
337
- model_name: str, callback: typing.Callable[[typing.Type["GremlinModel"]], None]
338
- ):
339
- """
340
- Register a callback for a model that will be called when the model is loaded.
341
-
342
- Args:
343
- model_name (str): The name of the model to register the callback for.
344
- callback (typing.Callable[[typing.Type["GremlinModel"]], None]): The callback function to register. It should accept a single argument, which is the model class itself.
345
- """
346
- GremlinModel.__model_callbacks__[model_name].append(callback)
347
-
348
- def is_model_valid(self):
349
- try:
350
- super().is_model_valid()
351
- except MultipleColumnsException:
352
- if not self.__table__:
353
- raise
354
- if not self.__janusgraph_python_exists__:
355
- return
356
- pk = self.get_pk()
357
- if isinstance(pk, str):
358
- for letter in self.__reserved_letters__:
359
- if letter in pk:
360
- raise fastapi.HTTPException(
361
- fastapi.status.HTTP_400_BAD_REQUEST,
362
- f"Primary key '{pk}' contains reserved letter '{letter}'. "
363
- "Please use a different primary key.",
364
- )
@@ -1,23 +0,0 @@
1
- from .db import graph_db
2
-
3
- __all__ = ["get_graph_traversal_factory"]
4
-
5
-
6
- def get_graph_traversal_factory(keep_open=False):
7
- """
8
- Factory function that returns a generator for creating Gremlin graph traversals.
9
-
10
- Can be used as a dependency in FastAPI routes.
11
-
12
- Args:
13
- keep_open (bool, optional): Whether to keep the connection open after use. Defaults to False.
14
-
15
- Returns:
16
- typing.Callable[[], typing.Generator[gremlin_python.process.graph_traversal.GraphTraversal, Any, None]]: A generator function that yields a graph traversal object.
17
- """
18
-
19
- def get_graph_traversal():
20
- with graph_db.session(keep_open=keep_open) as session:
21
- yield session
22
-
23
- return get_graph_traversal
@@ -1,295 +0,0 @@
1
- import os
2
- from pathlib import Path
3
- from typing import Literal
4
-
5
- import sqlalchemy
6
- import typer
7
- from alembic import command
8
- from alembic.config import Config as AlembicConfig
9
- from rich import print
10
-
11
- from ..db import db
12
- from ..globals import g
13
- from ..security.sqla.models import Role
14
- from ..utils import safe_call
15
- from ..version import __version__
16
-
17
-
18
- class Config(AlembicConfig):
19
- def __init__(self, *args, **kwargs):
20
- self.template_directory = kwargs.pop("template_directory", None)
21
- super().__init__(*args, **kwargs)
22
-
23
- def get_template_directory(self):
24
- if self.template_directory:
25
- return self.template_directory
26
- package_dir = os.path.abspath(os.path.dirname(__file__))
27
- return os.path.join(package_dir, "templates")
28
-
29
-
30
- def version_callback(value: bool) -> None:
31
- if value:
32
- print(f"FastAPI-RTK CLI version: [green]{__version__}[/green]")
33
- raise typer.Exit()
34
-
35
-
36
- def path_callback(value: Path | None) -> None:
37
- g.path = value
38
-
39
-
40
- def list_templates():
41
- config = Config()
42
- config.print_stdout("Available templates:\n")
43
- for tempname in sorted(os.listdir(config.get_template_directory())):
44
- with open(
45
- os.path.join(config.get_template_directory(), tempname, "README")
46
- ) as readme:
47
- synopsis = next(readme).strip()
48
- config.print_stdout("%s - %s", tempname, synopsis)
49
-
50
-
51
- def init(
52
- directory: str = "migrations",
53
- multidb: bool = False,
54
- template: str = "fastapi",
55
- package: bool = False,
56
- ):
57
- template_directory = None
58
- if "/" in template or "\\" in template:
59
- template_directory, template = os.path.split(template)
60
- config = Config(template_directory=template_directory)
61
- config.set_main_option("script_location", directory)
62
- config.config_file_name = os.path.join(directory, "alembic.ini")
63
- # TODO: Fix this
64
- # config = current_app.extensions["migrate"].migrate.call_configure_callbacks(config)
65
- if multidb and template == "fastapi":
66
- template = "fastapi-multidb"
67
- command.init(config, directory, template=template, package=package)
68
-
69
-
70
- def revision(
71
- directory: str = "migrations",
72
- message: str = "",
73
- autogenerate: bool = False,
74
- sql: bool = False,
75
- head: str = "head",
76
- splice: bool = False,
77
- branch_label: str = None,
78
- version_path: str = None,
79
- rev_id: str = None,
80
- ):
81
- opts = ["autogenerate"] if autogenerate else None
82
- config = Config()
83
- config.set_main_option("script_location", directory)
84
- config.cmd_opts = opts
85
- # config = current_app.extensions["migrate"].migrate.get_config(directory, opts=opts)
86
- command.revision(
87
- config,
88
- message,
89
- autogenerate=autogenerate,
90
- sql=sql,
91
- head=head,
92
- splice=splice,
93
- branch_label=branch_label,
94
- version_path=version_path,
95
- rev_id=rev_id,
96
- )
97
-
98
-
99
- def migrate(
100
- directory: str = "migrations",
101
- message: str = "",
102
- sql: bool = False,
103
- head: str = "head",
104
- splice: bool = False,
105
- branch_label: str = None,
106
- version_path: str = None,
107
- rev_id: str = None,
108
- ):
109
- return revision(
110
- directory=directory,
111
- message=message,
112
- autogenerate=True,
113
- sql=sql,
114
- head=head,
115
- splice=splice,
116
- branch_label=branch_label,
117
- version_path=version_path,
118
- rev_id=rev_id,
119
- )
120
-
121
-
122
- def edit(
123
- directory: str = "migrations",
124
- revision: str = "current",
125
- ):
126
- config = Config()
127
- config.set_main_option("script_location", directory)
128
- command.edit(config, revision)
129
-
130
-
131
- def merge(
132
- directory: str = "migrations",
133
- revisions: str = "",
134
- message: str = "",
135
- branch_label: str = None,
136
- rev_id: str = None,
137
- ):
138
- config = Config()
139
- config.set_main_option("script_location", directory)
140
- command.merge(
141
- config,
142
- revisions,
143
- message=message,
144
- branch_label=branch_label,
145
- rev_id=rev_id,
146
- )
147
-
148
-
149
- def upgrade(
150
- directory: str = "migrations",
151
- revision: str = "head",
152
- sql: bool = False,
153
- tag: str = None,
154
- ):
155
- config = Config()
156
- config.set_main_option("script_location", directory)
157
- command.upgrade(config, revision, sql=sql, tag=tag)
158
-
159
-
160
- def downgrade(
161
- directory: str = "migrations",
162
- revision: str = "-1",
163
- sql: bool = False,
164
- tag: str = None,
165
- ):
166
- config = Config()
167
- config.set_main_option("script_location", directory)
168
- command.downgrade(config, revision, sql=sql, tag=tag)
169
-
170
-
171
- def show(directory: str = "migrations", revision: str = "head"):
172
- config = Config()
173
- config.set_main_option("script_location", directory)
174
- command.show(config, revision)
175
-
176
-
177
- def history(
178
- directory: str = "migrations",
179
- rev_range: str = None,
180
- verbose: bool = False,
181
- indicate_current: bool = False,
182
- ):
183
- config = Config()
184
- config.set_main_option("script_location", directory)
185
- command.history(
186
- config, rev_range, verbose=verbose, indicate_current=indicate_current
187
- )
188
-
189
-
190
- def heads(
191
- directory: str = "migrations",
192
- verbose: bool = False,
193
- resolve_dependencies: bool = False,
194
- ):
195
- config = Config()
196
- config.set_main_option("script_location", directory)
197
- command.heads(config, verbose=verbose, resolve_dependencies=resolve_dependencies)
198
-
199
-
200
- def branches(
201
- directory: str = "migrations",
202
- verbose: bool = False,
203
- ):
204
- config = Config()
205
- config.set_main_option("script_location", directory)
206
- command.branches(config, verbose=verbose)
207
-
208
-
209
- def current(directory: str = "migrations", verbose: bool = False):
210
- config = Config()
211
- config.set_main_option("script_location", directory)
212
- command.current(config, verbose=verbose)
213
-
214
-
215
- def stamp(
216
- directory: str = "migrations",
217
- revision: str = "head",
218
- sql: bool = False,
219
- tag: str = None,
220
- purge: bool = False,
221
- ):
222
- config = Config()
223
- config.set_main_option("script_location", directory)
224
- command.stamp(config, revision, sql=sql, tag=tag, purge=purge)
225
-
226
-
227
- def check(directory: str = "migrations"):
228
- config = Config()
229
- config.set_main_option("script_location", directory)
230
- command.check(config)
231
-
232
-
233
- ### RTK CLI ###
234
-
235
-
236
- async def _check_roles(name: str, create: bool = False):
237
- async with db.session() as session:
238
- stmt = sqlalchemy.select(Role).where(Role.name == name)
239
- role = (await safe_call(session.execute(stmt))).scalar_one_or_none()
240
- if not role:
241
- if not create:
242
- raise Exception(f"Role {name} does not exist")
243
- await g.current_app.security.create_role(name=name, session=session)
244
-
245
-
246
- async def create_user(
247
- username: str,
248
- email: str,
249
- password: str,
250
- first_name: str = "",
251
- last_name: str = "",
252
- role: str = "",
253
- create_role: bool = False,
254
- ):
255
- """
256
- Create an admin user.
257
- """
258
- if role:
259
- await _check_roles(role, create=create_role)
260
- return await g.current_app.security.create_user(
261
- email=email,
262
- username=username,
263
- password=password,
264
- first_name=first_name,
265
- last_name=last_name,
266
- roles=[role] if role else None,
267
- )
268
-
269
-
270
- async def reset_password(email_or_username: str, password: str):
271
- """
272
- Reset user password.
273
- """
274
- user = await g.current_app.security.get_user(email_or_username)
275
- return await g.current_app.security.reset_password(user, password)
276
-
277
-
278
- async def export_data(
279
- file_path: str,
280
- data: Literal["users", "roles"],
281
- type: Literal["json", "csv"] = "json",
282
- ):
283
- """
284
- Export data.
285
- """
286
- data = await g.current_app.security.export_data(data, type)
287
- with open(file_path, "w") as f:
288
- f.write(data)
289
-
290
-
291
- async def cleanup():
292
- """
293
- Cleanup unused permissions from apis and roles.
294
- """
295
- await g.current_app.security.cleanup()
@@ -1,23 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: fastapi-rtk
3
- Version: 0.2.27
4
- Summary: A package that provides a set of tools to build a FastAPI application with a Class-Based CRUD API.
5
- Author-email: Matthias Leinweber <m.leinweber@datatactics.de>, Agustinus Nicander Hery <a.hery@datatactics.de>
6
- Project-URL: Homepage, https://github.com/dttctcs/fastapi-rtk
7
- Project-URL: Issues, https://github.com/dttctcs/fastapi-rtk/issues
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.10
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Requires-Dist: fastapi[standard]
15
- Requires-Dist: fastapi-users[oauth,sqlalchemy]
16
- Requires-Dist: sqlalchemy_utils
17
- Requires-Dist: alembic
18
- Requires-Dist: prometheus-fastapi-instrumentator
19
- Requires-Dist: marshmallow-sqlalchemy
20
- Requires-Dist: jsonschema2md
21
- Requires-Dist: Secweb
22
- Requires-Dist: beautifulsoup4
23
- Dynamic: license-file