1
+ import inspect
1
2
import itertools
2
3
import json
3
4
import logging
11
12
12
13
import openeo
13
14
import openeo .rest .job
14
- from openeo .rest import JobFailedException , OpenEoApiPlainError , OpenEoClientException
15
+ from openeo .rest import JobFailedException , OpenEoApiPlainError , OpenEoClientException , DEFAULT_DOWNLOAD_CHUNK_SIZE
15
16
from openeo .rest .job import BatchJob , ResultAsset
16
17
from openeo .rest .models .general import Link
17
18
from openeo .rest .models .logs import LogEntry
18
19
19
20
API_URL = "https://oeo.test"
20
21
21
- TIFF_CONTENT = b'T1f7D6t6l0l' * 1000
22
-
23
-
22
+ TIFF_CONTENT = b'T1f7D6t6l0l' * 10000
24
23
25
24
@pytest .fixture
26
25
def con100 (requests_mock ):
@@ -74,7 +73,7 @@ def test_execute_batch(con100, requests_mock, tmpdir):
74
73
}
75
74
},
76
75
)
77
- requests_mock . get ( API_URL + "/jobs/f00ba5/files/output.tiff" , text = "tiffdata" )
76
+ _mock_get_head_content ( requests_mock , API_URL + "/jobs/f00ba5/files/output.tiff" , "tiffdata" )
78
77
requests_mock .get (API_URL + "/jobs/f00ba5/logs" , json = {'logs' : []})
79
78
80
79
path = tmpdir .join ("tmp.tiff" )
@@ -231,7 +230,8 @@ def test_execute_batch_with_soft_errors(con100, requests_mock, tmpdir, error_res
231
230
}
232
231
},
233
232
)
234
- requests_mock .get (API_URL + "/jobs/f00ba5/files/output.tiff" , text = "tiffdata" )
233
+ _mock_get_head_content (requests_mock , API_URL + "/jobs/f00ba5/files/output.tiff" , "tiffdata" )
234
+ # requests_mock.get(API_URL + "/jobs/f00ba5/files/output.tiff", text="tiffdata")
235
235
requests_mock .get (API_URL + "/jobs/f00ba5/logs" , json = {'logs' : []})
236
236
237
237
path = tmpdir .join ("tmp.tiff" )
@@ -536,10 +536,28 @@ def job_with_1_asset(con100, requests_mock, tmp_path) -> BatchJob:
536
536
requests_mock .get (API_URL + "/jobs/jj1/results" , json = {"assets" : {
537
537
"1.tiff" : {"href" : API_URL + "/dl/jjr1.tiff" , "type" : "image/tiff; application=geotiff" },
538
538
}})
539
+ requests_mock .head (API_URL + "/dl/jjr1.tiff" , headers = {"Content-Length" : f"{ len (TIFF_CONTENT )} " })
539
540
requests_mock .get (API_URL + "/dl/jjr1.tiff" , content = TIFF_CONTENT )
541
+
540
542
job = BatchJob ("jj1" , connection = con100 )
541
543
return job
542
544
545
+ @pytest .fixture
546
+ def job_with_chunked_asset (con100 , requests_mock , tmp_path ) -> BatchJob :
547
+ requests_mock .get (API_URL + "/jobs/jj1/results" , json = {"assets" : {
548
+ "1.tiff" : {"href" : API_URL + "/dl/jjr1.tiff" , "type" : "image/tiff; application=geotiff" },
549
+ }})
550
+ requests_mock .head (API_URL + "/dl/jjr1.tiff" , headers = {"Content-Length" : f"{ len (TIFF_CONTENT )} " })
551
+
552
+ chunk_size = 1000
553
+ for r in range (0 , len (TIFF_CONTENT ), chunk_size ):
554
+ from_bytes = r
555
+ to_bytes = min (r + chunk_size , len (TIFF_CONTENT )) - 1
556
+ # fail the 1st time, serve the content chunk the 2nd time
557
+ requests_mock .get (API_URL + "/dl/jjr1.tiff" , request_headers = {"Range" : f"bytes={ from_bytes } -{ to_bytes } " },
558
+ response_list = [{"status_code" : 500 , "text" : "Server error" }, {"content" : TIFF_CONTENT [from_bytes :to_bytes + 1 ]}])
559
+ job = BatchJob ("jj1" , connection = con100 )
560
+ return job
543
561
544
562
@pytest .fixture
545
563
def job_with_2_assets (con100 , requests_mock , tmp_path ) -> BatchJob :
@@ -551,8 +569,11 @@ def job_with_2_assets(con100, requests_mock, tmp_path) -> BatchJob:
551
569
"2.tiff" : {"href" : API_URL + "/dl/jjr2.tiff" , "type" : "image/tiff; application=geotiff" },
552
570
}
553
571
})
572
+ requests_mock .head (API_URL + "/dl/jjr1.tiff" , headers = {"Content-Length" : f"{ len (TIFF_CONTENT )} " })
554
573
requests_mock .get (API_URL + "/dl/jjr1.tiff" , content = TIFF_CONTENT )
574
+ requests_mock .head (API_URL + "/dl/jjr2.tiff" , headers = {"Content-Length" : f"{ len (TIFF_CONTENT )} " })
555
575
requests_mock .get (API_URL + "/dl/jjr2.tiff" , content = TIFF_CONTENT )
576
+
556
577
job = BatchJob ("jj2" , connection = con100 )
557
578
return job
558
579
@@ -574,6 +595,13 @@ def test_get_results_download_file(job_with_1_asset: BatchJob, tmp_path):
574
595
with target .open ("rb" ) as f :
575
596
assert f .read () == TIFF_CONTENT
576
597
598
+ def test_get_results_download_chunked_file (job_with_chunked_asset : BatchJob , tmp_path ):
599
+ job = job_with_chunked_asset
600
+ target = tmp_path / "result.tiff"
601
+ res = job .get_results ().download_file (target , chunk_size = 1000 )
602
+ assert res == target
603
+ with target .open ("rb" ) as f :
604
+ assert f .read () == TIFF_CONTENT
577
605
578
606
def test_download_result_folder (job_with_1_asset : BatchJob , tmp_path ):
579
607
job = job_with_1_asset
@@ -714,7 +742,7 @@ def test_get_results_download_files_include_stac_metadata(
714
742
715
743
def test_result_asset_download_file (con100 , requests_mock , tmp_path ):
716
744
href = API_URL + "/dl/jjr1.tiff"
717
- requests_mock . get ( href , content = TIFF_CONTENT )
745
+ _mock_get_head_content ( requests_mock , href , TIFF_CONTENT )
718
746
719
747
job = BatchJob ("jj" , connection = con100 )
720
748
asset = ResultAsset (job , name = "1.tiff" , href = href , metadata = {'type' : 'image/tiff; application=geotiff' })
@@ -729,6 +757,7 @@ def test_result_asset_download_file(con100, requests_mock, tmp_path):
729
757
730
758
def test_result_asset_download_file_error (con100 , requests_mock , tmp_path ):
731
759
href = API_URL + "/dl/jjr1.tiff"
760
+ requests_mock .head (href , status_code = 500 , text = "Nope!" )
732
761
requests_mock .get (href , status_code = 500 , text = "Nope!" )
733
762
734
763
job = BatchJob ("jj" , connection = con100 )
@@ -743,7 +772,7 @@ def test_result_asset_download_file_error(con100, requests_mock, tmp_path):
743
772
744
773
def test_result_asset_download_folder (con100 , requests_mock , tmp_path ):
745
774
href = API_URL + "/dl/jjr1.tiff"
746
- requests_mock . get ( href , content = TIFF_CONTENT )
775
+ _mock_get_head_content ( requests_mock , href , TIFF_CONTENT )
747
776
748
777
job = BatchJob ("jj" , connection = con100 )
749
778
asset = ResultAsset (job , name = "1.tiff" , href = href , metadata = {"type" : "image/tiff; application=geotiff" })
@@ -770,7 +799,7 @@ def test_result_asset_load_json(con100, requests_mock):
770
799
771
800
def test_result_asset_load_bytes (con100 , requests_mock ):
772
801
href = API_URL + "/dl/jjr1.tiff"
773
- requests_mock . get ( href , content = TIFF_CONTENT )
802
+ _mock_get_head_content ( requests_mock , href , TIFF_CONTENT )
774
803
775
804
job = BatchJob ("jj" , connection = con100 )
776
805
asset = ResultAsset (job , name = "out.tiff" , href = href , metadata = {"type" : "image/tiff; application=geotiff" })
@@ -797,6 +826,7 @@ def download_tiff(request, context):
797
826
return TIFF_CONTENT
798
827
799
828
requests_mock .get (API_URL + "/jobs/jj1/results" , json = get_results )
829
+ requests_mock .head ("https://evilcorp.test/dl/jjr1.tiff" , headers = {"Content-Length" : "666" })
800
830
requests_mock .get ("https://evilcorp.test/dl/jjr1.tiff" , content = download_tiff )
801
831
802
832
con100 .authenticate_basic ("john" , "j0hn" )
@@ -880,3 +910,15 @@ def get_jobs(request, context):
880
910
assert jobs .links == [Link (rel = "next" , href = "https://oeo.test/jobs?limit=2&offset=2" )]
881
911
assert jobs .ext_federation_missing () == ["oeob" ]
882
912
assert "Partial job listing: missing federation components: ['oeob']." in caplog .text
913
+
914
+
915
+ def _mock_get_head_content (requests_mock , url : str , content ):
916
+ if callable (content ):
917
+ requests_mock .head (url , headers = {"Content-Length" : "666" })
918
+ requests_mock .get (url , content = content )
919
+ elif type (content ) == str :
920
+ requests_mock .head (url , headers = {"Content-Length" : f"{ len (content )} " })
921
+ requests_mock .get (url , text = content )
922
+ else :
923
+ requests_mock .head (url , headers = {"Content-Length" : f"{ len (content )} " })
924
+ requests_mock .get (url , content = content )
0 commit comments