elody 0.0.63__py3-none-any.whl → 0.0.162__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.
Files changed (41) hide show
  1. elody/client.py +70 -23
  2. elody/csv.py +118 -21
  3. elody/error_codes.py +112 -0
  4. elody/exceptions.py +14 -0
  5. elody/job.py +95 -0
  6. elody/loader.py +33 -5
  7. elody/migration/__init__.py +0 -0
  8. elody/migration/base_object_migrator.py +18 -0
  9. elody/object_configurations/__init__.py +0 -0
  10. elody/object_configurations/base_object_configuration.py +174 -0
  11. elody/object_configurations/elody_configuration.py +144 -0
  12. elody/object_configurations/job_configuration.py +65 -0
  13. elody/policies/authentication/base_user_tenant_validation_policy.py +48 -15
  14. elody/policies/authorization/filter_generic_objects_policy.py +68 -22
  15. elody/policies/authorization/filter_generic_objects_policy_v2.py +166 -0
  16. elody/policies/authorization/generic_object_detail_policy.py +10 -27
  17. elody/policies/authorization/generic_object_mediafiles_policy.py +82 -0
  18. elody/policies/authorization/generic_object_metadata_policy.py +8 -27
  19. elody/policies/authorization/generic_object_relations_policy.py +12 -29
  20. elody/policies/authorization/generic_object_request_policy.py +56 -55
  21. elody/policies/authorization/generic_object_request_policy_v2.py +133 -0
  22. elody/policies/authorization/mediafile_derivatives_policy.py +92 -0
  23. elody/policies/authorization/mediafile_download_policy.py +71 -0
  24. elody/policies/authorization/multi_tenant_policy.py +14 -6
  25. elody/policies/authorization/tenant_request_policy.py +3 -1
  26. elody/policies/helpers.py +37 -0
  27. elody/policies/permission_handler.py +217 -199
  28. elody/policies/tenant_id_resolver.py +375 -0
  29. elody/schemas.py +0 -3
  30. elody/util.py +165 -11
  31. {elody-0.0.63.dist-info → elody-0.0.162.dist-info}/METADATA +16 -11
  32. elody-0.0.162.dist-info/RECORD +47 -0
  33. {elody-0.0.63.dist-info → elody-0.0.162.dist-info}/WHEEL +1 -1
  34. {elody-0.0.63.dist-info → elody-0.0.162.dist-info}/top_level.txt +1 -0
  35. tests/__init_.py +0 -0
  36. tests/data.py +74 -0
  37. tests/unit/__init__.py +0 -0
  38. tests/unit/test_csv.py +410 -0
  39. tests/unit/test_utils.py +293 -0
  40. elody-0.0.63.dist-info/RECORD +0 -27
  41. {elody-0.0.63.dist-info → elody-0.0.162.dist-info}/LICENSE +0 -0
