fastapi-rtk 1.0.2__tar.gz → 1.0.4__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.2 → fastapi_rtk-1.0.4}/.gitignore +2 -1
  2. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/PKG-INFO +1 -1
  3. fastapi_rtk-1.0.4/fastapi_rtk/_version.py +1 -0
  4. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/api/model_rest_api.py +2 -5
  5. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/bases/db.py +20 -2
  6. fastapi_rtk-1.0.4/fastapi_rtk/utils/csv_json_converter.py +429 -0
  7. fastapi_rtk-1.0.4/fastapi_rtk/version.py +6 -0
  8. fastapi_rtk-1.0.2/fastapi_rtk/utils/csv_json_converter.py +0 -226
  9. fastapi_rtk-1.0.2/fastapi_rtk/version.py +0 -1
  10. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/LICENSE +0 -0
  11. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/README.md +0 -0
  12. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/__init__.py +0 -0
  13. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/api/__init__.py +0 -0
  14. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/api/base_api.py +0 -0
  15. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/apis.py +0 -0
  16. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/auth/__init__.py +0 -0
  17. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/auth/auth.py +0 -0
  18. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/auth/hashers/__init__.py +0 -0
  19. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
  20. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
  21. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/auth/hashers/utils.py +0 -0
  22. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
  23. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
  24. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/auth/strategies/__init__.py +0 -0
  25. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/auth/strategies/config.py +0 -0
  26. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/auth/strategies/db.py +0 -0
  27. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/auth/strategies/jwt.py +0 -0
  28. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/__init__.py +0 -0
  29. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/generic/__init__.py +0 -0
  30. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/generic/column.py +0 -0
  31. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/generic/db.py +0 -0
  32. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/generic/exceptions.py +0 -0
  33. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/generic/filters.py +0 -0
  34. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/generic/interface.py +0 -0
  35. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/generic/model.py +0 -0
  36. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/generic/session.py +0 -0
  37. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/__init__.py +0 -0
  38. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/column.py +0 -0
  39. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/db.py +0 -0
  40. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
  41. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
  42. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
  43. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
  44. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
  45. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
  46. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
  47. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
  48. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/filters.py +0 -0
  49. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/interface.py +0 -0
  50. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/model.py +0 -0
  51. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/backends/sqla/session.py +0 -0
  52. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/bases/__init__.py +0 -0
  53. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/bases/file_manager.py +0 -0
  54. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/bases/filter.py +0 -0
  55. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/bases/interface.py +0 -0
  56. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/bases/model.py +0 -0
  57. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/bases/session.py +0 -0
  58. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/__init__.py +0 -0
  59. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/cli.py +0 -0
  60. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/commands/__init__.py +0 -0
  61. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
  62. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
  63. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
  64. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
  65. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
  66. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
  67. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
  68. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
  69. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
  70. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/commands/export.py +0 -0
  71. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/commands/security.py +0 -0
  72. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/commands/translate.py +0 -0
  73. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/const.py +0 -0
  74. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/decorators.py +0 -0
  75. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/types.py +0 -0
  76. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/cli/utils.py +0 -0
  77. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/config.py +0 -0
  78. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/const.py +0 -0
  79. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/db.py +0 -0
  80. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/decorators.py +0 -0
  81. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/dependencies.py +0 -0
  82. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/exceptions.py +0 -0
  83. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
  84. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/file_managers/__init__.py +0 -0
  85. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/file_managers/file_manager.py +0 -0
  86. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/file_managers/image_manager.py +0 -0
  87. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
  88. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
  89. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/filters.py +0 -0
  90. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/globals.py +0 -0
  91. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/lang/__init__.py +0 -0
  92. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/lang/babel/__init__.py +0 -0
  93. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/lang/babel/cli.py +0 -0
  94. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/lang/babel/config.py +0 -0
  95. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/lang/babel.cfg +0 -0
  96. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/lang/lazy_text.py +0 -0
  97. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/lang/messages.pot +0 -0
  98. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
  99. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
  100. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
  101. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
  102. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/manager.py +0 -0
  103. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/middlewares.py +0 -0
  104. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/mixins.py +0 -0
  105. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/models.py +0 -0
  106. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/routers.py +0 -0
  107. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/schemas.py +0 -0
  108. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/security/__init__.py +0 -0
  109. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/security/sqla/__init__.py +0 -0
  110. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/security/sqla/apis.py +0 -0
  111. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/security/sqla/models.py +0 -0
  112. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/security/sqla/security_manager.py +0 -0
  113. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/setting.py +0 -0
  114. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/types.py +0 -0
  115. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/__init__.py +0 -0
  116. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/async_task_runner.py +0 -0
  117. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/class_factory.py +0 -0
  118. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/deep_merge.py +0 -0
  119. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/extender_mixin.py +0 -0
  120. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
  121. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/hooks.py +0 -0
  122. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/lazy.py +0 -0
  123. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/merge_schema.py +0 -0
  124. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
  125. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/prettify_dict.py +0 -0
  126. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/pydantic.py +0 -0
  127. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/run_utils.py +0 -0
  128. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/self_dependencies.py +0 -0
  129. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
  130. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/sqla.py +0 -0
  131. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/timezone.py +0 -0
  132. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/update_signature.py +0 -0
  133. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/use_default_when_none.py +0 -0
  134. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/fastapi_rtk/utils/werkzeug.py +0 -0
  135. {fastapi_rtk-1.0.2 → fastapi_rtk-1.0.4}/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.2
