Skip to content

feat(download): first class support for streaming download #2013#2021

Open
pdavid-cssopra wants to merge 2 commits intodevelopfrom
feat-2013-first-class-support-for-streaming-down-in-eodag
Open

feat(download): first class support for streaming download #2013#2021
pdavid-cssopra wants to merge 2 commits intodevelopfrom
feat-2013-first-class-support-for-streaming-down-in-eodag

Conversation

@pdavid-cssopra
Copy link
Collaborator

@pdavid-cssopra pdavid-cssopra commented Jan 30, 2026

What would you like to be added ?

The support for streaming downloads that is already available for use in server mode should also be available for users in the eodag library.
Why is this needed ?

Streaming downloads would allow users to send the data they retrieved from EODAG to another tool of their choice without having to store on their filesystem first. This feature would enable more powerful and performant data processing pipelines using the EODAG library.

the new stream_download method will be used in stac-fastapi-eodag in place of _stream_download_dict

this should also be demonstrated in a tutorial to upload products/assets on a local S3 created with minio: will fix #1325

How should it be implemented ?

Currently there is a method plugin.__stream_download_dict() that is implemented in the download plugins. This method should be used in a public method EODataAccessGateway.stream_download(product) similar to EODataAccessGateway.download(product).
Proposed enhancements:

  • rename plugin._stream_download_dict to plugin.stream_download
  • rename plugin._stream_download to plugin._raw_stream_download
  • add product.stream_download & asset.stream_download methods
  • add a tutorial that uses this method to upload files on a local S3 created with minio

No need to have the stream_download method exposed through EODataAccessGateway.

Technical note

Minio is no longer supported; rustfs is suggested as a replacement for tutorials.

Tests update reveals somes bugs:

@github-actions
Copy link
Contributor

github-actions bot commented Jan 30, 2026

Test Results

    4 files  ± 0      4 suites  ±0   3m 28s ⏱️ +15s
  690 tests + 5    689 ✅ + 5  1 💤 ±0  0 ❌ ±0 
2 802 runs  +20  2 796 ✅ +20  6 💤 ±0  0 ❌ ±0 

Results for commit 4ea1844. ± Comparison against base commit 798a34a.

This pull request removes 6 and adds 11 tests. Note that renamed tests count towards both.
tests.units.test_download_plugins.TestDownloadPluginHttp ‑ test_stream_download_dict_asset_not_available
tests.units.test_download_plugins.TestDownloadPluginHttp ‑ test_stream_download_dict_fallback_to_product
tests.units.test_download_plugins.TestDownloadPluginHttp ‑ test_stream_download_dict_multiple_assets_zip
tests.units.test_download_plugins.TestDownloadPluginHttp ‑ test_stream_download_dict_product_empty_raises
tests.units.test_download_plugins.TestDownloadPluginHttp ‑ test_stream_download_dict_single_asset
tests.units.test_download_plugins.TestDownloadPluginHttp ‑ test_stream_download_dict_single_asset_with_type
tests.units.test_download_plugins.TestDownloadPluginHttp ‑ test_stream_download_asset_not_available
tests.units.test_download_plugins.TestDownloadPluginHttp ‑ test_stream_download_fallback_to_product
tests.units.test_download_plugins.TestDownloadPluginHttp ‑ test_stream_download_multiple_assets_zip
tests.units.test_download_plugins.TestDownloadPluginHttp ‑ test_stream_download_product_empty_raises
tests.units.test_download_plugins.TestDownloadPluginHttp ‑ test_stream_download_single_asset
tests.units.test_download_plugins.TestDownloadPluginHttp ‑ test_stream_download_single_asset_with_type
tests.units.test_eoproduct.TestEOProduct ‑ test_eoproduct_asset_stream_download
tests.units.test_eoproduct.TestEOProduct ‑ test_eoproduct_stream_download
tests.units.test_utils_streamingresponse.StreamResponseTest ‑ test_streamresponse_download_byteio
tests.units.test_utils_streamingresponse.StreamResponseTest ‑ test_streamresponse_download_byteio_interrupted
…

