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
|
@@ -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
|
+
)
|
fastapi_rtk/cli/decorators.py
CHANGED
|
@@ -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
|
-
|
|
32
|
-
|
|
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 + "/
|
|
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)
|