eodag 3.0.0b3__py3-none-any.whl → 3.1.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. eodag/api/core.py +292 -198
  2. eodag/api/product/_assets.py +6 -6
  3. eodag/api/product/_product.py +18 -18
  4. eodag/api/product/metadata_mapping.py +51 -14
  5. eodag/api/search_result.py +29 -3
  6. eodag/cli.py +57 -20
  7. eodag/config.py +413 -117
  8. eodag/plugins/apis/base.py +10 -4
  9. eodag/plugins/apis/ecmwf.py +49 -16
  10. eodag/plugins/apis/usgs.py +30 -7
  11. eodag/plugins/authentication/aws_auth.py +14 -5
  12. eodag/plugins/authentication/base.py +10 -1
  13. eodag/plugins/authentication/generic.py +14 -3
  14. eodag/plugins/authentication/header.py +12 -4
  15. eodag/plugins/authentication/keycloak.py +41 -22
  16. eodag/plugins/authentication/oauth.py +11 -1
  17. eodag/plugins/authentication/openid_connect.py +178 -163
  18. eodag/plugins/authentication/qsauth.py +12 -4
  19. eodag/plugins/authentication/sas_auth.py +19 -2
  20. eodag/plugins/authentication/token.py +93 -15
  21. eodag/plugins/authentication/token_exchange.py +19 -19
  22. eodag/plugins/crunch/base.py +4 -1
  23. eodag/plugins/crunch/filter_date.py +5 -2
  24. eodag/plugins/crunch/filter_latest_intersect.py +5 -4
  25. eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
  26. eodag/plugins/crunch/filter_overlap.py +5 -7
  27. eodag/plugins/crunch/filter_property.py +6 -6
  28. eodag/plugins/download/aws.py +50 -34
  29. eodag/plugins/download/base.py +41 -50
  30. eodag/plugins/download/creodias_s3.py +40 -2
  31. eodag/plugins/download/http.py +221 -195
  32. eodag/plugins/download/s3rest.py +25 -25
  33. eodag/plugins/manager.py +168 -23
  34. eodag/plugins/search/base.py +106 -39
  35. eodag/plugins/search/build_search_result.py +1065 -324
  36. eodag/plugins/search/cop_marine.py +112 -29
  37. eodag/plugins/search/creodias_s3.py +45 -24
  38. eodag/plugins/search/csw.py +41 -1
  39. eodag/plugins/search/data_request_search.py +109 -9
  40. eodag/plugins/search/qssearch.py +549 -257
  41. eodag/plugins/search/static_stac_search.py +20 -21
  42. eodag/resources/ext_product_types.json +1 -1
  43. eodag/resources/product_types.yml +577 -87
  44. eodag/resources/providers.yml +1619 -2776
  45. eodag/resources/stac.yml +3 -163
  46. eodag/resources/user_conf_template.yml +112 -97
  47. eodag/rest/config.py +1 -2
  48. eodag/rest/constants.py +0 -1
  49. eodag/rest/core.py +138 -98
  50. eodag/rest/errors.py +181 -0
  51. eodag/rest/server.py +55 -329
  52. eodag/rest/stac.py +93 -544
  53. eodag/rest/types/eodag_search.py +19 -8
  54. eodag/rest/types/queryables.py +6 -8
  55. eodag/rest/types/stac_search.py +11 -2
  56. eodag/rest/utils/__init__.py +3 -0
  57. eodag/types/__init__.py +71 -18
  58. eodag/types/download_args.py +3 -3
  59. eodag/types/queryables.py +180 -73
  60. eodag/types/search_args.py +3 -3
  61. eodag/types/whoosh.py +126 -0
  62. eodag/utils/__init__.py +147 -66
  63. eodag/utils/exceptions.py +47 -26
  64. eodag/utils/logging.py +37 -77
  65. eodag/utils/repr.py +65 -6
  66. eodag/utils/requests.py +11 -13
  67. eodag/utils/stac_reader.py +1 -1
  68. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/METADATA +80 -81
  69. eodag-3.1.0b1.dist-info/RECORD +108 -0
  70. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/WHEEL +1 -1
  71. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/entry_points.txt +4 -2
  72. eodag/resources/constraints/climate-dt.json +0 -13
  73. eodag/resources/constraints/extremes-dt.json +0 -8
  74. eodag/utils/constraints.py +0 -244
  75. eodag-3.0.0b3.dist-info/RECORD +0 -110
  76. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/LICENSE +0 -0
  77. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/top_level.txt +0 -0
