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.
Files changed (126) hide show
  1. {backo-0.3.7 → backo-0.3.8}/CHANGELOG.md +16 -0
  2. {backo-0.3.7 → backo-0.3.8}/PKG-INFO +2 -2
  3. {backo-0.3.7 → backo-0.3.8}/backo/db/mongo.py +18 -2
  4. {backo-0.3.7 → backo-0.3.8}/backo/file/file.py +2 -2
  5. {backo-0.3.7 → backo-0.3.8}/backo/initialisation/init_script.py +50 -19
  6. backo-0.3.8/backo/initialisation/templates/backoffice.pytemplate +29 -0
  7. {backo-0.3.7 → backo-0.3.8}/backo/initialisation/templates/collection.pytemplate +7 -4
  8. backo-0.3.7/backo/initialisation/templates/backoffice.pytemplate → backo-0.3.8/backo/initialisation/templates/main.pytemplate +7 -27
  9. backo-0.3.8/backo/initialisation/templates/migrate.pytemplate +58 -0
  10. {backo-0.3.7 → backo-0.3.8}/backo/item.py +2 -20
  11. {backo-0.3.7 → backo-0.3.8}/backo/meta_data_handler.py +5 -5
  12. {backo-0.3.7 → backo-0.3.8}/backo/patch.py +1 -1
  13. {backo-0.3.7 → backo-0.3.8}/backo/selection.py +1 -0
  14. {backo-0.3.7 → backo-0.3.8}/examples/nationality/collections_set/people.py +2 -2
  15. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/collections_set/users.py +3 -3
  16. {backo-0.3.7 → backo-0.3.8}/pyproject.toml +2 -2
  17. {backo-0.3.7 → backo-0.3.8}/tests/test_file.py +1 -1
  18. {backo-0.3.7 → backo-0.3.8}/tests/test_selections.py +27 -1
  19. {backo-0.3.7 → backo-0.3.8}/uv.lock +5 -5
  20. {backo-0.3.7 → backo-0.3.8}/.coveragerc +0 -0
  21. {backo-0.3.7 → backo-0.3.8}/.github/workflows/pylint.yml +0 -0
  22. {backo-0.3.7 → backo-0.3.8}/.github/workflows/release.yml +0 -0
  23. {backo-0.3.7 → backo-0.3.8}/.github/workflows/test.yml +0 -0
  24. {backo-0.3.7 → backo-0.3.8}/.github/workflows/test_examples.yml +0 -0
  25. {backo-0.3.7 → backo-0.3.8}/.gitignore +0 -0
  26. {backo-0.3.7 → backo-0.3.8}/.pylintrc +0 -0
  27. {backo-0.3.7 → backo-0.3.8}/.readthedocs.yaml +0 -0
  28. {backo-0.3.7 → backo-0.3.8}/CONTRIBUTING.md +0 -0
  29. {backo-0.3.7 → backo-0.3.8}/LICENSE +0 -0
  30. {backo-0.3.7 → backo-0.3.8}/README.md +0 -0
  31. {backo-0.3.7 → backo-0.3.8}/backo/__init__.py +0 -0
  32. {backo-0.3.7 → backo-0.3.8}/backo/action.py +0 -0
  33. {backo-0.3.7 → backo-0.3.8}/backo/api_toolbox.py +0 -0
  34. {backo-0.3.7 → backo-0.3.8}/backo/backoffice.py +0 -0
  35. {backo-0.3.7 → backo-0.3.8}/backo/collection.py +0 -0
  36. {backo-0.3.7 → backo-0.3.8}/backo/collection_addon.py +0 -0
  37. {backo-0.3.7 → backo-0.3.8}/backo/current_user.py +0 -0
  38. {backo-0.3.7 → backo-0.3.8}/backo/db/__init__.py +0 -0
  39. {backo-0.3.7 → backo-0.3.8}/backo/db/backo_redirect.py +0 -0
  40. {backo-0.3.7 → backo-0.3.8}/backo/db/generic/db_handler.py +0 -0
  41. {backo-0.3.7 → backo-0.3.8}/backo/db/generic/filter.py +0 -0
  42. {backo-0.3.7 → backo-0.3.8}/backo/db/generic/interface.py +0 -0
  43. {backo-0.3.7 → backo-0.3.8}/backo/db/generic/transformer.py +0 -0
  44. {backo-0.3.7 → backo-0.3.8}/backo/db/generic/transformer_cache.py +0 -0
  45. {backo-0.3.7 → backo-0.3.8}/backo/db/memory.py +0 -0
  46. {backo-0.3.7 → backo-0.3.8}/backo/db/restfull.py +0 -0
  47. {backo-0.3.7 → backo-0.3.8}/backo/db/sqlite3/__init__.py +0 -0
  48. {backo-0.3.7 → backo-0.3.8}/backo/db/sqlite3/db_handler.py +0 -0
  49. {backo-0.3.7 → backo-0.3.8}/backo/db/sqlite3/filter.py +0 -0
  50. {backo-0.3.7 → backo-0.3.8}/backo/db/sqlite3/pragma.py +0 -0
  51. {backo-0.3.7 → backo-0.3.8}/backo/db/sqlite3/transformers.py +0 -0
  52. {backo-0.3.7 → backo-0.3.8}/backo/db/valkeydb.py +0 -0
  53. {backo-0.3.7 → backo-0.3.8}/backo/db/yml.py +0 -0
  54. {backo-0.3.7 → backo-0.3.8}/backo/db/yml_dir.py +0 -0
  55. {backo-0.3.7 → backo-0.3.8}/backo/error.py +0 -0
  56. {backo-0.3.7 → backo-0.3.8}/backo/file/blob_file.py +0 -0
  57. {backo-0.3.7 → backo-0.3.8}/backo/file/file_blob_connector.py +0 -0
  58. {backo-0.3.7 → backo-0.3.8}/backo/file/file_connector.py +0 -0
  59. {backo-0.3.7 → backo-0.3.8}/backo/file/file_system_connector.py +0 -0
  60. {backo-0.3.7 → backo-0.3.8}/backo/initialisation/templates/__init__.pytemplate +0 -0
  61. {backo-0.3.7 → backo-0.3.8}/backo/log.py +0 -0
  62. {backo-0.3.7 → backo-0.3.8}/backo/loop_path.py +0 -0
  63. {backo-0.3.7 → backo-0.3.8}/backo/migration.py +0 -0
  64. {backo-0.3.7 → backo-0.3.8}/backo/openapi.py +0 -0
  65. {backo-0.3.7 → backo-0.3.8}/backo/ref.py +0 -0
  66. {backo-0.3.7 → backo-0.3.8}/backo/refs_strategies.py +0 -0
  67. {backo-0.3.7 → backo-0.3.8}/backo/refslist.py +0 -0
  68. {backo-0.3.7 → backo-0.3.8}/backo/request_decorators.py +0 -0
  69. {backo-0.3.7 → backo-0.3.8}/backo/sort.py +0 -0
  70. {backo-0.3.7 → backo-0.3.8}/backo/status.py +0 -0
  71. {backo-0.3.7 → backo-0.3.8}/backo/transaction.py +0 -0
  72. {backo-0.3.7 → backo-0.3.8}/docs/Makefile +0 -0
  73. {backo-0.3.7 → backo-0.3.8}/docs/make.bat +0 -0
  74. {backo-0.3.7 → backo-0.3.8}/docs/requirements.txt +0 -0
  75. {backo-0.3.7 → backo-0.3.8}/docs/source/DB_management.md +0 -0
  76. {backo-0.3.7 → backo-0.3.8}/docs/source/api_reference.rst +0 -0
  77. {backo-0.3.7 → backo-0.3.8}/docs/source/conf.py +0 -0
  78. {backo-0.3.7 → backo-0.3.8}/docs/source/errors.rst +0 -0
  79. {backo-0.3.7 → backo-0.3.8}/docs/source/index.rst +0 -0
  80. {backo-0.3.7 → backo-0.3.8}/docs/source/readme.rst +0 -0
  81. {backo-0.3.7 → backo-0.3.8}/examples/media_library/README.md +0 -0
  82. {backo-0.3.7 → backo-0.3.8}/examples/media_library/__init__.py +0 -0
  83. {backo-0.3.7 → backo-0.3.8}/examples/media_library/backoffice.py +0 -0
  84. {backo-0.3.7 → backo-0.3.8}/examples/media_library/collections_set/__init__.py +0 -0
  85. {backo-0.3.7 → backo-0.3.8}/examples/media_library/collections_set/books.py +0 -0
  86. {backo-0.3.7 → backo-0.3.8}/examples/media_library/collections_set/users.py +0 -0
  87. {backo-0.3.7 → backo-0.3.8}/examples/media_library/migration.py +0 -0
  88. {backo-0.3.7 → backo-0.3.8}/examples/media_library/tests.py +0 -0
  89. {backo-0.3.7 → backo-0.3.8}/examples/nationality/README.md +0 -0
  90. {backo-0.3.7 → backo-0.3.8}/examples/nationality/__init__.py +0 -0
  91. {backo-0.3.7 → backo-0.3.8}/examples/nationality/backoffice.py +0 -0
  92. {backo-0.3.7 → backo-0.3.8}/examples/nationality/collections_set/__init__.py +0 -0
  93. {backo-0.3.7 → backo-0.3.8}/examples/nationality/collections_set/countries.py +0 -0
  94. {backo-0.3.7 → backo-0.3.8}/examples/nationality/collections_set/db_country_connector.py +0 -0
  95. {backo-0.3.7 → backo-0.3.8}/examples/nationality/tests.py +0 -0
  96. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/README.md +0 -0
  97. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/__init__.py +0 -0
  98. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/backoffice.py +0 -0
  99. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/collections_set/__init__.py +0 -0
  100. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/collections_set/vms.py +0 -0
  101. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/collections_set/vms_connector.py +0 -0
  102. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/config.yaml +0 -0
  103. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/constants.py +0 -0
  104. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/requirements.txt +0 -0
  105. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/rest-api/README.md +0 -0
  106. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/rest-api/collections_set/__init__.py +0 -0
  107. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/rest-api/collections_set/vms.py +0 -0
  108. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/rest-api/config.yaml +0 -0
  109. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/rest-api/constants.py +0 -0
  110. {backo-0.3.7 → backo-0.3.8}/examples/rest_api_connector/rest-api/rest_api.py +0 -0
  111. {backo-0.3.7 → backo-0.3.8}/requirements.txt +0 -0
  112. {backo-0.3.7 → backo-0.3.8}/tests/__init__.py +0 -0
  113. {backo-0.3.7 → backo-0.3.8}/tests/test_action.py +0 -0
  114. {backo-0.3.7 → backo-0.3.8}/tests/test_api_toolbox.py +0 -0
  115. {backo-0.3.7 → backo-0.3.8}/tests/test_crud.py +0 -0
  116. {backo-0.3.7 → backo-0.3.8}/tests/test_current_user.py +0 -0
  117. {backo-0.3.7 → backo-0.3.8}/tests/test_db_connector.py +0 -0
  118. {backo-0.3.7 → backo-0.3.8}/tests/test_db_connector_cache.py +0 -0
  119. {backo-0.3.7 → backo-0.3.8}/tests/test_log.py +0 -0
  120. {backo-0.3.7 → backo-0.3.8}/tests/test_meta.py +0 -0
  121. {backo-0.3.7 → backo-0.3.8}/tests/test_migrations.py +0 -0
  122. {backo-0.3.7 → backo-0.3.8}/tests/test_mongo.py +0 -0
  123. {backo-0.3.7 → backo-0.3.8}/tests/test_openapi.py +0 -0
  124. {backo-0.3.7 → backo-0.3.8}/tests/test_reference.py +0 -0
  125. {backo-0.3.7 → backo-0.3.8}/tests/test_rest_api_connector.py +0 -0
  126. {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.7
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.2.11
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, o: dict) -> str: # pylint: disable=unused-argument
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
- return "666"
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, o: 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, description="Name of the field", regexp=r"[A-z_-]+"
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(default=False, description="is the field required"),
266
- "default": String(description="a default value", can_read=args.expert),
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, description="Name of the selection", regexp=r"[A-z_-]+"
283
+ require=True,
284
+ description="Name of the selection",
285
+ regexp=r"[A-z_-]+",
286
+ default="",
275
287
  ),
