Repository: CS-SI/eodag
Pull request #1187 (2024-06-07 07:40:59)

fix(server): avoid multiplication of providers


Patch:
diff --git a/eodag/api/product/_product.py b/eodag/api/product/_product.py
index 07189156..31174d26 100644
--- a/eodag/api/product/_product.py
+++ b/eodag/api/product/_product.py
@@ -37,7 +37,11 @@ except ImportError:
     from eodag.api.product._assets import AssetsDict  # type: ignore # noqa
 
 from eodag.api.product.drivers import DRIVERS, NoDriver
-from eodag.api.product.metadata_mapping import NOT_AVAILABLE, NOT_MAPPED
+from eodag.api.product.metadata_mapping import (
+    DEFAULT_GEOMETRY,
+    NOT_AVAILABLE,
+    NOT_MAPPED,
+)
 from eodag.utils import (
     DEFAULT_DOWNLOAD_TIMEOUT,
     DEFAULT_DOWNLOAD_WAIT,
@@ -142,7 +146,7 @@ class EOProduct:
                 f"No geometry available to build EOProduct(id={properties.get('id', None)}, provider={provider})"
             )
         elif not properties["geometry"] or properties["geometry"] == NOT_AVAILABLE:
-            product_geometry = properties.pop("defaultGeometry")
+            product_geometry = properties.pop("defaultGeometry", DEFAULT_GEOMETRY)
         else:
             product_geometry = properties["geometry"]
         # Let's try 'latmin lonmin latmax lonmax'
diff --git a/eodag/api/product/metadata_mapping.py b/eodag/api/product/metadata_mapping.py
index 46cacde8..26ff803c 100644
--- a/eodag/api/product/metadata_mapping.py
+++ b/eodag/api/product/metadata_mapping.py
@@ -82,6 +82,7 @@ OFFLINE_STATUS = "OFFLINE"
 COORDS_ROUNDING_PRECISION = 4
 WKT_MAX_LEN = 1600
 COMPLEX_QS_REGEX = re.compile(r"^(.+=)?([^=]*)({.+})+([^=&]*)$")
+DEFAULT_GEOMETRY = "POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90))"
 
 
 def get_metadata_path(
@@ -387,7 +388,9 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
         def convert_from_ewkt(ewkt_string: str) -> Union[BaseGeometry, str]:
             """Convert EWKT (Extended Well-Known text) to shapely geometry"""
 
-            ewkt_regex = re.compile(r"^(?P<proj>[A-Za-z]+=[0-9]+);(?P<wkt>.*)$")
+            ewkt_regex = re.compile(
+                r"^.*(?P<proj>SRID=[0-9]+);(?P<wkt>[A-Z0-9 \(\),\.-]+).*$"
+            )
             ewkt_match = ewkt_regex.match(ewkt_string)
             if ewkt_match:
                 g = ewkt_match.groupdict()
diff --git a/eodag/resources/providers.yml b/eodag/resources/providers.yml
index 2b82541d..27aab61c 100644
--- a/eodag/resources/providers.yml
+++ b/eodag/resources/providers.yml
@@ -1020,7 +1020,7 @@
       # The geographic extent of the product
       geometry:
         - null
-        - '{$.Footprint.`sub(/.*(SRID=[0-9]+;[A-Z0-9 \(\),\.-]+).*/, \\1)`#from_ewkt}'
+        - '{$.Footprint#from_ewkt}'
       # The url to download the product "as is" (literal or as a template to be completed either after the search result
       # is obtained from the provider or during the eodag download phase)
       downloadLink: 'https://zipper.creodias.eu/odata/v1/Products({uid})/$value'
@@ -3967,7 +3967,7 @@
       # The geographic extent of the product
       geometry:
         - null
-        - '{$.Footprint.`sub(/.*(SRID=[0-9]+;[A-Z0-9 \(\),\.-]+).*/, \\1)`#from_ewkt}'
+        - '{$.Footprint#from_ewkt}'
       # The url to download the product "as is" (literal or as a template to be completed either after the search result
       # is obtained from the provider or during the eodag download phase)
       downloadLink: 'https://catalogue.dataspace.copernicus.eu/odata/v1/Products({uid})/$value'
@@ -6205,7 +6205,7 @@
       # The geographic extent of the product
       geometry:
         - null
-        - '{$.Footprint.`sub(/.*(SRID=[0-9]+;[A-Z0-9 \(\),\.-]+).*/, \\1)`#from_ewkt}'
+        - '{$.Footprint#from_ewkt}'
       # The url to download the product "as is" (literal or as a template to be completed either after the search result
       # is obtained from the provider or during the eodag download phase)
       downloadLink: 'https://zipper.creodias.eu/odata/v1/Products({uid})/$value'
diff --git a/eodag/rest/stac.py b/eodag/rest/stac.py
index cc32eef6..1764ccd9 100644
--- a/eodag/rest/stac.py
+++ b/eodag/rest/stac.py
@@ -744,7 +744,9 @@ class StacCollection(StacCommon):
             },
         )
         # override EODAG's collection with the external collection