♻️ This comment has been updated with latest results.

@eodag-bot
Copy link
Collaborator

eodag-bot commented Jan 30, 2026

badge

Code Coverage (Ubuntu)

Details
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                      8       0  100.00%
cli.py                                         251      11  95.62%   104-115, 377, 644
config.py                                      305      24  92.13%   70-72, 75, 78, 81, 85, 89, 93-95, 583-585, 707-709, 728, 736, 766-771, 773
crunch.py                                        5       5  0.00%    20-24
api/__init__.py                                  0       0  100.00%
api/collection.py                              150       8  94.67%   176, 212, 215, 322, 361, 364, 382, 385
api/core.py                                    787      57  92.76%   266, 552, 600, 643, 683, 703, 744-749, 774, 858-877, 891, 897, 1050, 1055, 1154, 1193-1194, 1290-1291, 1317, 1348-1349, 1375, 1388, 1449-1450, 1467, 1481-1482, 1556-1561, 1573-1576, 1688, 2075, 2197, 2285-2286
api/provider.py                                381      35  90.81%   186, 190-191, 316-337, 405, 521, 525-526, 531-534, 545, 621-631, 821-822, 870, 877, 908-911, 947-948, 956-957
api/search_result.py                           181      19  89.50%   111, 123, 133, 154, 203, 220, 320, 375-378, 448, 453-454, 488, 502, 525-526, 532
api/product/__init__.py                         18       2  88.89%   57, 59
api/product/_assets.py                          54       4  92.59%   97, 191, 202-206
api/product/_product.py                        253      21  91.70%   189, 305-306, 324-325, 434, 463, 470, 509, 621, 641, 664-667, 676-679, 733, 799, 811
api/product/metadata_mapping.py                791      53  93.30%   125-127, 220-225, 249, 307-308, 396, 417, 469-470, 507, 528-531, 554, 566-567, 608, 631, 661-666, 731-736, 748, 756, 988, 1163, 1172-1176, 1193-1198, 1331, 1354, 1363, 1385, 1390, 1442, 1514, 1535, 1561, 1575, 1600, 1646, 1715, 1790
api/product/drivers/__init__.py                 11       0  100.00%
api/product/drivers/base.py                     29       0  100.00%
api/product/drivers/generic.py                  11       0  100.00%
api/product/drivers/sentinel1.py                33       0  100.00%
api/product/drivers/sentinel2.py                33       0  100.00%
plugins/__init__.py                              0       0  100.00%
plugins/base.py                                 22       4  81.82%   48, 55, 68-69
plugins/manager.py                             173      16  90.75%   102-107, 179, 201, 219-220, 232, 271-272, 372-375, 387-388
plugins/apis/__init__.py                         0       0  100.00%
plugins/apis/base.py                             4       0  100.00%
plugins/apis/ecmwf.py                          101       8  92.08%   178-180, 228-229, 255-257
plugins/apis/usgs.py                           182      25  86.26%   157, 263, 297, 339-341, 346, 374-375, 380, 410-417, 428-433, 455-461
plugins/authentication/__init__.py               6       1  83.33%   31
plugins/authentication/aws_auth.py             124      35  71.77%   52-54, 69-70, 142-149, 177-203, 226, 258-262, 279, 303, 319-320
plugins/authentication/base.py                  22       4  81.82%   45, 58, 81, 95
plugins/authentication/generic.py               16       3  81.25%   50, 55, 65
plugins/authentication/header.py                19       0  100.00%
plugins/authentication/keycloak.py              46       7  84.78%   153-156, 177-182
plugins/authentication/openid_connect.py       232      28  87.93%   91-92, 104-122, 169, 175-203, 211, 350-353, 379, 420
plugins/authentication/qsauth.py                34       1  97.06%   91
plugins/authentication/sas_auth.py              57       3  94.74%   68, 89, 135
plugins/authentication/token.py                128       9  92.97%   180, 217, 288-289, 339-343
plugins/authentication/token_exchange.py        36      14  61.11%   75, 93-121
plugins/crunch/__init__.py                       0       0  100.00%
plugins/crunch/base.py                          10       1  90.00%   43
plugins/crunch/filter_date.py                   59      14  76.27%   52-57, 69, 78, 87, 90, 100-102, 109-111, 118
plugins/crunch/filter_latest_intersect.py       54      39  27.78%   50-55, 68-115
plugins/crunch/filter_latest_tpl_name.py        31      20  35.48%   46-54, 64-95
plugins/crunch/filter_overlap.py                63      25  60.32%   62-65, 72-75, 81, 85, 89, 100-116, 131-157
plugins/crunch/filter_property.py               30       5  83.33%   56-61, 64-65
plugins/download/__init__.py                     4       0  100.00%
plugins/download/aws.py                        402      76  81.09%   271, 305, 354-357, 387-388, 396-400, 480-483, 523-525, 529, 560-561, 567-571, 602, 667-675, 739-834, 846-851, 889, 915, 960-962, 1014
plugins/download/base.py                       284      35  87.68%   135, 165, 312-313, 371-372, 414, 418-429, 443, 520-524, 554, 589-590, 615-624, 683, 704, 726, 734, 768
plugins/download/http.py                       568      73  87.15%   233, 275-278, 340-343, 346, 353-358, 389-391, 408, 423, 483, 518, 532, 546, 556-560, 576-581, 592, 611, 648-651, 672, 682, 689, 854, 886, 916-925, 961, 1006-1011, 1020, 1035-1037, 1041, 1044, 1059-1060, 1070, 1147, 1199, 1241-1242, 1254, 1264, 1314-1315, 1345, 1365, 1462-1463
plugins/search/__init__.py                      25       0  100.00%
plugins/search/base.py                         196      18  90.82%   109, 113, 137-143, 200-203, 296, 317, 442, 492, 525-528, 537
plugins/search/build_search_result.py          504      83  83.53%   259-260, 296, 300, 320, 550-561, 576-578, 701, 725, 727, 794, 802-806, 827, 837, 863, 908, 932, 960, 978-993, 1043, 1068, 1071, 1075, 1084, 1090, 1128-1149, 1190, 1217-1218, 1227-1236, 1300, 1315, 1321, 1340-1349, 1470-1471, 1515, 1524-1526, 1583, 1631-1641
plugins/search/cop_marine.py                   268      56  79.10%   57, 65-67, 77-78, 83, 88-89, 105, 107, 110, 176-177, 220, 236, 242, 246, 250, 261, 272-273, 281, 313-316, 322, 343, 347, 351, 355, 359-363, 369-372, 375-392, 409-412, 465-469, 474, 486
plugins/search/creodias_s3.py                   29       1  96.55%   59
plugins/search/csw.py                          112      87  22.32%   99-100, 104-105, 113-170, 176-189, 197-229, 247-288
plugins/search/qssearch.py                     823      94  88.58%   415-416, 533-534, 557-558, 570-574, 789-795, 853, 949, 956, 1027, 1048, 1051-1052, 1070, 1079-1080, 1107, 1179, 1188, 1193-1210, 1219, 1234, 1243-1246, 1256, 1278, 1367, 1390, 1463-1464, 1470, 1560, 1667-1671, 1737, 1740, 1744-1745, 1766-1769, 1781, 1803-1815, 1823, 1858-1860, 1883-1889, 1896, 1950, 1973, 1978-1979, 1994, 2000, 2010, 2100, 2104, 2115, 2139, 2152, 2160-2170, 2208-2212
plugins/search/stac_list_assets.py              25      10  60.00%   44-51, 75-85
plugins/search/static_stac_search.py            84      18  78.57%   99-127, 166-169, 182, 224
resources/__init__.py                            0       0  100.00%
resources/shp/__init__.py                        0       0  100.00%
types/__init__.py                              167      43  74.25%   58, 62, 71-75, 86-98, 126-128, 135-140, 216, 219, 257, 267-283, 288, 290, 312, 317, 325, 335
types/bbox.py                                   39      19  51.28%   46-61, 72-74, 85-87, 99-101, 113-115, 123
types/download_args.py                          10       0  100.00%
types/queryables.py                            112       0  100.00%
types/search_args.py                            70      18  74.29%   60-64, 71-88, 103
types/stac_extensions.py                       114       1  99.12%   282
types/stac_metadata.py                         116      16  86.21%   92, 113-114, 147, 177-191, 200-207
utils/__init__.py                              572      39  93.18%   60, 197, 228-229, 238-264, 267, 282, 362-366, 441-445, 525, 565-566, 595, 973-976, 1027, 1046-1047, 1076, 1094-1095, 1207, 1295, 1459, 1697
utils/cache.py                                  22       0  100.00%
utils/dates.py                                 113       4  96.46%   171, 329-331
utils/env.py                                     3       0  100.00%
utils/exceptions.py                             47       0  100.00%
utils/free_text_search.py                       65       2  96.92%   83, 91
utils/import_system.py                          28      19  32.14%   64-78, 89-99
utils/logging.py                                31       1  96.77%   41
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/repr.py                                   38       0  100.00%
utils/requests.py                               55      29  47.27%   51-52, 64, 85-96, 107-124, 128
utils/s3.py                                    240      12  95.00%   200-203, 245, 263, 489, 537-538, 585, 660, 686
utils/stac_reader.py                           113      44  61.06%   63-85, 95-97, 101, 138, 154-159, 206-216, 226-256
utils/streamresponse.py                         82       7  91.46%   24-26, 63, 124, 133, 145
TOTAL                                        10155    1329  86.91%

