elody 0.0.156__py3-none-any.whl → 0.0.158__py3-none-any.whl

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.
elody/csv.py CHANGED
@@ -188,9 +188,7 @@ class CSVMultiObject(CSVParser):
188
188
  mandatory_columns = [
189
189
  v for k, v in self.index_mapping.items() if not k.startswith("?")
190
190
  ]
191
- missing_columns = [
192
- x for x in mandatory_columns if x not in row.keys()
193
- ]
191
+ missing_columns = [x for x in mandatory_columns if x not in row.keys()]
194
192
  if missing_columns:
195
193
  raise ColumnNotFoundException(f"{', '.join(missing_columns)}")
196
194
  lang = self.__determine_language(row)
@@ -10,20 +10,24 @@ class BaseObjectConfiguration(ABC):
10
10
  @abstractmethod
11
11
  def crud(self):
12
12
  return {
13
- "collection": "entities",
14
- "collection_history": "history",
15
13
  "creator": lambda post_body, **kwargs: post_body,
14
+ "document_content_patcher": lambda *, document, content, overwrite=False, **kwargs: self._document_content_patcher(
15
+ document=document,
16
+ content=content,
17
+ overwrite=overwrite,
18
+ **kwargs,
19
+ ),
16
20
  "nested_matcher_builder": lambda object_lists, keys_info, value: self.__build_nested_matcher(
17
21
  object_lists, keys_info, value
18
22
  ),
19
23
  "post_crud_hook": lambda **kwargs: None,
20
- "pre_crud_hook": lambda **kwargs: None,
24
+ "pre_crud_hook": lambda *, document, **kwargs: document,
21
25
  "storage_type": "db",
22
26
  }
23
27
 
24
28
  @abstractmethod
25
29
  def document_info(self):
26
- return {"object_lists": {"metadata": "key", "relations": "type"}}
30
+ return {}
27
31
 
28
32
  @abstractmethod
29
33
  def logging(self, flat_document, **kwargs):
@@ -33,7 +37,9 @@ class BaseObjectConfiguration(ABC):
33
37
  "schema": f"{flat_document.get('schema.type')}:{flat_document.get('schema.version')}",
34
38
  }
35
39
  try:
36
- user_context = kwargs.get("get_user_context")() # pyright: ignore
40
+ from policy_factory import get_user_context # pyright: ignore
41
+
42
+ user_context = get_user_context()
37
43
  info_labels["http_method"] = user_context.bag.get("http_method")
