arkindex-base-worker 0.4.0__py3-none-any.whl → 0.4.0a2__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 (51) hide show
  1. {arkindex_base_worker-0.4.0.dist-info → arkindex_base_worker-0.4.0a2.dist-info}/METADATA +13 -15
  2. arkindex_base_worker-0.4.0a2.dist-info/RECORD +51 -0
  3. {arkindex_base_worker-0.4.0.dist-info → arkindex_base_worker-0.4.0a2.dist-info}/WHEEL +1 -1
  4. arkindex_worker/cache.py +1 -1
  5. arkindex_worker/image.py +1 -120
  6. arkindex_worker/utils.py +0 -82
  7. arkindex_worker/worker/__init__.py +161 -46
  8. arkindex_worker/worker/base.py +11 -36
  9. arkindex_worker/worker/classification.py +18 -34
  10. arkindex_worker/worker/corpus.py +4 -21
  11. arkindex_worker/worker/dataset.py +1 -71
  12. arkindex_worker/worker/element.py +91 -352
  13. arkindex_worker/worker/entity.py +11 -11
  14. arkindex_worker/worker/metadata.py +9 -19
  15. arkindex_worker/worker/task.py +4 -5
  16. arkindex_worker/worker/training.py +6 -6
  17. arkindex_worker/worker/transcription.py +68 -89
  18. arkindex_worker/worker/version.py +1 -3
  19. tests/__init__.py +1 -1
  20. tests/conftest.py +45 -33
  21. tests/test_base_worker.py +3 -204
  22. tests/test_dataset_worker.py +4 -7
  23. tests/test_elements_worker/{test_classification.py → test_classifications.py} +61 -194
  24. tests/test_elements_worker/test_corpus.py +1 -32
  25. tests/test_elements_worker/test_dataset.py +1 -1
  26. tests/test_elements_worker/test_elements.py +2734 -0
  27. tests/test_elements_worker/{test_entity_create.py → test_entities.py} +160 -26
  28. tests/test_elements_worker/test_image.py +1 -2
  29. tests/test_elements_worker/test_metadata.py +99 -224
  30. tests/test_elements_worker/test_task.py +1 -1
  31. tests/test_elements_worker/test_training.py +2 -2
  32. tests/test_elements_worker/test_transcriptions.py +2102 -0
  33. tests/test_elements_worker/test_worker.py +280 -563
  34. tests/test_image.py +204 -429
  35. tests/test_merge.py +2 -1
  36. tests/test_utils.py +3 -66
  37. arkindex_base_worker-0.4.0.dist-info/RECORD +0 -61
  38. arkindex_worker/worker/process.py +0 -92
  39. tests/test_elements_worker/test_element.py +0 -427
  40. tests/test_elements_worker/test_element_create_multiple.py +0 -715
  41. tests/test_elements_worker/test_element_create_single.py +0 -528
  42. tests/test_elements_worker/test_element_list_children.py +0 -969
  43. tests/test_elements_worker/test_element_list_parents.py +0 -530
  44. tests/test_elements_worker/test_entity_list_and_check.py +0 -160
  45. tests/test_elements_worker/test_process.py +0 -89
  46. tests/test_elements_worker/test_transcription_create.py +0 -873
  47. tests/test_elements_worker/test_transcription_create_with_elements.py +0 -951
  48. tests/test_elements_worker/test_transcription_list.py +0 -450
  49. tests/test_elements_worker/test_version.py +0 -60
  50. {arkindex_base_worker-0.4.0.dist-info → arkindex_base_worker-0.4.0a2.dist-info}/LICENSE +0 -0
  51. {arkindex_base_worker-0.4.0.dist-info → arkindex_base_worker-0.4.0a2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,2734 @@
1
+ import json
2
+ import re
3
+ from argparse import Namespace
4
+ from uuid import UUID
5
+
6
+ import pytest
7
+ from apistar.exceptions import ErrorResponse
8
+ from responses import matchers
9
+
10
+ from arkindex_worker.cache import (
11
+ SQL_VERSION,
12
+ CachedElement,
13
+ CachedImage,
14
+ create_version_table,
15
+ init_cache_db,
16
+ )
17
+ from arkindex_worker.models import Element
18
+ from arkindex_worker.worker import ElementsWorker
19
+ from arkindex_worker.worker.element import MissingTypeError
20
+ from tests import CORPUS_ID
21
+
22
+ from . import BASE_API_CALLS
23
+
24
+
25
+ def test_check_required_types_argument_types(mock_elements_worker):
26
+ with pytest.raises(
27
+ AssertionError, match="At least one element type slug is required."
28
+ ):
29
+ mock_elements_worker.check_required_types()
30
+
31
+ with pytest.raises(AssertionError, match="Element type slugs must be strings."):
32
+ mock_elements_worker.check_required_types("lol", 42)
33
+
34
+
35
+ def test_check_required_types(responses, mock_elements_worker):
36
+ responses.add(
37
+ responses.GET,
38
+ f"http://testserver/api/v1/corpus/{CORPUS_ID}/",
39
+ json={
40
+ "id": CORPUS_ID,
41
+ "name": "Some Corpus",
42
+ "types": [{"slug": "folder"}, {"slug": "page"}],
43
+ },
44
+ )
45
+ mock_elements_worker.setup_api_client()
46
+
47
+ assert mock_elements_worker.check_required_types("page")
48
+ assert mock_elements_worker.check_required_types("page", "folder")
49
+
50
+ with pytest.raises(
51
+ MissingTypeError,
52
+ match=re.escape(
53
+ "Element type(s) act, text_line were not found in the Some Corpus corpus (11111111-1111-1111-1111-111111111111)."
54
+ ),
55
+ ):
56
+ assert mock_elements_worker.check_required_types("page", "text_line", "act")
57
+
58
+
59
+ def test_create_missing_types(responses, mock_elements_worker):
60
+ responses.add(
61
+ responses.GET,
62
+ f"http://testserver/api/v1/corpus/{CORPUS_ID}/",
63
+ json={
64
+ "id": CORPUS_ID,
65
+ "name": "Some Corpus",
66
+ "types": [{"slug": "folder"}, {"slug": "page"}],
67
+ },
68
+ )
69
+ responses.add(
70
+ responses.POST,
71
+ "http://testserver/api/v1/elements/type/",
72
+ match=[
73
+ matchers.json_params_matcher(
74
+ {
75
+ "slug": "text_line",
76
+ "display_name": "text_line",
77
+ "folder": False,
78
+ "corpus": CORPUS_ID,
79
+ }
80
+ )
81
+ ],
82
+ )
83
+ responses.add(
84
+ responses.POST,
85
+ "http://testserver/api/v1/elements/type/",
86
+ match=[
87
+ matchers.json_params_matcher(
88
+ {
89
+ "slug": "act",
90
+ "display_name": "act",
91
+ "folder": False,
92
+ "corpus": CORPUS_ID,
93
+ }
94
+ )
95
+ ],
96
+ )
97
+ mock_elements_worker.setup_api_client()
98
+
99
+ assert mock_elements_worker.check_required_types(
100
+ "page", "text_line", "act", create_missing=True
101
+ )
102
+
103
+
104
+ def test_list_elements_elements_list_arg_wrong_type(
105
+ monkeypatch, tmp_path, mock_elements_worker
106
+ ):
107
+ elements_path = tmp_path / "elements.json"
108
+ elements_path.write_text("{}")
109
+
110
+ monkeypatch.setenv("TASK_ELEMENTS", str(elements_path))
111
+ worker = ElementsWorker()
112
+ worker.configure()
113
+
114
+ with pytest.raises(AssertionError, match="Elements list must be a list"):
115
+ worker.list_elements()
116
+
117
+
118
+ def test_list_elements_elements_list_arg_empty_list(
119
+ monkeypatch, tmp_path, mock_elements_worker
120
+ ):
121
+ elements_path = tmp_path / "elements.json"
122
+ elements_path.write_text("[]")
123
+
124
+ monkeypatch.setenv("TASK_ELEMENTS", str(elements_path))
125
+ worker = ElementsWorker()
126
+ worker.configure()
127
+
128
+ with pytest.raises(AssertionError, match="No elements in elements list"):
129
+ worker.list_elements()
130
+
131
+
132
+ def test_list_elements_elements_list_arg_missing_id(
133
+ monkeypatch, tmp_path, mock_elements_worker
134
+ ):
135
+ elements_path = tmp_path / "elements.json"
136
+ elements_path.write_text(json.dumps([{"type": "volume"}]))
137
+
138
+ monkeypatch.setenv("TASK_ELEMENTS", str(elements_path))
139
+ worker = ElementsWorker()
140
+ worker.configure()
141
+
142
+ elt_list = worker.list_elements()
143
+
144
+ assert elt_list == []
145
+
146
+
147
+ def test_list_elements_elements_list_arg_not_uuid(
148
+ monkeypatch, tmp_path, mock_elements_worker
149
+ ):
150
+ elements_path = tmp_path / "elements.json"
151
+ elements_path.write_text(
152
+ json.dumps(
153
+ [
154
+ {"id": "volumeid", "type": "volume"},
155
+ {"id": "pageid", "type": "page"},
156
+ {"id": "actid", "type": "act"},
157
+ {"id": "surfaceid", "type": "surface"},
158
+ ]
159
+ )
160
+ )
161
+
162
+ monkeypatch.setenv("TASK_ELEMENTS", str(elements_path))
163
+ worker = ElementsWorker()
164
+ worker.configure()
165
+
166
+ with pytest.raises(
167
+ Exception,
168
+ match="These element IDs are invalid: volumeid, pageid, actid, surfaceid",
169
+ ):
170
+ worker.list_elements()
171
+
172
+
173
+ def test_list_elements_elements_list_arg(monkeypatch, tmp_path, mock_elements_worker):
174
+ elements_path = tmp_path / "elements.json"
175
+ elements_path.write_text(
176
+ json.dumps(
177
+ [
178
+ {"id": "11111111-1111-1111-1111-111111111111", "type": "volume"},
179
+ {"id": "22222222-2222-2222-2222-222222222222", "type": "page"},
180
+ {"id": "33333333-3333-3333-3333-333333333333", "type": "act"},
181
+ ]
182
+ )
183
+ )
184
+
185
+ monkeypatch.setenv("TASK_ELEMENTS", str(elements_path))
186
+ worker = ElementsWorker()
187
+ worker.configure()
188
+
189
+ elt_list = worker.list_elements()
190
+
191
+ assert elt_list == [
192
+ "11111111-1111-1111-1111-111111111111",
193
+ "22222222-2222-2222-2222-222222222222",
194
+ "33333333-3333-3333-3333-333333333333",
195
+ ]
196
+
197
+
198
+ def test_list_elements_element_arg_not_uuid(mocker, mock_elements_worker):
199
+ mocker.patch(
200
+ "arkindex_worker.worker.base.argparse.ArgumentParser.parse_args",
201
+ return_value=Namespace(
202
+ element=["volumeid", "pageid"],
203
+ verbose=False,
204
+ elements_list=None,
205
+ database=None,
206
+ dev=False,
207
+ ),
208
+ )
209
+
210
+ worker = ElementsWorker()
211
+ worker.configure()
212
+
213
+ with pytest.raises(
214
+ Exception, match="These element IDs are invalid: volumeid, pageid"
215
+ ):
216
+ worker.list_elements()
217
+
218
+
219
+ def test_list_elements_element_arg(mocker, mock_elements_worker):
220
+ mocker.patch(
221
+ "arkindex_worker.worker.base.argparse.ArgumentParser.parse_args",
222
+ return_value=Namespace(
223
+ element=[
224
+ "11111111-1111-1111-1111-111111111111",
225
+ "22222222-2222-2222-2222-222222222222",
226
+ ],
227
+ verbose=False,
228
+ elements_list=None,
229
+ database=None,
230
+ dev=False,
231
+ ),
232
+ )
233
+
234
+ worker = ElementsWorker()
235
+ worker.configure()
236
+
237
+ elt_list = worker.list_elements()
238
+
239
+ assert elt_list == [
240
+ "11111111-1111-1111-1111-111111111111",
241
+ "22222222-2222-2222-2222-222222222222",
242
+ ]
243
+
244
+
245
+ def test_list_elements_both_args_error(mocker, mock_elements_worker, tmp_path):
246
+ elements_path = tmp_path / "elements.json"
247
+ elements_path.write_text(
248
+ json.dumps(
249
+ [
250
+ {"id": "volumeid", "type": "volume"},
251
+ {"id": "pageid", "type": "page"},
252
+ {"id": "actid", "type": "act"},
253
+ {"id": "surfaceid", "type": "surface"},
254
+ ]
255
+ )
256
+ )
257
+ mocker.patch(
258
+ "arkindex_worker.worker.base.argparse.ArgumentParser.parse_args",
259
+ return_value=Namespace(
260
+ element=["anotherid", "againanotherid"],
261
+ verbose=False,
262
+ elements_list=elements_path.open(),
263
+ database=None,
264
+ dev=False,
265
+ ),
266
+ )
267
+
268
+ worker = ElementsWorker()
269
+ worker.configure()
270
+
271
+ with pytest.raises(
272
+ AssertionError, match="elements-list and element CLI args shouldn't be both set"
273
+ ):
274
+ worker.list_elements()
275
+
276
+
277
+ def test_database_arg(mocker, mock_elements_worker, tmp_path):
278
+ database_path = tmp_path / "my_database.sqlite"
279
+ init_cache_db(database_path)
280
+ create_version_table()
281
+
282
+ mocker.patch(
283
+ "arkindex_worker.worker.base.argparse.ArgumentParser.parse_args",
284
+ return_value=Namespace(
285
+ element=["volumeid", "pageid"],
286
+ verbose=False,
287
+ elements_list=None,
288
+ database=database_path,
289
+ dev=False,
290
+ ),
291
+ )
292
+
293
+ worker = ElementsWorker(support_cache=True)
294
+ worker.configure()
295
+
296
+ assert worker.use_cache is True
297
+ assert worker.cache_path == database_path
298
+
299
+
300
+ def test_database_arg_cache_missing_version_table(
301
+ mocker, mock_elements_worker, tmp_path
302
+ ):
303
+ database_path = tmp_path / "my_database.sqlite"
304
+ database_path.touch()
305
+
306
+ mocker.patch(
307
+ "arkindex_worker.worker.base.argparse.ArgumentParser.parse_args",
308
+ return_value=Namespace(
309
+ element=["volumeid", "pageid"],
310
+ verbose=False,
311
+ elements_list=None,
312
+ database=database_path,
313
+ dev=False,
314
+ ),
315
+ )
316
+
317
+ worker = ElementsWorker(support_cache=True)
318
+ with pytest.raises(
319
+ AssertionError,
320
+ match=f"The SQLite database {database_path} does not have the correct cache version, it should be {SQL_VERSION}",
321
+ ):
322
+ worker.configure()
323
+
324
+
325
+ def test_load_corpus_classes_api_error(responses, mock_elements_worker):
326
+ responses.add(
327
+ responses.GET,
328
+ f"http://testserver/api/v1/corpus/{CORPUS_ID}/classes/",
329
+ status=418,
330
+ )
331
+
332
+ assert not mock_elements_worker.classes
333
+ with pytest.raises(
334
+ Exception, match="Stopping pagination as data will be incomplete"
335
+ ):
336
+ mock_elements_worker.load_corpus_classes()
337
+
338
+ assert len(responses.calls) == len(BASE_API_CALLS) + 5
339
+ assert [
340
+ (call.request.method, call.request.url) for call in responses.calls
341
+ ] == BASE_API_CALLS + [
342
+ # We do 5 retries
343
+ (
344
+ "GET",
345
+ f"http://testserver/api/v1/corpus/{CORPUS_ID}/classes/",
346
+ ),
347
+ (
348
+ "GET",
349
+ f"http://testserver/api/v1/corpus/{CORPUS_ID}/classes/",
350
+ ),
351
+ (
352
+ "GET",
353
+ f"http://testserver/api/v1/corpus/{CORPUS_ID}/classes/",
354
+ ),
355
+ (
356
+ "GET",
357
+ f"http://testserver/api/v1/corpus/{CORPUS_ID}/classes/",
358
+ ),
359
+ (
360
+ "GET",
361
+ f"http://testserver/api/v1/corpus/{CORPUS_ID}/classes/",
362
+ ),
363
+ ]
364
+ assert not mock_elements_worker.classes
365
+
366
+
367
+ def test_load_corpus_classes(responses, mock_elements_worker):
368
+ responses.add(
369
+ responses.GET,
370
+ f"http://testserver/api/v1/corpus/{CORPUS_ID}/classes/",
371
+ status=200,
372
+ json={
373
+ "count": 3,
374
+ "next": None,
375
+ "results": [
376
+ {
377
+ "id": "0000",
378
+ "name": "good",
379
+ },
380
+ {
381
+ "id": "1111",
382
+ "name": "average",
383
+ },
384
+ {
385
+ "id": "2222",
386
+ "name": "bad",
387
+ },
388
+ ],
389
+ },
390
+ )
391
+
392
+ assert not mock_elements_worker.classes
393
+ mock_elements_worker.load_corpus_classes()
394
+
395
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
396
+ assert [
397
+ (call.request.method, call.request.url) for call in responses.calls
398
+ ] == BASE_API_CALLS + [
399
+ (
400
+ "GET",
401
+ f"http://testserver/api/v1/corpus/{CORPUS_ID}/classes/",
402
+ ),
403
+ ]
404
+ assert mock_elements_worker.classes == {
405
+ "good": "0000",
406
+ "average": "1111",
407
+ "bad": "2222",
408
+ }
409
+
410
+
411
+ def test_create_sub_element_wrong_element(mock_elements_worker):
412
+ with pytest.raises(
413
+ AssertionError, match="element shouldn't be null and should be of type Element"
414
+ ):
415
+ mock_elements_worker.create_sub_element(
416
+ element=None,
417
+ type="something",
418
+ name="0",
419
+ polygon=[[1, 1], [2, 2], [2, 1], [1, 2]],
420
+ )
421
+
422
+ with pytest.raises(
423
+ AssertionError, match="element shouldn't be null and should be of type Element"
424
+ ):
425
+ mock_elements_worker.create_sub_element(
426
+ element="not element type",
427
+ type="something",
428
+ name="0",
429
+ polygon=[[1, 1], [2, 2], [2, 1], [1, 2]],
430
+ )
431
+
432
+
433
+ def test_create_sub_element_wrong_type(mock_elements_worker):
434
+ elt = Element({"zone": None})
435
+
436
+ with pytest.raises(
437
+ AssertionError, match="type shouldn't be null and should be of type str"
438
+ ):
439
+ mock_elements_worker.create_sub_element(
440
+ element=elt,
441
+ type=None,
442
+ name="0",
443
+ polygon=[[1, 1], [2, 2], [2, 1], [1, 2]],
444
+ )
445
+
446
+ with pytest.raises(
447
+ AssertionError, match="type shouldn't be null and should be of type str"
448
+ ):
449
+ mock_elements_worker.create_sub_element(
450
+ element=elt,
451
+ type=1234,
452
+ name="0",
453
+ polygon=[[1, 1], [2, 2], [2, 1], [1, 2]],
454
+ )
455
+
456
+
457
+ def test_create_sub_element_wrong_name(mock_elements_worker):
458
+ elt = Element({"zone": None})
459
+
460
+ with pytest.raises(
461
+ AssertionError, match="name shouldn't be null and should be of type str"
462
+ ):
463
+ mock_elements_worker.create_sub_element(
464
+ element=elt,
465
+ type="something",
466
+ name=None,
467
+ polygon=[[1, 1], [2, 2], [2, 1], [1, 2]],
468
+ )
469
+
470
+ with pytest.raises(
471
+ AssertionError, match="name shouldn't be null and should be of type str"
472
+ ):
473
+ mock_elements_worker.create_sub_element(
474
+ element=elt,
475
+ type="something",
476
+ name=1234,
477
+ polygon=[[1, 1], [2, 2], [2, 1], [1, 2]],
478
+ )
479
+
480
+
481
+ def test_create_sub_element_wrong_polygon(mock_elements_worker):
482
+ elt = Element({"zone": None})
483
+
484
+ with pytest.raises(AssertionError, match="polygon should be None or a list"):
485
+ mock_elements_worker.create_sub_element(
486
+ element=elt,
487
+ type="something",
488
+ name="O",
489
+ polygon="not a polygon",
490
+ )
491
+
492
+ with pytest.raises(
493
+ AssertionError, match="polygon should have at least three points"
494
+ ):
495
+ mock_elements_worker.create_sub_element(
496
+ element=elt,
497
+ type="something",
498
+ name="O",
499
+ polygon=[[1, 1], [2, 2]],
500
+ )
501
+
502
+ with pytest.raises(
503
+ AssertionError, match="polygon points should be lists of two items"
504
+ ):
505
+ mock_elements_worker.create_sub_element(
506
+ element=elt,
507
+ type="something",
508
+ name="O",
509
+ polygon=[[1, 1, 1], [2, 2, 1], [2, 1, 1], [1, 2, 1]],
510
+ )
511
+
512
+ with pytest.raises(
513
+ AssertionError, match="polygon points should be lists of two items"
514
+ ):
515
+ mock_elements_worker.create_sub_element(
516
+ element=elt,
517
+ type="something",
518
+ name="O",
519
+ polygon=[[1], [2], [2], [1]],
520
+ )
521
+
522
+ with pytest.raises(
523
+ AssertionError, match="polygon points should be lists of two numbers"
524
+ ):
525
+ mock_elements_worker.create_sub_element(
526
+ element=elt,
527
+ type="something",
528
+ name="O",
529
+ polygon=[["not a coord", 1], [2, 2], [2, 1], [1, 2]],
530
+ )
531
+
532
+
533
+ @pytest.mark.parametrize("confidence", ["lol", "0.2", -1.0, 1.42, float("inf")])
534
+ def test_create_sub_element_wrong_confidence(mock_elements_worker, confidence):
535
+ with pytest.raises(
536
+ AssertionError,
537
+ match=re.escape("confidence should be None or a float in [0..1] range"),
538
+ ):
539
+ mock_elements_worker.create_sub_element(
540
+ element=Element({"zone": None}),
541
+ type="something",
542
+ name="blah",
543
+ polygon=[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]],
544
+ confidence=confidence,
545
+ )
546
+
547
+
548
+ @pytest.mark.parametrize(
549
+ ("image", "error_type", "error_message"),
550
+ [
551
+ (1, AssertionError, "image should be None or string"),
552
+ ("not a uuid", ValueError, "image is not a valid uuid."),
553
+ ],
554
+ )
555
+ def test_create_sub_element_wrong_image(
556
+ mock_elements_worker, image, error_type, error_message
557
+ ):
558
+ with pytest.raises(error_type, match=re.escape(error_message)):
559
+ mock_elements_worker.create_sub_element(
560
+ element=Element({"zone": None}),
561
+ type="something",
562
+ name="blah",
563
+ polygon=[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]],
564
+ image=image,
565
+ )
566
+
567
+
568
+ def test_create_sub_element_wrong_image_and_polygon(mock_elements_worker):
569
+ with pytest.raises(
570
+ AssertionError,
571
+ match=re.escape(
572
+ "An image or a parent with an image is required to create an element with a polygon."
573
+ ),
574
+ ):
575
+ mock_elements_worker.create_sub_element(
576
+ element=Element({"zone": None}),
577
+ type="something",
578
+ name="blah",
579
+ polygon=[[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]],
580
+ image=None,
581
+ )
582
+
583
+
584
+ def test_create_sub_element_api_error(responses, mock_elements_worker):
585
+ elt = Element(
586
+ {
587
+ "id": "12341234-1234-1234-1234-123412341234",
588
+ "corpus": {"id": CORPUS_ID},
589
+ "zone": {"image": {"id": "22222222-2222-2222-2222-222222222222"}},
590
+ }
591
+ )
592
+ responses.add(
593
+ responses.POST,
594
+ "http://testserver/api/v1/elements/create/",
595
+ status=418,
596
+ )
597
+
598
+ with pytest.raises(ErrorResponse):
599
+ mock_elements_worker.create_sub_element(
600
+ element=elt,
601
+ type="something",
602
+ name="0",
603
+ polygon=[[1, 1], [2, 2], [2, 1], [1, 2]],
604
+ )
605
+
606
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
607
+ assert [
608
+ (call.request.method, call.request.url) for call in responses.calls
609
+ ] == BASE_API_CALLS + [("POST", "http://testserver/api/v1/elements/create/")]
610
+
611
+
612
+ @pytest.mark.parametrize("slim_output", [True, False])
613
+ def test_create_sub_element(responses, mock_elements_worker, slim_output):
614
+ elt = Element(
615
+ {
616
+ "id": "12341234-1234-1234-1234-123412341234",
617
+ "corpus": {"id": CORPUS_ID},
618
+ "zone": {"image": {"id": "22222222-2222-2222-2222-222222222222"}},
619
+ }
620
+ )
621
+ child_elt = {
622
+ "id": "12345678-1234-1234-1234-123456789123",
623
+ "corpus": {"id": CORPUS_ID},
624
+ "zone": {"image": {"id": "22222222-2222-2222-2222-222222222222"}},
625
+ }
626
+ responses.add(
627
+ responses.POST,
628
+ "http://testserver/api/v1/elements/create/",
629
+ status=200,
630
+ json=child_elt,
631
+ )
632
+
633
+ element_creation_response = mock_elements_worker.create_sub_element(
634
+ element=elt,
635
+ type="something",
636
+ name="0",
637
+ polygon=[[1, 1], [2, 2], [2, 1], [1, 2]],
638
+ slim_output=slim_output,
639
+ )
640
+
641
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
642
+ assert [
643
+ (call.request.method, call.request.url) for call in responses.calls
644
+ ] == BASE_API_CALLS + [
645
+ (
646
+ "POST",
647
+ "http://testserver/api/v1/elements/create/",
648
+ ),
649
+ ]
650
+ assert json.loads(responses.calls[-1].request.body) == {
651
+ "type": "something",
652
+ "name": "0",
653
+ "image": None,
654
+ "corpus": CORPUS_ID,
655
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
656
+ "parent": "12341234-1234-1234-1234-123412341234",
657
+ "worker_run_id": "56785678-5678-5678-5678-567856785678",
658
+ "confidence": None,
659
+ }
660
+ if slim_output:
661
+ assert element_creation_response == "12345678-1234-1234-1234-123456789123"
662
+ else:
663
+ assert Element(element_creation_response) == Element(child_elt)
664
+
665
+
666
+ def test_create_sub_element_confidence(responses, mock_elements_worker):
667
+ elt = Element(
668
+ {
669
+ "id": "12341234-1234-1234-1234-123412341234",
670
+ "corpus": {"id": CORPUS_ID},
671
+ "zone": {"image": {"id": "22222222-2222-2222-2222-222222222222"}},
672
+ }
673
+ )
674
+ responses.add(
675
+ responses.POST,
676
+ "http://testserver/api/v1/elements/create/",
677
+ status=200,
678
+ json={"id": "12345678-1234-1234-1234-123456789123"},
679
+ )
680
+
681
+ sub_element_id = mock_elements_worker.create_sub_element(
682
+ element=elt,
683
+ type="something",
684
+ name="0",
685
+ polygon=[[1, 1], [2, 2], [2, 1], [1, 2]],
686
+ confidence=0.42,
687
+ )
688
+
689
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
690
+ assert [
691
+ (call.request.method, call.request.url) for call in responses.calls
692
+ ] == BASE_API_CALLS + [
693
+ ("POST", "http://testserver/api/v1/elements/create/"),
694
+ ]
695
+ assert json.loads(responses.calls[-1].request.body) == {
696
+ "type": "something",
697
+ "name": "0",
698
+ "image": None,
699
+ "corpus": CORPUS_ID,
700
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
701
+ "parent": "12341234-1234-1234-1234-123412341234",
702
+ "worker_run_id": "56785678-5678-5678-5678-567856785678",
703
+ "confidence": 0.42,
704
+ }
705
+ assert sub_element_id == "12345678-1234-1234-1234-123456789123"
706
+
707
+
708
+ def test_create_elements_wrong_parent(mock_elements_worker):
709
+ with pytest.raises(
710
+ TypeError, match="Parent element should be an Element or CachedElement instance"
711
+ ):
712
+ mock_elements_worker.create_elements(
713
+ parent=None,
714
+ elements=[],
715
+ )
716
+
717
+ with pytest.raises(
718
+ TypeError, match="Parent element should be an Element or CachedElement instance"
719
+ ):
720
+ mock_elements_worker.create_elements(
721
+ parent="not element type",
722
+ elements=[],
723
+ )
724
+
725
+
726
+ def test_create_elements_no_zone(mock_elements_worker):
727
+ elt = Element({"zone": None})
728
+ with pytest.raises(
729
+ AssertionError, match="create_elements cannot be used on parents without zones"
730
+ ):
731
+ mock_elements_worker.create_elements(
732
+ parent=elt,
733
+ elements=None,
734
+ )
735
+
736
+ elt = CachedElement(
737
+ id="11111111-1111-1111-1111-1111111111", name="blah", type="blah"
738
+ )
739
+ with pytest.raises(
740
+ AssertionError, match="create_elements cannot be used on parents without images"
741
+ ):
742
+ mock_elements_worker.create_elements(
743
+ parent=elt,
744
+ elements=None,
745
+ )
746
+
747
+
748
+ def test_create_elements_wrong_elements(mock_elements_worker):
749
+ elt = Element({"zone": {"image": {"id": "image_id"}}})
750
+
751
+ with pytest.raises(
752
+ AssertionError, match="elements shouldn't be null and should be of type list"
753
+ ):
754
+ mock_elements_worker.create_elements(
755
+ parent=elt,
756
+ elements=None,
757
+ )
758
+
759
+ with pytest.raises(
760
+ AssertionError, match="elements shouldn't be null and should be of type list"
761
+ ):
762
+ mock_elements_worker.create_elements(
763
+ parent=elt,
764
+ elements="not a list",
765
+ )
766
+
767
+
768
+ def test_create_elements_wrong_elements_instance(mock_elements_worker):
769
+ elt = Element({"zone": {"image": {"id": "image_id"}}})
770
+
771
+ with pytest.raises(
772
+ AssertionError, match="Element at index 0 in elements: Should be of type dict"
773
+ ):
774
+ mock_elements_worker.create_elements(
775
+ parent=elt,
776
+ elements=["not a dict"],
777
+ )
778
+
779
+
780
+ def test_create_elements_wrong_elements_name(mock_elements_worker):
781
+ elt = Element({"zone": {"image": {"id": "image_id"}}})
782
+
783
+ with pytest.raises(
784
+ AssertionError,
785
+ match="Element at index 0 in elements: name shouldn't be null and should be of type str",
786
+ ):
787
+ mock_elements_worker.create_elements(
788
+ parent=elt,
789
+ elements=[
790
+ {
791
+ "name": None,
792
+ "type": "something",
793
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
794
+ }
795
+ ],
796
+ )
797
+
798
+ with pytest.raises(
799
+ AssertionError,
800
+ match="Element at index 0 in elements: name shouldn't be null and should be of type str",
801
+ ):
802
+ mock_elements_worker.create_elements(
803
+ parent=elt,
804
+ elements=[
805
+ {
806
+ "name": 1234,
807
+ "type": "something",
808
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
809
+ }
810
+ ],
811
+ )
812
+
813
+
814
+ def test_create_elements_wrong_elements_type(mock_elements_worker):
815
+ elt = Element({"zone": {"image": {"id": "image_id"}}})
816
+
817
+ with pytest.raises(
818
+ AssertionError,
819
+ match="Element at index 0 in elements: type shouldn't be null and should be of type str",
820
+ ):
821
+ mock_elements_worker.create_elements(
822
+ parent=elt,
823
+ elements=[
824
+ {
825
+ "name": "0",
826
+ "type": None,
827
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
828
+ }
829
+ ],
830
+ )
831
+
832
+ with pytest.raises(
833
+ AssertionError,
834
+ match="Element at index 0 in elements: type shouldn't be null and should be of type str",
835
+ ):
836
+ mock_elements_worker.create_elements(
837
+ parent=elt,
838
+ elements=[
839
+ {
840
+ "name": "0",
841
+ "type": 1234,
842
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
843
+ }
844
+ ],
845
+ )
846
+
847
+
848
+ def test_create_elements_wrong_elements_polygon(mock_elements_worker):
849
+ elt = Element({"zone": {"image": {"id": "image_id"}}})
850
+
851
+ with pytest.raises(
852
+ AssertionError,
853
+ match="Element at index 0 in elements: polygon shouldn't be null and should be of type list",
854
+ ):
855
+ mock_elements_worker.create_elements(
856
+ parent=elt,
857
+ elements=[
858
+ {
859
+ "name": "0",
860
+ "type": "something",
861
+ "polygon": None,
862
+ }
863
+ ],
864
+ )
865
+
866
+ with pytest.raises(
867
+ AssertionError,
868
+ match="Element at index 0 in elements: polygon shouldn't be null and should be of type list",
869
+ ):
870
+ mock_elements_worker.create_elements(
871
+ parent=elt,
872
+ elements=[
873
+ {
874
+ "name": "0",
875
+ "type": "something",
876
+ "polygon": "not a polygon",
877
+ }
878
+ ],
879
+ )
880
+
881
+ with pytest.raises(
882
+ AssertionError,
883
+ match="Element at index 0 in elements: polygon should have at least three points",
884
+ ):
885
+ mock_elements_worker.create_elements(
886
+ parent=elt,
887
+ elements=[
888
+ {
889
+ "name": "0",
890
+ "type": "something",
891
+ "polygon": [[1, 1], [2, 2]],
892
+ }
893
+ ],
894
+ )
895
+
896
+ with pytest.raises(
897
+ AssertionError,
898
+ match="Element at index 0 in elements: polygon points should be lists of two items",
899
+ ):
900
+ mock_elements_worker.create_elements(
901
+ parent=elt,
902
+ elements=[
903
+ {
904
+ "name": "0",
905
+ "type": "something",
906
+ "polygon": [[1, 1, 1], [2, 2, 1], [2, 1, 1], [1, 2, 1]],
907
+ }
908
+ ],
909
+ )
910
+
911
+ with pytest.raises(
912
+ AssertionError,
913
+ match="Element at index 0 in elements: polygon points should be lists of two items",
914
+ ):
915
+ mock_elements_worker.create_elements(
916
+ parent=elt,
917
+ elements=[
918
+ {
919
+ "name": "0",
920
+ "type": "something",
921
+ "polygon": [[1], [2], [2], [1]],
922
+ }
923
+ ],
924
+ )
925
+
926
+ with pytest.raises(
927
+ AssertionError,
928
+ match="Element at index 0 in elements: polygon points should be lists of two numbers",
929
+ ):
930
+ mock_elements_worker.create_elements(
931
+ parent=elt,
932
+ elements=[
933
+ {
934
+ "name": "0",
935
+ "type": "something",
936
+ "polygon": [["not a coord", 1], [2, 2], [2, 1], [1, 2]],
937
+ }
938
+ ],
939
+ )
940
+
941
+
942
+ @pytest.mark.parametrize("confidence", ["lol", "0.2", -1.0, 1.42, float("inf")])
943
+ def test_create_elements_wrong_elements_confidence(mock_elements_worker, confidence):
944
+ with pytest.raises(
945
+ AssertionError,
946
+ match=re.escape(
947
+ "Element at index 0 in elements: confidence should be None or a float in [0..1] range"
948
+ ),
949
+ ):
950
+ mock_elements_worker.create_elements(
951
+ parent=Element({"zone": {"image": {"id": "image_id"}}}),
952
+ elements=[
953
+ {
954
+ "name": "a",
955
+ "type": "something",
956
+ "polygon": [[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]],
957
+ "confidence": confidence,
958
+ }
959
+ ],
960
+ )
961
+
962
+
963
+ def test_create_elements_api_error(responses, mock_elements_worker):
964
+ elt = Element(
965
+ {
966
+ "id": "12341234-1234-1234-1234-123412341234",
967
+ "zone": {
968
+ "image": {
969
+ "id": "c0fec0fe-c0fe-c0fe-c0fe-c0fec0fec0fe",
970
+ "width": 42,
971
+ "height": 42,
972
+ "url": "http://aaaa",
973
+ }
974
+ },
975
+ }
976
+ )
977
+ responses.add(
978
+ responses.POST,
979
+ "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/children/bulk/",
980
+ status=418,
981
+ )
982
+
983
+ with pytest.raises(ErrorResponse):
984
+ mock_elements_worker.create_elements(
985
+ parent=elt,
986
+ elements=[
987
+ {
988
+ "name": "0",
989
+ "type": "something",
990
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
991
+ }
992
+ ],
993
+ )
994
+
995
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
996
+ assert [
997
+ (call.request.method, call.request.url) for call in responses.calls
998
+ ] == BASE_API_CALLS + [
999
+ (
1000
+ "POST",
1001
+ "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/children/bulk/",
1002
+ )
1003
+ ]
1004
+
1005
+
1006
+ def test_create_elements_cached_element(responses, mock_elements_worker_with_cache):
1007
+ image = CachedImage.create(
1008
+ id=UUID("c0fec0fe-c0fe-c0fe-c0fe-c0fec0fec0fe"),
1009
+ width=42,
1010
+ height=42,
1011
+ url="http://aaaa",
1012
+ )
1013
+ elt = CachedElement.create(
1014
+ id=UUID("12341234-1234-1234-1234-123412341234"),
1015
+ type="parent",
1016
+ image_id=image.id,
1017
+ polygon="[[0, 0], [0, 1000], [1000, 1000], [1000, 0], [0, 0]]",
1018
+ )
1019
+ responses.add(
1020
+ responses.POST,
1021
+ "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/children/bulk/",
1022
+ status=200,
1023
+ json=[{"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}],
1024
+ )
1025
+
1026
+ created_ids = mock_elements_worker_with_cache.create_elements(
1027
+ parent=elt,
1028
+ elements=[
1029
+ {
1030
+ "name": "0",
1031
+ "type": "something",
1032
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
1033
+ }
1034
+ ],
1035
+ )
1036
+
1037
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
1038
+ assert [
1039
+ (call.request.method, call.request.url) for call in responses.calls
1040
+ ] == BASE_API_CALLS + [
1041
+ (
1042
+ "POST",
1043
+ "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/children/bulk/",
1044
+ ),
1045
+ ]
1046
+ assert json.loads(responses.calls[-1].request.body) == {
1047
+ "elements": [
1048
+ {
1049
+ "name": "0",
1050
+ "type": "something",
1051
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
1052
+ }
1053
+ ],
1054
+ "worker_run_id": "56785678-5678-5678-5678-567856785678",
1055
+ }
1056
+ assert created_ids == [{"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}]
1057
+
1058
+ # Check that created elements were properly stored in SQLite cache
1059
+ assert list(CachedElement.select().order_by(CachedElement.id)) == [
1060
+ elt,
1061
+ CachedElement(
1062
+ id=UUID("497f6eca-6276-4993-bfeb-53cbbbba6f08"),
1063
+ parent_id=elt.id,
1064
+ type="something",
1065
+ image_id="c0fec0fe-c0fe-c0fe-c0fe-c0fec0fec0fe",
1066
+ polygon=[[1, 1], [2, 2], [2, 1], [1, 2]],
1067
+ worker_run_id=UUID("56785678-5678-5678-5678-567856785678"),
1068
+ ),
1069
+ ]
1070
+
1071
+
1072
+ def test_create_elements(responses, mock_elements_worker_with_cache, tmp_path):
1073
+ elt = Element(
1074
+ {
1075
+ "id": "12341234-1234-1234-1234-123412341234",
1076
+ "zone": {
1077
+ "image": {
1078
+ "id": "c0fec0fe-c0fe-c0fe-c0fe-c0fec0fec0fe",
1079
+ "width": 42,
1080
+ "height": 42,
1081
+ "url": "http://aaaa",
1082
+ }
1083
+ },
1084
+ }
1085
+ )
1086
+ responses.add(
1087
+ responses.POST,
1088
+ "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/children/bulk/",
1089
+ status=200,
1090
+ json=[{"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}],
1091
+ )
1092
+
1093
+ created_ids = mock_elements_worker_with_cache.create_elements(
1094
+ parent=elt,
1095
+ elements=[
1096
+ {
1097
+ "name": "0",
1098
+ "type": "something",
1099
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
1100
+ }
1101
+ ],
1102
+ )
1103
+
1104
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
1105
+ assert [
1106
+ (call.request.method, call.request.url) for call in responses.calls
1107
+ ] == BASE_API_CALLS + [
1108
+ (
1109
+ "POST",
1110
+ "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/children/bulk/",
1111
+ ),
1112
+ ]
1113
+ assert json.loads(responses.calls[-1].request.body) == {
1114
+ "elements": [
1115
+ {
1116
+ "name": "0",
1117
+ "type": "something",
1118
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
1119
+ }
1120
+ ],
1121
+ "worker_run_id": "56785678-5678-5678-5678-567856785678",
1122
+ }
1123
+ assert created_ids == [{"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}]
1124
+
1125
+ # Check that created elements were properly stored in SQLite cache
1126
+ assert (tmp_path / "db.sqlite").is_file()
1127
+
1128
+ assert list(CachedElement.select()) == [
1129
+ CachedElement(
1130
+ id=UUID("497f6eca-6276-4993-bfeb-53cbbbba6f08"),
1131
+ parent_id=UUID("12341234-1234-1234-1234-123412341234"),
1132
+ type="something",
1133
+ image_id="c0fec0fe-c0fe-c0fe-c0fe-c0fec0fec0fe",
1134
+ polygon=[[1, 1], [2, 2], [2, 1], [1, 2]],
1135
+ worker_run_id=UUID("56785678-5678-5678-5678-567856785678"),
1136
+ confidence=None,
1137
+ )
1138
+ ]
1139
+
1140
+
1141
+ def test_create_elements_confidence(
1142
+ responses, mock_elements_worker_with_cache, tmp_path
1143
+ ):
1144
+ elt = Element(
1145
+ {
1146
+ "id": "12341234-1234-1234-1234-123412341234",
1147
+ "zone": {
1148
+ "image": {
1149
+ "id": "c0fec0fe-c0fe-c0fe-c0fe-c0fec0fec0fe",
1150
+ "width": 42,
1151
+ "height": 42,
1152
+ "url": "http://aaaa",
1153
+ }
1154
+ },
1155
+ }
1156
+ )
1157
+ responses.add(
1158
+ responses.POST,
1159
+ "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/children/bulk/",
1160
+ status=200,
1161
+ json=[{"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}],
1162
+ )
1163
+
1164
+ created_ids = mock_elements_worker_with_cache.create_elements(
1165
+ parent=elt,
1166
+ elements=[
1167
+ {
1168
+ "name": "0",
1169
+ "type": "something",
1170
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
1171
+ "confidence": 0.42,
1172
+ }
1173
+ ],
1174
+ )
1175
+
1176
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
1177
+ assert [
1178
+ (call.request.method, call.request.url) for call in responses.calls
1179
+ ] == BASE_API_CALLS + [
1180
+ (
1181
+ "POST",
1182
+ "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/children/bulk/",
1183
+ ),
1184
+ ]
1185
+ assert json.loads(responses.calls[-1].request.body) == {
1186
+ "elements": [
1187
+ {
1188
+ "name": "0",
1189
+ "type": "something",
1190
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
1191
+ "confidence": 0.42,
1192
+ }
1193
+ ],
1194
+ "worker_run_id": "56785678-5678-5678-5678-567856785678",
1195
+ }
1196
+ assert created_ids == [{"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"}]
1197
+
1198
+ # Check that created elements were properly stored in SQLite cache
1199
+ assert (tmp_path / "db.sqlite").is_file()
1200
+
1201
+ assert list(CachedElement.select()) == [
1202
+ CachedElement(
1203
+ id=UUID("497f6eca-6276-4993-bfeb-53cbbbba6f08"),
1204
+ parent_id=UUID("12341234-1234-1234-1234-123412341234"),
1205
+ type="something",
1206
+ image_id="c0fec0fe-c0fe-c0fe-c0fe-c0fec0fec0fe",
1207
+ polygon=[[1, 1], [2, 2], [2, 1], [1, 2]],
1208
+ worker_run_id=UUID("56785678-5678-5678-5678-567856785678"),
1209
+ confidence=0.42,
1210
+ )
1211
+ ]
1212
+
1213
+
1214
+ def test_create_elements_integrity_error(
1215
+ responses, mock_elements_worker_with_cache, caplog
1216
+ ):
1217
+ elt = Element(
1218
+ {
1219
+ "id": "12341234-1234-1234-1234-123412341234",
1220
+ "zone": {
1221
+ "image": {
1222
+ "id": "c0fec0fe-c0fe-c0fe-c0fe-c0fec0fec0fe",
1223
+ "width": 42,
1224
+ "height": 42,
1225
+ "url": "http://aaaa",
1226
+ }
1227
+ },
1228
+ }
1229
+ )
1230
+ responses.add(
1231
+ responses.POST,
1232
+ "http://testserver/api/v1/element/12341234-1234-1234-1234-123412341234/children/bulk/",
1233
+ status=200,
1234
+ json=[
1235
+ # Duplicate IDs, which will cause an IntegrityError when stored in the cache
1236
+ {"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"},
1237
+ {"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"},
1238
+ ],
1239
+ )
1240
+
1241
+ elements = [
1242
+ {
1243
+ "name": "0",
1244
+ "type": "something",
1245
+ "polygon": [[1, 1], [2, 2], [2, 1], [1, 2]],
1246
+ },
1247
+ {
1248
+ "name": "1",
1249
+ "type": "something",
1250
+ "polygon": [[1, 1], [3, 3], [3, 1], [1, 3]],
1251
+ },
1252
+ ]
1253
+
1254
+ created_ids = mock_elements_worker_with_cache.create_elements(
1255
+ parent=elt,
1256
+ elements=elements,
1257
+ )
1258
+
1259
+ assert created_ids == [
1260
+ {"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"},
1261
+ {"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"},
1262
+ ]
1263
+
1264
+ assert len(caplog.records) == 1
1265
+ assert caplog.records[0].levelname == "WARNING"
1266
+ assert caplog.records[0].message.startswith(
1267
+ "Couldn't save created elements in local cache:"
1268
+ )
1269
+
1270
+ assert list(CachedElement.select()) == []
1271
+
1272
+
1273
+ @pytest.mark.parametrize(
1274
+ ("params", "error_message"),
1275
+ [
1276
+ (
1277
+ {"parent": None, "child": None},
1278
+ "parent shouldn't be null and should be of type Element",
1279
+ ),
1280
+ (
1281
+ {"parent": "not an element", "child": None},
1282
+ "parent shouldn't be null and should be of type Element",
1283
+ ),
1284
+ (
1285
+ {"parent": Element(zone=None), "child": None},
1286
+ "child shouldn't be null and should be of type Element",
1287
+ ),
1288
+ (
1289
+ {"parent": Element(zone=None), "child": "not an element"},
1290
+ "child shouldn't be null and should be of type Element",
1291
+ ),
1292
+ ],
1293
+ )
1294
+ def test_create_element_parent_invalid_params(
1295
+ mock_elements_worker, params, error_message
1296
+ ):
1297
+ with pytest.raises(AssertionError, match=re.escape(error_message)):
1298
+ mock_elements_worker.create_element_parent(**params)
1299
+
1300
+
1301
+ def test_create_element_parent_api_error(responses, mock_elements_worker):
1302
+ parent = Element({"id": "12341234-1234-1234-1234-123412341234"})
1303
+ child = Element({"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"})
1304
+ responses.add(
1305
+ responses.POST,
1306
+ "http://testserver/api/v1/element/497f6eca-6276-4993-bfeb-53cbbbba6f08/parent/12341234-1234-1234-1234-123412341234/",
1307
+ status=418,
1308
+ )
1309
+
1310
+ with pytest.raises(ErrorResponse):
1311
+ mock_elements_worker.create_element_parent(
1312
+ parent=parent,
1313
+ child=child,
1314
+ )
1315
+
1316
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
1317
+ assert [
1318
+ (call.request.method, call.request.url) for call in responses.calls
1319
+ ] == BASE_API_CALLS + [
1320
+ (
1321
+ "POST",
1322
+ "http://testserver/api/v1/element/497f6eca-6276-4993-bfeb-53cbbbba6f08/parent/12341234-1234-1234-1234-123412341234/",
1323
+ )
1324
+ ]
1325
+
1326
+
1327
+ def test_create_element_parent(responses, mock_elements_worker):
1328
+ parent = Element({"id": "12341234-1234-1234-1234-123412341234"})
1329
+ child = Element({"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08"})
1330
+ responses.add(
1331
+ responses.POST,
1332
+ "http://testserver/api/v1/element/497f6eca-6276-4993-bfeb-53cbbbba6f08/parent/12341234-1234-1234-1234-123412341234/",
1333
+ status=200,
1334
+ json={
1335
+ "parent": "12341234-1234-1234-1234-123412341234",
1336
+ "child": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
1337
+ },
1338
+ )
1339
+
1340
+ created_element_parent = mock_elements_worker.create_element_parent(
1341
+ parent=parent,
1342
+ child=child,
1343
+ )
1344
+
1345
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
1346
+ assert [
1347
+ (call.request.method, call.request.url) for call in responses.calls
1348
+ ] == BASE_API_CALLS + [
1349
+ (
1350
+ "POST",
1351
+ "http://testserver/api/v1/element/497f6eca-6276-4993-bfeb-53cbbbba6f08/parent/12341234-1234-1234-1234-123412341234/",
1352
+ ),
1353
+ ]
1354
+ assert created_element_parent == {
1355
+ "parent": "12341234-1234-1234-1234-123412341234",
1356
+ "child": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
1357
+ }
1358
+
1359
+
1360
+ @pytest.mark.parametrize(
1361
+ ("payload", "error"),
1362
+ [
1363
+ # Element
1364
+ (
1365
+ {"element": None},
1366
+ "element shouldn't be null and should be an Element or CachedElement",
1367
+ ),
1368
+ (
1369
+ {"element": "not element type"},
1370
+ "element shouldn't be null and should be an Element or CachedElement",
1371
+ ),
1372
+ ],
1373
+ )
1374
+ def test_partial_update_element_wrong_param_element(
1375
+ mock_elements_worker, payload, error
1376
+ ):
1377
+ api_payload = {
1378
+ "element": Element({"zone": None}),
1379
+ **payload,
1380
+ }
1381
+
1382
+ with pytest.raises(AssertionError, match=error):
1383
+ mock_elements_worker.partial_update_element(
1384
+ **api_payload,
1385
+ )
1386
+
1387
+
1388
+ @pytest.mark.parametrize(
1389
+ ("payload", "error"),
1390
+ [
1391
+ # Type
1392
+ ({"type": 1234}, "type should be a str"),
1393
+ ({"type": None}, "type should be a str"),
1394
+ ],
1395
+ )
1396
+ def test_partial_update_element_wrong_param_type(mock_elements_worker, payload, error):
1397
+ api_payload = {
1398
+ "element": Element({"zone": None}),
1399
+ **payload,
1400
+ }
1401
+
1402
+ with pytest.raises(AssertionError, match=error):
1403
+ mock_elements_worker.partial_update_element(
1404
+ **api_payload,
1405
+ )
1406
+
1407
+
1408
+ @pytest.mark.parametrize(
1409
+ ("payload", "error"),
1410
+ [
1411
+ # Name
1412
+ ({"name": 1234}, "name should be a str"),
1413
+ ({"name": None}, "name should be a str"),
1414
+ ],
1415
+ )
1416
+ def test_partial_update_element_wrong_param_name(mock_elements_worker, payload, error):
1417
+ api_payload = {
1418
+ "element": Element({"zone": None}),
1419
+ **payload,
1420
+ }
1421
+
1422
+ with pytest.raises(AssertionError, match=error):
1423
+ mock_elements_worker.partial_update_element(
1424
+ **api_payload,
1425
+ )
1426
+
1427
+
1428
+ @pytest.mark.parametrize(
1429
+ ("payload", "error"),
1430
+ [
1431
+ # Polygon
1432
+ ({"polygon": "not a polygon"}, "polygon should be a list"),
1433
+ ({"polygon": None}, "polygon should be a list"),
1434
+ ({"polygon": [[1, 1], [2, 2]]}, "polygon should have at least three points"),
1435
+ (
1436
+ {"polygon": [[1, 1, 1], [2, 2, 1], [2, 1, 1], [1, 2, 1]]},
1437
+ "polygon points should be lists of two items",
1438
+ ),
1439
+ (
1440
+ {"polygon": [[1], [2], [2], [1]]},
1441
+ "polygon points should be lists of two items",
1442
+ ),
1443
+ (
1444
+ {"polygon": [["not a coord", 1], [2, 2], [2, 1], [1, 2]]},
1445
+ "polygon points should be lists of two numbers",
1446
+ ),
1447
+ ],
1448
+ )
1449
+ def test_partial_update_element_wrong_param_polygon(
1450
+ mock_elements_worker, payload, error
1451
+ ):
1452
+ api_payload = {
1453
+ "element": Element({"zone": None}),
1454
+ **payload,
1455
+ }
1456
+
1457
+ with pytest.raises(AssertionError, match=error):
1458
+ mock_elements_worker.partial_update_element(
1459
+ **api_payload,
1460
+ )
1461
+
1462
+
1463
+ @pytest.mark.parametrize(
1464
+ ("payload", "error"),
1465
+ [
1466
+ # Confidence
1467
+ ({"confidence": "lol"}, "confidence should be None or a float in [0..1] range"),
1468
+ ({"confidence": "0.2"}, "confidence should be None or a float in [0..1] range"),
1469
+ ({"confidence": -1.0}, "confidence should be None or a float in [0..1] range"),
1470
+ ({"confidence": 1.42}, "confidence should be None or a float in [0..1] range"),
1471
+ (
1472
+ {"confidence": float("inf")},
1473
+ "confidence should be None or a float in [0..1] range",
1474
+ ),
1475
+ ],
1476
+ )
1477
+ def test_partial_update_element_wrong_param_conf(mock_elements_worker, payload, error):
1478
+ api_payload = {
1479
+ "element": Element({"zone": None}),
1480
+ **payload,
1481
+ }
1482
+
1483
+ with pytest.raises(AssertionError, match=re.escape(error)):
1484
+ mock_elements_worker.partial_update_element(
1485
+ **api_payload,
1486
+ )
1487
+
1488
+
1489
+ @pytest.mark.parametrize(
1490
+ ("payload", "error"),
1491
+ [
1492
+ # Rotation angle
1493
+ ({"rotation_angle": "lol"}, "rotation_angle should be a positive integer"),
1494
+ ({"rotation_angle": -1}, "rotation_angle should be a positive integer"),
1495
+ ({"rotation_angle": 0.5}, "rotation_angle should be a positive integer"),
1496
+ ({"rotation_angle": None}, "rotation_angle should be a positive integer"),
1497
+ ],
1498
+ )
1499
+ def test_partial_update_element_wrong_param_rota(mock_elements_worker, payload, error):
1500
+ api_payload = {
1501
+ "element": Element({"zone": None}),
1502
+ **payload,
1503
+ }
1504
+
1505
+ with pytest.raises(AssertionError, match=error):
1506
+ mock_elements_worker.partial_update_element(
1507
+ **api_payload,
1508
+ )
1509
+
1510
+
1511
+ @pytest.mark.parametrize(
1512
+ ("payload", "error"),
1513
+ [
1514
+ # Mirrored
1515
+ ({"mirrored": "lol"}, "mirrored should be a boolean"),
1516
+ ({"mirrored": 1234}, "mirrored should be a boolean"),
1517
+ ({"mirrored": None}, "mirrored should be a boolean"),
1518
+ ],
1519
+ )
1520
+ def test_partial_update_element_wrong_param_mir(mock_elements_worker, payload, error):
1521
+ api_payload = {
1522
+ "element": Element({"zone": None}),
1523
+ **payload,
1524
+ }
1525
+
1526
+ with pytest.raises(AssertionError, match=error):
1527
+ mock_elements_worker.partial_update_element(
1528
+ **api_payload,
1529
+ )
1530
+
1531
+
1532
+ @pytest.mark.parametrize(
1533
+ ("payload", "error"),
1534
+ [
1535
+ # Image
1536
+ ({"image": "lol"}, "image should be a UUID"),
1537
+ ({"image": 1234}, "image should be a UUID"),
1538
+ ({"image": None}, "image should be a UUID"),
1539
+ ],
1540
+ )
1541
+ def test_partial_update_element_wrong_param_image(mock_elements_worker, payload, error):
1542
+ api_payload = {
1543
+ "element": Element({"zone": None}),
1544
+ **payload,
1545
+ }
1546
+
1547
+ with pytest.raises(AssertionError, match=error):
1548
+ mock_elements_worker.partial_update_element(
1549
+ **api_payload,
1550
+ )
1551
+
1552
+
1553
+ def test_partial_update_element_api_error(responses, mock_elements_worker):
1554
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1555
+ responses.add(
1556
+ responses.PATCH,
1557
+ f"http://testserver/api/v1/element/{elt.id}/",
1558
+ status=418,
1559
+ )
1560
+
1561
+ with pytest.raises(ErrorResponse):
1562
+ mock_elements_worker.partial_update_element(
1563
+ element=elt,
1564
+ type="something",
1565
+ name="0",
1566
+ polygon=[[1, 1], [2, 2], [2, 1], [1, 2]],
1567
+ )
1568
+
1569
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
1570
+ assert [
1571
+ (call.request.method, call.request.url) for call in responses.calls
1572
+ ] == BASE_API_CALLS + [("PATCH", f"http://testserver/api/v1/element/{elt.id}/")]
1573
+
1574
+
1575
+ @pytest.mark.usefixtures("_mock_cached_elements", "_mock_cached_images")
1576
+ @pytest.mark.parametrize(
1577
+ "payload",
1578
+ [
1579
+ (
1580
+ {
1581
+ "polygon": [[10, 10], [20, 20], [20, 10], [10, 20]],
1582
+ "confidence": None,
1583
+ }
1584
+ ),
1585
+ (
1586
+ {
1587
+ "rotation_angle": 45,
1588
+ "mirrored": False,
1589
+ }
1590
+ ),
1591
+ (
1592
+ {
1593
+ "polygon": [[10, 10], [20, 20], [20, 10], [10, 20]],
1594
+ "confidence": None,
1595
+ "rotation_angle": 45,
1596
+ "mirrored": False,
1597
+ }
1598
+ ),
1599
+ ],
1600
+ )
1601
+ def test_partial_update_element(responses, mock_elements_worker_with_cache, payload):
1602
+ elt = CachedElement.select().first()
1603
+ new_image = CachedImage.select().first()
1604
+
1605
+ elt_response = {
1606
+ "image": str(new_image.id),
1607
+ **payload,
1608
+ }
1609
+ responses.add(
1610
+ responses.PATCH,
1611
+ f"http://testserver/api/v1/element/{elt.id}/",
1612
+ status=200,
1613
+ # UUID not allowed in JSON
1614
+ json=elt_response,
1615
+ )
1616
+
1617
+ element_update_response = mock_elements_worker_with_cache.partial_update_element(
1618
+ element=elt,
1619
+ **{**elt_response, "image": new_image.id},
1620
+ )
1621
+
1622
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
1623
+ assert [
1624
+ (call.request.method, call.request.url) for call in responses.calls
1625
+ ] == BASE_API_CALLS + [
1626
+ (
1627
+ "PATCH",
1628
+ f"http://testserver/api/v1/element/{elt.id}/",
1629
+ ),
1630
+ ]
1631
+ assert json.loads(responses.calls[-1].request.body) == elt_response
1632
+ assert element_update_response == elt_response
1633
+
1634
+ cached_element = CachedElement.get(CachedElement.id == elt.id)
1635
+ # Always present in payload
1636
+ assert str(cached_element.image_id) == elt_response["image"]
1637
+ # Optional params
1638
+ if "polygon" in payload:
1639
+ # Cast to string as this is the only difference compared to model
1640
+ elt_response["polygon"] = str(elt_response["polygon"])
1641
+
1642
+ for param in payload:
1643
+ assert getattr(cached_element, param) == elt_response[param]
1644
+
1645
+
1646
+ @pytest.mark.usefixtures("_mock_cached_elements")
1647
+ @pytest.mark.parametrize("confidence", [None, 0.42])
1648
+ def test_partial_update_element_confidence(
1649
+ responses, mock_elements_worker_with_cache, confidence
1650
+ ):
1651
+ elt = CachedElement.select().first()
1652
+ elt_response = {
1653
+ "polygon": [[10, 10], [20, 20], [20, 10], [10, 20]],
1654
+ "confidence": confidence,
1655
+ }
1656
+ responses.add(
1657
+ responses.PATCH,
1658
+ f"http://testserver/api/v1/element/{elt.id}/",
1659
+ status=200,
1660
+ json=elt_response,
1661
+ )
1662
+
1663
+ element_update_response = mock_elements_worker_with_cache.partial_update_element(
1664
+ element=elt,
1665
+ **elt_response,
1666
+ )
1667
+
1668
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
1669
+ assert [
1670
+ (call.request.method, call.request.url) for call in responses.calls
1671
+ ] == BASE_API_CALLS + [
1672
+ (
1673
+ "PATCH",
1674
+ f"http://testserver/api/v1/element/{elt.id}/",
1675
+ ),
1676
+ ]
1677
+ assert json.loads(responses.calls[-1].request.body) == elt_response
1678
+ assert element_update_response == elt_response
1679
+
1680
+ cached_element = CachedElement.get(CachedElement.id == elt.id)
1681
+ assert cached_element.polygon == str(elt_response["polygon"])
1682
+ assert cached_element.confidence == confidence
1683
+
1684
+
1685
+ def test_list_element_children_wrong_element(mock_elements_worker):
1686
+ with pytest.raises(
1687
+ AssertionError,
1688
+ match="element shouldn't be null and should be an Element or CachedElement",
1689
+ ):
1690
+ mock_elements_worker.list_element_children(element=None)
1691
+
1692
+ with pytest.raises(
1693
+ AssertionError,
1694
+ match="element shouldn't be null and should be an Element or CachedElement",
1695
+ ):
1696
+ mock_elements_worker.list_element_children(element="not element type")
1697
+
1698
+
1699
+ def test_list_element_children_wrong_folder(mock_elements_worker):
1700
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1701
+
1702
+ with pytest.raises(AssertionError, match="folder should be of type bool"):
1703
+ mock_elements_worker.list_element_children(
1704
+ element=elt,
1705
+ folder="not bool",
1706
+ )
1707
+
1708
+
1709
+ def test_list_element_children_wrong_name(mock_elements_worker):
1710
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1711
+
1712
+ with pytest.raises(AssertionError, match="name should be of type str"):
1713
+ mock_elements_worker.list_element_children(
1714
+ element=elt,
1715
+ name=1234,
1716
+ )
1717
+
1718
+
1719
+ def test_list_element_children_wrong_recursive(mock_elements_worker):
1720
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1721
+
1722
+ with pytest.raises(AssertionError, match="recursive should be of type bool"):
1723
+ mock_elements_worker.list_element_children(
1724
+ element=elt,
1725
+ recursive="not bool",
1726
+ )
1727
+
1728
+
1729
+ def test_list_element_children_wrong_type(mock_elements_worker):
1730
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1731
+
1732
+ with pytest.raises(AssertionError, match="type should be of type str"):
1733
+ mock_elements_worker.list_element_children(
1734
+ element=elt,
1735
+ type=1234,
1736
+ )
1737
+
1738
+
1739
+ def test_list_element_children_wrong_with_classes(mock_elements_worker):
1740
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1741
+
1742
+ with pytest.raises(AssertionError, match="with_classes should be of type bool"):
1743
+ mock_elements_worker.list_element_children(
1744
+ element=elt,
1745
+ with_classes="not bool",
1746
+ )
1747
+
1748
+
1749
+ def test_list_element_children_wrong_with_corpus(mock_elements_worker):
1750
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1751
+
1752
+ with pytest.raises(AssertionError, match="with_corpus should be of type bool"):
1753
+ mock_elements_worker.list_element_children(
1754
+ element=elt,
1755
+ with_corpus="not bool",
1756
+ )
1757
+
1758
+
1759
+ def test_list_element_children_wrong_with_has_children(mock_elements_worker):
1760
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1761
+
1762
+ with pytest.raises(
1763
+ AssertionError, match="with_has_children should be of type bool"
1764
+ ):
1765
+ mock_elements_worker.list_element_children(
1766
+ element=elt,
1767
+ with_has_children="not bool",
1768
+ )
1769
+
1770
+
1771
+ def test_list_element_children_wrong_with_zone(mock_elements_worker):
1772
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1773
+
1774
+ with pytest.raises(AssertionError, match="with_zone should be of type bool"):
1775
+ mock_elements_worker.list_element_children(
1776
+ element=elt,
1777
+ with_zone="not bool",
1778
+ )
1779
+
1780
+
1781
+ def test_list_element_children_wrong_with_metadata(mock_elements_worker):
1782
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1783
+
1784
+ with pytest.raises(AssertionError, match="with_metadata should be of type bool"):
1785
+ mock_elements_worker.list_element_children(
1786
+ element=elt,
1787
+ with_metadata="not bool",
1788
+ )
1789
+
1790
+
1791
+ @pytest.mark.parametrize(
1792
+ ("param", "value"),
1793
+ [
1794
+ ("worker_run", 1234),
1795
+ ("transcription_worker_run", 1234),
1796
+ ],
1797
+ )
1798
+ def test_list_element_children_wrong_worker_run(mock_elements_worker, param, value):
1799
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1800
+
1801
+ with pytest.raises(AssertionError, match=f"{param} should be of type str or bool"):
1802
+ mock_elements_worker.list_element_children(
1803
+ element=elt,
1804
+ **{param: value},
1805
+ )
1806
+
1807
+
1808
+ @pytest.mark.parametrize(
1809
+ ("param", "alternative", "value"),
1810
+ [
1811
+ ("worker_version", "worker_run", 1234),
1812
+ ("transcription_worker_version", "transcription_worker_run", 1234),
1813
+ ],
1814
+ )
1815
+ def test_list_element_children_wrong_worker_version(
1816
+ mock_elements_worker, param, alternative, value
1817
+ ):
1818
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1819
+
1820
+ # WARNING: pytest.deprecated_call must be placed BEFORE pytest.raises, otherwise `match` argument won't be checked
1821
+ with (
1822
+ pytest.deprecated_call(
1823
+ match=f"`{param}` usage is deprecated. Consider using `{alternative}` instead."
1824
+ ),
1825
+ pytest.raises(AssertionError, match=f"{param} should be of type str or bool"),
1826
+ ):
1827
+ mock_elements_worker.list_element_children(
1828
+ element=elt,
1829
+ **{param: value},
1830
+ )
1831
+
1832
+
1833
+ @pytest.mark.parametrize(
1834
+ "param",
1835
+ [
1836
+ "worker_run",
1837
+ "transcription_worker_run",
1838
+ ],
1839
+ )
1840
+ def test_list_element_children_wrong_bool_worker_run(mock_elements_worker, param):
1841
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1842
+
1843
+ with pytest.raises(
1844
+ AssertionError, match=f"if of type bool, {param} can only be set to False"
1845
+ ):
1846
+ mock_elements_worker.list_element_children(
1847
+ element=elt,
1848
+ **{param: True},
1849
+ )
1850
+
1851
+
1852
+ @pytest.mark.parametrize(
1853
+ ("param", "alternative"),
1854
+ [
1855
+ ("worker_version", "worker_run"),
1856
+ ("transcription_worker_version", "transcription_worker_run"),
1857
+ ],
1858
+ )
1859
+ def test_list_element_children_wrong_bool_worker_version(
1860
+ mock_elements_worker, param, alternative
1861
+ ):
1862
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1863
+
1864
+ # WARNING: pytest.deprecated_call must be placed BEFORE pytest.raises, otherwise `match` argument won't be checked
1865
+ with (
1866
+ pytest.deprecated_call(
1867
+ match=f"`{param}` usage is deprecated. Consider using `{alternative}` instead."
1868
+ ),
1869
+ pytest.raises(
1870
+ AssertionError, match=f"if of type bool, {param} can only be set to False"
1871
+ ),
1872
+ ):
1873
+ mock_elements_worker.list_element_children(
1874
+ element=elt,
1875
+ **{param: True},
1876
+ )
1877
+
1878
+
1879
+ def test_list_element_children_api_error(responses, mock_elements_worker):
1880
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1881
+ responses.add(
1882
+ responses.GET,
1883
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/children/",
1884
+ status=418,
1885
+ )
1886
+
1887
+ with pytest.raises(
1888
+ Exception, match="Stopping pagination as data will be incomplete"
1889
+ ):
1890
+ next(mock_elements_worker.list_element_children(element=elt))
1891
+
1892
+ assert len(responses.calls) == len(BASE_API_CALLS) + 5
1893
+ assert [
1894
+ (call.request.method, call.request.url) for call in responses.calls
1895
+ ] == BASE_API_CALLS + [
1896
+ # We do 5 retries
1897
+ (
1898
+ "GET",
1899
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/children/",
1900
+ ),
1901
+ (
1902
+ "GET",
1903
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/children/",
1904
+ ),
1905
+ (
1906
+ "GET",
1907
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/children/",
1908
+ ),
1909
+ (
1910
+ "GET",
1911
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/children/",
1912
+ ),
1913
+ (
1914
+ "GET",
1915
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/children/",
1916
+ ),
1917
+ ]
1918
+
1919
+
1920
+ def test_list_element_children(responses, mock_elements_worker):
1921
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1922
+ expected_children = [
1923
+ {
1924
+ "id": "0000",
1925
+ "type": "page",
1926
+ "name": "Test",
1927
+ "corpus": {},
1928
+ "thumbnail_url": None,
1929
+ "zone": {},
1930
+ "best_classes": None,
1931
+ "has_children": None,
1932
+ "worker_version_id": None,
1933
+ "worker_run_id": None,
1934
+ },
1935
+ {
1936
+ "id": "1111",
1937
+ "type": "page",
1938
+ "name": "Test 2",
1939
+ "corpus": {},
1940
+ "thumbnail_url": None,
1941
+ "zone": {},
1942
+ "best_classes": None,
1943
+ "has_children": None,
1944
+ "worker_version_id": None,
1945
+ "worker_run_id": None,
1946
+ },
1947
+ {
1948
+ "id": "2222",
1949
+ "type": "page",
1950
+ "name": "Test 3",
1951
+ "corpus": {},
1952
+ "thumbnail_url": None,
1953
+ "zone": {},
1954
+ "best_classes": None,
1955
+ "has_children": None,
1956
+ "worker_version_id": None,
1957
+ "worker_run_id": None,
1958
+ },
1959
+ ]
1960
+ responses.add(
1961
+ responses.GET,
1962
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/children/",
1963
+ status=200,
1964
+ json={
1965
+ "count": 3,
1966
+ "next": None,
1967
+ "results": expected_children,
1968
+ },
1969
+ )
1970
+
1971
+ for idx, child in enumerate(
1972
+ mock_elements_worker.list_element_children(element=elt)
1973
+ ):
1974
+ assert child == expected_children[idx]
1975
+
1976
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
1977
+ assert [
1978
+ (call.request.method, call.request.url) for call in responses.calls
1979
+ ] == BASE_API_CALLS + [
1980
+ (
1981
+ "GET",
1982
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/children/",
1983
+ ),
1984
+ ]
1985
+
1986
+
1987
+ def test_list_element_children_manual_worker_version(responses, mock_elements_worker):
1988
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
1989
+ expected_children = [
1990
+ {
1991
+ "id": "0000",
1992
+ "type": "page",
1993
+ "name": "Test",
1994
+ "corpus": {},
1995
+ "thumbnail_url": None,
1996
+ "zone": {},
1997
+ "best_classes": None,
1998
+ "has_children": None,
1999
+ "worker_version_id": None,
2000
+ "worker_run_id": None,
2001
+ }
2002
+ ]
2003
+ responses.add(
2004
+ responses.GET,
2005
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/children/?worker_version=False",
2006
+ status=200,
2007
+ json={
2008
+ "count": 1,
2009
+ "next": None,
2010
+ "results": expected_children,
2011
+ },
2012
+ )
2013
+
2014
+ with pytest.deprecated_call(
2015
+ match="`worker_version` usage is deprecated. Consider using `worker_run` instead."
2016
+ ):
2017
+ for idx, child in enumerate(
2018
+ mock_elements_worker.list_element_children(
2019
+ element=elt, worker_version=False
2020
+ )
2021
+ ):
2022
+ assert child == expected_children[idx]
2023
+
2024
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
2025
+ assert [
2026
+ (call.request.method, call.request.url) for call in responses.calls
2027
+ ] == BASE_API_CALLS + [
2028
+ (
2029
+ "GET",
2030
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/children/?worker_version=False",
2031
+ ),
2032
+ ]
2033
+
2034
+
2035
+ def test_list_element_children_manual_worker_run(responses, mock_elements_worker):
2036
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2037
+ expected_children = [
2038
+ {
2039
+ "id": "0000",
2040
+ "type": "page",
2041
+ "name": "Test",
2042
+ "corpus": {},
2043
+ "thumbnail_url": None,
2044
+ "zone": {},
2045
+ "best_classes": None,
2046
+ "has_children": None,
2047
+ "worker_version_id": None,
2048
+ "worker_run_id": None,
2049
+ }
2050
+ ]
2051
+ responses.add(
2052
+ responses.GET,
2053
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/children/?worker_run=False",
2054
+ status=200,
2055
+ json={
2056
+ "count": 1,
2057
+ "next": None,
2058
+ "results": expected_children,
2059
+ },
2060
+ )
2061
+
2062
+ for idx, child in enumerate(
2063
+ mock_elements_worker.list_element_children(element=elt, worker_run=False)
2064
+ ):
2065
+ assert child == expected_children[idx]
2066
+
2067
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
2068
+ assert [
2069
+ (call.request.method, call.request.url) for call in responses.calls
2070
+ ] == BASE_API_CALLS + [
2071
+ (
2072
+ "GET",
2073
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/children/?worker_run=False",
2074
+ ),
2075
+ ]
2076
+
2077
+
2078
+ def test_list_element_children_with_cache_unhandled_param(
2079
+ mock_elements_worker_with_cache,
2080
+ ):
2081
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2082
+
2083
+ with pytest.raises(
2084
+ AssertionError,
2085
+ match="When using the local cache, you can only filter by 'type' and/or 'worker_version' and/or 'worker_run'",
2086
+ ):
2087
+ mock_elements_worker_with_cache.list_element_children(
2088
+ element=elt, with_corpus=True
2089
+ )
2090
+
2091
+
2092
+ @pytest.mark.usefixtures("_mock_cached_elements")
2093
+ @pytest.mark.parametrize(
2094
+ ("filters", "expected_ids"),
2095
+ [
2096
+ # Filter on element should give all elements inserted
2097
+ (
2098
+ {
2099
+ "element": CachedElement(id="12341234-1234-1234-1234-123412341234"),
2100
+ },
2101
+ (
2102
+ "11111111-1111-1111-1111-111111111111",
2103
+ "22222222-2222-2222-2222-222222222222",
2104
+ "33333333-3333-3333-3333-333333333333",
2105
+ ),
2106
+ ),
2107
+ # Filter on element and page should give the second element
2108
+ (
2109
+ {
2110
+ "element": CachedElement(id="12341234-1234-1234-1234-123412341234"),
2111
+ "type": "page",
2112
+ },
2113
+ ("22222222-2222-2222-2222-222222222222",),
2114
+ ),
2115
+ # Filter on element and worker run should give second
2116
+ (
2117
+ {
2118
+ "element": CachedElement(id="12341234-1234-1234-1234-123412341234"),
2119
+ "worker_run": "56785678-5678-5678-5678-567856785678",
2120
+ },
2121
+ ("22222222-2222-2222-2222-222222222222",),
2122
+ ),
2123
+ # Filter on element, manual worker run should give first and third
2124
+ (
2125
+ {
2126
+ "element": CachedElement(id="12341234-1234-1234-1234-123412341234"),
2127
+ "worker_run": False,
2128
+ },
2129
+ (
2130
+ "11111111-1111-1111-1111-111111111111",
2131
+ "33333333-3333-3333-3333-333333333333",
2132
+ ),
2133
+ ),
2134
+ ],
2135
+ )
2136
+ def test_list_element_children_with_cache(
2137
+ responses,
2138
+ mock_elements_worker_with_cache,
2139
+ filters,
2140
+ expected_ids,
2141
+ ):
2142
+ # Check we have 5 elements already present in database
2143
+ assert CachedElement.select().count() == 5
2144
+
2145
+ # Query database through cache
2146
+ elements = mock_elements_worker_with_cache.list_element_children(**filters)
2147
+ assert elements.count() == len(expected_ids)
2148
+ for child, expected_id in zip(elements.order_by("id"), expected_ids, strict=True):
2149
+ assert child.id == UUID(expected_id)
2150
+
2151
+ # Check the worker never hits the API for elements
2152
+ assert len(responses.calls) == len(BASE_API_CALLS)
2153
+ assert [
2154
+ (call.request.method, call.request.url) for call in responses.calls
2155
+ ] == BASE_API_CALLS
2156
+
2157
+
2158
+ @pytest.mark.usefixtures("_mock_cached_elements")
2159
+ @pytest.mark.parametrize(
2160
+ ("filters", "expected_ids"),
2161
+ [
2162
+ # Filter on element and worker version
2163
+ (
2164
+ {
2165
+ "element": CachedElement(id="12341234-1234-1234-1234-123412341234"),
2166
+ "worker_version": "56785678-5678-5678-5678-567856785678",
2167
+ },
2168
+ (
2169
+ "11111111-1111-1111-1111-111111111111",
2170
+ "22222222-2222-2222-2222-222222222222",
2171
+ ),
2172
+ ),
2173
+ # Filter on element, type double_page and worker version
2174
+ (
2175
+ {
2176
+ "element": CachedElement(id="12341234-1234-1234-1234-123412341234"),
2177
+ "type": "page",
2178
+ "worker_version": "56785678-5678-5678-5678-567856785678",
2179
+ },
2180
+ ("22222222-2222-2222-2222-222222222222",),
2181
+ ),
2182
+ # Filter on element, manual worker version
2183
+ (
2184
+ {
2185
+ "element": CachedElement(id="12341234-1234-1234-1234-123412341234"),
2186
+ "worker_version": False,
2187
+ },
2188
+ ("33333333-3333-3333-3333-333333333333",),
2189
+ ),
2190
+ ],
2191
+ )
2192
+ def test_list_element_children_with_cache_deprecation(
2193
+ responses,
2194
+ mock_elements_worker_with_cache,
2195
+ filters,
2196
+ expected_ids,
2197
+ ):
2198
+ # Check we have 5 elements already present in database
2199
+ assert CachedElement.select().count() == 5
2200
+
2201
+ with pytest.deprecated_call(
2202
+ match="`worker_version` usage is deprecated. Consider using `worker_run` instead."
2203
+ ):
2204
+ # Query database through cache
2205
+ elements = mock_elements_worker_with_cache.list_element_children(**filters)
2206
+ assert elements.count() == len(expected_ids)
2207
+ for child, expected_id in zip(elements.order_by("id"), expected_ids, strict=True):
2208
+ assert child.id == UUID(expected_id)
2209
+
2210
+ # Check the worker never hits the API for elements
2211
+ assert len(responses.calls) == len(BASE_API_CALLS)
2212
+ assert [
2213
+ (call.request.method, call.request.url) for call in responses.calls
2214
+ ] == BASE_API_CALLS
2215
+
2216
+
2217
+ def test_list_element_parents_wrong_element(mock_elements_worker):
2218
+ with pytest.raises(
2219
+ AssertionError,
2220
+ match="element shouldn't be null and should be an Element or CachedElement",
2221
+ ):
2222
+ mock_elements_worker.list_element_parents(element=None)
2223
+
2224
+ with pytest.raises(
2225
+ AssertionError,
2226
+ match="element shouldn't be null and should be an Element or CachedElement",
2227
+ ):
2228
+ mock_elements_worker.list_element_parents(element="not element type")
2229
+
2230
+
2231
+ def test_list_element_parents_wrong_folder(mock_elements_worker):
2232
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2233
+
2234
+ with pytest.raises(AssertionError, match="folder should be of type bool"):
2235
+ mock_elements_worker.list_element_parents(
2236
+ element=elt,
2237
+ folder="not bool",
2238
+ )
2239
+
2240
+
2241
+ def test_list_element_parents_wrong_name(mock_elements_worker):
2242
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2243
+
2244
+ with pytest.raises(AssertionError, match="name should be of type str"):
2245
+ mock_elements_worker.list_element_parents(
2246
+ element=elt,
2247
+ name=1234,
2248
+ )
2249
+
2250
+
2251
+ def test_list_element_parents_wrong_recursive(mock_elements_worker):
2252
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2253
+
2254
+ with pytest.raises(AssertionError, match="recursive should be of type bool"):
2255
+ mock_elements_worker.list_element_parents(
2256
+ element=elt,
2257
+ recursive="not bool",
2258
+ )
2259
+
2260
+
2261
+ def test_list_element_parents_wrong_type(mock_elements_worker):
2262
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2263
+
2264
+ with pytest.raises(AssertionError, match="type should be of type str"):
2265
+ mock_elements_worker.list_element_parents(
2266
+ element=elt,
2267
+ type=1234,
2268
+ )
2269
+
2270
+
2271
+ def test_list_element_parents_wrong_with_classes(mock_elements_worker):
2272
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2273
+
2274
+ with pytest.raises(AssertionError, match="with_classes should be of type bool"):
2275
+ mock_elements_worker.list_element_parents(
2276
+ element=elt,
2277
+ with_classes="not bool",
2278
+ )
2279
+
2280
+
2281
+ def test_list_element_parents_wrong_with_corpus(mock_elements_worker):
2282
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2283
+
2284
+ with pytest.raises(AssertionError, match="with_corpus should be of type bool"):
2285
+ mock_elements_worker.list_element_parents(
2286
+ element=elt,
2287
+ with_corpus="not bool",
2288
+ )
2289
+
2290
+
2291
+ def test_list_element_parents_wrong_with_has_children(mock_elements_worker):
2292
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2293
+
2294
+ with pytest.raises(
2295
+ AssertionError, match="with_has_children should be of type bool"
2296
+ ):
2297
+ mock_elements_worker.list_element_parents(
2298
+ element=elt,
2299
+ with_has_children="not bool",
2300
+ )
2301
+
2302
+
2303
+ def test_list_element_parents_wrong_with_zone(mock_elements_worker):
2304
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2305
+
2306
+ with pytest.raises(AssertionError, match="with_zone should be of type bool"):
2307
+ mock_elements_worker.list_element_parents(
2308
+ element=elt,
2309
+ with_zone="not bool",
2310
+ )
2311
+
2312
+
2313
+ def test_list_element_parents_wrong_with_metadata(mock_elements_worker):
2314
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2315
+
2316
+ with pytest.raises(AssertionError, match="with_metadata should be of type bool"):
2317
+ mock_elements_worker.list_element_parents(
2318
+ element=elt,
2319
+ with_metadata="not bool",
2320
+ )
2321
+
2322
+
2323
+ @pytest.mark.parametrize(
2324
+ ("param", "value"),
2325
+ [
2326
+ ("worker_run", 1234),
2327
+ ("transcription_worker_run", 1234),
2328
+ ],
2329
+ )
2330
+ def test_list_element_parents_wrong_worker_run(mock_elements_worker, param, value):
2331
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2332
+
2333
+ with pytest.raises(AssertionError, match=f"{param} should be of type str or bool"):
2334
+ mock_elements_worker.list_element_parents(
2335
+ element=elt,
2336
+ **{param: value},
2337
+ )
2338
+
2339
+
2340
+ @pytest.mark.parametrize(
2341
+ ("param", "alternative", "value"),
2342
+ [
2343
+ ("worker_version", "worker_run", 1234),
2344
+ ("transcription_worker_version", "transcription_worker_run", 1234),
2345
+ ],
2346
+ )
2347
+ def test_list_element_parents_wrong_worker_version(
2348
+ mock_elements_worker, param, alternative, value
2349
+ ):
2350
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2351
+
2352
+ # WARNING: pytest.deprecated_call must be placed BEFORE pytest.raises, otherwise `match` argument won't be checked
2353
+ with (
2354
+ pytest.deprecated_call(
2355
+ match=f"`{param}` usage is deprecated. Consider using `{alternative}` instead."
2356
+ ),
2357
+ pytest.raises(AssertionError, match=f"{param} should be of type str or bool"),
2358
+ ):
2359
+ mock_elements_worker.list_element_parents(
2360
+ element=elt,
2361
+ **{param: value},
2362
+ )
2363
+
2364
+
2365
+ @pytest.mark.parametrize(
2366
+ "param",
2367
+ [
2368
+ "worker_run",
2369
+ "transcription_worker_run",
2370
+ ],
2371
+ )
2372
+ def test_list_element_parents_wrong_bool_worker_run(mock_elements_worker, param):
2373
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2374
+
2375
+ with pytest.raises(
2376
+ AssertionError, match=f"if of type bool, {param} can only be set to False"
2377
+ ):
2378
+ mock_elements_worker.list_element_parents(
2379
+ element=elt,
2380
+ **{param: True},
2381
+ )
2382
+
2383
+
2384
+ @pytest.mark.parametrize(
2385
+ ("param", "alternative"),
2386
+ [
2387
+ ("worker_version", "worker_run"),
2388
+ ("transcription_worker_version", "transcription_worker_run"),
2389
+ ],
2390
+ )
2391
+ def test_list_element_parents_wrong_bool_worker_version(
2392
+ mock_elements_worker, param, alternative
2393
+ ):
2394
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2395
+
2396
+ # WARNING: pytest.deprecated_call must be placed BEFORE pytest.raises, otherwise `match` argument won't be checked
2397
+ with (
2398
+ pytest.deprecated_call(
2399
+ match=f"`{param}` usage is deprecated. Consider using `{alternative}` instead."
2400
+ ),
2401
+ pytest.raises(
2402
+ AssertionError, match=f"if of type bool, {param} can only be set to False"
2403
+ ),
2404
+ ):
2405
+ mock_elements_worker.list_element_parents(
2406
+ element=elt,
2407
+ **{param: True},
2408
+ )
2409
+
2410
+
2411
+ def test_list_element_parents_api_error(responses, mock_elements_worker):
2412
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2413
+ responses.add(
2414
+ responses.GET,
2415
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/parents/",
2416
+ status=418,
2417
+ )
2418
+
2419
+ with pytest.raises(
2420
+ Exception, match="Stopping pagination as data will be incomplete"
2421
+ ):
2422
+ next(mock_elements_worker.list_element_parents(element=elt))
2423
+
2424
+ assert len(responses.calls) == len(BASE_API_CALLS) + 5
2425
+ assert [
2426
+ (call.request.method, call.request.url) for call in responses.calls
2427
+ ] == BASE_API_CALLS + [
2428
+ # We do 5 retries
2429
+ (
2430
+ "GET",
2431
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/parents/",
2432
+ ),
2433
+ (
2434
+ "GET",
2435
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/parents/",
2436
+ ),
2437
+ (
2438
+ "GET",
2439
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/parents/",
2440
+ ),
2441
+ (
2442
+ "GET",
2443
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/parents/",
2444
+ ),
2445
+ (
2446
+ "GET",
2447
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/parents/",
2448
+ ),
2449
+ ]
2450
+
2451
+
2452
+ def test_list_element_parents(responses, mock_elements_worker):
2453
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2454
+ expected_parents = [
2455
+ {
2456
+ "id": "0000",
2457
+ "type": "page",
2458
+ "name": "Test",
2459
+ "corpus": {},
2460
+ "thumbnail_url": None,
2461
+ "zone": {},
2462
+ "best_classes": None,
2463
+ "has_children": None,
2464
+ "worker_version_id": None,
2465
+ "worker_run_id": None,
2466
+ },
2467
+ {
2468
+ "id": "1111",
2469
+ "type": "page",
2470
+ "name": "Test 2",
2471
+ "corpus": {},
2472
+ "thumbnail_url": None,
2473
+ "zone": {},
2474
+ "best_classes": None,
2475
+ "has_children": None,
2476
+ "worker_version_id": None,
2477
+ "worker_run_id": None,
2478
+ },
2479
+ {
2480
+ "id": "2222",
2481
+ "type": "page",
2482
+ "name": "Test 3",
2483
+ "corpus": {},
2484
+ "thumbnail_url": None,
2485
+ "zone": {},
2486
+ "best_classes": None,
2487
+ "has_children": None,
2488
+ "worker_version_id": None,
2489
+ "worker_run_id": None,
2490
+ },
2491
+ ]
2492
+ responses.add(
2493
+ responses.GET,
2494
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/parents/",
2495
+ status=200,
2496
+ json={
2497
+ "count": 3,
2498
+ "next": None,
2499
+ "results": expected_parents,
2500
+ },
2501
+ )
2502
+
2503
+ for idx, parent in enumerate(
2504
+ mock_elements_worker.list_element_parents(element=elt)
2505
+ ):
2506
+ assert parent == expected_parents[idx]
2507
+
2508
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
2509
+ assert [
2510
+ (call.request.method, call.request.url) for call in responses.calls
2511
+ ] == BASE_API_CALLS + [
2512
+ (
2513
+ "GET",
2514
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/parents/",
2515
+ ),
2516
+ ]
2517
+
2518
+
2519
+ def test_list_element_parents_manual_worker_version(responses, mock_elements_worker):
2520
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2521
+ expected_parents = [
2522
+ {
2523
+ "id": "0000",
2524
+ "type": "page",
2525
+ "name": "Test",
2526
+ "corpus": {},
2527
+ "thumbnail_url": None,
2528
+ "zone": {},
2529
+ "best_classes": None,
2530
+ "has_children": None,
2531
+ "worker_version_id": None,
2532
+ "worker_run_id": None,
2533
+ }
2534
+ ]
2535
+ responses.add(
2536
+ responses.GET,
2537
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/parents/?worker_version=False",
2538
+ status=200,
2539
+ json={
2540
+ "count": 1,
2541
+ "next": None,
2542
+ "results": expected_parents,
2543
+ },
2544
+ )
2545
+
2546
+ with pytest.deprecated_call(
2547
+ match="`worker_version` usage is deprecated. Consider using `worker_run` instead."
2548
+ ):
2549
+ for idx, parent in enumerate(
2550
+ mock_elements_worker.list_element_parents(element=elt, worker_version=False)
2551
+ ):
2552
+ assert parent == expected_parents[idx]
2553
+
2554
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
2555
+ assert [
2556
+ (call.request.method, call.request.url) for call in responses.calls
2557
+ ] == BASE_API_CALLS + [
2558
+ (
2559
+ "GET",
2560
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/parents/?worker_version=False",
2561
+ ),
2562
+ ]
2563
+
2564
+
2565
+ def test_list_element_parents_manual_worker_run(responses, mock_elements_worker):
2566
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2567
+ expected_parents = [
2568
+ {
2569
+ "id": "0000",
2570
+ "type": "page",
2571
+ "name": "Test",
2572
+ "corpus": {},
2573
+ "thumbnail_url": None,
2574
+ "zone": {},
2575
+ "best_classes": None,
2576
+ "has_children": None,
2577
+ "worker_version_id": None,
2578
+ "worker_run_id": None,
2579
+ }
2580
+ ]
2581
+ responses.add(
2582
+ responses.GET,
2583
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/parents/?worker_run=False",
2584
+ status=200,
2585
+ json={
2586
+ "count": 1,
2587
+ "next": None,
2588
+ "results": expected_parents,
2589
+ },
2590
+ )
2591
+
2592
+ for idx, parent in enumerate(
2593
+ mock_elements_worker.list_element_parents(element=elt, worker_run=False)
2594
+ ):
2595
+ assert parent == expected_parents[idx]
2596
+
2597
+ assert len(responses.calls) == len(BASE_API_CALLS) + 1
2598
+ assert [
2599
+ (call.request.method, call.request.url) for call in responses.calls
2600
+ ] == BASE_API_CALLS + [
2601
+ (
2602
+ "GET",
2603
+ "http://testserver/api/v1/elements/12341234-1234-1234-1234-123412341234/parents/?worker_run=False",
2604
+ ),
2605
+ ]
2606
+
2607
+
2608
+ def test_list_element_parents_with_cache_unhandled_param(
2609
+ mock_elements_worker_with_cache,
2610
+ ):
2611
+ elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
2612
+
2613
+ with pytest.raises(
2614
+ AssertionError,
2615
+ match="When using the local cache, you can only filter by 'type' and/or 'worker_version' and/or 'worker_run'",
2616
+ ):
2617
+ mock_elements_worker_with_cache.list_element_parents(
2618
+ element=elt, with_corpus=True
2619
+ )
2620
+
2621
+
2622
+ @pytest.mark.usefixtures("_mock_cached_elements")
2623
+ @pytest.mark.parametrize(
2624
+ ("filters", "expected_id"),
2625
+ [
2626
+ # Filter on element
2627
+ (
2628
+ {
2629
+ "element": CachedElement(id="11111111-1111-1111-1111-111111111111"),
2630
+ },
2631
+ "12341234-1234-1234-1234-123412341234",
2632
+ ),
2633
+ # Filter on element and double_page
2634
+ (
2635
+ {
2636
+ "element": CachedElement(id="22222222-2222-2222-2222-222222222222"),
2637
+ "type": "double_page",
2638
+ },
2639
+ "12341234-1234-1234-1234-123412341234",
2640
+ ),
2641
+ # Filter on element and worker run
2642
+ (
2643
+ {
2644
+ "element": CachedElement(id="22222222-2222-2222-2222-222222222222"),
2645
+ "worker_run": "56785678-5678-5678-5678-567856785678",
2646
+ },
2647
+ "12341234-1234-1234-1234-123412341234",
2648
+ ),
2649
+ # Filter on element, manual worker run
2650
+ (
2651
+ {
2652
+ "element": CachedElement(id="12341234-1234-1234-1234-123412341234"),
2653
+ "worker_run": False,
2654
+ },
2655
+ "99999999-9999-9999-9999-999999999999",
2656
+ ),
2657
+ ],
2658
+ )
2659
+ def test_list_element_parents_with_cache(
2660
+ responses,
2661
+ mock_elements_worker_with_cache,
2662
+ filters,
2663
+ expected_id,
2664
+ ):
2665
+ # Check we have 5 elements already present in database
2666
+ assert CachedElement.select().count() == 5
2667
+
2668
+ # Query database through cache
2669
+ elements = mock_elements_worker_with_cache.list_element_parents(**filters)
2670
+ assert elements.count() == 1
2671
+ for parent in elements.order_by("id"):
2672
+ assert parent.id == UUID(expected_id)
2673
+
2674
+ # Check the worker never hits the API for elements
2675
+ assert len(responses.calls) == len(BASE_API_CALLS)
2676
+ assert [
2677
+ (call.request.method, call.request.url) for call in responses.calls
2678
+ ] == BASE_API_CALLS
2679
+
2680
+
2681
+ @pytest.mark.usefixtures("_mock_cached_elements")
2682
+ @pytest.mark.parametrize(
2683
+ ("filters", "expected_id"),
2684
+ [
2685
+ # Filter on element and worker version
2686
+ (
2687
+ {
2688
+ "element": CachedElement(id="33333333-3333-3333-3333-333333333333"),
2689
+ "worker_version": "56785678-5678-5678-5678-567856785678",
2690
+ },
2691
+ "12341234-1234-1234-1234-123412341234",
2692
+ ),
2693
+ # Filter on element, type double_page and worker version
2694
+ (
2695
+ {
2696
+ "element": CachedElement(id="11111111-1111-1111-1111-111111111111"),
2697
+ "type": "double_page",
2698
+ "worker_version": "56785678-5678-5678-5678-567856785678",
2699
+ },
2700
+ "12341234-1234-1234-1234-123412341234",
2701
+ ),
2702
+ # Filter on element, manual worker version
2703
+ (
2704
+ {
2705
+ "element": CachedElement(id="12341234-1234-1234-1234-123412341234"),
2706
+ "worker_version": False,
2707
+ },
2708
+ "99999999-9999-9999-9999-999999999999",
2709
+ ),
2710
+ ],
2711
+ )
2712
+ def test_list_element_parents_with_cache_deprecation(
2713
+ responses,
2714
+ mock_elements_worker_with_cache,
2715
+ filters,
2716
+ expected_id,
2717
+ ):
2718
+ # Check we have 5 elements already present in database
2719
+ assert CachedElement.select().count() == 5
2720
+
2721
+ with pytest.deprecated_call(
2722
+ match="`worker_version` usage is deprecated. Consider using `worker_run` instead."
2723
+ ):
2724
+ # Query database through cache
2725
+ elements = mock_elements_worker_with_cache.list_element_parents(**filters)
2726
+ assert elements.count() == 1
2727
+ for parent in elements.order_by("id"):
2728
+ assert parent.id == UUID(expected_id)
2729
+
2730
+ # Check the worker never hits the API for elements
2731
+ assert len(responses.calls) == len(BASE_API_CALLS)
2732
+ assert [
2733
+ (call.request.method, call.request.url) for call in responses.calls
2734
+ ] == BASE_API_CALLS