elody 0.0.155__py3-none-any.whl → 0.0.157__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
@@ -185,14 +185,21 @@ class CSVMultiObject(CSVParser):
185
185
  indexed_dict = dict()
186
186
  external_mediafiles_ids = []
187
187
  for row in self.reader:
188
- missing_columns = [
189
- x for x in self.index_mapping.values() if x not in row.keys()
188
+ mandatory_columns = [
189
+ v for k, v in self.index_mapping.items() if not k.startswith("?")
190
190
  ]
191
+ missing_columns = [x for x in mandatory_columns if x not in row.keys()]
191
192
  if missing_columns:
192
193
  raise ColumnNotFoundException(f"{', '.join(missing_columns)}")
193
194
  lang = self.__determine_language(row)
194
195
  previous_id = None
195
196
  for type, identifying_column in self.index_mapping.items():
197
+ is_type_optional = False
198
+ if type.startswith("?"):
199
+ is_type_optional = True
200
+ type = type.lstrip("?")
201
+ if not row.get(identifying_column) and is_type_optional:
202
+ continue
196
203
  id = row[identifying_column]
197
204
  if type not in indexed_dict:
198
205
  indexed_dict[type] = dict()
@@ -1,6 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from copy import deepcopy
3
3
  from elody.migration.base_object_migrator import BaseObjectMigrator
4
+ from policy_factory import get_user_context # pyright: ignore
4
5
 
5
6
 
6
7
  class BaseObjectConfiguration(ABC):
@@ -10,20 +11,24 @@ class BaseObjectConfiguration(ABC):
10
11
  @abstractmethod
11
12
  def crud(self):
12
13
  return {
13
- "collection": "entities",
14
- "collection_history": "history",
15
14
  "creator": lambda post_body, **kwargs: post_body,
15
+ "document_content_patcher": lambda *, document, content, overwrite=False, **kwargs: self._document_content_patcher(
16
+ document=document,
17
+ content=content,
18
+ overwrite=overwrite,
19
+ **kwargs,
20
+ ),
16
21
  "nested_matcher_builder": lambda object_lists, keys_info, value: self.__build_nested_matcher(
17
22
  object_lists, keys_info, value
18
23
  ),
19
24
  "post_crud_hook": lambda **kwargs: None,
20
- "pre_crud_hook": lambda **kwargs: None,
25
+ "pre_crud_hook": lambda *, document, **kwargs: document,
21
26
  "storage_type": "db",
22
27
  }
23
28
 
24
29
  @abstractmethod
25
30
  def document_info(self):
26
- return {"object_lists": {"metadata": "key", "relations": "type"}}
31
+ return {}
27
32
 
28
33
  @abstractmethod
29
34
  def logging(self, flat_document, **kwargs):
@@ -33,7 +38,7 @@ class BaseObjectConfiguration(ABC):
33
38
  "schema": f"{flat_document.get('schema.type')}:{flat_document.get('schema.version')}",
34
39
  }
35
40
  try:
36
- user_context = kwargs.get("get_user_context")() # pyright: ignore
41
+ user_context = get_user_context()
37
42
  info_labels["http_method"] = user_context.bag.get("http_method")
38
43
  info_labels["requested_endpoint"] = user_context.bag.get(
39
44
  "requested_endpoint"
@@ -65,20 +70,46 @@ class BaseObjectConfiguration(ABC):
65
70
 
66
71
  return "function", validator
67
72
 
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,
73
+ def _document_content_patcher(
74
+ self, *, document, content, overwrite=False, **kwargs
75
+ ):
76
+ raise NotImplementedError(
77
+ "Provide concrete implementation in child object configuration"
74
78
  )
75
- return post_body
76
79
 
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)
80
+ def _merge_object_lists(self, source, target, object_list_key):
81
+ for target_item in target:
82
+ for source_item in source:
83
+ if source_item[object_list_key] == target_item[object_list_key]:
84
+ source.remove(source_item)
85
+ return [*source, *target]
86
+
87
+ def _get_user_context_id(self):
88
+ try:
89
+ return get_user_context().id
90
+ except Exception:
91
+ return None
92
+
93
+ def _sanitize_document(self, *, document, **kwargs):
94
+ sanitized_document = {}
95
+ document_deepcopy = deepcopy(document)
96
+ for key, value in document_deepcopy.items():
97
+ if isinstance(value, dict):
98
+ sanitized_value = BaseObjectConfiguration._sanitize_document(
99
+ self, document=value
100
+ )
101
+ if sanitized_value:
102
+ sanitized_document[key] = sanitized_value
103
+ elif value:
104
+ sanitized_document[key] = value
105
+ return sanitized_document
106
+
107
+ def _should_create_history_object(self):
108
+ try:
109
+ get_user_context()
110
+ return bool(self.crud().get("collection_history"))
111
+ except Exception:
112
+ return False
82
113
 
