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.
Files changed (135) hide show
  1. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/.gitignore +2 -1
  2. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/PKG-INFO +1 -1
  3. fastapi_rtk-1.0.5/fastapi_rtk/_version.py +1 -0
  4. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/api/model_rest_api.py +2 -5
  5. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/async_task_runner.py +4 -1
  6. fastapi_rtk-1.0.5/fastapi_rtk/utils/csv_json_converter.py +429 -0
  7. fastapi_rtk-1.0.5/fastapi_rtk/version.py +6 -0
  8. fastapi_rtk-1.0.3/fastapi_rtk/utils/csv_json_converter.py +0 -226
  9. fastapi_rtk-1.0.3/fastapi_rtk/version.py +0 -1
  10. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/LICENSE +0 -0
  11. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/README.md +0 -0
  12. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/__init__.py +0 -0
  13. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/api/__init__.py +0 -0
  14. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/api/base_api.py +0 -0
  15. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/apis.py +0 -0
  16. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/__init__.py +0 -0
  17. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/auth.py +0 -0
  18. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/hashers/__init__.py +0 -0
  19. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
  20. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
  21. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/hashers/utils.py +0 -0
  22. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
  23. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
  24. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/strategies/__init__.py +0 -0
  25. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/strategies/config.py +0 -0
  26. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/strategies/db.py +0 -0
  27. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/auth/strategies/jwt.py +0 -0
  28. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/__init__.py +0 -0
  29. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/__init__.py +0 -0
  30. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/column.py +0 -0
  31. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/db.py +0 -0
  32. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/exceptions.py +0 -0
  33. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/filters.py +0 -0
  34. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/interface.py +0 -0
  35. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/model.py +0 -0
  36. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/generic/session.py +0 -0
  37. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/__init__.py +0 -0
  38. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/column.py +0 -0
  39. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/db.py +0 -0
  40. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
  41. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
  42. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
  43. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
  44. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
  45. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
  46. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
  47. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
  48. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/filters.py +0 -0
  49. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/interface.py +0 -0
  50. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/model.py +0 -0
  51. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/backends/sqla/session.py +0 -0
  52. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/__init__.py +0 -0
  53. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/db.py +0 -0
  54. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/file_manager.py +0 -0
  55. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/filter.py +0 -0
  56. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/interface.py +0 -0
  57. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/model.py +0 -0
  58. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/bases/session.py +0 -0
  59. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/__init__.py +0 -0
  60. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/cli.py +0 -0
  61. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/__init__.py +0 -0
  62. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
  63. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
  64. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
  65. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
  66. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
  67. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
  68. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
  69. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
  70. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
  71. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/export.py +0 -0
  72. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/security.py +0 -0
  73. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/commands/translate.py +0 -0
  74. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/const.py +0 -0
  75. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/decorators.py +0 -0
  76. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/types.py +0 -0
  77. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/cli/utils.py +0 -0
  78. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/config.py +0 -0
  79. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/const.py +0 -0
  80. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/db.py +0 -0
  81. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/decorators.py +0 -0
  82. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/dependencies.py +0 -0
  83. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/exceptions.py +0 -0
  84. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
  85. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/file_managers/__init__.py +0 -0
  86. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/file_managers/file_manager.py +0 -0
  87. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/file_managers/image_manager.py +0 -0
  88. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
  89. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
  90. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/filters.py +0 -0
  91. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/globals.py +0 -0
  92. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/__init__.py +0 -0
  93. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/babel/__init__.py +0 -0
  94. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/babel/cli.py +0 -0
  95. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/babel/config.py +0 -0
  96. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/babel.cfg +0 -0
  97. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/lazy_text.py +0 -0
  98. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/messages.pot +0 -0
  99. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  100. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
  101. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  102. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
  103. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/manager.py +0 -0
  104. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/middlewares.py +0 -0
  105. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/mixins.py +0 -0
  106. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/models.py +0 -0
  107. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/routers.py +0 -0
  108. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/schemas.py +0 -0
  109. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/security/__init__.py +0 -0
  110. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/security/sqla/__init__.py +0 -0
  111. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/security/sqla/apis.py +0 -0
  112. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/security/sqla/models.py +0 -0
  113. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/security/sqla/security_manager.py +0 -0
  114. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/setting.py +0 -0
  115. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/types.py +0 -0
  116. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/__init__.py +0 -0
  117. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/class_factory.py +0 -0
  118. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/deep_merge.py +0 -0
  119. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/extender_mixin.py +0 -0
  120. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
  121. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/hooks.py +0 -0
  122. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/lazy.py +0 -0
  123. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/merge_schema.py +0 -0
  124. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
  125. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/prettify_dict.py +0 -0
  126. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/pydantic.py +0 -0
  127. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/run_utils.py +0 -0
  128. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/self_dependencies.py +0 -0
  129. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
  130. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/sqla.py +0 -0
  131. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/timezone.py +0 -0
  132. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/update_signature.py +0 -0
  133. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/use_default_when_none.py +0 -0
  134. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/fastapi_rtk/utils/werkzeug.py +0 -0
  135. {fastapi_rtk-1.0.3 → fastapi_rtk-1.0.5}/pyproject.toml +0 -0
@@ -290,4 +290,5 @@ dist
290
290
  .yarn/install-state.gz
291
291
  .pnp.*
292
292
 
293
- .vscode
293
+ .vscode
294
+ py/fastapi_rtk/_version.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-rtk
3
- Version: 1.0.3
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
- async with AsyncTaskRunner():
2739
- item_model = schema.model_validate(item, from_attributes=True)
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': exc.original_exception.__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
@@ -0,0 +1,6 @@
1
+ __version__ = "0.0.0"
2
+ # Try to import the version from the generated _version.py file
3
+ try:
4
+ from ._version import __version__ # noqa: F401
5
+ except (ImportError, ModuleNotFoundError):
6
+ pass
@@ -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