backo 0.3.7__tar.gz → 0.3.8__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.7 → backo-0.3.8}/CHANGELOG.md +16 -0
- {backo-0.3.7 → backo-0.3.8}/PKG-INFO +2 -2
- {backo-0.3.7 → backo-0.3.8}/backo/db/mongo.py +18 -2
- {backo-0.3.7 → backo-0.3.8}/backo/file/file.py +2 -2
- {backo-0.3.7 → backo-0.3.8}/backo/initialisation/init_script.py +50 -19
- backo-0.3.8/backo/initialisation/templates/backoffice.pytemplate +29 -0
- {backo-0.3.7 → backo-0.3.8}/backo/initialisation/templates/collection.pytemplate +7 -4
- backo-0.3.7/backo/initialisation/templates/backoffice.pytemplate → backo-0.3.8/backo/initialisation/templates/main.pytemplate +7 -27
- backo-0.3.8/backo/initialisation/templates/migrate.pytemplate +58 -0
- {backo-0.3.7 → backo-0.3.8}/backo/item.py +2 -20
- {backo-0.3.7 → backo-0.3.8}/backo/meta_data_handler.py +5 -5
- {backo-0.3.7 → backo-0.3.8}/backo/patch.py +1 -1
- {backo-0.3.7 → backo-0.3.8}/backo/selection.py +1 -0
- {backo-0.3.7 → backo-0.3.8}/examples/nationality/collections_set/people.py +2 -2
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/collections_set/users.py +3 -3
- {backo-0.3.7 → backo-0.3.8}/pyproject.toml +2 -2
- {backo-0.3.7 → backo-0.3.8}/tests/test_file.py +1 -1
- {backo-0.3.7 → backo-0.3.8}/tests/test_selections.py +27 -1
- {backo-0.3.7 → backo-0.3.8}/uv.lock +5 -5
- {backo-0.3.7 → backo-0.3.8}/.coveragerc +0 -0
- {backo-0.3.7 → backo-0.3.8}/.github/workflows/pylint.yml +0 -0
- {backo-0.3.7 → backo-0.3.8}/.github/workflows/release.yml +0 -0
- {backo-0.3.7 → backo-0.3.8}/.github/workflows/test.yml +0 -0
- {backo-0.3.7 → backo-0.3.8}/.github/workflows/test_examples.yml +0 -0
- {backo-0.3.7 → backo-0.3.8}/.gitignore +0 -0
- {backo-0.3.7 → backo-0.3.8}/.pylintrc +0 -0
- {backo-0.3.7 → backo-0.3.8}/.readthedocs.yaml +0 -0
- {backo-0.3.7 → backo-0.3.8}/CONTRIBUTING.md +0 -0
- {backo-0.3.7 → backo-0.3.8}/LICENSE +0 -0
- {backo-0.3.7 → backo-0.3.8}/README.md +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/__init__.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/action.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/api_toolbox.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/backoffice.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/collection.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/collection_addon.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/current_user.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/__init__.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/backo_redirect.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/generic/db_handler.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/generic/filter.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/generic/interface.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/generic/transformer.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/generic/transformer_cache.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/memory.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/restfull.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/sqlite3/__init__.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/sqlite3/db_handler.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/sqlite3/filter.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/sqlite3/pragma.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/sqlite3/transformers.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/valkeydb.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/yml.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/db/yml_dir.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/error.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/file/blob_file.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/file/file_blob_connector.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/file/file_connector.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/file/file_system_connector.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/initialisation/templates/__init__.pytemplate +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/log.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/loop_path.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/migration.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/openapi.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/ref.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/refs_strategies.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/refslist.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/request_decorators.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/sort.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/status.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/backo/transaction.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/docs/Makefile +0 -0
- {backo-0.3.7 → backo-0.3.8}/docs/make.bat +0 -0
- {backo-0.3.7 → backo-0.3.8}/docs/requirements.txt +0 -0
- {backo-0.3.7 → backo-0.3.8}/docs/source/DB_management.md +0 -0
- {backo-0.3.7 → backo-0.3.8}/docs/source/api_reference.rst +0 -0
- {backo-0.3.7 → backo-0.3.8}/docs/source/conf.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/docs/source/errors.rst +0 -0
- {backo-0.3.7 → backo-0.3.8}/docs/source/index.rst +0 -0
- {backo-0.3.7 → backo-0.3.8}/docs/source/readme.rst +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/media_library/README.md +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/media_library/__init__.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/media_library/backoffice.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/media_library/collections_set/__init__.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/media_library/collections_set/books.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/media_library/collections_set/users.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/media_library/migration.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/media_library/tests.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/nationality/README.md +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/nationality/__init__.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/nationality/backoffice.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/nationality/collections_set/__init__.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/nationality/collections_set/countries.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/nationality/collections_set/db_country_connector.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/nationality/tests.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/README.md +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/__init__.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/backoffice.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/collections_set/__init__.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/collections_set/vms.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/collections_set/vms_connector.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/config.yaml +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/constants.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/requirements.txt +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/rest-api/README.md +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/rest-api/collections_set/__init__.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/rest-api/collections_set/vms.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/rest-api/config.yaml +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/rest-api/constants.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/rest-api/rest_api.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/requirements.txt +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/__init__.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_action.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_api_toolbox.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_crud.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_current_user.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_db_connector.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_db_connector_cache.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_log.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_meta.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_migrations.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_mongo.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_openapi.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_reference.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_rest_api_connector.py +0 -0
- {backo-0.3.7 → backo-0.3.8}/tests/test_routes.py +0 -0
|
@@ -5,6 +5,22 @@ 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.8] 2026-09-29
|
|
9
|
+
|
|
10
|
+
* Internal :
|
|
11
|
+
* BREAKING CHANGE : use stricto 0.3.0
|
|
12
|
+
* function signature of transform=, contraint=, contraints= has change to (object, value, old_value)
|
|
13
|
+
* required= field must be with a default=
|
|
14
|
+
* Fix
|
|
15
|
+
* fix issue #32 (load and check value)
|
|
16
|
+
* fix issue #31 (StandardMetaDataHandler do set_value() rather than set())
|
|
17
|
+
* fix issue #29
|
|
18
|
+
* fix issue require= during init_script
|
|
19
|
+
* fix issue need mongo for init_script (now with DBYmlDirConnector)
|
|
20
|
+
* Feature
|
|
21
|
+
* BREAKING CHANGE : add feature by default fields with set= are not saved into the DB. Must
|
|
22
|
+
add view= [ "save" ] to save it.
|
|
23
|
+
|
|
8
24
|
## [0.3.7] 2026-09-23
|
|
9
25
|
|
|
10
26
|
* Internal :
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: backo
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
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
|
|
@@ -41,7 +41,7 @@ Requires-Dist: pyyaml
|
|
|
41
41
|
Requires-Dist: questionary
|
|
42
42
|
Requires-Dist: redis>=8.1.0
|
|
43
43
|
Requires-Dist: requests
|
|
44
|
-
Requires-Dist: stricto>=0.
|
|
44
|
+
Requires-Dist: stricto>=0.3.0
|
|
45
45
|
Provides-Extra: mongo
|
|
46
46
|
Requires-Dist: pymongo>=4.17.0; extra == 'mongo'
|
|
47
47
|
Description-Content-Type: text/markdown
|
|
@@ -214,6 +214,22 @@ class IdTransformer(Transformer):
|
|
|
214
214
|
"""
|
|
215
215
|
_loaded_object["_id"] = str(_loaded_object["_id"])
|
|
216
216
|
|
|
217
|
+
def transform_value_to_db(self, v: Any) -> Any:
|
|
218
|
+
"""
|
|
219
|
+
Transform its value on save, create, filter
|
|
220
|
+
|
|
221
|
+
"""
|
|
222
|
+
if isinstance(v, str):
|
|
223
|
+
return ObjectId(v)
|
|
224
|
+
return v
|
|
225
|
+
|
|
226
|
+
def transform_value_from_db(self, v: Any) -> Any:
|
|
227
|
+
"""
|
|
228
|
+
Transform its value on load
|
|
229
|
+
|
|
230
|
+
"""
|
|
231
|
+
return str(v)
|
|
232
|
+
|
|
217
233
|
def on_create(self, obj: dict, _key_path: list[str]):
|
|
218
234
|
"""
|
|
219
235
|
Delete the _id field if exists
|
|
@@ -399,12 +415,12 @@ class DBMongoConnector(DBHandler):
|
|
|
399
415
|
'Mongo connection error while "{0}.drop()"', self._collection_name
|
|
400
416
|
) from e
|
|
401
417
|
|
|
402
|
-
def generate_id(self,
|
|
418
|
+
def generate_id(self, _o: dict) -> str:
|
|
403
419
|
"""
|
|
404
420
|
Not used in Mongo
|
|
405
421
|
(the _id is generated by the DB itself)
|
|
406
422
|
"""
|
|
407
|
-
|
|
423
|
+
raise DBError("generate_id() not implemented in DBMongoConnector")
|
|
408
424
|
|
|
409
425
|
def get_by_id(self, _id: str) -> dict:
|
|
410
426
|
"""get"""
|
|
@@ -35,7 +35,7 @@ FILE_MODEL = {
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def transform_to_filestorage( # pylint: disable=unused-argument
|
|
38
|
-
v: Any,
|
|
38
|
+
_o: Any, v: Any, _old_value: Any
|
|
39
39
|
) -> FileStorage | None:
|
|
40
40
|
"""Transform any kind of object into a FileStorage
|
|
41
41
|
|
|
@@ -267,7 +267,7 @@ class File(Dict):
|
|
|
267
267
|
Set the file content
|
|
268
268
|
"""
|
|
269
269
|
return self._set_content_from_filestorage(
|
|
270
|
-
transform_to_filestorage(content, None)
|
|
270
|
+
transform_to_filestorage(None, content, None)
|
|
271
271
|
)
|
|
272
272
|
|
|
273
273
|
def set_value(self, value: Any) -> bool:
|
|
@@ -157,6 +157,7 @@ class Init:
|
|
|
157
157
|
|
|
158
158
|
if exists == False:
|
|
159
159
|
return
|
|
160
|
+
|
|
160
161
|
if can_read == False:
|
|
161
162
|
return
|
|
162
163
|
|
|
@@ -254,7 +255,10 @@ and if it is required.",
|
|
|
254
255
|
views=["!conf"],
|
|
255
256
|
),
|
|
256
257
|
"name": String(
|
|
257
|
-
require=True,
|
|
258
|
+
require=True,
|
|
259
|
+
description="Name of the field",
|
|
260
|
+
regexp=r"[A-z_-]+",
|
|
261
|
+
default="",
|
|
258
262
|
),
|
|
259
263
|
"type": String(
|
|
260
264
|
require=True,
|
|
@@ -262,8 +266,13 @@ and if it is required.",
|
|
|
262
266
|
default="String",
|
|
263
267
|
description="type of the field",
|
|
264
268
|
),
|
|
265
|
-
"required": Bool(
|
|
266
|
-
|
|
269
|
+
"required": Bool(
|
|
270
|
+
default=False, description="is the field required", can_read=args.expert
|
|
271
|
+
),
|
|
272
|
+
"default": String(
|
|
273
|
+
description="a default value (mandatory if the field is required)",
|
|
274
|
+
can_read=args.expert,
|
|
275
|
+
),
|
|
267
276
|
},
|
|
268
277
|
description="field",
|
|
269
278
|
)
|
|
@@ -271,10 +280,15 @@ and if it is required.",
|
|
|
271
280
|
selection_model = Dict(
|
|
272
281
|
{
|
|
273
282
|
"name": String(
|
|
274
|
-
require=True,
|
|
283
|
+
require=True,
|
|
284
|
+
description="Name of the selection",
|
|
285
|
+
regexp=r"[A-z_-]+",
|
|
286
|
+
default="",
|
|
275
287
|
),
|
|
276
288
|
"paths": List(
|
|
277
|
-
String(
|
|
289
|
+
String(
|
|
290
|
+
required=True, description="path", regexp=r"^\$\..+", default=""
|
|
291
|
+
),
|
|
278
292
|
description="paths list",
|
|
279
293
|
),
|
|
280
294
|
},
|
|
@@ -284,11 +298,17 @@ and if it is required.",
|
|
|
284
298
|
action_model = Dict(
|
|
285
299
|
{
|
|
286
300
|
"name": String(
|
|
287
|
-
require=True,
|
|
301
|
+
require=True,
|
|
302
|
+
description="Name of the action",
|
|
303
|
+
regexp=r"[A-z_-]+",
|
|
304
|
+
default="",
|
|
288
305
|
),
|
|
289
306
|
"fields": List(field_model, default=[], description="action fields list"),
|
|
290
307
|
"function_name": String(
|
|
291
|
-
required=True,
|
|
308
|
+
required=True,
|
|
309
|
+
description="name of the function",
|
|
310
|
+
regexp=r"[A-z_-]+",
|
|
311
|
+
default="",
|
|
292
312
|
),
|
|
293
313
|
},
|
|
294
314
|
description="selection",
|
|
@@ -297,7 +317,10 @@ and if it is required.",
|
|
|
297
317
|
collection_model = Dict(
|
|
298
318
|
{
|
|
299
319
|
"name": String(
|
|
300
|
-
require=True,
|
|
320
|
+
require=True,
|
|
321
|
+
description="Name of the collection",
|
|
322
|
+
regexp=r"[A-z_-]+",
|
|
323
|
+
default="",
|
|
301
324
|
),
|
|
302
325
|
"welcome1": Message(
|
|
303
326
|
default='\
|
|
@@ -358,7 +381,10 @@ Lets go.",
|
|
|
358
381
|
views=["!conf"],
|
|
359
382
|
),
|
|
360
383
|
"name": String(
|
|
361
|
-
require=True,
|
|
384
|
+
require=True,
|
|
385
|
+
description="Name of the application",
|
|
386
|
+
regexp=r"[A-z_-]+",
|
|
387
|
+
default="",
|
|
362
388
|
),
|
|
363
389
|
"welcome2": Message(
|
|
364
390
|
default='\
|
|
@@ -469,21 +495,26 @@ It is better to have at least one collection :).',
|
|
|
469
495
|
|
|
470
496
|
template = env.get_template("__init__.pytemplate")
|
|
471
497
|
rendered = template.render(db_json_struct)
|
|
472
|
-
|
|
473
|
-
with open(filename, mode="w", encoding="utf-8") as outfile:
|
|
474
|
-
questionary.print(f"> creating file {filename}", style="italic fg:yellow")
|
|
475
|
-
outfile.write(rendered)
|
|
476
|
-
|
|
477
|
-
template = env.get_template("backoffice.pytemplate")
|
|
478
|
-
rendered = template.render(db_json_struct)
|
|
479
|
-
backo_filename = os.path.join(repo_dir, "backoffice.py")
|
|
498
|
+
backo_filename = os.path.join(destination_collection_directory, "__init__.py")
|
|
480
499
|
with open(backo_filename, mode="w", encoding="utf-8") as outfile:
|
|
481
500
|
questionary.print(f"> creating file {backo_filename}", style="italic fg:yellow")
|
|
482
501
|
outfile.write(rendered)
|
|
483
502
|
|
|
503
|
+
for fname in ["backoffice", "main", "migrate"]:
|
|
504
|
+
template = env.get_template(f"{fname}.pytemplate")
|
|
505
|
+
rendered = template.render(db_json_struct)
|
|
506
|
+
backo_filename = os.path.join(repo_dir, f"{fname}.py")
|
|
507
|
+
with open(backo_filename, mode="w", encoding="utf-8") as outfile:
|
|
508
|
+
questionary.print(
|
|
509
|
+
f"> creating file {backo_filename}", style="italic fg:yellow"
|
|
510
|
+
)
|
|
511
|
+
outfile.write(rendered)
|
|
512
|
+
|
|
484
513
|
questionary.print(f'{"\u2500"*40}', style="bold fg:yellow")
|
|
485
|
-
questionary.print(
|
|
486
|
-
|
|
514
|
+
questionary.print(
|
|
515
|
+
f"Now you can start the backoffice (in {repo_dir}) :", style="fg:darkred"
|
|
516
|
+
)
|
|
517
|
+
questionary.print("[uv run] python main.py")
|
|
487
518
|
|
|
488
519
|
|
|
489
520
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
backoffice : The main application
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# linter is lost with different modules with the same name
|
|
6
|
+
# pylint: disable=no-name-in-module
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from backo import Backoffice, log_system, LogLevel
|
|
10
|
+
|
|
11
|
+
# Import all collections
|
|
12
|
+
{% for collection in collections %}from collections_set import {{ collection.name }}
|
|
13
|
+
{% endfor %}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Set log system (for debug)
|
|
17
|
+
log_system.add_handler(log_system.set_streamhandler()) # output to stderr
|
|
18
|
+
log = log_system.get_or_create_logger("{{ name }}", LogLevel.INFO)
|
|
19
|
+
|
|
20
|
+
# -----------------------------------------
|
|
21
|
+
# Backoffice creation
|
|
22
|
+
# -----------------------------------------
|
|
23
|
+
my_backoffice = Backoffice("{{ name }}")
|
|
24
|
+
{% for collection in collections %}my_backoffice.register_collection({{ collection.name }})
|
|
25
|
+
{% endfor %}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# To check if you have some syntax issue
|
|
29
|
+
my_backoffice.check_syntax()
|
|
@@ -18,7 +18,8 @@ from backo import (
|
|
|
18
18
|
Action,
|
|
19
19
|
Selection,
|
|
20
20
|
)
|
|
21
|
-
from backo.db import DBMongoConnector
|
|
21
|
+
# from backo.db import DBMongoConnector
|
|
22
|
+
from backo.db import DBYmlDirConnector
|
|
22
23
|
|
|
23
24
|
log = log_system.get_or_create_logger("{{ name }}", LogLevel.INFO)
|
|
24
25
|
|
|
@@ -39,9 +40,11 @@ log = log_system.get_or_create_logger("{{ name }}", LogLevel.INFO)
|
|
|
39
40
|
# ------------------------------------------------
|
|
40
41
|
# COLLECTION
|
|
41
42
|
# ------------------------------------------------
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
# If you want mongo...
|
|
44
|
+
# connector = DBMongoConnector(
|
|
45
|
+
# mongodb://localhost:27017/{{ app_name }}", "{{ name }}"
|
|
46
|
+
# )
|
|
47
|
+
connector = DBYmlDirConnector( "/tmp/{{ app_name }}/{{ name }}" )
|
|
45
48
|
|
|
46
49
|
|
|
47
50
|
def can_create(right_name: str, {{ name }}: Item) -> bool:
|
|
@@ -6,27 +6,21 @@ backoffice : The main application
|
|
|
6
6
|
# pylint: disable=no-name-in-module
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
|
-
import sys
|
|
10
9
|
from functools import wraps
|
|
11
10
|
from datetime import datetime, timezone, timedelta
|
|
12
11
|
import jwt
|
|
13
12
|
from flask import Flask, request, jsonify, make_response, Response
|
|
13
|
+
from backo import current_user, log_system
|
|
14
|
+
from backoffice import my_backoffice
|
|
15
|
+
|
|
16
|
+
# Add log output to stderr
|
|
17
|
+
log_system.add_handler(log_system.set_streamhandler())
|
|
18
|
+
|
|
14
19
|
|
|
15
20
|
# Avoid CORS issues
|
|
16
21
|
# --------------------------
|
|
17
22
|
# from flask_cors import CORS
|
|
18
23
|
|
|
19
|
-
from backo import Backoffice, current_user, log_system, LogLevel
|
|
20
|
-
|
|
21
|
-
# Import all collections
|
|
22
|
-
{% for collection in collections %}from collections_set import {{ collection.name }}
|
|
23
|
-
{% endfor %}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# Set log system (for debug)
|
|
27
|
-
log_system.add_handler(log_system.set_streamhandler()) # output to stderr
|
|
28
|
-
log = log_system.get_or_create_logger("{{ name }}", LogLevel.INFO)
|
|
29
|
-
|
|
30
24
|
|
|
31
25
|
# set the flask application route
|
|
32
26
|
flask = Flask("{{ name }}")
|
|
@@ -119,24 +113,10 @@ def logout():
|
|
|
119
113
|
|
|
120
114
|
|
|
121
115
|
# -----------------------------------------
|
|
122
|
-
#
|
|
116
|
+
# Adding routes
|
|
123
117
|
# -----------------------------------------
|
|
124
|
-
my_backoffice = Backoffice("{{ name }}")
|
|
125
|
-
{% for collection in collections %}my_backoffice.register_collection({{ collection.name }})
|
|
126
|
-
{% endfor %}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
# To check if you have some syntax issue
|
|
130
|
-
my_backoffice.check_syntax()
|
|
131
118
|
my_backoffice.build_routes(flask, "", check_user_token)
|
|
132
119
|
|
|
133
120
|
|
|
134
|
-
# ------------------------------------
|
|
135
|
-
# Clean the database
|
|
136
|
-
# ------------------------------------
|
|
137
|
-
# {% for collection in collections %}{{ collection.name }}.drop()
|
|
138
|
-
# {% endfor %}
|
|
139
|
-
|
|
140
|
-
|
|
141
121
|
if __name__ == "__main__":
|
|
142
122
|
flask.run(host="0.0.0.0", port=5000)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
backoffice : The migration
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# linter is lost with different modules with the same name
|
|
6
|
+
# pylint: disable=no-name-in-module
|
|
7
|
+
import argparse
|
|
8
|
+
from backo import log_system, LogLevel, MigrationStrategy
|
|
9
|
+
from backoffice import my_backoffice
|
|
10
|
+
|
|
11
|
+
log = log_system.get_or_create_logger("migration")
|
|
12
|
+
log.addHandler( log_system.set_streamhandler() )
|
|
13
|
+
log.setLevel( LogLevel.INFO )
|
|
14
|
+
|
|
15
|
+
# Setup argument parser
|
|
16
|
+
parser = argparse.ArgumentParser(description="Backo: initialisation tool.")
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
"--id", "--ids",
|
|
19
|
+
nargs="*",
|
|
20
|
+
type=str,
|
|
21
|
+
default=[],
|
|
22
|
+
dest="_ids",
|
|
23
|
+
help="id or ids to check for migration",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--mode",
|
|
27
|
+
type=str,
|
|
28
|
+
choices=["dry_run", "execute", "force"],
|
|
29
|
+
default="dry_run",
|
|
30
|
+
dest="mode",
|
|
31
|
+
help="execution mode (by default dry_run)"
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"-coll", "--collection",
|
|
35
|
+
type=str,
|
|
36
|
+
required=True,
|
|
37
|
+
dest="collection",
|
|
38
|
+
help="the collection name"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Parse command line arguments
|
|
42
|
+
args = parser.parse_args()
|
|
43
|
+
s = MigrationStrategy[args.mode.upper()]
|
|
44
|
+
_ids = args._ids if args._ids else None
|
|
45
|
+
# ------------------------------------------------------
|
|
46
|
+
# Change Here the migration function
|
|
47
|
+
# ------------------------------------------------------
|
|
48
|
+
def migrate(o: dict) -> dict:
|
|
49
|
+
"""fake migration"""
|
|
50
|
+
return o
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
log.info(f'Start migration for collection="{args.collection}", _ids={_ids}, strategy={s}')
|
|
54
|
+
# To check compatibility
|
|
55
|
+
my_backoffice.migrate( args.collection, _ids=_ids, strategy=s)
|
|
56
|
+
|
|
57
|
+
# To do or check migration (the migration function must be filled)
|
|
58
|
+
# my_backoffice.migrate( args.collection, migrate, _ids=_ids, strategy=s)
|
|
@@ -179,6 +179,7 @@ class Item(Dict): # pylint: disable=too-many-instance-attributes
|
|
|
179
179
|
self.set_value(obj)
|
|
180
180
|
self.set_status_saved()
|
|
181
181
|
self.__dict__["_loaded_object"] = copy.copy(self)
|
|
182
|
+
self.enable_permissions()
|
|
182
183
|
self.trigg("loaded", **kwargs)
|
|
183
184
|
|
|
184
185
|
def load(self, _id: str, **kwargs) -> None:
|
|
@@ -385,20 +386,6 @@ class Item(Dict): # pylint: disable=too-many-instance-attributes
|
|
|
385
386
|
self.get_value(),
|
|
386
387
|
)
|
|
387
388
|
|
|
388
|
-
def create_uniq_id(self) -> str:
|
|
389
|
-
"""
|
|
390
|
-
Create an _id before creation.
|
|
391
|
-
Depends on the db_connector used. some of them needs _ids
|
|
392
|
-
|
|
393
|
-
is probably overwritten
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
:meta private:
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
"""
|
|
400
|
-
return self.db_handler.generate_id(self)
|
|
401
|
-
|
|
402
389
|
def create(self, obj: dict, **kwargs):
|
|
403
390
|
"""
|
|
404
391
|
Create and save an object into the DB
|
|
@@ -433,9 +420,6 @@ class Item(Dict): # pylint: disable=too-many-instance-attributes
|
|
|
433
420
|
|
|
434
421
|
self.set(obj)
|
|
435
422
|
|
|
436
|
-
# Set the _id
|
|
437
|
-
self._id = self.create_uniq_id()
|
|
438
|
-
|
|
439
423
|
# Set _meta
|
|
440
424
|
if self.meta_data_handler:
|
|
441
425
|
self.meta_data_handler.update(self)
|
|
@@ -447,9 +431,7 @@ class Item(Dict): # pylint: disable=too-many-instance-attributes
|
|
|
447
431
|
# dict_to_save = self.get_value()
|
|
448
432
|
dict_to_save = self.get_view("save").get_encoded()
|
|
449
433
|
|
|
450
|
-
self.
|
|
451
|
-
self._id = self.db_handler.create(dict_to_save)
|
|
452
|
-
self.enable_permissions()
|
|
434
|
+
self._id.set_value(self.db_handler.create(dict_to_save))
|
|
453
435
|
|
|
454
436
|
self.set_status_saved()
|
|
455
437
|
|
|
@@ -62,17 +62,17 @@ class StandardMetaDataHandler(
|
|
|
62
62
|
|
|
63
63
|
# Set creation ctime and owner
|
|
64
64
|
if o._meta.ctime == None: # pylint: disable=singleton-comparison
|
|
65
|
-
o._meta.ctime
|
|
65
|
+
o._meta.ctime.set_value(now)
|
|
66
66
|
|
|
67
67
|
login = current_user.login.copy()
|
|
68
68
|
user_id = current_user._id.copy()
|
|
69
69
|
|
|
70
70
|
if o._meta.created_by._id == None: # pylint: disable=singleton-comparison
|
|
71
|
-
o._meta.created_by.
|
|
71
|
+
o._meta.created_by.set_value({"_id": user_id, "login": login})
|
|
72
72
|
|
|
73
73
|
# Set modificattion time and last updater
|
|
74
|
-
o._meta.modified_by.
|
|
75
|
-
o._meta.mtime.
|
|
74
|
+
o._meta.modified_by.set_value({"_id": user_id, "login": login})
|
|
75
|
+
o._meta.mtime.set_value(now)
|
|
76
76
|
|
|
77
77
|
# Put permission back
|
|
78
78
|
if permission_enabled is True:
|
|
@@ -95,7 +95,7 @@ class StandardMetaDataHandler(
|
|
|
95
95
|
),
|
|
96
96
|
"modified_by": Dict(
|
|
97
97
|
{"_id": String(), "login": String(default="ANONYMOUS")},
|
|
98
|
-
description="
|
|
98
|
+
description="Modified by",
|
|
99
99
|
),
|
|
100
100
|
},
|
|
101
101
|
can_modify=False,
|
|
@@ -13,7 +13,7 @@ sys.path.insert(1, "../../stricto")
|
|
|
13
13
|
from stricto import Dict, String, GenericType
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def must(value,
|
|
16
|
+
def must(_o, value, _old_value) -> bool:
|
|
17
17
|
"""check if start with a $.
|
|
18
18
|
|
|
19
19
|
:param value: _description_
|
|
@@ -105,6 +105,7 @@ class Selection(CollectionAddon):
|
|
|
105
105
|
CollectionAddon.__init__(self)
|
|
106
106
|
self._permissions = Permissions(**kwargs)
|
|
107
107
|
self._permissions.add_or_modify_permission("read", options.get("can_read"))
|
|
108
|
+
self._permissions.enable()
|
|
108
109
|
|
|
109
110
|
def get_schema(self) -> dict:
|
|
110
111
|
"""
|
|
@@ -28,8 +28,8 @@ log = log_system.get_or_create_logger("user")
|
|
|
28
28
|
# ------------------------------------
|
|
29
29
|
item = Item(
|
|
30
30
|
{
|
|
31
|
-
"name": String(require=True),
|
|
32
|
-
"surname": String(require=True),
|
|
31
|
+
"name": String(require=True, default=""),
|
|
32
|
+
"surname": String(require=True, default=""),
|
|
33
33
|
"first_nationality": Ref(coll="countries"),
|
|
34
34
|
"other_nationalities": RefsList(coll="countries"),
|
|
35
35
|
},
|
|
@@ -28,9 +28,9 @@ log = log_system.get_or_create_logger("users")
|
|
|
28
28
|
# ------------------------------------
|
|
29
29
|
item = Item(
|
|
30
30
|
{
|
|
31
|
-
"name": String(require=True),
|
|
32
|
-
"surname": String(require=True),
|
|
33
|
-
"login": String(require=True),
|
|
31
|
+
"name": String(require=True, default=""),
|
|
32
|
+
"surname": String(require=True, default=""),
|
|
33
|
+
"login": String(require=True, default=""),
|
|
34
34
|
"vms": RefsList(coll="vms"),
|
|
35
35
|
},
|
|
36
36
|
meta_data_handler=GenericMetaDataHandler(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "backo"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.8"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="Bertrand Wallrich", email="Bertrand.Wallrich@inria.fr" },
|
|
6
6
|
]
|
|
@@ -23,7 +23,7 @@ dependencies = [
|
|
|
23
23
|
"python-magic",
|
|
24
24
|
"redis>=8.1.0",
|
|
25
25
|
"python-jsonpath",
|
|
26
|
-
"stricto>=0.
|
|
26
|
+
"stricto>=0.3.0"
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[project.optional-dependencies]
|
|
@@ -218,7 +218,7 @@ class TestFile(unittest.TestCase):
|
|
|
218
218
|
def test_file_constraints(self):
|
|
219
219
|
"""Test constraints on a file"""
|
|
220
220
|
|
|
221
|
-
def only_text(f: FileStorage | None,
|
|
221
|
+
def only_text(_o, f: FileStorage | None, _old_f) -> bool:
|
|
222
222
|
"""check manualy if the mime type is text/plain"""
|
|
223
223
|
|
|
224
224
|
if f is None:
|
|
@@ -9,7 +9,7 @@ from backo import Item, Collection
|
|
|
9
9
|
from backo.db import DBYmlDirConnector
|
|
10
10
|
from backo import Backoffice
|
|
11
11
|
from backo import current_user, Selection
|
|
12
|
-
from backo import String, Bool, SFilter, Operator, Sort, SortItem
|
|
12
|
+
from backo import String, Bool, SFilter, Operator, Sort, SortItem, SRightError
|
|
13
13
|
|
|
14
14
|
### --- For development ---
|
|
15
15
|
# import logging
|
|
@@ -33,6 +33,8 @@ class TestSelections(unittest.TestCase):
|
|
|
33
33
|
"""
|
|
34
34
|
super().__init__(*args, **kwargs)
|
|
35
35
|
|
|
36
|
+
self.read = True
|
|
37
|
+
|
|
36
38
|
# --- DB for user
|
|
37
39
|
self.yml_users = DBYmlDirConnector(YML_DIR)
|
|
38
40
|
self.yml_users.generate_id = lambda o: f"User_{o["name"]}_{o["surname"]}"
|
|
@@ -48,6 +50,8 @@ class TestSelections(unittest.TestCase):
|
|
|
48
50
|
}
|
|
49
51
|
),
|
|
50
52
|
self.yml_users,
|
|
53
|
+
can_read=self.can_read,
|
|
54
|
+
can_modify=True,
|
|
51
55
|
)
|
|
52
56
|
|
|
53
57
|
self.backo.register_collection(self.users)
|
|
@@ -59,6 +63,12 @@ class TestSelections(unittest.TestCase):
|
|
|
59
63
|
current_user.reinit()
|
|
60
64
|
return super().tearDown()
|
|
61
65
|
|
|
66
|
+
def can_read(self, _right_name, _obj):
|
|
67
|
+
"""
|
|
68
|
+
function for can_read method used in test
|
|
69
|
+
"""
|
|
70
|
+
return self.read
|
|
71
|
+
|
|
62
72
|
def test_all_selection(self):
|
|
63
73
|
"""
|
|
64
74
|
creating an backoffice with ref one to many
|
|
@@ -96,6 +106,22 @@ class TestSelections(unittest.TestCase):
|
|
|
96
106
|
)
|
|
97
107
|
self.assertEqual(rep["total"], 2)
|
|
98
108
|
|
|
109
|
+
def test_selection_cant_read_collection(self):
|
|
110
|
+
"""
|
|
111
|
+
test cannot read the selection
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
self.yml_users.drop()
|
|
115
|
+
|
|
116
|
+
self.read = False
|
|
117
|
+
with self.assertRaises(SRightError) as e:
|
|
118
|
+
self.users._selections["_all"].select()
|
|
119
|
+
self.assertEqual(
|
|
120
|
+
e.exception.to_string(),
|
|
121
|
+
"Execute _all selection is forbidden",
|
|
122
|
+
)
|
|
123
|
+
self.read = True
|
|
124
|
+
|
|
99
125
|
def test_multi_selection(self):
|
|
100
126
|
"""
|
|
101
127
|
test selection with multi projections
|
|
@@ -31,7 +31,7 @@ wheels = [
|
|
|
31
31
|
|
|
32
32
|
[[package]]
|
|
33
33
|
name = "backo"
|
|
34
|
-
version = "0.3.
|
|
34
|
+
version = "0.3.8"
|
|
35
35
|
source = { editable = "." }
|
|
36
36
|
dependencies = [
|
|
37
37
|
{ name = "deepdiff" },
|
|
@@ -77,7 +77,7 @@ requires-dist = [
|
|
|
77
77
|
{ name = "questionary" },
|
|
78
78
|
{ name = "redis", specifier = ">=8.1.0" },
|
|
79
79
|
{ name = "requests" },
|
|
80
|
-
{ name = "stricto", specifier = ">=0.
|
|
80
|
+
{ name = "stricto", specifier = ">=0.3.0" },
|
|
81
81
|
]
|
|
82
82
|
provides-extras = ["mongo"]
|
|
83
83
|
|
|
@@ -1057,11 +1057,11 @@ wheels = [
|
|
|
1057
1057
|
|
|
1058
1058
|
[[package]]
|
|
1059
1059
|
name = "stricto"
|
|
1060
|
-
version = "0.
|
|
1060
|
+
version = "0.3.0"
|
|
1061
1061
|
source = { registry = "https://pypi.org/simple" }
|
|
1062
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1062
|
+
sdist = { url = "https://files.pythonhosted.org/packages/67/4e/66a23d47a8def2c264395253a775a0c284cb6b8bfd680238986ba54352fe/stricto-0.3.0.tar.gz", hash = "sha256:b393b6f5d9543e7ed3c7110d2f23d769591805c7790397cb7711df92e1cc36b6", size = 63704, upload-time = "2026-09-25T09:12:58.34Z" }
|
|
1063
1063
|
wheels = [
|
|
1064
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1064
|
+
{ url = "https://files.pythonhosted.org/packages/25/f5/e36b5fdcbfb309a233d72ed8b10a4ba93a8249638e68710e345daf8aad21/stricto-0.3.0-py3-none-any.whl", hash = "sha256:269417730a166081680cbd420a31eecf46d700c326f0ad164b0bff726c0d0c12", size = 52313, upload-time = "2026-09-25T09:12:57.343Z" },
|
|
1065
1065
|
]
|
|
1066
1066
|
|
|
1067
1067
|
[[package]]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/rest-api/collections_set/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|