83
114
  def _sort_document_keys(self, document):
84
115
  def sort_keys(data):
@@ -98,12 +129,13 @@ class BaseObjectConfiguration(ABC):
98
129
  else:
99
130
  return data
100
131
 
101
- for key, value in self.document_info()["object_lists"].items():
132
+ for key, value in self.document_info().get("object_lists", {}).items():
102
133
  if document.get(key):
103
134
  document[key] = sorted(
104
135
  document[key], key=lambda property: property[value]
105
136
  )
106
137
  sort_keys(document)
138
+ return document
107
139
 
108
140
  def __build_nested_matcher(self, object_lists, keys_info, value, index=0):
109
141
  if index == 0 and not any(info["object_list"] for info in keys_info):
@@ -135,10 +167,3 @@ class BaseObjectConfiguration(ABC):
135
167
  return elem_match if index > 0 else {info["key"]: {"$all": [elem_match]}}
136
168
 
137
169
  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()
@@ -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.155
3
+ Version: 0.0.157
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=KbsvZJAzv222CL-72vAdKDqy9-93UPVG8m_Vqc42hbM,12853
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=2yL8cCgWyT9lwTrpwjL_6TOYynWXRj6XdaHcCGC4RPM,6373
16
+ elody/object_configurations/elody_configuration.py,sha256=Bp3OSHBHuyPs-ubIULzl9wqZd3akYD2idh3mhYo7zu0,5339
17
+ elody/object_configurations/job_configuration.py,sha256=FagGQ7WBAcRHTeftezGOa7ykuYQvMB2GKeri80GkcMw,1813
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
@@ -36,10 +36,10 @@ elody/policies/authorization/tenant_request_policy.py,sha256=dEgblwRAqwWVcE-O7Jn
36
36
  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
- tests/unit/test_csv.py,sha256=FXuIWIuVqXnoRHKjUj7vCu-V7Vta5m8zVk3GyQroKv8,13947
40
- tests/unit/test_utils.py,sha256=V-lKHaJH1Os1td6ZoubRU-5S2TOAepNsE3n-tO93ctQ,8781
41
- elody-0.0.155.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
42
- elody-0.0.155.dist-info/METADATA,sha256=Cv1VoSd7-dmlZr-DyxUHJTs9z8baR3QTuXjQy0WM__0,23282
43
- elody-0.0.155.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
44
- elody-0.0.155.dist-info/top_level.txt,sha256=E0mImupLj0KmtUUCXRYEoLDRaSkuiGaOIIseAa0oQ-M,21
45
- elody-0.0.155.dist-info/RECORD,,
39
+ tests/unit/test_csv.py,sha256=NQaOhehfQ4GuXku0Y1SA8DYjJeqqidbF50zEHAi8RZA,15923
40
+ tests/unit/test_utils.py,sha256=g63szcEZyHhCOtrW4BnNbcgVca3oYPIOLjBdIzNwwN0,8784
41
+ elody-0.0.157.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
42
+ elody-0.0.157.dist-info/METADATA,sha256=ZI5G47P1QM9FsrLjWu7b16W7htJ4B7wxFUptsUHNAy4,23283
43
+ elody-0.0.157.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
44
+ elody-0.0.157.dist-info/top_level.txt,sha256=E0mImupLj0KmtUUCXRYEoLDRaSkuiGaOIIseAa0oQ-M,21
45
+ elody-0.0.157.dist-info/RECORD,,
tests/unit/test_csv.py CHANGED
@@ -35,6 +35,14 @@ sample_multiple_keywords_csv_vliz = """same_entity,title,description,coordinates
35
35
  1,,,,keyword2,,,,,,,,,,,leeuw.jpg,,,,,,,keyword_mediafile,,,,
36
36
  """
37
37
 