276
288
  "paths": List(
277
- String(required=True, description="path", regexp=r"^\$\..+"),
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, description="Name of the action", regexp=r"[A-z_-]+"
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, description="name of the function", regexp=r"[A-z_-]+"
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, description="Name of the collection", regexp=r"[A-z_-]+"
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, description="Name of the application", regexp=r"[A-z_-]+"
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
- filename = os.path.join(destination_collection_directory, "__init__.py")
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("Now you can start the backoffice :", style="fg:darkred")
486
- questionary.print(f"python {backo_filename}")
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
- connector = DBMongoConnector(
43
- mongodb://localhost:27017/{{ app_name }}", "{{ name }}"
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
- # Backoffice creation
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.disable_permissions()
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 = now
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.set({"_id": user_id, "login": login})
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.set({"_id": user_id, "login": login})
75
- o._meta.mtime.set(now)
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="Modifyied by",
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, o) -> bool: # pylint: disable=unused-argument
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.7"
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.2.11"
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, o) -> bool:
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.7"
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.2.11" },
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.2.11"
1060
+ version = "0.3.0"
1061
1061
  source = { registry = "https://pypi.org/simple" }
1062
- sdist = { url = "https://files.pythonhosted.org/packages/bd/42/a498ba8dece692bd81532e92ceed91b24effbc12d9e5cf02daad69f1ea78/stricto-0.2.11.tar.gz", hash = "sha256:c085063a3e31aa29d12ab32002f0eb2666eaf3382da94fe2a4978d324e954c77", size = 63504, upload-time = "2026-09-23T09:04:27.021Z" }
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/6c/10/95456e60f411a19b734ed8013d4d5acdeb9e093403ee3a794db4b898dc88/stricto-0.2.11-py3-none-any.whl", hash = "sha256:71ccd471368e54c32b411f2693f8c7ee468abc4561e37c7da80ba60582b11f2d", size = 52220, upload-time = "2026-09-23T09:04:25.774Z" },
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