backo 0.1.2__tar.gz → 0.2.0__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 (79) hide show
  1. {backo-0.1.2 → backo-0.2.0}/.pylintrc +1 -1
  2. {backo-0.1.2 → backo-0.2.0}/CHANGELOG.md +4 -0
  3. {backo-0.1.2 → backo-0.2.0}/PKG-INFO +3 -3
  4. {backo-0.1.2 → backo-0.2.0}/README.md +1 -1
  5. {backo-0.1.2 → backo-0.2.0}/backo/__init__.py +4 -1
  6. {backo-0.1.2 → backo-0.2.0}/backo/collection.py +5 -3
  7. {backo-0.1.2 → backo-0.2.0}/backo/file/file.py +47 -7
  8. {backo-0.1.2 → backo-0.2.0}/backo/file/file_blob_connector.py +6 -7
  9. {backo-0.1.2 → backo-0.2.0}/backo/file/file_connector.py +8 -2
  10. {backo-0.1.2 → backo-0.2.0}/backo/file/file_system_connector.py +9 -7
  11. {backo-0.1.2 → backo-0.2.0}/backo/item.py +16 -10
  12. {backo-0.1.2 → backo-0.2.0}/backo/meta_data_handler.py +6 -5
  13. {backo-0.1.2 → backo-0.2.0}/backo/patch.py +16 -1
  14. backo-0.2.0/backo/ref.py +411 -0
  15. backo-0.1.2/backo/reference.py → backo-0.2.0/backo/refslist.py +117 -410
  16. {backo-0.1.2 → backo-0.2.0}/pyproject.toml +2 -2
  17. {backo-0.1.2 → backo-0.2.0}/requirements.txt +1 -1
  18. {backo-0.1.2 → backo-0.2.0}/tests/test_file.py +9 -13
  19. {backo-0.1.2 → backo-0.2.0}/tests/test_migrations.py +3 -1
  20. {backo-0.1.2 → backo-0.2.0}/tests/test_reference.py +2 -2
  21. {backo-0.1.2 → backo-0.2.0}/tests/test_routes.py +1 -1
  22. {backo-0.1.2 → backo-0.2.0}/tests/test_selections.py +1 -1
  23. {backo-0.1.2 → backo-0.2.0}/.github/workflows/pylint.yml +0 -0
  24. {backo-0.1.2 → backo-0.2.0}/.github/workflows/release.yml +0 -0
  25. {backo-0.1.2 → backo-0.2.0}/.github/workflows/test.yml +0 -0
  26. {backo-0.1.2 → backo-0.2.0}/.gitignore +0 -0
  27. {backo-0.1.2 → backo-0.2.0}/.readthedocs.yaml +0 -0
  28. {backo-0.1.2 → backo-0.2.0}/CONTRIBUTING.md +0 -0
  29. {backo-0.1.2 → backo-0.2.0}/LICENSE +0 -0
  30. {backo-0.1.2 → backo-0.2.0}/backo/action.py +0 -0
  31. {backo-0.1.2 → backo-0.2.0}/backo/api_toolbox.py +0 -0
  32. {backo-0.1.2 → backo-0.2.0}/backo/backoffice.py +0 -0
  33. {backo-0.1.2 → backo-0.2.0}/backo/collection_addon.py +0 -0
  34. {backo-0.1.2 → backo-0.2.0}/backo/current_user.py +0 -0
  35. {backo-0.1.2 → backo-0.2.0}/backo/db_connector.py +0 -0
  36. {backo-0.1.2 → backo-0.2.0}/backo/db_mongo_connector.py +0 -0
  37. {backo-0.1.2 → backo-0.2.0}/backo/db_yml_connector.py +0 -0
  38. {backo-0.1.2 → backo-0.2.0}/backo/error.py +0 -0
  39. {backo-0.1.2 → backo-0.2.0}/backo/file/blob_file.py +0 -0
  40. {backo-0.1.2 → backo-0.2.0}/backo/log.py +0 -0
  41. {backo-0.1.2 → backo-0.2.0}/backo/loop_path.py +0 -0
  42. {backo-0.1.2 → backo-0.2.0}/backo/migration_report.py +0 -0
  43. {backo-0.1.2 → backo-0.2.0}/backo/request_decorators.py +0 -0
  44. {backo-0.1.2 → backo-0.2.0}/backo/selection.py +0 -0
  45. {backo-0.1.2 → backo-0.2.0}/backo/status.py +0 -0
  46. {backo-0.1.2 → backo-0.2.0}/backo/transaction.py +0 -0
  47. {backo-0.1.2 → backo-0.2.0}/backo/view.py +0 -0
  48. {backo-0.1.2 → backo-0.2.0}/docs/Makefile +0 -0
  49. {backo-0.1.2 → backo-0.2.0}/docs/make.bat +0 -0
  50. {backo-0.1.2 → backo-0.2.0}/docs/requirements.txt +0 -0
  51. {backo-0.1.2 → backo-0.2.0}/docs/source/api_reference.rst +0 -0
  52. {backo-0.1.2 → backo-0.2.0}/docs/source/conf.py +0 -0
  53. {backo-0.1.2 → backo-0.2.0}/docs/source/errors.rst +0 -0
  54. {backo-0.1.2 → backo-0.2.0}/docs/source/index.rst +0 -0
  55. {backo-0.1.2 → backo-0.2.0}/docs/source/readme.rst +0 -0
  56. {backo-0.1.2 → backo-0.2.0}/examples/media_library/README.md +0 -0
  57. {backo-0.1.2 → backo-0.2.0}/examples/media_library/__init__.py +0 -0
  58. {backo-0.1.2 → backo-0.2.0}/examples/media_library/backoffice.py +0 -0
  59. {backo-0.1.2 → backo-0.2.0}/examples/media_library/collections_set/__init__.py +0 -0
  60. {backo-0.1.2 → backo-0.2.0}/examples/media_library/collections_set/books.py +0 -0
  61. {backo-0.1.2 → backo-0.2.0}/examples/media_library/collections_set/users.py +0 -0
  62. {backo-0.1.2 → backo-0.2.0}/examples/media_library/migration.py +0 -0
  63. {backo-0.1.2 → backo-0.2.0}/examples/media_library/tests.py +0 -0
  64. {backo-0.1.2 → backo-0.2.0}/examples/nationality/README.md +0 -0
  65. {backo-0.1.2 → backo-0.2.0}/examples/nationality/__init__.py +0 -0
  66. {backo-0.1.2 → backo-0.2.0}/examples/nationality/backoffice.py +0 -0
  67. {backo-0.1.2 → backo-0.2.0}/examples/nationality/collections_set/__init__.py +0 -0
  68. {backo-0.1.2 → backo-0.2.0}/examples/nationality/collections_set/countries.py +0 -0
  69. {backo-0.1.2 → backo-0.2.0}/examples/nationality/collections_set/db_country_connector.py +0 -0
  70. {backo-0.1.2 → backo-0.2.0}/examples/nationality/collections_set/people.py +0 -0
  71. {backo-0.1.2 → backo-0.2.0}/examples/nationality/tests.py +0 -0
  72. {backo-0.1.2 → backo-0.2.0}/tests/__init__.py +0 -0
  73. {backo-0.1.2 → backo-0.2.0}/tests/test_action.py +0 -0
  74. {backo-0.1.2 → backo-0.2.0}/tests/test_api_toolbox.py +0 -0
  75. {backo-0.1.2 → backo-0.2.0}/tests/test_crud.py +0 -0
  76. {backo-0.1.2 → backo-0.2.0}/tests/test_current_user.py +0 -0
  77. {backo-0.1.2 → backo-0.2.0}/tests/test_log.py +0 -0
  78. {backo-0.1.2 → backo-0.2.0}/tests/test_meta.py +0 -0
  79. {backo-0.1.2 → backo-0.2.0}/tests/test_mongo.py +0 -0