38
+ sample_csv_without_mediafile_digi = """external_id,external_system,type,asset_copyright_color
39
+ tg:lhps:32930:m1,arches,asset,green
40
+ """
41
+
42
+ sample_csv_without_mediafile_vliz = """same_entity,title,description,coordinates,media_keyword,language,asset_category,location_type,marine_region,event,project,partner,creator_person,owner_partner,type
43
+ 1,This is a media entity without a mediafile,This is a description,,keyword1,,,,,,,,person1,partner1,media
44
+ """
45
+
38
46
  expected_basic_objects_digipolis = {
39
47
  "entities": [
40
48
  {
@@ -181,13 +189,47 @@ expected_multiple_keywords_objects_vliz = {
181
189
  ],
182
190
  }
183
191
 
192
+ expected_only_entities_object_digipolis = {
193
+ "entities": [
194
+ {
195
+ "matching_id": "tg:lhps:32930:m1",
196
+ "metadata": [
197
+ {"key": "external_id", "value": "tg:lhps:32930:m1", "lang": "en"},
198
+ {"key": "external_system", "value": "arches", "lang": "en"},
199
+ {"key": "copyright_color", "value": "green", "lang": "en"},
200
+ ],
201
+ "type": "asset",
202
+ }
203
+ ]
204
+ }
205
+
206
+ expected_only_entities_object_vliz = {
207
+ "entities": [
208
+ {
209
+ "matching_id": "1",
210
+ "metadata": [
211
+ {
212
+ "key": "title",
213
+ "value": "This is a media entity without a mediafile",
214
+ "lang": "",
215
+ },
216
+ {"key": "description", "value": "This is a description", "lang": ""},
217
+ {"key": "media_keyword", "value": "keyword1", "lang": ""},
218
+ {"key": "creator_person", "value": "person1", "lang": ""},
219
+ {"key": "owner_partner", "value": "partner1", "lang": ""},
220
+ ],
221
+ "type": "media",
222
+ }
223
+ ]
224
+ }
225
+
184
226
 
185
227
  def init_digipolis_csv_object(csv):
186
228
  csv_multi_object = CSVMultiObject(
187
229
  csv,
188
230
  index_mapping={
189
231
  "entities": "external_id",
190
- "mediafiles": "file_identifier",
232
+ "?mediafiles": "file_identifier",
191
233
  },
192
234
  object_field_mapping={
193
235
  "mediafiles": [
@@ -246,7 +288,7 @@ def init_digipolis_csv_object(csv):
246
288
  def init_vliz_csv_object(csv):
247
289
  csv_multi_object = CSVMultiObject(
248
290
  csv,
249
- index_mapping={"entities": "same_entity", "mediafiles": "filename"},
291
+ index_mapping={"entities": "same_entity", "?mediafiles": "filename"},
250
292
  object_field_mapping={
251
293
  "mediafiles": [
252
294
  "filename",
@@ -342,15 +384,27 @@ def test_meemoo_csv_digipolis():
342
384
  assert csv_multi_object.objects == expected_meemoo_objects_digipolis
343
385
 
344
386
 
387
+ def test_csv_with_only_an_entity_digipolis():
388
+ csv_multi_object = init_digipolis_csv_object(sample_csv_without_mediafile_digi)
389
+ assert csv_multi_object.objects == expected_only_entities_object_digipolis
390
+
391
+
345
392
  # Tests CSVMultiObject VLIZ
346
393
  def test_basic_csv_vliz():
347
394
  csv_multi_object = init_vliz_csv_object(sample_basic_csv_vliz)
348
395
  assert csv_multi_object.objects == expected_basic_objects_vliz
349
-
396
+
397
+
350
398
  def test_basic_csv_digipolis_with_wrong_values():
351
399
  with pytest.raises(ColumnNotFoundException):
352
400
  init_vliz_csv_object(sample_basic_csv_vliz_missing_values)
353
-
401
+
402
+
354
403
  def test_multiple_keywords_csv_vliz():
355
404
  csv_multi_object = init_vliz_csv_object(sample_multiple_keywords_csv_vliz)
356
405
  assert csv_multi_object.objects == expected_multiple_keywords_objects_vliz
406
+
407
+
408
+ def test_csv_with_only_an_entity_vliz():
409
+ csv_multi_object = init_vliz_csv_object(sample_csv_without_mediafile_vliz)
410
+ assert csv_multi_object.objects == expected_only_entities_object_vliz
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"}