1+ import inspect
12import itertools
23import json
34import logging
1112
1213import openeo
1314import openeo .rest .job
14- from openeo .rest import JobFailedException , OpenEoApiPlainError , OpenEoClientException
15+ from openeo .rest import JobFailedException , OpenEoApiPlainError , OpenEoClientException , DEFAULT_DOWNLOAD_CHUNK_SIZE
1516from openeo .rest .job import BatchJob , ResultAsset
1617from openeo .rest .models .general import Link
1718from openeo .rest .models .logs import LogEntry
1819
1920API_URL = "https://oeo.test"
2021
21- TIFF_CONTENT = b'T1f7D6t6l0l' * 1000
22-
23-
22+ TIFF_CONTENT = b'T1f7D6t6l0l' * 10000
2423
2524@pytest .fixture
2625def con100 (requests_mock ):
@@ -74,7 +73,7 @@ def test_execute_batch(con100, requests_mock, tmpdir):
7473 }
7574 },
7675 )
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" )
7877 requests_mock .get (API_URL + "/jobs/f00ba5/logs" , json = {'logs' : []})
7978
8079 path = tmpdir .join ("tmp.tiff" )
@@ -231,7 +230,8 @@ def test_execute_batch_with_soft_errors(con100, requests_mock, tmpdir, error_res
231230 }
232231 },
233232 )
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")
235235 requests_mock .get (API_URL + "/jobs/f00ba5/logs" , json = {'logs' : []})
236236
237237 path = tmpdir .join ("tmp.tiff" )
@@ -536,10 +536,28 @@ def job_with_1_asset(con100, requests_mock, tmp_path) -> BatchJob:
536536 requests_mock .get (API_URL + "/jobs/jj1/results" , json = {"assets" : {
537537 "1.tiff" : {"href" : API_URL + "/dl/jjr1.tiff" , "type" : "image/tiff; application=geotiff" },
538538 }})
539+ requests_mock .head (API_URL + "/dl/jjr1.tiff" , headers = {"Content-Length" : f"{ len (TIFF_CONTENT )} " })
539540 requests_mock .get (API_URL + "/dl/jjr1.tiff" , content = TIFF_CONTENT )
541+
540542 job = BatchJob ("jj1" , connection = con100 )
541543 return job
542544
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
543561
544562@pytest .fixture
545563def 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:
551569 "2.tiff" : {"href" : API_URL + "/dl/jjr2.tiff" , "type" : "image/tiff; application=geotiff" },
552570 }
553571 })
572+ requests_mock .head (API_URL + "/dl/jjr1.tiff" , headers = {"Content-Length" : f"{ len (TIFF_CONTENT )} " })
554573 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 )} " })
555575 requests_mock .get (API_URL + "/dl/jjr2.tiff" , content = TIFF_CONTENT )
576+
556577 job = BatchJob ("jj2" , connection = con100 )
557578 return job
558579
@@ -574,6 +595,13 @@ def test_get_results_download_file(job_with_1_asset: BatchJob, tmp_path):
574595 with target .open ("rb" ) as f :
575596 assert f .read () == TIFF_CONTENT
576597
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
577605
578606def test_download_result_folder (job_with_1_asset : BatchJob , tmp_path ):
579607 job = job_with_1_asset
@@ -714,7 +742,7 @@ def test_get_results_download_files_include_stac_metadata(
714742
715743def test_result_asset_download_file (con100 , requests_mock , tmp_path ):
716744 href = API_URL + "/dl/jjr1.tiff"
717- requests_mock . get ( href , content = TIFF_CONTENT )
745+ _mock_get_head_content ( requests_mock , href , TIFF_CONTENT )
718746
719747 job = BatchJob ("jj" , connection = con100 )
720748 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):
729757
730758def test_result_asset_download_file_error (con100 , requests_mock , tmp_path ):
731759 href = API_URL + "/dl/jjr1.tiff"
760+ requests_mock .head (href , status_code = 500 , text = "Nope!" )
732761 requests_mock .get (href , status_code = 500 , text = "Nope!" )
733762
734763 job = BatchJob ("jj" , connection = con100 )
@@ -743,7 +772,7 @@ def test_result_asset_download_file_error(con100, requests_mock, tmp_path):
743772
744773def test_result_asset_download_folder (con100 , requests_mock , tmp_path ):
745774 href = API_URL + "/dl/jjr1.tiff"
746- requests_mock . get ( href , content = TIFF_CONTENT )
775+ _mock_get_head_content ( requests_mock , href , TIFF_CONTENT )
747776
748777 job = BatchJob ("jj" , connection = con100 )
749778 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):
770799
771800def test_result_asset_load_bytes (con100 , requests_mock ):
772801 href = API_URL + "/dl/jjr1.tiff"
773- requests_mock . get ( href , content = TIFF_CONTENT )
802+ _mock_get_head_content ( requests_mock , href , TIFF_CONTENT )
774803
775804 job = BatchJob ("jj" , connection = con100 )
776805 asset = ResultAsset (job , name = "out.tiff" , href = href , metadata = {"type" : "image/tiff; application=geotiff" })
@@ -797,6 +826,7 @@ def download_tiff(request, context):
797826 return TIFF_CONTENT
798827
799828 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" })
800830 requests_mock .get ("https://evilcorp.test/dl/jjr1.tiff" , content = download_tiff )
801831
802832 con100 .authenticate_basic ("john" , "j0hn" )
@@ -880,3 +910,15 @@ def get_jobs(request, context):
880910 assert jobs .links == [Link (rel = "next" , href = "https://oeo.test/jobs?limit=2&offset=2" )]
881911 assert jobs .ext_federation_missing () == ["oeob" ]
882912 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