@@ -98,12 +98,12 @@ class AssetsDict(UserDict):
98
98
  <details><summary style='color: grey;'>
99
99
  <span style='color: black'>'{k}'</span>:&ensp;
100
100
  {{
101
- {"'roles': '<span style='color: black'>"+str(v['roles'])+"</span>',&ensp;"
102
- if v.get("roles") else ""}
103
- {"'type': '"+str(v['type'])+"',&ensp;"
104
- if v.get("type") else ""}
105
- {"'title': '<span style='color: black'>"+str(v['title'])+"</span>',&ensp;"
106
- if v.get("title") else ""}
101
+ {"'roles': '<span style='color: black'>" + str(v['roles']) + "</span>',&ensp;"
102
+ if v.get("roles") else ""}
103
+ {"'type': '" + str(v['type']) + "',&ensp;"
104
+ if v.get("type") else ""}
105
+ {"'title': '<span style='color: black'>" + str(v['title']) + "</span>',&ensp;"
106
+ if v.get("title") else ""}
107
107
  ...
108
108
  }}
109
109
  </summary>
@@ -116,6 +116,7 @@ class EOProduct:
116
116
  properties: Dict[str, Any]
117
117
  product_type: Optional[str]
118
118
  location: str
119
+ filename: str
119
120
  remote_location: str
120
121
  search_kwargs: Any
121
122
  geometry: BaseGeometry
@@ -281,8 +282,8 @@ class EOProduct:
281
282
  def download(
282
283
  self,
283
284
  progress_callback: Optional[ProgressCallback] = None,
284
- wait: int = DEFAULT_DOWNLOAD_WAIT,
285
- timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
285
+ wait: float = DEFAULT_DOWNLOAD_WAIT,
286
+ timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
286
287
  **kwargs: Unpack[DownloadConf],
287
288
  ) -> str:
288
289
  """Download the EO product using the provided download plugin and the
@@ -525,22 +526,21 @@ class EOProduct:
525
526
  <tr style='background-color: transparent;'>
526
527
  <td style='text-align: left; vertical-align: top;'>
527
528
  {dict_to_html_table({
528
- "provider": self.provider,
529
- "product_type": self.product_type,
530
- "properties[&quot;id&quot;]": self.properties.get('id', None),
531
- "properties[&quot;startTimeFromAscendingNode&quot;]": self.properties.get(
532
- 'startTimeFromAscendingNode', None
533
- ),
534
- "properties[&quot;completionTimeFromAscendingNode&quot;]": self.properties.get(
535
- 'completionTimeFromAscendingNode', None
536
- ),
537
- }, brackets=False)}
538
- <details><summary style='color: grey; margin-top: 10px;'>properties:&ensp;({
539
- len(self.properties)
540
- })</summary>{dict_to_html_table(self.properties, depth=1)}</details>
541
- <details><summary style='color: grey; margin-top: 10px;'>assets:&ensp;({
542
- len(self.assets)
543
- })</summary>{self.assets._repr_html_(embeded=True)}</details>
529
+ "provider": self.provider,
530
+ "product_type": self.product_type,
531
+ "properties[&quot;id&quot;]": self.properties.get('id', None),
532
+ "properties[&quot;startTimeFromAscendingNode&quot;]": self.properties.get(
533
+ 'startTimeFromAscendingNode', None
534
+ ),
535
+ "properties[&quot;completionTimeFromAscendingNode&quot;]": self.properties.get(
536
+ 'completionTimeFromAscendingNode', None
537
+ ),
538
+ }, brackets=False)}
539
+ <details><summary style='color: grey; margin-top: 10px;'>properties:&ensp;({len(
540
+ self.properties)})</summary>{
541
+ dict_to_html_table(self.properties, depth=1)}</details>
542
+ <details><summary style='color: grey; margin-top: 10px;'>assets:&ensp;({len(
543
+ self.assets)})</summary>{self.assets._repr_html_(embeded=True)}</details>
544
544
  </td>
545
545
  <td {geom_style} title='geometry'>geometry<br />{self.geometry._repr_svg_()}</td>
546
546
  <td {thumbnail_style} title='properties[&quot;thumbnail&quot;]'>{thumbnail_html}</td>
@@ -40,7 +40,9 @@ from typing import (
40
40
  import geojson
41
41
  import orjson
42
42
  import pyproj
43
+ import shapely
43
44
  from dateutil.parser import isoparse
45
+ from dateutil.relativedelta import relativedelta
44
46
  from dateutil.tz import UTC, tzutc
45
47
  from jsonpath_ng.jsonpath import Child, JSONPath
46
48
  from lxml import etree
@@ -152,7 +154,7 @@ def get_search_param(map_value: List[str]) -> str:
152
154
 
153
155
 
154
156
  def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
155
- """Format a string of form {<field_name>#<conversion_function>}
157
+ """Format a string of form ``{<field_name>#<conversion_function>}``
156
158
 
157
159
  The currently understood converters are:
158
160
  - ``datetime_to_timestamp_milliseconds``: converts a utc date string to a timestamp in
@@ -178,6 +180,8 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
178
180
  - ``recursive_sub_str``: recursively substitue in the structure (e.g. dict)
179
181
  values matching a regex
180
182
  - ``slice_str``: slice a string (equivalent to s[start, end, step])
183
+ - ``to_lower``: Convert a string to lowercase
184
+ - ``to_upper``: Convert a string to uppercase
181
185
  - ``fake_l2a_title_from_l1c``: used to generate SAFE format metadata for data from AWS
182
186
  - ``s2msil2a_title_to_aws_productinfo``: used to generate SAFE format metadata for data from AWS
183
187
  - ``split_cop_dem_id``: get the bbox by splitting the product id
@@ -362,6 +366,8 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
362
366
 
363
367
  @staticmethod
364
368
  def convert_to_nwse_bounds(input_geom: BaseGeometry) -> List[float]:
369
+ if isinstance(input_geom, str):
370
+ input_geom = shapely.wkt.loads(input_geom)
365
371
  return list(input_geom.bounds[-1:] + input_geom.bounds[:-1])
366
372
 
367
373
  @staticmethod
@@ -557,6 +563,16 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
557
563
  ]
558
564
  return string[cmin:cmax:cstep]
559
565
 
566
+ @staticmethod
567
+ def convert_to_lower(string: str) -> str:
568
+ """Convert a string to lowercase."""
569
+ return string.lower()
570
+
571
+ @staticmethod
572
+ def convert_to_upper(string: str) -> str:
573
+ """Convert a string to uppercase."""
574
+ return string.upper()
575
+
560
576
  @staticmethod
561
577
  def convert_fake_l2a_title_from_l1c(string: str) -> str:
562
578
  id_regex = re.compile(
@@ -831,9 +847,9 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
831
847
  def convert_get_hydrological_year(date: str):
832
848
  utc_date = MetadataFormatter.convert_to_iso_utc_datetime(date)
833
849
  date_object = datetime.strptime(utc_date, "%Y-%m-%dT%H:%M:%S.%fZ")
834
- date_object_second_year = date_object + timedelta(days=365)
850
+ date_object_second_year = date_object + relativedelta(years=1)
835
851
  return [
836
- f'{date_object.strftime("%Y")}_{date_object_second_year.strftime("%y")}'
852
+ f"{date_object.strftime('%Y')}_{date_object_second_year.strftime('%y')}"
837
853
  ]
838
854
 
839
855
  @staticmethod
@@ -901,10 +917,8 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
901
917
  return assets_dict
902
918
 
903
919
  # if stac extension colon separator `:` is in search params, parse it to prevent issues with vformat
904
- if re.search(r"{[a-zA-Z0-9_-]*:[a-zA-Z0-9_-]*}", search_param):
905
- search_param = re.sub(
906
- r"{([a-zA-Z0-9_-]*):([a-zA-Z0-9_-]*)}", r"{\1_COLON_\2}", search_param
907
- )
920
+ if re.search(r"{[\w-]*:[\w#-]*}", search_param):
921
+ search_param = re.sub(r"{([\w-]*):([\w#-]*)}", r"{\1_COLON_\2}", search_param)
908
922
  kwargs = {k.replace(":", "_COLON_"): v for k, v in kwargs.items()}
909
923
 
910
924
  return MetadataFormatter().vformat(search_param, args, kwargs)
@@ -974,10 +988,24 @@ def properties_from_json(
974
988
  if re.search(r"({[^{}:]+})+", conversion_or_none):
975
989
  conversion_or_none = conversion_or_none.format(**properties)
976
990
 
977
- properties[metadata] = format_metadata(
978
- "{%s%s%s}" % (metadata, SEP, conversion_or_none),
979
- **{metadata: extracted_value},
980
- )
991
+ if extracted_value == NOT_AVAILABLE:
992
+ # try if value can be formatted even if it is not available
993
+ try:
994
+ properties[metadata] = format_metadata(
995
+ "{%s%s%s}" % (metadata, SEP, conversion_or_none),
996
+ **{metadata: extracted_value},
997
+ )
998
+ except ValueError:
999
+ logger.debug(
1000
+ f"{metadata}: {extracted_value} could not be formatted with {conversion_or_none}"
1001
+ )
1002
+ continue
1003
+ else:
1004
+ # in this case formatting should work, otherwise something is wrong in the mapping
1005
+ properties[metadata] = format_metadata(
1006
+ "{%s%s%s}" % (metadata, SEP, conversion_or_none),
1007
+ **{metadata: extracted_value},
1008
+ )
981
1009
  # properties as python objects when possible (format_metadata returns only strings)
982
1010
  try:
983
1011
  properties[metadata] = ast.literal_eval(properties[metadata])
@@ -1208,7 +1236,7 @@ def mtd_cfg_as_conversion_and_querypath(
1208
1236
  dest_dict: Dict[str, Any] = {},
1209
1237
  result_type: str = "json",
1210
1238
  ) -> Dict[str, Any]:
1211
- """Metadata configuration dictionary to querypath with conversion dictionnary
1239
+ """Metadata configuration dictionary to querypath with conversion dictionary
1212
1240
  Transform every src_dict value from jsonpath_str to tuple `(conversion, jsonpath_object)`
1213
1241
  or from xpath_str to tuple `(conversion, xpath_str)`
1214
1242
 
@@ -1461,7 +1489,15 @@ def get_queryable_from_provider(
1461
1489
  :param metadata_mapping: metadata-mapping configuration
1462
1490
  :returns: EODAG configured queryable parameter or None
1463
1491
  """
1464
- pattern = rf"\b{provider_queryable}\b"
1492
+ pattern = rf"\"{provider_queryable}\""
1493
+ # if 1:1 mapping exists privilege this one instead of other mapping
1494
+ # e.g. provider queryable = year -> use year and not date in which year also appears
1495
+ mapping_values = [
1496
+ v[0] if isinstance(v, list) else "" for v in metadata_mapping.values()
1497
+ ]
1498
+ if provider_queryable in mapping_values:
1499
+ ind = mapping_values.index(provider_queryable)
1500
+ return Queryables.get_queryable_from_alias(list(metadata_mapping.keys())[ind])
1465
1501
  for param, param_conf in metadata_mapping.items():
1466
1502
  if isinstance(param_conf, list) and re.search(pattern, param_conf[0]):
1467
1503
  return Queryables.get_queryable_from_alias(param)
@@ -1489,7 +1525,8 @@ def get_provider_queryable_key(
1489
1525
  provider_queryables: Dict[str, Any],
1490
1526
  metadata_mapping: Dict[str, Union[List[Any], str]],
1491
1527
  ) -> str:
1492
- """finds the provider queryable corresponding to the given eodag key based on the metadata mapping
1528
+ """Finds the provider queryable corresponding to the given eodag key based on the metadata mapping
1529
+
1493
1530
  :param eodag_key: key in eodag
1494
1531
  :param provider_queryables: queryables returned from the provider
1495
1532
  :param metadata_mapping: metadata mapping from which the keys are retrieved
@@ -18,9 +18,20 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  from collections import UserList
21
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
21
+ from typing import (
22
+ TYPE_CHECKING,
23
+ Annotated,
24
+ Any,
25
+ Dict,
26
+ Iterable,
27
+ List,
28
+ Optional,
29
+ Tuple,
30
+ Union,
31
+ )
22
32
 
23
33
  from shapely.geometry import GeometryCollection, shape
34
+ from typing_extensions import Doc
24
35
 
25
36
  from eodag.api.product import EOProduct
26
37
  from eodag.plugins.crunch.filter_date import FilterDate
@@ -47,11 +58,19 @@ class SearchResult(UserList):
47
58
 
48
59
  data: List[EOProduct]
49
60
 
61
+ errors: Annotated[
62
+ List[Tuple[str, Exception]], Doc("Tuple of provider name, exception")
63
+ ]
64
+
50
65
  def __init__(
51
- self, products: List[EOProduct], number_matched: Optional[int] = None
66
+ self,
67
+ products: List[EOProduct],
68
+ number_matched: Optional[int] = None,
69
+ errors: List[Tuple[str, Exception]] = [],
52
70
  ) -> None:
53
- super(SearchResult, self).__init__(products)
71
+ super().__init__(products)
54
72
  self.number_matched = number_matched
73
+ self.errors = errors
55
74
 
56
75
  def crunch(self, cruncher: Crunch, **search_params: Any) -> SearchResult:
57
76
  """Do some crunching with the underlying EO products.
@@ -197,6 +216,13 @@ class SearchResult(UserList):
197
216
  + "</table>"
198
217
  )
199
218
 
219
+ def extend(self, other: Iterable) -> None:
220
+ """override extend method to include errors"""
221
+ if isinstance(other, SearchResult):
222
+ self.errors.extend(other.errors)
223
+
224
+ return super().extend(other)
225
+
200
226
 
201
227
  class RawSearchResult(UserList):
202
228
  """An object representing a collection of raw/unparsed search results obtained from a provider.
eodag/cli.py CHANGED
@@ -39,6 +39,7 @@ Commands:
39
39
 
40
40
  noqa: D103
41
41
  """
42
+
42
43
  from __future__ import annotations
43
44
 
44
45
  import json
@@ -56,6 +57,11 @@ from eodag.utils import DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE, parse_qs
56
57
  from eodag.utils.exceptions import NoMatchingProductType, UnsupportedProvider
57
58
  from eodag.utils.logging import setup_logging
58
59
 
60
+ try:
61
+ from eodag.rest.utils import LIVENESS_PROBE_PATH
62
+ except ImportError:
63
+ pass
64
+
59
65
  if TYPE_CHECKING:
60
66
  from click import Context
61
67
 
@@ -69,6 +75,18 @@ CRUNCHERS = [
69
75
  ]
70
76
 
71
77
 
78
+ class LivenessFilter:
79
+ """
80
+ Filter out requests to the liveness probe endpoint
81
+ """
82
+
83
+ def filter(self, record):
84
+ """
85
+ Filter method required by the Python logging API.
86
+ """
87
+ return LIVENESS_PROBE_PATH not in record.getMessage()
88
+
89
+
72
90
  class MutuallyExclusiveOption(click.Option):
73
91
  """Mutually Exclusive Options for Click
74
92
  from https://gist.github.com/jacobtolar/fb80d5552a9a9dfc32b12a829fa21c0c
@@ -553,12 +571,13 @@ def download(ctx: Context, **kwargs: Any) -> None:
553
571
 
554
572
  for idx, product in enumerate(search_results):
555
573
  if product.downloader is None:
574
+ downloader = satim_api._plugins_manager.get_download_plugin(product)
556
575
  auth = product.downloader_auth
557
576
  if auth is None:
558
- auth = satim_api._plugins_manager.get_auth_plugin(product.provider)
559
- search_results[idx].register_downloader(
560
- satim_api._plugins_manager.get_download_plugin(product), auth
561
- )
577
+ auth = satim_api._plugins_manager.get_auth_plugin(
578
+ downloader, product
579
+ )
580
+ search_results[idx].register_downloader(downloader, auth)
562
581
 
563
582
  downloaded_file = product.get_quicklook()
564
583
  if not downloaded_file:
@@ -573,12 +592,13 @@ def download(ctx: Context, **kwargs: Any) -> None:
573
592
  # register downloader
574
593
  for idx, product in enumerate(search_results):
575
594
  if product.downloader is None:
595
+ downloader = satim_api._plugins_manager.get_download_plugin(product)
576
596
  auth = product.downloader_auth
577
597
  if auth is None:
578
- auth = satim_api._plugins_manager.get_auth_plugin(product.provider)
579
- search_results[idx].register_downloader(
580
- satim_api._plugins_manager.get_download_plugin(product), auth
581
- )
598
+ auth = satim_api._plugins_manager.get_auth_plugin(
599
+ downloader, product
600
+ )
601
+ search_results[idx].register_downloader(downloader, auth)
582
602
 
583
603
  downloaded_files = satim_api.download_all(search_results)
584
604
  if downloaded_files and len(downloaded_files) > 0:
@@ -676,7 +696,9 @@ def serve_rest(
676
696
  try:
677
697
  pid = os.fork()
678
698
  except OSError as e:
679
- raise Exception("%s [%d]" % (e.strerror, e.errno))
699
+ raise Exception(
700
+ "%s [%d]" % (e.strerror, e.errno) if e.errno is not None else e.strerror
701
+ )
680
702
 
681
703
  if pid == 0:
682
704
  os.setsid()
@@ -684,19 +706,34 @@ def serve_rest(
684
706
  else:
685
707
  sys.exit(0)
686
708
  else:
709
+ import logging
710
+
687
711
  logging_config = uvicorn.config.LOGGING_CONFIG
688
- if debug:
689
- logging_config["loggers"]["uvicorn"]["level"] = "DEBUG"
690
- logging_config["loggers"]["uvicorn.error"]["level"] = "DEBUG"
691
- logging_config["loggers"]["uvicorn.access"]["level"] = "DEBUG"
692
- logging_config["formatters"]["default"][
693
- "fmt"
694
- ] = "%(asctime)-15s %(name)-32s [%(levelname)-8s] (%(module)-17s) %(message)s"
695
- logging_config["loggers"]["eodag"] = {
696
- "handlers": ["default"],
697
- "level": "DEBUG" if debug else "INFO",
698
- "propagate": False,
712
+ uvicorn_fmt = "%(asctime)-15s %(name)-32s [%(levelname)-8s] %(message)s"
713
+ logging_config["filters"] = {"liveness": {"()": LivenessFilter}}
714
+ logging_config["formatters"]["access"]["fmt"] = uvicorn_fmt
715
+ logging_config["formatters"]["default"]["fmt"] = uvicorn_fmt
716
+ logging_config["loggers"]["uvicorn.access"]["filters"] = ["liveness"]
717
+
718
+ eodag_formatter = logging.Formatter(
719
+ "%(asctime)-15s %(name)-32s [%(levelname)-8s] (tid=%(thread)d) %(message)s"
720
+ )
721
+ logging.getLogger("eodag").handlers[0].setFormatter(eodag_formatter)
722
+
723
+ if ctx.obj["verbosity"] <= 1:
724
+ logging_config["handlers"]["null"] = {
725
+ "level": "DEBUG",
726
+ "class": "logging.NullHandler",
699
727
  }
728
+ logging_config["loggers"]["uvicorn"]["handlers"] = ["null"]
729
+ logging_config["loggers"]["uvicorn.error"]["handlers"] = ["null"]
730
+ logging_config["loggers"]["uvicorn.access"]["handlers"] = ["null"]
731
+ else:
732
+ log_level = "INFO" if ctx.obj["verbosity"] == 2 else "DEBUG"
733
+ logging_config["loggers"]["uvicorn"]["level"] = log_level
734
+ logging_config["loggers"]["uvicorn.error"]["level"] = log_level
735
+ logging_config["loggers"]["uvicorn.access"]["level"] = log_level
736
+
700
737
  uvicorn.run(
701
738
  "eodag.rest.server:app",
702
739
  host=bind_host,