1
+ from unittest .mock import patch
2
+
1
3
import requests
2
4
import responses
3
5
from responses .matchers import json_params_matcher
4
- from unittest .mock import patch
5
6
6
7
from roboflow import API_URL
7
8
from roboflow .adapters .rfapi import AnnotationSaveError , ImageUploadError
@@ -13,70 +14,68 @@ class TestProject(RoboflowTest):
13
14
def _create_test_dataset (self , images = None ):
14
15
"""
15
16
Create a test dataset with specified images or a default image
16
-
17
+
17
18
Args:
18
19
images: List of image dictionaries. If None, a default image will be used.
19
-
20
+
20
21
Returns:
21
22
Dictionary representing a parsed dataset
22
23
"""
23
24
if images is None :
24
- images = [
25
- {
26
- "file" : "image1.jpg" ,
27
- "split" : "train" ,
28
- "annotationfile" : {
29
- "file" : "image1.xml"
30
- }
31
- }
32
- ]
33
-
34
- return {
35
- "location" : "/test/location/" ,
36
- "images" : images
37
- }
38
-
39
- def _setup_upload_dataset_mocks (self , test_dataset = None , image_return = None , annotation_return = None ,
40
- project_created = False , save_annotation_side_effect = None ,
41
- upload_image_side_effect = None ):
25
+ images = [{"file" : "image1.jpg" , "split" : "train" , "annotationfile" : {"file" : "image1.xml" }}]
26
+
27
+ return {"location" : "/test/location/" , "images" : images }
28
+
29
+ def _setup_upload_dataset_mocks (
30
+ self ,
31
+ test_dataset = None ,
32
+ image_return = None ,
33
+ annotation_return = None ,
34
+ project_created = False ,
35
+ save_annotation_side_effect = None ,
36
+ upload_image_side_effect = None ,
37
+ ):
42
38
"""
43
39
Set up common mocks for upload_dataset tests
44
-
40
+
45
41
Args:
46
42
test_dataset: The dataset to return from parsefolder. If None, creates a default dataset
47
43
image_return: Return value for upload_image. Default is successful upload
48
44
annotation_return: Return value for save_annotation. Default is successful annotation
49
45
project_created: Whether to simulate a newly created project
50
46
save_annotation_side_effect: Side effect function for save_annotation
51
47
upload_image_side_effect: Side effect function for upload_image
52
-
48
+
53
49
Returns:
54
50
Dictionary of mock objects with start and stop methods
55
51
"""
56
52
if test_dataset is None :
57
53
test_dataset = self ._create_test_dataset ()
58
-
54
+
59
55
if image_return is None :
60
56
image_return = ({"id" : "test-id" , "success" : True }, 0.1 , 0 )
61
-
57
+
62
58
if annotation_return is None :
63
59
annotation_return = ({"success" : True }, 0.1 , 0 )
64
-
60
+
65
61
# Create the mock objects
66
62
mocks = {
67
- 'parser' : patch ('roboflow.core.workspace.folderparser.parsefolder' , return_value = test_dataset ),
68
- 'upload' : patch ('roboflow.core.workspace.Project.upload_image' ,
69
- side_effect = upload_image_side_effect ) if upload_image_side_effect
70
- else patch ('roboflow.core.workspace.Project.upload_image' , return_value = image_return ),
71
- 'save_annotation' : patch ('roboflow.core.workspace.Project.save_annotation' ,
72
- side_effect = save_annotation_side_effect ) if save_annotation_side_effect
73
- else patch ('roboflow.core.workspace.Project.save_annotation' , return_value = annotation_return ),
74
- 'get_project' : patch ('roboflow.core.workspace.Workspace._get_or_create_project' ,
75
- return_value = (self .project , project_created ))
63
+ "parser" : patch ("roboflow.core.workspace.folderparser.parsefolder" , return_value = test_dataset ),
64
+ "upload" : patch ("roboflow.core.workspace.Project.upload_image" , side_effect = upload_image_side_effect )
65
+ if upload_image_side_effect
66
+ else patch ("roboflow.core.workspace.Project.upload_image" , return_value = image_return ),
67
+ "save_annotation" : patch (
68
+ "roboflow.core.workspace.Project.save_annotation" , side_effect = save_annotation_side_effect
69
+ )
70
+ if save_annotation_side_effect
71
+ else patch ("roboflow.core.workspace.Project.save_annotation" , return_value = annotation_return ),
72
+ "get_project" : patch (
73
+ "roboflow.core.workspace.Workspace._get_or_create_project" , return_value = (self .project , project_created )
74
+ ),
76
75
}
77
-
76
+
78
77
return mocks
79
-
78
+
80
79
def test_check_valid_image_with_accepted_formats (self ):
81
80
images_to_test = [
82
81
"rabbit.JPG" ,
@@ -292,35 +291,27 @@ def test_create_annotation_job_error(self):
292
291
)
293
292
294
293
self .assertEqual (str (context .exception ), "Batch not found" )
295
-
294
+
296
295
@ordered
297
296
@responses .activate
298
297
def test_project_upload_dataset (self ):
299
298
"""Test upload_dataset functionality with various scenarios"""
300
299
test_scenarios = [
301
300
{
302
301
"name" : "string_annotationdesc" ,
303
- "dataset" : [{
304
- "file" : "test_image.jpg" ,
305
- "split" : "train" ,
306
- "annotationfile" : "string_annotation.txt"
307
- }],
302
+ "dataset" : [{"file" : "test_image.jpg" , "split" : "train" , "annotationfile" : "string_annotation.txt" }],
308
303
"params" : {"num_workers" : 1 },
309
- "assertions" : {}
304
+ "assertions" : {},
310
305
},
311
306
{
312
307
"name" : "success_basic" ,
313
308
"dataset" : [
314
309
{"file" : "image1.jpg" , "split" : "train" , "annotationfile" : {"file" : "image1.xml" }},
315
- {"file" : "image2.jpg" , "split" : "valid" , "annotationfile" : {"file" : "image2.xml" }}
310
+ {"file" : "image2.jpg" , "split" : "valid" , "annotationfile" : {"file" : "image2.xml" }},
316
311
],
317
312
"params" : {},
318
- "assertions" : {
319
- "parser" : [("/test/dataset" ,)],
320
- "upload" : {"count" : 2 },
321
- "save_annotation" : {"count" : 2 }
322
- },
323
- "image_return" : ({"id" : "test-id-1" , "success" : True }, 0.1 , 0 )
313
+ "assertions" : {"parser" : [("/test/dataset" ,)], "upload" : {"count" : 2 }, "save_annotation" : {"count" : 2 }},
314
+ "image_return" : ({"id" : "test-id-1" , "success" : True }, 0.1 , 0 ),
324
315
},
325
316
{
326
317
"name" : "custom_parameters" ,
@@ -330,120 +321,108 @@ def test_project_upload_dataset(self):
330
321
"project_license" : "CC BY 4.0" ,
331
322
"project_type" : "classification" ,
332
323
"batch_name" : "test-batch" ,
333
- "num_retries" : 3
324
+ "num_retries" : 3 ,
334
325
},
335
- "assertions" : {
336
- "upload" : {"count" : 1 , "kwargs" : {"batch_name" : "test-batch" , "num_retry_uploads" : 3 }}
337
- }
326
+ "assertions" : {"upload" : {"count" : 1 , "kwargs" : {"batch_name" : "test-batch" , "num_retry_uploads" : 3 }}},
338
327
},
339
328
{
340
329
"name" : "project_creation" ,
341
330
"dataset" : None ,
342
331
"params" : {"project_name" : "new-project" },
343
332
"assertions" : {},
344
- "project_created" : True
333
+ "project_created" : True ,
345
334
},
346
335
{
347
336
"name" : "with_labelmap" ,
348
- "dataset" : [{
349
- "file" : "image1.jpg" ,
350
- "split" : "train" ,
351
- "annotationfile" : {
352
- "file" : "image1.xml" ,
353
- "labelmap" : "path/to/labelmap.json"
337
+ "dataset" : [
338
+ {
339
+ "file" : "image1.jpg" ,
340
+ "split" : "train" ,
341
+ "annotationfile" : {"file" : "image1.xml" , "labelmap" : "path/to/labelmap.json" },
354
342
}
355
- } ],
343
+ ],
356
344
"params" : {},
357
- "assertions" : {
358
- "save_annotation" : {"count" : 1 },
359
- "load_labelmap" : {"count" : 1 }
360
- },
345
+ "assertions" : {"save_annotation" : {"count" : 1 }, "load_labelmap" : {"count" : 1 }},
361
346
"extra_mocks" : [
362
- ("load_labelmap" , "roboflow.core.workspace.load_labelmap" , {"return_value" : {"old_label" : "new_label" }})
363
- ]
347
+ (
348
+ "load_labelmap" ,
349
+ "roboflow.core.workspace.load_labelmap" ,
350
+ {"return_value" : {"old_label" : "new_label" }},
351
+ )
352
+ ],
364
353
},
365
354
{
366
355
"name" : "concurrent_uploads" ,
367
356
"dataset" : [{"file" : f"image{ i } .jpg" , "split" : "train" } for i in range (10 )],
368
357
"params" : {"num_workers" : 5 },
369
- "assertions" : {
370
- "thread_pool" : {"count" : 1 , "kwargs" : {"max_workers" : 5 }}
371
- },
372
- "extra_mocks" : [
373
- ("thread_pool" , "concurrent.futures.ThreadPoolExecutor" , {})
374
- ]
375
- },
376
- {
377
- "name" : "empty_dataset" ,
378
- "dataset" : [],
379
- "params" : {},
380
- "assertions" : {
381
- "upload" : {"count" : 0 }
382
- }
358
+ "assertions" : {"thread_pool" : {"count" : 1 , "kwargs" : {"max_workers" : 5 }}},
359
+ "extra_mocks" : [("thread_pool" , "concurrent.futures.ThreadPoolExecutor" , {})],
383
360
},
361
+ {"name" : "empty_dataset" , "dataset" : [], "params" : {}, "assertions" : {"upload" : {"count" : 0 }}},
384
362
{
385
363
"name" : "raw_text_annotation" ,
386
- "dataset" : [{
387
- "file" : "image1.jpg" ,
388
- "split" : "train" ,
389
- "annotationfile" : {
390
- "rawText" : "annotation content here" ,
391
- "format" : "json"
364
+ "dataset" : [
365
+ {
366
+ "file" : "image1.jpg" ,
367
+ "split" : "train" ,
368
+ "annotationfile" : {"rawText" : "annotation content here" , "format" : "json" },
392
369
}
393
- } ],
370
+ ],
394
371
"params" : {},
395
- "assertions" : {
396
- "save_annotation" : {"count" : 1 }
397
- }
398
- }
372
+ "assertions" : {"save_annotation" : {"count" : 1 }},
373
+ },
399
374
]
400
-
375
+
401
376
error_cases = [
402
377
{
403
378
"name" : "image_upload_error" ,
404
379
"side_effect" : {
405
- "upload_image_side_effect" : lambda * args , ** kwargs :
406
- (_ for _ in ()).throw (ImageUploadError ("Failed to upload image" ))
380
+ "upload_image_side_effect" : lambda * args , ** kwargs : (_ for _ in ()).throw (
381
+ ImageUploadError ("Failed to upload image" )
382
+ )
407
383
},
408
- "params" : {"num_workers" : 1 }
384
+ "params" : {"num_workers" : 1 },
409
385
},
410
386
{
411
387
"name" : "annotation_upload_error" ,
412
388
"side_effect" : {
413
- "save_annotation_side_effect" : lambda * args , ** kwargs :
414
- (_ for _ in ()).throw (AnnotationSaveError ("Failed to save annotation" ))
389
+ "save_annotation_side_effect" : lambda * args , ** kwargs : (_ for _ in ()).throw (
390
+ AnnotationSaveError ("Failed to save annotation" )
391
+ )
415
392
},
416
- "params" : {"num_workers" : 1 }
417
- }
393
+ "params" : {"num_workers" : 1 },
394
+ },
418
395
]
419
-
396
+
420
397
for scenario in test_scenarios :
421
- test_dataset = self ._create_test_dataset (scenario .get ("dataset" )) if scenario .get ("dataset" ) is not None else None
422
-
398
+ test_dataset = (
399
+ self ._create_test_dataset (scenario .get ("dataset" )) if scenario .get ("dataset" ) is not None else None
400
+ )
401
+
423
402
extra_mocks = {}
424
403
if "extra_mocks" in scenario :
425
404
for mock_name , target , config in scenario .get ("extra_mocks" , []):
426
405
extra_mocks [mock_name ] = patch (target , ** config )
427
-
406
+
428
407
mocks = self ._setup_upload_dataset_mocks (
429
408
test_dataset = test_dataset ,
430
409
image_return = scenario .get ("image_return" ),
431
- project_created = scenario .get ("project_created" , False )
410
+ project_created = scenario .get ("project_created" , False ),
432
411
)
433
-
412
+
434
413
mock_objects = {}
435
414
for name , mock in mocks .items ():
436
415
mock_objects [name ] = mock .start ()
437
-
416
+
438
417
for name , mock in extra_mocks .items ():
439
418
mock_objects [name ] = mock .start ()
440
-
419
+
441
420
try :
442
421
params = {"dataset_path" : "/test/dataset" , "project_name" : PROJECT_NAME }
443
422
params .update (scenario .get ("params" , {}))
444
-
423
+
445
424
self .workspace .upload_dataset (** params )
446
-
425
+
447
426
for mock_name , assertion in scenario .get ("assertions" , {}).items ():
448
427
if isinstance (assertion , list ):
449
428
mock_obj = mock_objects .get (mock_name )
@@ -461,13 +440,13 @@ def test_project_upload_dataset(self):
461
440
finally :
462
441
for mock in list (mocks .values ()) + list (extra_mocks .values ()):
463
442
mock .stop ()
464
-
443
+
465
444
for case in error_cases :
466
445
mocks = self ._setup_upload_dataset_mocks (** case .get ("side_effect" , {}))
467
-
446
+
468
447
for mock in mocks .values ():
469
448
mock .start ()
470
-
449
+
471
450
try :
472
451
params = {"dataset_path" : "/test/dataset" , "project_name" : PROJECT_NAME }
473
452
params .update (case .get ("params" , {}))
0 commit comments