@@ -28,7 +28,7 @@ ignore-imports=no
28
28
  [MESSAGES CONTROL]
29
29
  disable =
30
30
  import-error,
31
- missing-class-docstring, # all class docstrin are in __init__ to mix sphinx and vscode
31
+ cyclic-import, # due to Ref and RefsList mutual import
32
32
  wrong-import-position,
33
33
  dangerous-default-value,
34
34
  super-init-not-called, # Bug ?
@@ -5,6 +5,10 @@ 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.2.0] 2026-06-01
9
+ * Internal
10
+ * Refactoring to follow stricto 0.1.x to 0.2.0
11
+
8
12
  ## [0.1.2] 2026-04-29
9
13
  * Feature
10
14
  * Files management with File() / BlobFile() objects & co, with documentations, tests.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: backo
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Back office low code
5
5
  Project-URL: GitHub, https://github.com/bwallrich/backo
6
6
  Project-URL: Homepage, https://github.com/bwallrich/backo
@@ -37,7 +37,7 @@ Requires-Dist: flask
37
37
  Requires-Dist: pyjwt
38
38
  Requires-Dist: python-magic
39
39
  Requires-Dist: pyyaml
40
- Requires-Dist: stricto>=0.1.2
40
+ Requires-Dist: stricto>=0.2.0
41
41
  Description-Content-Type: text/markdown
