fastapi-rtk 1.0.3__tar.gz → 1.0.5__tar.gz
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-1.0.3 → fastapi_rtk-1.0.5}/.gitignore +2 -1
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/PKG-INFO +1 -1
- fastapi_rtk-1.0.5/fastapi_rtk/_version.py +1 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/api/model_rest_api.py +2 -5
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/async_task_runner.py +4 -1
- fastapi_rtk-1.0.5/fastapi_rtk/utils/csv_json_converter.py +429 -0
- fastapi_rtk-1.0.5/fastapi_rtk/version.py +6 -0
- fastapi_rtk-1.0.3/fastapi_rtk/utils/csv_json_converter.py +0 -226
- fastapi_rtk-1.0.3/fastapi_rtk/version.py +0 -1
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/LICENSE +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/README.md +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/api/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/api/base_api.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/apis.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/auth.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/hashers/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/hashers/utils.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/strategies/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/strategies/config.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/strategies/db.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/strategies/jwt.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/column.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/db.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/exceptions.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/filters.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/interface.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/model.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/session.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/column.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/db.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/filters.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/interface.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/model.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/session.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/db.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/file_manager.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/filter.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/interface.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/model.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/session.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/cli.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/export.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/security.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/translate.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/const.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/decorators.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/types.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/utils.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/config.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/const.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/db.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/decorators.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/dependencies.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/exceptions.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/file_managers/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/file_managers/file_manager.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/file_managers/image_manager.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/filters.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/globals.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/babel/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/babel/cli.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/babel/config.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/babel.cfg +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/lazy_text.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/messages.pot +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/manager.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/middlewares.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/mixins.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/models.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/routers.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/schemas.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/security/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/security/sqla/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/security/sqla/apis.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/security/sqla/models.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/security/sqla/security_manager.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/setting.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/types.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/__init__.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/class_factory.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/deep_merge.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/extender_mixin.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/hooks.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/lazy.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/merge_schema.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/prettify_dict.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/pydantic.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/run_utils.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/self_dependencies.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/sqla.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/timezone.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/update_signature.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/use_default_when_none.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/werkzeug.py +0 -0
- {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-rtk
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
4
4
|
Summary: A package that provides a set of tools to build a FastAPI application with a Class-Based CRUD API.
|
|
5
5
|
Project-URL: Homepage, https://codeberg.org/datatactics/fastapi-rtk
|
|
6
6
|
Project-URL: Issues, https://codeberg.org/datatactics/fastapi-rtk/issues
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.5"
|
|
@@ -2735,11 +2735,8 @@ class ModelRestApi(BaseApi):
|
|
|
2735
2735
|
|
|
2736
2736
|
async for chunk in data:
|
|
2737
2737
|
for item in chunk:
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
item_dict = item_model.model_dump(mode="json")
|
|
2741
|
-
row = CSVJSONConverter._json_to_csv(
|
|
2742
|
-
item_dict,
|
|
2738
|
+
row = await CSVJSONConverter.ajson_to_csv_single(
|
|
2739
|
+
item,
|
|
2743
2740
|
list_columns=list_columns,
|
|
2744
2741
|
delimiter=delimiter,
|
|
2745
2742
|
export_mode=export_mode,
|
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import contextvars
|
|
3
3
|
import functools
|
|
4
4
|
import inspect
|
|
5
|
+
import traceback as tb
|
|
5
6
|
import typing
|
|
6
7
|
|
|
7
8
|
from .prettify_dict import prettify_dict
|
|
@@ -233,7 +234,9 @@ class AsyncTaskRunner:
|
|
|
233
234
|
f'Task {index + 1}': {
|
|
234
235
|
'message': str(exc),
|
|
235
236
|
'caller': exc.caller,
|
|
236
|
-
'traceback':
|
|
237
|
+
'traceback': ''.join(
|
|
238
|
+
tb.format_exception(exc.original_exception)
|
|
239
|
+
),
|
|
237
240
|
}
|
|
238
241
|
for index, exc in enumerate(exceptions)
|
|
239
242
|
},
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import csv
|
|
3
|
+
import enum
|
|
4
|
+
import inspect
|
|
5
|
+
import json
|
|
6
|
+
import typing
|
|
7
|
+
|
|
8
|
+
from .run_utils import safe_call
|
|
9
|
+
|
|
10
|
+
if typing.TYPE_CHECKING:
|
|
11
|
+
from ..bases import BasicModel
|
|
12
|
+
|
|
13
|
+
__all__ = ["Line", "CSVJSONConverter"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Line:
|
|
17
|
+
_line = ""
|
|
18
|
+
|
|
19
|
+
def write(self, line: str):
|
|
20
|
+
self._line = line
|
|
21
|
+
|
|
22
|
+
def read(self):
|
|
23
|
+
return self._line
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CSVJSONConverter:
|
|
27
|
+
"""
|
|
28
|
+
A utility class for converting CSV data to JSON format and vice versa.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
ExportMode = typing.Literal["simplified", "detailed"]
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def csv_to_json(
|
|
35
|
+
cls,
|
|
36
|
+
csv_data: str | bytes,
|
|
37
|
+
*,
|
|
38
|
+
delimiter=",",
|
|
39
|
+
quotechar: str | None = None,
|
|
40
|
+
):
|
|
41
|
+
"""
|
|
42
|
+
Converts CSV data to JSON format.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
csv_data (str, bytes): The CSV data.
|
|
46
|
+
delimiter (str, optional): The delimiter to use in the CSV. Defaults to ",".
|
|
47
|
+
quotechar (str | None, optional): Quote character for the CSV file. If not given, it will not be used. Defaults to None.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
list[dict[str, Any]]: The JSON data as a list of dictionaries.
|
|
51
|
+
"""
|
|
52
|
+
if isinstance(csv_data, bytes):
|
|
53
|
+
csv_data = csv_data.decode("utf-8")
|
|
54
|
+
|
|
55
|
+
lines = csv_data.splitlines()
|
|
56
|
+
reader = csv.DictReader(lines, delimiter=delimiter, quotechar=quotechar)
|
|
57
|
+
return [
|
|
58
|
+
cls._convert_nested_col_into_dict(
|
|
59
|
+
row, list_delimiter=";" if delimiter != ";" else ","
|
|
60
|
+
)
|
|
61
|
+
for row in reader
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def json_to_csv(
|
|
66
|
+
cls,
|
|
67
|
+
data: "dict[str, typing.Any] | list[dict[str, typing.Any]] | BasicModel | list[BasicModel]",
|
|
68
|
+
/,
|
|
69
|
+
*,
|
|
70
|
+
list_columns: list[str],
|
|
71
|
+
label_columns: dict[str, str],
|
|
72
|
+
with_header=True,
|
|
73
|
+
delimiter=",",
|
|
74
|
+
quotechar: str | None = None,
|
|
75
|
+
relation_separator: str = ".",
|
|
76
|
+
export_mode: ExportMode = "simplified",
|
|
77
|
+
):
|
|
78
|
+
"""
|
|
79
|
+
Converts JSON data to CSV format.
|
|
80
|
+
|
|
81
|
+
- Data can also be a subclass of `BasicModel` or a list of subclasses of `BasicModel`.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
data (dict[str, typing.Any] | list[dict[str, typing.Any]] | BasicModel | list[BasicModel]): The JSON data to be converted. Can also be a subclass of `BasicModel` or a list of subclasses of `BasicModel`.
|
|
85
|
+
list_columns (list[str]): The list of columns to be included in the CSV.
|
|
86
|
+
label_columns (dict[str, str]): The mapping of column names to labels.
|
|
87
|
+
with_header (bool, optional): Whether to include the header in the CSV. Defaults to True.
|
|
88
|
+
delimiter (str, optional): The delimiter to use in the CSV. Defaults to ",".
|
|
89
|
+
quotechar (str | None, optional): Quote character for the CSV file. If not given, it will not be used. Defaults to None.
|
|
90
|
+
relation_separator (str, optional): The separator to use for nested keys. Defaults to ".".
|
|
91
|
+
export_mode (ExportMode, optional): Export mode (simplified or detailed). Defaults to "simplified".
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
str: The CSV data as a string.
|
|
95
|
+
"""
|
|
96
|
+
csv_data = ""
|
|
97
|
+
line = Line()
|
|
98
|
+
writer = csv.writer(line, delimiter=delimiter, quotechar=quotechar)
|
|
99
|
+
|
|
100
|
+
if with_header:
|
|
101
|
+
header = [label_columns[col] for col in list_columns]
|
|
102
|
+
writer.writerow(header)
|
|
103
|
+
csv_data = line.read()
|
|
104
|
+
|
|
105
|
+
if not isinstance(data, list):
|
|
106
|
+
data = [data]
|
|
107
|
+
|
|
108
|
+
for item in data:
|
|
109
|
+
row = cls.json_to_csv_single(
|
|
110
|
+
item,
|
|
111
|
+
list_columns=list_columns,
|
|
112
|
+
delimiter=delimiter,
|
|
113
|
+
relation_separator=relation_separator,
|
|
114
|
+
export_mode=export_mode,
|
|
115
|
+
)
|
|
116
|
+
writer.writerow(row)
|
|
117
|
+
csv_data += line.read()
|
|
118
|
+
|
|
119
|
+
return csv_data.strip()
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def json_to_csv_single(
|
|
123
|
+
self,
|
|
124
|
+
data: "dict[str, typing.Any] | BasicModel",
|
|
125
|
+
/,
|
|
126
|
+
*,
|
|
127
|
+
list_columns: list[str],
|
|
128
|
+
delimiter=",",
|
|
129
|
+
relation_separator=".",
|
|
130
|
+
export_mode: ExportMode = "simplified",
|
|
131
|
+
):
|
|
132
|
+
"""
|
|
133
|
+
Converts single JSON object to CSV format.
|
|
134
|
+
|
|
135
|
+
- Data can also be a subclass of `BasicModel`.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
data (dict[str, typing.Any] | BasicModel): The JSON data to be converted. Can also be a subclass of `BasicModel`.
|
|
139
|
+
list_columns (list[str]): The list of columns to be included in the CSV.
|
|
140
|
+
delimiter (str, optional): The delimiter to use in the CSV. Defaults to ",".
|
|
141
|
+
relation_separator (str, optional): The separator to use for nested keys. Defaults to ".".
|
|
142
|
+
export_mode (ExportMode, optional): Export mode (simplified or detailed). Defaults to "simplified".
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
str: The CSV data as a string.
|
|
146
|
+
"""
|
|
147
|
+
csv_data: list[str] = []
|
|
148
|
+
data_pipeline = DataPipeline()
|
|
149
|
+
data_pipeline.add_processor(ColumnProcessor(relation_separator))
|
|
150
|
+
data_pipeline.add_processor(ModelProcessor())
|
|
151
|
+
data_pipeline.add_processor(
|
|
152
|
+
ListProcessor(delimiter=delimiter, export_mode=export_mode)
|
|
153
|
+
)
|
|
154
|
+
data_pipeline.add_processor(EnumProcessor())
|
|
155
|
+
data_pipeline.add_processor(FallbackProcessor())
|
|
156
|
+
|
|
157
|
+
for col in list_columns:
|
|
158
|
+
value = data_pipeline.process(data, col)
|
|
159
|
+
csv_data.append(value)
|
|
160
|
+
|
|
161
|
+
return csv_data
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
async def ajson_to_csv_single(
|
|
165
|
+
cls,
|
|
166
|
+
data: "dict[str, typing.Any] | BasicModel",
|
|
167
|
+
/,
|
|
168
|
+
*,
|
|
169
|
+
list_columns: list[str],
|
|
170
|
+
delimiter=",",
|
|
171
|
+
relation_separator=".",
|
|
172
|
+
export_mode: ExportMode = "simplified",
|
|
173
|
+
):
|
|
174
|
+
"""
|
|
175
|
+
Asynchronously converts single JSON object to CSV format.
|
|
176
|
+
|
|
177
|
+
- Data can also be a `BasicModel`.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
data (dict[str, typing.Any] | BasicModel): The JSON data to be converted. Can also be a `BasicModel`.
|
|
181
|
+
list_columns (list[str]): The list of columns to be included in the CSV.
|
|
182
|
+
delimiter (str, optional): The delimiter to use in the CSV. Defaults to ",".
|
|
183
|
+
relation_separator (str, optional): The separator to use for nested keys. Defaults to ".".
|
|
184
|
+
export_mode (ExportMode, optional): Export mode (simplified or detailed). Defaults to "simplified".
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
str: The CSV data as a string.
|
|
188
|
+
"""
|
|
189
|
+
csv_data: list[str] = []
|
|
190
|
+
data_pipeline = DataPipeline()
|
|
191
|
+
data_pipeline.add_processor(AsyncColumnProcessor(relation_separator))
|
|
192
|
+
data_pipeline.add_processor(ModelProcessor())
|
|
193
|
+
data_pipeline.add_processor(
|
|
194
|
+
ListProcessor(delimiter=delimiter, export_mode=export_mode)
|
|
195
|
+
)
|
|
196
|
+
data_pipeline.add_processor(EnumProcessor())
|
|
197
|
+
data_pipeline.add_processor(FallbackProcessor())
|
|
198
|
+
|
|
199
|
+
for col in list_columns:
|
|
200
|
+
value = await data_pipeline.aprocess(data, col)
|
|
201
|
+
csv_data.append(value)
|
|
202
|
+
|
|
203
|
+
return csv_data
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
def _convert_nested_col_into_dict(
|
|
207
|
+
cls,
|
|
208
|
+
data: dict[str, typing.Any],
|
|
209
|
+
/,
|
|
210
|
+
*,
|
|
211
|
+
separator: str = ".",
|
|
212
|
+
list_delimiter: str = ";",
|
|
213
|
+
):
|
|
214
|
+
"""
|
|
215
|
+
Converts nested columns in a dictionary into a nested dictionary.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
data (dict[str, Any]): The dictionary to be converted.
|
|
219
|
+
separator (str, optional): Separator used to split the keys into nested dictionaries. Defaults to ".".
|
|
220
|
+
list_delimiter (str, optional): Delimiter used to join list values. Defaults to ";"
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
dict[str, Any]: The converted dictionary with nested keys.
|
|
224
|
+
|
|
225
|
+
Example:
|
|
226
|
+
```python
|
|
227
|
+
data = {
|
|
228
|
+
"name": "Alice",
|
|
229
|
+
"age": 30,
|
|
230
|
+
"address.city": "New York",
|
|
231
|
+
"address.state": "NY",
|
|
232
|
+
}
|
|
233
|
+
result = CSVJSONConverter._convert_nested_col_into_dict(data)
|
|
234
|
+
# result = {
|
|
235
|
+
# "name": "Alice",
|
|
236
|
+
# "age": 30,
|
|
237
|
+
# "address": {
|
|
238
|
+
# "city": "New York",
|
|
239
|
+
# "state": "NY"
|
|
240
|
+
# }
|
|
241
|
+
# }
|
|
242
|
+
```
|
|
243
|
+
"""
|
|
244
|
+
result: dict[str, typing.Any] = {}
|
|
245
|
+
for key, value in data.items():
|
|
246
|
+
parts = key.strip().split(separator)
|
|
247
|
+
current = result
|
|
248
|
+
for part in parts[:-1]:
|
|
249
|
+
if part not in current:
|
|
250
|
+
current[part] = {}
|
|
251
|
+
current = current[part]
|
|
252
|
+
current[parts[-1]] = value
|
|
253
|
+
|
|
254
|
+
if list_delimiter in value:
|
|
255
|
+
value = value.split(list_delimiter)
|
|
256
|
+
current[parts[-1]] = [item.strip() for item in value if item.strip()]
|
|
257
|
+
return result
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class DataPipeline:
|
|
261
|
+
def __init__(self):
|
|
262
|
+
self.processors = list[DataProcessor]()
|
|
263
|
+
|
|
264
|
+
def add_processor(self, processor: "DataProcessor"):
|
|
265
|
+
"""
|
|
266
|
+
Adds a data processor to the pipeline.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
processor (DataProcessor): The data processor to add.
|
|
270
|
+
"""
|
|
271
|
+
self.processors.append(processor)
|
|
272
|
+
|
|
273
|
+
def process(self, data: typing.Any, col: str):
|
|
274
|
+
"""
|
|
275
|
+
Processes the data through the pipeline.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
data (typing.Any): The data to process.
|
|
279
|
+
col (str): The column to process.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
typing.Any: The processed data.
|
|
283
|
+
"""
|
|
284
|
+
for processor in self.processors:
|
|
285
|
+
data, col, should_continue = processor.process(data, col)
|
|
286
|
+
if not should_continue:
|
|
287
|
+
break
|
|
288
|
+
return data
|
|
289
|
+
|
|
290
|
+
async def aprocess(self, data: typing.Any, col: str):
|
|
291
|
+
"""
|
|
292
|
+
Asynchronously processes the data through the pipeline.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
data (typing.Any): The data to process.
|
|
296
|
+
col (str): The column to process.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
typing.Any: The processed data.
|
|
300
|
+
"""
|
|
301
|
+
for processor in self.processors:
|
|
302
|
+
data, col, should_continue = await safe_call(processor.process(data, col))
|
|
303
|
+
if not should_continue:
|
|
304
|
+
break
|
|
305
|
+
return data
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class DataProcessor(abc.ABC):
|
|
309
|
+
@abc.abstractmethod
|
|
310
|
+
def process(self, data: typing.Any, col: str) -> tuple[typing.Any, str, bool]:
|
|
311
|
+
"""
|
|
312
|
+
Processes the data for a specific column.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
data (typing.Any): The data to process.
|
|
316
|
+
col (str): The column to process.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
tuple[typing.Any, str, bool]: The processed data, the column name, and a boolean indicating whether to continue processing.
|
|
320
|
+
"""
|
|
321
|
+
raise NotImplementedError()
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class ColumnProcessor(DataProcessor):
|
|
325
|
+
def __init__(self, relation_separator: str = "."):
|
|
326
|
+
super().__init__()
|
|
327
|
+
self.relation_separator = relation_separator
|
|
328
|
+
|
|
329
|
+
def process(self, data, col):
|
|
330
|
+
sub_col = []
|
|
331
|
+
if self.relation_separator in col:
|
|
332
|
+
col, *sub_col = col.split(self.relation_separator)
|
|
333
|
+
data = data.get(col, "") if isinstance(data, dict) else getattr(data, col, "")
|
|
334
|
+
for sub in sub_col:
|
|
335
|
+
data = (
|
|
336
|
+
data.get(sub, "") if isinstance(data, dict) else getattr(data, sub, "")
|
|
337
|
+
)
|
|
338
|
+
return data, col, True
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class AsyncColumnProcessor(ColumnProcessor):
|
|
342
|
+
async def process(self, data, col):
|
|
343
|
+
data, col, continue_processing = super().process(data, col)
|
|
344
|
+
if inspect.iscoroutine(data):
|
|
345
|
+
data = await data
|
|
346
|
+
return data, col, continue_processing
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class ModelProcessor(DataProcessor):
|
|
350
|
+
def __init__(self, attr="name_"):
|
|
351
|
+
super().__init__()
|
|
352
|
+
self.attr = attr
|
|
353
|
+
|
|
354
|
+
def process(self, data, col):
|
|
355
|
+
from ..bases import BasicModel
|
|
356
|
+
|
|
357
|
+
continue_processing = True
|
|
358
|
+
|
|
359
|
+
if isinstance(data, BasicModel):
|
|
360
|
+
data = getattr(data, self.attr, "")
|
|
361
|
+
continue_processing = False
|
|
362
|
+
return data, col, continue_processing
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class DictProcessor(ModelProcessor):
|
|
366
|
+
def process(self, data, col):
|
|
367
|
+
continue_processing = True
|
|
368
|
+
|
|
369
|
+
if isinstance(data, dict):
|
|
370
|
+
data = data.get(self.attr, json.dumps(data))
|
|
371
|
+
continue_processing = False
|
|
372
|
+
return data, col, continue_processing
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class ListProcessor(DataProcessor):
|
|
376
|
+
def __init__(
|
|
377
|
+
self,
|
|
378
|
+
delimiter=",",
|
|
379
|
+
export_mode: CSVJSONConverter.ExportMode = "simplified",
|
|
380
|
+
attr_detailed="id_",
|
|
381
|
+
attr_simplified="name_",
|
|
382
|
+
):
|
|
383
|
+
super().__init__()
|
|
384
|
+
self.separator = "," if delimiter == ";" else ";"
|
|
385
|
+
self.export_mode = export_mode
|
|
386
|
+
self.model_processor = ModelProcessor(
|
|
387
|
+
attr_detailed if export_mode == "detailed" else attr_simplified
|
|
388
|
+
)
|
|
389
|
+
self.dict_processor = DictProcessor(
|
|
390
|
+
attr_detailed if export_mode == "detailed" else attr_simplified
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
def process(self, data, col):
|
|
394
|
+
from ..bases import BasicModel
|
|
395
|
+
|
|
396
|
+
continue_processing = True
|
|
397
|
+
|
|
398
|
+
if isinstance(data, list):
|
|
399
|
+
processed_list = []
|
|
400
|
+
for item in data:
|
|
401
|
+
if isinstance(item, dict):
|
|
402
|
+
item_processed, _, _ = self.dict_processor.process(item, col)
|
|
403
|
+
elif isinstance(item, BasicModel):
|
|
404
|
+
item_processed, _, _ = self.model_processor.process(item, col)
|
|
405
|
+
else:
|
|
406
|
+
item_processed = str(item)
|
|
407
|
+
processed_list.append(item_processed)
|
|
408
|
+
data = self.separator.join(processed_list)
|
|
409
|
+
continue_processing = False
|
|
410
|
+
return data, col, continue_processing
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class EnumProcessor(DataProcessor):
|
|
414
|
+
def process(self, data, col):
|
|
415
|
+
continue_processing = True
|
|
416
|
+
|
|
417
|
+
if isinstance(data, enum.Enum):
|
|
418
|
+
data = data.value
|
|
419
|
+
continue_processing = False
|
|
420
|
+
return data, col, continue_processing
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
class FallbackProcessor(DataProcessor):
|
|
424
|
+
def process(self, data, col):
|
|
425
|
+
if data is None:
|
|
426
|
+
data = ""
|
|
427
|
+
else:
|
|
428
|
+
data = str(data)
|
|
429
|
+
return data, col, False
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import csv
|
|
2
|
-
import enum
|
|
3
|
-
import json
|
|
4
|
-
import typing
|
|
5
|
-
|
|
6
|
-
__all__ = ["Line", "CSVJSONConverter"]
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Line:
|
|
10
|
-
_line = ""
|
|
11
|
-
|
|
12
|
-
def write(self, line: str):
|
|
13
|
-
self._line = line
|
|
14
|
-
|
|
15
|
-
def read(self):
|
|
16
|
-
return self._line
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class CSVJSONConverter:
|
|
20
|
-
"""
|
|
21
|
-
A utility class for converting CSV data to JSON format and vice versa.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
ExportMode = typing.Literal["simplified", "detailed"]
|
|
25
|
-
|
|
26
|
-
@classmethod
|
|
27
|
-
def csv_to_json(
|
|
28
|
-
cls,
|
|
29
|
-
csv_data: str | bytes,
|
|
30
|
-
*,
|
|
31
|
-
delimiter=",",
|
|
32
|
-
quotechar: str | None = None,
|
|
33
|
-
):
|
|
34
|
-
"""
|
|
35
|
-
Converts CSV data to JSON format.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
csv_data (str, bytes): The CSV data.
|
|
39
|
-
delimiter (str, optional): The delimiter to use in the CSV. Defaults to ",".
|
|
40
|
-
quotechar (str | None, optional): Quote character for the CSV file. If not given, it will not be used. Defaults to None.
|
|
41
|
-
|
|
42
|
-
Returns:
|
|
43
|
-
list[dict[str, Any]]: The JSON data as a list of dictionaries.
|
|
44
|
-
"""
|
|
45
|
-
if isinstance(csv_data, bytes):
|
|
46
|
-
csv_data = csv_data.decode("utf-8")
|
|
47
|
-
|
|
48
|
-
lines = csv_data.splitlines()
|
|
49
|
-
reader = csv.DictReader(lines, delimiter=delimiter, quotechar=quotechar)
|
|
50
|
-
return [
|
|
51
|
-
cls._convert_nested_col_into_dict(
|
|
52
|
-
row, list_delimiter=";" if delimiter != ";" else ","
|
|
53
|
-
)
|
|
54
|
-
for row in reader
|
|
55
|
-
]
|
|
56
|
-
|
|
57
|
-
@classmethod
|
|
58
|
-
def json_to_csv(
|
|
59
|
-
cls,
|
|
60
|
-
data: dict[str, typing.Any] | list[dict[str, typing.Any]],
|
|
61
|
-
/,
|
|
62
|
-
*,
|
|
63
|
-
list_columns: list[str],
|
|
64
|
-
label_columns: dict[str, str],
|
|
65
|
-
with_header=True,
|
|
66
|
-
delimiter=",",
|
|
67
|
-
quotechar: str | None = None,
|
|
68
|
-
relation_separator: str = ".",
|
|
69
|
-
export_mode: ExportMode = "simplified",
|
|
70
|
-
):
|
|
71
|
-
"""
|
|
72
|
-
Converts JSON data to CSV format.
|
|
73
|
-
|
|
74
|
-
Args:
|
|
75
|
-
data (dict[str, Any] | list[dict[str, Any]]): The JSON data to be converted.
|
|
76
|
-
list_columns (list[str]): The list of columns to be included in the CSV.
|
|
77
|
-
label_columns (dict[str, str]): The mapping of column names to labels.
|
|
78
|
-
with_header (bool, optional): Whether to include the header in the CSV. Defaults to True.
|
|
79
|
-
delimiter (str, optional): The delimiter to use in the CSV. Defaults to ",".
|
|
80
|
-
quotechar (str | None, optional): Quote character for the CSV file. If not given, it will not be used. Defaults to None.
|
|
81
|
-
relation_separator (str, optional): The separator to use for nested keys. Defaults to ".".
|
|
82
|
-
export_mode (ExportMode, optional): Export mode (simplified or detailed). Defaults to "simplified".
|
|
83
|
-
|
|
84
|
-
Returns:
|
|
85
|
-
str: The CSV data as a string.
|
|
86
|
-
"""
|
|
87
|
-
csv_data = ""
|
|
88
|
-
line = Line()
|
|
89
|
-
writer = csv.writer(line, delimiter=delimiter, quotechar=quotechar)
|
|
90
|
-
|
|
91
|
-
if with_header:
|
|
92
|
-
header = [label_columns[col] for col in list_columns]
|
|
93
|
-
writer.writerow(header)
|
|
94
|
-
csv_data = line.read()
|
|
95
|
-
|
|
96
|
-
if isinstance(data, dict):
|
|
97
|
-
data = [data]
|
|
98
|
-
|
|
99
|
-
for item in data:
|
|
100
|
-
row = cls._json_to_csv(
|
|
101
|
-
item,
|
|
102
|
-
list_columns=list_columns,
|
|
103
|
-
delimiter=delimiter,
|
|
104
|
-
relation_separator=relation_separator,
|
|
105
|
-
export_mode=export_mode,
|
|
106
|
-
)
|
|
107
|
-
writer.writerow(row)
|
|
108
|
-
csv_data += line.read()
|
|
109
|
-
|
|
110
|
-
return csv_data.strip()
|
|
111
|
-
|
|
112
|
-
@classmethod
|
|
113
|
-
def _json_to_csv(
|
|
114
|
-
self,
|
|
115
|
-
data: dict[str, typing.Any],
|
|
116
|
-
/,
|
|
117
|
-
*,
|
|
118
|
-
list_columns: list[str],
|
|
119
|
-
delimiter=",",
|
|
120
|
-
relation_separator=".",
|
|
121
|
-
export_mode: ExportMode = "simplified",
|
|
122
|
-
):
|
|
123
|
-
"""
|
|
124
|
-
Converts single JSON object to CSV format.
|
|
125
|
-
|
|
126
|
-
Args:
|
|
127
|
-
data (dict[str, Any]): The JSON data to be converted.
|
|
128
|
-
list_columns (list[str]): The list of columns to be included in the CSV.
|
|
129
|
-
delimiter (str, optional): The delimiter to use in the CSV. Defaults to ",".
|
|
130
|
-
relation_separator (str, optional): The separator to use for nested keys. Defaults to ".".
|
|
131
|
-
export_mode (ExportMode, optional): Export mode (simplified or detailed). Defaults to "simplified".
|
|
132
|
-
|
|
133
|
-
Returns:
|
|
134
|
-
str: The CSV data as a string.
|
|
135
|
-
"""
|
|
136
|
-
csv_data: list[str] = []
|
|
137
|
-
|
|
138
|
-
for col in list_columns:
|
|
139
|
-
sub_col = []
|
|
140
|
-
if relation_separator in col:
|
|
141
|
-
col, *sub_col = col.split(relation_separator)
|
|
142
|
-
curr_val = data.get(col, "")
|
|
143
|
-
for sub in sub_col:
|
|
144
|
-
if isinstance(curr_val, dict):
|
|
145
|
-
curr_val = curr_val.get(sub, "")
|
|
146
|
-
else:
|
|
147
|
-
curr_val = ""
|
|
148
|
-
|
|
149
|
-
if isinstance(curr_val, dict):
|
|
150
|
-
curr_val = curr_val.get("name_", curr_val)
|
|
151
|
-
elif isinstance(curr_val, list):
|
|
152
|
-
curr_val = [
|
|
153
|
-
curr_val.get(
|
|
154
|
-
"id_" if export_mode == "detailed" else "name_",
|
|
155
|
-
json.dumps(curr_val),
|
|
156
|
-
)
|
|
157
|
-
for curr_val in curr_val
|
|
158
|
-
]
|
|
159
|
-
array_separator = "," if delimiter == ";" else ";"
|
|
160
|
-
curr_val = array_separator.join(curr_val)
|
|
161
|
-
elif isinstance(curr_val, enum.Enum):
|
|
162
|
-
curr_val = curr_val.value
|
|
163
|
-
if curr_val is not None:
|
|
164
|
-
if isinstance(curr_val, dict):
|
|
165
|
-
curr_val = json.dumps(curr_val)
|
|
166
|
-
else:
|
|
167
|
-
curr_val = str(curr_val)
|
|
168
|
-
else:
|
|
169
|
-
curr_val = ""
|
|
170
|
-
csv_data.append(curr_val)
|
|
171
|
-
|
|
172
|
-
return csv_data
|
|
173
|
-
|
|
174
|
-
@classmethod
|
|
175
|
-
def _convert_nested_col_into_dict(
|
|
176
|
-
cls,
|
|
177
|
-
data: dict[str, typing.Any],
|
|
178
|
-
/,
|
|
179
|
-
*,
|
|
180
|
-
separator: str = ".",
|
|
181
|
-
list_delimiter: str = ";",
|
|
182
|
-
):
|
|
183
|
-
"""
|
|
184
|
-
Converts nested columns in a dictionary into a nested dictionary.
|
|
185
|
-
|
|
186
|
-
Args:
|
|
187
|
-
data (dict[str, Any]): The dictionary to be converted.
|
|
188
|
-
separator (str, optional): Separator used to split the keys into nested dictionaries. Defaults to ".".
|
|
189
|
-
list_delimiter (str, optional): Delimiter used to join list values. Defaults to ";"
|
|
190
|
-
|
|
191
|
-
Returns:
|
|
192
|
-
dict[str, Any]: The converted dictionary with nested keys.
|
|
193
|
-
|
|
194
|
-
Example:
|
|
195
|
-
```python
|
|
196
|
-
data = {
|
|
197
|
-
"name": "Alice",
|
|
198
|
-
"age": 30,
|
|
199
|
-
"address.city": "New York",
|
|
200
|
-
"address.state": "NY",
|
|
201
|
-
}
|
|
202
|
-
result = CSVJSONConverter._convert_nested_col_into_dict(data)
|
|
203
|
-
# result = {
|
|
204
|
-
# "name": "Alice",
|
|
205
|
-
# "age": 30,
|
|
206
|
-
# "address": {
|
|
207
|
-
# "city": "New York",
|
|
208
|
-
# "state": "NY"
|
|
209
|
-
# }
|
|
210
|
-
# }
|
|
211
|
-
```
|
|
212
|
-
"""
|
|
213
|
-
result: dict[str, typing.Any] = {}
|
|
214
|
-
for key, value in data.items():
|
|
215
|
-
parts = key.strip().split(separator)
|
|
216
|
-
current = result
|
|
217
|
-
for part in parts[:-1]:
|
|
218
|
-
if part not in current:
|
|
219
|
-
current[part] = {}
|
|
220
|
-
current = current[part]
|
|
221
|
-
current[parts[-1]] = value
|
|
222
|
-
|
|
223
|
-
if list_delimiter in value:
|
|
224
|
-
value = value.split(list_delimiter)
|
|
225
|
-
current[parts[-1]] = [item.strip() for item in value if item.strip()]
|
|
226
|
-
return result
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py
RENAMED
|
File without changes
|
{fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi/README
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py
RENAMED
|
File without changes
|
{fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako
RENAMED
|
File without changes
|
{fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|