Diff against develop

Filename                        Stmts    Miss  Cover
----------------------------  -------  ------  --------
api/product/_assets.py             +2       0  +0.28%
api/product/_product.py            +5      +1  -0.24%
plugins/download/__init__.py       +4       0  +100.00%
plugins/download/aws.py            +1       0  +0.04%
plugins/download/base.py           +4       0  +0.18%
plugins/download/http.py           +4      -4  +0.80%
utils/__init__.py                 -36      -4  +0.25%
utils/logging.py                   +3       0  +0.34%
utils/streamresponse.py           +82      +7  +91.46%
TOTAL                             +69       0  +0.09%

Results for commit: 4ea1844

Minimum allowed coverage is 70%

♻️ This comment has been updated with latest results

@eodag-bot
Copy link
Collaborator

eodag-bot commented Jan 30, 2026

badge

Code Coverage (Windows)

Details
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                      8       0  100.00%
cli.py                                         251      11  95.62%   104-115, 377, 644
config.py                                      305      24  92.13%   70-72, 75, 78, 81, 85, 89, 93-95, 583-585, 707-709, 728, 736, 766-771, 773
crunch.py                                        5       5  0.00%    20-24
api/__init__.py                                  0       0  100.00%
api/collection.py                              150       8  94.67%   176, 212, 215, 322, 361, 364, 382, 385
api/core.py                                    787      57  92.76%   266, 552, 600, 643, 683, 703, 744-749, 774, 858-877, 891, 897, 1050, 1055, 1154, 1193-1194, 1290-1291, 1317, 1348-1349, 1375, 1388, 1449-1450, 1467, 1481-1482, 1556-1561, 1573-1576, 1688, 2075, 2197, 2285-2286
api/provider.py                                381      35  90.81%   186, 190-191, 316-337, 405, 521, 525-526, 531-534, 545, 621-631, 821-822, 870, 877, 908-911, 947-948, 956-957
api/search_result.py                           181      19  89.50%   111, 123, 133, 154, 203, 220, 320, 375-378, 448, 453-454, 488, 502, 525-526, 532
api/product/__init__.py                         18       2  88.89%   57, 59
api/product/_assets.py                          54       4  92.59%   97, 191, 202-206
api/product/_product.py                        253      21  91.70%   189, 305-306, 324-325, 434, 463, 470, 509, 621, 641, 664-667, 676-679, 733, 799, 811
api/product/metadata_mapping.py                791      53  93.30%   125-127, 220-225, 249, 307-308, 396, 417, 469-470, 507, 528-531, 554, 566-567, 608, 631, 661-666, 731-736, 748, 756, 988, 1163, 1172-1176, 1193-1198, 1331, 1354, 1363, 1385, 1390, 1442, 1514, 1535, 1561, 1575, 1600, 1646, 1715, 1790
api/product/drivers/__init__.py                 11       0  100.00%
api/product/drivers/base.py                     29       0  100.00%
api/product/drivers/generic.py                  11       0  100.00%
api/product/drivers/sentinel1.py                33       0  100.00%
api/product/drivers/sentinel2.py                33       0  100.00%
plugins/__init__.py                              0       0  100.00%
plugins/base.py                                 22       4  81.82%   48, 55, 68-69
plugins/manager.py                             173      16  90.75%   102-107, 179, 201, 219-220, 232, 271-272, 372-375, 387-388
plugins/apis/__init__.py                         0       0  100.00%
plugins/apis/base.py                             4       0  100.00%
plugins/apis/ecmwf.py                          101       8  92.08%   178-180, 228-229, 255-257
plugins/apis/usgs.py                           182      25  86.26%   157, 263, 297, 339-341, 346, 374-375, 380, 410-417, 428-433, 455-461
plugins/authentication/__init__.py               6       1  83.33%   31
plugins/authentication/aws_auth.py             124      35  71.77%   52-54, 69-70, 142-149, 177-203, 226, 258-262, 279, 303, 319-320
plugins/authentication/base.py                  22       4  81.82%   45, 58, 81, 95
plugins/authentication/generic.py               16       3  81.25%   50, 55, 65
plugins/authentication/header.py                19       0  100.00%
plugins/authentication/keycloak.py              46       7  84.78%   153-156, 177-182
plugins/authentication/openid_connect.py       232      28  87.93%   91-92, 104-122, 169, 175-203, 211, 350-353, 379, 420
plugins/authentication/qsauth.py                34       1  97.06%   91
plugins/authentication/sas_auth.py              57       3  94.74%   68, 89, 135
plugins/authentication/token.py                128       9  92.97%   180, 217, 288-289, 339-343
plugins/authentication/token_exchange.py        36      14  61.11%   75, 93-121
plugins/crunch/__init__.py                       0       0  100.00%
plugins/crunch/base.py                          10       1  90.00%   43
plugins/crunch/filter_date.py                   59      14  76.27%   52-57, 69, 78, 87, 90, 100-102, 109-111, 118
plugins/crunch/filter_latest_intersect.py       54      39  27.78%   50-55, 68-115
plugins/crunch/filter_latest_tpl_name.py        31      20  35.48%   46-54, 64-95
plugins/crunch/filter_overlap.py                63      25  60.32%   62-65, 72-75, 81, 85, 89, 100-116, 131-157
plugins/crunch/filter_property.py               30       5  83.33%   56-61, 64-65
plugins/download/__init__.py                     4       0  100.00%
plugins/download/aws.py                        402      76  81.09%   271, 305, 354-357, 387-388, 396-400, 480-483, 523-525, 529, 560-561, 567-571, 602, 667-675, 739-834, 846-851, 889, 915, 960-962, 1014
plugins/download/base.py                       284      37  86.97%   135, 165, 232-234, 312-313, 371-372, 414, 418-429, 443, 520-524, 554, 589-590, 615-624, 683, 704, 726, 734, 768
plugins/download/http.py                       568      73  87.15%   233, 275-278, 340-343, 346, 353-358, 389-391, 408, 423, 483, 518, 532, 546, 556-560, 576-581, 592, 611, 648-651, 672, 682, 689, 854, 886, 916-925, 961, 1006-1011, 1020, 1035-1037, 1041, 1044, 1059-1060, 1070, 1147, 1199, 1241-1242, 1254, 1264, 1314-1315, 1345, 1365, 1462-1463
plugins/search/__init__.py                      25       0  100.00%
plugins/search/base.py                         196      18  90.82%   109, 113, 137-143, 200-203, 296, 317, 442, 492, 525-528, 537
plugins/search/build_search_result.py          504      83  83.53%   259-260, 296, 300, 320, 550-561, 576-578, 701, 725, 727, 794, 802-806, 827, 837, 863, 908, 932, 960, 978-993, 1043, 1068, 1071, 1075, 1084, 1090, 1128-1149, 1190, 1217-1218, 1227-1236, 1300, 1315, 1321, 1340-1349, 1470-1471, 1515, 1524-1526, 1583, 1631-1641
plugins/search/cop_marine.py                   268      56  79.10%   57, 65-67, 77-78, 83, 88-89, 105, 107, 110, 176-177, 220, 236, 242, 246, 250, 261, 272-273, 281, 313-316, 322, 343, 347, 351, 355, 359-363, 369-372, 375-392, 409-412, 465-469, 474, 486
plugins/search/creodias_s3.py                   29       1  96.55%   59
plugins/search/csw.py                          112      87  22.32%   99-100, 104-105, 113-170, 176-189, 197-229, 247-288
plugins/search/qssearch.py                     823      94  88.58%   415-416, 533-534, 557-558, 570-574, 789-795, 853, 949, 956, 1027, 1048, 1051-1052, 1070, 1079-1080, 1107, 1179, 1188, 1193-1210, 1219, 1234, 1243-1246, 1256, 1278, 1367, 1390, 1463-1464, 1470, 1560, 1667-1671, 1737, 1740, 1744-1745, 1766-1769, 1781, 1803-1815, 1823, 1858-1860, 1883-1889, 1896, 1950, 1973, 1978-1979, 1994, 2000, 2010, 2100, 2104, 2115, 2139, 2152, 2160-2170, 2208-2212
plugins/search/stac_list_assets.py              25      10  60.00%   44-51, 75-85
plugins/search/static_stac_search.py            84      18  78.57%   99-127, 166-169, 182, 224
resources/__init__.py                            0       0  100.00%
resources/shp/__init__.py                        0       0  100.00%
types/__init__.py                              167      43  74.25%   58, 62, 71-75, 86-98, 126-128, 135-140, 216, 219, 257, 267-283, 288, 290, 312, 317, 325, 335
types/bbox.py                                   39      19  51.28%   46-61, 72-74, 85-87, 99-101, 113-115, 123
types/download_args.py                          10       0  100.00%
types/queryables.py                            112       0  100.00%
types/search_args.py                            70      18  74.29%   60-64, 71-88, 103
types/stac_extensions.py                       114       1  99.12%   282
types/stac_metadata.py                         116      16  86.21%   92, 113-114, 147, 177-191, 200-207
utils/__init__.py                              572      39  93.18%   60, 197, 228-229, 238-264, 267, 282, 362-366, 441-445, 525, 565-566, 595, 973-976, 1027, 1046-1047, 1076, 1094-1095, 1207, 1295, 1459, 1697
utils/cache.py                                  22       0  100.00%
utils/dates.py                                 113       4  96.46%   171, 329-331
utils/env.py                                     3       0  100.00%
utils/exceptions.py                             47       0  100.00%
utils/free_text_search.py                       65       2  96.92%   83, 91
utils/import_system.py                          28      19  32.14%   64-78, 89-99
utils/logging.py                                31       1  96.77%   41
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/repr.py                                   38       0  100.00%
utils/requests.py                               55      29  47.27%   51-52, 64, 85-96, 107-124, 128
utils/s3.py                                    240      12  95.00%   200-203, 245, 263, 489, 537-538, 585, 660, 686
utils/stac_reader.py                           113      44  61.06%   63-85, 95-97, 101, 138, 154-159, 206-216, 226-256
utils/streamresponse.py                         82       7  91.46%   24-26, 63, 124, 133, 145
TOTAL                                        10155    1331  86.89%