3
+ Version: 1.0.4
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.4"
@@ -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,
@@ -55,9 +55,15 @@ class DBQueryParams(typing.TypedDict):
55
55
  where_in: tuple[str, list[typing.Any]] | None
56
56
  where_id: PRIMARY_KEY | None
57
57
  where_id_in: list[PRIMARY_KEY] | None
58
- filters: list[FilterSchema] | None
58
+ filters: (
59
+ list[FilterSchema | dict[str, typing.Any] | tuple[str, str, typing.Any] | list]
60
+ | None
61
+ )
59
62
  filter_classes: list[tuple[str, AbstractBaseFilter, typing.Any]] | None
60
- opr_filters: list[OprFilterSchema] | None
63
+ opr_filters: (
64
+ list[OprFilterSchema | dict[str, typing.Any] | tuple[str, typing.Any] | list]
65
+ | None
66
+ )
61
67
  opr_filter_classes: list[tuple[AbstractBaseFilter, typing.Any]] | None
62
68
  global_filter: tuple[list[str], str] | None
63
69
 
@@ -182,6 +188,12 @@ class AbstractQueryBuilder(abc.ABC, typing.Generic[T]):
182
188
  )
183
189
  elif step == "filters" and params.get("filters"):
184
190
  for filter in params["filters"]:
191
+ if isinstance(filter, dict):
192
+ filter = FilterSchema(**filter)
193
+ elif isinstance(filter, (list, tuple)):
194
+ filter = FilterSchema(
195
+ col=filter[0], opr=filter[1], value=filter[2]
196
+ )
185
197
  statement = await safe_call(self.apply_filter(statement, filter))
186
198
  elif step == "filter_classes" and params.get("filter_classes"):
187
199
  for filter_class in params["filter_classes"]:
@@ -190,6 +202,12 @@ class AbstractQueryBuilder(abc.ABC, typing.Generic[T]):
190
202
  )
191
203
  elif step == "opr_filters" and params.get("opr_filters"):
192
204
  for opr_filter in params["opr_filters"]:
205
+ if isinstance(opr_filter, dict):
206
+ opr_filter = OprFilterSchema(**opr_filter)
207
+ elif isinstance(opr_filter, (list, tuple)):
208
+ opr_filter = OprFilterSchema(
209
+ opr=opr_filter[0], value=opr_filter[1]
210
+ )
193
211
  statement = await safe_call(
194
212
  self.apply_opr_filter(statement, opr_filter)
195
213
  )
@@ -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__ = "1.0.0"
File without changes
File without changes
File without changes