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
@@ -0,0 +1,299 @@
1
+ import os
2
+ from typing import Annotated, Union
3
+
4
+ import fastapi_babel
5
+ import typer
6
+
7
+ from ...const import INTERNAL_LANG_FOLDER
8
+ from ...lang import __all__
9
+ from ...lang.babel import FastAPIRTKBabelCLI, FastAPIRTKBabelConfigs
10
+ from ...setting import Setting
11
+ from ..cli import app
12
+ from ..decorators import check_existing_app_silent
13
+ from ..utils import merge_pot_files
14
+ from . import version_callback
15
+
16
+ translate_app = typer.Typer(rich_markup_mode="rich")
17
+ app.add_typer(translate_app, name="translate")
18
+
19
+ DEFAULT_EXTRACT_KEYWORDS = f"{' '.join(__all__)} lazy_gettext"
20
+
21
+ RootPathType = Annotated[
22
+ str | None,
23
+ typer.Option(
24
+ help="Path to the root directory of the Babel. If not provided, `LANG_FOLDER` setting will be used. If `BABEL_OPTIONS` contains `ROOT_DIR`, it will be used instead."
25
+ ),
26
+ ]
27
+
28
+ BabelCfgType = Annotated[
29
+ str | None,
30
+ typer.Option(
31
+ help="Path to the Babel configuration file. If not provided, it will check for `babel.cfg` in the root directory first, then use the default from `fastapi_rtk` library."
32
+ ),
33
+ ]
34
+
35
+ ExtractDirType = Annotated[
36
+ str,
37
+ typer.Option(
38
+ help="Directory to extract translations from. Defaults to the current directory."
39
+ ),
40
+ ]
41
+
42
+ KeywordsType = Annotated[
43
+ str,
44
+ typer.Option(
45
+ help="Additional keywords to extract translations for separated by space."
46
+ ),
47
+ ]
48
+
49
+ LocalesType = Annotated[
50
+ str | None,
51
+ typer.Option(
52
+ help="Locales to initialize, separated by commas. If not provided, `LANGUAGES` setting will be used."
53
+ ),
54
+ ]
55
+
56
+ MergeWithInternalType = Annotated[
57
+ bool,
58
+ typer.Option(
59
+ help="Merge translations with internal translations from `fastapi_rtk` library."
60
+ ),
61
+ ]
62
+
63
+
64
+ @translate_app.callback()
65
+ @check_existing_app_silent
66
+ def callback(
67
+ version: Annotated[
68
+ Union[bool, None],
69
+ typer.Option(
70
+ "--version", help="Show the version and exit.", callback=version_callback
71
+ ),
72
+ ] = None,
73
+ ) -> None:
74
+ """
75
+ FastAPI RTK Translate CLI - The [bold]fastapi-rtk translate[/bold] command line app. 😎
76
+
77
+ Manage your [bold]FastAPI React Toolkit[/bold] translations easily with this CLI.
78
+ """
79
+
80
+
81
+ @translate_app.command()
82
+ def extract(
83
+ root_path: RootPathType = None,
84
+ babel_cfg: BabelCfgType = None,
85
+ extract_dir: ExtractDirType = ".",
86
+ keywords: KeywordsType = DEFAULT_EXTRACT_KEYWORDS,
87
+ ):
88
+ """
89
+ Extract translations from the source code.
90
+ """
91
+ babel_cli = init_babel_cli(root_path=root_path, babel_cfg=babel_cfg)
92
+ typer.echo(f"Extracting translations with additional keywords: {keywords}")
93
+ babel_cli.extract(extract_dir, keywords)
94
+
95
+
96
+ @translate_app.command()
97
+ def init(
98
+ root_path: RootPathType = None,
99
+ babel_cfg: BabelCfgType = None,
100
+ locales: LocalesType = None,
101
+ merge_with_internal: MergeWithInternalType = True,
102
+ ):
103
+ """
104
+ Initialize translations for the specified locales. If the locale files do not exist, it will create them based on the domain file (messages.pot). If the domain file does not exist, it will run the `extract` command first.
105
+ """
106
+ babel_cli = init_babel_cli(root_path=root_path, babel_cfg=babel_cfg)
107
+
108
+ # Check for the domain file
109
+ domain_file = babel_cli.babel.config.BABEL_MESSAGE_POT_FILE
110
+ if not os.path.exists(domain_file):
111
+ typer.echo(
112
+ f"Domain file {domain_file} does not exist. running `extract` command"
113
+ )
114
+ extract_dir = babel_cli.babel.config.ROOT_DIR
115
+ # If the extract_dir does not contain any .py files, we will run the extract command on the parent directory
116
+ if not any(f.endswith(".py") for f in os.listdir(extract_dir)):
117
+ extract_dir = os.path.join(extract_dir, "..")
118
+
119
+ extract(
120
+ root_path=root_path,
121
+ babel_cfg=babel_cfg,
122
+ extract_dir=extract_dir,
123
+ merge_with_internal=merge_with_internal,
124
+ )
125
+
126
+ if locales is None:
127
+ locales = Setting.LANGUAGES
128
+ typer.echo(
129
+ f"No locales provided. Using `LANGUAGES` setting: {locales}. If you want to specify locales, use the `--locales` option."
130
+ )
131
+
132
+ for local in locales.split(","):
133
+ local = local.strip()
134
+ # Check whether the locale directory exists
135
+ locale_dir = os.path.join(
136
+ babel_cli.babel.config.BABEL_TRANSLATION_DIRECTORY, local
137
+ )
138
+ if os.path.exists(locale_dir):
139
+ typer.echo(
140
+ f"Locale directory {locale_dir} already exists. Skipping initialization for {local}."
141
+ )
142
+ continue
143
+
144
+ typer.echo(f"Initializing translations for locale: {local}")
145
+ babel_cli.init(local)
146
+
147
+
148
+ @translate_app.command()
149
+ def update(
150
+ root_path: RootPathType = None,
151
+ babel_cfg: BabelCfgType = None,
152
+ locales: LocalesType = None,
153
+ merge_with_internal: MergeWithInternalType = True,
154
+ ):
155
+ """
156
+ Update translations from the domain (messages.pot) file to the locale files. If the locale files do not exist, `init` command will be run for the locale.
157
+ """
158
+ babel = init_babel_cli(root_path=root_path, babel_cfg=babel_cfg)
159
+
160
+ if locales is None:
161
+ locales = Setting.LANGUAGES
162
+ typer.echo(
163
+ f"No locales provided. Using `LANGUAGES` setting: {locales}. If you want to specify locales, use the `--locales` option."
164
+ )
165
+
166
+ for local in locales.split(","):
167
+ local = local.strip()
168
+ locale_dir = os.path.join(babel.babel.config.BABEL_TRANSLATION_DIRECTORY, local)
169
+ if not os.path.exists(locale_dir):
170
+ typer.echo(
171
+ f"Locale directory {locale_dir} does not exist. running `init` command for {local}."
172
+ )
173
+ init(
174
+ root_path=root_path,
175
+ babel_cfg=babel_cfg,
176
+ locales=local,
177
+ merge_with_internal=merge_with_internal,
178
+ )
179
+
180
+ typer.echo(f"Updating translations for locales: {locales}")
181
+ babel.update()
182
+
183
+ if not merge_with_internal:
184
+ typer.echo(
185
+ "Skipping merging with internal translations. If you want to merge with internal translations, use the `--merge-with-internal` option."
186
+ )
187
+ return
188
+
189
+ for local in locales.split(","):
190
+ # Combine the locale with the internal translations
191
+ local = local.strip()
192
+ locale_dir = os.path.join(babel.babel.config.BABEL_TRANSLATION_DIRECTORY, local)
193
+
194
+ internal_locale_dir = os.path.join(INTERNAL_LANG_FOLDER, "translations", local)
195
+ po_file = os.path.join(
196
+ locale_dir,
197
+ "LC_MESSAGES",
198
+ babel.babel.config.BABEL_DOMAIN.replace(".pot", "") + ".po",
199
+ )
200
+ internal_po_file = os.path.join(
201
+ internal_locale_dir,
202
+ "LC_MESSAGES",
203
+ babel.babel.config.BABEL_DOMAIN.replace(".pot", "") + ".po",
204
+ )
205
+ merge_pot_files(pot_files=[po_file, internal_po_file], output_path=po_file)
206
+
207
+
208
+ @translate_app.command()
209
+ def compile(root_path: RootPathType = None, babel_cfg: BabelCfgType = None):
210
+ """
211
+ Compile the translations. This will create the .mo files from the .po files.
212
+ """
213
+ babel = init_babel_cli(root_path=root_path, babel_cfg=babel_cfg)
214
+ typer.echo("Compiling translations.")
215
+ babel.compile()
216
+
217
+
218
+ @translate_app.command()
219
+ def build(
220
+ root_path: RootPathType = None,
221
+ babel_cfg: BabelCfgType = None,
222
+ extract_dir: ExtractDirType = ".",
223
+ keywords: KeywordsType = DEFAULT_EXTRACT_KEYWORDS,
224
+ locales: LocalesType = None,
225
+ merge_with_internal: MergeWithInternalType = True,
226
+ ):
227
+ """
228
+ Build the translations by extracting, updating, and compiling them in one step. This keeps the translations up-to-date and ready for use in your FastAPI application.
229
+ """
230
+ extract(
231
+ root_path=root_path,
232
+ babel_cfg=babel_cfg,
233
+ extract_dir=extract_dir,
234
+ keywords=keywords,
235
+ )
236
+ update(
237
+ root_path=root_path,
238
+ babel_cfg=babel_cfg,
239
+ locales=locales,
240
+ merge_with_internal=merge_with_internal,
241
+ )
242
+ compile(root_path=root_path, babel_cfg=babel_cfg)
243
+
244
+
245
+ def init_babel_cli(
246
+ root_path: RootPathType = None,
247
+ babel_cfg: BabelCfgType = None,
248
+ *,
249
+ create_root_path_if_not_exists=True,
250
+ log=True,
251
+ ):
252
+ """
253
+ Initialize Babel CLI with the provided root path and Babel configuration file.
254
+
255
+ Args:
256
+ root_path (RootPathType): Path to the root directory of the Babel. If not provided, `LANG_FOLDER` setting will be used. If `BABEL_OPTIONS` contains `ROOT_DIR`, it will be used instead.
257
+ babel_cfg (BabelCfgType): Path to the Babel configuration file. If not provided, it will check for `babel.cfg` in the root directory first, then use the default from `fastapi_rtk` library.
258
+ create_if_not_exists (bool): Whether to create the root path if it does not exist. Defaults to True.
259
+ log (bool): Whether to log the actions taken. Defaults to True.
260
+
261
+ Raises:
262
+ FileNotFoundError: If the root path does not exist and `create_root_path_if_not_exists` is False.
263
+
264
+ Returns:
265
+ FastAPIRTKBabelCLI: An instance of the FastAPIRTKBabelCLI class initialized with the Babel configurations.
266
+ """
267
+ if root_path is None and "ROOT_DIR" not in Setting.BABEL_OPTIONS:
268
+ root_path = Setting.LANG_FOLDER
269
+ log and typer.echo(
270
+ f"No root directory provided. Using `LANG_FOLDER` setting: {root_path}"
271
+ )
272
+ if babel_cfg is None and "BABEL_CONFIG_FILE" not in Setting.BABEL_OPTIONS:
273
+ file_exists = os.path.exists(os.path.join(root_path, "babel.cfg"))
274
+ if not file_exists:
275
+ babel_cfg = os.path.join(INTERNAL_LANG_FOLDER, "babel.cfg")
276
+ log and typer.echo(
277
+ f"No Babel configuration file provided. Using default from {babel_cfg}"
278
+ )
279
+
280
+ # Ensure root_path exists
281
+ if not os.path.exists(root_path):
282
+ log and typer.echo(f"Root path {root_path} does not exist.")
283
+ if create_root_path_if_not_exists:
284
+ log and typer.echo(f"Creating necessary directories at {root_path}.")
285
+ os.makedirs(root_path, exist_ok=True)
286
+ else:
287
+ raise FileNotFoundError(f"Root path {root_path} does not exist.")
288
+
289
+ kwargs = {
290
+ "ROOT_DIR": root_path,
291
+ "BABEL_DEFAULT_LOCALE": "en",
292
+ "BABEL_TRANSLATION_DIRECTORY": "translations",
293
+ } | Setting.BABEL_OPTIONS
294
+ if babel_cfg is not None:
295
+ kwargs["BABEL_CONFIG_FILE"] = babel_cfg
296
+
297
+ return FastAPIRTKBabelCLI(
298
+ fastapi_babel.Babel(configs=FastAPIRTKBabelConfigs(**kwargs))
299
+ )
@@ -21,19 +21,24 @@ def ensure_fastapi_rtk_tables_exist(f):
21
21
  return wrapper