Diff against develop

Filename                        Stmts    Miss  Cover
----------------------------  -------  ------  --------
api/product/_assets.py             +2       0  +0.28%
api/product/_product.py            +5      +1  -0.24%
plugins/download/__init__.py       +4       0  +100.00%
plugins/download/aws.py            +1       0  +0.04%
plugins/download/base.py           +4       0  +0.18%
plugins/download/http.py           +4      -4  +0.80%
utils/__init__.py                 -36      -4  +0.25%
utils/logging.py                   +3       0  +0.34%
utils/streamresponse.py           +82      +7  +91.46%
TOTAL                             +69       0  +0.09%

Results for commit: 4ea1844

Minimum allowed coverage is 70%

♻️ This comment has been updated with latest results

@pdavid-cssopra pdavid-cssopra force-pushed the feat-2013-first-class-support-for-streaming-down-in-eodag branch 2 times, most recently from 4a4afa6 to f70e19a Compare February 10, 2026 09:50
@pdavid-cssopra pdavid-cssopra force-pushed the feat-2013-first-class-support-for-streaming-down-in-eodag branch 3 times, most recently from 47999eb to b9f0490 Compare February 16, 2026 09:17
@sbrunato sbrunato marked this pull request as draft February 16, 2026 09:46
@pdavid-cssopra pdavid-cssopra force-pushed the feat-2013-first-class-support-for-streaming-down-in-eodag branch 19 times, most recently from ecb9fba to 775e01e Compare February 18, 2026 13:56
# Download
asset_stream = product.assets["foo"].stream_download()
self.assertEqual(asset_stream.headers["content-type"], "image/tiff")
self.assertEqual(asset_stream.headers["content-length"], "1")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a content could be downloaded here