42
42
 
43
43
 
@@ -1169,7 +1169,7 @@ Ask the backoffice the current meta informations for this collection object.
1169
1169
  "type": ty,
1170
1170
  "type_short" : re.sub(".*\.|\'>", '', ty),
1171
1171
  "description": self.get_as_string(self._description),
1172
- "required": self.get_as_string(self._not_none),
1172
+ "required": self.get_as_string(self._required),
1173
1173
  "in": self.get_as_string(self._union),
1174
1174
  "constraints": self.get_as_string(self._constraints),
1175
1175
  "default": self.get_as_string(self._default),
@@ -1127,7 +1127,7 @@ Ask the backoffice the current meta informations for this collection object.
1127
1127
  "type": ty,
1128
1128
  "type_short" : re.sub(".*\.|\'>", '', ty),
1129
1129
  "description": self.get_as_string(self._description),
1130
- "required": self.get_as_string(self._not_none),
1130
+ "required": self.get_as_string(self._required),
1131
1131
  "in": self.get_as_string(self._union),
1132
1132
  "constraints": self.get_as_string(self._constraints),
1133
1133
  "default": self.get_as_string(self._default),
@@ -27,7 +27,10 @@ from .backoffice import Backoffice
27
27
  from .collection import Collection
28
28
  from .selection import Selection
29
29
  from .log import Logger, log_system, LogLevel, stack
30
- from .reference import Ref, RefsList, FillStrategy, DeleteStrategy
30
+
31
+ # from .reference import Ref, RefsList, FillStrategy, DeleteStrategy
32
+ from .refslist import RefsList, FillStrategy, DeleteStrategy
33
+ from .ref import Ref
31
34
  from .file.file import File
32
35
  from .file.file_connector import FileConnector
33
36
  from .file.file_system_connector import FileSystemConnector
@@ -365,7 +365,8 @@ class Collection:
365
365
 
366
366
  # Check if the object match the model despite the fact there is no change
367
367
  o = self.new_item()
368
- print(o)
368
+ print(f">> {o}")
369
+ print(f"<< {new_obj}")
369
370
  o.set(new_obj)
370
371
 
371
372
  return None
@@ -877,7 +878,7 @@ class Collection:
877
878
 
878
879
  # Create a partial object with values given in request.item
879
880
  obj = self.new_item()
880
- obj.set_value_without_checks(c.item.get_value())
881
+ obj.set_value(c.item.get_value())
881
882
  obj.enable_permissions()
882
883
  sub_object = obj.select(c.path)
883
884
  if sub_object is None:
@@ -916,7 +917,7 @@ class Collection:
916
917
 
917
918
  # Create a partial object with values given in request.item
918
919
  obj = self.new_item()
919
- obj.set_value_without_checks(c.get_value())
920
+ obj.set_value(c.get_value())
920
921
  obj.enable_permissions()
921
922
 
922
923
  return (json.dumps(obj.get_current_meta()), 200)
