Repository: CS-SI/eodag
Pull request #1488 (2025-01-22 08:21:14)

refactor(core): drivers from eodag-cube to core
Bring back drivers from [eodag-cube](https://github.com/CS-SI/eodag-cube) to `eodag` core.

Use them to guess assets keys and roles using `DatasetDriver.guess_asset_key_and_roles()`.

Use new `DatasetDriver.legacy` attribute to store legacy driver for deprecated `get_data()` method usage

github-actions[bot]: ## Test Results
    2 files   -     2      2 suites   - 2   3m 12s ⏱️ - 3m 16s
  597 tests +    6    581 ✅  -     7  3 💤 ± 0  13 ❌ +13 
1 194 runs   - 1 170  1 162 ✅  - 1 104  6 💤  - 92  26 ❌ +26 

For more details on these failures, see [this check](https://github.com/CS-SI/eodag/runs/35980560465).

Results for commit d4765cae. ± Comparison against base commit 045a8492.

[test-results]:data:application/gzip;base64,H4sIACCrkGcC/03MSw6DIBSF4a0Yxh3cy0vpZhrBS0Lqo0EYNd17IVXs8Hwn+d/Mh5l2du/4rWN7DqmNKccxhW0tE02FcqV6KtOf67Fn5yoNeNEzvAqJBn4Mc41cQjFusRAUiXmtUUQjj3VGETVv9Ivqcx9N3uA/6bZlCakMNsleKzcS0YAehBTGKyDg1ltB0AsDFqyRjn2+kcL5ogkBAAA=

github-actions[bot]: ![badge](https://raw.githubusercontent.com/CS-SI/eodag/_xml_coverage_reports/data/bring-back-drivers/./badge.svg)


## Code Coverage (Ubuntu)

<details>


```
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                      8       0  100.00%
cli.py                                         323      64  80.19%   62-63, 87, 674-737, 839-890, 894
config.py                                      430      27  93.72%   80-82, 91, 99, 103-105, 176, 187, 692-694, 807-810, 853-854, 863-864, 969, 1032-1037, 1039
crunch.py                                        5       5  0.00%    20-24
api/__init__.py                                  0       0  100.00%
api/core.py                                    783      75  90.42%   317-318, 368, 658, 702-705, 743, 787, 821, 866-871, 897, 988, 1056, 1194, 1279-1291, 1327, 1329, 1357, 1361-1372, 1385-1391, 1474-1477, 1510-1530, 1582, 1599-1603, 1615-1618, 1898, 1954, 1978-1984, 2235, 2239-2243, 2252-2254, 2298-2299, 2328-2329
api/search_result.py                            58       4  93.10%   82, 91, 98, 112
api/product/__init__.py                          6       0  100.00%
api/product/_assets.py                          48       5  89.58%   75, 147, 155, 158-162
api/product/_product.py                        194      19  90.21%   70-72, 238-239, 314, 343, 400, 414-417, 430, 454-457, 509-512
api/product/metadata_mapping.py                695      76  89.06%   119-121, 218, 250-251, 297-298, 308-320, 322, 333, 339-351, 398-399, 436, 457-460, 483, 491-492, 578-579, 603-604, 610-613, 628-629, 778, 824, 977, 986-990, 1007-1012, 1139, 1153-1173, 1193, 1198, 1327, 1341, 1366, 1412, 1464, 1487-1488, 1504-1508, 1524, 1532
api/product/drivers/__init__.py                 10       0  100.00%
api/product/drivers/base.py                     14       3  78.57%   52-55
api/product/drivers/generic.py                  17       2  88.24%   56, 66
api/product/drivers/sentinel2_l1c.py            36      17  52.78%   79-91, 102-110, 129, 139
api/product/drivers/stac_assets.py              20       2  90.00%   50, 66
plugins/__init__.py                              0       0  100.00%
plugins/base.py                                 21       2  90.48%   48, 55
plugins/manager.py                             172      15  91.28%   106-111, 161, 199, 221, 225, 249, 271, 389-392, 404-405
plugins/apis/__init__.py                         0       0  100.00%
plugins/apis/base.py                             4       0  100.00%
plugins/apis/ecmwf.py                           97      10  89.69%   171-173, 220-221, 247-249, 302-303
plugins/apis/usgs.py                           180      26  85.56%   156, 258, 292, 327-329, 334, 360-361, 366, 396-403, 414-419, 441-447, 478
plugins/authentication/__init__.py               6       1  83.33%   31
plugins/authentication/aws_auth.py              20       0  100.00%
plugins/authentication/base.py                  17       2  88.24%   45, 58
plugins/authentication/generic.py               14       2  85.71%   51, 61
plugins/authentication/header.py                19       0  100.00%
plugins/authentication/keycloak.py              46       7  84.78%   151-154, 175-180
plugins/authentication/oauth.py                 13       7  46.15%   43-45, 49-52
plugins/authentication/openid_connect.py       206      27  86.89%   80-81, 93-111, 149, 155-183, 191, 323-326, 352
plugins/authentication/qsauth.py                34       1  97.06%   91
plugins/authentication/sas_auth.py              47       1  97.87%   76
plugins/authentication/token.py                100      16  84.00%   141, 170, 172, 207-220, 276-280
plugins/authentication/token_exchange.py        36      14  61.11%   75, 92-120
plugins/crunch/__init__.py                       0       0  100.00%
plugins/crunch/base.py                          10       1  90.00%   43
plugins/crunch/filter_date.py                   59      42  28.81%   52-57, 72-126
plugins/crunch/filter_latest_intersect.py       47       8  82.98%   52-53, 69, 78-81, 83, 90-93
plugins/crunch/filter_latest_tpl_name.py        31       6  80.65%   73-83
plugins/crunch/filter_overlap.py                66      56  15.15%   28-30, 60-167
plugins/crunch/filter_property.py               30       7  76.67%   55-60, 63-64, 80-83
plugins/download/__init__.py                     0       0  100.00%
plugins/download/aws.py                        490     162  66.94%   269, 282, 349-352, 366-370, 412-414, 418, 450-451, 457-461, 491, 523, 527, 534, 564-572, 576, 608-616, 627-629, 660-734, 752-810, 821-826, 838-851, 876, 891-893, 896, 906-914, 922-935, 945-967, 974-986, 1024, 1050, 1095-1097, 1317
plugins/download/base.py                       253      42  83.40%   127, 155, 235-238, 291-292, 340-344, 350-351, 393, 396-410, 422, 426, 490-494, 524-525, 550-558, 560-564, 607, 629, 651, 659
plugins/download/creodias_s3.py                 25       9  64.00%   55-69
plugins/download/http.py                       529      95  82.04%   227, 323-326, 329, 336-341, 359-374, 391, 403, 451, 458-464, 482, 496, 510, 518-520, 536-541, 552, 570, 606-609, 638, 642, 662, 739, 758-769, 777-782, 792-809, 827, 857-866, 902, 927-928, 947-952, 958, 961, 978, 981, 996-997, 1032, 1094, 1109, 1168-1169, 1175, 1185, 1221, 1257, 1277, 1312-1314
plugins/download/s3rest.py                     116      24  79.31%   119, 153, 160, 195, 222-229, 232-234, 238, 249-255, 263-264, 267-271, 294, 315-318
plugins/search/__init__.py                      22       0  100.00%
plugins/search/base.py                         145      11  92.41%   101, 105, 129-135, 275, 295, 428
plugins/search/build_search_result.py          357      49  86.27%   237, 265-266, 302, 305, 379-382, 471-488, 516, 565, 567, 592-599, 631-633, 637, 659, 694, 745, 774-775, 790-805, 848, 873, 876, 879, 887, 999-1000, 1006, 1017, 1075, 1126
plugins/search/cop_marine.py                   244      47  80.74%   56, 64-66, 76-77, 82, 87-88, 104, 106, 109, 175-176, 228, 234, 238, 242, 255, 266-267, 275, 303, 307, 322, 326, 330, 334, 338-342, 348-351, 354-368, 385, 434-438, 443, 455
plugins/search/creodias_s3.py                   57       9  84.21%   59, 77, 95-106, 110
plugins/search/csw.py                          105      81  22.86%   98-99, 103-104, 112-160, 166-179, 187-219, 237-278
plugins/search/data_request_search.py          202      69  65.84%   188-191, 207, 218, 222-223, 234, 239, 244, 251, 264-267, 321-322, 326, 336-342, 347, 373-376, 384-395, 412, 414, 421-424, 426-427, 445-449, 482, 492, 503, 516, 522-537, 542
plugins/search/qssearch.py                     737      90  87.79%   449, 507, 521, 525-531, 559-563, 676-688, 733-736, 807-808, 875, 882, 894, 951, 972, 975-976, 985-986, 995-996, 1005-1006, 1033, 1095-1096, 1115-1120, 1124-1133, 1167, 1189, 1266, 1356, 1441-1442, 1452, 1529-1533, 1595, 1598, 1604-1605, 1626, 1654-1666, 1673, 1705-1707, 1717-1723, 1753, 1776, 1781-1782, 1797, 1813, 1898-1901, 1906-1909, 1918, 1948-1952, 1958
plugins/search/static_stac_search.py            75      30  60.00%   98-124, 140, 153, 178-218
rest/__init__.py                                 4       2  50.00%   21-22
rest/cache.py                                   33       7  78.79%   35-37, 53-55, 59, 68
rest/config.py                                  25       0  100.00%
rest/constants.py                                6       0  100.00%
rest/core.py                                   260      66  74.62%   258, 266, 284-301, 316-352, 446, 483-522, 705, 712-765
rest/errors.py                                  69       5  92.75%   106, 116, 127, 143-144
rest/server.py                                 192      24  87.50%   86, 109-111, 285-290, 318, 514-516, 533-538, 567, 569, 573-574, 578-579
rest/stac.py                                   319      63  80.25%   308, 330, 382-385, 412-439, 470-472, 495, 527-528, 610-650, 672-688, 780-784, 791, 845-846, 907, 997-999
rest/types/__init__.py                           0       0  100.00%
rest/types/collections_search.py                13      13  0.00%    18-44
rest/types/eodag_search.py                     180       6  96.67%   225-229, 282, 285, 353, 385
rest/types/queryables.py                        57       5  91.23%   93-98, 162
rest/types/stac_search.py                      125       7  94.40%   128, 174, 189-191, 199, 203
rest/utils/__init__.py                          94      12  87.23%   101-102, 121-123, 175, 185-199
rest/utils/cql_evaluate.py                      48       5  89.58%   69, 76, 90, 97, 105
rest/utils/rfc3339.py                           23       3  86.96%   48, 60, 62
types/__init__.py                              133      42  68.42%   63, 76-80, 91-103, 131-133, 140-145, 186, 205, 227, 237-253, 258, 260, 282, 287, 295, 305
types/bbox.py                                   43      19  55.81%   46-61, 72-74, 85-87, 99-101, 113-115, 123
types/download_args.py                           9       0  100.00%
types/queryables.py                            108       1  99.07%   63
types/search_args.py                            70      18  74.29%   60-64, 71-88, 103
types/whoosh.py                                 81      16  80.25%   129-132, 136-143, 155-161, 174-176
utils/__init__.py                              533      48  90.99%   81, 197-198, 207-234, 237, 252, 332-336, 409-413, 432-434, 447-461, 540, 555, 595-596, 625, 1000-1003, 1011-1012, 1050-1051, 1098-1099, 1233
utils/exceptions.py                             46       0  100.00%
utils/import_system.py                          28      19  32.14%   64-78, 89-99
utils/logging.py                                28       1  96.43%   41
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/repr.py                                   38       8  78.95%   51, 53, 57, 98, 122-129
utils/requests.py                               55      29  47.27%   51-52, 64, 85-96, 107-124, 128
utils/rest.py                                   36       1  97.22%   55
utils/stac_reader.py                           111      45  59.46%   56-57, 63-85, 95-97, 101, 137, 153-156, 203-212, 222-252
TOTAL                                        10090    1756  82.60%
```

</details>


### Diff against develop

```
Filename                                    Stmts    Miss  Cover
----------------------------------------  -------  ------  --------
api/core.py                                     0      +1  -0.13%
api/product/_product.py                        +6      -1  +0.85%
api/product/drivers/__init__.py                +4       0  +100.00%
api/product/drivers/base.py                    +8      +2  -4.76%
api/product/drivers/generic.py                +17      +2  +88.24%
api/product/drivers/sentinel2_l1c.py          +36     +17  +52.78%
api/product/drivers/stac_assets.py            +20      +2  +90.00%
plugins/manager.py                              0      +1  -0.58%
plugins/crunch/filter_date.py                   0     +28  -47.46%
plugins/crunch/filter_latest_tpl_name.py        0      +5  -16.12%
plugins/crunch/filter_overlap.py                0     +38  -57.58%
plugins/crunch/filter_property.py               0      +2  -6.66%
plugins/search/creodias_s3.py                   0      +6  -10.53%
plugins/search/qssearch.py                     +4      +1  -0.07%
plugins/search/static_stac_search.py            0     +17  -22.67%
TOTAL                                         +95    +121  -1.04%
```


Results for commit: d4765caeee81f03439f50e02bfb3e07390b0b94c


_Minimum allowed coverage is `70%`_


:recycle: This comment has been updated with latest results


<!-- Sticky Pull Request Commentartifacts/unit-test-results-python3.13-ubuntu-latest/coverage.xml -->
github-actions[bot]: ![badge](https://raw.githubusercontent.com/CS-SI/eodag/_xml_coverage_reports_win/data/bring-back-drivers/./badge.svg)


## Code Coverage (Windows)

<details>


```
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                      8       0  100.00%
cli.py                                         323     220  31.89%   62-63, 87, 110-116, 129-131, 137, 283-409, 435-497, 513-528, 552-613, 674-737, 839-890, 894
config.py                                      430      71  83.49%   80-82, 91, 99, 103-105, 171, 173, 176, 187, 202, 650, 692-694, 807-810, 840-870, 875, 895, 934-941, 951-973, 1000, 1026-1037, 1039, 1044
crunch.py                                        5       5  0.00%    20-24
api/__init__.py                                  0       0  100.00%
api/core.py                                    783     195  75.10%   136-149, 318, 330, 368, 373-377, 441, 452-454, 474-478, 496-504, 525-532, 589, 604, 632-642, 658, 702-705, 743, 783, 787, 821, 824-835, 857, 862, 866-871, 897, 924, 949, 962-966, 988, 1010, 1056, 1194, 1229, 1279-1291, 1327, 1329, 1357, 1361-1372, 1385-1391, 1474-1477, 1510-1530, 1582, 1599-1603, 1615-1618, 1898, 1954, 1978-1984, 1996-2005, 2049-2067, 2079-2081, 2090-2091, 2100-2109, 2143-2172, 2227-2235, 2238-2243, 2252-2254, 2276-2351
api/search_result.py                            58      14  75.86%   82, 91, 98, 112, 138, 156, 163, 172, 180, 183-184, 211-214
api/product/__init__.py                          6       0  100.00%
api/product/_assets.py                          48       5  89.58%   75, 147, 155, 158-162
api/product/_product.py                        194      19  90.21%   70-72, 238-239, 314, 343, 400, 414-417, 430, 454-457, 509-512
api/product/metadata_mapping.py                695     135  80.58%   119-121, 218, 250-251, 268-269, 297-298, 308-320, 322, 333, 339-351, 396-399, 436, 457-460, 476, 483, 491-496, 533, 578-579, 603-604, 610-613, 628-629, 778, 824, 949-950, 977, 986-990, 1007-1012, 1102, 1134-1173, 1193, 1198, 1206-1217, 1260, 1278, 1305, 1314, 1327, 1341, 1354-1373, 1412, 1439-1464, 1480-1492, 1504-1508, 1523-1534
api/product/drivers/__init__.py                 10       0  100.00%
api/product/drivers/base.py                     14       3  78.57%   52-55
api/product/drivers/generic.py                  17       4  76.47%   55-58, 66
api/product/drivers/sentinel2_l1c.py            36      19  47.22%   79-91, 102-110, 128-131, 139
api/product/drivers/stac_assets.py              20      11  45.00%   49-68
plugins/__init__.py                              0       0  100.00%
plugins/base.py                                 21       3  85.71%   48, 55, 68
plugins/manager.py                             172      33  80.81%   99-111, 121-133, 152-157, 161, 199, 221, 225, 249, 271, 278, 288-289, 385-393, 404-405
plugins/apis/__init__.py                         0       0  100.00%
plugins/apis/base.py                             4       0  100.00%
plugins/apis/ecmwf.py                           97      54  44.33%   156-175, 187-264, 279, 302-303
plugins/apis/usgs.py                           180     152  15.56%   118-136, 144-294, 307-463, 478
plugins/authentication/__init__.py               6       1  83.33%   31
plugins/authentication/aws_auth.py              20      12  40.00%   56-60, 67-87
plugins/authentication/base.py                  17       3  82.35%   45, 58, 68
plugins/authentication/generic.py               14       2  85.71%   51, 61
plugins/authentication/header.py                19       2  89.47%   98-99
plugins/authentication/keycloak.py              46      30  34.78%   108, 112-116, 125-127, 134-155, 158-181
plugins/authentication/oauth.py                 13       7  46.15%   43-45, 49-52
plugins/authentication/openid_connect.py       206      39  81.07%   80-81, 93-111, 143, 149, 155-183, 191, 323-326, 352, 529-547
plugins/authentication/qsauth.py                34      20  41.18%   73-95, 102, 106-112
plugins/authentication/sas_auth.py              47      31  34.04%   48-52, 58-84, 110, 114-125
plugins/authentication/token.py                100      79  21.00%   91-93, 97-127, 133-174, 182-246, 265-268, 272-294
plugins/authentication/token_exchange.py        36      20  44.44%   72-78, 90-120
plugins/crunch/__init__.py                       0       0  100.00%
plugins/crunch/base.py                          10       1  90.00%   43
plugins/crunch/filter_date.py                   59      45  23.73%   52-57, 67-126
plugins/crunch/filter_latest_intersect.py       47      33  29.79%   49-54, 67-112
plugins/crunch/filter_latest_tpl_name.py        31       6  80.65%   73-83
plugins/crunch/filter_overlap.py                66      56  15.15%   28-30, 60-167
plugins/crunch/filter_property.py               30       7  76.67%   55-60, 63-64, 80-83
plugins/download/__init__.py                     0       0  100.00%
plugins/download/aws.py                        490     422  13.88%   232-234, 266-390, 408-422, 430-461, 480-508, 522-577, 596-623, 627-629, 660-734, 752-810, 821-826, 838-851, 866-896, 906-914, 921-937, 945-967, 974-986, 997-1013, 1017-1037, 1041-1097, 1107-1302, 1317
plugins/download/base.py                       253     107  57.71%   127, 155, 183-186, 207, 222-224, 233, 235-238, 291-292, 309-315, 340-344, 350-351, 393, 396-410, 422, 426, 467-568, 607, 614-634, 647-651, 657-666
plugins/download/creodias_s3.py                 25      15  40.00%   45, 55-69, 86-96
plugins/download/http.py                       529     105  80.15%   227, 323-326, 329, 336-341, 359-374, 391, 403, 451, 458-464, 482, 496, 510, 518-520, 536-541, 552, 570, 606-609, 638, 642, 662, 684, 739, 758-769, 777-782, 792-809, 827, 836-866, 902, 927-928, 947-952, 958, 961, 977-981, 996-997, 1032, 1094, 1109, 1168-1169, 1175, 1185, 1221, 1257, 1277, 1312-1314, 1375
plugins/download/s3rest.py                     116      97  16.38%   91-92, 118-335
plugins/search/__init__.py                      22       0  100.00%
plugins/search/base.py                         145      53  63.45%   101, 105, 116, 129-135, 179, 197-200, 275, 295, 332-342, 364-389, 404-433
plugins/search/build_search_result.py          357     203  43.14%   235, 237, 249-305, 310, 379-382, 402, 471-488, 499, 509, 516, 528-529, 539-547, 558-702, 720-821, 838-903, 922-942, 953-962, 971-980, 999-1000, 1006, 1017, 1032, 1051, 1071-1083, 1097, 1126, 1184, 1236-1251, 1260, 1273-1280
plugins/search/cop_marine.py                   244     209  14.34%   51-68, 74-83, 87-88, 101-109, 140-178, 190-204, 216-285, 298-469
plugins/search/creodias_s3.py                   57      36  36.84%   54-59, 63-113, 139-149
plugins/search/csw.py                          105      81  22.86%   98-99, 103-104, 112-160, 166-179, 187-219, 237-278
plugins/search/data_request_search.py          202     175  13.37%   167-211, 218, 222-223, 233-350, 357-381, 384-395, 398-430, 435-449, 459-509, 512-517, 522-537, 541-544
plugins/search/qssearch.py                     737     290  60.65%   449, 459-488, 507, 517-523, 525-531, 542-543, 549-693, 703-716, 733-736, 807-808, 874-880, 882, 894, 939-976, 985-986, 995-996, 1005-1006, 1019, 1021, 1033, 1044-1045, 1095-1096, 1114-1138, 1167, 1181, 1189, 1235-1236, 1258, 1260, 1266, 1274, 1341-1368, 1374, 1386-1407, 1439-1452, 1458-1506, 1529-1533, 1562, 1574-1612, 1626, 1652-1666, 1673, 1697, 1705-1707, 1717-1723, 1753, 1776, 1781-1782, 1795, 1797, 1803, 1805, 1813, 1821, 1881-1991, 2006
plugins/search/static_stac_search.py            75      30  60.00%   98-124, 140, 153, 178-218
rest/__init__.py                                 4       2  50.00%   21-22
rest/cache.py                                   33      22  33.33%   35-37, 44-70
rest/config.py                                  25       1  96.00%   35
rest/constants.py                                6       0  100.00%
rest/core.py                                   260     153  41.15%   157, 159, 161, 164-165, 179-189, 198-199, 205, 208, 249-303, 316-352, 383-420, 435-451, 467-476, 483-522, 539, 581-666, 705, 712-765
rest/errors.py                                  69      49  28.99%   60, 65-100, 105-108, 115-118, 126-147, 155-160, 175-181
rest/server.py                                 192     192  0.00%    18-592
rest/stac.py                                   319      68  78.68%   242, 308, 330, 382-385, 412-439, 470-472, 495, 527-528, 610-650, 672-688, 715, 780-784, 791, 845-846, 852, 907, 945, 978, 997-999
rest/types/__init__.py                           0       0  100.00%
rest/types/collections_search.py                13      13  0.00%    18-44
rest/types/eodag_search.py                     180      83  53.89%   132-144, 163, 166-169, 181, 193-238, 248, 259-297, 303-305, 316-320, 335-337, 342-353, 358-360, 371-386
rest/types/queryables.py                        57      13  77.19%   50-51, 58-59, 66-67, 93-98, 107-108, 162
rest/types/stac_search.py                      125      53  57.60%   126-128, 134, 136, 138, 146, 167, 173-179, 186-205, 211-240, 251, 254, 264-272
rest/utils/__init__.py                          94      49  47.87%   72-78, 83-85, 90-92, 97-102, 107-113, 118-123, 136, 143, 147, 160, 168-176, 183-204
rest/utils/cql_evaluate.py                      48      27  43.75%   36, 41, 46, 51, 68-105, 110-114, 119
rest/utils/rfc3339.py                           23      17  26.09%   42-64
types/__init__.py                              133     109  18.05%   62-67, 76-80, 91-103, 119-145, 171-205, 226-307, 326-332, 345-349
types/bbox.py                                   43      19  55.81%   46-61, 72-74, 85-87, 99-101, 113-115, 123
types/download_args.py                           9       0  100.00%
types/queryables.py                            108      16  85.19%   48-53, 60-65, 168-170, 173-189, 234
types/search_args.py                            70      33  52.86%   60-64, 71-88, 102-133
types/whoosh.py                                 81      15  81.48%   129-132, 136-143, 155-156, 159-161, 174-176
utils/__init__.py                              533      53  90.06%   81, 197-198, 207-234, 237, 252, 332-336, 371, 409-413, 432-434, 447-461, 540, 555, 595-596, 625, 942, 1000-1003, 1011-1012, 1050-1051, 1098-1099, 1151, 1165, 1183, 1233
utils/exceptions.py                             46       1  97.83%   56
utils/import_system.py                          28      19  32.14%   64-78, 89-99
utils/logging.py                                28       8  71.43%   41, 44, 109, 111-115
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/repr.py                                   38      13  65.79%   51, 53, 57, 98, 122-129, 153, 167-172
utils/requests.py                               55      29  47.27%   51-52, 64, 85-96, 107-124, 128
utils/rest.py                                   36      21  41.67%   44-63, 69-80, 98
utils/stac_reader.py                           111      49  55.86%   56-59, 63-85, 95-97, 101, 130, 137, 153-156, 160, 203-212, 222-252
TOTAL                                        10090    4310  57.28%
```

</details>


### Diff against develop

```
Filename                                    Stmts    Miss  Cover
----------------------------------------  -------  ------  --------
cli.py                                          0    +156  -48.30%
config.py                                       0     +43  -10.00%
api/core.py                                     0    +121  -15.45%
api/search_result.py                            0     +10  -17.24%
api/product/_product.py                        +6      -1  +0.85%
api/product/metadata_mapping.py                 0     +58  -8.34%
api/product/drivers/__init__.py                +4       0  +100.00%
api/product/drivers/base.py                    +8      +2  -4.76%
api/product/drivers/generic.py                +17      +4  +76.47%
api/product/drivers/sentinel2_l1c.py          +36     +19  +47.22%
api/product/drivers/stac_assets.py            +20     +11  +45.00%
plugins/manager.py                              0     +19  -11.05%
plugins/apis/ecmwf.py                           0     +44  -45.36%
plugins/apis/usgs.py                            0    +126  -70.00%
plugins/authentication/aws_auth.py              0     +12  -60.00%
plugins/authentication/base.py                  0      +1  -5.89%
plugins/authentication/header.py                0      +2  -10.53%
plugins/authentication/keycloak.py              0     +23  -50.00%
plugins/authentication/openid_connect.py        0     +12  -5.82%
plugins/authentication/qsauth.py                0     +19  -55.88%
plugins/authentication/sas_auth.py              0     +30  -63.83%
plugins/authentication/token.py                 0     +63  -63.00%
plugins/authentication/token_exchange.py        0      +6  -16.67%
plugins/crunch/filter_date.py                   0     +31  -52.54%
plugins/crunch/filter_latest_tpl_name.py        0      +5  -16.12%
plugins/crunch/filter_overlap.py                0     +38  -57.58%
plugins/crunch/filter_property.py               0      +2  -6.66%
plugins/download/aws.py                         0    +260  -53.06%
plugins/download/base.py                        0     +63  -24.90%
plugins/download/creodias_s3.py                 0      +6  -24.00%
plugins/download/http.py                        0     +10  -1.89%
plugins/download/s3rest.py                      0     +73  -62.93%
plugins/search/base.py                          0     +42  -28.96%
plugins/search/build_search_result.py           0    +153  -42.85%
plugins/search/cop_marine.py                    0    +162  -66.40%
plugins/search/creodias_s3.py                   0     +33  -57.90%
plugins/search/data_request_search.py           0    +106  -52.47%
plugins/search/qssearch.py                     +4    +165  -22.30%
plugins/search/static_stac_search.py            0     +17  -22.67%
rest/types/eodag_search.py                      0     +63  -35.00%
rest/types/stac_search.py                       0     +42  -33.60%
rest/utils/__init__.py                          0     +19  -20.22%
rest/utils/cql_evaluate.py                      0     +22  -45.83%
rest/utils/rfc3339.py                           0     +12  -52.17%
types/__init__.py                               0     +65  -48.87%
types/queryables.py                             0     +15  -13.88%
types/search_args.py                            0     +15  -21.43%
types/whoosh.py                                 0      -1  +1.23%
utils/__init__.py                               0      +5  -0.93%
utils/exceptions.py                             0      +1  -2.17%
utils/logging.py                                0      +7  -25.00%
utils/repr.py                                   0      +5  -13.16%
utils/rest.py                                   0     +20  -55.55%
utils/stac_reader.py                            0      +4  -3.60%
TOTAL                                         +95   +2240  -22.01%
```


Results for commit: d4765caeee81f03439f50e02bfb3e07390b0b94c


_Minimum allowed coverage is `70%`_


:recycle: This comment has been updated with latest results


<!-- Sticky Pull Request Commentartifacts/unit-test-results-python3.13-windows-latest/coverage.xml -->

Patch:
diff --git a/.gitignore b/.gitignore
index 0fcb9b90..c01c2553 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,6 +94,7 @@ ENV/
 
 # Autogenerated docs by sphinx
 docs/plugins_reference/generated/*.rst
+docs/drivers_generated/*.rst
 
 docs/notebooks/api_user_guide/*workspace*
 docs/notebooks/tutos/*workspace*
diff --git a/BREAKING_CHANGES.rst b/BREAKING_CHANGES.rst
index e05cee75..920ee03b 100644
--- a/BREAKING_CHANGES.rst
+++ b/BREAKING_CHANGES.rst
@@ -3,14 +3,14 @@ Breaking changes
 
 Full changelog available in `Release history <changelog.html>`_.
 
-3.0.0b3
-+++++++
+v3.0.0b3
+++++++++
 
 * :meth:`~eodag.api.core.EODataAccessGateway.download` / :class:`~eodag.types.download_args.DownloadConf` parameters
   ``outputs_prefix`` and ``outputs_extension`` renamed to ``output_dir`` and ``output_extension``.
 
-3.0.0b1
-+++++++
+v3.0.0b1
+++++++++
 
 * :meth:`~eodag.api.core.EODataAccessGateway.search` method now returns only a
   :class:`~eodag.api.search_result.SearchResult` instead of a 2 values tuple. It can optionally store the estimated
@@ -45,14 +45,14 @@ Full changelog available in `Release history <changelog.html>`_.
     # install eodag with all available providers supported
     pip install "eodag[all-providers]"
 
-2.0b1
-+++++
+v2.0b1
+++++++
 
 - STAC API compliant REST server
 - Common configuration for STAC providers
 
-1.0
-+++
+v1.0
+++++
 
 - Adds product type search functionality
 - The cli arguments are now fully compliant with opensearch geo(bbox)/time extensions
diff --git a/docs/api_reference/eoproduct.rst b/docs/api_reference/eoproduct.rst
index 96325f0e..fe0174db 100644
--- a/docs/api_reference/eoproduct.rst
+++ b/docs/api_reference/eoproduct.rst
@@ -19,6 +19,13 @@ Download
    EOProduct.download
    EOProduct.get_quicklook
 
+Driver
+--------
+
+.. autosummary::
+
+   EOProduct.driver
+
 Conversion
 ----------
 
@@ -36,4 +43,4 @@ Interface
 
 
 .. autoclass:: eodag.api.product._product.EOProduct
-   :members: download, get_quicklook, as_dict, from_geojson, __geo_interface__
+   :members: driver, download, get_quicklook, as_dict, from_geojson, __geo_interface__
diff --git a/docs/conf.py b/docs/conf.py
index dd980df0..c8e6320c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -281,12 +281,19 @@ def _build_finished(app, exception):
                         r"\1",
                         file_content,
                     )
+                    # remove long prefix from drivers titles
+                    file_content = re.sub(
+                        r"(\.html\">|\"#\">|<h1>)eodag\.api\.product\.drivers\.[a-z0-9]+\.",
+                        r"\1",
+                        file_content,
+                    )
                     # write
                     file.seek(0)
                     file.write(file_content)
-                print(f"Plugins titles shortened in {file_path}")
+                print(f"Titles shortened in {file_path}")
 
     _shorten_titles(os.path.join(app.outdir, "plugins_reference"))
+    _shorten_titles(os.path.join(app.outdir, "drivers_generated"))
 
 
 def _html_page_context(app, pagename, templatename, context, doctree):
diff --git a/docs/drivers.rst b/docs/drivers.rst
new file mode 100644
index 00000000..1dc38a62
--- /dev/null
+++ b/docs/drivers.rst
@@ -0,0 +1,46 @@
+.. module:: eodag.api.product.drivers
+
+Drivers
+=======
+
+Drivers enable additional methods to be called on the :class:`~eodag.api.product._product.EOProduct`. They are set as
+:attr:`~eodag.api.product._product.EOProduct.driver` attribute of the :class:`~eodag.api.product._product.EOProduct`
+during its initialization, using some criteria to determine the most adapted driver. The first driver having its
+associated criteria matching will be used. If no driver is found, the :class:`~eodag.api.product.drivers.base.NoDriver`
+criteria is used.
+
+
+Criteria
+^^^^^^^^
+
+.. autoclass:: eodag.api.product.drivers.DriverCriteria
+    :members:
+
+.. autodata:: DRIVERS
+    :no-value:
+.. autodata:: LEGACY_DRIVERS
+    :no-value:
+
+
+Methods available
+^^^^^^^^^^^^^^^^^
+
+.. autoclass:: eodag.api.product.drivers.base.DatasetDriver
+   :members:
+   :member-order: bysource
+
+.. autoclass:: eodag.api.product.drivers.base.AssetPatterns
+   :members:
+
+Drivers Available
+^^^^^^^^^^^^^^^^^
+
+EODAG currently advertises the following drivers:
+
+.. autosummary::
+   :toctree: drivers_generated/
+
+   base.NoDriver
+   generic.GenericDriver
+   sentinel1.Sentinel1Driver
+   sentinel2.Sentinel2Driver
diff --git a/docs/index.rst b/docs/index.rst
index e961949f..6d7d5cd3 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -132,6 +132,7 @@ Site contents
    add_provider
    add_product_type
    plugins
+   drivers
    params_mapping
    contribute
    changelog
diff --git a/eodag/api/product/_product.py b/eodag/api/product/_product.py
index a6577d33..4cc2030a 100644
--- a/eodag/api/product/_product.py
+++ b/eodag/api/product/_product.py
@@ -38,7 +38,7 @@ try:
 except ImportError:
     from eodag.api.product._assets import AssetsDict
 
-from eodag.api.product.drivers import DRIVERS, NoDriver
+from eodag.api.product.drivers import DRIVERS, LEGACY_DRIVERS, NoDriver
 from eodag.api.product.metadata_mapping import (
     DEFAULT_GEOMETRY,
     NOT_AVAILABLE,
@@ -122,6 +122,8 @@ class EOProduct:
     geometry: BaseGeometry
     search_intersection: Optional[BaseGeometry]
     assets: AssetsDict
+    #: Driver enables additional methods to be called on the EOProduct
+    driver: DatasetDriver
 
     def __init__(
         self, provider: str, properties: dict[str, Any], **kwargs: Any
@@ -498,11 +500,16 @@ class EOProduct:
         try:
             for driver_conf in DRIVERS:
                 if all([criteria(self) for criteria in driver_conf["criteria"]]):
-                    return driver_conf["driver"]
+                    driver = driver_conf["driver"]
+                    break
+            # use legacy driver for deprecated get_data method usage
+            for lecacy_conf in LEGACY_DRIVERS:
+                if all([criteria(self) for criteria in lecacy_conf["criteria"]]):
+                    driver.legacy = lecacy_conf["driver"]
+                    break
+            return driver
         except TypeError:
-            logger.warning(
-                "Drivers definition seems out-of-date, please update eodag-cube"
-            )
+            logger.info("No driver matching")
             pass
         return NoDriver()
 
diff --git a/eodag/api/product/drivers/__init__.py b/eodag/api/product/drivers/__init__.py
index bb1b14d7..7f8151ca 100644
--- a/eodag/api/product/drivers/__init__.py
+++ b/eodag/api/product/drivers/__init__.py
@@ -16,14 +16,91 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """EODAG drivers package"""
+from __future__ import annotations
+
+from typing import Callable, TypedDict
+
 from eodag.api.product.drivers.base import DatasetDriver, NoDriver
+from eodag.api.product.drivers.generic import GenericDriver
+from eodag.api.product.drivers.sentinel1 import Sentinel1Driver
+from eodag.api.product.drivers.sentinel2 import Sentinel2Driver
 
 try:
-    from eodag_cube.api.product.drivers import (  # pyright: ignore[reportMissingImports]
-        DRIVERS,
+    # import from eodag-cube if installed
+    from eodag_cube.api.product.drivers.generic import (  # pyright: ignore[reportMissingImports]; isort: skip
+        GenericDriver as GenericDriver_cube,
+    )
+    from eodag_cube.api.product.drivers.sentinel2_l1c import (  # pyright: ignore[reportMissingImports]; isort: skip
+        Sentinel2L1C as Sentinel2L1C_cube,
+    )
+    from eodag_cube.api.product.drivers.stac_assets import (  # pyright: ignore[reportMissingImports]; isort: skip
+        StacAssets as StacAssets_cube,
     )
 except ImportError:
-    DRIVERS = []
+    GenericDriver_cube = NoDriver
+    Sentinel2L1C_cube = NoDriver
+    StacAssets_cube = NoDriver
+
+
+class DriverCriteria(TypedDict):
+    """Driver criteria definition"""
+
+    #: Function that returns True if the driver is suitable for the given :class:`~eodag.api.product._product.EOProduct`
+    criteria: list[Callable[..., bool]]
+    #: driver to use
+    driver: DatasetDriver
+
+
+#: list of drivers and their criteria
+DRIVERS: list[DriverCriteria] = [
+    {
+        "criteria": [
+            lambda prod: True
+            if (prod.product_type or "").startswith("S2_MSI_")
+            else False
+        ],
+        "driver": Sentinel2Driver(),
+    },
+    {
+        "criteria": [
+            lambda prod: True
+            if (prod.product_type or "").startswith("S1_SAR_")
+            else False
+        ],
+        "driver": Sentinel1Driver(),
+    },
+    {
+        "criteria": [lambda prod: True],
+        "driver": GenericDriver(),
+    },
+]
+
+
+#: list of legacy drivers and their criteria
+LEGACY_DRIVERS: list[DriverCriteria] = [
+    {
+        "criteria": [
+            lambda prod: True if len(getattr(prod, "assets", {})) > 0 else False
+        ],
+        "driver": StacAssets_cube(),
+    },
+    {
+        "criteria": [lambda prod: True if "assets" in prod.properties else False],
+        "driver": StacAssets_cube(),
+    },
+    {
+        "criteria": [
+            lambda prod: True
+            if getattr(prod, "product_type") == "S2_MSI_L1C"
+            else False
+        ],
+        "driver": Sentinel2L1C_cube(),
+    },
+    {
+        "criteria": [lambda prod: True],
+        "driver": GenericDriver_cube(),
+    },
+]
 
 # exportable content
-__all__ = ["DRIVERS", "DatasetDriver", "NoDriver"]
+__all__ = ["DRIVERS", "DatasetDriver", "GenericDriver", "NoDriver", "Sentinel2Driver"]
diff --git a/eodag/api/product/drivers/base.py b/eodag/api/product/drivers/base.py
index 1dd46f46..5be2e46b 100644
--- a/eodag/api/product/drivers/base.py
+++ b/eodag/api/product/drivers/base.py
@@ -17,15 +17,72 @@
 # limitations under the License.
 from __future__ import annotations
 
-from typing import TYPE_CHECKING
+import logging
+import re
+from typing import TYPE_CHECKING, Optional, TypedDict
+
+from eodag.utils import _deprecated
 
 if TYPE_CHECKING:
     from eodag.api.product import EOProduct
 
 
+class AssetPatterns(TypedDict):
+    """Asset patterns definition"""
+
+    #: pattern to match and extract asset key
+    pattern: re.Pattern
+    #: roles associated to the asset key
+    roles: list[str]
+
+
+logger = logging.getLogger("eodag.driver.base")
+
+
 class DatasetDriver(metaclass=type):
-    """Dataset driver"""
+    """Parent class for all dataset drivers.
+
+    Drivers will provide methods adapted to a given :class:`~eodag.api.product._product.EOProduct` related to predefined
+    criteria.
+    """
+
+    #: legacy driver for deprecated :meth:`~eodag_cube.api.product._product.EOProduct.get_data` method usage
+    legacy: DatasetDriver
 
+    #: list of patterns to match asset keys and roles
+    ASSET_KEYS_PATTERNS_ROLES: list[AssetPatterns] = []
+
+    #: strip non-alphanumeric characters at the beginning and end of the key
+    STRIP_SPECIAL_PATTERN = re.compile(r"^[^A-Z0-9]+|[^A-Z0-9]+$", re.IGNORECASE)
+
+    def _normalize_key(self, key, eo_product):
+        # default cleanup
+        norm_key = key.replace(eo_product.properties.get("id", ""), "")
+        norm_key = re.sub(self.STRIP_SPECIAL_PATTERN, "", norm_key)
+
+        return norm_key
+
+    def guess_asset_key_and_roles(
+        self, href: str, eo_product: EOProduct
+    ) -> tuple[Optional[str], Optional[list[str]]]:
+        """Guess the asset key and roles from the given href.
+
+        :param href: The asset href
+        :param eo_product: The product to which the asset belongs
+        :returns: The asset key and roles
+        """
+        for pattern_dict in self.ASSET_KEYS_PATTERNS_ROLES:
+            if matched := pattern_dict["pattern"].match(href):
+                extracted_key, roles = (
+                    "".join([m for m in matched.groups() if m is not None]),
+                    pattern_dict.get("roles"),
+                )
+                normalized_key = self._normalize_key(extracted_key, eo_product)
+                return normalized_key or extracted_key, roles
+        logger.debug(f"No key & roles could be guessed for {href}")
+        return None, None
+
+    @_deprecated(reason="Method used by deprecated get_data", version="3.1.0")
     def get_data_address(self, eo_product: EOProduct, band: str) -> str:
         """Retrieve the address of the dataset represented by `eo_product`.
 
@@ -34,12 +91,16 @@ class DatasetDriver(metaclass=type):
         :returns: An address for the dataset
         :raises: :class:`~eodag.utils.exceptions.AddressNotFound`
         :raises: :class:`~eodag.utils.exceptions.UnsupportedDatasetAddressScheme`
+
+        .. deprecated:: 3.1.0
+           Method used by deprecated :meth:`~eodag_cube.api.product._product.EOProduct.get_data`
         """
         raise NotImplementedError
 
 
 class NoDriver(DatasetDriver):
-    """A default driver that does not implement any of the methods it should implement, used for all product types for
-    which the :meth:`~eodag.api.product._product.EOProduct.get_data` method is not yet implemented in eodag. Expect a
+    """A default :attr:`~eodag.api.product.drivers.base.DatasetDriver.legacy` driver that does not implement any of the
+    methods it should implement, used for all product types for  which the deprecated
+    :meth:`~eodag_cube.api.product._product.EOProduct.get_data` method is not implemented. Expect a
     :exc:`NotImplementedError` when trying to get the data in that case.
     """
diff --git a/eodag/api/product/drivers/generic.py b/eodag/api/product/drivers/generic.py
new file mode 100644
index 00000000..cb8d3422
--- /dev/null
+++ b/eodag/api/product/drivers/generic.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021, CS GROUP - France, http://www.c-s.fr
+#
+# This file is part of EODAG project
+#     https://www.github.com/CS-SI/EODAG
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import annotations
+
+import logging
+import re
+
+from eodag.api.product.drivers.base import AssetPatterns, DatasetDriver
+
+logger = logging.getLogger("eodag.driver.generic")
+
+
+class GenericDriver(DatasetDriver):
+    """Generic default Driver"""
+
+    #: list of patterns to match asset keys and roles
+    ASSET_KEYS_PATTERNS_ROLES: list[AssetPatterns] = [
+        # data
+        {
+            "pattern": re.compile(
+                r"^(?:.*[/\\])?([^/\\]+)(\.jp2|\.tiff?|\.dat|\.nc|\.grib2?)$",
+                re.IGNORECASE,
+            ),
+            "roles": ["data"],
+        },
+        # metadata
+        {
+            "pattern": re.compile(
+                r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)$", re.IGNORECASE
+            ),
+            "roles": ["metadata"],
+        },
+        # thumbnail
+        {
+            "pattern": re.compile(
+                r"^(?:.*[/\\])?(thumbnail)(\.jpg|\.jpeg|\.png)$", re.IGNORECASE
+            ),
+            "roles": ["thumbnail"],
+        },
+        # quicklook
+        {
+            "pattern": re.compile(
+                r"^(?:.*[/\\])?([^/\\]+-ql|preview|quick-?look)(\.jpg|\.jpeg|\.png)$",
+                re.IGNORECASE,
+            ),
+            "roles": ["overview"],
+        },
+        # default
+        {"pattern": re.compile(r"^(?:.*[/\\])?([^/\\]+)$"), "roles": ["auxiliary"]},
+    ]
diff --git a/eodag/api/product/drivers/sentinel1.py b/eodag/api/product/drivers/sentinel1.py
new file mode 100644
index 00000000..dc17d18f
--- /dev/null
+++ b/eodag/api/product/drivers/sentinel1.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+# Copyright 2025, CS GROUP - France, http://www.c-s.fr
+#
+# This file is part of EODAG project
+#     https://www.github.com/CS-SI/EODAG
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import annotations
+
+import re
+from typing import TYPE_CHECKING
+
+from eodag.api.product.drivers.base import AssetPatterns, DatasetDriver
+
+if TYPE_CHECKING:
+    from eodag.api.product._product import EOProduct
+
+
+class Sentinel1Driver(DatasetDriver):
+    """Driver for Sentinel1 products"""
+
+    #: pattern to match data-role keys
+    DATA_PATTERN = re.compile(r"[vh]{2}", re.IGNORECASE)
+
+    #: list of patterns to replace in asset keys
+    REPLACE_PATTERNS = [
+        (re.compile(r"s1a?", re.IGNORECASE), ""),
+        (re.compile(r"grd", re.IGNORECASE), ""),
+        (re.compile(r"slc", re.IGNORECASE), ""),
+        (re.compile(r"ocn", re.IGNORECASE), ""),
+        (re.compile(r"iw", re.IGNORECASE), ""),
+        (re.compile(r"ew", re.IGNORECASE), ""),
+        (re.compile(r"wv", re.IGNORECASE), ""),
+        (re.compile(r"sm", re.IGNORECASE), ""),
+        (re.compile(r"raw([-_]s)?", re.IGNORECASE), ""),
+        (re.compile(r"[t?0-9]{3,}", re.IGNORECASE), ""),
+        (re.compile(r"-+"), "-"),
+        (re.compile(r"-+\."), "."),
+        (re.compile(r"_+"), "_"),
+        (re.compile(r"_+\."), "."),
+    ]
+
+    #: list of patterns to match asset keys and roles
+    ASSET_KEYS_PATTERNS_ROLES: list[AssetPatterns] = [
+        # data
+        {
+            "pattern": re.compile(
+                r"^.*?([vh]{2}).*\.(?:jp2|tiff?|dat)$", re.IGNORECASE
+            ),
+            "roles": ["data"],
+        },
+        # metadata
+        {
+            "pattern": re.compile(
+                r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)$", re.IGNORECASE
+            ),
+            "roles": ["metadata"],
+        },
+        # thumbnail
+        {
+            "pattern": re.compile(
+                r"^(?:.*[/\\])?(thumbnail)(\.jpe?g|\.png)$", re.IGNORECASE
+            ),
+            "roles": ["thumbnail"],
+        },
+        # quicklook
+        {
+            "pattern": re.compile(
+                r"^(?:.*[/\\])?([^/\\]+-ql|preview|quick-?look)(\.jpe?g|\.png)$",
+                re.IGNORECASE,
+            ),
+            "roles": ["overview"],
+        },
+        # default
+        {"pattern": re.compile(r"^(?:.*[/\\])?([^/\\]+)$"), "roles": ["auxiliary"]},
+    ]
+
+    def _normalize_key(self, key: str, eo_product: EOProduct) -> str:
+        if self.DATA_PATTERN.fullmatch(key):
+            return key.upper()
+
+        key = super()._normalize_key(key, eo_product)
+
+        for pattern, replacement in self.REPLACE_PATTERNS:
+            key = pattern.sub(replacement, key)
+
+        return super()._normalize_key(key, eo_product)
diff --git a/eodag/api/product/drivers/sentinel2.py b/eodag/api/product/drivers/sentinel2.py
new file mode 100644
index 00000000..76e61706
--- /dev/null
+++ b/eodag/api/product/drivers/sentinel2.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021, CS GROUP - France, http://www.c-s.fr
+#
+# This file is part of EODAG project
+#     https://www.github.com/CS-SI/EODAG
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import annotations
+
+import re
+from typing import TYPE_CHECKING
+
+from eodag.api.product.drivers.base import AssetPatterns, DatasetDriver
+
+if TYPE_CHECKING:
+    from eodag.api.product._product import EOProduct
+
+
+class Sentinel2Driver(DatasetDriver):
+    """Driver for Sentinel2 products"""
+
+    #: Band keys associated with their default Ground Sampling Distance (GSD)
+    BANDS_DEFAULT_GSD = {
+        "10M": ("B02", "B03", "B04", "B08", "TCI"),
+        "20M": ("B05", "B06", "B07", "B11", "B12", "B8A"),
+        "60M": ("B01", "B09", "B10"),
+    }
+
+    #: list of patterns to match asset keys and roles
+    ASSET_KEYS_PATTERNS_ROLES: list[AssetPatterns] = [
+        # masks
+        {
+            "pattern": re.compile(r"^.*?(MSK_[^/\\]+)\.(?:jp2|tiff?)$", re.IGNORECASE),
+            "roles": ["data-mask"],
+        },
+        # visual
+        {
+            "pattern": re.compile(
+                r"^.*?(TCI)(_[0-9]+m)?\.(?:jp2|tiff?)$", re.IGNORECASE
+            ),
+            "roles": ["visual"],
+        },
+        # bands
+        {
+            "pattern": re.compile(
+                r"^.*?([A-Z]+[0-9]*[A-Z]?)(_[0-9]+m)?\.(?:jp2|tiff?)$", re.IGNORECASE
+            ),
+            "roles": ["data"],
+        },
+        # metadata
+        {
+            "pattern": re.compile(
+                r"^(?:.*[/\\])?([^/\\]+)(\.xml|\.xsd|\.safe|\.json)$", re.IGNORECASE
+            ),
+            "roles": ["metadata"],
+        },
+        # thumbnail
+        {
+            "pattern": re.compile(
+                r"^(?:.*[/\\])?(thumbnail)(\.jpe?g|\.png)$", re.IGNORECASE
+            ),
+            "roles": ["thumbnail"],
+        },
+        # quicklook
+        {
+            "pattern": re.compile(
+                r"^(?:.*[/\\])?[^/\\]+(-ql|preview|quick-?look)(\.jpe?g|\.png)$",
+                re.IGNORECASE,
+            ),
+            "roles": ["overview"],
+        },
+        # default
+        {"pattern": re.compile(r"^(?:.*[/\\])?([^/\\]+)$"), "roles": ["auxiliary"]},
+    ]
+
+    def _normalize_key(self, key: str, eo_product: EOProduct) -> str:
+        upper_key = key.upper()
+        # check if key matched any normalized
+        for res in self.BANDS_DEFAULT_GSD:
+            if res in upper_key:
+                for norm_key in self.BANDS_DEFAULT_GSD[res]:
+                    if norm_key in upper_key:
+                        return norm_key
+
+        return super()._normalize_key(key, eo_product)
diff --git a/eodag/plugins/search/creodias_s3.py b/eodag/plugins/search/creodias_s3.py
index 656a9d76..d5223be1 100644
--- a/eodag/plugins/search/creodias_s3.py
+++ b/eodag/plugins/search/creodias_s3.py
@@ -16,8 +16,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 import logging
+import os
 from types import MethodType
 from typing import Any
+from urllib.parse import urlparse
 
 import boto3
 import botocore
@@ -82,32 +84,28 @@ def _update_assets(product: EOProduct, config: PluginConfig, auth: AwsAuth):
                 )
             logger.debug("Listing assets in %s", prefix)
             product.assets = AssetsDict(product)
-            s3_res = auth.s3_client.list_objects(
+            s3_objects = auth.s3_client.list_objects(
                 Bucket=config.s3_bucket, Prefix=prefix, MaxKeys=300
             )
             # check if product path has assets or is already a file
-            if "Contents" in s3_res:
-                for asset in s3_res["Contents"]:
-                    asset_basename = (
-                        asset["Key"].split("/")[-1]
-                        if "/" in asset["Key"]
-                        else asset["Key"]
+            if "Contents" in s3_objects:
+                for s3_obj in s3_objects["Contents"]:
+                    key, roles = product.driver.guess_asset_key_and_roles(
+                        s3_obj["Key"], product
                     )
-
-                    if len(asset_basename) > 0 and asset_basename not in product.assets:
-                        role = (
-                            "data"
-                            if asset_basename.split(".")[-1] in DATA_EXTENSIONS
-                            else "metadata"
-                        )
-
-                        product.assets[asset_basename] = {
-                            "title": asset_basename,
-                            "roles": [role],
-                            "href": f"s3://{config.s3_bucket}/{asset['Key']}",
+                    parsed_url = urlparse(s3_obj["Key"])
+                    title = os.path.basename(parsed_url.path)
+
+                    if key and key not in product.assets:
+                        product.assets[key] = {
+                            "title": title,
+                            "roles": roles,
+                            "href": f"s3://{config.s3_bucket}/{s3_obj['Key']}",
                         }
-                        if mime_type := guess_file_type(asset["Key"]):
-                            product.assets[asset_basename]["type"] = mime_type
+                        if mime_type := guess_file_type(s3_obj["Key"]):
+                            product.assets[key]["type"] = mime_type
+                # sort assets
+                product.assets.data = dict(sorted(product.assets.data.items()))
             # update driver
             product.driver = product.get_driver()
 
diff --git a/eodag/plugins/search/qssearch.py b/eodag/plugins/search/qssearch.py
index 29e605b9..2a7dd054 100644
--- a/eodag/plugins/search/qssearch.py
+++ b/eodag/plugins/search/qssearch.py
@@ -1087,8 +1087,15 @@ class QueryStringSearch(Search):
             product.properties = dict(
                 getattr(self.config, "product_type_config", {}), **product.properties
             )
-            # move assets from properties to product's attr
-            product.assets.update(product.properties.pop("assets", {}))
+            # move assets from properties to product's attr, normalize keys & roles
+            for key, asset in product.properties.pop("assets", {}).items():
+                norm_key, asset["roles"] = product.driver.guess_asset_key_and_roles(
+                    asset.get("href", ""), product
+                )
+                if norm_key:
+                    product.assets[norm_key] = asset
+            # sort assets
+            product.assets.data = dict(sorted(product.assets.data.items()))
             products.append(product)
         return products
 
diff --git a/eodag/resources/providers.yml b/eodag/resources/providers.yml
index 5cb34c89..9486f2a9 100644
--- a/eodag/resources/providers.yml
+++ b/eodag/resources/providers.yml
@@ -5910,6 +5910,9 @@
       orbitNumber:
         - '{{"query":{{"spaceborne:absoluteOrbitID":{{"eq":"{orbitNumber}"}}}}}}'
         - '$.properties."spaceborne:absoluteOrbitID"'
+      relativeOrbitNumber:
+        - '{{"query":{{"spaceborne:orbitID":{{"eq":"{relativeOrbitNumber}"}}}}}}'
+        - '$.properties."spaceborne:orbitID"'
       orbitDirection:
         - '{{"query":{{"spaceborne:orbitDirection":{{"eq":"{orbitDirection}"}}}}}}'
         - '$.properties."spaceborne:orbitDirection"'


Test patch:
diff --git a/tests/context.py b/tests/context.py
index 0b64d07f..2fbb7ced 100644
--- a/tests/context.py
+++ b/tests/context.py
@@ -28,7 +28,10 @@ from eodag import EODataAccessGateway, api, config, setup_logging
 from eodag.api.core import DEFAULT_ITEMS_PER_PAGE, DEFAULT_MAX_ITEMS_PER_PAGE
 from eodag.api.product import EOProduct
 from eodag.api.product.drivers import DRIVERS
-from eodag.api.product.drivers.base import DatasetDriver
+from eodag.api.product.drivers.generic import GenericDriver
+from eodag.api.product.drivers.sentinel1 import Sentinel1Driver
+from eodag.api.product.drivers.sentinel2 import Sentinel2Driver
+from eodag.api.product.drivers.base import DatasetDriver, NoDriver
 from eodag.api.product.metadata_mapping import (
     format_metadata,
     OFFLINE_STATUS,
diff --git a/tests/units/test_eoproduct_driver_generic.py b/tests/units/test_eoproduct_driver_generic.py
new file mode 100644
index 00000000..d7e882ae
--- /dev/null
+++ b/tests/units/test_eoproduct_driver_generic.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021, CS GROUP - France, http://www.c-s.fr
+#
+# This file is part of EODAG project
+#     https://www.github.com/CS-SI/EODAG
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+from tests import TEST_RESOURCES_PATH, EODagTestCase
+from tests.context import EOProduct, GenericDriver, NoDriver
+
+
+class TestEOProductDriverGeneric(EODagTestCase):
+    def setUp(self):
+        super(TestEOProductDriverGeneric, self).setUp()
+        self.product = EOProduct(
+            self.provider, self.eoproduct_props, productType="FAKE_PRODUCT_TYPE"
+        )
+        self.product.properties["title"] = os.path.join(
+            TEST_RESOURCES_PATH,
+            "products",
+            "S2A_MSIL1C_20180101T105441_N0206_R051_T31TDH_20180101T124911",
+        )
+
+    def test_driver_generic_init(self):
+        """The appropriate driver must have been set"""
+        self.assertIsInstance(self.product.driver, GenericDriver)
+        self.assertTrue(hasattr(self.product.driver, "legacy"))
+        try:
+            # import from eodag-cube if installed
+            from eodag_cube.api.product.drivers.base import (  # pyright: ignore[reportMissingImports]; isort: skip
+                DatasetDriver as DatasetDriver_cube,
+            )
+
+            self.assertIsInstance(self.product.driver.legacy, DatasetDriver_cube)
+        except ImportError:
+            self.assertIsInstance(self.product.driver.legacy, NoDriver)
+
+    def test_driver_generic_guess_asset_key_and_roles(self):
+        """The driver must guess appropriate asset key and roles"""
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles("", self.product),
+            (None, None),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "2018/1/28/0/ew-hv.tif", self.product
+            ),
+            ("ew-hv.tif", ["data"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "2018/1/28/0/ew-hv.jp2", self.product
+            ),
+            ("ew-hv.jp2", ["data"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "2018/1/28/0/ew-hv.nc", self.product
+            ),
+            ("ew-hv.nc", ["data"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "2018/1/28/0/ew-hv.grib2", self.product
+            ),
+            ("ew-hv.grib2", ["data"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "2018/1/28/0/ew-hv.foo", self.product
+            ),
+            ("ew-hv.foo", ["auxiliary"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/rfi-ew-hh.xml", self.product
+            ),
+            ("rfi-ew-hh.xml", ["metadata"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/thumbnail.png", self.product
+            ),
+            ("thumbnail.png", ["thumbnail"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/quick-look.jpg", self.product
+            ),
+            ("quick-look.jpg", ["overview"]),
+        )
diff --git a/tests/units/test_eoproduct_driver_sentinel1.py b/tests/units/test_eoproduct_driver_sentinel1.py
new file mode 100644
index 00000000..a2d30793
--- /dev/null
+++ b/tests/units/test_eoproduct_driver_sentinel1.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021, CS GROUP - France, http://www.c-s.fr
+#
+# This file is part of EODAG project
+#     https://www.github.com/CS-SI/EODAG
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+from tests import TEST_RESOURCES_PATH, EODagTestCase
+from tests.context import EOProduct, NoDriver, Sentinel1Driver
+
+
+class TestEOProductDriverSentinel1Driver(EODagTestCase):
+    def setUp(self):
+        super(TestEOProductDriverSentinel1Driver, self).setUp()
+        self.product = EOProduct(
+            self.provider, self.eoproduct_props, productType="S1_SAR_OCN"
+        )
+        self.product.properties["title"] = os.path.join(
+            TEST_RESOURCES_PATH,
+            "products",
+            "S2A_MSIL1C_20180101T105441_N0206_R051_T31TDH_20180101T124911",
+        )
+
+    def test_driver_s1_init(self):
+        """The appropriate driver must have been set"""
+        self.assertIsInstance(self.product.driver, Sentinel1Driver)
+        self.assertTrue(hasattr(self.product.driver, "legacy"))
+        try:
+            # import from eodag-cube if installed
+            from eodag_cube.api.product.drivers.base import (  # pyright: ignore[reportMissingImports]; isort: skip
+                DatasetDriver as DatasetDriver_cube,
+            )
+
+            self.assertIsInstance(self.product.driver.legacy, DatasetDriver_cube)
+        except ImportError:
+            self.assertIsInstance(self.product.driver.legacy, NoDriver)
+
+    def test_driver_s1_guess_asset_key_and_roles(self):
+        """The driver must guess appropriate asset key and roles"""
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles("", self.product),
+            (None, None),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "2018/1/28/0/ew-hv.tiff", self.product
+            ),
+            ("HV", ["data"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "http://foo/1/28/0/iw-hh.tif", self.product
+            ),
+            ("HH", ["data"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/s1a-vv.tiff", self.product
+            ),
+            ("VV", ["data"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/rfi-ew-hh.xml", self.product
+            ),
+            ("rfi-hh.xml", ["metadata"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/thumbnail.png", self.product
+            ),
+            ("thumbnail.png", ["thumbnail"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/quick-look.jpg", self.product
+            ),
+            ("quick-look.jpg", ["overview"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/foo.bar", self.product
+            ),
+            ("foo.bar", ["auxiliary"]),
+        )
diff --git a/tests/units/test_eoproduct_driver_sentinel2.py b/tests/units/test_eoproduct_driver_sentinel2.py
new file mode 100644
index 00000000..3abc37e1
--- /dev/null
+++ b/tests/units/test_eoproduct_driver_sentinel2.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021, CS GROUP - France, http://www.c-s.fr
+#
+# This file is part of EODAG project
+#     https://www.github.com/CS-SI/EODAG
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+from tests import TEST_RESOURCES_PATH, EODagTestCase
+from tests.context import EOProduct, NoDriver, Sentinel2Driver
+
+
+class TestEOProductDriverSentinel2Driver(EODagTestCase):
+    def setUp(self):
+        super(TestEOProductDriverSentinel2Driver, self).setUp()
+        self.product = EOProduct(
+            self.provider, self.eoproduct_props, productType=self.product_type
+        )
+        self.product.properties["title"] = os.path.join(
+            TEST_RESOURCES_PATH,
+            "products",
+            "S2A_MSIL1C_20180101T105441_N0206_R051_T31TDH_20180101T124911",
+        )
+
+    def test_driver_s2_init(self):
+        """The appropriate driver must have been set"""
+        self.assertIsInstance(self.product.driver, Sentinel2Driver)
+        self.assertTrue(hasattr(self.product.driver, "legacy"))
+        try:
+            # import from eodag-cube if installed
+            from eodag_cube.api.product.drivers.base import (  # pyright: ignore[reportMissingImports]; isort: skip
+                DatasetDriver as DatasetDriver_cube,
+            )
+
+            self.assertIsInstance(self.product.driver.legacy, DatasetDriver_cube)
+        except ImportError:
+            self.assertIsInstance(self.product.driver.legacy, NoDriver)
+
+    def test_driver_s2_guess_asset_key_and_roles(self):
+        """The driver must guess appropriate asset key and roles"""
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles("", self.product),
+            (None, None),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "tiles/31/T/DJ/2018/1/28/0/bla_TCI.jp2", self.product
+            ),
+            ("TCI", ["visual"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "tiles/31/T/DJ/2018/1/28/0/B01.jp2", self.product
+            ),
+            ("B01", ["data"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "http://foo/1/28/0/bla_bla-B02_10m.tif", self.product
+            ),
+            ("B02", ["data"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/bla_bla-B02_20m.tiff", self.product
+            ),
+            ("B02_20m", ["data"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/MSK_bla_B02.tiff", self.product
+            ),
+            ("MSK_bla_B02", ["data-mask"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/Foo-bar.xml", self.product
+            ),
+            ("Foo-bar.xml", ["metadata"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/thumbnail.png", self.product
+            ),
+            ("thumbnail.png", ["thumbnail"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/Foo_bar-ql.jpg", self.product
+            ),
+            ("ql.jpg", ["overview"]),
+        )
+        self.assertEqual(
+            self.product.driver.guess_asset_key_and_roles(
+                "s3://foo/1/28/0/foo.bar", self.product
+            ),
+            ("foo.bar", ["auxiliary"]),
+        )