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,293 @@
1
+ import pytest
2
+ import json
3
+
4
+ from elody.util import (
5
+ parse_string_to_bool,
6
+ interpret_flat_key,
7
+ __flatten_dict_generator,
8
+ get_raw_id,
9
+ get_item_metadata_value,
10
+ get_mimetype_from_filename,
11
+ mediafile_is_public,
12
+ read_json_as_dict,
13
+ parse_url_unfriendly_string,
14
+ CustomJSONEncoder,
15
+ )
16
+ from data import mediafile1, mediafile2
17
+ from datetime import datetime, timezone
18
+ from unittest.mock import mock_open, patch, MagicMock
19
+
20
+
21
+ def test_default_method_with_datetime():
22
+ encoder = CustomJSONEncoder()
23
+ dt = datetime(2023, 10, 1, 12, 0, 0, tzinfo=timezone.utc)
24
+ result = encoder.default(dt)
25
+ assert result == "2023-10-01T12:00:00+00:00"
26
+
27
+
28
+ def test_default_method_with_naive_datetime():
29
+ encoder = CustomJSONEncoder()
30
+ dt = datetime(2023, 10, 1, 12, 0, 0)
31
+ result = encoder.default(dt)
32
+ assert result == "2023-10-01T10:00:00+00:00"
33
+
34
+
35
+ def test_encode_method_with_non_datetime():
36
+ encoder = CustomJSONEncoder()
37
+ obj = {"key": "value"}
38
+ result = encoder.encode(obj)
39
+ expected_result = json.dumps(obj)
40
+ assert result == expected_result
41
+
42
+
43
+ @pytest.mark.parametrize(
44
+ "input_value, expected_output",
45
+ [
46
+ # Truthy values
47
+ ("true", True),
48
+ ("yes", True),
49
+ ("1", True),
50
+ ("Y", True),
51
+ (" true ", True),
52
+ # Falsy values
53
+ ("false", False),
54
+ ("no", False),
55
+ ("0", False),
56
+ ("N", False),
57
+ (" no ", False),
58
+ # Non-string inputs
59
+ (123, 123),
60
+ (None, None),
61
+ (True, True),
62
+ (False, False),
63
+ # Edge cases
64
+ ("", ""), # Empty string
65
+ ("maybe", "maybe"), # Unexpected string
66
+ ("random", "random"), # Another unexpected string
67
+ ],
68
+ )
69
+ def test_parse_string_to_bool(input_value, expected_output):
70
+ assert parse_string_to_bool(input_value) == expected_output
71
+
72
+
73
+ def test_interpret_flat_key():
74
+ object_lists = {"user": ["name", "email"], "address": ["street", "city"]}
75
+
76
+ # Test case 1
77
+ flat_key = "user.name"
78
+ expected_output = [{"key": "user", "object_list": "user", "object_key": "name"}]
79
+ assert interpret_flat_key(flat_key, object_lists) == expected_output
80
+
81
+ # Test case 2
82
+ flat_key = "user.email"
83
+ expected_output = [{"key": "user", "object_list": "user", "object_key": "email"}]
84
+ assert interpret_flat_key(flat_key, object_lists) == expected_output
85
+
86
+ # Test case 3
87
+ flat_key = "address.street"
88
+ expected_output = [
89
+ {"key": "address", "object_list": "address", "object_key": "street"}
90
+ ]
91
+ assert interpret_flat_key(flat_key, object_lists) == expected_output
92
+
93
+ # Test case 4
94
+ flat_key = "address.city"
95
+ expected_output = [
96
+ {"key": "address", "object_list": "address", "object_key": "city"}
97
+ ]
98
+ assert interpret_flat_key(flat_key, object_lists) == expected_output
99
+
100
+
101
+ def test_flatten_dict_generator():
102
+ object_lists = {"items": "id"}
103
+
104
+ data = {
105
+ "name": "John",
106
+ "address": {"street": "123 Main St", "city": "Anytown"},
107
+ "items": [{"id": "1", "value": "item1"}, {"id": "2", "value": "item2"}],
108
+ "tags": ["tag1", "tag2"],
109
+ }
110
+
111
+ expected_output = {
112
+ "name": "John",
113
+ "address.street": "123 Main St",
114
+ "address.city": "Anytown",
115
+ "items.1.id": "1",
116
+ "items.1.value": "item1",
117
+ "items.2.id": "2",
118
+ "items.2.value": "item2",
119
+ "tags": ["tag1", "tag2"],
120
+ }
121
+
122
+ result = dict(__flatten_dict_generator(object_lists, data, ""))
123
+ assert result == expected_output
124
+
125
+
126
+ def test_flatten_dict_generator_nested():
127
+ object_lists = {"items": "id"}
128
+
129
+ data = {
130
+ "user": {
131
+ "name": "John",
132
+ "address": {"street": "123 Main St", "city": "Anytown"},
133
+ "items": [{"id": "1", "value": "item1"}, {"id": "2", "value": "item2"}],
134
+ "tags": ["tag1", "tag2"],
135
+ }
136
+ }
137
+
138
+ expected_output = {
139
+ "user.name": "John",
140
+ "user.address.street": "123 Main St",
141
+ "user.address.city": "Anytown",
142
+ "user.items.1.id": "1",
143
+ "user.items.1.value": "item1",
144
+ "user.items.2.id": "2",
145
+ "user.items.2.value": "item2",
146
+ "user.tags": ["tag1", "tag2"],
147
+ }
148
+
149
+ result = dict(__flatten_dict_generator(object_lists, data, ""))
150
+ assert result == expected_output
151
+
152
+
153
+ def test_flatten_dict_generator_no_items():
154
+ object_lists = {"items": "id"}
155
+
156
+ data = {
157
+ "name": "John",
158
+ "address": {"street": "123 Main St", "city": "Anytown"},
159
+ "items": [{"value": "item1"}, {"value": "item2"}],
160
+ "tags": ["tag1", "tag2"],
161
+ }
162
+
163
+ expected_output = {
164
+ "name": "John",
165
+ "address.street": "123 Main St",
166
+ "address.city": "Anytown",
167
+ "items": [{"value": "item1"}, {"value": "item2"}],
168
+ "tags": ["tag1", "tag2"],
169
+ }
170
+
171
+ result = dict(__flatten_dict_generator(object_lists, data, ""))
172
+ assert result == expected_output
173
+
174
+
175
+ @pytest.mark.parametrize(
176
+ "mediafile_modification, expected_output",
177
+ [
178
+ (lambda mf: mf, "2476c1a2-c323-499e-9e28-6d1562296f3f"),
179
+ (lambda mf: mf.update({"_key": "test-key"}) or mf, "test-key"),
180
+ ],
181
+ )
182
+ def test_get_raw_id(mediafile_modification, expected_output):
183
+ modified_mediafile = mediafile_modification(mediafile1.copy())
184
+ assert get_raw_id(modified_mediafile) == expected_output
185
+
186
+ # Test for KeyError
187
+ modified_mediafile.pop("_key", None)
188
+ modified_mediafile.pop("_id", None)
189
+ with pytest.raises(KeyError):
190
+ get_raw_id(modified_mediafile)
191
+
192
+
193
+ @pytest.mark.parametrize(
194
+ "metadata_key, expected_output",
195
+ [
196
+ ("copyright_color", "green"),
197
+ ("copyright_color_calculation", "automatic"),
198
+ ("not_existing", ""),
199
+ ],
200
+ )
201
+ def test_get_item_metadata_value(metadata_key, expected_output):
202
+ assert get_item_metadata_value(mediafile1, metadata_key) == expected_output
203
+
204
+
205
+ @pytest.mark.parametrize(
206
+ "filename, expected_output",
207
+ [
208
+ ("test.jpg", "image/jpeg"),
209
+ ("test.png", "image/png"),
210
+ ("test.tif", "image/tiff"),
211
+ ("test.mp4", "video/mp4"),
212
+ ("test", "application/octet-stream"),
213
+ ],
214
+ )
215
+ def test_get_mimetype_from_filename(filename, expected_output):
216
+ assert get_mimetype_from_filename(filename) == expected_output
217
+
218
+
219
+ @pytest.mark.parametrize(
220
+ "mediafile, expected_output",
221
+ [
222
+ (mediafile1, True),
223
+ (mediafile2, False),
224
+ ],
225
+ )
226
+ def test_mediafile_is_public(mediafile, expected_output):
227
+ assert mediafile_is_public(mediafile) == expected_output
228
+
229
+
230
+ @pytest.fixture
231
+ def logger():
232
+ return MagicMock()
233
+
234
+
235
+ def test_read_json_as_dict_success(logger):
236
+ filename = "test.json"
237
+ json_content = '{"key": "value"}'
238
+ expected_output = {"key": "value"}
239
+
240
+ with patch("builtins.open", mock_open(read_data=json_content)):
241
+ result = read_json_as_dict(filename, logger)
242
+ assert result == expected_output
243
+ logger.error.assert_not_called()
244
+
245
+
246
+ def test_read_json_as_dict_file_not_found(logger):
247
+ filename = "test.json"
248
+
249
+ with patch("builtins.open", side_effect=FileNotFoundError):
250
+ result = read_json_as_dict(filename, logger)
251
+ assert result == {}
252
+ logger.error.assert_called_once_with(f"Could not read {filename} as a dict: ")
253
+
254
+
255
+ def test_read_json_as_dict_invalid_json(logger):
256
+ filename = "test.json"
257
+ invalid_json_content = '{"key": "value"' # Missing closing brace
258
+
259
+ with patch("builtins.open", mock_open(read_data=invalid_json_content)):
260
+ result = read_json_as_dict(filename, logger)
261
+ assert result == {}
262
+ logger.error.assert_called_once_with(
263
+ f"Could not read {filename} as a dict: Expecting ',' delimiter: line 1 column 16 (char 15)"
264
+ )
265
+
266
+
267
+ @pytest.mark.parametrize(
268
+ "input_str, replace_char, return_unfriendly_chars, expected_output",
269
+ [
270
+ # Test cases with default behavior
271
+ ("Hello World!", None, False, "Hello%20World%21"),
272
+ ("abc/def", None, False, "abc%2Fdef"),
273
+ # Test cases with replace_char specified
274
+ ("Hello World!", "-", False, "Hello-World-"),
275
+ ("abc/def", "*", False, "abc*def"),
276
+ # Test cases with return_unfriendly_chars enabled
277
+ ("Hello World!", None, True, ("Hello%20World%21", [" ", "!"])),
278
+ ("abc/def", None, True, ("abc%2Fdef", ["/"])),
279
+ ("Hello World!", "-", True, ("Hello-World-", [" ", "!"])),
280
+ # Edge cases
281
+ ("", None, False, ""),
282
+ ("NoSpecialChars", None, False, "NoSpecialChars"),
283
+ ],
284
+ )
285
+ def test_parse_url_unfriendly_string(
286
+ input_str, replace_char, return_unfriendly_chars, expected_output
287
+ ):
288
+ result = parse_url_unfriendly_string(
289
+ input_str,
290
+ replace_char=replace_char,
291
+ return_unfriendly_chars=return_unfriendly_chars,
292
+ )
293
+ assert result == expected_output
@@ -1,27 +0,0 @@
1
- __init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- elody/__init__.py,sha256=d0Q6Fn44e7wFfLabDOBxpcJ1DPKWlFunGYDUBmO-4hA,22
3
- elody/client.py,sha256=cgkmYHPvRluvyVHJrf0XwNCI1pNBpsvdARFRXUabUG8,6863
4
- elody/csv.py,sha256=FlFm_vAXx926R486x9uAjlERnbhT-ZUeFG0tkeBlFoM,8883
5
- elody/exceptions.py,sha256=PQHs4yi5jFCZYWJz_Hujg6KR5tmDqXmbxlLUgXdWbvY,695
6
- elody/loader.py,sha256=55RB0sAybNwRQPCOZeo69W49SSGbuRRY_1EE1oQuJj8,4382
7
- elody/schemas.py,sha256=iHWrXWq7b5XOygN3RsWxrkUR-SROkHtmVcgAkzZc5LI,4721
8
- elody/util.py,sha256=V9e0lMq5qRrkYLZW2XFDlVZxLCwCp4bhZpDlG_HouqM,3661
9
- elody/validator.py,sha256=G7Ya538EJHCFzOxEri2OcFMabfLBCtTKxuf4os_KuNw,260
10
- elody/policies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- elody/policies/permission_handler.py,sha256=sx4X9lmkog7kb1wYFbVEIF9IU8EpOPmLNUwLkBkF94A,8321
12
- elody/policies/authentication/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- elody/policies/authentication/base_user_tenant_validation_policy.py,sha256=V4V4fAPuICAHhyqJ06yx1i-ZsBbVYEfn1vWMISrut00,3715
14
- elody/policies/authentication/multi_tenant_policy.py,sha256=jmV1RTsApt2IV8BgSRcfnIjWHhDtFjW4OHJgWNUDKRw,3822
15
- elody/policies/authorization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- elody/policies/authorization/filter_generic_objects_policy.py,sha256=c4_6BRoDdSyICNU3zOlMvwwgU_tCKJLdlBQ3RDwPj5E,4584
17
- elody/policies/authorization/generic_object_detail_policy.py,sha256=DJi3-pH7zOYB5s0kzE05jfjTdxNUQbkZvuYcUz_hC2g,4125
18
- elody/policies/authorization/generic_object_metadata_policy.py,sha256=8Hh5KduWKcmhsfcZAZVVRW5n4uYfuFmS7qBM7r9iGDg,3370
19
- elody/policies/authorization/generic_object_relations_policy.py,sha256=fwiaGGM_JAZhiWFRuHmYkkgB-7sBurYPIi9Lli1y4PI,4187
20
- elody/policies/authorization/generic_object_request_policy.py,sha256=kv2Big7VCQ7W79juf7W_Kcv_8drddW711vM4v7ZRBoA,5346
21
- elody/policies/authorization/multi_tenant_policy.py,sha256=o138uz3sANyjlQc4qb-6inbi9YHZbc1bs5GFr8QL4nY,2685
22
- elody/policies/authorization/tenant_request_policy.py,sha256=jQU8y6S0i96M4l4jMYRx_EwmgFvOkXdfskrvZIr5uX8,1177
23
- elody-0.0.63.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
24
- elody-0.0.63.dist-info/METADATA,sha256=s65eNEAB-w6KrMfTHRILaBdigXHskxCraczzlIk5xKY,23078
25
- elody-0.0.63.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
26
- elody-0.0.63.dist-info/top_level.txt,sha256=I4Aci8dpg1DrbByPnQsMmuWsBXWCnrwJDfGWUcKG9p8,15
27
- elody-0.0.63.dist-info/RECORD,,