@@ -952,6 +953,7 @@ class Collection:
952
953
  # apply patches
953
954
  for p in patch_list:
954
955
  patch = Patch()
956
+ print(f"patch {p}")
955
957
  patch.set(p)
956
958
  obj.patch(patch.op, patch.path, patch.value)
957
959
 
@@ -46,7 +46,6 @@ def transform_to_filestorage( # pylint: disable=unused-argument
46
46
  :rtype: FileStorage
47
47
  """
48
48
  if v is None:
49
- print("transform None")
50
49
  return None
51
50
 
52
51
  if isinstance(v, File):
@@ -106,11 +105,17 @@ KPARSE_MODEL = {
106
105
 
107
106
 
108
107
  class File(Dict):
108
+ """
109
+ Object to manage files.
110
+ inherite from https://stricto.readthedocs.io/en/latest/api_reference.html#stricto.Dict
111
+
112
+
113
+ :param Dict: Dict
114
+ :type Dict: https://stricto.readthedocs.io/en/latest/api_reference.html#stricto.Dict
115
+ """
109
116
 
110
117
  def __init__(self, **kwargs):
111
118
  """
112
- Object to manage files.
113
- inherite from https://stricto.readthedocs.io/en/latest/api_reference.html#stricto.Dict
114
119
 
115
120
 
116
121
  :param buffer_size=: The buffer size to read chunk on this file
@@ -268,13 +273,40 @@ class File(Dict):
268
273
  transform_to_filestorage(content, None)
269
274
  )
270
275
 
271
- def set_value_without_checks(self, value):
276
+ def set_value(self, value: Any) -> bool:
277
+ """Set the value of the file.
278
+
279
+ If the value is a dict, handle it as file structure. if a str|bytes|FileStorage,
280
+ set the content and fill meta datas in the structure.
272
281
 
282
+ :param value: The file Structure or content
283
+ :type value: Any
284
+ :return: True if modifications or False if nothing changed.
285
+ :rtype: bool
286
+ """
273
287
  if isinstance(value, (FileStorage, str, bytes)):
288
+ old_content = None
289
+ if self.has_file() is True:
290
+ old_content = self.get_content()
274
291
  self.set_content(value)
275
- return
276
- super().set_value_without_checks(value)
277
- return
292
+ if self.get_content() == old_content:
293
+ return False
294
+ return True
295
+
296
+ return super().set_value(value)
297
+
298
+ def compute_value(self) -> bool:
299
+ """compute the value if needed
300
+
301
+ :return: True if changed
302
+ :rtype: bool
303
+ """
304
+
305
+ if callable(self._auto_set):
306
+ value = self._auto_set(self.get_root())
307
+ return self.set_value(value)
308
+
309
+ return False
278
310
 
279
311
  def delete_content(self) -> None:
280
312
  """Delete the file"""
@@ -407,6 +439,8 @@ class File(Dict):
407
439
  size = value.size.get_value()
408
440
  if isinstance(value, FileStorage):
409
441
  size = value.content_length
442
+ if isinstance(value, dict):
443
+ size = value.get("size")
410
444
 
411
445
  if size is not None and size > self._max_size:
412
446
  raise SConstraintError(
@@ -417,11 +451,17 @@ class File(Dict):
417
451
 
418
452
  # check the content_type
419
453
  if self._authorized_mime_types is not None:
454
+ print(
455
+ f"File check constraint mime types {type(value)} {self._authorized_mime_types}"
456
+ )
457
+
420
458
  mt = None
421
459
  if isinstance(value, File):
422
460
  mt = value.content_type.get_value()
423
461
  if isinstance(value, FileStorage):
424
462
  mt = value.content_type
463
+ if isinstance(value, dict):
464
+ mt = value.get("content_type")
425
465
 
426
466
  if mt not in self._authorized_mime_types:
427
467
  raise SConstraintError(
@@ -17,16 +17,15 @@ log = log_system.get_or_create_logger("file", LogLevel.DEBUG)
17
17
 
18
18
 
19
19
  class FileBlobConnector(FileConnector): # pylint: disable=too-many-instance-attributes
20
+ """
21
+ File connector in memory
22
+ This is the way to save files just in memory.
23
+
24
+ """
20
25
 
21
26
  @validation_parameters
22
27
  def __init__(self, **kwargs):
23
- """
24
- File connector in memory
25
-
26
- This is the way to save files just in memory.
27
-
28
-
29
- """
28
+ """ """
30
29
 
31
30
  # The array of bytes
32
31
  self._blobs = {}
@@ -19,13 +19,19 @@ KPARSE_MODEL = {
19
19
 
20
20
 
21
21
  class FileConnector: # pylint: disable=too-many-instance-attributes
22
+ """
23
+ Abstract Class fir File connector.
24
+ this is the family (parent) object for files connector
25
+ """
22
26
 
23
27
  @validation_parameters
24
28
  def __init__(self, **kwargs):
25
29
  """
26
- File connector main object.
30
+ :param kwargs: arguments as kwargs for the FileConnector
31
+ :type kwargs: object
27
32
 
28
- this is the family (parent) object for files connector
33
+ :keyword buffer_size: The biffer size (default = 0812)
34
+ :type buffer_size: int
29
35
  """
30
36
 
31
37
  options = Kparse(kwargs, KPARSE_MODEL, strict=True)
@@ -29,18 +29,20 @@ KPARSE_MODEL = {
29
29
  class FileSystemConnector(
30
30
  FileConnector
31
31
  ): # pylint: disable=too-many-instance-attributes
32
+ """
33
+ File connector on a file system
34
+
35
+ This is the way to save / store / retrieve objects
36
+ """
32
37
 
33
38
  @validation_parameters
34
39
  def __init__(self, **kwargs):
35
40
  """
36
- File connector on a file system
37
-
38
- This is the way to save / store / retrieve objects
39
-
40
-
41
- :param path=: The directory to store files
42
- :type path=: str
41
+ :param kwargs: arguments as kwargs for the FileSystemConnector
42
+ :type kwargs: object
43
43
 
44
+ :keyword path: The path to store files
45
+ :type path: str
44
46
  """
45
47
 
46
48
  options = Kparse(kwargs, KPARSE_MODEL)
@@ -21,7 +21,14 @@ log = log_system.get_or_create_logger("Item")
21
21
  # used for developpement
22
22
  sys.path.insert(1, "../../stricto")
23
23
 
24
- from stricto import Dict, String, SRightError, Kparse, validation_parameters
24
+ from stricto import (
25
+ Dict,
26
+ String,
27
+ SRightError,
28
+ Kparse,
29
+ EVENT_MANAGER,
30
+ validation_parameters,
31
+ )
25
32
 
26
33
  KPARSE_MODEL = {
27
34
  "meta_data_handler": {
@@ -83,9 +90,7 @@ class Item(Dict): # pylint: disable=too-many-instance-attributes
83
90
  Dict.__init__(self, schema, **kwargs)
84
91
 
85
92
  # Append then change event to the item
86
- if "change" not in self._events:
87
- self._events["change"] = []
88
- self._events["change"].append(self.on_change)
93
+ EVENT_MANAGER.register_event(self, "change", self.on_change)
89
94
 
90
95
  # adding _id to the model
91
96
  self.add_to_model("_id", String(can_modify=False))
@@ -192,7 +197,7 @@ class Item(Dict): # pylint: disable=too-many-instance-attributes
192
197
  # if kwargs.get("m_path") is None:
193
198
  # kwargs["m_path"] = []
194
199
 
195
- self.trigg("loaded", id(self), **kwargs)
200
+ self.trigg("loaded", **kwargs)
196
201
 
197
202
  # print(f"Load {int(datetime.timestamp(datetime.now()))}", self)
198
203
 
@@ -227,7 +232,7 @@ class Item(Dict): # pylint: disable=too-many-instance-attributes
227
232
  # if kwargs.get("m_path") is None:
228
233
  # kwargs["m_path"] = []
229
234
 
230
- self.trigg("loaded", id(self), **kwargs)
235
+ self.trigg("loaded", **kwargs)
231
236
 
232
237
  def save(self, **kwargs) -> None:
233
238
  """
@@ -275,7 +280,7 @@ class Item(Dict): # pylint: disable=too-many-instance-attributes
275
280
  self.__dict__["_loaded_object"] = a
276
281
 
277
282
  kwargs["old_object"] = self.__dict__["_loaded_object"]
278
- self.trigg("before_save", id(self), **kwargs)
283
+ self.trigg("before_save", **kwargs)
279
284
 
280
285
  # print(f"Save {int(datetime.timestamp(datetime.now()))}", self)
281
286
  dict_to_save = self.get_view("save").get_encoded()
@@ -295,7 +300,7 @@ class Item(Dict): # pylint: disable=too-many-instance-attributes
295
300
  self.__dict__["_loaded_object"].get_value(),
296
301
  )
297
302
 
298
- self.trigg("saved", id(self), **kwargs)
303
+ self.trigg("saved", **kwargs)
299
304
 
300
305
  def delete(self, **kwargs) -> None:
301
306
  """
@@ -332,7 +337,7 @@ class Item(Dict): # pylint: disable=too-many-instance-attributes
332
337
  )
333
338
 
334
339
  # Send delete event before deletion to do some stufs
335
- self.trigg("before_delete", id(self), **kwargs)
340
+ self.trigg("before_delete", **kwargs)
336
341
  self.db_handler.delete_by_id(self._id.get_value())
337
342
 
338
343
  log.info(
@@ -437,4 +442,5 @@ class Item(Dict): # pylint: disable=too-many-instance-attributes
437
442
  self._collection.name,
438
443
  self._id,
439
444
  )
440
- self.trigg("created", id(self), **kwargs)
445
+ print(f"Item trigg created {id(self)} {self.get_value()}")
446
+ self.trigg("created", **kwargs)
@@ -64,14 +64,15 @@ class StandardMetaDataHandler(
64
64
  if o._meta.ctime == None: # pylint: disable=singleton-comparison
65
65
  o._meta.ctime = now
66
66
 
67
+ login = current_user.login.copy()
68
+ user_id = current_user._id.copy()
69
+
67
70
  if o._meta.created_by._id == None: # pylint: disable=singleton-comparison
68
- o._meta.created_by._id = current_user._id.copy()
69
- o._meta.created_by.login = current_user.login.copy()
71
+ o._meta.created_by.set({"_id": user_id, "login": login})
70
72
 
71
73
  # Set modificattion time and last updater
72
- o._meta.mtime = now
73
- o._meta.modified_by._id = current_user._id.copy()
74
- o._meta.modified_by.login = current_user.login.copy()
74
+ o._meta.modified_by.set({"_id": user_id, "login": login})
75
+ o._meta.mtime.set(now)
75
76
 
76
77
  # Put permission back
77
78
  if permission_enabled is True:
@@ -14,6 +14,20 @@ sys.path.insert(1, "../../stricto")
14
14
  from stricto import Dict, String
15
15
 
16
16
 
17
+ def must(value, o) -> bool: # pylint: disable=unused-argument
18
+ """check if start with a $.
19
+
20
+ :param value: _description_
21
+ :type value: _type_
22
+ :param o: Not used
23
+ :type o: _tyAnype_
24
+ :return: True if start with a $.
25
+ :rtype: bool
26
+ """
27
+ print(f'MUST "{value}" -> {bool(re.match(r"^\$.*", value))}')
28
+ return bool(re.match(r"^\$.*", value))
29
+
30
+
17
31
  class Patch(Dict): # pylint: disable=too-many-instance-attributes
18
32
  """
19
33
  A Object for patching
@@ -29,7 +43,8 @@ class Patch(Dict): # pylint: disable=too-many-instance-attributes
29
43
  "op": String(require=True, union=["test", "replace", "remove", "add"]),
30
44
  "path": String(
31
45
  require=True,
32
- constraint=lambda value, o: bool(re.match(r"^\$.*", value)),
46
+ # constraint=lambda value, o: bool(re.match(r"^\$.*", value)),
47
+ constraint=must,
33
48
  ),
34
49
  "value": String(),
35
50
  },