# -*- coding: utf-8 -*-
# Copyright 2018, CS GROUP - France, https://www.csgroup.eu/
#
# 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 os
from importlib.resources import files as res_files
from typing import TYPE_CHECKING, Annotated, Any, Literal, Optional, Union
import orjson
import requests
import yaml
import yaml.parser
from annotated_types import Gt
from jsonpath_ng import JSONPath
from typing_extensions import TypedDict
from eodag.utils import (
HTTP_REQ_TIMEOUT,
USER_AGENT,
cached_yaml_load,
cached_yaml_load_all,
deepcopy,
dict_items_recursive_apply,
merge_mappings,
sort_dict,
string_to_jsonpath,
uri_to_path,
)
from eodag.utils.exceptions import ValidationError
if TYPE_CHECKING:
from typing import ItemsView, Iterator, ValuesView
from typing_extensions import Self
from eodag.api.provider import ProviderConfig
logger = logging.getLogger("eodag.config")
EXT_COLLECTIONS_CONF_URI = (
"https://cs-si.github.io/eodag/eodag/resources/ext_collections.json"
)
AUTH_TOPIC_KEYS = ("auth", "search_auth", "download_auth")
PLUGINS_TOPICS_KEYS = ("api", "search", "download") + AUTH_TOPIC_KEYS
class SimpleYamlProxyConfig:
"""A simple configuration class acting as a proxy to an underlying dict object
as returned by yaml.load"""
def __init__(self, conf_file_path: str) -> None:
try:
self.source: dict[str, Any] = cached_yaml_load(conf_file_path)
except yaml.parser.ParserError as e:
print("Unable to load user configuration file")
raise e
def __getitem__(self, item: Any) -> Any:
return self.source[item]
def __contains__(self, item: Any) -> Any:
return item in self.source
def __iter__(self) -> Iterator[str]:
return iter(self.source)
def items(self) -> ItemsView[str, Any]:
"""Iterate over keys and values of source"""
return self.source.items()
def values(self) -> ValuesView[Any]:
"""Iterate over values of source"""
return self.source.values()
def update(self, other: "SimpleYamlProxyConfig") -> None:
"""Update a :class:`~eodag.config.SimpleYamlProxyConfig`"""
if not isinstance(other, self.__class__):
raise ValueError("'{}' must be of type {}".format(other, self.__class__))
self.source.update(other.source)
[docs]
class PluginConfig(yaml.YAMLObject):
"""Representation of a plugin config.
This class variables describe available plugins configuration parameters.
"""
[docs]
class Sort(TypedDict):
"""Configuration for sort during search"""
#: Default sort settings
sort_by_default: list[tuple[str, str]]
#: F-string template to add to :attr:`~eodag.config.PluginConfig.Pagination.next_page_url_tpl` to sort search
#: results
sort_by_tpl: str
#: Mapping between eodag and provider query parameters used for sort
sort_param_mapping: dict[str, str]
#: Mapping between eodag and provider sort-order parameters
sort_order_mapping: dict[Literal["ascending", "descending"], str]
#: Maximum number of allowed sort parameters per request
max_sort_params: Annotated[int, Gt(0)]
[docs]
class DiscoverCollections(TypedDict, total=False):
"""Configuration for collections discovery"""
#: URL from which the collections can be fetched
fetch_url: Optional[str]
#: HTTP method used to fetch collections
fetch_method: str
#: Request body to fetch collections using POST method
fetch_body: dict[str, Any]
#: Maximum number of connections for concurrent HTTP requests
max_connections: int
#: The f-string template for pagination requests.
next_page_url_tpl: str
#: Index of the starting page for pagination requests.
start_page: int
#: Type of the provider result
result_type: str
#: JsonPath to the list of collections
results_entry: Union[str, JSONPath]
#: Mapping for the collection id
generic_collection_id: str
#: Mapping for collection metadata (e.g. ``description``, ``license``) which can be parsed from the provider
#: result
generic_collection_parsable_metadata: dict[str, str]
#: Mapping for collection properties which can be parsed from the result and are not collection metadata
generic_collection_parsable_properties: dict[str, str]
#: Mapping for collection properties which cannot be parsed from the result and are not collection metadata
generic_collection_unparsable_properties: dict[str, Any]
#: URL to fetch data for a single collection
single_collection_fetch_url: str
#: Query string to be added to the fetch_url to filter for a collection
single_collection_fetch_qs: str
#: Mapping for collection metadata returned by the endpoint given in single_collection_fetch_url. If ``id``
#: is redefined in this mapping, it will replace ``generic_collection_id`` value
single_collection_parsable_metadata: dict[str, str]
[docs]
class DiscoverQueryables(TypedDict, total=False):
"""Configuration for queryables discovery"""
#: URL to fetch the queryables valid for all collections
fetch_url: Optional[str]
#: URL to fetch the queryables for a specific collection
collection_fetch_url: Optional[str]
#: Type of the result
result_type: str
#: JsonPath to retrieve the queryables from the provider result
results_entry: str
#: :class:`~eodag.plugins.search.base.Search` URL of the constraint file used to build queryables
constraints_url: str
#: :class:`~eodag.plugins.search.base.Search` Key in the json result where the constraints can be found
constraints_entry: str
[docs]
class CollectionSelector(TypedDict, total=False):
"""Define the criteria to select a collection in :class:`~eodag.config.PluginConfig.DynamicDiscoverQueryables`.
The selector matches if the field value starts with the given prefix,
i.e. it matches if ``parameters[field].startswith(prefix)==True``"""
#: Field in the search parameters to match
field: str
#: Prefix to match in the field
prefix: str
[docs]
class DynamicDiscoverQueryables(TypedDict, total=False):
"""Configuration for queryables dynamic discovery.
The given configuration for queryables discovery is used if any collection selector
matches the search parameters.
"""
#: List of collection selection criterias
collection_selector: list[PluginConfig.CollectionSelector]
#: Configuration for queryables discovery to use
discover_queryables: PluginConfig.DiscoverQueryables
[docs]
class OrderOnResponse(TypedDict):
"""Configuration for order on-response during download"""
#: Parameters metadata-mapping to apply to the order response
metadata_mapping: dict[str, Union[str, list[str]]]
[docs]
class OrderStatusSuccess(TypedDict):
"""
Configuration to identify order status success during download
Order status response matching the following parameters are considered success
At least one is required
"""
#: Success value for ``status``
status: str
#: Success value for ``message``
message: str
#: Success value for status response HTTP code
http_code: int
[docs]
class OrderStatusOrdered(TypedDict, total=False):
"""
Configuration to identify order status ordered during download
"""
#: HTTP code of the order status response
http_code: int
[docs]
class OrderStatusRequest(TypedDict, total=False):
"""
Order status request configuration
"""
#: Request HTTP method
method: str
#: Request hearders
headers: dict[str, Any]
[docs]
class OrderStatusOnSuccess(TypedDict, total=False):
"""Configuration for order status on-success during download"""
#: Whether a new search is needed on success or not
need_search: bool
#: Return type of the success result
result_type: str
#: Key in the success response that gives access to the result
results_entry: str
#: Metadata-mapping to apply to the success status result
metadata_mapping: dict[str, Union[str, list[str]]]
[docs]
class OrderStatus(TypedDict, total=False):
"""Configuration for order status during download"""
#: Order status request configuration
request: PluginConfig.OrderStatusRequest
#: Metadata-mapping used to parse order status response
metadata_mapping: dict[str, Union[str, list[str]]]
#: Configuration to identify order status success during download
success: PluginConfig.OrderStatusSuccess
#: Part of the order status response that tells there is an error
error: dict[str, Any]
#: Configuration to identify order status ordered during download
ordered: PluginConfig.OrderStatusOrdered
#: Configuration for order status on-success during download
on_success: PluginConfig.OrderStatusOnSuccess
#: :class:`~eodag.plugins.base.PluginTopic` The name of the plugin class to use to instantiate the plugin object
name: str
#: :class:`~eodag.plugins.base.PluginTopic` Plugin type
type: str
#: :class:`~eodag.plugins.base.PluginTopic` Whether the ssl certificates should be verified in the request or not
ssl_verify: bool
#: :class:`~eodag.plugins.base.PluginTopic` Default s3 bucket
s3_bucket: str
#: :class:`~eodag.plugins.base.PluginTopic` Authentication error codes
auth_error_code: Union[int, list[int]]
#: :class:`~eodag.plugins.base.PluginTopic` Time to wait until request timeout in seconds
timeout: float
#: :class:`~eodag.plugins.base.PluginTopic` :class:`urllib3.util.Retry` ``total`` parameter,
#: total number of retries to allow
retry_total: int
#: :class:`~eodag.plugins.base.PluginTopic` :class:`urllib3.util.Retry` ``backoff_factor`` parameter,
#: backoff factor to apply between attempts after the second try
retry_backoff_factor: int
#: :class:`~eodag.plugins.base.PluginTopic` :class:`urllib3.util.Retry` ``status_forcelist`` parameter,
#: list of integer HTTP status codes that we should force a retry on
retry_status_forcelist: list[int]
# search & api -----------------------------------------------------------------------------------------------------
# copied from ProviderConfig in PluginManager.get_search_plugins()
priority: int
# per collection metadata-mapping, set in core._prepare_search
collection_config: dict[str, Any]
#: :class:`~eodag.plugins.search.base.Search` Plugin API endpoint
api_endpoint: str
#: :class:`~eodag.plugins.search.base.Search` Whether Search plugin needs authentification or not
need_auth: bool
#: :class:`~eodag.plugins.search.base.Search` Return type of the provider result
result_type: str
#: :class:`~eodag.plugins.search.base.Search`
#: Key in the provider search result that gives access to the result entries
results_entry: str
#: :class:`~eodag.plugins.search.base.Search` Dict containing parameters for pagination
pagination: PluginConfig.Pagination
#: :class:`~eodag.plugins.search.base.Search` Configuration for sorting the results
sort: PluginConfig.Sort
#: :class:`~eodag.plugins.search.base.Search` Configuration for the metadata auto-discovery
discover_metadata: PluginConfig.DiscoverMetadata
#: :class:`~eodag.plugins.search.base.Search` Configuration for the collections auto-discovery
discover_collections: PluginConfig.DiscoverCollections
#: :class:`~eodag.plugins.search.base.Search` Configuration for the queryables auto-discovery
discover_queryables: PluginConfig.DiscoverQueryables
#: :class:`~eodag.plugins.search.base.Search` The mapping between eodag metadata and the plugin specific metadata
metadata_mapping: dict[str, Union[str, list[str]]]
#: :class:`~eodag.plugins.search.base.Search` :attr:`~eodag.config.PluginConfig.metadata_mapping` got from the given
#: collection
metadata_mapping_from_product: str
#: :class:`~eodag.plugins.search.base.Search` A mapping for the metadata of individual assets
assets_mapping: dict[str, dict[str, Any]]
#: :class:`~eodag.plugins.search.base.Search` Parameters to remove from queryables
remove_from_queryables: list[str]
#: :class:`~eodag.plugins.search.base.Search` Parameters to be passed as is in the search url query string
literal_search_params: dict[str, str]
#: :class:`~eodag.plugins.search.qssearch.QueryStringSearch` Characters that should not be quoted in the url params
dont_quote: list[str]
#: :class:`~eodag.plugins.search.qssearch.QueryStringSearch` Guess assets keys using their ``href``.
#: Use their original key if ``False``
asset_key_from_href: bool
#: :class:`~eodag.plugins.search.qssearch.ODataV4Search` Dict describing free text search request build
free_text_search_operations: dict[str, Any]
#: :class:`~eodag.plugins.search.qssearch.ODataV4Search` Set to ``True`` if the metadata is not given in the search
#: result and a two step search has to be performed
per_product_metadata_query: bool
#: :class:`~eodag.plugins.search.qssearch.ODataV4Search` Dict used to simplify further metadata extraction
metadata_pre_mapping: PluginConfig.MetadataPreMapping
#: :class:`~eodag.plugins.search.csw.CSWSearch` Search definition dictionary
search_definition: dict[str, Any]
#: :class:`~eodag.plugins.search.qssearch.PostJsonSearch` Whether to merge responses or not (`aws_eos` specific)
merge_responses: bool
#: :class:`~eodag.plugins.search.qssearch.PostJsonSearch` Collections names (`aws_eos` specific)
_collection: list[str]
#: :class:`~eodag.plugins.search.static_stac_search.StaticStacSearch`
#: Maximum number of connections for concurrent HTTP requests
max_connections: int
#: :class:`~eodag.plugins.search.build_search_result.ECMWFSearch`
#: Whether end date should be excluded from search request or not
end_date_excluded: bool
#: :class:`~eodag.plugins.search.build_search_result.ECMWFSearch`
#: List of parameters used to parse metadata but that must not be included to the query
remove_from_query: list[str]
#: :class:`~eodag.plugins.search.csw.CSWSearch`
#: OGC Catalogue Service version
version: str
#: :class:`~eodag.plugins.apis.ecmwf.EcmwfApi` url of the authentication endpoint
auth_endpoint: str
#: :class:`~eodag.plugins.search.build_search_result.WekeoECMWFSearch`
#: Configurations for the queryables dynamic auto-discovery.
#: A configuration is used based on the given selection criterias. The first match is used.
#: If no match is found, it falls back to standard behaviors (e.g. discovery using
#: :attr:`~eodag.config.PluginConfig.discover_queryables`).
dynamic_discover_queryables: list[PluginConfig.DynamicDiscoverQueryables]
# download ---------------------------------------------------------------------------------------------------------
#: :class:`~eodag.plugins.download.base.Download` Default endpoint url
base_uri: str
#: :class:`~eodag.plugins.download.base.Download` Where to store downloaded products, as an absolute file path
output_dir: str
#: :class:`~eodag.plugins.download.base.Download`
#: Whether the content of the downloaded file should be extracted or not
extract: bool
#: :class:`~eodag.plugins.download.base.Download` Which extension should be used for the downloaded file
output_extension: str
#: :class:`~eodag.plugins.download.base.Download` Whether the directory structure should be flattened or not
flatten_top_dirs: bool
#: :class:`~eodag.plugins.download.base.Download` Level in extracted path tree where to find data
archive_depth: int
#: :class:`~eodag.plugins.download.base.Download` Whether ignore assets and download using ``eodag:download_link``
#: or not
ignore_assets: bool
#: :class:`~eodag.plugins.download.base.Download` Collection specific configuration
products: dict[str, dict[str, Any]]
#: :class:`~eodag.plugins.download.base.Download` Number of maximum workers allowed for parallel downloads
max_workers: int
#: :class:`~eodag.plugins.download.http.HTTPDownload` Whether the product has to be ordered to download it or not
order_enabled: bool
#: :class:`~eodag.plugins.download.http.HTTPDownload` HTTP request method for the order request
order_method: str
#: :class:`~eodag.plugins.download.http.HTTPDownload` Headers to be added to the order request
order_headers: dict[str, str]
#: :class:`~eodag.plugins.download.http.HTTPDownload`
#: Dictionary containing the key :attr:`~eodag.config.PluginConfig.metadata_mapping` which can be used to add new
#: product properties based on the data in response to the order request
order_on_response: PluginConfig.OrderOnResponse
#: :class:`~eodag.plugins.download.http.HTTPDownload` Order status handling
order_status: PluginConfig.OrderStatus
#: :class:`~eodag.plugins.download.http.HTTPDownload`
#: Do not authenticate the download request but only the order and order status ones
no_auth_download: bool
#: :class:`~eodag.plugins.download.http.HTTPDownload` Parameters to be added to the query params of the request
dl_url_params: dict[str, str]
#: :class:`~eodag.plugins.download.aws.AwsDownload`
#: At which level of the path part of the url the bucket can be found
bucket_path_level: int
#: :class:`~eodag.plugins.download.aws.AwsDownload` Whether download is done from a requester-pays bucket or not
requester_pays: bool
#: :class:`~eodag.plugins.download.aws.AwsDownload` S3 endpoint
s3_endpoint: str
# auth -------------------------------------------------------------------------------------------------------------
#: :class:`~eodag.plugins.authentication.base.Authentication` Authentication credentials dictionary
credentials: dict[str, str]
#: :class:`~eodag.plugins.authentication.base.Authentication` Authentication URL
auth_uri: str
#: :class:`~eodag.plugins.authentication.base.Authentication`
#: Dictionary containing all keys/value pairs that should be added to the headers
headers: dict[str, str]
#: :class:`~eodag.plugins.authentication.base.Authentication`
#: Dictionary containing all keys/value pairs that should be added to the headers for token retrieve only
retrieve_headers: dict[str, str]
#: :class:`~eodag.plugins.authentication.base.Authentication`
#: The key pointing to the token in the response from the token server
token_key: str
#: :class:`~eodag.plugins.authentication.base.Authentication`
#: Key to get the refresh token in the response from the token server
refresh_token_key: str
#: :class:`~eodag.plugins.authentication.base.Authentication` URL pattern to match with search plugin endpoint or
#: download link
matching_url: str
#: :class:`~eodag.plugins.authentication.base.Authentication` Part of the search or download plugin configuration
#: that needs authentication
matching_conf: dict[str, Any]
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCRefreshTokenBase`
#: How the token should be used in the request
token_provision: str
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCRefreshTokenBase` The OIDC provider's client ID
client_id: str
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCRefreshTokenBase` The OIDC provider's client secret
client_secret: str
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCRefreshTokenBase`
#: The OIDC provider's ``.well-known/openid-configuration`` url.
oidc_config_url: str
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCRefreshTokenBase` The OIDC token audiences
allowed_audiences: list[str]
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: Whether a user consent is needed during the authentication or not
user_consent_needed: str
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: Where to look for the :attr:`~eodag.config.PluginConfig.authorization_uri`
authentication_uri_source: str
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: The callback url that will handle the code given by the OIDC provider
authentication_uri: str
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: The URL of the authentication backend of the OIDC provider
redirect_uri: str
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: The authorization url of the server (where to query for grants)
authorization_uri: str
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: The xpath to the HTML form element representing the user login form
login_form_xpath: str
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: The xpath to the user consent form
user_consent_form_xpath: str
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: The data that will be passed with the POST request on the form 'action' URL
user_consent_form_data: dict[str, str]
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: Additional data to be passed to the login POST request
additional_login_form_data: dict[str, str]
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: Key/value pairs of patterns/messages used for Authentication errors
exchange_url_error_pattern: dict[str, str]
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: A mapping between OIDC url query string and token handler query string params
token_exchange_params: dict[str, str]
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: Refers to the name of the query param to be used in the query request
token_qs_key: str
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: Way to pass the data to the POST request that is made to the token server
token_exchange_post_data_method: str
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
#: The url to query to get the authorized token
token_uri: str
#: :class:`~eodag.plugins.authentication.sas_auth.SASAuth` Key to get the signed url
signed_url_key: str
#: :class:`~eodag.plugins.authentication.token.TokenAuth`
#: If expected, list of keys to be sent as a tuple through the 'auth' parameter of the request
auth_tuple: list[str]
#: :class:`~eodag.plugins.authentication.token.TokenAuth`
#: Credentials json structure if they should be sent as POST data
req_data: dict[str, Any]
#: :class:`~eodag.plugins.authentication.token.TokenAuth`
#: Whether to send credentials as POST data. If ``True``, always sent; if ``False``, never sent;
#: if not set, sent only when credentials are not embedded in ``auth_uri``
post_credentials: bool
#: :class:`~eodag.plugins.authentication.token.TokenAuth`
#: URL used to fetch the access token with a refresh token
refresh_uri: str
#: :class:`~eodag.plugins.authentication.token.TokenAuth`
#: type of the token
token_type: str
#: :class:`~eodag.plugins.authentication.token.TokenAuth`
#: key to get the expiration time of the token
token_expiration_key: str
#: :class:`~eodag.plugins.authentication.token.TokenAuth`
#: HTTP method to use
request_method: str
#: :class:`~eodag.plugins.authentication.token_exchange.OIDCTokenExchangeAuth`
#: The full :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth` plugin configuration
#: used to retrieve subject token
subject: dict[str, Any]
#: :class:`~eodag.plugins.authentication.token_exchange.OIDCTokenExchangeAuth`
#: Identifies the issuer of the `subject_token`
subject_issuer: str
#: :class:`~eodag.plugins.authentication.token.TokenAuth`
#: :class:`~eodag.plugins.authentication.openid_connect.OIDCRefreshTokenBase`
#: Safety buffer to prevent token rejection from unexpected expiry between validity check and request.
token_expiration_margin: int
#: :class:`~eodag.plugins.authentication.token_exchange.OIDCTokenExchangeAuth`
#: Audience that the token ID is intended for. :attr:`~eodag.config.PluginConfig.client_id` of the Relying Party
audience: str
#: :class:`~eodag.plugins.authentication.generic.GenericAuth`
#: which authentication method should be used
method: str
yaml_loader = yaml.Loader
yaml_dumper = yaml.SafeDumper
yaml_tag = "!plugin"
def __or__(self, other: Union[Self, dict[str, Any]]) -> Self:
"""Return a new PluginConfig with merged values."""
new_config = self.__class__.from_mapping(self.__dict__)
new_config.update(other)
return new_config
def __ior__(self, other: Union[Self, dict[str, Any]]) -> Self:
"""In-place update of the PluginConfig."""
self.update(other)
return self
def __contains__(self, item: str) -> bool:
"""Check if a key is in the PluginConfig."""
return item in self.__dict__
@classmethod
def from_yaml(cls, loader: yaml.Loader, node: Any) -> Self:
"""Build a :class:`~eodag.config.PluginConfig` from Yaml"""
cls.validate(tuple(node_key.value for node_key, _ in node.value))
return loader.construct_yaml_object(node, cls)
@classmethod
def from_mapping(cls, mapping: dict[str, Any]) -> Self:
"""Build a :class:`~eodag.config.PluginConfig` from a mapping"""
cls.validate(tuple(mapping.keys()))
c = cls()
c.__dict__.update(deepcopy(mapping))
return c
@staticmethod
def validate(config_keys: tuple[Any, ...]) -> None:
"""Validate a :class:`~eodag.config.PluginConfig`"""
# credentials may be set without type when the provider uses search_auth plugin.
if "type" not in config_keys and "credentials" not in config_keys:
raise ValidationError(
"A Plugin config must specify the type of Plugin it configures"
)
def update(self, config: Optional[Union[Self, dict[Any, Any]]]) -> None:
"""Update the configuration parameters with values from `mapping`
:param mapping: The mapping from which to override configuration parameters
"""
if config is None:
return
source = config if isinstance(config, dict) else config.__dict__
merge_mappings(
self.__dict__, {k: v for k, v in source.items() if v is not None}
)
[docs]
def matches_target_auth(self, target_config: Self):
"""Check if the target auth configuration matches this one"""
target_matching_conf = getattr(target_config, "matching_conf", {})
target_matching_url = getattr(target_config, "matching_url", None)
matching_conf = getattr(self, "matching_conf", {})
matching_url = getattr(self, "matching_url", None)
# both match
if (
target_matching_conf
and sort_dict(target_matching_conf) == sort_dict(matching_conf)
and target_matching_url
and target_matching_url == matching_url
):
return True
# conf matches and no matching_url expected
if (
target_matching_conf
and sort_dict(target_matching_conf) == sort_dict(matching_conf)
and not target_matching_url
):
return True
# url matches and no matching_conf expected
if (
target_matching_url
and target_matching_url == matching_url
and not target_matching_conf
):
return True
return False
def credentials_in_auth(auth_conf: PluginConfig) -> bool:
"""Checks if credentials are set for this Authentication plugin configuration
:param auth_conf: Authentication plugin configuration
:returns: True if credentials are set, else False
"""
return any(
c is not None for c in (getattr(auth_conf, "credentials", {}) or {}).values()
)
def load_default_config() -> dict[str, ProviderConfig]:
"""Load the providers configuration into a dictionary.
Load from eodag `resources/providers.yml` or `EODAG_PROVIDERS_CFG_FILE` environment
variable if exists.
:returns: The default provider's configuration
"""
eodag_providers_cfg_file = os.getenv("EODAG_PROVIDERS_CFG_FILE") or str(
res_files("eodag") / "resources" / "providers.yml"
)
return load_config(eodag_providers_cfg_file)
def load_config(config_path: str) -> dict[str, ProviderConfig]:
"""Load the providers configuration into a dictionary from a given file
If EODAG_PROVIDERS_WHITELIST is set, only load listed providers.
:param config_path: The path to the provider config file
:returns: The default provider's configuration
"""
logger.debug("Loading configuration from %s", config_path)
try:
# Providers configs are stored in this file as separated yaml documents
# Load all of it
providers_configs: list[ProviderConfig] = cached_yaml_load_all(config_path)
except yaml.parser.ParserError as e:
logger.error("Unable to load configuration")
raise e
return {p.name: p for p in providers_configs if p is not None}
def load_yml_config(yml_path: str) -> dict[Any, Any]:
"""Load a conf dictionary from given yml absolute path
:returns: The yml configuration file
"""
config = SimpleYamlProxyConfig(yml_path)
return dict_items_recursive_apply(config.source, string_to_jsonpath)
def load_stac_config() -> dict[str, Any]:
"""Load the stac configuration into a dictionary
:returns: The stac configuration
"""
return load_yml_config(str(res_files("eodag") / "resources" / "stac.yml"))
def load_stac_api_config() -> dict[str, Any]:
"""Load the stac API configuration into a dictionary
:returns: The stac API configuration
"""
return load_yml_config(str(res_files("eodag") / "resources" / "stac_api.yml"))
def load_stac_provider_config() -> dict[str, Any]:
"""Load the stac provider configuration into a dictionary
:returns: The stac provider configuration
"""
return SimpleYamlProxyConfig(
str(res_files("eodag") / "resources" / "stac_provider.yml")
).source
def get_ext_collections_conf(
conf_uri: str = EXT_COLLECTIONS_CONF_URI,
) -> dict[str, Any]:
"""Read external collections conf
:param conf_uri: URI to local or remote configuration file
:returns: The external collections configuration
"""
logger.info("Fetching external collections from %s", conf_uri)
if conf_uri.lower().startswith("http"):
# read from remote
try:
response = requests.get(
conf_uri, headers=USER_AGENT, timeout=HTTP_REQ_TIMEOUT
)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
logger.debug(e)
logger.warning(
"Could not read remote external collections conf from %s", conf_uri
)
return {}
elif conf_uri.lower().startswith("file"):
conf_uri = uri_to_path(conf_uri)
# read from local
try:
with open(conf_uri, "rb") as f:
return orjson.loads(f.read())
except (orjson.JSONDecodeError, FileNotFoundError) as e:
logger.debug(e)
logger.warning(
"Could not read local external collections conf from %s", conf_uri
)
return {}