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.
- {backo-0.3.0 → backo-0.3.3}/CHANGELOG.md +20 -2
- {backo-0.3.0 → backo-0.3.3}/PKG-INFO +20 -3
- {backo-0.3.0 → backo-0.3.3}/README.md +19 -2
- {backo-0.3.0 → backo-0.3.3}/backo/collection.py +73 -2
- {backo-0.3.0 → backo-0.3.3}/backo/db/backo_redirect.py +1 -1
- {backo-0.3.0 → backo-0.3.3}/backo/db/generic/db_handler.py +117 -58
- {backo-0.3.0 → backo-0.3.3}/backo/db/generic/filter.py +4 -4
- {backo-0.3.0 → backo-0.3.3}/backo/db/generic/transformer.py +32 -9
- {backo-0.3.0 → backo-0.3.3}/backo/db/memory.py +2 -2
- {backo-0.3.0 → backo-0.3.3}/backo/db/mongo.py +132 -20
- {backo-0.3.0 → backo-0.3.3}/backo/db/sqlite3/db_handler.py +82 -39
- {backo-0.3.0 → backo-0.3.3}/backo/db/sqlite3/filter.py +7 -0
- {backo-0.3.0 → backo-0.3.3}/backo/db/sqlite3/pragma.py +12 -0
- {backo-0.3.0 → backo-0.3.3}/backo/db/sqlite3/transformers.py +16 -0
- {backo-0.3.0 → backo-0.3.3}/backo/db/valkeydb.py +10 -7
- {backo-0.3.0 → backo-0.3.3}/backo/db/yml.py +2 -9
- {backo-0.3.0 → backo-0.3.3}/backo/db/yml_dir.py +2 -1
- {backo-0.3.0 → backo-0.3.3}/backo/ref.py +1 -0
- {backo-0.3.0 → backo-0.3.3}/backo/selection.py +13 -2
- {backo-0.3.0 → backo-0.3.3}/examples/nationality/collections_set/db_country_connector.py +1 -1
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/collections_set/vms_connector.py +1 -1
- {backo-0.3.0 → backo-0.3.3}/pyproject.toml +1 -1
- backo-0.3.3/tests/test_db_connector.py +455 -0
- {backo-0.3.0 → backo-0.3.3}/tests/test_mongo.py +0 -19
- {backo-0.3.0 → backo-0.3.3}/tests/test_selections.py +60 -4
- {backo-0.3.0 → backo-0.3.3}/uv.lock +1 -1
- backo-0.3.0/tests/test_db_connector.py +0 -499
- {backo-0.3.0 → backo-0.3.3}/.coveragerc +0 -0
- {backo-0.3.0 → backo-0.3.3}/.github/workflows/pylint.yml +0 -0
- {backo-0.3.0 → backo-0.3.3}/.github/workflows/release.yml +0 -0
- {backo-0.3.0 → backo-0.3.3}/.github/workflows/test.yml +0 -0
- {backo-0.3.0 → backo-0.3.3}/.github/workflows/test_examples.yml +0 -0
- {backo-0.3.0 → backo-0.3.3}/.gitignore +0 -0
- {backo-0.3.0 → backo-0.3.3}/.pylintrc +0 -0
- {backo-0.3.0 → backo-0.3.3}/.readthedocs.yaml +0 -0
- {backo-0.3.0 → backo-0.3.3}/CONTRIBUTING.md +0 -0
- {backo-0.3.0 → backo-0.3.3}/LICENSE +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/__init__.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/action.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/api_toolbox.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/backoffice.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/collection_addon.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/current_user.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/db/__init__.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/db/generic/interface.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/db/restfull.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/db/sqlite3/__init__.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/error.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/file/blob_file.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/file/file.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/file/file_blob_connector.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/file/file_connector.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/file/file_system_connector.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/initialisation/init_script.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/initialisation/templates/__init__.pytemplate +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/initialisation/templates/backoffice.pytemplate +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/initialisation/templates/collection.pytemplate +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/item.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/log.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/loop_path.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/meta_data_handler.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/migration_report.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/openapi.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/patch.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/refs_strategies.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/refslist.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/request_decorators.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/status.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/backo/transaction.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/docs/Makefile +0 -0
- {backo-0.3.0 → backo-0.3.3}/docs/make.bat +0 -0
- {backo-0.3.0 → backo-0.3.3}/docs/requirements.txt +0 -0
- {backo-0.3.0 → backo-0.3.3}/docs/source/DB_management.md +0 -0
- {backo-0.3.0 → backo-0.3.3}/docs/source/api_reference.rst +0 -0
- {backo-0.3.0 → backo-0.3.3}/docs/source/conf.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/docs/source/errors.rst +0 -0
- {backo-0.3.0 → backo-0.3.3}/docs/source/index.rst +0 -0
- {backo-0.3.0 → backo-0.3.3}/docs/source/readme.rst +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/media_library/README.md +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/media_library/__init__.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/media_library/backoffice.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/media_library/collections_set/__init__.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/media_library/collections_set/books.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/media_library/collections_set/users.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/media_library/migration.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/media_library/tests.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/nationality/README.md +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/nationality/__init__.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/nationality/backoffice.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/nationality/collections_set/__init__.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/nationality/collections_set/countries.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/nationality/collections_set/people.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/nationality/tests.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/README.md +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/__init__.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/backoffice.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/collections_set/__init__.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/collections_set/users.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/collections_set/vms.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/config.yaml +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/constants.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/requirements.txt +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/rest-api/README.md +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/rest-api/collections_set/__init__.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/rest-api/collections_set/vms.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/rest-api/config.yaml +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/rest-api/constants.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/examples/rest_api_connector/rest-api/rest_api.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/requirements.txt +0 -0
- {backo-0.3.0 → backo-0.3.3}/tests/__init__.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/tests/test_action.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/tests/test_api_toolbox.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/tests/test_crud.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/tests/test_current_user.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/tests/test_file.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/tests/test_log.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/tests/test_meta.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/tests/test_migrations.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/tests/test_openapi.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/tests/test_reference.py +0 -0
- {backo-0.3.0 → backo-0.3.3}/tests/test_rest_api_connector.py +0 -0
- {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.
|
|
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":
|
|
1026
|
-
curl -X GET 'http://localhost/media_library/coll/books/
|
|
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":
|
|
978
|
-
curl -X GET 'http://localhost/media_library/coll/books/
|
|
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.
|
|
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.
|
|
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):
|
|
@@ -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
|
-
|
|
47
|
-
self.
|
|
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,
|
|
57
|
+
def set_model(self, model: dict) -> None:
|
|
54
58
|
"""
|
|
55
59
|
Register the model (backo meta() into the connector)
|
|
56
60
|
|
|
57
|
-
:param
|
|
58
|
-
:type
|
|
61
|
+
:param model: Item model
|
|
62
|
+
:type model: dict
|
|
59
63
|
"""
|
|
60
|
-
self.model =
|
|
64
|
+
self.model = model
|
|
61
65
|
if self.filter:
|
|
62
|
-
self.filter.set_model(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
|
171
|
+
raise DBError("Transformer on_create error") from e
|
|
108
172
|
|
|
109
173
|
def _transform_on_save(self, obj: dict):
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
|
180
|
+
raise DBError("Transformer on_save error") from e
|
|
117
181
|
|
|
118
|
-
def
|
|
119
|
-
self, key_path: list[str]
|
|
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
|
|
123
|
-
|
|
124
|
-
:param key_path:
|
|
125
|
-
:type key_path: list[str]
|
|
126
|
-
:param
|
|
127
|
-
:type
|
|
128
|
-
:
|
|
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
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
29
|
-
:type
|
|
28
|
+
:param model: scheme
|
|
29
|
+
:type model: dict
|
|
30
30
|
"""
|
|
31
|
-
self.model =
|
|
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,
|
|
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
|
|
69
|
-
:type
|
|
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,
|