-        ext_stac_collection = self.ext_stac_collections.get(product_type["ID"], {})
+        ext_stac_collection = deepcopy(
+            self.ext_stac_collections.get(product_type["ID"], {})
+        )
 
         # update links (keep eodag links as defaults)
         ext_stac_collection.setdefault("links", {})
diff --git a/eodag/rest/types/stac_queryables.py b/eodag/rest/types/stac_queryables.py
index 2596dc33..4127b9ae 100644
--- a/eodag/rest/types/stac_queryables.py
+++ b/eodag/rest/types/stac_queryables.py
@@ -19,7 +19,13 @@ from __future__ import annotations
 
 from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Union
 
-from pydantic import BaseModel, Field
+from pydantic import (
+    BaseModel,
+    Field,
+    SerializationInfo,
+    SerializerFunctionWrapHandler,
+    model_serializer,
+)
 
 from eodag.types import python_field_definition_to_json
 from eodag.utils import Annotated
@@ -46,6 +52,8 @@ class StacQueryableProperty(BaseModel):
     value: Optional[Any] = None
     min: Optional[Union[int, List[Union[int, None]]]] = None
     max: Optional[Union[int, List[Union[int, None]]]] = None
+    oneOf: Optional[List[Any]] = None
+    items: Optional[Any] = None
 
     @classmethod
     def from_python_field_definition(
@@ -59,6 +67,16 @@ class StacQueryableProperty(BaseModel):
 
         return cls(**def_dict)
 
+    @model_serializer(mode="wrap")
+    def remove_none(
+        self,
+        handler: SerializerFunctionWrapHandler,
+        _: SerializationInfo,
+    ):
+        """Remove none value property fields during serialization"""
+        props: Dict[str, Any] = handler(self)
+        return {k: v for k, v in props.items() if v is not None}
+
 
 class StacQueryables(BaseModel):
     """A class representing queryable properties for the STAC API.
@@ -106,6 +124,12 @@ class StacQueryables(BaseModel):
             description="Datetime - use parameters year, month, day, time instead if available",
             ref="https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/datetime.json#/properties/datetime",
         ),
+        "bbox": StacQueryableProperty(
+            description="BBox",
+            type="array",
+            oneOf=[{"minItems": 4, "maxItems": 4}, {"minItems": 6, "maxItems": 6}],
+            items={"type": "number"},
+        ),
     }
     properties: Dict[str, StacQueryableProperty] = Field()
     additional_properties: bool = Field(


Test patch:
diff --git a/tests/units/test_http_server.py b/tests/units/test_http_server.py
index 8ff5bd42..abd85023 100644
--- a/tests/units/test_http_server.py
+++ b/tests/units/test_http_server.py
@@ -1620,7 +1620,9 @@ class RequestTestCase(unittest.TestCase):
         self.assertIn("processing:level", res["properties"])
         processing_level = res["properties"]["processing:level"]
         self.assertListEqual(["string", "null"], processing_level["type"])
-        self.assertIsNone(processing_level["min"])
+        self.assertNotIn(
+            "min", processing_level
+        )  # none values are left out in serialization
 
     @mock.patch("eodag.utils.requests.requests.Session.get", autospec=True)
     def test_product_type_queryables_from_constraints(
diff --git a/tests/units/test_metadata_mapping.py b/tests/units/test_metadata_mapping.py
index d742084f..63ae1183 100644
--- a/tests/units/test_metadata_mapping.py
+++ b/tests/units/test_metadata_mapping.py
@@ -135,6 +135,12 @@ class TestMetadataFormatter(unittest.TestCase):
         geom = wkt.loads(wkt_str)
         self.assertEqual(round(geom.x, 1), 2.9)
         self.assertEqual(round(geom.y, 1), 43.5)
+        self.assertEqual(
+            wkt_str,
+            format_metadata(
+                to_format, fieldname="geography'SRID=3857;POINT (321976 5390999)'"
+            ),
+        )
 
     def test_convert_to_ewkt(self):
         to_format = "{fieldname#to_ewkt}"