Copy link
Collaborator Author

@pdavid-cssopra pdavid-cssopra Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the case

# Download to tmp directory
        filepath = os.path.join(self.output_dir, asset_stream.filename)
        with open(filepath, "wb") as fp:
            for chunk in asset_stream.content:
                fp.write(chunk)

Global emultation is unconsistent, even if has headers like content length = 1, mock not serve real file

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several issues here:

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method for emulating external requests is inadequate for the required tests, which demand sequencing multiple calls. Lacking the time to properly recreate the emulations, the problem will be solved with an unsightly workaround.

Regarding HTTP headers, the IEEE retains the HTTP 1.0 header, which is the most restrictive of the three current standard versions (1.0, 1.1, 2.0), in a CamelCase-like format, such as "Content-Length".

It's reassuring that the code has a safeguard, but not that it compensates for inconsistencies in code writing. All fields have been standardized to a common naming convention.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a representative example of what HTTP emulation mechanics for testing could look like.
http_mock.py

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like already discussed, we are using responses in tests to handle multiple calls. Please use it instead of rewriting an alternative

Copy link
Collaborator Author

@pdavid-cssopra pdavid-cssopra Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

responses librairie does not expose overloadable classes and not allow a way to adapt protocol behaviour. It's unfortunatly not enough to manage custom case like streaming, in particular when eodag does not go through the standard stream transfer, and does not expose a generic generator object.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do not need to overload responses classes, neither to adapt protocol behavior. Please test following this example #2062

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