22
22
 
23
23
 
24
- def _check_existing_app(f):
24
+ def _check_existing_app(f, *, silent=False):
25
25
  @wraps(f)
26
26
  def wrapper(*args, **kwargs):
27
27
  try:
28
28
  get_import_data(path=g.path)
29
- return f(*args, **kwargs)
30
29
  except FastAPICLIException as e:
31
- logger.error(str(e))
32
- raise typer.Exit(code=1) from None
30
+ if not silent:
31
+ logger.error(str(e))
32
+ raise typer.Exit(code=1) from None
33
+ return f(*args, **kwargs)
33
34
 
34
35
  return wrapper
35
36
 
36
37
 
38
+ def check_existing_app_silent(f):
39
+ return _check_existing_app(f, silent=True)
40
+
41
+
37
42
  def _set_migrate_mode(f):
38
43
  @wraps(f)
39
44
  def wrapper(*args, **kwargs):
fastapi_rtk/cli/utils.py CHANGED
@@ -1,7 +1,9 @@
1
1
  import asyncio
2
+ import os
2
3
  import time
3
4
  from typing import Any, Coroutine
4
5
 
6
+ import polib
5
7
  from annotated_types import T
6
8
 
7
9
 
@@ -132,3 +134,47 @@ def json_to_markdown(
132
134
  else:
133
135
  content += f"{data}{'\n' * newline_length}"
134
136
  return content
137
+
138
+
139
+ def merge_pot_files(pot_files: list[str], output_path: str):
140
+ """
141
+ Merges multiple .pot files into a single .pot file while preserving header information from the first file.
142
+
143
+ This function:
144
+ - Loads the first .pot file to use as a base, ensuring that its header is preserved.
145
+ - Iterates over the remaining .pot files.
146
+ - Adds comment for the file source for the first entry of each subsequent .pot file.
147
+ - Appends any translation entries not already present in the base file.
148
+ - Merges source references (occurrences) for entries that already exist in the base file to include unique locations.
149
+
150
+ Args:
151
+ pot_files (list[str]): A list of file paths pointing to .pot files that need to be merged.
152
+ output_path (str): The file path where the merged .pot file will be saved.
153
+
154
+ Returns:
155
+ None
156
+ """
157
+ base = polib.pofile(pot_files[0]) # use first file to preserve header
158
+
159
+ for pot_path in pot_files[1:]:
160
+ pot = polib.pofile(pot_path)
161
+ first_entry = True
162
+ for entry in pot:
163
+ if first_entry:
164
+ # Add label where the file coming from
165
+ relative_path = os.path.relpath(
166
+ pot_path, start=os.path.dirname(output_path)
167
+ )
168
+ entry.comment = f"File: {relative_path}"
169
+ first_entry = False
170
+
171
+ existing = base.find(entry.msgid)
172
+ if not existing:
173
+ base.append(entry)
174
+ else:
175
+ # Optionally merge source refs
176
+ existing.occurrences = list(
177
+ set(existing.occurrences + entry.occurrences)
178
+ )
179
+
180
+ base.save(output_path)
fastapi_rtk/config.py CHANGED
@@ -6,11 +6,36 @@ import os
6
6
  import types
7
7
  import typing as t
8
8
 
9
- from .utils import import_string
9
+ from .utils import deep_merge, import_string
10
10
 
11
11
  __all__ = ["Config"]
12
12
 
13
13
 
14
+ class ConfigKeyword:
15
+ ENABLE_DEEP_MERGE = (
16
+ "CONFIG_ENABLE_DEEP_MERGE",
17
+ bool,
18
+ "a reserved keyword for config to enable deep merge",
19
+ )
20
+ DEEP_MERGE_WHITELIST = (
21
+ "CONFIG_DEEP_MERGE_WHITELIST",
22
+ (list, tuple),
23
+ "a reserved keyword for config to define a whitelist of keys to deep merge",
24
+ )
25
+
26
+ @classmethod
27
+ def check_key_value(cls, key: str, value: t.Any):
28
+ for item in [
29
+ v for k, v in cls.__dict__.items() if k.isupper() and isinstance(v, tuple)
30
+ ]:
31
+ if isinstance(item, tuple) and item[0] == key:
32
+ expected_type = item[1]
33
+ if not isinstance(value, expected_type):
34
+ raise TypeError(
35
+ f"Expected type {expected_type} for key '{key}', got {type(value)}, '{key}' is {item[2]}"
36
+ )
37
+
38
+
14
39
  class Config(dict):
15
40
  """
16
41
  COPIED FROM FLASK
@@ -60,6 +85,21 @@ class Config(dict):
60
85
 
61
86
  __callbacks: list[t.Callable[..., None]] = None
62
87
 
88
+ def __setitem__(self, key, value):
89
+ ConfigKeyword.check_key_value(key, value)
90
+ if (
91
+ ConfigKeyword.ENABLE_DEEP_MERGE[0] in self
92
+ and self[ConfigKeyword.ENABLE_DEEP_MERGE[0]]
93
+ and key in self
94
+ and isinstance(value, dict)
95
+ and isinstance(self[key], dict)
96
+ ):
97
+ whitelist = self.get(ConfigKeyword.DEEP_MERGE_WHITELIST[0], None)
98
+ if whitelist is None or key in whitelist:
99
+ value = deep_merge(self[key], value)
100
+
101
+ return super().__setitem__(key, value)
102
+
63
103
  def add_callback(self, callback: t.Callable[..., None]) -> None:
64
104
  """Add a function to call after the configuration is updated.
65
105
 
fastapi_rtk/const.py CHANGED
@@ -1,10 +1,13 @@
1
+ import enum
1
2
  import inspect
2
3
  import logging
4
+ import os
3
5
  from typing import Literal
4
6
 
5
7
  import Secweb.ContentSecurityPolicy
6
8
  import Secweb.StrictTransportSecurity
7
9
 
10
+ from . import lang
8
11
  from .utils import deep_merge
9
12
 
10
13
  __all__ = ["logger"]
@@ -26,6 +29,7 @@ ASSOC_USER_ROLE_SEQUENCE = "ab_user_role_id_seq"
26
29
  OAUTH_TABLE = "ab_oauth_account"
27
30
  OAUTH_SEQUENCE = "ab_oauth_account_id_seq"
28
31
  ACCESSTOKEN_TABLE = "ab_accesstoken"
32
+ DEFAULT_METADATA_KEY = "default"
29
33
 
30
34
  FASTAPI_RTK_TABLES = [
31
35
  USER_TABLE,
@@ -72,7 +76,10 @@ DEFAULT_COOKIE_NAME = "dataTactics"
72
76
  DEFAULT_BASEDIR = "app"
73
77
  DEFAULT_STATIC_FOLDER = DEFAULT_BASEDIR + "/static"
74
78
  DEFAULT_TEMPLATE_FOLDER = DEFAULT_BASEDIR + "/templates"
75
- DEFAULT_PROFILER_FOLDER = DEFAULT_STATIC_FOLDER + "/profiles"
79
+ DEFAULT_PROFILER_FOLDER = DEFAULT_STATIC_FOLDER + "/profiler"
80
+ DEFAULT_LANG_FOLDER = DEFAULT_BASEDIR + "/lang"
81
+ DEFAULT_LANGUAGES = "en,de"
82
+ DEFAULT_TRANSLATIONS_KEY = "translations"
76
83
  DEFAULT_SECWEB_PARAMS = {
77
84
  "Option": {
78
85
  "csp": {
@@ -93,6 +100,7 @@ DEFAULT_SECWEB_PARAMS = {
93
100
  )
94
101
  },
95
102
  "wshsts": False,
103
+ "cacheControl": False,
96
104
  },
97
105
  "script_nonce": True,
98
106
  "style_nonce": True,
@@ -104,6 +112,9 @@ DEFAULT_SECWEB_PARAMS["Option"]["csp"]["style-src"] = [
104
112
  ]
105
113
  del DEFAULT_SECWEB_PARAMS["Option"]["csp"]["require-trusted-types-for"]
106
114
 
115
+ INTERNAL_LANG_FOLDER = lang.__file__.replace("__init__.py", "")
116
+ INTERNAL_BABEL_FOLDER = os.path.join(INTERNAL_LANG_FOLDER, "babel")
117
+
107
118
  COOKIE_PREFIX = "COOKIE_"
108
119
  COOKIE_CONFIG = {
109
120
  f"{COOKIE_PREFIX}NAME": f"{COOKIE_PREFIX.lower()}name",
@@ -136,6 +147,23 @@ AUTH_ROLE_CONFIG = {
136
147
  f"{AUTH_ROLE_PREFIX}PUBLIC_ROLE": "public_role",
137
148
  }
138
149
 
150
+
151
+ class ErrorCode(str, enum.Enum):
152
+ GET_USER_MISSING_TOKEN_OR_INACTIVE_USER = "GET_USER_MISSING_TOKEN_OR_INACTIVE_USER"
153
+ GET_USER_NO_ROLES = "GET_USER_NO_ROLES"
154
+ PERMISSION_DENIED = "PERMISSION_DENIED"
155
+ FEATURE_NOT_IMPLEMENTED = "FEATURE_NOT_IMPLEMENTED"
156
+ PAGE_NOT_FOUND = "PAGE_NOT_FOUND"
157
+ ITEM_NOT_FOUND = "ITEM_NOT_FOUND"
158
+ HANDLER_NOT_FOUND = "HANDLER_NOT_FOUND"
159
+ FILE_NOT_FOUND = "FILE_NOT_FOUND"
160
+ IMAGE_NOT_FOUND = "IMAGE_NOT_FOUND"
161
+
162
+
163
+ class AuthType(str, enum.Enum):
164
+ LDAP = "ldap"
165
+
166
+
139
167
  logging.basicConfig(format="%(asctime)s:%(levelname)s:%(name)s:%(message)s")
140
168
  logger = logging.getLogger("DT_FASTAPI")
141
169
  logger.setLevel(logging.INFO)