backo 0.3.0__tar.gz → 0.3.3__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 (122) hide show
  1. {backo-0.3.0 → backo-0.3.3}/CHANGELOG.md +20 -2
  2. {backo-0.3.0 → backo-0.3.3}/PKG-INFO +20 -3
  3. {backo-0.3.0 → backo-0.3.3}/README.md +19 -2
  4. {backo-0.3.0 → backo-0.3.3}/backo/collection.py +73 -2
  5. {backo-0.3.0 → backo-0.3.3}/backo/db/backo_redirect.py +1 -1
  6. {backo-0.3.0 → backo-0.3.3}/backo/db/generic/db_handler.py +117 -58
  7. {backo-0.3.0 → backo-0.3.3}/backo/db/generic/filter.py +4 -4
  8. {backo-0.3.0 → backo-0.3.3}/backo/db/generic/transformer.py +32 -9
  9. {backo-0.3.0 → backo-0.3.3}/backo/db/memory.py +2 -2
  10. {backo-0.3.0 → backo-0.3.3}/backo/db/mongo.py +132 -20
  11. {backo-0.3.0 → backo-0.3.3}/backo/db/sqlite3/db_handler.py +82 -39
  12. {backo-0.3.0 → backo-0.3.3}/backo/db/sqlite3/filter.py +7 -0
  13. {backo-0.3.0 → backo-0.3.3}/backo/db/sqlite3/pragma.py +12 -0
  14. {backo-0.3.0 → backo-0.3.3}/backo/db/sqlite3/transformers.py +16 -0
  15. {backo-0.3.0 → backo-0.3.3}/backo/db/valkeydb.py +10 -7
  16. {backo-0.3.0 → backo-0.3.3}/backo/db/yml.py +2 -9
  17. {backo-0.3.0 → backo-0.3.3}/backo/db/yml_dir.py +2 -1
  18. {backo-0.3.0 → backo-0.3.3}/backo/ref.py +1 -0
  19. {backo-0.3.0 → backo-0.3.3}/backo/selection.py +13 -2
  20. {backo-0.3.0 → backo-0.3.3}/examples/nationality/collections_set/db_country_connector.py +1 -1
  21. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/collections_set/vms_connector.py +1 -1
  22. {backo-0.3.0 → backo-0.3.3}/pyproject.toml +1 -1
  23. backo-0.3.3/tests/test_db_connector.py +455 -0
  24. {backo-0.3.0 → backo-0.3.3}/tests/test_mongo.py +0 -19
  25. {backo-0.3.0 → backo-0.3.3}/tests/test_selections.py +60 -4
  26. {backo-0.3.0 → backo-0.3.3}/uv.lock +1 -1
  27. backo-0.3.0/tests/test_db_connector.py +0 -499
  28. {backo-0.3.0 → backo-0.3.3}/.coveragerc +0 -0
  29. {backo-0.3.0 → backo-0.3.3}/.github/workflows/pylint.yml +0 -0
  30. {backo-0.3.0 → backo-0.3.3}/.github/workflows/release.yml +0 -0
  31. {backo-0.3.0 → backo-0.3.3}/.github/workflows/test.yml +0 -0
  32. {backo-0.3.0 → backo-0.3.3}/.github/workflows/test_examples.yml +0 -0
  33. {backo-0.3.0 → backo-0.3.3}/.gitignore +0 -0
  34. {backo-0.3.0 → backo-0.3.3}/.pylintrc +0 -0
  35. {backo-0.3.0 → backo-0.3.3}/.readthedocs.yaml +0 -0
  36. {backo-0.3.0 → backo-0.3.3}/CONTRIBUTING.md +0 -0
  37. {backo-0.3.0 → backo-0.3.3}/LICENSE +0 -0
  38. {backo-0.3.0 → backo-0.3.3}/backo/__init__.py +0 -0
  39. {backo-0.3.0 → backo-0.3.3}/backo/action.py +0 -0
  40. {backo-0.3.0 → backo-0.3.3}/backo/api_toolbox.py +0 -0
  41. {backo-0.3.0 → backo-0.3.3}/backo/backoffice.py +0 -0
  42. {backo-0.3.0 → backo-0.3.3}/backo/collection_addon.py +0 -0
  43. {backo-0.3.0 → backo-0.3.3}/backo/current_user.py +0 -0
  44. {backo-0.3.0 → backo-0.3.3}/backo/db/__init__.py +0 -0
  45. {backo-0.3.0 → backo-0.3.3}/backo/db/generic/interface.py +0 -0
  46. {backo-0.3.0 → backo-0.3.3}/backo/db/restfull.py +0 -0
  47. {backo-0.3.0 → backo-0.3.3}/backo/db/sqlite3/__init__.py +0 -0
  48. {backo-0.3.0 → backo-0.3.3}/backo/error.py +0 -0
  49. {backo-0.3.0 → backo-0.3.3}/backo/file/blob_file.py +0 -0
  50. {backo-0.3.0 → backo-0.3.3}/backo/file/file.py +0 -0
  51. {backo-0.3.0 → backo-0.3.3}/backo/file/file_blob_connector.py +0 -0
  52. {backo-0.3.0 → backo-0.3.3}/backo/file/file_connector.py +0 -0
  53. {backo-0.3.0 → backo-0.3.3}/backo/file/file_system_connector.py +0 -0
  54. {backo-0.3.0 → backo-0.3.3}/backo/initialisation/init_script.py +0 -0
  55. {backo-0.3.0 → backo-0.3.3}/backo/initialisation/templates/__init__.pytemplate +0 -0
  56. {backo-0.3.0 → backo-0.3.3}/backo/initialisation/templates/backoffice.pytemplate +0 -0
  57. {backo-0.3.0 → backo-0.3.3}/backo/initialisation/templates/collection.pytemplate +0 -0
  58. {backo-0.3.0 → backo-0.3.3}/backo/item.py +0 -0
  59. {backo-0.3.0 → backo-0.3.3}/backo/log.py +0 -0
  60. {backo-0.3.0 → backo-0.3.3}/backo/loop_path.py +0 -0
  61. {backo-0.3.0 → backo-0.3.3}/backo/meta_data_handler.py +0 -0
  62. {backo-0.3.0 → backo-0.3.3}/backo/migration_report.py +0 -0
  63. {backo-0.3.0 → backo-0.3.3}/backo/openapi.py +0 -0
  64. {backo-0.3.0 → backo-0.3.3}/backo/patch.py +0 -0
  65. {backo-0.3.0 → backo-0.3.3}/backo/refs_strategies.py +0 -0
  66. {backo-0.3.0 → backo-0.3.3}/backo/refslist.py +0 -0
  67. {backo-0.3.0 → backo-0.3.3}/backo/request_decorators.py +0 -0
  68. {backo-0.3.0 → backo-0.3.3}/backo/status.py +0 -0
  69. {backo-0.3.0 → backo-0.3.3}/backo/transaction.py +0 -0
  70. {backo-0.3.0 → backo-0.3.3}/docs/Makefile +0 -0
  71. {backo-0.3.0 → backo-0.3.3}/docs/make.bat +0 -0
  72. {backo-0.3.0 → backo-0.3.3}/docs/requirements.txt +0 -0
  73. {backo-0.3.0 → backo-0.3.3}/docs/source/DB_management.md +0 -0
  74. {backo-0.3.0 → backo-0.3.3}/docs/source/api_reference.rst +0 -0
  75. {backo-0.3.0 → backo-0.3.3}/docs/source/conf.py +0 -0
  76. {backo-0.3.0 → backo-0.3.3}/docs/source/errors.rst +0 -0
  77. {backo-0.3.0 → backo-0.3.3}/docs/source/index.rst +0 -0
  78. {backo-0.3.0 → backo-0.3.3}/docs/source/readme.rst +0 -0
  79. {backo-0.3.0 → backo-0.3.3}/examples/media_library/README.md +0 -0
  80. {backo-0.3.0 → backo-0.3.3}/examples/media_library/__init__.py +0 -0
  81. {backo-0.3.0 → backo-0.3.3}/examples/media_library/backoffice.py +0 -0
  82. {backo-0.3.0 → backo-0.3.3}/examples/media_library/collections_set/__init__.py +0 -0
  83. {backo-0.3.0 → backo-0.3.3}/examples/media_library/collections_set/books.py +0 -0
  84. {backo-0.3.0 → backo-0.3.3}/examples/media_library/collections_set/users.py +0 -0
  85. {backo-0.3.0 → backo-0.3.3}/examples/media_library/migration.py +0 -0
  86. {backo-0.3.0 → backo-0.3.3}/examples/media_library/tests.py +0 -0
  87. {backo-0.3.0 → backo-0.3.3}/examples/nationality/README.md +0 -0
  88. {backo-0.3.0 → backo-0.3.3}/examples/nationality/__init__.py +0 -0
  89. {backo-0.3.0 → backo-0.3.3}/examples/nationality/backoffice.py +0 -0
  90. {backo-0.3.0 → backo-0.3.3}/examples/nationality/collections_set/__init__.py +0 -0
  91. {backo-0.3.0 → backo-0.3.3}/examples/nationality/collections_set/countries.py +0 -0
  92. {backo-0.3.0 → backo-0.3.3}/examples/nationality/collections_set/people.py +0 -0
  93. {backo-0.3.0 → backo-0.3.3}/examples/nationality/tests.py +0 -0
  94. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/README.md +0 -0
  95. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/__init__.py +0 -0
  96. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/backoffice.py +0 -0
  97. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/collections_set/__init__.py +0 -0
  98. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/collections_set/users.py +0 -0
  99. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/collections_set/vms.py +0 -0
  100. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/config.yaml +0 -0
  101. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/constants.py +0 -0
  102. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/requirements.txt +0 -0
  103. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/rest-api/README.md +0 -0
  104. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/rest-api/collections_set/__init__.py +0 -0
  105. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/rest-api/collections_set/vms.py +0 -0
  106. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/rest-api/config.yaml +0 -0
  107. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/rest-api/constants.py +0 -0
  108. {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/rest-api/rest_api.py +0 -0
  109. {backo-0.3.0 → backo-0.3.3}/requirements.txt +0 -0
  110. {backo-0.3.0 → backo-0.3.3}/tests/__init__.py +0 -0
  111. {backo-0.3.0 → backo-0.3.3}/tests/test_action.py +0 -0
  112. {backo-0.3.0 → backo-0.3.3}/tests/test_api_toolbox.py +0 -0
  113. {backo-0.3.0 → backo-0.3.3}/tests/test_crud.py +0 -0
  114. {backo-0.3.0 → backo-0.3.3}/tests/test_current_user.py +0 -0
  115. {backo-0.3.0 → backo-0.3.3}/tests/test_file.py +0 -0
  116. {backo-0.3.0 → backo-0.3.3}/tests/test_log.py +0 -0
  117. {backo-0.3.0 → backo-0.3.3}/tests/test_meta.py +0 -0
  118. {backo-0.3.0 → backo-0.3.3}/tests/test_migrations.py +0 -0
  119. {backo-0.3.0 → backo-0.3.3}/tests/test_openapi.py +0 -0
  120. {backo-0.3.0 → backo-0.3.3}/tests/test_reference.py +0 -0
  121. {backo-0.3.0 → backo-0.3.3}/tests/test_rest_api_connector.py +0 -0
  122. {backo-0.3.0 → backo-0.3.3}/tests/test_routes.py +0 -0
@@ -5,9 +5,27 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
+ ## [0.3.3] 2026-09-04
9
+
10
+ * fix : Error on release 0.3.1 and 0.3.2
11
+
12
+ ## [0.3.2] 2026-09-04
13
+ * Doc
14
+ * Minor documentation fix
15
+
16
+ ## [0.3.1] 2026-09-04
17
+ * Feat
18
+ * Adding routes for Total computation in selections (/_selections_total/ path)
19
+ * Internal
20
+ * Cross database references
21
+ * Fix
22
+ * datetime was stored in DBMongoConnector as a string. -> fixed but need probably a migration
23
+ * Doc
24
+ * Minor documentation fix
25
+
8
26
  ## [0.3.0] 2026-09-01
9
27
  * Feat
10
- * BREAKING CHANGE : DBHandler refactoring (import and definition of DBConnector)
28
+ * BREAKING CHANGE : DBHandler refactoring (import and definition of DBConnector, use from backo.db import ...)
11
29
  * BREAKING CHANGE : In DBHandler select the field 'total' in response is actually set to 'None'
12
30
  * DBValKeyConnector (for Redis)
13
31
  * DBSqlite3Connector (for SQLite3)
@@ -21,7 +39,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
21
39
  * fix current_user anonymous bug overlap between tests
22
40
  * Doc
23
41
  * Adding DBHandlers documentation
24
- *
42
+
25
43
  ## [0.2.1] 2026-06-18
26
44
  * Feat
27
45
  * openapi information
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: backo
3
- Version: 0.3.0
3
+ Version: 0.3.3
4
4
  Summary: Back office low code
5
5
  Project-URL: GitHub, https://github.com/backo-stricto/backo
6
6
  Project-URL: Homepage, https://github.com/backo-stricto/backo
@@ -69,7 +69,13 @@ It aims at providing a simple and efficient way to create a backend application
69
69
  ## Installation
70
70
 
71
71
  ```bash
72
+
73
+ # basic
72
74
  pip install backo
75
+
76
+ # With mongo
77
+ pip install backo[mongo]
78
+
73
79
  ```
74
80
 
75
81
  ## Quickstart
@@ -1014,6 +1020,13 @@ is used to call an action.
1014
1020
  | <kbd>GET</kbd> | \<my-app-name\>/\<collection name\>/_selections/\<selection_name\> | do the selection |
1015
1021
  | <kbd>POST</kbd> | \<my-app-name\>/\<collection name\>/_selections/\<selection_name\> | do the selection with complex filter |
1016
1022
 
1023
+ And for counting the total of objects
1024
+
1025
+ | Method | Route | Description |
1026
+ | --------------- | ------------------------------------------------------------------ | ------------------------------------ |
1027
+ | <kbd>GET</kbd> | \<my-app-name\>/\<collection name\>/_selections_total/\<selection_name\> | get the total for the selection |
1028
+ | <kbd>POST</kbd> | \<my-app-name\>/\<collection name\>/_selections_total/\<selection_name\> | get the total for the selection with complex filter |
1029
+
1017
1030
  #### example
1018
1031
 
1019
1032
  ```bash
@@ -1022,8 +1035,12 @@ curl -X GET 'http://localhost/media_library/coll/books/_selections/borrowed'
1022
1035
  ["666", "Docker as BDSM toy", "Wallrich"],
1023
1036
  ["1234", "Milf forever", "Epstein"]
1024
1037
  ],
1025
- "total": 2, "_skip": 0, "_page": 10}'
1026
- curl -X GET 'http://localhost/media_library/coll/books/_selections/borrowed?title.$reg=Dock'
1038
+ "total": None, "_skip": 0, "_page": 10}'
1039
+ curl -X GET 'http://localhost/media_library/coll/books/_selections_total/borrowed'
1040
+ '2'
1041
+
1042
+ curl -X GET 'http://localhost/media_library/coll/books/_selections_total/borrowed?title.$reg=Dock'
1043
+ '1'
1027
1044
  ```
1028
1045
 
1029
1046
 
@@ -21,7 +21,13 @@ It aims at providing a simple and efficient way to create a backend application
21
21
  ## Installation
22
22
 
23
23
  ```bash
24
+
25
+ # basic
24
26
  pip install backo
27
+
28
+ # With mongo
29
+ pip install backo[mongo]
30
+
25
31
  ```
26
32
 
27
33
  ## Quickstart
@@ -966,6 +972,13 @@ is used to call an action.
966
972
  | <kbd>GET</kbd> | \<my-app-name\>/\<collection name\>/_selections/\<selection_name\> | do the selection |
967
973
  | <kbd>POST</kbd> | \<my-app-name\>/\<collection name\>/_selections/\<selection_name\> | do the selection with complex filter |
968
974
 
975
+ And for counting the total of objects
976
+
977
+ | Method | Route | Description |
978
+ | --------------- | ------------------------------------------------------------------ | ------------------------------------ |
979
+ | <kbd>GET</kbd> | \<my-app-name\>/\<collection name\>/_selections_total/\<selection_name\> | get the total for the selection |
980
+ | <kbd>POST</kbd> | \<my-app-name\>/\<collection name\>/_selections_total/\<selection_name\> | get the total for the selection with complex filter |
981
+
969
982
  #### example
970
983
 
971
984
  ```bash
@@ -974,8 +987,12 @@ curl -X GET 'http://localhost/media_library/coll/books/_selections/borrowed'
974
987
  ["666", "Docker as BDSM toy", "Wallrich"],
975
988
  ["1234", "Milf forever", "Epstein"]
976
989
  ],
977
- "total": 2, "_skip": 0, "_page": 10}'
978
- curl -X GET 'http://localhost/media_library/coll/books/_selections/borrowed?title.$reg=Dock'
990
+ "total": None, "_skip": 0, "_page": 10}'
991
+ curl -X GET 'http://localhost/media_library/coll/books/_selections_total/borrowed'
992
+ '2'
993
+
994
+ curl -X GET 'http://localhost/media_library/coll/books/_selections_total/borrowed?title.$reg=Dock'
995
+ '1'
979
996
  ```
980
997
 
981
998
 
@@ -170,7 +170,7 @@ class Collection:
170
170
 
171
171
  self._selections = {}
172
172
 
173
- db_handler.set_model(self.get_meta())
173
+ db_handler.set_model(self.model.get_schema())
174
174
 
175
175
  # Adding the "_all" selection
176
176
  can_read = self._permissions.get("read", True)
@@ -347,7 +347,7 @@ class Collection:
347
347
 
348
348
  """
349
349
 
350
- if selection_name in self._actions:
350
+ if selection_name in self._selections:
351
351
  raise SSyntaxError(
352
352
  'Collection {0} register_selection : selection "{1}" already registered',
353
353
  self.name,
@@ -714,6 +714,15 @@ class Collection:
714
714
  collection_blueprint.view_functions[f"{self.name}.do_selection"] = (
715
715
  self.do_selection
716
716
  )
717
+ log.info(
718
+ "Add route GET selections count /_selections_count/<string:_selection_name>"
719
+ )
720
+ collection_blueprint.add_url_rule(
721
+ "/_selections_total/<string:_selection_name>",
722
+ "do_count",
723
+ methods=["GET"],
724
+ )
725
+ collection_blueprint.view_functions[f"{self.name}.do_count"] = self.do_count
717
726
  log.info("Add route POST selections /_selections/<string:_selection_name>")
718
727
  collection_blueprint.add_url_rule(
719
728
  "/_selections/<string:_selection_name>",
@@ -723,6 +732,17 @@ class Collection:
723
732
  collection_blueprint.view_functions[f"{self.name}.do_post_selection"] = (
724
733
  self.do_post_selection
725
734
  )
735
+ log.info(
736
+ "Add route POST selections count /_selections_count/<string:_selection_name>"
737
+ )
738
+ collection_blueprint.add_url_rule(
739
+ "/_selections_total/<string:_selection_name>",
740
+ "do_post_count",
741
+ methods=["POST"],
742
+ )
743
+ collection_blueprint.view_functions[f"{self.name}.do_post_count"] = (
744
+ self.do_post_count
745
+ )
726
746
  self._openapi.add_selections(
727
747
  f"/{self.name}/_selections",
728
748
  self.name,
@@ -892,6 +912,30 @@ class Collection:
892
912
 
893
913
  return (json.dumps(result, cls=StrictoEncoder), 200)
894
914
 
915
+ @error_to_http_handler
916
+ def do_count(self, _selection_name: str):
917
+ """
918
+ Do a count of total objects on a SFilter
919
+ This route works with the selection route
920
+
921
+ :param _selection_name: The name of the selection
922
+ :type _selection_name: str
923
+ :raises Error: _description_
924
+ :return: _description_
925
+ :rtype: _type_
926
+ """
927
+
928
+ if _selection_name not in self._selections:
929
+ raise SSyntaxError(
930
+ "Collection {0} has no selection {0}", self.name, _selection_name
931
+ )
932
+
933
+ match_filter: SFilter = multidict_to_sfilter(request.args)
934
+ count = self._selections[_selection_name].count(match_filter)
935
+
936
+ log.debug(f"count in {self.name}/{_selection_name} {match_filter} -> {count}")
937
+ return (count, 200)
938
+
895
939
  @check_content_type
896
940
  @error_to_http_handler
897
941
  def do_post_selection(self, _selection_name: str):
@@ -925,6 +969,33 @@ class Collection:
925
969
 
926
970
  return (json.dumps(result, cls=StrictoEncoder), 200)
927
971
 
972
+ @check_content_type
973
+ @error_to_http_handler
974
+ def do_post_count(self, _selection_name: str):
975
+ """Do a count on a selection with filter given in post
976
+
977
+ :param _selection_name: The name of the selection
978
+ :type _selection_name: str
979
+ :raises Error: _description_
980
+ :return: _description_
981
+ :rtype: _type_
982
+ """
983
+
984
+ if _selection_name not in self._selections:
985
+ raise SSyntaxError(
986
+ "Collection {0} has no selection {0}", self.name, _selection_name
987
+ )
988
+
989
+ request_content = request_to_object(request)
990
+ log.debug(f"http post selection {_selection_name} content {request_content}")
991
+
992
+ match_filter: SFilter = dict_to_sfilter(request_content)
993
+ count = self._selections[_selection_name].select(match_filter)
994
+
995
+ log.debug(f"count in {self.name}/{_selection_name} {match_filter} -> {count}")
996
+
997
+ return (count, 200)
998
+
928
999
  @check_content_type
929
1000
  @error_to_http_handler
930
1001
  def http_create(self):
@@ -59,7 +59,7 @@ class DBBackoRedirectConnector(
59
59
 
60
60
  def select(
61
61
  self,
62
- select_filter: SFilter,
62
+ select_filter: SFilter = None,
63
63
  projection: list[str] = None,
64
64
  page_size=0,
65
65
  num_of_element_to_skip=0,
@@ -43,40 +43,100 @@ class DBHandler(ABC): # pylint: disable=too-many-instance-attributes
43
43
  self._table_name = None
44
44
  self.model = None
45
45
  self.filter: Filter = None
46
- self.transformers: dict[str, dict[str, Transformer]] = {}
47
- self.type_transformers: dict[str, Transformer] = {}
46
+
47
+ self.transformers_per_db_path: dict[str, tuple[list[str], Transformer]] = {}
48
+ self.transformers_per_key_path: dict[str, tuple[list[str], Transformer]] = {}
49
+
50
+ self._temp_transformers: list[Transformer] = []
51
+ self._temp_type_transformers: list[Transformer] = []
48
52
 
49
53
  options = Kparse(kwargs, KPARSE_MODEL)
50
54
 
51
55
  self.restriction_filter = options.get("restriction")
52
56
 
53
- def set_model(self, scheme: dict) -> None:
57
+ def set_model(self, model: dict) -> None:
54
58
  """
55
59
  Register the model (backo meta() into the connector)
56
60
 
57
- :param scheme: backo meta data
58
- :type scheme: dict
61
+ :param model: Item model
62
+ :type model: dict
59
63
  """
60
- self.model = scheme["item"]
64
+ self.model = model
61
65
  if self.filter:
62
- self.filter.set_model(scheme)
63
-
64
- def register_transformer(
65
- self, transformer: Transformer, table_name: str = None
66
+ self.filter.set_model(model)
67
+
68
+ # Re-arange transformer according to the model
69
+ for transformer in self._temp_transformers:
70
+ self._reorder_transformers_per_key(transformer)
71
+ self._temp_transformers.clear()
72
+
73
+ # Re-arange type transformer according to the model
74
+ for transformer in self._temp_type_transformers:
75
+ self._reorder_type_transformers_per_key(transformer, [], model)
76
+ self._temp_type_transformers.clear()
77
+
78
+ def _reorder_transformers_per_key(self, transformer: Transformer) -> None:
79
+ # Add the transformer indexed by key_path
80
+ key_path_string = None
81
+ if transformer.key_path:
82
+ key_path_string = "_".join(transformer.key_path)
83
+ if key_path_string:
84
+ self.transformers_per_key_path[key_path_string] = (
85
+ transformer.key_path,
86
+ transformer,
87
+ )
88
+
89
+ # Add the transformer indexed by db_path
90
+ db_path_string = transformer.get_db_path()
91
+ if db_path_string:
92
+ self.transformers_per_db_path["_".join(db_path_string)] = (
93
+ db_path_string,
94
+ transformer,
95
+ )
96
+
97
+ def _reorder_type_transformers_per_key(
98
+ self, transformer: Transformer, key_path: list[str], model: dict
66
99
  ) -> None:
100
+
101
+ if key_path and transformer.backo_type in model["types"]:
102
+ self.transformers_per_key_path["_".join(key_path)] = (key_path, transformer)
103
+ db_path = transformer.get_db_path(key_path)
104
+ if db_path:
105
+ self.transformers_per_db_path["_".join(db_path)] = (
106
+ db_path,
107
+ transformer,
108
+ )
109
+
110
+ if "sub_scheme" in model:
111
+ for k, v in model["sub_scheme"].items():
112
+ new_key_path = key_path.copy()
113
+ new_key_path.append(k)
114
+ self._reorder_type_transformers_per_key(transformer, new_key_path, v)
115
+ return
116
+
117
+ # A list
118
+ if "sub_type" in model:
119
+ sub_model = model["sub_type"]
120
+ new_key_path = key_path.copy()
121
+ new_key_path.append("_")
122
+ self._reorder_type_transformers_per_key(
123
+ transformer, new_key_path, sub_model
124
+ )
125
+ return
126
+
127
+ def register_transformer(self, transformer: Transformer) -> None:
67
128
  """
68
129
  Register a :py:class:`Transformer`
69
130
 
70
131
  :param transformer: the transformer to register
71
132
  :type transformer: :py:class:`Transformer`
72
- :param table_name: The table_name, defaults to None
73
- :type table_name: str, optional
74
133
  """
75
- t_name = self._table_name if table_name is None else table_name
76
-
77
- if t_name not in self.transformers:
78
- self.transformers[t_name] = {}
79
- self.transformers[t_name]["_".join(transformer.key_path)] = transformer
134
+ # Already a model : store the transformer by key
135
+ if self.model:
136
+ self._reorder_transformers_per_key(transformer)
137
+ return
138
+ # Keep it temporary waiting for model to store transformer
139
+ self._temp_transformers.append(transformer)
80
140
 
81
141
  def register_type_transformer(self, transformer: Transformer) -> None:
82
142
  """
@@ -85,66 +145,65 @@ class DBHandler(ABC): # pylint: disable=too-many-instance-attributes
85
145
  :param transformer: the transformer
86
146
  :type transformer: Transformer
87
147
  """
88
- self.type_transformers[transformer.backo_type] = transformer
148
+
149
+ if self.model:
150
+ self._reorder_type_transformers_per_key(transformer, [], self.model)
151
+ return
152
+ # Keep it temporary waiting for model to store transformer
153
+ self._temp_type_transformers.append(transformer)
89
154
 
90
155
  def _transform_on_load(self, loaded_object: dict):
91
- for transformers in self.transformers.values():
92
- for transformer in transformers.values():
156
+
157
+ for db_path, transformer in self.transformers_per_db_path.values():
158
+ if transformer.path_exists_in_object(db_path, loaded_object):
93
159
  try:
94
- db_path = transformer.get_db_path()
95
- if transformer.path_exists_in_object(db_path, loaded_object):
96
- transformer.on_load(loaded_object, db_path)
160
+ transformer.on_load(loaded_object, db_path)
97
161
  except Exception as e:
98
162
  raise DBError("Transformer on load error") from e
99
163
 
100
164
  def _transform_on_create(self, obj: dict):
101
- for transformers in self.transformers.values():
102
- for transformer in transformers.values():
165
+
166
+ for key_path, transformer in self.transformers_per_key_path.values():
167
+ if transformer.path_exists_in_object(key_path, obj):
103
168
  try:
104
- if transformer.path_exists_in_object(transformer.key_path, obj):
105
- transformer.on_create(obj, transformer.key_path)
169
+ transformer.on_create(obj, key_path)
106
170
  except Exception as e:
107
- raise DBError("Transformer on create error") from e
171
+ raise DBError("Transformer on_create error") from e
108
172
 
109
173
  def _transform_on_save(self, obj: dict):
110
- for transformers in self.transformers.values():
111
- for transformer in transformers.values():
174
+
175
+ for key_path, transformer in self.transformers_per_key_path.values():
176
+ if transformer.path_exists_in_object(key_path, obj):
112
177
  try:
113
- if transformer.path_exists_in_object(transformer.key_path, obj):
114
- transformer.on_save(obj, transformer.key_path)
178
+ transformer.on_save(obj, key_path)
115
179
  except Exception as e:
116
- raise DBError("Transformer on save error") from e
180
+ raise DBError("Transformer on_save error") from e
117
181
 
118
- def get_transformer(
119
- self, key_path: list[str], table_name: str = None, backo_types: list[str] = None
182
+ def get_transformer_by_key(
183
+ self, key_path: list[str] = None, db_path: list[str] = None
120
184
  ) -> Transformer:
121
185
  """
122
- Get a transformer for this key_path and the table or type
123
-
124
- :param key_path: the key in the object (like [ 'address', 'street' ])
125
- :type key_path: list[str]
126
- :param table_name: the table_name, defaults to None
127
- :type table_name: str, optional
128
- :param backo_types: types of the key, defaults to None
129
- :type backo_types: list[str], optional
130
- :return: _description_A transformer or None
186
+ Get a transformer by key_path or by _db_path
187
+
188
+ :param key_path: _description_, defaults to None
189
+ :type key_path: list[str], optional
190
+ :param db_path: _description_, defaults to None
191
+ :type db_path: list[str], optional
192
+ :return: _description_
131
193
  :rtype: Transformer
132
194
  """
133
-
134
- if backo_types:
135
- for backo_type in backo_types:
136
- if backo_type in self.type_transformers:
137
- return self.type_transformers[backo_type]
138
-
139
- t_name = self._table_name if table_name is None else table_name
140
- if t_name not in self.transformers:
195
+ # print(f'get_trans_by_key {key_path} {db_path} {self.transformers_per_key_path}')
196
+ if key_path:
197
+ key_path_string = "_".join(key_path)
198
+ if key_path_string in self.transformers_per_key_path:
199
+ return self.transformers_per_key_path[key_path_string][1]
141
200
  return None
142
- t_path = self.transformers[t_name]
201
+ if db_path:
202
+ db_path_string = "_".join(db_path)
203
+ if db_path_string in self.transformers_per_db_path:
204
+ return self.transformers_per_db_path[db_path_string][1]
143
205
 
144
- key = "_".join(key_path)
145
- if key not in t_path:
146
- return None
147
- return t_path[key]
206
+ return None
148
207
 
149
208
  def check_structure(self, _update_directly=False) -> tuple[bool, str]:
150
209
  """
@@ -253,7 +312,7 @@ class DBHandler(ABC): # pylint: disable=too-many-instance-attributes
253
312
  @abstractmethod
254
313
  def select( # pylint: disable=unused-argument
255
314
  self,
256
- select_filter: SFilter,
315
+ select_filter: SFilter = None,
257
316
  projection: list[str] = None,
258
317
  page_size: int = 0,
259
318
  num_of_element_to_skip: int = 0,
@@ -20,15 +20,15 @@ class Filter:
20
20
  self.model = None
21
21
  self.get_transformer = get_transformer
22
22
 
23
- def set_model(self, scheme: dict) -> None:
23
+ def set_model(self, model: dict) -> None:
24
24
  """
25
25
  Set the model (meta() to the filter), to adapt
26
26
  the filtering on types of objects
27
27
 
28
- :param scheme: scheme
29
- :type scheme: dict
28
+ :param model: scheme
29
+ :type model: dict
30
30
  """
31
- self.model = scheme["item"]
31
+ self.model = model
32
32
 
33
33
  def _get_model_by_path(self, path: str, key_path: list[str], model: dict) -> dict:
34
34
  """
@@ -3,7 +3,7 @@ Attribut transformer
3
3
  """
4
4
 
5
5
  import uuid
6
-
6
+ from typing import Any
7
7
  from jsonpath import patch, findall
8
8
 
9
9
 
@@ -58,15 +58,15 @@ class Transformer:
58
58
  jpath = f"$.{'.'.join(searching_path)}"
59
59
  return bool(findall(jpath, obj))
60
60
 
61
- def on_load(self, _loaded_object: dict, _key_path: list[str]):
61
+ def on_load(self, _loaded_object: dict, _db_path: list[str]):
62
62
  """
63
63
  Called when the object is read from the DB.
64
64
  You can do some changement into the object (delete keys, modifications, ...)
65
65
 
66
66
  :param _loaded_object: the lodaded object
67
67
  :type _loaded_object: dict
68
- :param _key_path: a key path as list
69
- :type _key_path: list[str]
68
+ :param _db_path: a key path as list
69
+ :type _db_path: list[str]
70
70
  """
71
71
  return
72
72
 
@@ -94,14 +94,37 @@ class Transformer:
94
94
  """
95
95
  return
96
96
 
97
- def get_db_path(self) -> list[str]:
97
+ def get_db_path(self, _key_path: list[str] = None) -> list[str]:
98
98
  """
99
- Return the db_path (by default = sekf.key_path )
99
+ Return the db_path (by default = sekf.key_path or the _key_path given in case of a Type Transformer )
100
100
 
101
101
  :return: the key path associated to this transformer
102
102
  :rtype: list[str]
103
103
  """
104
- return self.key_path
104
+ return self.key_path if self.key_path else _key_path
105
+
106
+ def get_db_model(self, model: dict, _key_path: list[str] = None) -> list[str]:
107
+ """
108
+ Return an adaptation of the model to the Ddatabase
109
+
110
+ :return: A modified
111
+ :rtype: dict
112
+ """
113
+ return model
114
+
115
+ def transform_value_to_db(self, v: Any) -> Any:
116
+ """
117
+ Transform its value on save, create, filter
118
+
119
+ """
120
+ return v
121
+
122
+ def transform_value_from_db(self, v: Any) -> Any:
123
+ """
124
+ Transform its value on load
125
+
126
+ """
127
+ return v
105
128
 
106
129
  def must_be_store_in_db(self) -> bool:
107
130
  """
@@ -188,7 +211,7 @@ class RenameTransformer(Transformer):
188
211
  obj,
189
212
  )
190
213
 
191
- def get_db_path(self) -> list[str]:
214
+ def get_db_path(self, _key_path: list[str] = None) -> list[str]:
192
215
  """
193
216
  Return the key_path
194
217
 
@@ -236,7 +259,7 @@ class IgnoreTransformer(Transformer):
236
259
  """
237
260
  patch.apply([{"op": "remove", "path": self.db_path_string}], loaded_object)
238
261
 
239
- def get_db_path(self) -> list[str]:
262
+ def get_db_path(self, _key_path: list[str] = None) -> list[str]:
240
263
  """
241
264
  Return the key_path
242
265
 
@@ -89,7 +89,7 @@ class DBMemoryConnector(DBHandler):
89
89
  raise NotFoundError('_id "{0}" not found in "{1}"', _id, self._name)
90
90
 
91
91
  d = copy.deepcopy(o)
92
- d[_id] = _id
92
+ d["_id"] = _id
93
93
 
94
94
  # Do all transformations on the object
95
95
  self._transform_on_save(d)
@@ -100,7 +100,7 @@ class DBMemoryConnector(DBHandler):
100
100
 
101
101
  def select( # pylint: disable=unused-argument
102
102
  self,
103
- select_filter: SFilter,
103
+ select_filter: SFilter = None,
104
104
  projection: list[str] = None,
105
105
  page_size: int = 0,
106
106
  num_of_element_to_skip: int = 0,