conflict appears between response librairie and native mocking from EodagTestCase.
Heritance from EodagTestCase to TestEoProduct had to be removed to implement requested tests update.

@sbrunato sbrunato marked this pull request as draft February 20, 2026 18:08
@pdavid-cssopra pdavid-cssopra force-pushed the feat-2013-first-class-support-for-streaming-down-in-eodag branch 4 times, most recently from a0d88dc to 42aa3b5 Compare February 23, 2026 10:47
@pdavid-cssopra pdavid-cssopra marked this pull request as ready for review February 23, 2026 10:54
@pdavid-cssopra pdavid-cssopra force-pushed the feat-2013-first-class-support-for-streaming-down-in-eodag branch 2 times, most recently from 5bde4cf to de048c6 Compare February 23, 2026 14:02
@pdavid-cssopra pdavid-cssopra marked this pull request as draft February 23, 2026 15:36
@pdavid-cssopra pdavid-cssopra force-pushed the feat-2013-first-class-support-for-streaming-down-in-eodag branch 4 times, most recently from d6a9e54 to 1882636 Compare February 25, 2026 10:00
@pdavid-cssopra pdavid-cssopra marked this pull request as ready for review February 25, 2026 13:11
from typing import Union


class ResponseFile:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use responses library. A file content can be passed as body if needed https://github.com/getsentry/responses?tab=readme-ov-file#response-parameters