@@ -0,0 +1,375 @@
1
+ import re as regex
2
+
3
+ from elody.error_codes import ErrorCode, get_error_code, get_read, get_write
4
+ from flask import Request
5
+ from flask_restful import abort
6
+ from storage.storagemanager import StorageManager # pyright: ignore
7
+ from elody.util import get_item_metadata_value
8
+ from elody.exceptions import NotFoundException, NoTenantException
9
+
10
+
11
+ class TenantIdResolver:
12
+ def resolve(self, request, user):
13
+ endpoints = [
14
+ EntityGetRequest,
15
+ EntityPostRequest,
16
+ EntityDetailGetRequest,
17
+ EntityDetailUpdateRequest,
18
+ EntityDetailDeleteRequest,
19
+ EntityDetailGetRelationsRequest,
20
+ EntityDetailUpdateRelationsRequest,
21
+ EntityDetailCreateRelationsRequest,
22
+ EntityDetailDeleteRelationsRequest,
23
+ EntityDetailGetMetadataRequest,
24
+ EntityDetailUpdateMetadataRequest,
25
+ EntityDetailCreateMetadataRequest,
26
+ EntityDetailGetMediafilesRequest,
27
+ EntityDetailCreateMediafilesRequest,
28
+ MediafileGetRequest,
29
+ MediafilePostRequest,
30
+ MediafileDetailGetRequest,
31
+ MediafileDetailUpdateRequest,
32
+ MediafileDetailDeleteRequest,
33
+ MediafileDetailGetDerivativesRequest,
34
+ MediafileDetailCreateDerivativesRequest,
35
+ MediafileDetailDeleteDerivativesRequest,
36
+ ]
37
+
38
+ for endpoint in endpoints:
39
+ tenant_id = endpoint().get_tenant_id(request)
40
+ if tenant_id != None:
41
+ relations = user.get("relations", [])
42
+ if len(relations) < 1:
43
+ raise NoTenantException(f"User is not attached to a tenant")
44
+ has_tenant_relation = any(
45
+ relation.get("type") == "hasTenant"
46
+ and relation.get("key") == tenant_id
47
+ for relation in relations
48
+ )
49
+ if has_tenant_relation:
50
+ return tenant_id
51
+ elif len(relations) == 1 and relations[0].get("key") == "tenant:super":
52
+ return "tenant:super"
53
+ else:
54
+ return relations[0].get("key")
55
+ return "tenant:super"
56
+
57
+
58
+ class BaseRequest:
59
+ def __init__(self) -> None:
60
+ self.storage = StorageManager().get_db_engine()
61
+ self.super_tenant_id = "tenant:super"
62
+ # TODO refactor this in a more generic way
63
+ self.global_types = [
64
+ "language",
65
+ "type",
66
+ "collectionForm",
67
+ "institution",
68
+ "tag",
69
+ "triple",
70
+ "person",
71
+ "externalRecord",
72
+ "verzameling",
73
+ "arches_record",
74
+ "manufacturer",
75
+ "photographer",
76
+ "creator",
77
+ "assetPart",
78
+ "set",
79
+ "download",
80
+ "license",
81
+ "share_link"
82
+ # TODO Mediafile should have a link to an asset
83
+ "mediafile",
84
+ "savedSearch",
85
+ "original_data",
86
+ "user",
87
+ ]
88
+
89
+ def _get_tenant_id_from_entity(self, entity_id):
90
+ entity_relations = self.storage.get_collection_item_relations(
91
+ "entities", entity_id
92
+ )
93
+ entity = self.storage.get_item_from_collection_by_id("entities", entity_id)
94
+ if not entity:
95
+ abort(
96
+ 404,
97
+ message=f"{get_error_code(ErrorCode.ITEM_NOT_FOUND, get_read())} | id:{entity_id} - Item with id {entity_id} doesn't exist",
98
+ )
99
+ type = entity.get("type")
100
+ if type in self.global_types:
101
+ return "tenant:super"
102
+ if entity_relations:
103
+ for entity_relation in entity_relations:
104
+ if entity_relation.get("type") == "isIn":
105
+ tenant = self.storage.get_item_from_collection_by_id(
106
+ "entities", entity_relation.get("key")
107
+ )
108
+ return tenant["_id"]
109
+ if entity_relation.get("type") == "hasInstitution":
110
+ instution_relations = self.storage.get_collection_item_relations(
111
+ "entities", entity_relation.get("key")
112
+ )
113
+ for institution_relation in instution_relations:
114
+ if institution_relation.get("type") == "defines":
115
+ return institution_relation.get("key")
116
+
117
+ if entity and get_item_metadata_value(entity, "institution"):
118
+ return f"tenant:{get_item_metadata_value(entity, 'institution')}"
119
+ abort(
120
+ 400,
121
+ message=f"{get_error_code(ErrorCode.ENTITY_HAS_NO_TENANT, get_read())} - Entity has no tenant, and is suppose to have one.",
122
+ )
123
+
124
+ def _get_tenant_id_from_mediafile(self, mediafile_id):
125
+ mediafile_relations = self.storage.get_collection_item_relations(
126
+ "mediafiles", mediafile_id
127
+ )
128
+
129
+ for relation in mediafile_relations:
130
+ if relation.get("type") == "belongsTo":
131
+ return self._get_tenant_id_from_entity(relation.get("key"))
132
+
133
+ # TODO Mediafile should have a link to an asset
134
+ def _get_tenant_id_from_body(self, item, soft_call=False):
135
+ if soft_call:
136
+ return "tenant:super"
137
+ if item.get("type") in self.global_types or item.get("type") == "institution":
138
+ return "tenant:super"
139
+ if item.get("type") == "asset":
140
+ for relation in item.get("relations", []):
141
+ if relation.get("type") in [
142
+ "hasArchesLink",
143
+ "hasAdlibLink",
144
+ "hasBrocadeLink",
145
+ ]:
146
+ return "tenant:super"
147
+ institution_id = get_item_metadata_value(item, "institution")
148
+ if not institution_id:
149
+ for relation in item.get("relations", []):
150
+ if relation.get("type") == "hasInstitution":
151
+ institution_id = relation.get("key")
152
+ if institution_id:
153
+ return f"tenant:{institution_id}"
154
+ abort(
155
+ 400,
156
+ message=f"{get_error_code(ErrorCode.ENTITY_HAS_NO_TENANT, get_read())} - Item in body doesn't have an institution.",
157
+ )
158
+
159
+
160
+ class EntityGetRequest(BaseRequest):
161
+ def get_tenant_id(self, request: Request) -> str | None:
162
+ if (
163
+ regex.match(r"^/entities(?:\?(.*))?$", request.path)
164
+ and request.method == "GET"
165
+ ):
166
+ return self.super_tenant_id
167
+ return None
168
+
169
+
170
+ class EntityPostRequest(BaseRequest):
171
+ def get_tenant_id(self, request: Request) -> str | None:
172
+ if (
173
+ regex.match(r"^/entities(?:\?(.*))?$", request.path)
174
+ and request.method == "POST"
175
+ ):
176
+ is_soft_call = request.args.get("soft") is not None
177
+ return self._get_tenant_id_from_body(request.json, soft_call=is_soft_call)
178
+ return None
179
+
180
+
181
+ class EntityDetailGetRequest(BaseRequest):
182
+ def get_tenant_id(self, request: Request) -> str | None:
183
+ if regex.match(r"/entities/(.+)$", request.path) and request.method == "GET":
184
+ return self._get_tenant_id_from_entity(request.view_args.get("id"))
185
+ return None
186
+
187
+
188
+ class EntityDetailUpdateRequest(BaseRequest):
189
+ def get_tenant_id(self, request: Request) -> str | None:
190
+ if regex.match(r"^/entities/(.+)$", request.path) and request.method in [
191
+ "PUT",
192
+ "PATCH",
193
+ ]:
194
+ return self._get_tenant_id_from_entity(request.view_args.get("id"))
195
+ return None
196
+
197
+
198
+ class EntityDetailDeleteRequest(BaseRequest):
199
+ def get_tenant_id(self, request: Request) -> str | None:
200
+ if (
201
+ regex.match(r"^/entities/([^/]+)$", request.path)
202
+ and request.method == "DELETE"
203
+ ):
204
+ return self._get_tenant_id_from_entity(request.view_args.get("id"))
205
+ return None
206
+
207
+
208
+ class EntityDetailGetRelationsRequest(BaseRequest):
209
+ def get_tenant_id(self, request: Request) -> str | None:
210
+ if (
211
+ regex.match(r"^/entities/(.+)/relations$", request.path)
212
+ and request.method == "GET"
213
+ ):
214
+ return self._get_tenant_id_from_entity(request.view_args.get("id"))
215
+ return None
216
+
217
+
218
+ class EntityDetailUpdateRelationsRequest(BaseRequest):
219
+ def get_tenant_id(self, request: Request) -> str | None:
220
+ if regex.match(
221
+ r"^/entities/(.+)/relations$", request.path
222
+ ) and request.method in ["PUT", "PATCH"]:
223
+ return self._get_tenant_id_from_entity(request.view_args.get("id"))
224
+ return None
225
+
226
+
227
+ class EntityDetailCreateRelationsRequest(BaseRequest):
228
+ def get_tenant_id(self, request: Request) -> str | None:
229
+ if (
230
+ regex.match(r"^/entities/(.+)/relations$", request.path)
231
+ and request.method == "POST"
232
+ ):
233
+ return self._get_tenant_id_from_entity(request.view_args.get("id"))
234
+ return None
235
+
236
+
237
+ class EntityDetailDeleteRelationsRequest(BaseRequest):
238
+ def get_tenant_id(self, request: Request) -> str | None:
239
+ if (
240
+ regex.match(r"^/entities/(.+)/relations$", request.path)
241
+ and request.method == "DELETE"
242
+ ):
243
+ return self._get_tenant_id_from_entity(request.view_args.get("id"))
244
+ return None
245
+
246
+
247
+ class EntityDetailGetMetadataRequest(BaseRequest):
248
+ def get_tenant_id(self, request: Request) -> str | None:
249
+ if (
250
+ regex.match(r"^/entities/(.+)/metadata$", request.path)
251
+ and request.method == "GET"
252
+ ):
253
+ return self._get_tenant_id_from_entity(request.view_args.get("id"))
254
+ return None
255
+
256
+
257
+ class EntityDetailUpdateMetadataRequest(BaseRequest):
258
+ def get_tenant_id(self, request: Request) -> str | None:
259
+ if regex.match(
260
+ r"^/entities/(.+)/metadata$", request.path
261
+ ) and request.method in ["PUT", "PATCH"]:
262
+ return self._get_tenant_id_from_entity(request.view_args.get("id"))
263
+ return None
264
+
265
+
266
+ class EntityDetailCreateMetadataRequest(BaseRequest):
267
+ def get_tenant_id(self, request: Request) -> str | None:
268
+ if (
269
+ regex.match(r"^/entities/(.+)/metadata$", request.path)
270
+ and request.method == "POST"
271
+ ):
272
+ return self._get_tenant_id_from_entity(request.view_args.get("id"))
273
+ return None
274
+
275
+
276
+ class EntityDetailGetMediafilesRequest(BaseRequest):
277
+ def get_tenant_id(self, request: Request) -> str | None:
278
+ if (
279
+ regex.match(r"^/entities/(.+)/mediafiles$", request.path)
280
+ and request.method == "GET"
281
+ ):
282
+ return self._get_tenant_id_from_entity(request.view_args.get("id"))
283
+ return None
284
+
285
+
286
+ class EntityDetailCreateMediafilesRequest(BaseRequest):
287
+ def get_tenant_id(self, request: Request) -> str | None:
288
+ if (
289
+ regex.match(r"^/entities/(.+)/mediafiles$", request.path)
290
+ and request.method == "POST"
291
+ ):
292
+ return self._get_tenant_id_from_entity(request.view_args.get("id"))
293
+ return None
294
+
295
+
296
+ # Mediafiles
297
+ class MediafileGetRequest(BaseRequest):
298
+ def get_tenant_id(self, request: Request) -> str | None:
299
+ if (
300
+ regex.match(r"^/mediafiles(?:\?(.*))?$", request.path)
301
+ and request.method == "GET"
302
+ ):
303
+ return self.super_tenant_id
304
+ return None
305
+
306
+
307
+ class MediafilePostRequest(BaseRequest):
308
+ def get_tenant_id(self, request: Request) -> str | None:
309
+ if (
310
+ regex.match(r"^/mediafiles(?:\?(.*))?$", request.path)
311
+ and request.method == "POST"
312
+ ):
313
+ is_soft_call = request.args.get("soft") is not None
314
+ return self._get_tenant_id_from_body(request.json, soft_call=is_soft_call)
315
+ return None
316
+
317
+
318
+ class MediafileDetailGetRequest(BaseRequest):
319
+ def get_tenant_id(self, request: Request) -> str | None:
320
+ if (
321
+ regex.match(r"^/mediafiles/([^/]+)$", request.path)
322
+ and request.method == "GET"
323
+ ):
324
+ return self.super_tenant_id
325
+ return None
326
+
327
+
328
+ class MediafileDetailUpdateRequest(BaseRequest):
329
+ def get_tenant_id(self, request: Request) -> str | None:
330
+ if regex.match(r"^/mediafiles/(.+)$", request.path) and request.method in [
331
+ "PUT",
332
+ "PATCH",
333
+ ]:
334
+ return self._get_tenant_id_from_mediafile(request.view_args.get("id"))
335
+ return None
336
+
337
+
338
+ class MediafileDetailDeleteRequest(BaseRequest):
339
+ def get_tenant_id(self, request: Request) -> str | None:
340
+ if (
341
+ regex.match(r"^/mediafiles/(.+)$", request.path)
342
+ and request.method == "DELETE"
343
+ ):
344
+ return self._get_tenant_id_from_mediafile(request.view_args.get("id"))
345
+ return None
346
+
347
+
348
+ class MediafileDetailGetDerivativesRequest(BaseRequest):
349
+ def get_tenant_id(self, request: Request) -> str | None:
350
+ if (
351
+ regex.match(r"^/mediafiles/(.+)/derivatives$", request.path)
352
+ and request.method == "GET"
353
+ ):
354
+ return self.super_tenant_id
355
+ return None
356
+
357
+
358
+ class MediafileDetailCreateDerivativesRequest(BaseRequest):
359
+ def get_tenant_id(self, request: Request) -> str | None:
360
+ if (
361
+ regex.match(r"^/mediafiles/(.+)/derivatives$", request.path)
362
+ and request.method == "POST"
363
+ ):
364
+ return self._get_tenant_id_from_mediafile(request.view_args.get("id"))
365
+ return None
366
+
367
+
368
+ class MediafileDetailDeleteDerivativesRequest(BaseRequest):
369
+ def get_tenant_id(self, request: Request) -> str | None:
370
+ if (
371
+ regex.match(r"^/mediafiles/(.+)/derivatives$", request.path)
372
+ and request.method == "DELETE"
373
+ ):
374
+ return self._get_tenant_id_from_mediafile(request.view_args.get("id"))
375
+ return None
elody/schemas.py CHANGED
@@ -105,9 +105,6 @@ mediafile_schema = {
105
105
  "$schema": "http://json-schema.org/draft-07/schema#",
106
106
  "type": "object",
107
107
  "default": {},
108
- "required": [
109
- "filename",
110
- ],
111
108
  "properties": {
112
109
  "filename": {
113
110
  "type": "string",
elody/util.py CHANGED
@@ -1,9 +1,45 @@
1
1
  import json
2
2
  import mimetypes
3
+ import re as regex
3
4
 
4
5
  from cloudevents.conversion import to_dict
5
6
  from cloudevents.http import CloudEvent
7
+ from collections.abc import MutableMapping, Iterable
8
+ from copy import deepcopy
6
9
  from datetime import datetime, timezone
10
+ from os import getenv
11
+
12
+
13
+ URL_UNFRIENDLY_CHARS = {
14
+ " ": "%20",
15
+ "!": "%21",
16
+ "'": "%22",
17
+ "#": "%23",
18
+ "%": "%25",
19
+ "&": "%26",
20
+ '"': "%27",
21
+ "(": "%28",
22
+ ")": "%29",
23
+ "*": "%2A",
24
+ "+": "%2B",
25
+ ",": "%2C",
26
+ "/": "%2F",
27
+ ";": "%3B",
28
+ "<": "%3C",
29
+ "=": "%3D",
30
+ ">": "%3E",
31
+ "?": "%3F",
32
+ "@": "%40",
33
+ "[": "%5B",
34
+ "\\": "%5C",
35
+ "]": "%5D",
36
+ "^": "%5E",
37
+ "`": "%60",
38
+ "{": "%7B",
39
+ "|": "%7C",
40
+ "}": "%7D",
41
+ "~": "%7E",
42
+ }
7
43
 
8
44
 
9
45
  class CustomJSONEncoder(json.JSONEncoder):
@@ -36,10 +72,46 @@ def custom_json_dumps(obj):
36
72
  return json.dumps(obj, cls=CustomJSONEncoder)
37
73
 
38
74
 
39
- def __send_cloudevent(mq_client, routing_key, data):
40
- attributes = {"type": routing_key, "source": "dams"}
41
- event = to_dict(CloudEvent(attributes, data))
42
- mq_client.send(event, routing_key=routing_key)
75
+ def flatten_dict(object_lists, data: MutableMapping, parent_key=""):
76
+ if not parent_key:
77
+ data = deepcopy(data)
78
+ flat_dict = {}
79
+ for key, value in __flatten_dict_generator(object_lists, data, parent_key):
80
+ if key in flat_dict:
81
+ if not isinstance(flat_dict[key], list):
82
+ flat_dict[key] = [flat_dict[key]]
83
+ if isinstance(value, list):
84
+ flat_dict[key].extend(value)
85
+ else:
86
+ flat_dict[key].append(value)
87
+ else:
88
+ flat_dict[key] = value
89
+ return flat_dict
90
+
91
+
92
+ def __flatten_dict_generator(object_lists, data: MutableMapping, parent_key):
93
+ for key, value in data.items():
94
+ flattened_key = f"{parent_key}.{key}" if parent_key else key
95
+ if isinstance(value, MutableMapping):
96
+ yield from flatten_dict(object_lists, value, flattened_key).items()
97
+ elif isinstance(value, Iterable) and not isinstance(value, (str, bytes)):
98
+ if all(isinstance(item, MutableMapping) for item in value):
99
+ item_key = None
100
+ for item in value:
101
+ item_key = item.get(object_lists.get(key))
102
+ if item_key:
103
+ if isinstance(item, MutableMapping):
104
+ yield from flatten_dict(
105
+ object_lists, item, f"{flattened_key}.{item_key}"
106
+ ).items()
107
+ else:
108
+ yield f"{flattened_key}.{item_key}", item["value"]
109
+ if not item_key:
110
+ yield flattened_key, value
111
+ else:
112
+ yield flattened_key, value
113
+ else:
114
+ yield flattened_key, value
43
115
 
44
116
 
45
117
  def get_raw_id(item):
@@ -58,6 +130,35 @@ def get_mimetype_from_filename(filename):
58
130
  return mime if mime else "application/octet-stream"
59
131
 
60
132
 
133
+ def interpret_flat_key(flat_key: str, object_lists):
134
+ keys_info = []
135
+ index = 0
136
+
137
+ flat_key_parts = regex.split(r"\.(?=(?:[^`]*`[^`]*`)*[^`]*$)", flat_key)
138
+ while index < len(flat_key_parts):
139
+ info = {
140
+ "key": flat_key_parts[index],
141
+ "object_list": (
142
+ flat_key_parts[index]
143
+ if flat_key_parts[index] in object_lists.keys()
144
+ else ""
145
+ ),
146
+ }
147
+
148
+ if info["object_list"]:
149
+ combined_key = "".join(
150
+ [f"{info['key']}." for info in keys_info if not info["object_list"]]
151
+ )
152
+ info["key"] = f"{combined_key}{info['key']}"
153
+ info["object_key"] = flat_key_parts[index + 1]
154
+ keys_info = [info for info in keys_info if info["object_list"]]
155
+
156
+ keys_info.append(info)
157
+ index += 2 if info["object_list"] else 1
158
+
159
+ return keys_info
160
+
161
+
61
162
  def mediafile_is_public(mediafile):
62
163
  publication_status = get_item_metadata_value(mediafile, "publication_status")
63
164
  copyright_color = get_item_metadata_value(mediafile, "copyright_color")
@@ -68,6 +169,16 @@ def mediafile_is_public(mediafile):
68
169
  ] or copyright_color.lower() in ["green", "groen"]
69
170
 
70
171
 
172
+ def parse_string_to_bool(value):
173
+ if isinstance(value, str):
174
+ value_lower = value.strip().lower()
175
+ if value_lower in ["true", "yes", "1", "y"]:
176
+ return True
177
+ elif value_lower in ["false", "no", "0", "n"]:
178
+ return False
179
+ return value
180
+
181
+
71
182
  def read_json_as_dict(filename, logger):
72
183
  try:
73
184
  with open(filename) as file:
@@ -77,16 +188,39 @@ def read_json_as_dict(filename, logger):
77
188
  return {}
78
189
 
79
190
 
191
+ def parse_url_unfriendly_string(
192
+ input: str, *, replace_char=None, return_unfriendly_chars=False
193
+ ):
194
+ unfriendly_chars = []
195
+ result = input
196
+ for char, encoded in URL_UNFRIENDLY_CHARS.items():
197
+ if char in input:
198
+ unfriendly_chars.append(char)
199
+ replacement = encoded if replace_char is None else replace_char
200
+ result = result.replace(char, replacement)
201
+ if return_unfriendly_chars:
202
+ return result, unfriendly_chars
203
+ return result
204
+
205
+
206
+ def send_cloudevent(mq_client, source, routing_key, data, exchange_name=None):
207
+ event = to_dict(CloudEvent({"source": source, "type": routing_key}, data))
208
+ if getenv("AMQP_MANAGER", "amqpstorm_flask") in ["amqpstorm_flask"]:
209
+ mq_client.send(event, routing_key=routing_key, exchange_name=exchange_name)
210
+ else:
211
+ mq_client.send(event, routing_key=routing_key)
212
+
213
+
80
214
  def signal_child_relation_changed(mq_client, collection, id):
81
215
  data = {"parent_id": id, "collection": collection}
82
- __send_cloudevent(mq_client, "dams.child_relation_changed", data)
216
+ send_cloudevent(mq_client, "dams", "dams.child_relation_changed", data)
83
217
 
84
218
 
85
219
  def signal_edge_changed(mq_client, parent_ids_from_changed_edges):
86
220
  data = {
87
221
  "location": f'/entities?ids={",".join(parent_ids_from_changed_edges)}&skip_relations=1'
88
222
  }
89
- __send_cloudevent(mq_client, "dams.edge_changed", data)
223
+ send_cloudevent(mq_client, "dams", "dams.edge_changed", data)
90
224
 
91
225
 
92
226
  def signal_entity_changed(mq_client, entity):
@@ -94,24 +228,44 @@ def signal_entity_changed(mq_client, entity):
94
228
  "location": f"/entities/{get_raw_id(entity)}",
95
229
  "type": entity.get("type", "unspecified"),
96
230
  }
97
- __send_cloudevent(mq_client, "dams.entity_changed", data)
231
+ send_cloudevent(mq_client, "dams", "dams.entity_changed", data)
98
232
 
99
233
 
100
234
  def signal_entity_deleted(mq_client, entity):
101
235
  data = {"_id": get_raw_id(entity), "type": entity.get("type", "unspecified")}
102
- __send_cloudevent(mq_client, "dams.entity_deleted", data)
236
+ send_cloudevent(mq_client, "dams", "dams.entity_deleted", data)
103
237
 
104
238
 
105
239
  def signal_mediafiles_added_for_entity(mq_client, entity, mediafiles):
106
240
  data = {"entity": entity, "mediafiles": mediafiles}
107
- __send_cloudevent(mq_client, "dams.mediafiles_added_for_entity", data)
241
+ send_cloudevent(mq_client, "dams", "dams.mediafiles_added_for_entity", data)
242
+
243
+
244
+ def signal_relations_deleted_for_entity(mq_client, entity, relations):
245
+ data = {"entity": entity, "relations": relations}
246
+ send_cloudevent(mq_client, "dams", "dams.relations_deleted_for_entity", data)
108
247
 
109
248
 
110
249
  def signal_mediafile_changed(mq_client, old_mediafile, mediafile):
111
250
  data = {"old_mediafile": old_mediafile, "mediafile": mediafile}
112
- __send_cloudevent(mq_client, "dams.mediafile_changed", data)
251
+ send_cloudevent(mq_client, "dams", "dams.mediafile_changed", data)
113
252
 
114
253
 
115
254
  def signal_mediafile_deleted(mq_client, mediafile, linked_entities):
116
255
  data = {"mediafile": mediafile, "linked_entities": linked_entities}
117
- __send_cloudevent(mq_client, "dams.mediafile_deleted", data)
256
+ send_cloudevent(mq_client, "dams", "dams.mediafile_deleted", data)
257
+
258
+
259
+ def signal_update_copyright_color_entity(mq_client, entity_id):
260
+ data = {"_id": entity_id}
261
+ send_cloudevent(mq_client, "dams", "dams.update_copyright_color_entity", data)
262
+
263
+
264
+ def signal_update_copyright_color_mediafile(mq_client, mediafile_id):
265
+ data = {"_id": mediafile_id}
266
+ send_cloudevent(mq_client, "dams", "dams.update_copyright_color_mediafile", data)
267
+
268
+
269
+ def signal_upload_file(mq_client, upload_links, selected_folder):
270
+ data = {"upload_links": upload_links, "selected_folder": selected_folder}
271
+ send_cloudevent(mq_client, "dams", "dams.upload_file", data)
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: elody
3
- Version: 0.0.63
3
+ Version: 0.0.162
4
4
  Summary: elody SDK for Python
5
5
  Author-email: Inuits <developers@inuits.eu>
6
- License: GNU GENERAL PUBLIC LICENSE
6
+ License: GNU GENERAL PUBLIC LICENSE
7
7
  Version 2, June 1991
8
8
 
9
9
  Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
@@ -354,16 +354,21 @@ Classifier: Programming Language :: Python :: 3.10
354
354
  Classifier: Programming Language :: Python :: 3.11
355
355
  Description-Content-Type: text/markdown
356
356
  License-File: LICENSE
357
- Requires-Dist: certifi >=2023.5.7
358
- Requires-Dist: charset-normalizer >=3.2.0
359
- Requires-Dist: idna >=3.4
360
- Requires-Dist: requests >=2.31.0
361
- Requires-Dist: urllib3 >=1.26.16
357
+ Requires-Dist: certifi>=2023.5.7
358
+ Requires-Dist: charset-normalizer>=3.2.0
359
+ Requires-Dist: idna>=3.4
360
+ Requires-Dist: requests>=2.31.0
361
+ Requires-Dist: urllib3>=1.26.16
362
362
  Provides-Extra: loader
363
- Requires-Dist: cloudevents >=1.9.0 ; extra == 'loader'
364
- Requires-Dist: inuits-policy-based-auth >=9.6.0 ; extra == 'loader'
363
+ Requires-Dist: APScheduler>=3.10.4; extra == "loader"
364
+ Requires-Dist: cloudevents>=1.9.0; extra == "loader"
365
+ Requires-Dist: inuits-policy-based-auth>=10.0.1; extra == "loader"
366
+ Requires-Dist: jsonschema>=4.23.0; extra == "loader"
367
+ Requires-Dist: pytz>=2024.1; extra == "loader"
368
+ Requires-Dist: six>=1.16.0; extra == "loader"
369
+ Requires-Dist: tzlocal>=5.2; extra == "loader"
365
370
  Provides-Extra: util
366
- Requires-Dist: cloudevents >=1.9.0 ; extra == 'util'
371
+ Requires-Dist: cloudevents>=1.9.0; extra == "util"
367
372
 
368
373
  # elody SDK for Python
369
374