38
44
  info_labels["requested_endpoint"] = user_context.bag.get(
39
45
  "requested_endpoint"
@@ -65,20 +71,50 @@ class BaseObjectConfiguration(ABC):
65
71
 
66
72
  return "function", validator
67
73
 
68
- def _get_merged_post_body(self, post_body, document_defaults, object_list_name):
69
- key = self.document_info()["object_lists"][object_list_name]
70
- post_body[object_list_name] = self.__merge_object_lists(
71
- document_defaults.get(object_list_name, []),
72
- post_body.get(object_list_name, []),
73
- key,
74
+ def _document_content_patcher(
75
+ self, *, document, content, overwrite=False, **kwargs
76
+ ):
77
+ raise NotImplementedError(
78
+ "Provide concrete implementation in child object configuration"
74
79
  )
75
- return post_body
76
80
 
77
- def _sanitize_document(self, document, object_list_name, value_field_name):
78
- object_list = deepcopy(document[object_list_name])
79
- for element in object_list:
80
- if not element[value_field_name]:
81
- document[object_list_name].remove(element)
81
+ def _merge_object_lists(self, source, target, object_list_key):
82
+ for target_item in target:
83
+ for source_item in source:
84
+ if source_item[object_list_key] == target_item[object_list_key]:
85
+ source.remove(source_item)
86
+ return [*source, *target]
87
+
88
+ def _get_user_context_id(self):
89
+ try:
90
+ from policy_factory import get_user_context # pyright: ignore
91
+
92
+ return get_user_context().id
93
+ except Exception:
94
+ return None
95
+
96
+ def _sanitize_document(self, *, document, **kwargs):
97
+ sanitized_document = {}
98
+ document_deepcopy = deepcopy(document)
99
+ for key, value in document_deepcopy.items():
100
+ if isinstance(value, dict):
101
+ sanitized_value = BaseObjectConfiguration._sanitize_document(
102
+ self, document=value
103
+ )
104
+ if sanitized_value:
105
+ sanitized_document[key] = sanitized_value
106
+ elif value:
107
+ sanitized_document[key] = value
108
+ return sanitized_document
109
+
110
+ def _should_create_history_object(self):
111
+ try:
112
+ from policy_factory import get_user_context # pyright: ignore
113
+
114
+ get_user_context()
115
+ return bool(self.crud().get("collection_history"))
116
+ except Exception:
117
+ return False
82
118
 
83
119
  def _sort_document_keys(self, document):
84
120
  def sort_keys(data):
@@ -98,12 +134,13 @@ class BaseObjectConfiguration(ABC):
98
134
  else:
99
135
  return data
100
136
 
101
- for key, value in self.document_info()["object_lists"].items():
137
+ for key, value in self.document_info().get("object_lists", {}).items():
102
138
  if document.get(key):
103
139
  document[key] = sorted(
104
140
  document[key], key=lambda property: property[value]
105
141
  )
106
142
  sort_keys(document)
143
+ return document
107
144
 
108
145
  def __build_nested_matcher(self, object_lists, keys_info, value, index=0):
109
146
  if index == 0 and not any(info["object_list"] for info in keys_info):
@@ -135,10 +172,3 @@ class BaseObjectConfiguration(ABC):
135
172
  return elem_match if index > 0 else {info["key"]: {"$all": [elem_match]}}
136
173
 
137
174
  raise Exception(f"Unable to build nested matcher. See keys_info: {keys_info}")
138
-
139
- def __merge_object_lists(self, source, target, key):
140
- for target_item in target:
141
- for source_item in source:
142
- if source_item[key] == target_item[key]:
143
- source.remove(source_item)
144
- return [*source, *target]
@@ -12,6 +12,8 @@ class ElodyConfiguration(BaseObjectConfiguration):
12
12
 
13
13
  def crud(self):
14
14
  crud = {
15
+ "collection": "entities",
16
+ "collection_history": "history",
15
17
  "creator": lambda post_body, **kwargs: self._creator(post_body, **kwargs),
16
18
  "post_crud_hook": lambda **kwargs: self._post_crud_hook(**kwargs),
17
19
  "pre_crud_hook": lambda **kwargs: self._pre_crud_hook(**kwargs),
@@ -37,7 +39,6 @@ class ElodyConfiguration(BaseObjectConfiguration):
37
39
  self,
38
40
  post_body,
39
41
  *,
40
- get_user_context,
41
42
  flat_post_body={},
42
43
  document_defaults={},
43
44
  ):
@@ -65,36 +66,79 @@ class ElodyConfiguration(BaseObjectConfiguration):
65
66
  "relations": [],
66
67
  "schema": {"type": self.SCHEMA_TYPE, "version": self.SCHEMA_VERSION},
67
68
  }
68
- if email := self.__get_email(get_user_context):
69
- template["computed_values"]["created_by"] = email
70
-
71
- for key in self.document_info()["object_lists"].keys():
72
- post_body = self._get_merged_post_body(post_body, document_defaults, key)
69
+ if user_context_id := self._get_user_context_id():
70
+ template["computed_values"]["created_by"] = user_context_id
71
+
72
+ for key, object_list_key in self.document_info()["object_lists"].items():
73
+ if not key.startswith("lookup.virtual_relations"):
74
+ post_body[key] = self._merge_object_lists(
75
+ document_defaults.get(key, []),
76
+ post_body.get(key, []),
77
+ object_list_key,
78
+ )
73
79
  document = {**template, **document_defaults, **post_body}
74
80
 
75
- self._sanitize_document(document, "metadata", "value")
76
- self._sort_document_keys(document)
81
+ document = self._sanitize_document(
82
+ document=document,
83
+ object_list_name="metadata",
84
+ object_list_value_field_name="value",
85
+ )
86
+ document = self._sort_document_keys(document)
87
+ return document
88
+
89
+ def _document_content_patcher(
90
+ self, *, document, content, overwrite=False, **kwargs
91
+ ):
92
+ object_lists = self.document_info().get("object_lists", {})
93
+ if overwrite:
94
+ document = content
95
+ else:
96
+ for key, value in content.items():
97
+ if key in object_lists:
98
+ if key != "relations":
99
+ for value_element in value:
100
+ for item_element in document[key]:
101
+ if (
102
+ item_element[object_lists[key]]
103
+ == value_element[object_lists[key]]
104
+ ):
105
+ document[key].remove(item_element)
106
+ break
107
+ document[key].extend(value)
108
+ else:
109
+ document[key] = value
110
+
77
111
  return document
78
112
 
79
- def _post_crud_hook(self, **_):
113
+ def _post_crud_hook(self, **kwargs):
80
114
  pass
81
115
 
82
- def _pre_crud_hook(self, *, crud, document={}, get_user_context=None, **_):
116
+ def _pre_crud_hook(self, *, crud, document={}, **kwargs):
83
117
  if document:
84
- self._sanitize_document(document, "metadata", "value")
85
- self.__patch_document_computed_values(
86
- crud, document, get_user_context=get_user_context
118
+ document = self._sanitize_document(
119
+ document=document,
120
+ object_list_name="metadata",
121
+ object_list_value_field_name="value",
87
122
  )
88
- self._sort_document_keys(document)
89
-
90
- def __get_email(self, get_user_context):
91
- try:
92
- return get_user_context().email
93
- except Exception:
94
- return None
123
+ document = self.__patch_document_computed_values(crud, document)
124
+ document = self._sort_document_keys(document)
125
+ return document
95
126
 
96
- def __patch_document_computed_values(self, crud, document, **kwargs):
127
+ def _sanitize_document(
128
+ self, *, document, object_list_name, object_list_value_field_name, **kwargs
129
+ ):
130
+ sanitized_document = super()._sanitize_document(document=document)
131
+ object_list = document[object_list_name]
132
+ for element in object_list:
133
+ if not element[object_list_value_field_name]:
134
+ sanitized_document[object_list_name].remove(element)
135
+ return sanitized_document
136
+
137
+ def __patch_document_computed_values(self, crud, document):
138
+ if not document.get("computed_values"):
139
+ document["computed_values"] = {}
97
140
  document["computed_values"].update({"event": crud})
98
141
  document["computed_values"].update({"modified_at": datetime.now(timezone.utc)})
99
- if email := self.__get_email(kwargs.get("get_user_context")):
142
+ if email := self._get_user_context_id():
100
143
  document["computed_values"].update({"modified_by": email})
144
+ return document
@@ -16,8 +16,8 @@ class JobConfiguration(ElodyConfiguration):
16
16
  def document_info(self):
17
17
  return super().document_info()
18
18
 
19
- def logging(self, flat_item, **kwargs):
20
- return super().logging(flat_item, **kwargs)
19
+ def logging(self, flat_document, **kwargs):
20
+ return super().logging(flat_document, **kwargs)
21
21
 
22
22
  def migration(self):
23
23
  return super().migration()
@@ -28,6 +28,12 @@ class JobConfiguration(ElodyConfiguration):
28
28
  def validation(self):
29
29
  return super().validation()
30
30
 
31
+ def _creator(self, post_body, *, get_user_context={}, **_):
32
+ document = super()._creator(post_body)
33
+ if email := get_user_context().email:
34
+ document["computed_values"]["created_by"] = email
35
+ return document
36
+
31
37
  def _post_crud_hook(self, *, crud, document, get_rabbit, **kwargs):
32
38
  if crud == "create":
33
39
  send_cloudevent(
@@ -21,9 +21,10 @@ def set_permissions(permissions: dict, placeholders: list[str] = []):
21
21
  def get_permissions(role: str, user_context: UserContext):
22
22
  permissions = deepcopy(_permissions)
23
23
 
24
- for placeholder in _placeholders:
24
+ for placeholder_key in _placeholders:
25
+ placeholder_value = user_context.bag.get(placeholder_key.lower())
25
26
  permissions = __replace_permission_placeholders(
26
- permissions, placeholder, user_context.bag[placeholder.lower()]
27
+ permissions, placeholder_key, placeholder_value
27
28
  )
28
29
  return permissions.get(role, {}) # pyright: ignore
29
30
 
@@ -84,8 +85,8 @@ def handle_single_item_request(
84
85
 
85
86
  def mask_protected_content_post_request_hook(user_context: UserContext, permissions):
86
87
  def __post_request_hook(response):
87
- items = response["results"]
88
- for item in items:
88
+ items = []
89
+ for item in response["results"]:
89
90
  try:
90
91
  (
91
92
  item_in_storage_format,
@@ -104,6 +105,7 @@ def mask_protected_content_post_request_hook(user_context: UserContext, permissi
104
105
  "read",
105
106
  object_lists,
106
107
  )
108
+ items.append(user_context.bag["requested_item"])
107
109
  except Exception as exception:
108
110
  log.debug(
109
111
  f"{exception.__class__.__name__}: {str(exception)}",
@@ -111,6 +113,7 @@ def mask_protected_content_post_request_hook(user_context: UserContext, permissi
111
113
  )
112
114
  raise exception
113
115
 
116
+ response["results"] = items
114
117
  return response
115
118
 
116
119
  return __post_request_hook
@@ -122,7 +125,7 @@ def __prepare_item_for_permission_check(item, permissions, crud):
122
125
  return item, None, None, None
123
126
 
124
127
  config = get_object_configuration_mapper().get(item["type"])
125
- object_lists = config.document_info()["object_lists"]
128
+ object_lists = config.document_info().get("object_lists", {})
126
129
  flat_item = flatten_dict(object_lists, item)
127
130
 
128
131
  return (
@@ -188,16 +191,23 @@ def __is_allowed_to_crud_item_keys(
188
191
  if condition_match:
189
192
  if crud == "read":
190
193
  keys_info = interpret_flat_key(restricted_key, object_lists)
191
- element = item_in_storage_format
192
194
  for info in keys_info:
193
195
  if info["object_list"]:
194
196
  element = __get_element_from_object_list_of_item(
195
- element,
197
+ item_in_storage_format,
196
198
  info["key"],
197
199
  info["object_key"],
198
200
  object_lists,
199
201
  )
200
- element[info["key"]] = "[protected content]" # pyright: ignore
202
+ item_in_storage_format[info["key"]].remove(element)
203
+ break
204
+ else:
205
+ try:
206
+ del item_in_storage_format[keys_info[0]["key"]][
207
+ keys_info[1]["key"]
208
+ ]
209
+ except KeyError:
210
+ pass
201
211
  else:
202
212
  if flat_request_body.get(restricted_key):
203
213
  user_context.bag["restricted_keys"].append(restricted_key)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: elody
3
- Version: 0.0.156
3
+ Version: 0.0.158
4
4
  Summary: elody SDK for Python
5
5
  Author-email: Inuits <developers@inuits.eu>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -362,7 +362,7 @@ Requires-Dist: urllib3>=1.26.16
362
362
  Provides-Extra: loader
363
363
  Requires-Dist: APScheduler>=3.10.4; extra == "loader"
364
364
  Requires-Dist: cloudevents>=1.9.0; extra == "loader"
365
- Requires-Dist: inuits-policy-based-auth>=9.6.0; extra == "loader"
365
+ Requires-Dist: inuits-policy-based-auth>=10.0.1; extra == "loader"
366
366
  Requires-Dist: pytz>=2024.1; extra == "loader"
367
367
  Requires-Dist: six>=1.16.0; extra == "loader"
368
368
  Requires-Dist: tzlocal>=5.2; extra == "loader"
@@ -1,7 +1,7 @@
1
1
  __init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  elody/__init__.py,sha256=d0Q6Fn44e7wFfLabDOBxpcJ1DPKWlFunGYDUBmO-4hA,22
3
3
  elody/client.py,sha256=Es0iloHq9aOiMgXfwssEiV1Nqvvd6Z3RfsyoJvIZ48I,8280
4
- elody/csv.py,sha256=AsBOsJRC_9OsYDboqvtkMGDIvvhAgXAyMV_jPMCd-Bw,13245
4
+ elody/csv.py,sha256=Ui9p3N-N9jQFvWTVecuUqqTfiN1HdpGmFGEqQK2g7OY,13215
5
5
  elody/error_codes.py,sha256=OBHvsKLRN5XU1Ro8Y5dwXWPE8zsiTBPwdoMPs-nL2Z4,3906
6
6
  elody/exceptions.py,sha256=5KSw2sPCZz3lDIJX4LiR2iL9n4m4KIil04D1d3X5rd0,968
7
7
  elody/job.py,sha256=QnN6Q45yqRimziqJX9SHrTVFVvky5mAc1WEza9ia8_w,2811
@@ -12,12 +12,12 @@ elody/validator.py,sha256=G7Ya538EJHCFzOxEri2OcFMabfLBCtTKxuf4os_KuNw,260
12
12
  elody/migration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  elody/migration/base_object_migrator.py,sha256=n8uvgGfjEUy60G47RD7Y-oxp1vHLOauwPMDl87LcxtU,436
14
14
  elody/object_configurations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- elody/object_configurations/base_object_configuration.py,sha256=A54UJt9_K9KPpNK9AZ5FfLY_1IthGNyPW9y8y-xbI6c,5641
16
- elody/object_configurations/elody_configuration.py,sha256=P4wxLjBcvVHTcVaak8EgPIg2Rg_gVQkETkfspghvu4I,3522
17
- elody/object_configurations/job_configuration.py,sha256=RffbWIpA8gvf6SD3hcwR_GEXyUO15XIPFqre4QCPIqI,1805
15
+ elody/object_configurations/base_object_configuration.py,sha256=L8K2AW-A3Bo4_Q_jl3S4hnwEGuZo3XmJH5Izditf6CM,6538
16
+ elody/object_configurations/elody_configuration.py,sha256=Bp3OSHBHuyPs-ubIULzl9wqZd3akYD2idh3mhYo7zu0,5339
17
+ elody/object_configurations/job_configuration.py,sha256=simi4TBiZ5cQePlzFHAd-C-khViWN9VUbAOKq9FYwk8,2057
18
18
  elody/policies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  elody/policies/helpers.py,sha256=AV3wtvthJafW6ueEYGxggB5kk5knWTWzh3zq29Y1-ws,1434
20
- elody/policies/permission_handler.py,sha256=lsCAPBlpBUhL3z-AtV3xbwWqG9zv38Uar1C6ArcERmY,9085
20
+ elody/policies/permission_handler.py,sha256=1aXA_xfRxdTfHZY5xRwQqJp5pjSzBrhko4eGRT38WLQ,9505
21
21
  elody/policies/tenant_id_resolver.py,sha256=BIl6lr9AH6Q_aZSsxF24B7ramkIZH-R-8bpLrTvYLtM,13796
22
22
  elody/policies/authentication/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  elody/policies/authentication/base_user_tenant_validation_policy.py,sha256=fxvrB4iG6fehBh6b4XS8AWewtNsCABgSooma5ljjZk4,5144
@@ -37,9 +37,9 @@ tests/__init_.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  tests/data.py,sha256=Q3oxduf-E3m-Z5G_p3fcs8jVy6g10I7zXKL1m94UVMI,2906
38
38
  tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  tests/unit/test_csv.py,sha256=NQaOhehfQ4GuXku0Y1SA8DYjJeqqidbF50zEHAi8RZA,15923
40
- tests/unit/test_utils.py,sha256=V-lKHaJH1Os1td6ZoubRU-5S2TOAepNsE3n-tO93ctQ,8781
41
- elody-0.0.156.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
42
- elody-0.0.156.dist-info/METADATA,sha256=5zqAlM8G8rLhk78qvd55bkt9r-KojjxAZWazXe0tROw,23282
43
- elody-0.0.156.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
44
- elody-0.0.156.dist-info/top_level.txt,sha256=E0mImupLj0KmtUUCXRYEoLDRaSkuiGaOIIseAa0oQ-M,21
45
- elody-0.0.156.dist-info/RECORD,,
40
+ tests/unit/test_utils.py,sha256=g63szcEZyHhCOtrW4BnNbcgVca3oYPIOLjBdIzNwwN0,8784
41
+ elody-0.0.158.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
42
+ elody-0.0.158.dist-info/METADATA,sha256=aGFDMi4tnUeUA1bh3x1xuUgLQEHM5hSl240-xroWCng,23283
43
+ elody-0.0.158.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
44
+ elody-0.0.158.dist-info/top_level.txt,sha256=E0mImupLj0KmtUUCXRYEoLDRaSkuiGaOIIseAa0oQ-M,21
45
+ elody-0.0.158.dist-info/RECORD,,
tests/unit/test_utils.py CHANGED
@@ -11,7 +11,7 @@ from elody.util import (
11
11
  mediafile_is_public,
12
12
  read_json_as_dict,
13
13
  parse_url_unfriendly_string,
14
- CustomJSONEncoder
14
+ CustomJSONEncoder,
15
15
  )
16
16
  from data import mediafile1, mediafile2
17
17
  from datetime import datetime, timezone
@@ -24,12 +24,14 @@ def test_default_method_with_datetime():
24
24
  result = encoder.default(dt)
25
25
  assert result == "2023-10-01T12:00:00+00:00"
26
26
 
27
+
27
28
  def test_default_method_with_naive_datetime():
28
29
  encoder = CustomJSONEncoder()
29
30
  dt = datetime(2023, 10, 1, 12, 0, 0)
30
31
  result = encoder.default(dt)
31
32
  assert result == "2023-10-01T10:00:00+00:00"
32
33
 
34
+
33
35
  def test_encode_method_with_non_datetime():
34
36
  encoder = CustomJSONEncoder()
35
37
  obj = {"key": "value"}