Copy link
Collaborator Author

@pdavid-cssopra pdavid-cssopra Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this class replace an existing code in test:

tests/units/test_download_plugins.py:230

    def _download_response_archive(self):
        class Response:
            """Emulation of a response to requests.get method for a zipped product"""

            def __init__(response):
                # Using a zipped product file
                with open(self.local_product_as_archive_path, "rb") as fh:
                    response.__zip_buffer = io.BytesIO(fh.read())
                cl = response.__zip_buffer.getbuffer().nbytes
                response.headers = CaseInsensitiveDict(
                    {
                        "content-length": cl,
                        "content-disposition": "attachment; filename=foobar.zip",
                    }
                )
                response.url = "http://foo.bar"

            def __enter__(response):
                return response

            def __exit__(response, *args):
                pass

            def iter_content(response, **kwargs):
                with response.__zip_buffer as fh:
                    while True:
                        chunk = fh.read(kwargs["chunk_size"])
                        if not chunk:
                            break
                        yield chunk

            def raise_for_status(response):
                pass

            def close(response):
                pass

        return Response()

The class has been generalized to allow for extending tests without rewriting existing ones. The file has been isolated into a separate file for cleanliness and dependency isolation.

The response library does not allow overloading the response object of a request, and is therefore not suitable here.

Copy link
Collaborator

@sbrunato sbrunato Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this manual mechanism was also not needed, I just removed it in #2062.

Please adapt your tests following this example

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

conflict appears between response librairie and native mocking from EodagTestCase.
Heritance from EodagTestCase to TestEoProduct had to be removed to implement requested tests update.

@sbrunato sbrunato marked this pull request as draft February 26, 2026 08:20
@pdavid-cssopra pdavid-cssopra force-pushed the feat-2013-first-class-support-for-streaming-down-in-eodag branch from 1882636 to 69cd665 Compare February 26, 2026 08:29
@pdavid-cssopra pdavid-cssopra marked this pull request as ready for review February 26, 2026 11:31
@pdavid-cssopra pdavid-cssopra self-assigned this Feb 26, 2026
@pdavid-cssopra pdavid-cssopra force-pushed the feat-2013-first-class-support-for-streaming-down-in-eodag branch 3 times, most recently from 009fd5a to 03bbe32 Compare March 4, 2026 13:35
@pdavid-cssopra pdavid-cssopra force-pushed the feat-2013-first-class-support-for-streaming-down-in-eodag branch from 03bbe32 to 4ea1844 Compare March 17, 2026 11:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

download to s3

3 participants