From 7cc13294c2434a38986f6d7eb4eabc6ee675154d Mon Sep 17 00:00:00 2001 From: "luigi.brancati" Date: Sun, 10 Sep 2023 22:18:36 +0200 Subject: [PATCH 1/7] Added caching --- atmospheric_explorer/api/cache/__init__.py | 3 + atmospheric_explorer/api/cache/cache.py | 85 ++++++++++ .../data_interface/cams_interface/__init__.py | 4 + .../{ => cams_interface}/cams_interface.py | 39 ++--- .../cams_interface/cams_parameters.py | 44 +++++ .../api/data_interface/eac4/__init__.py | 1 + .../api/data_interface/eac4/eac4.py | 131 ++++++--------- .../data_interface/eac4/eac4_parameters.py | 98 +++++++++++ .../api/data_interface/ghg/__init__.py | 5 +- .../api/data_interface/ghg/ghg.py | 116 ++++++------- .../api/data_interface/ghg/ghg_parameters.py | 57 +++++++ .../api/shape_selection/shapefile.py | 155 +++++++----------- atmospheric_explorer/cli/os_manager.py | 2 +- tests/api/cache/__init__.py | 0 tests/api/cache/test_cached.py | 76 +++++++++ .../data_interface/cams_interface/__init__.py | 2 + .../test_cams_interfaces.py | 14 +- .../cams_interface/test_cams_parameters.py | 34 ++++ tests/api/data_interface/eac4/test_eac4.py | 93 ++++------- .../eac4/test_eac4_parameters.py | 88 ++++++++++ tests/api/data_interface/ghg/test_ghg.py | 106 +++++------- .../data_interface/ghg/test_ghg_parameters.py | 47 ++++++ tests/api/shape_selection/test_shapefile.py | 70 ++------ 23 files changed, 806 insertions(+), 464 deletions(-) create mode 100644 atmospheric_explorer/api/cache/__init__.py create mode 100644 atmospheric_explorer/api/cache/cache.py create mode 100644 atmospheric_explorer/api/data_interface/cams_interface/__init__.py rename atmospheric_explorer/api/data_interface/{ => cams_interface}/cams_interface.py (59%) create mode 100644 atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py create mode 100644 atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py create mode 100644 atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py create mode 100644 tests/api/cache/__init__.py create mode 100644 tests/api/cache/test_cached.py create mode 100644 tests/api/data_interface/cams_interface/__init__.py rename tests/api/data_interface/{ => cams_interface}/test_cams_interfaces.py (67%) create mode 100644 tests/api/data_interface/cams_interface/test_cams_parameters.py create mode 100644 tests/api/data_interface/eac4/test_eac4_parameters.py create mode 100644 tests/api/data_interface/ghg/test_ghg_parameters.py diff --git a/atmospheric_explorer/api/cache/__init__.py b/atmospheric_explorer/api/cache/__init__.py new file mode 100644 index 0000000..be2cae1 --- /dev/null +++ b/atmospheric_explorer/api/cache/__init__.py @@ -0,0 +1,3 @@ +# pylint: disable=missing-module-docstring +# ruff: noqa: F401 +from atmospheric_explorer.api.cache.cache import Cached, Parameters diff --git a/atmospheric_explorer/api/cache/cache.py b/atmospheric_explorer/api/cache/cache.py new file mode 100644 index 0000000..b2a6617 --- /dev/null +++ b/atmospheric_explorer/api/cache/cache.py @@ -0,0 +1,85 @@ +"""This module defines classes used for caching.""" +from __future__ import annotations + +from abc import ABC, abstractmethod +from functools import wraps +from textwrap import dedent + +from atmospheric_explorer.api.loggers import get_logger + +logger = get_logger("atmexp") + + +class Parameters(ABC): + @abstractmethod + def subset(self, parameters: Parameters) -> bool: + """Determine wether this parameters instance makes up a subset of parameters.""" + raise NotImplementedError( + "A parameters class needs to implement the subset method" + ) + + +class Cached: + """This class defines a few methods that allow to cache another class instances. + Caching is based on the class attributes. + + To cache a class, inherit from this class and define the subset method, + which is used inside find_cache to determine wether an instance is already included + or is equal to another instance. Last, one needs to decorate the new class __init__ with the + static method Cached.init_cache. + """ + + _cache: list[Cached] = [] + + @classmethod + def find_cache(cls: Cached, parameters: Parameters) -> Cached | None: + """Find obj in cache that has a superset of the parameters passed in kwargs.""" + for sd_obj in cls._cache: + if parameters.subset(sd_obj.parameters): + return sd_obj + return None + + def is_cached(self) -> bool: + """Check if self or a superset of it is already cached.""" + return self in type(self)._cache + + def cache(self) -> None: + """Cache self.""" + type(self)._cache.append(self) + + @classmethod + def clear_cache(cls): + """Clear cache.""" + cls._cache = [] + + def __new__(cls: Cached, parameters: Parameters): + logger.debug( + dedent( + """\ + Attempting to create Cached object with attributes + %s + """ + ), + parameters, + ) + cached_obj = cls.find_cache(parameters) + if cached_obj is not None: + return cached_obj + return super().__new__(cls) + + @staticmethod + def init_cache(func): + """Wrapper for the __init__ method of a cached class. + + Checks if self is already cached. If yes, returs None. + If not, runs the __init__ and then caches the initialized instance of the class. + """ + + @wraps(func) + def wrapper(self, *args, **kwargs): + if self.is_cached(): + return + func(self, *args, **kwargs) + self.cache() + + return wrapper diff --git a/atmospheric_explorer/api/data_interface/cams_interface/__init__.py b/atmospheric_explorer/api/data_interface/cams_interface/__init__.py new file mode 100644 index 0000000..2f3934e --- /dev/null +++ b/atmospheric_explorer/api/data_interface/cams_interface/__init__.py @@ -0,0 +1,4 @@ +# pylint: disable=missing-module-docstring +# ruff: noqa: F401 +from atmospheric_explorer.api.data_interface.cams_interface.cams_interface import CAMSDataInterface +from atmospheric_explorer.api.data_interface.cams_interface.cams_parameters import CAMSParameters diff --git a/atmospheric_explorer/api/data_interface/cams_interface.py b/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py similarity index 59% rename from atmospheric_explorer/api/data_interface/cams_interface.py rename to atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py index 8df1f80..fa05f1f 100644 --- a/atmospheric_explorer/api/data_interface/cams_interface.py +++ b/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py @@ -16,6 +16,7 @@ get_local_folder, remove_folder, ) +from atmospheric_explorer.api.data_interface.cams_interface.cams_parameters import CAMSParameters logger = get_logger("atmexp") @@ -31,46 +32,26 @@ class CAMSDataInterface(ABC): file_format = None file_ext = None - def __init__(self: CAMSDataInterface, data_variables: str | set[str] | list[str]): - """Initializes CAMSDataInterface instance. - - Attributes: - data_variables (str | list[str]): data variables to be downloaded from CAMS, depend on the dataset - """ + def __init__(self: CAMSDataInterface): + """Initializes CAMSDataInterface instance.""" self._id = next(self._ids) self._instances.append(self) - self.data_variables = data_variables create_folder(self.data_folder) logger.info("Created folder %s", self.data_folder) - @property - def data_variables(self: CAMSDataInterface) -> str | list[str]: - """Time values are internally represented as a set, use this property to set/get its value.""" - return ( - list(self._data_variables) - if isinstance(self._data_variables, set) - else self._data_variables - ) - - @data_variables.setter - def data_variables( - self: CAMSDataInterface, data_variables_input: str | set[str] | list[str] - ) -> None: - if isinstance(data_variables_input, list): - data_variables_input = set(data_variables_input) - self._data_variables = data_variables_input - - def _build_call_body(self: CAMSDataInterface) -> dict: + def build_call_body(self: CAMSDataInterface, parameters: CAMSParameters): """Builds the CDS API call body.""" - return {"format": self.file_format, "variable": self.data_variables} + call_body = parameters.build_call_body() + call_body['format'] = self.file_format + return call_body - def _download(self: CAMSDataInterface, file_fullpath: str) -> None: - """Downloads the dataset and saves it to file specified in filename. + def download(self: CAMSDataInterface, parameters: CAMSParameters, file_fullpath: str) -> None: + """Download the dataset and saves it to file specified in filename. Uses cdsapi to interact with CAMS ADS. """ client = cdsapi.Client() - body = self._build_call_body() + body = self.build_call_body(parameters) logger.debug("Calling cdsapi with body %s", body) client.retrieve(self.dataset_name, body, file_fullpath) logger.info("Finished downloading file %s", file_fullpath) diff --git a/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py b/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py new file mode 100644 index 0000000..d56b246 --- /dev/null +++ b/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py @@ -0,0 +1,44 @@ +"""\ +This module collects classes to easily interact with data downloaded from CAMS ADS. +""" +# pylint: disable=too-few-public-methods +# pylint: disable=too-many-arguments +from __future__ import annotations + +from atmospheric_explorer.api.cache import Parameters +from atmospheric_explorer.api.loggers import get_logger + +logger = get_logger("atmexp") + + +class CAMSParameters(Parameters): + def __init__(self, data_variables: str | set[str] | list[str]) -> None: + self.data_variables = data_variables + + @property + def data_variables(self: CAMSParameters) -> str | set[str]: + """Time values are internally represented as a set, use this property to set/get its value""" + return ( + self._data_variables + if len(self._data_variables) > 1 + else next(iter(self._data_variables)) + ) + + @data_variables.setter + def data_variables( + self: CAMSParameters, data_variables_input: str | set[str] | list[str] + ) -> None: + if isinstance(data_variables_input, str): + data_variables_input = [data_variables_input] + self._data_variables = set(data_variables_input) + + def subset(self: CAMSParameters, obj: CAMSParameters): + return self.data_variables.issubset(obj.data_variables) + + def build_call_body(self: CAMSParameters) -> dict: + """Build the CDSAPI call body""" + return { + "variable": list(self.data_variables) + if isinstance(self.data_variables, set) + else self.data_variables + } diff --git a/atmospheric_explorer/api/data_interface/eac4/__init__.py b/atmospheric_explorer/api/data_interface/eac4/__init__.py index ffd8691..8e4a091 100644 --- a/atmospheric_explorer/api/data_interface/eac4/__init__.py +++ b/atmospheric_explorer/api/data_interface/eac4/__init__.py @@ -4,3 +4,4 @@ # ruff: noqa: F401 from atmospheric_explorer.api.data_interface.eac4.eac4 import EAC4Instance from atmospheric_explorer.api.data_interface.eac4.eac4_config import EAC4Config +from atmospheric_explorer.api.data_interface.eac4.eac4_parameters import EAC4Parameters diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4.py b/atmospheric_explorer/api/data_interface/eac4/eac4.py index d93b7da..4cf0f8e 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4.py @@ -7,15 +7,17 @@ import xarray as xr +from atmospheric_explorer.api.cache import Cached from atmospheric_explorer.api.config import CRS from atmospheric_explorer.api.data_interface.cams_interface import CAMSDataInterface +from atmospheric_explorer.api.data_interface.eac4.eac4_parameters import EAC4Parameters from atmospheric_explorer.api.loggers import get_logger from atmospheric_explorer.api.os_manager import create_folder, remove_folder logger = get_logger("atmexp") -class EAC4Instance(CAMSDataInterface): +class EAC4Instance(CAMSDataInterface, Cached): # pylint: disable=line-too-long # pylint: disable=too-many-instance-attributes """Interface for CAMS global reanalysis (EAC4) and CAMS global reanalysis (EAC4) monthly averaged fields datasets. @@ -28,41 +30,64 @@ class EAC4Instance(CAMSDataInterface): file_format = "netcdf" file_ext = "nc" + def __new__( + cls: Cached, + data_variables: set[str] | list[str], + dates_range: str, + time_values: set[str] | list[str], + files_dir: str | None = None, + area: list[int] | None = None, + pressure_level: set[str] | list[str] | None = None, + model_level: set[str] | list[str] | None = None, + ): + params = EAC4Parameters( + data_variables=data_variables, + dates_range=dates_range, + time_values=time_values, + area=area, + pressure_level=pressure_level, + model_level=model_level, + ) + return Cached.__new__(EAC4Instance, params) + + @Cached.init_cache def __init__( self, - data_variables: str | set[str] | list[str], + data_variables: set[str] | list[str], dates_range: str, - time_values: str | set[str] | list[str], + time_values: set[str] | list[str], files_dir: str | None = None, area: list[int] | None = None, - pressure_level: str | set[str] | list[str] | None = None, - model_level: str | set[str] | list[str] | None = None, + pressure_level: set[str] | list[str] | None = None, + model_level: set[str] | list[str] | None = None, ): """Initializes EAC4Instance instance. Attributes: - data_variables (str | list[str]): data varaibles to be downloaded from CAMS, + data_variables (set[str] | list[str]): data varaibles to be downloaded from CAMS, see https://confluence.ecmwf.int/display/CKB/CAMS%3A+Reanalysis+data+documentation#heading-CAMSglobalreanalysisEAC4Parameterlistings - file_format (str): format for the downloaded data, can be either 'netcdf' or 'grib' dates_range (str): range of dates to consider, provided as a 'start/end' string with dates in ISO format - time_values (str | list[str]): time in 'HH:MM' format. One value or a list of values can be provided. + time_values (set[str] | list[str]): times in 'HH:MM' format. A set or a list of values can be provided. Accepted values are [00:00, 03:00, 06:00, 09:00, 12:00, 15:00, 18:00, 21:00] - filename (str | None): file where to save the data. If not provided will be built using file_format and - with a dinamically generated name + files_dir (str | None): directory where to save the data. If not provided will be built using + a dinamically generated name area (list[int]): latitude-longitude area box to be considered, provided as a list of four values [NORTH, WEST, SOUTH, EAST]. If not provided, full area will be considered - pressure_level (str | list[str] | None): pressure levels to be considered for multilevel variables. - Can be a single level or a list of levels, see documentation linked above for all possible values. - model_level (str | list[str] | None): model levels to be considered for multilevel variables. - Can be a single level or a list of levels, chosen in a range from 1 to 60. + pressure_level (set[str] | list[str] | None): pressure levels to be considered for multilevel variables. + Can be a set or a list of levels, see documentation linked above for all possible values. + model_level (set[str] | list[str] | None): model levels to be considered for multilevel variables. + Can be a set or a list of levels, chosen in a range from 1 to 60. See documentation linked above for all possible values. """ - super().__init__(data_variables) - self.dates_range = dates_range - self.time_values = time_values - self.area = area - self.pressure_level = pressure_level - self.model_level = model_level + super().__init__() + self.parameters = EAC4Parameters( + data_variables=data_variables, + dates_range=dates_range, + time_values=time_values, + area=area, + pressure_level=pressure_level, + model_level=model_level, + ) self.files_dirname = files_dir if files_dir is not None else f"data_{self._id}" self.files_dir_path = os.path.join(self.dataset_dir, self.files_dirname) if os.path.exists(self.dataset_dir): @@ -77,76 +102,12 @@ def file_full_path(self: EAC4Instance) -> str: self.files_dir_path, f"{self.files_dirname}.{self.file_ext}" ) - @property - def time_values(self: EAC4Instance) -> str | list[str]: - """Time values are internally represented as a set, use this property to set/get its value.""" - return ( - list(self._time_values) - if isinstance(self._time_values, set) - else self._time_values - ) - - @time_values.setter - def time_values( - self: EAC4Instance, time_values: str | set[str] | list[str] - ) -> None: - if isinstance(time_values, list): - time_values = set(time_values) - self._time_values = time_values - - @property - def pressure_level(self: EAC4Instance) -> str | list[str] | None: - """Pressure level is internally represented as a set, use this property to set/get its value.""" - return ( - list(self._pressure_level) - if isinstance(self._pressure_level, set) - else self._pressure_level - ) - - @pressure_level.setter - def pressure_level( - self: EAC4Instance, pressure_level: str | set[str] | list[str] | None - ) -> None: - if isinstance(pressure_level, list): - pressure_level = set(pressure_level) - self._pressure_level = pressure_level - - @property - def model_level(self: EAC4Instance) -> str | list[str] | None: - """Model level is internally represented as a set, use this property to set/get its value.""" - return ( - list(self._model_level) - if isinstance(self._model_level, set) - else self._model_level - ) - - @model_level.setter - def model_level( - self: EAC4Instance, model_level: str | set[str] | list[str] | None - ) -> None: - if isinstance(model_level, list): - model_level = set(model_level) - self._model_level = model_level - - def _build_call_body(self: EAC4Instance) -> dict: - """Builds the CDS API call body.""" - call_body = super()._build_call_body() - call_body["date"] = self.dates_range - call_body["time"] = self.time_values - if self.area is not None: - call_body["area"] = self.area - if self.pressure_level is not None: - call_body["pressure_level"] = self.pressure_level - if self.model_level is not None: - call_body["model_level"] = self.model_level - return call_body - def download(self: EAC4Instance) -> None: """Downloads the dataset and saves it to file specified in filename. Uses cdsapi to interact with CAMS ADS. """ - return super()._download(self.file_full_path) + return super().download(self.parameters, self.file_full_path) def _simplify_dataset(self: EAC4Instance, dataset: xr.Dataset): return dataset.rio.write_crs(CRS) diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py b/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py new file mode 100644 index 0000000..edecef1 --- /dev/null +++ b/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py @@ -0,0 +1,98 @@ +"""\ +This module collects classes to easily interact with data downloaded from CAMS ADS. +""" +# pylint: disable=too-few-public-methods +# pylint: disable=too-many-arguments +from __future__ import annotations + +from atmospheric_explorer.api.data_interface.cams_interface import CAMSParameters +from atmospheric_explorer.api.loggers import get_logger + +logger = get_logger("atmexp") + + +class EAC4Parameters(CAMSParameters): + def __init__( + self: EAC4Parameters, + data_variables: set[str] | list[str], + dates_range: str, + time_values: set[str] | list[str], + area: list[int] | None = None, + pressure_level: set[str] | list[str] | None = None, + model_level: set[str] | list[str] | None = None, + ) -> None: + super().__init__(data_variables) + self.dates_range = dates_range + self.time_values = set(time_values) + self.area = area + self.pressure_level = ( + set(pressure_level) if pressure_level is not None else pressure_level + ) + self.model_level = set(model_level) if model_level is not None else model_level + + @staticmethod + def dates_issubset(date_range1, date_range2): + """Check if the first date range is a subset of the second date range.""" + start1, end1 = date_range1.strip().split("/") + start2, end2 = date_range2.strip().split("/") + return start1 >= start2 and end1 <= end2 + + @staticmethod + def _is_subset_none(arg1, arg2) -> bool | None: + """Returns wether either one or both arguments are None. If no argument is None, returns None.""" + if (arg1 or arg2) is None: + return arg1 is None and arg2 is None + return None + + @staticmethod + def area_issubset(area1: list, area2: list): + """Check if the first area is a subset of the second area.""" + res = EAC4Parameters._is_subset_none(area1, area2) + if res is not None: + return res + north1, west1, south1, east1 = area1 + north2, west2, south2, east2 = area2 + return ( + north1 <= north2 and east1 >= east2 and west1 <= west2 and south1 >= south2 + ) + + @staticmethod + def pressure_issubset(pl1: set | None, pl2: set | None): + """Check if the first pressure level is a subset of the second pressure level.""" + res = EAC4Parameters._is_subset_none(pl1, pl2) + if res is not None: + return res + return pl1.issubset(pl2) + + @staticmethod + def model_issubset(ml1: set | None, ml2: set | None): + """Check if the first model level is a subset of the second model level.""" + res = EAC4Parameters._is_subset_none(ml1, ml2) + if res is not None: + return res + return ml1.issubset(ml2) + + def subset(self: EAC4Parameters, obj: EAC4Parameters): + return ( + self.data_variables.issubset(obj.data_variables) + and EAC4Parameters.dates_issubset(self.dates_range, obj.dates_range) + and self.time_values.issubset(obj.time_values) + and EAC4Parameters.area_issubset(self.area, obj.area) + and EAC4Parameters.pressure_issubset( + self.pressure_level, obj.pressure_level + ) + and EAC4Parameters.model_issubset(self.model_level, obj.model_level) + ) + + def build_call_body(self: EAC4Parameters) -> dict: + """Build the CDSAPI call body""" + call_body = super().build_call_body() + call_body["date"] = self.dates_range + call_body["time"] = list(self.time_values) + if self.area is not None: + call_body["area"] = self.area + if self.pressure_level is not None: + call_body["pressure_level"] = list(self.pressure_level) + if self.model_level is not None: + call_body["model_level"] = list(self.model_level) + return call_body diff --git a/atmospheric_explorer/api/data_interface/ghg/__init__.py b/atmospheric_explorer/api/data_interface/ghg/__init__.py index 7379137..1906bd8 100644 --- a/atmospheric_explorer/api/data_interface/ghg/__init__.py +++ b/atmospheric_explorer/api/data_interface/ghg/__init__.py @@ -2,7 +2,6 @@ # pylint: disable=missing-module-docstring # ruff: noqa: F401 -from atmospheric_explorer.api.data_interface.ghg.ghg import ( - InversionOptimisedGreenhouseGas, -) +from atmospheric_explorer.api.data_interface.ghg.ghg import InversionOptimisedGreenhouseGas from atmospheric_explorer.api.data_interface.ghg.ghg_config import GHGConfig +from atmospheric_explorer.api.data_interface.ghg.ghg_parameters import GHGParameters diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg.py b/atmospheric_explorer/api/data_interface/ghg/ghg.py index 8564efb..ef75823 100644 --- a/atmospheric_explorer/api/data_interface/ghg/ghg.py +++ b/atmospheric_explorer/api/data_interface/ghg/ghg.py @@ -1,4 +1,4 @@ -"""This module collects classes to easily interact with GHG data downloaded from CAMS ADS.""" +"""This module collects classes to easily interact with Greenhouse gasses data downloaded from CAMS ADS.""" # pylint: disable=too-few-public-methods # pylint: disable=too-many-arguments from __future__ import annotations @@ -11,15 +11,19 @@ import numpy as np import xarray as xr +from atmospheric_explorer.api.cache import Cached from atmospheric_explorer.api.config import CRS -from atmospheric_explorer.api.data_interface.cams_interface import CAMSDataInterface +from atmospheric_explorer.api.data_interface.cams_interface.cams_interface import ( + CAMSDataInterface, +) +from atmospheric_explorer.api.data_interface.ghg.ghg_parameters import GHGParameters from atmospheric_explorer.api.loggers import get_logger from atmospheric_explorer.api.os_manager import create_folder, remove_folder logger = get_logger("atmexp") -class InversionOptimisedGreenhouseGas(CAMSDataInterface): +class InversionOptimisedGreenhouseGas(CAMSDataInterface, Cached): # pylint: disable=line-too-long # pylint: disable=too-many-instance-attributes """Interface for CAMS global inversion-optimised greenhouse gas fluxes and concentrations dataset. @@ -33,39 +37,64 @@ class InversionOptimisedGreenhouseGas(CAMSDataInterface): file_format = "zip" file_ext = "zip" + def __new__( + cls: Cached, + data_variables: str, + quantity: str, + input_observations: str, + time_aggregation: str, + year: set[str] | list[str], + month: set[str] | list[str], + files_dir: str | None = None, + version: str = "latest", + ): + params = GHGParameters( + data_variables=data_variables, + quantity=quantity, + input_observations=input_observations, + time_aggregation=time_aggregation, + year=year, + month=month, + version=version, + ) + return Cached.__new__(InversionOptimisedGreenhouseGas, params) + + @Cached.init_cache def __init__( self: InversionOptimisedGreenhouseGas, data_variables: str, quantity: str, input_observations: str, time_aggregation: str, - year: str | set[str] | list[str], - month: str | set[str] | list[str], + year: set[str] | list[str], + month: set[str] | list[str], files_dir: str | None = None, version: str = "latest", ): """Initializes InversionOptimisedGreenhouseGas instance. Attributes: - data_variables (str | list[str]): data varaibles to be downloaded from CAMS, + data_variables (str): data varaibles to be downloaded from CAMS, see https://ads.atmosphere.copernicus.eu/cdsapp#!/dataset/cams-global-greenhouse-gas-inversion?tab=overview - file_format (str): format for the downloaded data, can be either 'zip' or 'tar.gz' quantity (str): quantity, can be one of ['mean_column', 'surface_flux', 'concentration'] input_observations (str): input observations, can be one of ['surface', 'satellite', 'surface_satellite'] time_aggregation (str): time aggregation, can be one of ['instantaneous', 'daily_mean', 'monthly_mean'] - year (str | list[str]): single year or list of years, in 'YYYY' format - month (str | list[str]): single month or list of months, in 'MM' format - filename (str | None): file where to save the data. If not provided will be built using file_format and - with a dinamically generated name + year (set[str] | list[str]): set or list of years, in 'YYYY' format + month (set[str] | list[str]): set or list of months, in 'MM' format + files_dir (str | None): directory where to save the data. If not provided will be built using + a dinamically generated name version (str): version of the dataset, default is 'latest' """ - super().__init__(data_variables) - self.quantity = quantity - self.input_observations = input_observations - self.time_aggregation = time_aggregation - self.year = year - self.month = month - self.version = version + super().__init__() + self.parameters = GHGParameters( + data_variables=data_variables, + quantity=quantity, + input_observations=input_observations, + time_aggregation=time_aggregation, + year=year, + month=month, + version=version, + ) self.files_dirname = files_dir if files_dir is not None else f"data_{self._id}" self.files_dir_path = os.path.join(self.dataset_dir, self.files_dirname) if os.path.exists(self.dataset_dir): @@ -86,54 +115,13 @@ def file_full_path(self: InversionOptimisedGreenhouseGas, filename: str) -> None self.files_dir_path, f"{filename}.{self.file_ext}" ) - @property - def year(self: InversionOptimisedGreenhouseGas) -> str | list[str]: - """Year is internally represented as a set, use this property to set/get its value.""" - return list(self._year) if isinstance(self._year, set) else self._year - - @year.setter - def year( - self: InversionOptimisedGreenhouseGas, year: str | set[str] | list[str] - ) -> None: - if isinstance(year, list): - year = set(year) - self._year = year - - @property - def month(self: InversionOptimisedGreenhouseGas) -> str | list[str]: - """Month is internally represented as a set, use this property to set/get its value.""" - return list(self._month) if isinstance(self._month, set) else self._month - - @month.setter - def month( - self: InversionOptimisedGreenhouseGas, month: str | set[str] | list[str] - ) -> None: - if isinstance(month, list): - month = set(month) - self._month = month - - def _build_call_body(self: InversionOptimisedGreenhouseGas) -> dict: - """Builds the CDS API call body.""" - call_body = super()._build_call_body() - call_body.update( - { - "version": self.version, - "quantity": self.quantity, - "input_observations": self.input_observations, - "time_aggregation": self.time_aggregation, - "year": self.year, - "month": self.month, - } - ) - return call_body - def download(self: InversionOptimisedGreenhouseGas) -> None: """Downloads the dataset and saves it to file specified in filename. Uses cdsapi to interact with CAMS ADS. This function also extracts the netcdf file inside the zip file, which is then deleted. """ - super()._download(self.file_full_path) + super().download(self.parameters, self.file_full_path) # This dataset downloads zipfiles with possibly multiple netcdf files inside # We must extract it zip_filename = self.file_full_path @@ -159,7 +147,7 @@ def _align_dims(dataset: xr.Dataset, dim: str, values: list) -> xr.Dataset: return dataset def _simplify_dataset(self: InversionOptimisedGreenhouseGas, dataset: xr.Dataset): - if self.data_variables == "methane": + if self.parameters.data_variables == "methane": dataset = dataset.drop( ["longitude_bounds", "latitude_bounds", "time_bounds"] ) @@ -167,12 +155,12 @@ def _simplify_dataset(self: InversionOptimisedGreenhouseGas, dataset: xr.Dataset dataset = InversionOptimisedGreenhouseGas._align_dims( dataset, "time_aggregation", - np.array([self.time_aggregation], dtype="object"), + np.array([self.parameters.time_aggregation], dtype="object"), ) dataset = InversionOptimisedGreenhouseGas._align_dims( dataset, "input_observations", - np.array([self.input_observations], dtype="object"), + np.array([self.parameters.input_observations], dtype="object"), ) return dataset.rio.write_crs(CRS) @@ -193,8 +181,6 @@ def read_dataset( dataset = self._align_dims(dataset, "time", [date_index]) for file in files[1:]: # Merge remaining files - # ! This loop replaces xr.open_mfdataset(surface_data.file_full_path) that does not work - # (because time coordinate is not included in dataframe) temp = xr.open_dataset(file) date_index = datetime.strptime(file.split("_")[-1].split(".")[0], "%Y%m") temp = self._align_dims(temp, "time", [date_index]) diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py b/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py new file mode 100644 index 0000000..3e015e8 --- /dev/null +++ b/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py @@ -0,0 +1,57 @@ +"""\ +This module collects classes to easily interact with data downloaded from CAMS ADS. +""" +# pylint: disable=too-few-public-methods +# pylint: disable=too-many-arguments +from __future__ import annotations + +from atmospheric_explorer.api.data_interface.cams_interface import CAMSParameters +from atmospheric_explorer.api.loggers import get_logger + +logger = get_logger("atmexp") + + +class GHGParameters(CAMSParameters): + def __init__( + self: GHGParameters, + data_variables: str, + quantity: str, + input_observations: str, + time_aggregation: str, + year: set[str] | list[str], + month: set[str] | list[str], + version: str = "latest", + ): + super().__init__(data_variables) + self.quantity = quantity + self.input_observations = input_observations + self.time_aggregation = time_aggregation + self.year = set(year) + self.month = set(month) + self.version = version + + def subset(self: GHGParameters, obj: GHGParameters): + return ( + self.data_variables == obj.data_variables + and self.quantity == obj.quantity + and self.input_observations == obj.input_observations + and self.time_aggregation == obj.time_aggregation + and self.year.issubset(obj.year) + and self.month.issubset(obj.month) + and self.version == obj.version + ) + + def build_call_body(self: GHGParameters) -> dict: + """Build the CDSAPI call body""" + call_body = super().build_call_body() + call_body.update( + { + "version": self.version, + "quantity": self.quantity, + "input_observations": self.input_observations, + "time_aggregation": self.time_aggregation, + "year": self.year, + "month": self.month, + } + ) + return call_body diff --git a/atmospheric_explorer/api/shape_selection/shapefile.py b/atmospheric_explorer/api/shape_selection/shapefile.py index 974f82b..c98f4d6 100644 --- a/atmospheric_explorer/api/shape_selection/shapefile.py +++ b/atmospheric_explorer/api/shape_selection/shapefile.py @@ -13,6 +13,7 @@ import requests import requests.utils +from atmospheric_explorer.api.cache import Cached, Parameters from atmospheric_explorer.api.loggers import get_logger from atmospheric_explorer.api.os_manager import create_folder, get_local_folder from atmospheric_explorer.api.shape_selection.config import map_level_shapefile_mapping @@ -20,9 +21,41 @@ logger = get_logger("atmexp") -class ShapefilesDownloader: - """This class manages the download, extraction and saving on disk of \ - shapefiles. +class ShapefileParameters(Parameters): + def __init__( + self: ShapefileParameters, + resolution: str = "50m", + map_type: str = "cultural", + info_type: str = "admin", + depth: int = 0, + instance: str = "map_subunits" + ): # pylint: disable=too-many-arguments + self.resolution = resolution + self.map_type = map_type + self.info_type = info_type + self.depth = depth + self.instance = instance + + def subset( + self: ShapefileParameters, + obj: ShapefileParameters + ) -> bool: + # pylint: disable=too-many-arguments + return ( + self.resolution == obj.resolution + and self.map_type == obj.map_type + and self.info_type == obj.info_type + and self.depth == obj.depth + and self.instance == obj.instance + ) + + +class ShapefilesDownloader(Cached): + # pylint: disable=too-many-instance-attributes + """This class manages the download, extraction and saving on disk of shapefiles. + + Shapefiles are downloaded from Natural Earth Data as zip files and then extracted into a folder. + By default, the class download a 50m resolution "admin" shapefile for all countries in the world. Shapefiles will be downloaded as zip files and then extracted into a folder. Shapefiles are downloaded from Natural Earth Data. @@ -42,96 +75,27 @@ class ShapefilesDownloader: } ) _ROOT_DIR: str = "shapefiles" - _cache: list[ShapefilesDownloader] = [] - - @classmethod - def find_cache( - cls: ShapefilesDownloader, - resolution: str = "50m", - map_type: str = "cultural", - info_type: str = "admin", - depth: int = 0, - instance: str = "map_subunits", - ) -> ShapefilesDownloader | None: - # pylint: disable=too-many-arguments - """Finds shapefile object in cache.""" - for sd_obj in cls._cache: - if ( - sd_obj.resolution == resolution - and sd_obj.map_type == map_type - and sd_obj.info_type == info_type - and sd_obj.depth == depth - and sd_obj.instance == instance - ): - logger.debug("Found cached shapefile object") - return sd_obj - return None - - def __repr__(self): - """Printable representation of ShapefilesDownloader instance.""" - return dedent( - "ShapefilesDownloader object with attributes \ - resolution: {resolution} \ - map_type: {map_type} \ - info_type: %s \ - depth: {info_type} \ - instance: {instance}" - ) - - @classmethod - def is_cached(cls, obj: ShapefilesDownloader) -> bool: - """Checks if ShapefilesDownloader object is cached.""" - return obj in cls._cache - - @classmethod - def cache(cls, obj: ShapefilesDownloader): - """Adds ShapefilesDownloader object to cache.""" - logger.debug("Cached shapefile") - ShapefilesDownloader._cache.append(obj) - - @classmethod - def clear_cache(cls): - """Clears cache.""" - cls._cache = [] def __new__( - cls: ShapefilesDownloader, + cls: Cached, resolution: str = "50m", map_type: str = "cultural", info_type: str = "admin", depth: int = 0, instance: str = "map_subunits", - **kwargs, - ): # pylint: disable=too-many-arguments - """Returns new ShapefilesDownloader instance.""" - logger.debug( - dedent( - """\ - Attempting to create ShapefilesDownloader object with attributes - resolution: %s - map_type: %s - info_type: %s - depth: %s - instance: %s - """ - ), - resolution, - map_type, - info_type, - depth, - instance, - ) - cached_obj = ShapefilesDownloader.find_cache( + timeout: int = 10, + dst_dir: str | None = None, + ): + params = ShapefileParameters( resolution=resolution, map_type=map_type, info_type=info_type, depth=depth, - instance=instance, + instance=instance ) - if cached_obj is not None: - return cached_obj - return super(ShapefilesDownloader, cls).__new__(cls) + return Cached.__new__(ShapefilesDownloader, params) + @Cached.init_cache def __init__( self: ShapefilesDownloader, resolution: str = "50m", @@ -157,13 +121,13 @@ def __init__( See some more details here: https://www.naturalearthdata.com/downloads/ """ - if ShapefilesDownloader.is_cached(self): - return - self.resolution = resolution - self.map_type = map_type - self.info_type = info_type - self.depth = depth - self.instance = instance + self.parameters = ShapefileParameters( + resolution=resolution, + map_type=map_type, + info_type=info_type, + depth=depth, + instance=instance + ) self.timeout = timeout self.dst_dir = ( dst_dir @@ -173,7 +137,6 @@ def __init__( self._downloaded = False create_folder(self.dst_dir) logger.info("Created folder %s to save shapefiles", self.dst_dir) - ShapefilesDownloader.cache(self) logger.debug( dedent( """\ @@ -198,10 +161,10 @@ def __init__( @property def shapefile_name(self: ShapefilesDownloader) -> str: - """Shapefile file name.""" - if self.map_type == "physical": - return f"ne_{self.resolution}_{self.info_type}" - return f"ne_{self.resolution}_{self.info_type}_{self.depth}_{self.instance}" + """Shapefile name""" + if self.parameters.map_type == "physical": + return f"ne_{self.parameters.resolution}_{self.parameters.info_type}" + return f"ne_{self.parameters.resolution}_{self.parameters.info_type}_{self.parameters.depth}_{self.parameters.instance}" @property def shapefile_dir(self: ShapefilesDownloader) -> str: @@ -217,7 +180,7 @@ def shapefile_full_path(self: ShapefilesDownloader) -> str: def shapefile_url(self: ShapefilesDownloader) -> str: """Shapefile download url.""" # pylint: disable=line-too-long - return f"{self._BASE_URL}/{self.resolution}/{self.map_type}/{self.shapefile_name}.zip" # noqa: E501 + return f"{self._BASE_URL}/{self.parameters.resolution}/{self.parameters.map_type}/{self.shapefile_name}.zip" # noqa: E501 def _download_shapefile(self: ShapefilesDownloader) -> bytes: """Downloads shapefiles and returns their content in bytes.""" @@ -298,3 +261,9 @@ def dissolve_shapefile_level(level: str) -> gpd.GeoDataFrame: sh_df = ShapefilesDownloader(instance="map_subunits").get_as_dataframe() sh_df = sh_df[[col, "geometry"]].rename({col: "label"}, axis=1) return sh_df.dissolve(by="label").reset_index() + + +if __name__ == "__main__": + sh = ShapefilesDownloader() + sh2 = ShapefilesDownloader() + print(id(sh) == id(sh2)) diff --git a/atmospheric_explorer/cli/os_manager.py b/atmospheric_explorer/cli/os_manager.py index e319c5a..1e0c770 100644 --- a/atmospheric_explorer/cli/os_manager.py +++ b/atmospheric_explorer/cli/os_manager.py @@ -5,7 +5,7 @@ import click -from atmospheric_explorer.api.data_interface import CAMSDataInterface +from atmospheric_explorer.api.data_interface.cams_interface import CAMSDataInterface @click.group() diff --git a/tests/api/cache/__init__.py b/tests/api/cache/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/api/cache/test_cached.py b/tests/api/cache/test_cached.py new file mode 100644 index 0000000..991c027 --- /dev/null +++ b/tests/api/cache/test_cached.py @@ -0,0 +1,76 @@ +# pylint: disable=missing-module-docstring +# pylint: disable=missing-class-docstring +# pylint: disable=missing-function-docstring +# pylint: disable=protected-access +# pylint: disable=unused-argument +from __future__ import annotations + +import pytest + +from atmospheric_explorer.api.cache.cache import Cached, Parameters + + +class ParametersClass(Parameters): + def __init__(self: ParametersClass, arg): + self.arg = arg + + def subset(self: ParametersClass, other: ParametersClass) -> bool: + return self.arg == other.arg + + +class CachedClass(Cached): + + def __new__(cls, arg): + par = ParametersClass(arg) + return Cached.__new__(CachedClass, par) + + @Cached.init_cache + def __init__(self, arg): + self.parameters = ParametersClass(arg) + + +@pytest.fixture(autouse=True) +def clear_cache(): + CachedClass.clear_cache() + yield + CachedClass.clear_cache() + + +def test_cache(): + assert not CachedClass._cache + sh1 = CachedClass.__new__(CachedClass, arg="1") + sh2 = CachedClass.__new__(CachedClass, arg="2") + sh1.cache() + sh2.cache() + assert len(CachedClass._cache) == 2 + assert CachedClass._cache[0] is sh1 + assert CachedClass._cache[1] is sh2 + + +def test_find_cache(): + sh1 = CachedClass(arg="1") + assert CachedClass.find_cache(ParametersClass(arg="1")) is sh1 + assert CachedClass.find_cache(ParametersClass(arg="2")) is None + + +def test_is_cached(): + sh1 = CachedClass(arg="1") + assert sh1.is_cached() + sh2 = CachedClass.__new__(CachedClass, arg="2") + assert not sh2.is_cached() + + +def test_clear_cache(): + CachedClass(arg="1") + assert CachedClass._cache + CachedClass.clear_cache() + assert not CachedClass._cache + + +def test_obj_creation(): + sh1 = CachedClass(arg="1") + sh2 = CachedClass(arg="1") + assert id(sh1) == id(sh2) + assert sh1 in CachedClass._cache + sh3 = CachedClass(arg="3") + assert id(sh3) != id(sh1) diff --git a/tests/api/data_interface/cams_interface/__init__.py b/tests/api/data_interface/cams_interface/__init__.py new file mode 100644 index 0000000..c5bfe0c --- /dev/null +++ b/tests/api/data_interface/cams_interface/__init__.py @@ -0,0 +1,2 @@ +# pylint: disable=missing-module-docstring +# pylint: disable=missing-function-docstring diff --git a/tests/api/data_interface/test_cams_interfaces.py b/tests/api/data_interface/cams_interface/test_cams_interfaces.py similarity index 67% rename from tests/api/data_interface/test_cams_interfaces.py rename to tests/api/data_interface/cams_interface/test_cams_interfaces.py index ed19dc2..f2086e2 100644 --- a/tests/api/data_interface/test_cams_interfaces.py +++ b/tests/api/data_interface/cams_interface/test_cams_interfaces.py @@ -5,6 +5,7 @@ # pylint: disable=unused-argument from atmospheric_explorer.api.data_interface.cams_interface import CAMSDataInterface +from atmospheric_explorer.api.data_interface.cams_interface import CAMSParameters class CAMSDataInterfaceTesting(CAMSDataInterface): @@ -15,22 +16,15 @@ def read_dataset(self: CAMSDataInterface): def test__init(): - obj = CAMSDataInterfaceTesting({"a", "b", "c"}) - assert obj._data_variables == {"a", "b", "c"} + obj = CAMSDataInterfaceTesting() assert obj.file_format is None assert obj.file_ext is None assert obj.dataset_name is None assert obj._id == 0 -def test_data_variables(): - obj = CAMSDataInterfaceTesting(["a", "b", "d", "d"]) - assert obj._data_variables == {"a", "b", "d"} - assert sorted(obj.data_variables) == ["a", "b", "d"] - - def test__build_call_body(): - obj = CAMSDataInterfaceTesting({"a", "b", "c"}) - res = obj._build_call_body() + obj = CAMSDataInterfaceTesting() + res = obj.build_call_body(CAMSParameters({"a", "b", "c"})) res["variable"] = sorted(res["variable"]) assert res == {"format": None, "variable": sorted(["a", "b", "c"])} diff --git a/tests/api/data_interface/cams_interface/test_cams_parameters.py b/tests/api/data_interface/cams_interface/test_cams_parameters.py new file mode 100644 index 0000000..dc4821f --- /dev/null +++ b/tests/api/data_interface/cams_interface/test_cams_parameters.py @@ -0,0 +1,34 @@ +# pylint: disable=missing-module-docstring +# pylint: disable=missing-class-docstring +# pylint: disable=missing-function-docstring +# pylint: disable=protected-access +# pylint: disable=unused-argument + +from atmospheric_explorer.api.data_interface.cams_interface import CAMSParameters + + +def test__init(): + obj = CAMSParameters({"a", "b", "c"}) + assert obj._data_variables == {"a", "b", "c"} + assert obj.data_variables == {"a", "b", "c"} + + +def test_subset(): + obj = CAMSParameters(["a", "b", "d", "d"]) + obj2 = CAMSParameters(["a", "b", "d", "e"]) + assert obj.subset(obj2) + assert not obj2.subset(obj) + + +def test_data_variable(): + obj = CAMSParameters(["a", "b", "d", "d"]) + assert obj.data_variables == {"a", "b", "d"} + obj = CAMSParameters("a") + assert obj.data_variables == "a" + + +def test_build_call_body(): + obj = CAMSParameters({"a", "b", "c"}) + res = obj.build_call_body() + res["variable"] = sorted(res["variable"]) + assert res == {"variable": sorted(["a", "b", "c"])} diff --git a/tests/api/data_interface/eac4/test_eac4.py b/tests/api/data_interface/eac4/test_eac4.py index cdb0ef4..cfd1544 100644 --- a/tests/api/data_interface/eac4/test_eac4.py +++ b/tests/api/data_interface/eac4/test_eac4.py @@ -4,83 +4,50 @@ # pylint: disable=protected-access # pylint: disable=unused-argument +import pytest + from atmospheric_explorer.api.data_interface.eac4 import EAC4Instance +from atmospheric_explorer.api.data_interface.eac4 import EAC4Parameters + + +@pytest.fixture(autouse=True) +def clear_cache(): + EAC4Instance.clear_cache() + yield + EAC4Instance.clear_cache() def test__init(): obj = EAC4Instance( - {"a", "b", "c"}, - "2021-01-01/2022-01-01", - "00:00", + data_variables={"a", "b", "c"}, + dates_range="2021-01-01/2022-01-01", + time_values=["00:00"], area=[0, 0, 0, 0], pressure_level={"1", "2"}, model_level={"1", "2"}, files_dir="test", ) - assert obj._data_variables == {"a", "b", "c"} + assert isinstance(obj.parameters, EAC4Parameters) assert obj.file_format == "netcdf" - assert obj.dates_range == "2021-01-01/2022-01-01" - assert obj.time_values == "00:00" - assert obj.area == [0, 0, 0, 0] - assert obj._pressure_level == {"1", "2"} - assert obj._model_level == {"1", "2"} assert obj.files_dirname == "test" -def test_time_values(): - obj = EAC4Instance( - {"a", "b", "c"}, - "2021-01-01/2022-01-01", - ["00:00", "00:00", "03:00"], +def test_obj_creation(): + sh1 = EAC4Instance( + data_variables=["a", "b"], + dates_range="2021-01-01/2020-04-05", + time_values=["00:00"], ) - assert obj._time_values == {"00:00", "03:00"} - assert isinstance(obj.time_values, list) - assert sorted(obj.time_values) == ["00:00", "03:00"] - - -def test_pressure_level(): - obj = EAC4Instance( - {"a", "b", "c"}, - "2021-01-01/2022-01-01", - "00:00", - pressure_level=["1", "2", "2", "3"], + sh2 = EAC4Instance( + data_variables=["a", "b"], + dates_range="2021-02-01/2020-03-05", + time_values=["00:00"], ) - assert obj._pressure_level == {"1", "2", "3"} - assert isinstance(obj.pressure_level, list) - assert sorted(obj.pressure_level) == ["1", "2", "3"] - - -def test_model_level(): - obj = EAC4Instance( - {"a", "b", "c"}, - "2021-01-01/2022-01-01", - "00:00", - model_level=["1", "2", "2", "3"], - ) - assert obj._model_level == {"1", "2", "3"} - assert isinstance(obj.model_level, list) - assert sorted(obj.model_level) == ["1", "2", "3"] - - -def test__build_call_body(): - obj = EAC4Instance( - {"a", "b", "c"}, - "2021-01-01/2022-01-01", - "00:00", - area=[0, 0, 0, 0], - pressure_level={"1", "2"}, - model_level={"1", "2"}, + assert id(sh1) == id(sh2) + assert sh1 in EAC4Instance._cache + sh3 = EAC4Instance( + data_variables=["d", "c"], + dates_range="2021-01-01/2020-04-05", + time_values=["00:00"], ) - res = obj._build_call_body() - res["variable"] = sorted(res["variable"]) - res["pressure_level"] = sorted(res["pressure_level"]) - res["model_level"] = sorted(res["model_level"]) - assert res == { - "format": "netcdf", - "variable": sorted(["a", "b", "c"]), - "date": "2021-01-01/2022-01-01", - "time": "00:00", - "area": [0, 0, 0, 0], - "pressure_level": sorted(["1", "2"]), - "model_level": sorted(["1", "2"]), - } + assert id(sh3) != id(sh1) diff --git a/tests/api/data_interface/eac4/test_eac4_parameters.py b/tests/api/data_interface/eac4/test_eac4_parameters.py new file mode 100644 index 0000000..3310d19 --- /dev/null +++ b/tests/api/data_interface/eac4/test_eac4_parameters.py @@ -0,0 +1,88 @@ +# pylint: disable=missing-module-docstring +# pylint: disable=missing-class-docstring +# pylint: disable=missing-function-docstring +# pylint: disable=protected-access +# pylint: disable=unused-argument + +from atmospheric_explorer.api.data_interface.eac4 import EAC4Parameters + + +def test_is_subset_none(): + assert not EAC4Parameters._is_subset_none(None, "test") + assert not EAC4Parameters._is_subset_none("test", None) + assert EAC4Parameters._is_subset_none(None, None) + assert EAC4Parameters._is_subset_none("test", "test") is None + + +def test_dates_subset(): + dsfirst = "2021-01-01/2021-04-01" + dssecond = "2022-01-01/2022-04-01" + assert not EAC4Parameters.dates_issubset(dsfirst, dssecond) + dsthird = "2021-02-01/2021-02-04" + assert EAC4Parameters.dates_issubset(dsthird, dsfirst) + dsfourth = "2021-02-01/2022-02-04" + assert not EAC4Parameters.dates_issubset(dsfourth, dsfirst) + + +def test_area_subset(): + arfirst = [10, 10, -10, -10] + arsecond = [120, 120, 110, 110] + assert not EAC4Parameters.area_issubset(arfirst, arsecond) + arthird = [5, 5, -5, -5] + assert EAC4Parameters.area_issubset(arthird, arfirst) + arfourth = [5, 5, -15, -5] + assert not EAC4Parameters.area_issubset(arfourth, arfirst) + + +def test_pressure_subset(): + plfirst = {1, 2, 3} + plsecond = {1, 2, 3, 4, 5} + assert EAC4Parameters.pressure_issubset(plfirst, plsecond) + assert not EAC4Parameters.pressure_issubset(plsecond, plfirst) + + +def test_model_subset(): + mlfirst = {1, 2, 3} + mlsecond = {1, 2, 3, 4, 5} + assert EAC4Parameters.model_issubset(mlfirst, mlsecond) + assert not EAC4Parameters.model_issubset(mlsecond, mlfirst) + + +def test__init(): + obj = EAC4Parameters( + data_variables={"a", "b", "c"}, + dates_range="2021-01-01/2022-01-01", + time_values=["00:00"], + area=[0, 0, 0, 0], + pressure_level={"1", "2"}, + model_level={"1", "2"}, + ) + assert obj._data_variables == {"a", "b", "c"} + assert obj.dates_range == "2021-01-01/2022-01-01" + assert obj.time_values == {"00:00"} + assert obj.area == [0, 0, 0, 0] + assert obj.pressure_level == {"1", "2"} + assert obj.model_level == {"1", "2"} + + +def test__build_call_body(): + obj = EAC4Parameters( + data_variables={"a", "b", "c"}, + dates_range="2021-01-01/2022-01-01", + time_values=["00:00"], + area=[0, 0, 0, 0], + pressure_level={"1", "2"}, + model_level={"1", "2"}, + ) + res = obj.build_call_body() + res["variable"] = sorted(res["variable"]) + res["pressure_level"] = sorted(res["pressure_level"]) + res["model_level"] = sorted(res["model_level"]) + assert res == { + "variable": sorted(["a", "b", "c"]), + "date": "2021-01-01/2022-01-01", + "time": ["00:00"], + "area": [0, 0, 0, 0], + "pressure_level": sorted(["1", "2"]), + "model_level": sorted(["1", "2"]), + } diff --git a/tests/api/data_interface/ghg/test_ghg.py b/tests/api/data_interface/ghg/test_ghg.py index 8b0758f..c7b6500 100644 --- a/tests/api/data_interface/ghg/test_ghg.py +++ b/tests/api/data_interface/ghg/test_ghg.py @@ -4,77 +4,61 @@ # pylint: disable=protected-access # pylint: disable=unused-argument -from atmospheric_explorer.api.data_interface.ghg import InversionOptimisedGreenhouseGas +import pytest + +from atmospheric_explorer.api.data_interface.ghg import ( + GHGParameters, + InversionOptimisedGreenhouseGas, +) + + +@pytest.fixture(autouse=True) +def clear_cache(): + InversionOptimisedGreenhouseGas.clear_cache() + yield + InversionOptimisedGreenhouseGas.clear_cache() def test__init(): obj = InversionOptimisedGreenhouseGas( - {"a", "b", "c"}, - "quantity", - "input_observations", - "time_aggregation", - {"2021", "2022"}, - {"01", "02"}, + data_variables={"a", "b", "c"}, + quantity="quantity", + input_observations="input_observations", + time_aggregation="time_aggregation", + year={"2021", "2022"}, + month={"01", "02"}, files_dir="test", ) - assert obj._data_variables == {"a", "b", "c"} + assert isinstance(obj.parameters, GHGParameters) assert obj.file_format == "zip" - assert obj.quantity == "quantity" - assert obj.input_observations == "input_observations" - assert obj.time_aggregation == "time_aggregation" - assert obj._year == {"2021", "2022"} - assert obj._month == {"01", "02"} assert obj.files_dirname == "test" - assert obj.version == "latest" -def test_year(): - obj = InversionOptimisedGreenhouseGas( - {"a", "b", "c"}, - "quantity", - "input_observations", - "time_aggregation", - ["2021", "2022", "2023", "2022"], - "01", +def test_obj_creation(): + sh1 = InversionOptimisedGreenhouseGas( + data_variables={"a", "b", "c"}, + quantity="quantity", + input_observations="input_observations", + time_aggregation="time_aggregation", + year={"2021", "2022"}, + month={"01", "02"}, ) - assert obj._year == {"2021", "2022", "2023"} - assert sorted(obj.year) == sorted(["2021", "2022", "2023"]) - - -def test_month(): - obj = InversionOptimisedGreenhouseGas( - {"a", "b", "c"}, - "quantity", - "input_observations", - "time_aggregation", - "2021", - ["01", "02", "03", "02"], + sh2 = InversionOptimisedGreenhouseGas( + data_variables={"a", "b", "c"}, + quantity="quantity", + input_observations="input_observations", + time_aggregation="time_aggregation", + year={"2021", "2022"}, + month={"01", "02"}, ) - assert obj._month == {"01", "02", "03"} - assert sorted(obj.month) == sorted(["01", "02", "03"]) - - -def test__build_call_body(): - obj = InversionOptimisedGreenhouseGas( - {"a", "b", "c"}, - "quantity", - "input_observations", - "time_aggregation", - {"2000", "2001"}, - {"01", "02"}, - version="latest", + assert id(sh1) == id(sh2) + assert sh1 in InversionOptimisedGreenhouseGas._cache + sh3 = InversionOptimisedGreenhouseGas( + data_variables={"a", "b", "d"}, + quantity="quantity2", + input_observations="input_observations", + time_aggregation="time_aggregation", + year={"2021", "2022"}, + month={"01", "02"}, ) - res = obj._build_call_body() - res["variable"] = sorted(res["variable"]) - res["year"] = sorted(res["year"]) - res["month"] = sorted(res["month"]) - assert res == { - "format": "zip", - "variable": sorted(["a", "b", "c"]), - "quantity": "quantity", - "input_observations": "input_observations", - "time_aggregation": "time_aggregation", - "year": sorted(["2000", "2001"]), - "month": sorted(["01", "02"]), - "version": "latest", - } + assert id(sh3) != id(sh1) diff --git a/tests/api/data_interface/ghg/test_ghg_parameters.py b/tests/api/data_interface/ghg/test_ghg_parameters.py new file mode 100644 index 0000000..9160f6c --- /dev/null +++ b/tests/api/data_interface/ghg/test_ghg_parameters.py @@ -0,0 +1,47 @@ +# pylint: disable=missing-module-docstring +# pylint: disable=missing-class-docstring +# pylint: disable=missing-function-docstring +# pylint: disable=protected-access +# pylint: disable=unused-argument + + +from atmospheric_explorer.api.data_interface.ghg import GHGParameters + + +def test__init(): + obj = GHGParameters( + data_variables="a", + quantity="quantity", + input_observations="input_observations", + time_aggregation="time_aggregation", + year={"2021", "2022"}, + month={"01", "02"}, + ) + assert obj.data_variables == "a" + assert obj.quantity == "quantity" + assert obj.input_observations == "input_observations" + assert obj.time_aggregation == "time_aggregation" + assert obj.year == {"2021", "2022"} + assert obj.month == {"01", "02"} + assert obj.version == "latest" + + +def test_build_call_body(): + obj = GHGParameters( + data_variables="a", + quantity="quantity2", + input_observations="input_observations", + time_aggregation="time_aggregation", + year={"2021", "2022"}, + month={"01", "02"}, + ) + res = obj.build_call_body() + assert res == { + "variable": "a", + "quantity": "quantity2", + "input_observations": "input_observations", + "time_aggregation": "time_aggregation", + "year": {"2021", "2022"}, + "month": {"01", "02"}, + "version": "latest" + } diff --git a/tests/api/shape_selection/test_shapefile.py b/tests/api/shape_selection/test_shapefile.py index 44ce207..074a1d8 100644 --- a/tests/api/shape_selection/test_shapefile.py +++ b/tests/api/shape_selection/test_shapefile.py @@ -12,64 +12,26 @@ from atmospheric_explorer.api.shape_selection.config import SelectionLevel from atmospheric_explorer.api.shape_selection.shapefile import ( ShapefilesDownloader, + ShapefileParameters, dissolve_shapefile_level, ) -def test_cache(): - assert not ShapefilesDownloader._cache - sh1 = ShapefilesDownloader.__new__(ShapefilesDownloader) - sh2 = ShapefilesDownloader.__new__(ShapefilesDownloader) - ShapefilesDownloader.cache(sh1) - ShapefilesDownloader.cache(sh2) - assert len(ShapefilesDownloader._cache) == 2 - assert ShapefilesDownloader._cache[0] is sh1 - assert ShapefilesDownloader._cache[1] is sh2 - - -def test_find_cache(): - sh1 = ShapefilesDownloader( - resolution="10m", - map_type="cultural", - info_type="admin", - depth=0, - instance="countries", - ) - assert ( - ShapefilesDownloader.find_cache( - resolution="10m", - map_type="cultural", - info_type="admin", - depth=0, - instance="countries", - ) - is sh1 - ) - assert ( - ShapefilesDownloader.find_cache( - resolution="50m", - map_type="cultural", - info_type="admin", - depth=0, - instance="countries", - ) - is None - ) - - -def test_is_cached(): - sh1 = ShapefilesDownloader() - assert ShapefilesDownloader.is_cached(sh1) - assert not ShapefilesDownloader.is_cached( - ShapefilesDownloader.__new__(ShapefilesDownloader, instance="countries") - ) - - -def test_clear_cache(): - ShapefilesDownloader() - assert len(ShapefilesDownloader._cache) > 0 - ShapefilesDownloader.clear_cache() - assert not ShapefilesDownloader._cache +def test_param_init(): + sh_down = ShapefileParameters() + assert sh_down.resolution == "50m" + assert sh_down.map_type == "cultural" + assert sh_down.info_type == "admin" + assert sh_down.depth == 0 + assert sh_down.instance == "map_subunits" + + +def test_param_subset(): + sh_down = ShapefileParameters() + sh_down2 = ShapefileParameters() + assert sh_down.subset(sh_down2) + sh_down3 = ShapefileParameters(resolution="10m") + assert not sh_down.subset(sh_down3) def test_init(): From 3e487d13f10ce23a4a51cd6d6f9635b56e3f7679 Mon Sep 17 00:00:00 2001 From: luigibrancati Date: Wed, 13 Sep 2023 17:23:48 +0200 Subject: [PATCH 2/7] Fixed parameters in plotting api --- .../api/data_interface/eac4/eac4_parameters.py | 2 +- atmospheric_explorer/api/loggers.py | 2 +- atmospheric_explorer/api/plotting/anomalies.py | 6 +++--- atmospheric_explorer/api/plotting/hovmoeller.py | 2 +- atmospheric_explorer/api/plotting/yearly_flux.py | 8 ++++---- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py b/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py index edecef1..d82819b 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py @@ -74,7 +74,7 @@ def model_issubset(ml1: set | None, ml2: set | None): def subset(self: EAC4Parameters, obj: EAC4Parameters): return ( - self.data_variables.issubset(obj.data_variables) + self._data_variables.issubset(obj._data_variables) and EAC4Parameters.dates_issubset(self.dates_range, obj.dates_range) and self.time_values.issubset(obj.time_values) and EAC4Parameters.area_issubset(self.area, obj.area) diff --git a/atmospheric_explorer/api/loggers.py b/atmospheric_explorer/api/loggers.py index 1f4fedf..6ac769f 100644 --- a/atmospheric_explorer/api/loggers.py +++ b/atmospheric_explorer/api/loggers.py @@ -24,7 +24,7 @@ class LoggersMeta(type): "root": {"handlers": logging.root.handlers, "level": "WARNING"}, "atmexp": { "handlers": ["console", "rotatingfile"], - "level": "INFO", + "level": "DEBUG", "propagate": 0, "qualname": "atmexp", }, diff --git a/atmospheric_explorer/api/plotting/anomalies.py b/atmospheric_explorer/api/plotting/anomalies.py index 555e31d..fcb2279 100644 --- a/atmospheric_explorer/api/plotting/anomalies.py +++ b/atmospheric_explorer/api/plotting/anomalies.py @@ -23,13 +23,13 @@ def _eac4_anomalies_data( data_variable: str, var_name: str, dates_range: str, - time_values: str | list[str], + time_values: set[str] | list[str], shapes: Selection = Selection(), resampling: str = "1MS", ) -> xr.Dataset: # pylint: disable=too-many-arguments data = EAC4Instance( - data_variables=data_variable, + data_variables=[data_variable], dates_range=dates_range, time_values=time_values, ) @@ -55,7 +55,7 @@ def eac4_anomalies_plot( data_variable: str, var_name: str, dates_range: str, - time_values: str | list[str], + time_values: set[str] | list[str], title: str, shapes: Selection = Selection(), reference_dates_range: str | None = None, diff --git a/atmospheric_explorer/api/plotting/hovmoeller.py b/atmospheric_explorer/api/plotting/hovmoeller.py index 9f6ae2b..7254a47 100644 --- a/atmospheric_explorer/api/plotting/hovmoeller.py +++ b/atmospheric_explorer/api/plotting/hovmoeller.py @@ -32,7 +32,7 @@ def _eac4_hovmoeller_data( data = EAC4Instance( data_variables=data_variable, dates_range=dates_range, - time_values=time_values, + time_values={time_values}, pressure_level=pressure_level, model_level=model_level, ) diff --git a/atmospheric_explorer/api/plotting/yearly_flux.py b/atmospheric_explorer/api/plotting/yearly_flux.py index 1b36bd2..d051990 100644 --- a/atmospheric_explorer/api/plotting/yearly_flux.py +++ b/atmospheric_explorer/api/plotting/yearly_flux.py @@ -31,8 +31,8 @@ def _ghg_flux_over_full_area(dataset: xr.Dataset, var_name: str): def _ghg_surface_satellite_yearly_data( data_variable: str, - years: list[str], - months: list[str], + years: set[str] | list[str], + months: set[str] | list[str], var_name: str, shapes: Selection = Selection(), add_satellite_observations: bool = False, @@ -133,8 +133,8 @@ def _ghg_surface_satellite_yearly_data( def ghg_surface_satellite_yearly_plot( data_variable: str, var_name: str, - years: list[str], - months: list[str], + years: set[str] | list[str], + months: set[str] | list[str], title: str, shapes: Selection = Selection(), add_satellite_observations: bool = True, From 14d1fd341718dac8aaa1c8251e988aa4cd66244f Mon Sep 17 00:00:00 2001 From: "luigi.brancati" Date: Wed, 13 Sep 2023 17:39:29 +0200 Subject: [PATCH 3/7] Fixed cache --- atmospheric_explorer/api/cache/cache.py | 15 ++++++- .../cams_interface/cams_interface.py | 2 + .../api/data_interface/eac4/eac4.py | 3 +- .../data_interface/eac4/eac4_parameters.py | 21 ++++++++-- .../api/data_interface/ghg/ghg.py | 39 ++++++++++--------- .../api/data_interface/ghg/ghg_parameters.py | 20 ++++++++-- .../data_interface/ghg/test_ghg_parameters.py | 6 ++- 7 files changed, 76 insertions(+), 30 deletions(-) diff --git a/atmospheric_explorer/api/cache/cache.py b/atmospheric_explorer/api/cache/cache.py index b2a6617..a79710f 100644 --- a/atmospheric_explorer/api/cache/cache.py +++ b/atmospheric_explorer/api/cache/cache.py @@ -11,6 +11,8 @@ class Parameters(ABC): + """Abstract class to instantiate a dataset parameter used for caching.""" + @abstractmethod def subset(self, parameters: Parameters) -> bool: """Determine wether this parameters instance makes up a subset of parameters.""" @@ -34,9 +36,14 @@ class Cached: @classmethod def find_cache(cls: Cached, parameters: Parameters) -> Cached | None: """Find obj in cache that has a superset of the parameters passed in kwargs.""" + logger.debug("Looking in cache for parameters %s", parameters) for sd_obj in cls._cache: - if parameters.subset(sd_obj.parameters): + if isinstance(sd_obj.parameters, type(parameters)) and parameters.subset( + sd_obj.parameters + ): + logger.debug("Found cached object %s", sd_obj) return sd_obj + logger.debug("Object with parameters %s is not cached", parameters) return None def is_cached(self) -> bool: @@ -45,12 +52,14 @@ def is_cached(self) -> bool: def cache(self) -> None: """Cache self.""" + logger.debug("Caching object %s", self) type(self)._cache.append(self) @classmethod def clear_cache(cls): """Clear cache.""" - cls._cache = [] + logger.debug("Cleared objects %s from cache", cls) + cls._cache = list(filter(lambda obj: not(isinstance(obj,cls)), cls._cache)) def __new__(cls: Cached, parameters: Parameters): logger.debug( @@ -65,6 +74,7 @@ def __new__(cls: Cached, parameters: Parameters): cached_obj = cls.find_cache(parameters) if cached_obj is not None: return cached_obj + logger.debug("Cached object not found, creating a new one") return super().__new__(cls) @staticmethod @@ -79,6 +89,7 @@ def init_cache(func): def wrapper(self, *args, **kwargs): if self.is_cached(): return + logger.debug("Initializing an instance of %s", type(self)) func(self, *args, **kwargs) self.cache() diff --git a/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py b/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py index fa05f1f..a9bffa8 100644 --- a/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py +++ b/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py @@ -38,6 +38,7 @@ def __init__(self: CAMSDataInterface): self._instances.append(self) create_folder(self.data_folder) logger.info("Created folder %s", self.data_folder) + self.downloaded = False def build_call_body(self: CAMSDataInterface, parameters: CAMSParameters): """Builds the CDS API call body.""" @@ -55,6 +56,7 @@ def download(self: CAMSDataInterface, parameters: CAMSParameters, file_fullpath: logger.debug("Calling cdsapi with body %s", body) client.retrieve(self.dataset_name, body, file_fullpath) logger.info("Finished downloading file %s", file_fullpath) + self.downloaded = True @classmethod def list_data_files(cls) -> list: diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4.py b/atmospheric_explorer/api/data_interface/eac4/eac4.py index 4cf0f8e..a9a2b70 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4.py @@ -107,7 +107,8 @@ def download(self: EAC4Instance) -> None: Uses cdsapi to interact with CAMS ADS. """ - return super().download(self.parameters, self.file_full_path) + if not self.downloaded: + super().download(self.parameters, self.file_full_path) def _simplify_dataset(self: EAC4Instance, dataset: xr.Dataset): return dataset.rio.write_crs(CRS) diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py b/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py index d82819b..10cd4de 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py @@ -7,11 +7,14 @@ from atmospheric_explorer.api.data_interface.cams_interface import CAMSParameters from atmospheric_explorer.api.loggers import get_logger +from textwrap import dedent logger = get_logger("atmexp") class EAC4Parameters(CAMSParameters): + """Parameters for EAC4 dataset.""" + def __init__( self: EAC4Parameters, data_variables: set[str] | list[str], @@ -30,6 +33,16 @@ def __init__( ) self.model_level = set(model_level) if model_level is not None else model_level + def __repr__(self) -> str: + return dedent(f"""\ + data_variables: {self.data_variables} + dates_range: {self.dates_range} + time_values: {self.time_values} + area: {self.area} + pressure_level: {self.pressure_level} + model_level: {self.model_level} + """) + @staticmethod def dates_issubset(date_range1, date_range2): """Check if the first date range is a subset of the second date range.""" @@ -45,7 +58,7 @@ def _is_subset_none(arg1, arg2) -> bool | None: return None @staticmethod - def area_issubset(area1: list, area2: list): + def area_issubset(area1: list, area2: list) -> bool: """Check if the first area is a subset of the second area.""" res = EAC4Parameters._is_subset_none(area1, area2) if res is not None: @@ -57,7 +70,7 @@ def area_issubset(area1: list, area2: list): ) @staticmethod - def pressure_issubset(pl1: set | None, pl2: set | None): + def pressure_issubset(pl1: set | None, pl2: set | None) -> bool: """Check if the first pressure level is a subset of the second pressure level.""" res = EAC4Parameters._is_subset_none(pl1, pl2) if res is not None: @@ -65,14 +78,14 @@ def pressure_issubset(pl1: set | None, pl2: set | None): return pl1.issubset(pl2) @staticmethod - def model_issubset(ml1: set | None, ml2: set | None): + def model_issubset(ml1: set | None, ml2: set | None) -> bool: """Check if the first model level is a subset of the second model level.""" res = EAC4Parameters._is_subset_none(ml1, ml2) if res is not None: return res return ml1.issubset(ml2) - def subset(self: EAC4Parameters, obj: EAC4Parameters): + def subset(self: EAC4Parameters, obj: EAC4Parameters) -> bool: return ( self._data_variables.issubset(obj._data_variables) and EAC4Parameters.dates_issubset(self.dates_range, obj.dates_range) diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg.py b/atmospheric_explorer/api/data_interface/ghg/ghg.py index ef75823..8f8b97f 100644 --- a/atmospheric_explorer/api/data_interface/ghg/ghg.py +++ b/atmospheric_explorer/api/data_interface/ghg/ghg.py @@ -95,6 +95,7 @@ def __init__( month=month, version=version, ) + self.downloaded = False self.files_dirname = files_dir if files_dir is not None else f"data_{self._id}" self.files_dir_path = os.path.join(self.dataset_dir, self.files_dirname) if os.path.exists(self.dataset_dir): @@ -121,24 +122,26 @@ def download(self: InversionOptimisedGreenhouseGas) -> None: Uses cdsapi to interact with CAMS ADS. This function also extracts the netcdf file inside the zip file, which is then deleted. """ - super().download(self.parameters, self.file_full_path) - # This dataset downloads zipfiles with possibly multiple netcdf files inside - # We must extract it - zip_filename = self.file_full_path - with zipfile.ZipFile(zip_filename, "r") as zip_ref: - self.file_format = "netcdf" - self.file_ext = "nc" - zip_ref.extractall(self.files_dir_path) - logger.info( - "Extracted file %s to folder %s", - self.file_full_path, - self.files_dir_path, - ) - self.file_full_path = "*" - logger.info("Updated file_full_path to wildcard path %s", self.file_full_path) - # Remove zip file - os.remove(zip_filename) - logger.info("Removed %s", zip_filename) + if not self.downloaded: + super().download(self.parameters, self.file_full_path) + # This dataset downloads zipfiles with possibly multiple netcdf files inside + # We must extract it + zip_filename = self.file_full_path + with zipfile.ZipFile(zip_filename, "r") as zip_ref: + self.file_format = "netcdf" + self.file_ext = "nc" + zip_ref.extractall(self.files_dir_path) + logger.info( + "Extracted file %s to folder %s", + self.file_full_path, + self.files_dir_path, + ) + self.file_full_path = "*" + logger.info("Updated file_full_path to wildcard path %s", self.file_full_path) + # Remove zip file + os.remove(zip_filename) + logger.info("Removed %s", zip_filename) + self.downloaded = True @staticmethod def _align_dims(dataset: xr.Dataset, dim: str, values: list) -> xr.Dataset: diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py b/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py index 3e015e8..7c9a0e0 100644 --- a/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py +++ b/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py @@ -7,11 +7,14 @@ from atmospheric_explorer.api.data_interface.cams_interface import CAMSParameters from atmospheric_explorer.api.loggers import get_logger +from textwrap import dedent logger = get_logger("atmexp") class GHGParameters(CAMSParameters): + """Parameters for Global Inversion dataset.""" + def __init__( self: GHGParameters, data_variables: str, @@ -30,7 +33,18 @@ def __init__( self.month = set(month) self.version = version - def subset(self: GHGParameters, obj: GHGParameters): + def __repr__(self) -> str: + return dedent(f"""\ + data_variables: {self.data_variables} + quantity: {self.quantity} + input_observations: {self.input_observations} + time_aggregation: {self.time_aggregation} + year: {self.year} + month: {self.month} + version: {self.version} + """) + + def subset(self: GHGParameters, obj: GHGParameters) -> bool: return ( self.data_variables == obj.data_variables and self.quantity == obj.quantity @@ -50,8 +64,8 @@ def build_call_body(self: GHGParameters) -> dict: "quantity": self.quantity, "input_observations": self.input_observations, "time_aggregation": self.time_aggregation, - "year": self.year, - "month": self.month, + "year": list(self.year), + "month": list(self.month), } ) return call_body diff --git a/tests/api/data_interface/ghg/test_ghg_parameters.py b/tests/api/data_interface/ghg/test_ghg_parameters.py index 9160f6c..1e04915 100644 --- a/tests/api/data_interface/ghg/test_ghg_parameters.py +++ b/tests/api/data_interface/ghg/test_ghg_parameters.py @@ -36,12 +36,14 @@ def test_build_call_body(): month={"01", "02"}, ) res = obj.build_call_body() + res['year'] = sorted(res['year']) + res['month'] = sorted(res['month']) assert res == { "variable": "a", "quantity": "quantity2", "input_observations": "input_observations", "time_aggregation": "time_aggregation", - "year": {"2021", "2022"}, - "month": {"01", "02"}, + "year": ["2021", "2022"], + "month": ["01", "02"], "version": "latest" } From 13b68aa4006df090526d56ff06a824ef741fbab4 Mon Sep 17 00:00:00 2001 From: "luigi.brancati" Date: Thu, 14 Sep 2023 13:26:34 +0200 Subject: [PATCH 4/7] Fixed missing folder bug --- .../data_interface/cams_interface/cams_interface.py | 12 ++++++++---- .../api/data_interface/eac4/eac4.py | 9 ++++----- atmospheric_explorer/api/data_interface/ghg/ghg.py | 13 +++++++------ 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py b/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py index a9bffa8..54123b3 100644 --- a/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py +++ b/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py @@ -10,13 +10,15 @@ import cdsapi +from atmospheric_explorer.api.data_interface.cams_interface.cams_parameters import ( + CAMSParameters, +) from atmospheric_explorer.api.loggers import get_logger from atmospheric_explorer.api.os_manager import ( create_folder, get_local_folder, remove_folder, ) -from atmospheric_explorer.api.data_interface.cams_interface.cams_parameters import CAMSParameters logger = get_logger("atmexp") @@ -41,12 +43,14 @@ def __init__(self: CAMSDataInterface): self.downloaded = False def build_call_body(self: CAMSDataInterface, parameters: CAMSParameters): - """Builds the CDS API call body.""" + """Build call body to be passed to cdsapi.""" call_body = parameters.build_call_body() - call_body['format'] = self.file_format + call_body["format"] = self.file_format return call_body - def download(self: CAMSDataInterface, parameters: CAMSParameters, file_fullpath: str) -> None: + def _download( + self: CAMSDataInterface, parameters: CAMSParameters, file_fullpath: str + ) -> None: """Download the dataset and saves it to file specified in filename. Uses cdsapi to interact with CAMS ADS. diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4.py b/atmospheric_explorer/api/data_interface/eac4/eac4.py index a9a2b70..109457e 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4.py @@ -12,7 +12,7 @@ from atmospheric_explorer.api.data_interface.cams_interface import CAMSDataInterface from atmospheric_explorer.api.data_interface.eac4.eac4_parameters import EAC4Parameters from atmospheric_explorer.api.loggers import get_logger -from atmospheric_explorer.api.os_manager import create_folder, remove_folder +from atmospheric_explorer.api.os_manager import create_folder logger = get_logger("atmexp") @@ -90,9 +90,8 @@ def __init__( ) self.files_dirname = files_dir if files_dir is not None else f"data_{self._id}" self.files_dir_path = os.path.join(self.dataset_dir, self.files_dirname) - if os.path.exists(self.dataset_dir): - remove_folder(self.dataset_dir) - create_folder(self.files_dir_path) + if not os.path.exists(self.files_dir_path): + create_folder(self.files_dir_path) logger.info("Created folder %s", self.files_dir_path) @property @@ -108,7 +107,7 @@ def download(self: EAC4Instance) -> None: Uses cdsapi to interact with CAMS ADS. """ if not self.downloaded: - super().download(self.parameters, self.file_full_path) + super()._download(self.parameters, self.file_full_path) def _simplify_dataset(self: EAC4Instance, dataset: xr.Dataset): return dataset.rio.write_crs(CRS) diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg.py b/atmospheric_explorer/api/data_interface/ghg/ghg.py index 8f8b97f..35ff56a 100644 --- a/atmospheric_explorer/api/data_interface/ghg/ghg.py +++ b/atmospheric_explorer/api/data_interface/ghg/ghg.py @@ -18,7 +18,7 @@ ) from atmospheric_explorer.api.data_interface.ghg.ghg_parameters import GHGParameters from atmospheric_explorer.api.loggers import get_logger -from atmospheric_explorer.api.os_manager import create_folder, remove_folder +from atmospheric_explorer.api.os_manager import create_folder logger = get_logger("atmexp") @@ -98,10 +98,9 @@ def __init__( self.downloaded = False self.files_dirname = files_dir if files_dir is not None else f"data_{self._id}" self.files_dir_path = os.path.join(self.dataset_dir, self.files_dirname) - if os.path.exists(self.dataset_dir): - remove_folder(self.dataset_dir) + if not os.path.exists(self.files_dir_path): + create_folder(self.files_dir_path) self.file_full_path = self.files_dirname - create_folder(self.files_dir_path) logger.info("Created folder %s", self.files_dir_path) @property @@ -123,7 +122,7 @@ def download(self: InversionOptimisedGreenhouseGas) -> None: This function also extracts the netcdf file inside the zip file, which is then deleted. """ if not self.downloaded: - super().download(self.parameters, self.file_full_path) + super()._download(self.parameters, self.file_full_path) # This dataset downloads zipfiles with possibly multiple netcdf files inside # We must extract it zip_filename = self.file_full_path @@ -137,7 +136,9 @@ def download(self: InversionOptimisedGreenhouseGas) -> None: self.files_dir_path, ) self.file_full_path = "*" - logger.info("Updated file_full_path to wildcard path %s", self.file_full_path) + logger.info( + "Updated file_full_path to wildcard path %s", self.file_full_path + ) # Remove zip file os.remove(zip_filename) logger.info("Removed %s", zip_filename) From 94147995a1f492dad87b55f72c30421d19e767c5 Mon Sep 17 00:00:00 2001 From: "luigi.brancati" Date: Sat, 16 Sep 2023 11:54:01 +0200 Subject: [PATCH 5/7] Updated logger, modified singleton, added logs --- README.md | 7 +- atmospheric_explorer/api/cache/cache.py | 29 ++++--- .../cams_interface/cams_interface.py | 19 ++--- .../cams_interface/cams_parameters.py | 18 +++-- .../data_interface/data_transformations.py | 4 +- ...fig_parser.py => dataset_config_parser.py} | 72 +++++++++-------- .../api/data_interface/eac4/eac4.py | 9 +-- .../api/data_interface/eac4/eac4_config.py | 20 +++-- .../data_interface/eac4/eac4_parameters.py | 31 ++++---- .../api/data_interface/ghg/ghg.py | 18 ++--- .../api/data_interface/ghg/ghg_config.py | 20 +++-- .../api/data_interface/ghg/ghg_parameters.py | 32 ++++---- .../api/{os_manager.py => local_folder.py} | 16 +--- atmospheric_explorer/api/loggers/__init__.py | 4 + .../api/{ => loggers}/loggers.py | 47 +++-------- .../api/loggers/loggers_utils.py | 18 +++++ atmospheric_explorer/api/os_utils.py | 22 ++++++ .../api/plotting/anomalies.py | 6 +- .../api/plotting/hovmoeller.py | 6 +- .../api/plotting/plot_utils.py | 10 +-- .../api/plotting/yearly_flux.py | 8 +- .../api/shape_selection/shape_selection.py | 8 +- .../api/shape_selection/shapefile.py | 78 +++++++++---------- atmospheric_explorer/api/singleton.py | 23 ++++++ atmospheric_explorer/cli/logs.py | 6 +- .../cli/plotting/anomalies.py | 8 +- .../cli/plotting/hovmoeller.py | 8 +- .../cli/plotting/yearly_flux.py | 6 +- atmospheric_explorer/ui/Home.py | 20 ++--- .../ui/interactive_map/interactive_map.py | 49 +++++------- .../ui/pages/Anomalies Plot.py | 16 ++-- .../ui/pages/Hovmoeller Plot.py | 28 ++++--- .../ui/pages/Yearly Surface Plot.py | 18 ++--- atmospheric_explorer/ui/utils.py | 6 +- ...arser.py => test_dataset_config_parser.py} | 4 +- tests/api/data_interface/test_singleton.py | 17 ++++ tests/api/shape_selection/test_shapefile.py | 8 +- tests/api/test_os_manager.py | 2 +- tests/conftest.py | 17 ++++ 39 files changed, 389 insertions(+), 349 deletions(-) rename atmospheric_explorer/api/data_interface/{config_parser.py => dataset_config_parser.py} (64%) rename atmospheric_explorer/api/{os_manager.py => local_folder.py} (56%) create mode 100644 atmospheric_explorer/api/loggers/__init__.py rename atmospheric_explorer/api/{ => loggers}/loggers.py (57%) create mode 100644 atmospheric_explorer/api/loggers/loggers_utils.py create mode 100644 atmospheric_explorer/api/os_utils.py create mode 100644 atmospheric_explorer/api/singleton.py rename tests/api/data_interface/{test_config_parser.py => test_dataset_config_parser.py} (88%) create mode 100644 tests/api/data_interface/test_singleton.py create mode 100644 tests/conftest.py diff --git a/README.md b/README.md index 095a88b..9a314fc 100644 --- a/README.md +++ b/README.md @@ -417,12 +417,13 @@ Once pre-commit is enabled, it will run a number of check on **staged files**. A ## Logger The logger configuration is defined in `logger.py` inside a dictionary. -This application uses a logger called `atmexp`, if you want to use it just import it as show below +This application uses a logger called `atmexp`, which is already instantiated in the variable `atm_exp_logger`. +If you want to use it just import `atm_exp_logger` ```python -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger -logger = get_logger("atmexp") +atm_exp_logger.info("This is the Atmospheric explorer logger") ``` ## API Documentation diff --git a/atmospheric_explorer/api/cache/cache.py b/atmospheric_explorer/api/cache/cache.py index a79710f..3faf9b2 100644 --- a/atmospheric_explorer/api/cache/cache.py +++ b/atmospheric_explorer/api/cache/cache.py @@ -5,19 +5,18 @@ from functools import wraps from textwrap import dedent -from atmospheric_explorer.api.loggers import get_logger - -logger = get_logger("atmexp") +from atmospheric_explorer.api.loggers import atm_exp_logger class Parameters(ABC): - """Abstract class to instantiate a dataset parameter used for caching.""" + # pylint: disable = too-few-public-methods + """Abstract class to instantiate dataset parameters, used for caching.""" @abstractmethod - def subset(self, parameters: Parameters) -> bool: + def subset(self, other: Parameters) -> bool: """Determine wether this parameters instance makes up a subset of parameters.""" raise NotImplementedError( - "A parameters class needs to implement the subset method" + "A Parameters subclass needs to implement the subset method" ) @@ -36,14 +35,14 @@ class Cached: @classmethod def find_cache(cls: Cached, parameters: Parameters) -> Cached | None: """Find obj in cache that has a superset of the parameters passed in kwargs.""" - logger.debug("Looking in cache for parameters %s", parameters) + atm_exp_logger.debug("Looking in cache for parameters %s", parameters) for sd_obj in cls._cache: if isinstance(sd_obj.parameters, type(parameters)) and parameters.subset( sd_obj.parameters ): - logger.debug("Found cached object %s", sd_obj) + atm_exp_logger.debug("Found cached object %s", sd_obj) return sd_obj - logger.debug("Object with parameters %s is not cached", parameters) + atm_exp_logger.debug("Object with parameters %s is not cached", parameters) return None def is_cached(self) -> bool: @@ -52,17 +51,17 @@ def is_cached(self) -> bool: def cache(self) -> None: """Cache self.""" - logger.debug("Caching object %s", self) + atm_exp_logger.debug("Caching object %s", self) type(self)._cache.append(self) @classmethod def clear_cache(cls): """Clear cache.""" - logger.debug("Cleared objects %s from cache", cls) - cls._cache = list(filter(lambda obj: not(isinstance(obj,cls)), cls._cache)) + atm_exp_logger.debug("Cleared objects %s from cache", cls) + cls._cache = list(filter(lambda obj: not (isinstance(obj, cls)), cls._cache)) def __new__(cls: Cached, parameters: Parameters): - logger.debug( + atm_exp_logger.debug( dedent( """\ Attempting to create Cached object with attributes @@ -74,7 +73,7 @@ def __new__(cls: Cached, parameters: Parameters): cached_obj = cls.find_cache(parameters) if cached_obj is not None: return cached_obj - logger.debug("Cached object not found, creating a new one") + atm_exp_logger.debug("Cached object not found, creating a new one") return super().__new__(cls) @staticmethod @@ -89,7 +88,7 @@ def init_cache(func): def wrapper(self, *args, **kwargs): if self.is_cached(): return - logger.debug("Initializing an instance of %s", type(self)) + atm_exp_logger.debug("Initializing an instance of %s", type(self)) func(self, *args, **kwargs) self.cache() diff --git a/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py b/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py index 54123b3..8de806a 100644 --- a/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py +++ b/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py @@ -13,14 +13,9 @@ from atmospheric_explorer.api.data_interface.cams_interface.cams_parameters import ( CAMSParameters, ) -from atmospheric_explorer.api.loggers import get_logger -from atmospheric_explorer.api.os_manager import ( - create_folder, - get_local_folder, - remove_folder, -) - -logger = get_logger("atmexp") +from atmospheric_explorer.api.local_folder import get_local_folder +from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.os_utils import create_folder, remove_folder class CAMSDataInterface(ABC): @@ -39,7 +34,7 @@ def __init__(self: CAMSDataInterface): self._id = next(self._ids) self._instances.append(self) create_folder(self.data_folder) - logger.info("Created folder %s", self.data_folder) + atm_exp_logger.info("Created folder %s", self.data_folder) self.downloaded = False def build_call_body(self: CAMSDataInterface, parameters: CAMSParameters): @@ -57,9 +52,9 @@ def _download( """ client = cdsapi.Client() body = self.build_call_body(parameters) - logger.debug("Calling cdsapi with body %s", body) + atm_exp_logger.debug("Calling cdsapi with body %s", body) client.retrieve(self.dataset_name, body, file_fullpath) - logger.info("Finished downloading file %s", file_fullpath) + atm_exp_logger.info("Finished downloading file %s", file_fullpath) self.downloaded = True @classmethod @@ -70,7 +65,7 @@ def list_data_files(cls) -> list: @classmethod def clear_data_files(cls) -> None: """Clears all files inside data folder.""" - logger.info("Removed data folder.") + atm_exp_logger.info("Removed data folder.") remove_folder(CAMSDataInterface.data_folder) @abstractmethod diff --git a/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py b/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py index d56b246..bb2e7ff 100644 --- a/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py +++ b/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py @@ -1,18 +1,24 @@ """\ -This module collects classes to easily interact with data downloaded from CAMS ADS. +This module defines a generic base class to be used for the CAMS datasets parameters. """ # pylint: disable=too-few-public-methods # pylint: disable=too-many-arguments from __future__ import annotations from atmospheric_explorer.api.cache import Parameters -from atmospheric_explorer.api.loggers import get_logger - -logger = get_logger("atmexp") +from atmospheric_explorer.api.loggers import atm_exp_logger class CAMSParameters(Parameters): + """\ + Base class to be used for CAMS dataset parameters. + + Attributes: + data_variables (str | set[str] | list[str]): data varaibles to be downloaded from CAMS, depend on the dataset + """ + def __init__(self, data_variables: str | set[str] | list[str]) -> None: + atm_exp_logger.debug("Instantiating parameters object %s", type(self).__name__) self.data_variables = data_variables @property @@ -32,8 +38,8 @@ def data_variables( data_variables_input = [data_variables_input] self._data_variables = set(data_variables_input) - def subset(self: CAMSParameters, obj: CAMSParameters): - return self.data_variables.issubset(obj.data_variables) + def subset(self: CAMSParameters, other: CAMSParameters): + return self.data_variables.issubset(other.data_variables) def build_call_body(self: CAMSParameters) -> dict: """Build the CDSAPI call body""" diff --git a/atmospheric_explorer/api/data_interface/data_transformations.py b/atmospheric_explorer/api/data_interface/data_transformations.py index a436714..445e635 100644 --- a/atmospheric_explorer/api/data_interface/data_transformations.py +++ b/atmospheric_explorer/api/data_interface/data_transformations.py @@ -7,11 +7,13 @@ import xarray as xr from shapely.geometry import mapping +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.shape_selection.shape_selection import Selection def split_time_dim(dataset: xr.Dataset, time_dim: str): - """Split datetime dimension into times and dates.""" + """Return a new dataset where the time_dim dimension is split into times and dates dimensions.""" + atm_exp_logger.debug("Splitting time dim %s of dataset %s", time_dim, dataset) times = dataset[f"{time_dim}.time"].values dates = np.array(dataset[time_dim].values, dtype="datetime64[D]").astype( "datetime64[ns]" diff --git a/atmospheric_explorer/api/data_interface/config_parser.py b/atmospheric_explorer/api/data_interface/dataset_config_parser.py similarity index 64% rename from atmospheric_explorer/api/data_interface/config_parser.py rename to atmospheric_explorer/api/data_interface/dataset_config_parser.py index de65e71..78845d3 100644 --- a/atmospheric_explorer/api/data_interface/config_parser.py +++ b/atmospheric_explorer/api/data_interface/dataset_config_parser.py @@ -16,9 +16,7 @@ import yaml from atmospheric_explorer.api.exceptions import OperationNotAllowed -from atmospheric_explorer.api.loggers import get_logger - -logger = get_logger("atmexp") +from atmospheric_explorer.api.loggers import atm_exp_logger class OperationParser: @@ -55,13 +53,13 @@ def _eval(node): _eval(node.left), _eval(node.right) ) except KeyError as exc: - logger.error(exc) - logger.error("Unsupported operation %s", type(node.op)) + atm_exp_logger.error(exc) + atm_exp_logger.error("Unsupported operation %s", type(node.op)) raise OperationNotAllowed( f"Unsupported operation {type(node.op)}" ) from exc else: - logger.error("Unsupported type %s", node) + atm_exp_logger.error("Unsupported type %s", node) raise OperationNotAllowed(f"Unsupported type {node}") return _eval(node.body) @@ -78,49 +76,57 @@ def _(self, operation: list) -> list: return [self.arithmetic_eval(op) for op in operation] -class ConfigMeta(type): +class DatasetConfigParser: # pylint: disable=too-few-public-methods - """This meta class is needed to implement a singleton pattern so that the config files are loaded only once.""" + """This class implements some basic functionalities to parse a YAML file containing the configuration for a dataset, + i.e. variables, conversion factors etc. - _instances = {} - _parser = OperationParser() + For reference, check the file atmospheric_explorer/api/data_interface/ghg/ghg_config.yaml. + + A dataset configuration file is **expected** to have data structured in the following way + + variables: + data_variable_name: + var_name: # name of the dataset column + conversion: + conversion_factor: ... + conversion_unit: ... + """ - def __new__(mcs, *args, **kwargs): - """Returns new ConfigMeta instance.""" - return super().__new__(mcs, *args) + _parser = OperationParser() - def __init__(cls, *args, **kwargs): - """Initializes ConfigMeta instance.""" + def __init__(self, **kwargs): filename = kwargs["filename"] filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename) with open(filepath, "r", encoding="utf-8") as file: - cls.config = yaml.safe_load(file) + atm_exp_logger.debug("Reading config from file %s", file.name) + self.config = yaml.safe_load(file) # Convert formulas inside configuration to floats - logger.debug("Evaluating arithmetic formulas in config") - cls._parse_factors(cls.config["variables"]) - super().__init__(*args, **kwargs) - logger.debug("Loaded config from file %s", filename) - - def __call__(cls, *args, **kwargs): - """Enables calling ConfigMeta instance like a function.""" - if cls not in cls._instances: - instance = super().__call__(*args, **kwargs) - cls._instances[cls] = instance - return cls._instances[cls] + atm_exp_logger.debug("Evaluating arithmetic formulas in config") + self.parse_factors(self.config["variables"]) + + @classmethod + def get_config(cls) -> dict: + """Function to get the actual config object.""" + return cls().config @singledispatchmethod @classmethod - def _parse_factors(mcs, data_dict: dict): + def parse_factors(cls, data_dict: dict): + """Parse conversion factors in a dataset config file. + + The file is **expected** to have a 'conversion' section. + """ if data_dict.get("conversion") is not None: - data_dict["conversion"]["conversion_factor"] = mcs._parser.arithmetic_eval( + data_dict["conversion"]["conversion_factor"] = cls._parser.arithmetic_eval( data_dict["conversion"]["conversion_factor"] ) else: for k in data_dict.keys(): - mcs._parse_factors(data_dict[k]) + cls.parse_factors(data_dict[k]) - @_parse_factors.register + @parse_factors.register @classmethod - def _(mcs, data: list): + def _(cls, data: list): for elem in data: - mcs._parse_factors(elem) + cls.parse_factors(elem) diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4.py b/atmospheric_explorer/api/data_interface/eac4/eac4.py index 109457e..7cfeef5 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4.py @@ -11,10 +11,8 @@ from atmospheric_explorer.api.config import CRS from atmospheric_explorer.api.data_interface.cams_interface import CAMSDataInterface from atmospheric_explorer.api.data_interface.eac4.eac4_parameters import EAC4Parameters -from atmospheric_explorer.api.loggers import get_logger -from atmospheric_explorer.api.os_manager import create_folder - -logger = get_logger("atmexp") +from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.os_utils import create_folder class EAC4Instance(CAMSDataInterface, Cached): @@ -92,7 +90,7 @@ def __init__( self.files_dir_path = os.path.join(self.dataset_dir, self.files_dirname) if not os.path.exists(self.files_dir_path): create_folder(self.files_dir_path) - logger.info("Created folder %s", self.files_dir_path) + atm_exp_logger.info("Created folder %s", self.files_dir_path) @property def file_full_path(self: EAC4Instance) -> str: @@ -106,6 +104,7 @@ def download(self: EAC4Instance) -> None: Uses cdsapi to interact with CAMS ADS. """ + atm_exp_logger.info("Downloading dataset %s", self) if not self.downloaded: super()._download(self.parameters, self.file_full_path) diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4_config.py b/atmospheric_explorer/api/data_interface/eac4/eac4_config.py index 2615b6d..9c2a266 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4_config.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4_config.py @@ -5,21 +5,19 @@ import xarray as xr -from atmospheric_explorer.api.data_interface.config_parser import ConfigMeta -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.data_interface.dataset_config_parser import ( + DatasetConfigParser, +) +from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.singleton import Singleton -logger = get_logger("atmexp") - -class EAC4Config(metaclass=ConfigMeta, filename="eac4/eac4_config.yaml"): +class EAC4Config(DatasetConfigParser, metaclass=Singleton): # pylint: disable=too-few-public-methods """This class is needed to implement a singleton pattern so that config is loaded only once.""" - @classmethod - def get_config(cls) -> dict: - """Function to get the actual config object.""" - logger.debug("Loading EAC4 config") - return cls().config + def __init__(self): + super().__init__(filename="eac4/eac4_config.yaml") @classmethod def convert_units_array( @@ -28,7 +26,7 @@ def convert_units_array( """Converts an xarray.DataArray from its original units to the units specified in the eac4_config.yaml file.""" conf = cls.get_config()["variables"][data_variable] conv_f = float(conf["conversion"]["conversion_factor"]) - logger.debug( + atm_exp_logger.debug( "Converting array %s to unit %s with factor %f", array.name, conf["conversion"]["convert_unit"], diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py b/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py index 10cd4de..5fb88a2 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py @@ -5,11 +5,10 @@ # pylint: disable=too-many-arguments from __future__ import annotations -from atmospheric_explorer.api.data_interface.cams_interface import CAMSParameters -from atmospheric_explorer.api.loggers import get_logger from textwrap import dedent -logger = get_logger("atmexp") +from atmospheric_explorer.api.data_interface.cams_interface import CAMSParameters +from atmospheric_explorer.api.loggers import atm_exp_logger class EAC4Parameters(CAMSParameters): @@ -34,14 +33,16 @@ def __init__( self.model_level = set(model_level) if model_level is not None else model_level def __repr__(self) -> str: - return dedent(f"""\ + return dedent( + f"""\ data_variables: {self.data_variables} dates_range: {self.dates_range} time_values: {self.time_values} area: {self.area} pressure_level: {self.pressure_level} model_level: {self.model_level} - """) + """ + ) @staticmethod def dates_issubset(date_range1, date_range2): @@ -85,17 +86,20 @@ def model_issubset(ml1: set | None, ml2: set | None) -> bool: return res return ml1.issubset(ml2) - def subset(self: EAC4Parameters, obj: EAC4Parameters) -> bool: - return ( - self._data_variables.issubset(obj._data_variables) - and EAC4Parameters.dates_issubset(self.dates_range, obj.dates_range) - and self.time_values.issubset(obj.time_values) - and EAC4Parameters.area_issubset(self.area, obj.area) + def subset(self: EAC4Parameters, other: EAC4Parameters) -> bool: + # pylint: disable = protected-access + res = ( + self._data_variables.issubset(other._data_variables) + and EAC4Parameters.dates_issubset(self.dates_range, other.dates_range) + and self.time_values.issubset(other.time_values) + and EAC4Parameters.area_issubset(self.area, other.area) and EAC4Parameters.pressure_issubset( - self.pressure_level, obj.pressure_level + self.pressure_level, other.pressure_level ) - and EAC4Parameters.model_issubset(self.model_level, obj.model_level) + and EAC4Parameters.model_issubset(self.model_level, other.model_level) ) + atm_exp_logger.debug("Subset result: %s\nself: %s\nother: %s", res, self, other) + return res def build_call_body(self: EAC4Parameters) -> dict: """Build the CDSAPI call body""" @@ -108,4 +112,5 @@ def build_call_body(self: EAC4Parameters) -> dict: call_body["pressure_level"] = list(self.pressure_level) if self.model_level is not None: call_body["model_level"] = list(self.model_level) + atm_exp_logger.debug("Call body for %s:\n%s", self, call_body) return call_body diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg.py b/atmospheric_explorer/api/data_interface/ghg/ghg.py index 35ff56a..9257b23 100644 --- a/atmospheric_explorer/api/data_interface/ghg/ghg.py +++ b/atmospheric_explorer/api/data_interface/ghg/ghg.py @@ -17,10 +17,8 @@ CAMSDataInterface, ) from atmospheric_explorer.api.data_interface.ghg.ghg_parameters import GHGParameters -from atmospheric_explorer.api.loggers import get_logger -from atmospheric_explorer.api.os_manager import create_folder - -logger = get_logger("atmexp") +from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.os_utils import create_folder class InversionOptimisedGreenhouseGas(CAMSDataInterface, Cached): @@ -101,7 +99,7 @@ def __init__( if not os.path.exists(self.files_dir_path): create_folder(self.files_dir_path) self.file_full_path = self.files_dirname - logger.info("Created folder %s", self.files_dir_path) + atm_exp_logger.info("Created folder %s", self.files_dir_path) @property def file_full_path(self: InversionOptimisedGreenhouseGas) -> str: @@ -130,18 +128,18 @@ def download(self: InversionOptimisedGreenhouseGas) -> None: self.file_format = "netcdf" self.file_ext = "nc" zip_ref.extractall(self.files_dir_path) - logger.info( + atm_exp_logger.info( "Extracted file %s to folder %s", self.file_full_path, self.files_dir_path, ) self.file_full_path = "*" - logger.info( + atm_exp_logger.info( "Updated file_full_path to wildcard path %s", self.file_full_path ) # Remove zip file os.remove(zip_filename) - logger.info("Removed %s", zip_filename) + atm_exp_logger.info("Removed %s", zip_filename) self.downloaded = True @staticmethod @@ -177,7 +175,9 @@ def read_dataset( but the file themselves may miss the time dimension. It adds a time dimension for each file that's missing it and concats all files into a dataset. """ - logger.debug("Reading files iteratively from path %s", self.file_full_path) + atm_exp_logger.debug( + "Reading files iteratively from path %s", self.file_full_path + ) # Create dataset from first file files = sorted(glob(self.file_full_path)) dataset = xr.open_dataset(files[0]) diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg_config.py b/atmospheric_explorer/api/data_interface/ghg/ghg_config.py index aaf2742..5f9feb1 100644 --- a/atmospheric_explorer/api/data_interface/ghg/ghg_config.py +++ b/atmospheric_explorer/api/data_interface/ghg/ghg_config.py @@ -5,21 +5,19 @@ import xarray as xr -from atmospheric_explorer.api.data_interface.config_parser import ConfigMeta -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.data_interface.dataset_config_parser import ( + DatasetConfigParser, +) +from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.singleton import Singleton -logger = get_logger("atmexp") - -class GHGConfig(metaclass=ConfigMeta, filename="ghg/ghg_config.yaml"): +class GHGConfig(DatasetConfigParser, metaclass=Singleton): # pylint: disable=too-few-public-methods """This class is needed to implement a singleton pattern so that config is loaded only once.""" - @classmethod - def get_config(cls) -> dict: - """Function to get the actual config object.""" - logger.debug("Loading ghg config") - return cls().config + def __init__(self): + super().__init__(filename="ghg/ghg_config.yaml") @classmethod def get_var_names( @@ -50,7 +48,7 @@ def convert_units_array( ) )[0] conv_f = float(conf["conversion"]["conversion_factor"]) - logger.debug( + atm_exp_logger.debug( "Converting array %s to unit %s with factor %f", var_name, conf["conversion"]["convert_unit"], diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py b/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py index 7c9a0e0..5c9a782 100644 --- a/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py +++ b/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py @@ -5,11 +5,10 @@ # pylint: disable=too-many-arguments from __future__ import annotations -from atmospheric_explorer.api.data_interface.cams_interface import CAMSParameters -from atmospheric_explorer.api.loggers import get_logger from textwrap import dedent -logger = get_logger("atmexp") +from atmospheric_explorer.api.data_interface.cams_interface import CAMSParameters +from atmospheric_explorer.api.loggers import atm_exp_logger class GHGParameters(CAMSParameters): @@ -34,7 +33,8 @@ def __init__( self.version = version def __repr__(self) -> str: - return dedent(f"""\ + return dedent( + f"""\ data_variables: {self.data_variables} quantity: {self.quantity} input_observations: {self.input_observations} @@ -42,18 +42,21 @@ def __repr__(self) -> str: year: {self.year} month: {self.month} version: {self.version} - """) + """ + ) - def subset(self: GHGParameters, obj: GHGParameters) -> bool: - return ( - self.data_variables == obj.data_variables - and self.quantity == obj.quantity - and self.input_observations == obj.input_observations - and self.time_aggregation == obj.time_aggregation - and self.year.issubset(obj.year) - and self.month.issubset(obj.month) - and self.version == obj.version + def subset(self: GHGParameters, other: GHGParameters) -> bool: + res = ( + self.data_variables == other.data_variables + and self.quantity == other.quantity + and self.input_observations == other.input_observations + and self.time_aggregation == other.time_aggregation + and self.year.issubset(other.year) + and self.month.issubset(other.month) + and self.version == other.version ) + atm_exp_logger.debug("Subset result: %s\nself: %s\nother: %s", res, self, other) + return res def build_call_body(self: GHGParameters) -> dict: """Build the CDSAPI call body""" @@ -68,4 +71,5 @@ def build_call_body(self: GHGParameters) -> dict: "month": list(self.month), } ) + atm_exp_logger.debug("Call body for %s:\n%s", self, call_body) return call_body diff --git a/atmospheric_explorer/api/os_manager.py b/atmospheric_explorer/api/local_folder.py similarity index 56% rename from atmospheric_explorer/api/os_manager.py rename to atmospheric_explorer/api/local_folder.py index 5d21d7e..ef4462c 100644 --- a/atmospheric_explorer/api/os_manager.py +++ b/atmospheric_explorer/api/local_folder.py @@ -2,7 +2,6 @@ import os import platform -import shutil def get_local_folder(): @@ -11,17 +10,6 @@ def get_local_folder(): main_dir = os.path.join(os.getenv("LOCALAPPDATA") or ".", "AtmosphericExplorer") else: main_dir = os.path.join(os.getenv("HOME") or ".", ".atmospheric_explorer") - create_folder(main_dir) + if not os.path.exists(main_dir): + os.makedirs(main_dir) return main_dir - - -def create_folder(folder: str) -> None: - """Create folder if it doesn't exist.""" - if not os.path.exists(folder): - os.makedirs(folder) - - -def remove_folder(folder: str) -> None: - """Remove folder if it exists.""" - if os.path.exists(folder): - shutil.rmtree(folder) diff --git a/atmospheric_explorer/api/loggers/__init__.py b/atmospheric_explorer/api/loggers/__init__.py new file mode 100644 index 0000000..13ced50 --- /dev/null +++ b/atmospheric_explorer/api/loggers/__init__.py @@ -0,0 +1,4 @@ +# pylint: disable=missing-module-docstring +# ruff: noqa: F401 +from atmospheric_explorer.api.loggers.loggers import Logger, atm_exp_logger +from atmospheric_explorer.api.loggers.loggers_utils import clear_logs, list_logs diff --git a/atmospheric_explorer/api/loggers.py b/atmospheric_explorer/api/loggers/loggers.py similarity index 57% rename from atmospheric_explorer/api/loggers.py rename to atmospheric_explorer/api/loggers/loggers.py index 6ac769f..06d24bf 100644 --- a/atmospheric_explorer/api/loggers.py +++ b/atmospheric_explorer/api/loggers/loggers.py @@ -2,20 +2,14 @@ import logging import logging.config import os -from glob import glob -from atmospheric_explorer.api.os_manager import ( - create_folder, - get_local_folder, - remove_folder, -) +from atmospheric_explorer.api.local_folder import get_local_folder +from atmospheric_explorer.api.singleton import Singleton -class LoggersMeta(type): +class LoggerSingleton(Singleton): # pylint: disable=too-few-public-methods """This meta class is needed to implement a singleton pattern so that the logger config is loaded only once.""" - - _instances = {} logs_root_dir = os.path.join(get_local_folder(), "logs") logging_config = { "version": 1, @@ -48,41 +42,22 @@ class LoggersMeta(type): } def __init__(cls, *args, **kwargs): - """Initializes LoggersMeta instance.""" - create_folder(cls.logs_root_dir) + # pylint: disable = unused-argument + if not os.path.exists(cls.logs_root_dir): + os.makedirs(cls.logs_root_dir) logging.config.dictConfig(cls.logging_config) - super().__init__(*args, **kwargs) - - def __call__(cls, *args, **kwargs): - """Enables calling LoggersMeta instance like a function.""" - if cls not in cls._instances: - instance = super().__call__(*args, **kwargs) - cls._instances[cls] = instance - return cls._instances[cls] -class Loggers(metaclass=LoggersMeta): +class Logger(metaclass=LoggerSingleton): # pylint: disable=too-few-public-methods - """This class is needed to implement a singleton pattern so that the logger config is loaded only once.""" + """Class need to implement the singleton pattern for the logger configuration.""" @classmethod - def get_logger(cls, logger: str): + def get_logger(cls): """Function to get a logger.""" - return logging.getLogger(logger) - - @classmethod - def list_logs(cls) -> list: - """List all log files.""" - return glob(os.path.join(Loggers.logs_root_dir, "**"), recursive=True) - - @classmethod - def clear_logs(cls) -> list: - """Clear all log files.""" - remove_folder(Loggers.logs_root_dir) + return logging.getLogger("atmexp") # pylint: disable=unused-argument # ruff: noqa: F401 -def get_logger(logger: str): - """Function to get a logger.""" - return Loggers.get_logger(logger) +atm_exp_logger = Logger.get_logger() diff --git a/atmospheric_explorer/api/loggers/loggers_utils.py b/atmospheric_explorer/api/loggers/loggers_utils.py new file mode 100644 index 0000000..03f2596 --- /dev/null +++ b/atmospheric_explorer/api/loggers/loggers_utils.py @@ -0,0 +1,18 @@ +"""\ +Module to define the main logger and a few logging utils. +""" +import os +from glob import glob + +from atmospheric_explorer.api.loggers.loggers import Logger +from atmospheric_explorer.api.os_utils import remove_folder + + +def list_logs() -> list: + """List all log files.""" + return glob(os.path.join(Logger.logs_root_dir, "**"), recursive=True) + + +def clear_logs() -> list: + """Clear all log files.""" + remove_folder(Logger.logs_root_dir) diff --git a/atmospheric_explorer/api/os_utils.py b/atmospheric_explorer/api/os_utils.py new file mode 100644 index 0000000..89cad6f --- /dev/null +++ b/atmospheric_explorer/api/os_utils.py @@ -0,0 +1,22 @@ +"""\ +Module to gather all utility functions and classes. +""" + +import os +import shutil + +from atmospheric_explorer.api.loggers import atm_exp_logger + + +def create_folder(folder: str) -> None: + """Create folder if it doesn't exists""" + if not os.path.exists(folder): + atm_exp_logger.debug("Creating folder %s", folder) + os.makedirs(folder) + + +def remove_folder(folder: str) -> None: + """Remove folder if exists""" + if os.path.exists(folder): + atm_exp_logger.debug("Removing folder %s", folder) + shutil.rmtree(folder) diff --git a/atmospheric_explorer/api/plotting/anomalies.py b/atmospheric_explorer/api/plotting/anomalies.py index fcb2279..b8d08f7 100644 --- a/atmospheric_explorer/api/plotting/anomalies.py +++ b/atmospheric_explorer/api/plotting/anomalies.py @@ -12,12 +12,10 @@ split_time_dim, ) from atmospheric_explorer.api.data_interface.eac4 import EAC4Config, EAC4Instance -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.plot_utils import line_with_ci_subplots from atmospheric_explorer.api.shape_selection.shape_selection import Selection -logger = get_logger("atmexp") - def _eac4_anomalies_data( data_variable: str, @@ -63,7 +61,7 @@ def eac4_anomalies_plot( ) -> go.Figure: """Generate a monthly anomaly plot for a quantity from the Global Reanalysis EAC4 dataset.""" # pylint: disable=too-many-arguments - logger.debug( + atm_exp_logger.debug( dedent( """\ Function eac4_anomalies_plot called with arguments diff --git a/atmospheric_explorer/api/plotting/hovmoeller.py b/atmospheric_explorer/api/plotting/hovmoeller.py index 7254a47..56884e5 100644 --- a/atmospheric_explorer/api/plotting/hovmoeller.py +++ b/atmospheric_explorer/api/plotting/hovmoeller.py @@ -11,12 +11,10 @@ shifting_long, ) from atmospheric_explorer.api.data_interface.eac4 import EAC4Config, EAC4Instance -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.plot_utils import hovmoeller_plot from atmospheric_explorer.api.shape_selection.shape_selection import Selection -logger = get_logger("atmexp") - def _eac4_hovmoeller_data( data_variable: str, @@ -74,7 +72,7 @@ def eac4_hovmoeller_plot( # pylint: disable=too-many-arguments # pylint: disable=too-many-locals # pylint: disable=dangerous-default-value - logger.debug( + atm_exp_logger.debug( dedent( """\ Function eac4_hovmoeller_levels_plot called with arguments diff --git a/atmospheric_explorer/api/plotting/plot_utils.py b/atmospheric_explorer/api/plotting/plot_utils.py index 7a15944..a40094e 100644 --- a/atmospheric_explorer/api/plotting/plot_utils.py +++ b/atmospheric_explorer/api/plotting/plot_utils.py @@ -10,9 +10,7 @@ import plotly.graph_objects as go import xarray as xr -from atmospheric_explorer.api.loggers import get_logger - -logger = get_logger("atmexp") +from atmospheric_explorer.api.loggers import atm_exp_logger def _base_height(n_plots): @@ -199,12 +197,12 @@ def sequential_colorscale_bar( tickvals = [ np.mean(separators[k : k + 2]) for k in range(len(separators) - 1) ] # position of tick text - logger.debug("Separators for colorbar: %s", separators) + atm_exp_logger.debug("Separators for colorbar: %s", separators) if (separators.max() - separators.min()) < (len(separators) - 1): n_decimals = int( round(1 - log10(separators.max() / (len(separators) - 1)), 0) ) # number of decimals needed to distinguish color levels - logger.debug("Colorbar decimals: %i", n_decimals) + atm_exp_logger.debug("Colorbar decimals: %i", n_decimals) ticktext = ( [f"<{separators[1]:.{n_decimals}f}"] + [ @@ -214,7 +212,7 @@ def sequential_colorscale_bar( + [f">{separators[-2]:.{n_decimals}f}"] ) else: - logger.debug("Colorbar decimals: 0") + atm_exp_logger.debug("Colorbar decimals: 0") ticktext = ( [f"<{separators[1]:.0f}"] + [ diff --git a/atmospheric_explorer/api/plotting/yearly_flux.py b/atmospheric_explorer/api/plotting/yearly_flux.py index d051990..b6ee0ed 100644 --- a/atmospheric_explorer/api/plotting/yearly_flux.py +++ b/atmospheric_explorer/api/plotting/yearly_flux.py @@ -14,12 +14,10 @@ GHGConfig, InversionOptimisedGreenhouseGas, ) -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.plot_utils import line_with_ci_subplots from atmospheric_explorer.api.shape_selection.shape_selection import Selection -logger = get_logger("atmexp") - def _ghg_flux_over_full_area(dataset: xr.Dataset, var_name: str): dataset[var_name] = dataset[var_name] * dataset["area"] @@ -40,7 +38,7 @@ def _ghg_surface_satellite_yearly_data( # pylint: disable=too-many-arguments # pylint: disable=invalid-name # Download surface data file - logger.debug( + atm_exp_logger.debug( dedent( """\ _ghg_surface_satellite_yearly_data called with arguments @@ -162,7 +160,7 @@ def ghg_surface_satellite_yearly_plot( # pylint: disable=too-many-arguments # pylint: disable=too-many-locals # pylint: disable=invalid-name - logger.debug( + atm_exp_logger.debug( dedent( f"""\ ghg_surface_satellite_yearly_plot called with arguments diff --git a/atmospheric_explorer/api/shape_selection/shape_selection.py b/atmospheric_explorer/api/shape_selection/shape_selection.py index cd503a3..dfe3bec 100644 --- a/atmospheric_explorer/api/shape_selection/shape_selection.py +++ b/atmospheric_explorer/api/shape_selection/shape_selection.py @@ -9,7 +9,7 @@ from shapely.ops import unary_union from atmospheric_explorer.api.config import CRS -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.shape_selection.config import ( SelectionLevel, map_level_shapefile_mapping, @@ -19,8 +19,6 @@ dissolve_shapefile_level, ) -logger = get_logger("atmexp") - def selection_empty(func): """Decorator to add a check for an empty selection.""" @@ -88,7 +86,7 @@ def __init__(self, dataframe: gpd.GeoDataFrame | None = None): super().__init__(dataframe, None) if dataframe is not None: self.level = SelectionLevel.GENERIC - logger.debug( + atm_exp_logger.debug( """\ Created GenericShapeSelection with dataframe %s """, @@ -129,7 +127,7 @@ def __init__( super().__init__(dataframe, level) if self.level == SelectionLevel.GENERIC: raise ValueError("EntitySelection cannot have level SelectionLevel.GENERIC") - logger.debug( + atm_exp_logger.debug( """\ Created EntitySelection with dataframe %s and level %s """, diff --git a/atmospheric_explorer/api/shape_selection/shapefile.py b/atmospheric_explorer/api/shape_selection/shapefile.py index c98f4d6..4be4e4d 100644 --- a/atmospheric_explorer/api/shape_selection/shapefile.py +++ b/atmospheric_explorer/api/shape_selection/shapefile.py @@ -14,21 +14,23 @@ import requests.utils from atmospheric_explorer.api.cache import Cached, Parameters -from atmospheric_explorer.api.loggers import get_logger -from atmospheric_explorer.api.os_manager import create_folder, get_local_folder +from atmospheric_explorer.api.local_folder import get_local_folder +from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.os_utils import create_folder from atmospheric_explorer.api.shape_selection.config import map_level_shapefile_mapping -logger = get_logger("atmexp") - class ShapefileParameters(Parameters): + # pylint: disable = too-few-public-methods + """Parameters for the Natural Earth Data shapefile.""" + def __init__( self: ShapefileParameters, resolution: str = "50m", map_type: str = "cultural", info_type: str = "admin", depth: int = 0, - instance: str = "map_subunits" + instance: str = "map_subunits", ): # pylint: disable=too-many-arguments self.resolution = resolution self.map_type = map_type @@ -36,18 +38,16 @@ def __init__( self.depth = depth self.instance = instance - def subset( - self: ShapefileParameters, - obj: ShapefileParameters - ) -> bool: - # pylint: disable=too-many-arguments - return ( - self.resolution == obj.resolution - and self.map_type == obj.map_type - and self.info_type == obj.info_type - and self.depth == obj.depth - and self.instance == obj.instance + def subset(self: ShapefileParameters, other: ShapefileParameters) -> bool: + res = ( + self.resolution == other.resolution + and self.map_type == other.map_type + and self.info_type == other.info_type + and self.depth == other.depth + and self.instance == other.instance ) + atm_exp_logger.debug("Subset result: %s\nself: %s\nother: %s", res, self, other) + return res class ShapefilesDownloader(Cached): @@ -85,13 +85,13 @@ def __new__( instance: str = "map_subunits", timeout: int = 10, dst_dir: str | None = None, - ): + ): # pylint: disable=too-many-arguments params = ShapefileParameters( resolution=resolution, map_type=map_type, info_type=info_type, depth=depth, - instance=instance + instance=instance, ) return Cached.__new__(ShapefilesDownloader, params) @@ -126,7 +126,7 @@ def __init__( map_type=map_type, info_type=info_type, depth=depth, - instance=instance + instance=instance, ) self.timeout = timeout self.dst_dir = ( @@ -136,8 +136,8 @@ def __init__( ) self._downloaded = False create_folder(self.dst_dir) - logger.info("Created folder %s to save shapefiles", self.dst_dir) - logger.debug( + atm_exp_logger.info("Created folder %s to save shapefiles", self.dst_dir) + atm_exp_logger.debug( dedent( """\ Created ShapefilesDownloader object with attributes @@ -164,7 +164,7 @@ def shapefile_name(self: ShapefilesDownloader) -> str: """Shapefile name""" if self.parameters.map_type == "physical": return f"ne_{self.parameters.resolution}_{self.parameters.info_type}" - return f"ne_{self.parameters.resolution}_{self.parameters.info_type}_{self.parameters.depth}_{self.parameters.instance}" + return f"ne_{self.parameters.resolution}_{self.parameters.info_type}_{self.parameters.depth}_{self.parameters.instance}" # noqa: E501 @property def shapefile_dir(self: ShapefilesDownloader) -> str: @@ -180,17 +180,17 @@ def shapefile_full_path(self: ShapefilesDownloader) -> str: def shapefile_url(self: ShapefilesDownloader) -> str: """Shapefile download url.""" # pylint: disable=line-too-long - return f"{self._BASE_URL}/{self.parameters.resolution}/{self.parameters.map_type}/{self.shapefile_name}.zip" # noqa: E501 + return f"{self._BASE_URL}/{self.parameters.resolution}/{self.parameters.map_type}/{self.shapefile_name}.zip" def _download_shapefile(self: ShapefilesDownloader) -> bytes: - """Downloads shapefiles and returns their content in bytes.""" - logger.info("Downloading shapefiles from %s", self.shapefile_url) + """Download shapefiles and returns their content in bytes.""" + atm_exp_logger.info("Downloading shapefiles from %s", self.shapefile_url) try: response = requests.get( self.shapefile_url, headers=self._HEADERS, timeout=self.timeout ) except requests.exceptions.Timeout as err: - logger.error("Shapefile download timed out.\n%s", err) + atm_exp_logger.error("Shapefile download timed out.\n%s", err) raise requests.exceptions.Timeout( dedent( f"""\ @@ -202,7 +202,7 @@ def _download_shapefile(self: ShapefilesDownloader) -> bytes: ) if response.status_code != 200: - logger.error( + atm_exp_logger.error( "Failed to download shapefile, a wrong URL has been provided.\n%s", response.text, ) @@ -215,7 +215,7 @@ def _download_shapefile_to_zip(self: ShapefilesDownloader) -> None: """Saves shapefiles to zip file.""" with open(self.shapefile_dir + ".zip", "wb") as file_zip: file_zip.write(self._download_shapefile()) - logger.info("Shapefiles saved into file %s", file_zip.name) + atm_exp_logger.info("Shapefiles saved into file %s", file_zip.name) def _extract_to_folder(self: ShapefilesDownloader): """Extracts shapefile zip to directory. @@ -226,10 +226,10 @@ def _extract_to_folder(self: ShapefilesDownloader): filepath = self.shapefile_dir + ".zip" with zipfile.ZipFile(filepath, "r") as zip_ref: zip_ref.extractall(self.shapefile_dir) - logger.info("Shapefile extracted to %s", self.shapefile_dir) + atm_exp_logger.info("Shapefile extracted to %s", self.shapefile_dir) # Remove zip file os.remove(filepath) - logger.info("Removed file %s", filepath) + atm_exp_logger.info("Removed file %s", filepath) def download(self: ShapefilesDownloader) -> None: """Downloads and extracts shapefiles.""" @@ -239,31 +239,27 @@ def download(self: ShapefilesDownloader) -> None: def _read_as_dataframe(self: ShapefilesDownloader) -> gpd.GeoDataFrame: """Returns shapefile as geopandas dataframe.""" - logger.debug("Reading %s as dataframe", self.shapefile_full_path) + atm_exp_logger.debug("Reading %s as dataframe", self.shapefile_full_path) return gpd.read_file(self.shapefile_full_path) def get_as_dataframe(self: ShapefilesDownloader) -> gpd.GeoDataFrame: """Returns shapefile as geopandas dataframe, also downloads shapefile if needed.""" if not self._downloaded: - logger.info( + atm_exp_logger.info( "Shapefile not downloaded, downloading it from Natural Earth Data" ) self.download() else: - logger.info("Shapefile already downloaded in %s", self.shapefile_full_path) + atm_exp_logger.info( + "Shapefile already downloaded in %s", self.shapefile_full_path + ) return self._read_as_dataframe() def dissolve_shapefile_level(level: str) -> gpd.GeoDataFrame: - """Gets shapefile and dissolves it on a selection level.""" - logger.debug("Dissolve shapefile to level %s", level) + """Gets shapefile and dissolves it on a selection level""" + atm_exp_logger.debug("Dissolve shapefile to level %s", level) col = map_level_shapefile_mapping[level] sh_df = ShapefilesDownloader(instance="map_subunits").get_as_dataframe() sh_df = sh_df[[col, "geometry"]].rename({col: "label"}, axis=1) return sh_df.dissolve(by="label").reset_index() - - -if __name__ == "__main__": - sh = ShapefilesDownloader() - sh2 = ShapefilesDownloader() - print(id(sh) == id(sh2)) diff --git a/atmospheric_explorer/api/singleton.py b/atmospheric_explorer/api/singleton.py new file mode 100644 index 0000000..5248282 --- /dev/null +++ b/atmospheric_explorer/api/singleton.py @@ -0,0 +1,23 @@ +"""\ +Module to manage constants. + +This module defines a Singleton, in this way the file constants.cfg is loaded only once. +The singleton pattern was taken from here +https://refactoring.guru/design-patterns/singleton/python/example#example-0 +""" +# pylint: disable=no-else-return +# pylint: disable=missing-function-docstring +from __future__ import annotations + + +class Singleton(type): + # pylint: disable=too-few-public-methods + """This meta class is needed to implement a singleton pattern.""" + + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + instance = super().__call__(*args, **kwargs) + cls._instances[cls] = instance + return cls._instances[cls] diff --git a/atmospheric_explorer/cli/logs.py b/atmospheric_explorer/cli/logs.py index 41eabe8..2086630 100644 --- a/atmospheric_explorer/cli/logs.py +++ b/atmospheric_explorer/cli/logs.py @@ -5,7 +5,7 @@ import click -from atmospheric_explorer.api.loggers import Loggers +from atmospheric_explorer.api.loggers import clear_logs, list_logs @click.group() @@ -18,10 +18,10 @@ def logs(): @logs.command("list") def _(): """List all logs""" - pprint(Loggers.list_logs()) + pprint(list_logs()) @logs.command("clear") def _(): """Clear all logs""" - Loggers.clear_logs() + clear_logs() diff --git a/atmospheric_explorer/cli/plotting/anomalies.py b/atmospheric_explorer/cli/plotting/anomalies.py index 55ed38c..92df2fe 100644 --- a/atmospheric_explorer/cli/plotting/anomalies.py +++ b/atmospheric_explorer/cli/plotting/anomalies.py @@ -6,14 +6,12 @@ import click from atmospheric_explorer.api.data_interface.eac4.eac4_config import EAC4Config -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.anomalies import eac4_anomalies_plot from atmospheric_explorer.api.shape_selection.config import SelectionLevel from atmospheric_explorer.api.shape_selection.shape_selection import EntitySelection from atmospheric_explorer.cli.plotting.utils import comma_separated_list -logger = get_logger("atmexp") - @click.command() @click.option("--data-variable", "-v", required=True, type=str, help="Data variable") @@ -111,7 +109,7 @@ def anomalies( ) entities = EntitySelection.from_entities_list(entities, level=selection_level) var_name = EAC4Config.get_config()["variables"][data_variable]["var_name"] - logger.debug( + atm_exp_logger.debug( dedent( """\ Called anomalies CLI with parameters @@ -142,7 +140,7 @@ def anomalies( height, scale, ) - logger.debug("Selected variable %s", var_name) + atm_exp_logger.debug("Selected variable %s", var_name) fig = eac4_anomalies_plot( data_variable=data_variable, var_name=var_name, diff --git a/atmospheric_explorer/cli/plotting/hovmoeller.py b/atmospheric_explorer/cli/plotting/hovmoeller.py index 77071f8..74dcaf8 100644 --- a/atmospheric_explorer/cli/plotting/hovmoeller.py +++ b/atmospheric_explorer/cli/plotting/hovmoeller.py @@ -6,14 +6,12 @@ import click from atmospheric_explorer.api.data_interface.eac4.eac4_config import EAC4Config -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.hovmoeller import eac4_hovmoeller_plot from atmospheric_explorer.api.shape_selection.config import SelectionLevel from atmospheric_explorer.api.shape_selection.shape_selection import EntitySelection from atmospheric_explorer.cli.plotting.utils import comma_separated_list -logger = get_logger("atmexp") - @click.command() @click.option("--data-variable", "-v", required=True, type=str, help="Data variable") @@ -126,7 +124,7 @@ def hovmoeller( raise ValueError("Cannot provide both pressure_levels and model_levels") entities = EntitySelection.from_entities_list(entities, level=selection_level) var_name = EAC4Config.get_config()["variables"][data_variable]["var_name"] - logger.debug( + atm_exp_logger.debug( dedent( """\ Called hovmoeller CLI with parameters @@ -159,7 +157,7 @@ def hovmoeller( height, scale, ) - logger.debug("Selected variable %s", var_name) + atm_exp_logger.debug("Selected variable %s", var_name) fig = eac4_hovmoeller_plot( data_variable=data_variable, var_name=var_name, diff --git a/atmospheric_explorer/cli/plotting/yearly_flux.py b/atmospheric_explorer/cli/plotting/yearly_flux.py index 26cc86f..0cb77c0 100644 --- a/atmospheric_explorer/cli/plotting/yearly_flux.py +++ b/atmospheric_explorer/cli/plotting/yearly_flux.py @@ -6,7 +6,7 @@ import click from atmospheric_explorer.api.data_interface.ghg.ghg_config import GHGConfig -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.yearly_flux import ( ghg_surface_satellite_yearly_plot, ) @@ -14,8 +14,6 @@ from atmospheric_explorer.api.shape_selection.shape_selection import EntitySelection from atmospheric_explorer.cli.plotting.utils import comma_separated_list -logger = get_logger("atmexp") - def command_change_options(): """Function needed to change behaviour of the click option var_name.""" @@ -131,7 +129,7 @@ def yearly_flux( ) ) entities = EntitySelection.from_entities_list(entities, level=selection_level) - logger.debug( + atm_exp_logger.debug( dedent( """\ Called yearly flux CLI with parameters diff --git a/atmospheric_explorer/ui/Home.py b/atmospheric_explorer/ui/Home.py index f2d3840..b4207f0 100644 --- a/atmospheric_explorer/ui/Home.py +++ b/atmospheric_explorer/ui/Home.py @@ -1,11 +1,9 @@ -"""\ -Main UI page -""" +"""Main UI page.""" # pylint: disable=invalid-name import streamlit as st -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.shape_selection.config import ( SelectionLevel, organizations, @@ -19,8 +17,6 @@ from atmospheric_explorer.ui.session_state import GeneralSessionStateKeys from atmospheric_explorer.ui.utils import build_sidebar, page_init -logger = get_logger("atmexp") - def _init(): page_init() @@ -41,7 +37,7 @@ def _init(): def _selectors_org(): - logger.debug("Setting organization selectors") + atm_exp_logger.debug("Setting organization selectors") st.session_state[GeneralSessionStateKeys.SELECTED_ORGANIZATION] = st.selectbox( st.session_state["map_level_helper"], options=organizations.keys(), @@ -58,7 +54,7 @@ def _selectors_org(): def _selectors_no_org(): - logger.debug("Setting non-organization selectors") + atm_exp_logger.debug("Setting non-organization selectors") if st.session_state["used_form"]: st.session_state[ GeneralSessionStateKeys.SELECTED_SHAPES @@ -86,9 +82,9 @@ def selectors(): def _used_form(): st.session_state["used_form"] = True - logger.debug("Set used_form in session state to True") + atm_exp_logger.debug("Set used_form in session state to True") - logger.debug("Setting map selectors form") + atm_exp_logger.debug("Setting map selectors form") with st.form("selectors"): convert = ( st.session_state[GeneralSessionStateKeys.SELECT_ENTITIES] @@ -119,7 +115,7 @@ def _used_form(): """, ) if convert: - logger.debug("Converting previous selection to EntitySelection") + atm_exp_logger.debug("Converting previous selection to EntitySelection") st.session_state[ GeneralSessionStateKeys.SELECTED_SHAPES ] = EntitySelection.convert_selection( @@ -148,7 +144,7 @@ def _used_form(): def page(): _init() - logger.info("Starting streamlit app") + atm_exp_logger.info("Starting streamlit app") progress_bar = st.progress(0.0, "Starting app") st.title("Atmospheric Explorer") st.subheader("Geographical selection") diff --git a/atmospheric_explorer/ui/interactive_map/interactive_map.py b/atmospheric_explorer/ui/interactive_map/interactive_map.py index 24342c8..6c7a556 100644 --- a/atmospheric_explorer/ui/interactive_map/interactive_map.py +++ b/atmospheric_explorer/ui/interactive_map/interactive_map.py @@ -7,7 +7,7 @@ from folium.plugins import Draw from streamlit_folium import st_folium -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.shape_selection.shape_selection import ( EntitySelection, GenericShapeSelection, @@ -16,8 +16,6 @@ from atmospheric_explorer.api.shape_selection.shapefile import dissolve_shapefile_level from atmospheric_explorer.ui.session_state import GeneralSessionStateKeys -logger = get_logger("atmexp") - def _country_hover_style(_): """Style used for all countries, needed for caching""" @@ -31,10 +29,8 @@ def _selected_shapes_style(_) -> dict: @st.cache_data(show_spinner="Fetching world polygon...") def world_polygon(level: str) -> folium.GeoJson: - """\ - Return a folium.GeoJson object that adds colored polygons over all continents/countries in the interactive map.\ - """ - logger.info("Building world polygons") + """Returns a folium.GeoJson object that adds colored polygons over all entities in the level.""" + atm_exp_logger.info("Fetch world polygon") return folium.GeoJson( dissolve_shapefile_level(level), name="world_polygon", @@ -46,10 +42,8 @@ def world_polygon(level: str) -> folium.GeoJson: # @st.cache_data(show_spinner="Fetching selected state polygon...") def selected_entities_fgroup() -> folium.FeatureGroup: - """\ - Return a folium.FeatureGroup that adds a colored polygon over the selected entities. - """ - logger.info("Building selected entities polygons") + """Returns a folium.FeatureGroup that adds a colored polygon over the selected entities.""" + atm_exp_logger.info("Building selected entities polygons") countries_feature_group = folium.FeatureGroup(name="Countries") countries_feature_group.add_child( folium.GeoJson( @@ -62,11 +56,10 @@ def selected_entities_fgroup() -> folium.FeatureGroup: return countries_feature_group -def build_folium_map() -> folium.Map: - """\ - Build the interactive folium map with (if enabled) a layer of polygons on entities.\ - """ - logger.info("Building folium map") +# @st.cache_resource(show_spinner="Fetching map...") +def build_folium_map(): + """Build the interactive folium map with (if enabled) a layer of polygons on entities.""" + atm_exp_logger.info("Build folium map") folium_map = folium.Map() Draw( draw_options={ @@ -87,12 +80,12 @@ def build_folium_map() -> folium.Map: return folium_map -def show_folium_map() -> dict: - """\ - Render the folium map inside Streamlit. - Returns the output event generated when clicking or drawing shapes on the map.\ +def show_folium_map(): + """Render the folium map inside Streamlit. + + Returns the output event generated when clicking or drawing shapes on the map. """ - logger.info("Rendering folium map") + atm_exp_logger.info("Show folium map") folium_map = build_folium_map() out_event = st_folium( folium_map, @@ -106,9 +99,9 @@ def show_folium_map() -> dict: return out_event -def update_session_map_click(out_event): - """\ - When clicking or drawing on the map, an output event is generated and passed as an argument to this function. +def update_session_map_click(out_event) -> None: + """When clicking or drawing on the map, an output event is generated and passed as an argument to this function. + This function takes care of parsing the event and selecting the clicked entity or, if entities are not enabled, the generic shape.\ """ @@ -116,17 +109,17 @@ def update_session_map_click(out_event): out_event.get("last_object_clicked") != st.session_state[GeneralSessionStateKeys.LAST_OBJECT_CLICKED] ): - logger.debug("Updating last object clicked in session state") + atm_exp_logger.debug("Updating LAST_OBJECT_CLICKED session state") st.session_state[GeneralSessionStateKeys.LAST_OBJECT_CLICKED] = out_event[ "last_object_clicked" ] if out_event.get("last_active_drawing") is not None: - logger.debug("Updating last selected shape in session state") + atm_exp_logger.debug("Updating SELECT_ENTITIES session state") sel = from_out_event( out_event, level=st.session_state[GeneralSessionStateKeys.MAP_LEVEL] ) if st.session_state[GeneralSessionStateKeys.SELECT_ENTITIES]: - logger.debug("Last selected shape is an entity") + atm_exp_logger.debug("Last selected shape is an entity") selected_countries = EntitySelection.convert_selection( shape_selection=sel, level=st.session_state[GeneralSessionStateKeys.MAP_LEVEL], @@ -143,7 +136,7 @@ def update_session_map_click(out_event): ] = selected_countries st.experimental_rerun() else: - logger.debug("Last selected shape is a generic shape") + atm_exp_logger.debug("Last selected shape is a generic shape") selected_shape = GenericShapeSelection.convert_selection(sel) prev_selection = st.session_state[GeneralSessionStateKeys.SELECTED_SHAPES] if selected_shape != prev_selection: diff --git a/atmospheric_explorer/ui/pages/Anomalies Plot.py b/atmospheric_explorer/ui/pages/Anomalies Plot.py index 5e2ca68..d6b033e 100644 --- a/atmospheric_explorer/ui/pages/Anomalies Plot.py +++ b/atmospheric_explorer/ui/pages/Anomalies Plot.py @@ -7,7 +7,7 @@ import streamlit as st -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.anomalies import eac4_anomalies_plot from atmospheric_explorer.ui.session_state import ( EAC4AnomaliesSessionStateKeys, @@ -21,8 +21,6 @@ ) from atmospheric_explorer.ui.utils import build_sidebar, page_init -logger = get_logger("atmexp") - def _init(): page_init() @@ -57,7 +55,7 @@ def _init(): def _dates_selectors(): - logger.debug("Setting dates selectors") + atm_exp_logger.debug("Setting dates selectors") start_date_col, end_date_col, _ = st.columns([1, 1, 3]) start_date_col.date_input( label="Start date", @@ -86,7 +84,7 @@ def _dates_selectors(): def _times_selector(): - logger.debug("Setting time selector") + atm_exp_logger.debug("Setting time selector") st.multiselect( label="Times", options=eac4_times, @@ -95,11 +93,11 @@ def _times_selector(): def _selectors(): - logger.info("Adding selection expander") + atm_exp_logger.info("Adding selection expander") with st.expander("Selection", expanded=True): _dates_selectors() _times_selector() - logger.debug("Setting data variable and var name selectors") + atm_exp_logger.debug("Setting data variable and var name selectors") st.selectbox( label="Data variable", options=eac4_sl_data_variables, @@ -109,7 +107,7 @@ def _selectors(): st.session_state[EAC4AnomaliesSessionStateKeys.EAC4_ANOMALIES_DATA_VARIABLE] ] st.text(f"Var name: {v_name}") - logger.debug("Setting title input") + atm_exp_logger.debug("Setting title input") title = st.text_input( label="Plot title", value=eac4_sl_data_variable_default_plot_title_mapping[ @@ -126,7 +124,7 @@ def page(): var_name, plot_title = _selectors() build_sidebar() if st.button("Generate plot"): - logger.info("Generating plot") + atm_exp_logger.info("Generating plot") start_date = st.session_state[ EAC4AnomaliesSessionStateKeys.EAC4_ANOMALIES_START_DATE ] diff --git a/atmospheric_explorer/ui/pages/Hovmoeller Plot.py b/atmospheric_explorer/ui/pages/Hovmoeller Plot.py index 2107d0b..37e27a2 100644 --- a/atmospheric_explorer/ui/pages/Hovmoeller Plot.py +++ b/atmospheric_explorer/ui/pages/Hovmoeller Plot.py @@ -6,7 +6,7 @@ import streamlit as st -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.hovmoeller import eac4_hovmoeller_plot from atmospheric_explorer.ui.session_state import ( GeneralSessionStateKeys, @@ -25,8 +25,6 @@ ) from atmospheric_explorer.ui.utils import build_sidebar, page_init -logger = get_logger("atmexp") - def _init(): page_init() @@ -48,14 +46,14 @@ def _init(): def _year_selector(): - logger.debug("Setting year selector") + atm_exp_logger.debug("Setting year selector") start_date_col, end_date_col, _ = st.columns([1, 1, 3]) start_date_col.date_input("Start date", key=HovmSessionStateKeys.HOVM_START_DATE) end_date_col.date_input("End date", key=HovmSessionStateKeys.HOVM_END_DATE) def _times_selector(): - logger.debug("Setting times selector") + atm_exp_logger.debug("Setting times selector") st.selectbox( label="Time", options=eac4_times, @@ -64,7 +62,7 @@ def _times_selector(): def _y_axis_selector(): - logger.debug("Setting y axis selector") + atm_exp_logger.debug("Setting y axis selector") st.radio( "Vertical axis", ["Latitude", "Pressure Level", "Model Level"], @@ -77,14 +75,14 @@ def _y_axis_selector(): case "Latitude": pass case "Pressure Level": - logger.debug("Setting pressure level selector") + atm_exp_logger.debug("Setting pressure level selector") st.multiselect( label="Pressure Level", options=eac4_pressure_levels, key=HovmSessionStateKeys.HOVM_P_LEVELS, ) case "Model Level": - logger.debug("Setting model level selector") + atm_exp_logger.debug("Setting model level selector") st.multiselect( label="Model Level", options=eac4_model_levels, @@ -93,7 +91,7 @@ def _y_axis_selector(): def _var_selectors(): - logger.debug("Setting data variable and var name selectors") + atm_exp_logger.debug("Setting data variable and var name selectors") if st.session_state[HovmSessionStateKeys.HOVM_YAXIS] == "Latitude": all_vars = eac4_sl_data_variables vars_mapping = eac4_sl_data_variable_var_name_mapping @@ -109,7 +107,7 @@ def _var_selectors(): ) v_name = vars_mapping[st.session_state[HovmSessionStateKeys.HOVM_DATA_VARIABLE]] st.text(f"Var name: {v_name}") - logger.debug("Setting title input") + atm_exp_logger.debug("Setting title input") title = st.text_input( label="Plot title", value=title_mapping[st.session_state[HovmSessionStateKeys.HOVM_DATA_VARIABLE]], @@ -118,7 +116,7 @@ def _var_selectors(): def _selectors(): - logger.info("Adding selection expander") + atm_exp_logger.info("Adding selection expander") with st.expander("Selection", expanded=True): _year_selector() _times_selector() @@ -131,7 +129,7 @@ def page(): var_name, plot_title = _selectors() build_sidebar() if st.button("Generate plot"): - logger.info("Generating plot") + atm_exp_logger.info("Generating plot") y_axis = st.session_state[HovmSessionStateKeys.HOVM_YAXIS] start_date = st.session_state[HovmSessionStateKeys.HOVM_START_DATE] end_date = st.session_state[HovmSessionStateKeys.HOVM_END_DATE] @@ -145,7 +143,7 @@ def page(): with st.spinner("Downloading data and building plot"): match y_axis: case "Latitude": - logger.debug("Generating Latitude plot") + atm_exp_logger.debug("Generating Latitude plot") st.plotly_chart( eac4_hovmoeller_plot( data_variable=data_variable, @@ -158,7 +156,7 @@ def page(): use_container_width=True, ) case "Pressure Level": - logger.debug("Generating Pressure Level plot") + atm_exp_logger.debug("Generating Pressure Level plot") levels = st.session_state[HovmSessionStateKeys.HOVM_P_LEVELS] if levels: st.plotly_chart( @@ -174,7 +172,7 @@ def page(): use_container_width=True, ) case "Model Level": - logger.debug("Generating Model Level plot") + atm_exp_logger.debug("Generating Model Level plot") levels = st.session_state[HovmSessionStateKeys.HOVM_M_LEVELS] if levels: st.plotly_chart( diff --git a/atmospheric_explorer/ui/pages/Yearly Surface Plot.py b/atmospheric_explorer/ui/pages/Yearly Surface Plot.py index 083862a..d49ed88 100644 --- a/atmospheric_explorer/ui/pages/Yearly Surface Plot.py +++ b/atmospheric_explorer/ui/pages/Yearly Surface Plot.py @@ -6,7 +6,7 @@ import streamlit as st -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.yearly_flux import ( ghg_surface_satellite_yearly_plot, ) @@ -21,8 +21,6 @@ ) from atmospheric_explorer.ui.utils import build_sidebar, page_init -logger = get_logger("atmexp") - def _init(): page_init() @@ -43,7 +41,7 @@ def _init(): def _year_selectors(): - logger.debug("Setting years selector") + atm_exp_logger.debug("Setting years selector") start_year_col, end_year_col, _ = st.columns([1, 1, 3]) start_year_col.number_input( "Start year", key=GHGSessionStateKeys.GHG_START_YEAR, step=1 @@ -52,7 +50,7 @@ def _year_selectors(): def _months_selectors(): - logger.debug("Setting months selector") + atm_exp_logger.debug("Setting months selector") st.checkbox(label="All Months", key=GHGSessionStateKeys.GHG_ALL_MONTHS) if not st.session_state[GHGSessionStateKeys.GHG_ALL_MONTHS]: st.multiselect( @@ -67,14 +65,14 @@ def _months_selectors(): def _vars_selectors(): - logger.debug("Setting data variable selector") + atm_exp_logger.debug("Setting data variable selector") st.selectbox( label="Data variable", options=ghg_data_variables, key=GHGSessionStateKeys.GHG_DATA_VARIABLE, ) if st.session_state[GHGSessionStateKeys.GHG_DATA_VARIABLE] == "carbon_dioxide": - logger.debug("Setting satellite selector") + atm_exp_logger.debug("Setting satellite selector") st.checkbox( label="Include satellite observations", key=GHGSessionStateKeys.GHG_ADD_SATELLITE, @@ -87,7 +85,7 @@ def _vars_selectors(): def _selectors(): - logger.info("Adding selection expander") + atm_exp_logger.info("Adding selection expander") with st.expander("Selection", expanded=True): _year_selectors() _months_selectors() @@ -101,7 +99,7 @@ def _selectors(): help="Select var_name inside dataset", ) idx = var_names.index(v_name) - logger.debug("Setting title input") + atm_exp_logger.debug("Setting title input") title = st.text_input( "Plot title", value=ghg_data_variable_default_plot_title_mapping[ @@ -116,7 +114,7 @@ def page(): var_name, plot_title = _selectors() build_sidebar() if st.button("Generate plot"): - logger.info("Generating plot") + atm_exp_logger.info("Generating plot") years = [ str(y) for y in range( diff --git a/atmospheric_explorer/ui/utils.py b/atmospheric_explorer/ui/utils.py index 8516b24..83dcd40 100644 --- a/atmospheric_explorer/ui/utils.py +++ b/atmospheric_explorer/ui/utils.py @@ -5,13 +5,11 @@ import streamlit as st -from atmospheric_explorer.api.loggers import get_logger +from atmospheric_explorer.api.loggers import atm_exp_logger from atmospheric_explorer.api.shape_selection.config import SelectionLevel from atmospheric_explorer.api.shape_selection.shape_selection import EntitySelection from atmospheric_explorer.ui.session_state import GeneralSessionStateKeys -logger = get_logger("atmexp") - def local_css(filename: str | Path) -> None: """Load local css""" @@ -39,7 +37,7 @@ def page_init(): def build_sidebar(): """Build sidebar""" - logger.info("Building sidebar") + atm_exp_logger.info("Building sidebar") level_name = st.session_state[GeneralSessionStateKeys.MAP_LEVEL] with st.sidebar: if not st.session_state[GeneralSessionStateKeys.SELECTED_SHAPES].empty(): diff --git a/tests/api/data_interface/test_config_parser.py b/tests/api/data_interface/test_dataset_config_parser.py similarity index 88% rename from tests/api/data_interface/test_config_parser.py rename to tests/api/data_interface/test_dataset_config_parser.py index c0595fe..2745274 100644 --- a/tests/api/data_interface/test_config_parser.py +++ b/tests/api/data_interface/test_dataset_config_parser.py @@ -5,7 +5,9 @@ # pylint: disable=unused-argument import pytest -from atmospheric_explorer.api.data_interface.config_parser import OperationParser +from atmospheric_explorer.api.data_interface.dataset_config_parser import ( + OperationParser, +) from atmospheric_explorer.api.exceptions import OperationNotAllowed diff --git a/tests/api/data_interface/test_singleton.py b/tests/api/data_interface/test_singleton.py new file mode 100644 index 0000000..a52baed --- /dev/null +++ b/tests/api/data_interface/test_singleton.py @@ -0,0 +1,17 @@ +# pylint: disable=missing-module-docstring +# pylint: disable=missing-class-docstring +# pylint: disable=missing-function-docstring +# pylint: disable=protected-access +# pylint: disable=unused-argument +# pylint: disable=too-few-public-methods +from atmospheric_explorer.api.singleton import Singleton + + +class SingletonConcrete(metaclass=Singleton): + pass + + +def test_singleton(): + obj1 = SingletonConcrete() + obj2 = SingletonConcrete() + assert id(obj1) == id(obj2) diff --git a/tests/api/shape_selection/test_shapefile.py b/tests/api/shape_selection/test_shapefile.py index 074a1d8..f99f97e 100644 --- a/tests/api/shape_selection/test_shapefile.py +++ b/tests/api/shape_selection/test_shapefile.py @@ -8,11 +8,11 @@ import pytest import requests.exceptions -from atmospheric_explorer.api.os_manager import get_local_folder +from atmospheric_explorer.api.local_folder import get_local_folder from atmospheric_explorer.api.shape_selection.config import SelectionLevel from atmospheric_explorer.api.shape_selection.shapefile import ( - ShapefilesDownloader, ShapefileParameters, + ShapefilesDownloader, dissolve_shapefile_level, ) @@ -25,13 +25,13 @@ def test_param_init(): assert sh_down.depth == 0 assert sh_down.instance == "map_subunits" - + def test_param_subset(): sh_down = ShapefileParameters() sh_down2 = ShapefileParameters() assert sh_down.subset(sh_down2) sh_down3 = ShapefileParameters(resolution="10m") - assert not sh_down.subset(sh_down3) + assert not sh_down.subset(sh_down3) def test_init(): diff --git a/tests/api/test_os_manager.py b/tests/api/test_os_manager.py index d75bd8d..1628664 100644 --- a/tests/api/test_os_manager.py +++ b/tests/api/test_os_manager.py @@ -5,7 +5,7 @@ # pylint: disable=unused-argument import os -from atmospheric_explorer.api.os_manager import get_local_folder +from atmospheric_explorer.api.local_folder import get_local_folder def test_get_local_folder(): diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..98fa9b5 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,17 @@ +# pylint: disable=missing-module-docstring +# pylint: disable=missing-function-docstring +# pylint: disable=unused-import +# ruff: noqa: F401 +import logging + +import pytest + +# this import is needed because otherwise the atmexp logger won't be instantiated when disabling the logs +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger + + +@pytest.fixture(autouse=True, scope="session") +def disable_logging(): + logging.disable(logging.CRITICAL) + yield + logging.disable(logging.NOTSET) From bbf7435d0070112f022f8e49e8417fbda607535c Mon Sep 17 00:00:00 2001 From: "luigi.brancati" Date: Sat, 16 Sep 2023 13:23:10 +0200 Subject: [PATCH 6/7] Fixed nanosecond precision warning --- atmospheric_explorer/api/cache/cache.py | 2 +- .../api/data_interface/cams_interface/cams_interface.py | 2 +- .../api/data_interface/cams_interface/cams_parameters.py | 2 +- .../api/data_interface/data_transformations.py | 4 +++- .../api/data_interface/dataset_config_parser.py | 2 +- atmospheric_explorer/api/data_interface/eac4/eac4.py | 2 +- atmospheric_explorer/api/data_interface/eac4/eac4_config.py | 2 +- .../api/data_interface/eac4/eac4_parameters.py | 2 +- atmospheric_explorer/api/data_interface/ghg/ghg.py | 2 +- atmospheric_explorer/api/data_interface/ghg/ghg_config.py | 2 +- .../api/data_interface/ghg/ghg_parameters.py | 2 +- atmospheric_explorer/api/loggers/__init__.py | 4 ---- atmospheric_explorer/api/loggers/loggers_utils.py | 5 +++-- atmospheric_explorer/api/os_utils.py | 2 +- atmospheric_explorer/api/plotting/anomalies.py | 2 +- atmospheric_explorer/api/plotting/hovmoeller.py | 2 +- atmospheric_explorer/api/plotting/plot_utils.py | 2 +- atmospheric_explorer/api/plotting/yearly_flux.py | 2 +- atmospheric_explorer/api/shape_selection/shape_selection.py | 2 +- atmospheric_explorer/api/shape_selection/shapefile.py | 2 +- atmospheric_explorer/cli/logs.py | 2 +- atmospheric_explorer/cli/plotting/anomalies.py | 2 +- atmospheric_explorer/cli/plotting/hovmoeller.py | 2 +- atmospheric_explorer/cli/plotting/yearly_flux.py | 2 +- atmospheric_explorer/ui/Home.py | 2 +- atmospheric_explorer/ui/interactive_map/interactive_map.py | 2 +- atmospheric_explorer/ui/pages/Anomalies Plot.py | 2 +- atmospheric_explorer/ui/pages/Hovmoeller Plot.py | 2 +- atmospheric_explorer/ui/pages/Yearly Surface Plot.py | 2 +- atmospheric_explorer/ui/utils.py | 2 +- 30 files changed, 33 insertions(+), 34 deletions(-) diff --git a/atmospheric_explorer/api/cache/cache.py b/atmospheric_explorer/api/cache/cache.py index 3faf9b2..ad3fb3d 100644 --- a/atmospheric_explorer/api/cache/cache.py +++ b/atmospheric_explorer/api/cache/cache.py @@ -5,7 +5,7 @@ from functools import wraps from textwrap import dedent -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger class Parameters(ABC): diff --git a/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py b/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py index 8de806a..d7e9938 100644 --- a/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py +++ b/atmospheric_explorer/api/data_interface/cams_interface/cams_interface.py @@ -14,7 +14,7 @@ CAMSParameters, ) from atmospheric_explorer.api.local_folder import get_local_folder -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.os_utils import create_folder, remove_folder diff --git a/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py b/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py index bb2e7ff..d564ead 100644 --- a/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py +++ b/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py @@ -6,7 +6,7 @@ from __future__ import annotations from atmospheric_explorer.api.cache import Parameters -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger class CAMSParameters(Parameters): diff --git a/atmospheric_explorer/api/data_interface/data_transformations.py b/atmospheric_explorer/api/data_interface/data_transformations.py index 445e635..1051ab0 100644 --- a/atmospheric_explorer/api/data_interface/data_transformations.py +++ b/atmospheric_explorer/api/data_interface/data_transformations.py @@ -7,7 +7,7 @@ import xarray as xr from shapely.geometry import mapping -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.shape_selection.shape_selection import Selection @@ -15,6 +15,8 @@ def split_time_dim(dataset: xr.Dataset, time_dim: str): """Return a new dataset where the time_dim dimension is split into times and dates dimensions.""" atm_exp_logger.debug("Splitting time dim %s of dataset %s", time_dim, dataset) times = dataset[f"{time_dim}.time"].values + # The last astype is needed, otherwise the xarray resampling method + # will complain about datetime64[D] missing nanoseconds precision dates = np.array(dataset[time_dim].values, dtype="datetime64[D]").astype( "datetime64[ns]" ) diff --git a/atmospheric_explorer/api/data_interface/dataset_config_parser.py b/atmospheric_explorer/api/data_interface/dataset_config_parser.py index 78845d3..8510c0e 100644 --- a/atmospheric_explorer/api/data_interface/dataset_config_parser.py +++ b/atmospheric_explorer/api/data_interface/dataset_config_parser.py @@ -16,7 +16,7 @@ import yaml from atmospheric_explorer.api.exceptions import OperationNotAllowed -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger class OperationParser: diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4.py b/atmospheric_explorer/api/data_interface/eac4/eac4.py index 7cfeef5..f827ae1 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4.py @@ -11,7 +11,7 @@ from atmospheric_explorer.api.config import CRS from atmospheric_explorer.api.data_interface.cams_interface import CAMSDataInterface from atmospheric_explorer.api.data_interface.eac4.eac4_parameters import EAC4Parameters -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.os_utils import create_folder diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4_config.py b/atmospheric_explorer/api/data_interface/eac4/eac4_config.py index 9c2a266..f6546a6 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4_config.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4_config.py @@ -8,7 +8,7 @@ from atmospheric_explorer.api.data_interface.dataset_config_parser import ( DatasetConfigParser, ) -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.singleton import Singleton diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py b/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py index 5fb88a2..f97c074 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py @@ -8,7 +8,7 @@ from textwrap import dedent from atmospheric_explorer.api.data_interface.cams_interface import CAMSParameters -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger class EAC4Parameters(CAMSParameters): diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg.py b/atmospheric_explorer/api/data_interface/ghg/ghg.py index 9257b23..d87cfae 100644 --- a/atmospheric_explorer/api/data_interface/ghg/ghg.py +++ b/atmospheric_explorer/api/data_interface/ghg/ghg.py @@ -17,7 +17,7 @@ CAMSDataInterface, ) from atmospheric_explorer.api.data_interface.ghg.ghg_parameters import GHGParameters -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.os_utils import create_folder diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg_config.py b/atmospheric_explorer/api/data_interface/ghg/ghg_config.py index 5f9feb1..74edad1 100644 --- a/atmospheric_explorer/api/data_interface/ghg/ghg_config.py +++ b/atmospheric_explorer/api/data_interface/ghg/ghg_config.py @@ -8,7 +8,7 @@ from atmospheric_explorer.api.data_interface.dataset_config_parser import ( DatasetConfigParser, ) -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.singleton import Singleton diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py b/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py index 5c9a782..8fa6c0b 100644 --- a/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py +++ b/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py @@ -8,7 +8,7 @@ from textwrap import dedent from atmospheric_explorer.api.data_interface.cams_interface import CAMSParameters -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger class GHGParameters(CAMSParameters): diff --git a/atmospheric_explorer/api/loggers/__init__.py b/atmospheric_explorer/api/loggers/__init__.py index 13ced50..e69de29 100644 --- a/atmospheric_explorer/api/loggers/__init__.py +++ b/atmospheric_explorer/api/loggers/__init__.py @@ -1,4 +0,0 @@ -# pylint: disable=missing-module-docstring -# ruff: noqa: F401 -from atmospheric_explorer.api.loggers.loggers import Logger, atm_exp_logger -from atmospheric_explorer.api.loggers.loggers_utils import clear_logs, list_logs diff --git a/atmospheric_explorer/api/loggers/loggers_utils.py b/atmospheric_explorer/api/loggers/loggers_utils.py index 03f2596..009dcff 100644 --- a/atmospheric_explorer/api/loggers/loggers_utils.py +++ b/atmospheric_explorer/api/loggers/loggers_utils.py @@ -2,10 +2,10 @@ Module to define the main logger and a few logging utils. """ import os +import shutil from glob import glob from atmospheric_explorer.api.loggers.loggers import Logger -from atmospheric_explorer.api.os_utils import remove_folder def list_logs() -> list: @@ -15,4 +15,5 @@ def list_logs() -> list: def clear_logs() -> list: """Clear all log files.""" - remove_folder(Logger.logs_root_dir) + if os.path.exists(Logger.logs_root_dir): + shutil.rmtree(Logger.logs_root_dir) diff --git a/atmospheric_explorer/api/os_utils.py b/atmospheric_explorer/api/os_utils.py index 89cad6f..65a2c2d 100644 --- a/atmospheric_explorer/api/os_utils.py +++ b/atmospheric_explorer/api/os_utils.py @@ -5,7 +5,7 @@ import os import shutil -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger def create_folder(folder: str) -> None: diff --git a/atmospheric_explorer/api/plotting/anomalies.py b/atmospheric_explorer/api/plotting/anomalies.py index b8d08f7..7d4ac30 100644 --- a/atmospheric_explorer/api/plotting/anomalies.py +++ b/atmospheric_explorer/api/plotting/anomalies.py @@ -12,7 +12,7 @@ split_time_dim, ) from atmospheric_explorer.api.data_interface.eac4 import EAC4Config, EAC4Instance -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.plot_utils import line_with_ci_subplots from atmospheric_explorer.api.shape_selection.shape_selection import Selection diff --git a/atmospheric_explorer/api/plotting/hovmoeller.py b/atmospheric_explorer/api/plotting/hovmoeller.py index 56884e5..3289b68 100644 --- a/atmospheric_explorer/api/plotting/hovmoeller.py +++ b/atmospheric_explorer/api/plotting/hovmoeller.py @@ -11,7 +11,7 @@ shifting_long, ) from atmospheric_explorer.api.data_interface.eac4 import EAC4Config, EAC4Instance -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.plot_utils import hovmoeller_plot from atmospheric_explorer.api.shape_selection.shape_selection import Selection diff --git a/atmospheric_explorer/api/plotting/plot_utils.py b/atmospheric_explorer/api/plotting/plot_utils.py index a40094e..e0d77e8 100644 --- a/atmospheric_explorer/api/plotting/plot_utils.py +++ b/atmospheric_explorer/api/plotting/plot_utils.py @@ -10,7 +10,7 @@ import plotly.graph_objects as go import xarray as xr -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger def _base_height(n_plots): diff --git a/atmospheric_explorer/api/plotting/yearly_flux.py b/atmospheric_explorer/api/plotting/yearly_flux.py index b6ee0ed..ab0a778 100644 --- a/atmospheric_explorer/api/plotting/yearly_flux.py +++ b/atmospheric_explorer/api/plotting/yearly_flux.py @@ -14,7 +14,7 @@ GHGConfig, InversionOptimisedGreenhouseGas, ) -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.plot_utils import line_with_ci_subplots from atmospheric_explorer.api.shape_selection.shape_selection import Selection diff --git a/atmospheric_explorer/api/shape_selection/shape_selection.py b/atmospheric_explorer/api/shape_selection/shape_selection.py index dfe3bec..5cdb8c3 100644 --- a/atmospheric_explorer/api/shape_selection/shape_selection.py +++ b/atmospheric_explorer/api/shape_selection/shape_selection.py @@ -9,7 +9,7 @@ from shapely.ops import unary_union from atmospheric_explorer.api.config import CRS -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.shape_selection.config import ( SelectionLevel, map_level_shapefile_mapping, diff --git a/atmospheric_explorer/api/shape_selection/shapefile.py b/atmospheric_explorer/api/shape_selection/shapefile.py index 4be4e4d..689feca 100644 --- a/atmospheric_explorer/api/shape_selection/shapefile.py +++ b/atmospheric_explorer/api/shape_selection/shapefile.py @@ -15,7 +15,7 @@ from atmospheric_explorer.api.cache import Cached, Parameters from atmospheric_explorer.api.local_folder import get_local_folder -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.os_utils import create_folder from atmospheric_explorer.api.shape_selection.config import map_level_shapefile_mapping diff --git a/atmospheric_explorer/cli/logs.py b/atmospheric_explorer/cli/logs.py index 2086630..d9e7868 100644 --- a/atmospheric_explorer/cli/logs.py +++ b/atmospheric_explorer/cli/logs.py @@ -5,7 +5,7 @@ import click -from atmospheric_explorer.api.loggers import clear_logs, list_logs +from atmospheric_explorer.api.loggers.loggers_utils import clear_logs, list_logs @click.group() diff --git a/atmospheric_explorer/cli/plotting/anomalies.py b/atmospheric_explorer/cli/plotting/anomalies.py index 92df2fe..8650700 100644 --- a/atmospheric_explorer/cli/plotting/anomalies.py +++ b/atmospheric_explorer/cli/plotting/anomalies.py @@ -6,7 +6,7 @@ import click from atmospheric_explorer.api.data_interface.eac4.eac4_config import EAC4Config -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.anomalies import eac4_anomalies_plot from atmospheric_explorer.api.shape_selection.config import SelectionLevel from atmospheric_explorer.api.shape_selection.shape_selection import EntitySelection diff --git a/atmospheric_explorer/cli/plotting/hovmoeller.py b/atmospheric_explorer/cli/plotting/hovmoeller.py index 74dcaf8..3b4d239 100644 --- a/atmospheric_explorer/cli/plotting/hovmoeller.py +++ b/atmospheric_explorer/cli/plotting/hovmoeller.py @@ -6,7 +6,7 @@ import click from atmospheric_explorer.api.data_interface.eac4.eac4_config import EAC4Config -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.hovmoeller import eac4_hovmoeller_plot from atmospheric_explorer.api.shape_selection.config import SelectionLevel from atmospheric_explorer.api.shape_selection.shape_selection import EntitySelection diff --git a/atmospheric_explorer/cli/plotting/yearly_flux.py b/atmospheric_explorer/cli/plotting/yearly_flux.py index 0cb77c0..d108a6b 100644 --- a/atmospheric_explorer/cli/plotting/yearly_flux.py +++ b/atmospheric_explorer/cli/plotting/yearly_flux.py @@ -6,7 +6,7 @@ import click from atmospheric_explorer.api.data_interface.ghg.ghg_config import GHGConfig -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.yearly_flux import ( ghg_surface_satellite_yearly_plot, ) diff --git a/atmospheric_explorer/ui/Home.py b/atmospheric_explorer/ui/Home.py index b4207f0..1de4931 100644 --- a/atmospheric_explorer/ui/Home.py +++ b/atmospheric_explorer/ui/Home.py @@ -3,7 +3,7 @@ import streamlit as st -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.shape_selection.config import ( SelectionLevel, organizations, diff --git a/atmospheric_explorer/ui/interactive_map/interactive_map.py b/atmospheric_explorer/ui/interactive_map/interactive_map.py index 6c7a556..aa3cb7e 100644 --- a/atmospheric_explorer/ui/interactive_map/interactive_map.py +++ b/atmospheric_explorer/ui/interactive_map/interactive_map.py @@ -7,7 +7,7 @@ from folium.plugins import Draw from streamlit_folium import st_folium -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.shape_selection.shape_selection import ( EntitySelection, GenericShapeSelection, diff --git a/atmospheric_explorer/ui/pages/Anomalies Plot.py b/atmospheric_explorer/ui/pages/Anomalies Plot.py index d6b033e..a847da3 100644 --- a/atmospheric_explorer/ui/pages/Anomalies Plot.py +++ b/atmospheric_explorer/ui/pages/Anomalies Plot.py @@ -7,7 +7,7 @@ import streamlit as st -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.anomalies import eac4_anomalies_plot from atmospheric_explorer.ui.session_state import ( EAC4AnomaliesSessionStateKeys, diff --git a/atmospheric_explorer/ui/pages/Hovmoeller Plot.py b/atmospheric_explorer/ui/pages/Hovmoeller Plot.py index 37e27a2..773b39e 100644 --- a/atmospheric_explorer/ui/pages/Hovmoeller Plot.py +++ b/atmospheric_explorer/ui/pages/Hovmoeller Plot.py @@ -6,7 +6,7 @@ import streamlit as st -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.hovmoeller import eac4_hovmoeller_plot from atmospheric_explorer.ui.session_state import ( GeneralSessionStateKeys, diff --git a/atmospheric_explorer/ui/pages/Yearly Surface Plot.py b/atmospheric_explorer/ui/pages/Yearly Surface Plot.py index d49ed88..dcd0bb6 100644 --- a/atmospheric_explorer/ui/pages/Yearly Surface Plot.py +++ b/atmospheric_explorer/ui/pages/Yearly Surface Plot.py @@ -6,7 +6,7 @@ import streamlit as st -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.plotting.yearly_flux import ( ghg_surface_satellite_yearly_plot, ) diff --git a/atmospheric_explorer/ui/utils.py b/atmospheric_explorer/ui/utils.py index 83dcd40..a49a79d 100644 --- a/atmospheric_explorer/ui/utils.py +++ b/atmospheric_explorer/ui/utils.py @@ -5,7 +5,7 @@ import streamlit as st -from atmospheric_explorer.api.loggers import atm_exp_logger +from atmospheric_explorer.api.loggers.loggers import atm_exp_logger from atmospheric_explorer.api.shape_selection.config import SelectionLevel from atmospheric_explorer.api.shape_selection.shape_selection import EntitySelection from atmospheric_explorer.ui.session_state import GeneralSessionStateKeys From 92c23acc8b9406994adf6a69fc0c82a21a53b952 Mon Sep 17 00:00:00 2001 From: "luigi.brancati" Date: Sun, 24 Sep 2023 17:02:27 +0200 Subject: [PATCH 7/7] Fixed logs, all docstring, pylint and ruff configs, aligned documentation --- .pre-commit-config.yaml | 3 - .pylintrc | 742 ++++++++++++------ .ruff.toml | 4 + atmospheric_explorer/api/cache/__init__.py | 32 + atmospheric_explorer/api/cache/cache.py | 16 +- .../data_interface/cams_interface/__init__.py | 9 +- .../cams_interface/cams_parameters.py | 26 +- .../data_interface/dataset_config_parser.py | 13 +- .../api/data_interface/eac4/eac4.py | 18 + .../api/data_interface/eac4/eac4_config.py | 3 +- .../data_interface/eac4/eac4_parameters.py | 29 +- .../api/data_interface/ghg/__init__.py | 4 +- .../api/data_interface/ghg/ghg.py | 16 +- .../api/data_interface/ghg/ghg_config.py | 3 +- .../api/data_interface/ghg/ghg_parameters.py | 25 +- atmospheric_explorer/api/loggers/__init__.py | 1 + atmospheric_explorer/api/loggers/loggers.py | 1 + .../api/loggers/loggers_utils.py | 4 +- atmospheric_explorer/api/os_utils.py | 8 +- .../api/shape_selection/shapefile.py | 55 +- atmospheric_explorer/api/singleton.py | 8 +- atmospheric_explorer/cli/__init__.py | 1 + atmospheric_explorer/cli/plotting/__init__.py | 1 + atmospheric_explorer/ui/Home.py | 5 +- .../ui/pages/Anomalies Plot.py | 22 +- .../ui/pages/Hovmoeller Plot.py | 5 +- .../ui/pages/Yearly Surface Plot.py | 14 +- atmospheric_explorer/ui/utils.py | 4 +- .../doc_files/doctrees/api.cache.doctree | Bin 0 -> 30746 bytes .../api.data_interface.cams_interface.doctree | Bin 0 -> 40232 bytes .../doctrees/api.data_interface.doctree | Bin 62601 -> 47430 bytes .../doctrees/api.data_interface.eac4.doctree | Bin 57399 -> 81500 bytes .../doctrees/api.data_interface.ghg.doctree | Bin 55114 -> 64630 bytes documentation/doc_files/doctrees/api.doctree | Bin 34821 -> 20799 bytes .../doc_files/doctrees/api.loggers.doctree | Bin 0 -> 21161 bytes .../doc_files/doctrees/api.plotting.doctree | Bin 87419 -> 89563 bytes .../doctrees/api.shape_selection.doctree | Bin 111914 -> 111871 bytes .../doc_files/doctrees/environment.pickle | Bin 634293 -> 781014 bytes .../doc_files/html/_sources/api.cache.rst.txt | 21 + .../api.data_interface.cams_interface.rst.txt | 29 + .../_sources/api.data_interface.eac4.rst.txt | 9 +- .../_sources/api.data_interface.ghg.rst.txt | 9 +- .../html/_sources/api.data_interface.rst.txt | 21 +- .../html/_sources/api.loggers.rst.txt | 29 + .../doc_files/html/_sources/api.rst.txt | 22 +- documentation/doc_files/html/api.cache.html | 220 ++++++ .../api.data_interface.cams_interface.html | 235 ++++++ .../html/api.data_interface.eac4.html | 101 ++- .../html/api.data_interface.ghg.html | 74 +- .../doc_files/html/api.data_interface.html | 242 +++--- documentation/doc_files/html/api.html | 201 ++--- documentation/doc_files/html/api.loggers.html | 183 +++++ .../doc_files/html/api.plotting.html | 4 +- .../doc_files/html/api.shape_selection.html | 67 +- documentation/doc_files/html/genindex.html | 269 +++++-- documentation/doc_files/html/modules.html | 38 +- documentation/doc_files/html/objects.inv | Bin 1538 -> 1802 bytes documentation/doc_files/html/py-modindex.html | 58 +- documentation/doc_files/html/searchindex.js | 2 +- documentation/source/api.cache.rst | 21 + .../api.data_interface.cams_interface.rst | 29 + .../source/api.data_interface.eac4.rst | 9 +- .../source/api.data_interface.ghg.rst | 9 +- documentation/source/api.data_interface.rst | 21 +- documentation/source/api.loggers.rst | 29 + documentation/source/api.rst | 22 +- tests/api/cache/test_cached.py | 1 - .../cams_interface/test_cams_interfaces.py | 6 +- tests/api/data_interface/eac4/test_eac4.py | 3 +- .../data_interface/ghg/test_ghg_parameters.py | 6 +- 70 files changed, 2277 insertions(+), 785 deletions(-) create mode 100644 documentation/doc_files/doctrees/api.cache.doctree create mode 100644 documentation/doc_files/doctrees/api.data_interface.cams_interface.doctree create mode 100644 documentation/doc_files/doctrees/api.loggers.doctree create mode 100644 documentation/doc_files/html/_sources/api.cache.rst.txt create mode 100644 documentation/doc_files/html/_sources/api.data_interface.cams_interface.rst.txt create mode 100644 documentation/doc_files/html/_sources/api.loggers.rst.txt create mode 100644 documentation/doc_files/html/api.cache.html create mode 100644 documentation/doc_files/html/api.data_interface.cams_interface.html create mode 100644 documentation/doc_files/html/api.loggers.html create mode 100644 documentation/source/api.cache.rst create mode 100644 documentation/source/api.data_interface.cams_interface.rst create mode 100644 documentation/source/api.loggers.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1957dbd..3a8688a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,6 @@ repos: language: system types_or: [python, pyi] args: [--load-plugins=pylint.extensions.redefined_loop_name] - exclude: docs/ - id: pylint-unspecified-encoding entry: pylint name: Using open without explicitly specifying an encoding @@ -37,7 +36,6 @@ repos: language: system types_or: [python, pyi] args: [--disable=all, --enable=unspecified-encoding] - exclude: docs/ - id: pytest name: pytest entry: pytest --cov=atmospheric_explorer --log-disable=true tests/ @@ -51,7 +49,6 @@ repos: hooks: - id: ruff args: [--exit-non-zero-on-fix] - exclude: docs/ - repo: https://github.com/pycqa/pydocstyle rev: 07f6707e2c5612960347f7c00125620457f490a7 hooks: diff --git a/.pylintrc b/.pylintrc index 8616155..fcca705 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,410 +1,636 @@ -[MASTER] +[MAIN] -# pylint --list-msgs +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore= -# Specify a configuration file. -#rcfile= +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths=tests|documentation|exploration|build|atmospheric_explorer.egg-info + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=test_*.py + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=tests/*|docs/* +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=4 -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns=test_*.py +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= # Pickle collected data for later comparisons. persistent=yes -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.11 -# Use multiple processes to speed up Pylint. -jobs=4 +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= -# Allow optimization of some AST trees. This will activate a peephole AST -# optimizer, which will apply various small optimizations. For instance, it can -# be used to obtain the result of joining multiple strings with the addition -# operator. Joining a lot of strings can lead to a maximum recursion error in -# Pylint and this flag can prevent that. It has one side effect, the resulting -# AST will be different than the one from reality. This option is deprecated -# and it will be removed in Pylint 2.0. -optimize-ast=no +[BASIC] -[MESSAGES CONTROL] +# Naming style matching correct argument names. +argument-naming-style=snake_case -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" +# Naming style matching correct attribute names. +attr-naming-style=snake_case -#disable=all +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata -#enable= +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= -[REPORTS] +# Naming style matching correct class attribute names. +class-attribute-naming-style=any -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". This option is deprecated -# and it will be removed in Pylint 2.0. -files-output=no +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE -# Tells whether to display a full report or only the messages -reports=no +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) +# Naming style matching correct class names. +class-naming-style=PascalCase -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE -[BASIC] +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=yes + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= -# Include a hint for the correct naming format with invalid-name -include-naming-hint=yes +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ # List of decorators that produce properties, such as abc.abstractproperty. Add # to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. property-classes=abc.abstractproperty -# Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= -# Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{2,30}$ +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= -# Regular expression matching correct variable names -#variable-rgx=[a-z_][a-z0-9_]{2,30}$ +# Naming style matching correct variable names. +variable-naming-style=snake_case -# Naming hint for variable names -variable-name-hint=[a-z_][a-z0-9_]{2,30}$ +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= -# Regular expression matching correct constant names -#const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ -# Naming hint for constant names -#const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ +[CLASSES] -# Regular expression matching correct attribute names -#attr-rgx=[a-z_][a-z0-9_]{2,30}$ +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no -# Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{2,30}$ +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp -# Regular expression matching correct argument names -#argument-rgx=[a-z_][a-z0-9_]{2,30}$ +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make -# Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{2,30}$ +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls -# Regular expression matching correct class attribute names -#class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ +[DESIGN] -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= -# Regular expression matching correct class names -#class-rgx=[A-Z_][a-zA-Z0-9]+$ +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ +# Maximum number of arguments for function / method. +max-args=5 -# Regular expression matching correct module names -#module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ +# Maximum number of attributes for a class (see R0902). +max-attributes=7 -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 -# Regular expression matching correct method names -#method-rgx=[a-z_][a-z0-9_]{2,30}$ +# Maximum number of branch for function / method body. +max-branches=12 -# Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{2,30}$ +# Maximum number of locals for function / method body. +max-locals=15 -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ +# Maximum number of parents for a class (see R0901). +max-parents=7 -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 +# Maximum number of return / yield for function / method body. +max-returns=6 -[ELIF] +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.Exception [FORMAT] -# Maximum number of characters on a single line. -max-line-length=120 +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator -# Maximum number of lines in a module -max-module-lines=1000 +[IMPORTS] -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=regsub, + TERMIOS, + Bastion, + rexec + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= [LOGGING] +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + # Logging modules to check that the string format arguments are in logging -# function parameter format +# function parameter format. logging-modules=logging -[MISCELLANEOUS] +[MESSAGES CONTROL] -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead -[SIMILARITIES] +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member -# Minimum lines number of a similarity. -min-similarity-lines=4 -# Ignore comments when computing similarities. -ignore-comments=yes +[METHOD_ARGS] -# Ignore docstrings when computing similarities. -ignore-docstrings=yes +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request -# Ignore imports when computing similarities. -ignore-imports=no +[MISCELLANEOUS] -[SPELLING] +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= +# Regular expression of note tags to take in consideration. +notes-rgx= -# List of comma separated words that should not be checked. -spelling-ignore-words= -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= +[REFACTORING] -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error -[TYPECHECK] -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= +# Tells whether to display a full report or only the messages. +reports=no -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager +# Activate the evaluation score. +score=yes -[VARIABLES] +[SIMILARITIES] -# Tells whether we should check for unused import in __init__ files. -init-import=no +# Comments are removed from the similarity computation +ignore-comments=yes -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy +# Docstrings are removed from the similarity computation +ignore-docstrings=yes -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= +# Imports are removed from the similarity computation +ignore-imports=no -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb +# Signatures are removed from the similarity computation +ignore-signatures=yes -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins +# Minimum lines number of a similarity. +min-similarity-lines=4 -[CLASSES] +[SPELLING] -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work.. +spelling-dict= -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make +# List of comma separated words that should not be checked. +spelling-ignore-words= +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= -[DESIGN] +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no -# Maximum number of arguments for function / method -max-args=5 -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* +[STRING] -# Maximum number of locals for function / method body -max-locals=15 +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no -# Maximum number of return / yield for function / method body -max-returns=6 +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no -# Maximum number of branch for function / method body -max-branches=12 -# Maximum number of statements in function / method body -max-statements=50 +[TYPECHECK] -# Maximum number of parents for a class (see R0901). -max-parents=7 +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager -# Maximum number of attributes for a class (see R0902). -max-attributes=7 +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local -[IMPORTS] +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,TERMIOS,Bastion,rexec +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= +# List of decorators that change the signature of a decorated function. +signature-mutators= -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant +[VARIABLES] -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes -[EXCEPTIONS] +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.* -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,future.builtins diff --git a/.ruff.toml b/.ruff.toml index 6802919..5fb9867 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -1,2 +1,6 @@ # Allow lines to be as long as 120 characters. line-length = 120 +exclude = [ + "documentation", + "exploration" +] diff --git a/atmospheric_explorer/api/cache/__init__.py b/atmospheric_explorer/api/cache/__init__.py index be2cae1..f03fed3 100644 --- a/atmospheric_explorer/api/cache/__init__.py +++ b/atmospheric_explorer/api/cache/__init__.py @@ -1,3 +1,35 @@ +"""Module containing all caching functionalities for CAMS datasets. + +Datasets are downloaded when needed, and each call is cached **in memory** in order to avoid +downloading the same dataset multiple times. + +In this submodule we define two classes: + +- Parameters: + Abstract base class to be inherited by the dataset parameters. + + Each dataset should have a Parameters subclass where all its cdsapi parameters are defined. + + This class also expects a `subset` instance method to be defined: + this method expects another Parameters instance as the `other` argument + and returns True if `other` has the same or a superset of the Parameters + of the `self` instance. + +- Cached: + Class with some methods needed for caching a CAMS dataset call. + + To cache a class, inherit from this class and define the `__new__` method: + this method is only needed to instantiate the Parameters instance + corresponding to the specific CAMS dataset and pass it to Cached.__new__. + + Last, one needs to decorate the new class __init__ with the static method Cached.init_cache. + + See eac4 and ghg submodules as an example. + +Note that this cache only keeps track of files downloaded during a session and ignores preiously downloaded files. +This means that it only works when using the UI or the APIs, +while doesn't work with the CLI since each command is a session by itself. +""" # pylint: disable=missing-module-docstring # ruff: noqa: F401 from atmospheric_explorer.api.cache.cache import Cached, Parameters diff --git a/atmospheric_explorer/api/cache/cache.py b/atmospheric_explorer/api/cache/cache.py index ad3fb3d..f740c86 100644 --- a/atmospheric_explorer/api/cache/cache.py +++ b/atmospheric_explorer/api/cache/cache.py @@ -22,12 +22,14 @@ def subset(self, other: Parameters) -> bool: class Cached: """This class defines a few methods that allow to cache another class instances. + Caching is based on the class attributes. - To cache a class, inherit from this class and define the subset method, - which is used inside find_cache to determine wether an instance is already included - or is equal to another instance. Last, one needs to decorate the new class __init__ with the - static method Cached.init_cache. + To cache a class, inherit from this class and define the `__new__` method: + this method is only needed to instantiate the Parameters instance + corresponding to the specific CAMS dataset and pass it to Cached.__new__. + + Last, one needs to decorate the new class __init__ with the static method Cached.init_cache. """ _cache: list[Cached] = [] @@ -61,6 +63,12 @@ def clear_cache(cls): cls._cache = list(filter(lambda obj: not (isinstance(obj, cls)), cls._cache)) def __new__(cls: Cached, parameters: Parameters): + """Create a new instance of the cached class if there's no similar instance in the cache. + + When attempting to create an instance, a cached class first instantiates its parameters and + then checks them against other cached instances. If an instance with the same or a superset of parameters + is found, that instance instead of a new one is returned. + """ atm_exp_logger.debug( dedent( """\ diff --git a/atmospheric_explorer/api/data_interface/cams_interface/__init__.py b/atmospheric_explorer/api/data_interface/cams_interface/__init__.py index 2f3934e..86d6f8f 100644 --- a/atmospheric_explorer/api/data_interface/cams_interface/__init__.py +++ b/atmospheric_explorer/api/data_interface/cams_interface/__init__.py @@ -1,4 +1,9 @@ +"""Module containing the generic CAMS dataset interface.""" # pylint: disable=missing-module-docstring # ruff: noqa: F401 -from atmospheric_explorer.api.data_interface.cams_interface.cams_interface import CAMSDataInterface -from atmospheric_explorer.api.data_interface.cams_interface.cams_parameters import CAMSParameters +from atmospheric_explorer.api.data_interface.cams_interface.cams_interface import ( + CAMSDataInterface, +) +from atmospheric_explorer.api.data_interface.cams_interface.cams_parameters import ( + CAMSParameters, +) diff --git a/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py b/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py index d564ead..8fc7fe0 100644 --- a/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py +++ b/atmospheric_explorer/api/data_interface/cams_interface/cams_parameters.py @@ -1,6 +1,4 @@ -"""\ -This module defines a generic base class to be used for the CAMS datasets parameters. -""" +"""This module defines a generic base class to be used for the CAMS datasets parameters.""" # pylint: disable=too-few-public-methods # pylint: disable=too-many-arguments from __future__ import annotations @@ -10,20 +8,21 @@ class CAMSParameters(Parameters): - """\ - Base class to be used for CAMS dataset parameters. - - Attributes: - data_variables (str | set[str] | list[str]): data varaibles to be downloaded from CAMS, depend on the dataset - """ + """Base class to be used for CAMS dataset parameters.""" def __init__(self, data_variables: str | set[str] | list[str]) -> None: + """Initializes an instance of CAMSParameters. + + Attributes: + data_variables (str | set[str] | list[str]): data varaibles to be downloaded from CAMS, + depends on the dataset + """ atm_exp_logger.debug("Instantiating parameters object %s", type(self).__name__) self.data_variables = data_variables @property def data_variables(self: CAMSParameters) -> str | set[str]: - """Time values are internally represented as a set, use this property to set/get its value""" + """Time values are internally represented as a set, use this property to set/get its value.""" return ( self._data_variables if len(self._data_variables) > 1 @@ -39,10 +38,15 @@ def data_variables( self._data_variables = set(data_variables_input) def subset(self: CAMSParameters, other: CAMSParameters): + """Return true if the parameters of this instance are equal or a subset of other. + + Attributes: + other (CAMSParameters): the other instance of CAMSParameters to be compared with self + """ return self.data_variables.issubset(other.data_variables) def build_call_body(self: CAMSParameters) -> dict: - """Build the CDSAPI call body""" + """Build the CDSAPI call body.""" return { "variable": list(self.data_variables) if isinstance(self.data_variables, set) diff --git a/atmospheric_explorer/api/data_interface/dataset_config_parser.py b/atmospheric_explorer/api/data_interface/dataset_config_parser.py index 8510c0e..6383e91 100644 --- a/atmospheric_explorer/api/data_interface/dataset_config_parser.py +++ b/atmospheric_explorer/api/data_interface/dataset_config_parser.py @@ -2,7 +2,7 @@ This module defines a Singleton, in this way the file constants.cfg is loaded only once. The singleton pattern was taken from here -https://refactoring.guru/design-patterns/singleton/python/example#example-0 +https://refactoring.guru/design-patterns/singleton/python/example#example-0 . """ # pylint: disable=no-else-return # pylint: disable=missing-function-docstring @@ -78,13 +78,11 @@ def _(self, operation: list) -> list: class DatasetConfigParser: # pylint: disable=too-few-public-methods - """This class implements some basic functionalities to parse a YAML file containing the configuration for a dataset, - i.e. variables, conversion factors etc. + """This class implements some basic functionalities to parse a YAML file containing the configuration for a dataset. For reference, check the file atmospheric_explorer/api/data_interface/ghg/ghg_config.yaml. A dataset configuration file is **expected** to have data structured in the following way - variables: data_variable_name: var_name: # name of the dataset column @@ -96,6 +94,11 @@ class DatasetConfigParser: _parser = OperationParser() def __init__(self, **kwargs): + """Initializes a DatasetConfigParser instance. + + Attributes: + filename (str): the name of the config file passed as a key-value argument + """ filename = kwargs["filename"] filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename) with open(filepath, "r", encoding="utf-8") as file: @@ -114,7 +117,7 @@ def get_config(cls) -> dict: @classmethod def parse_factors(cls, data_dict: dict): """Parse conversion factors in a dataset config file. - + The file is **expected** to have a 'conversion' section. """ if data_dict.get("conversion") is not None: diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4.py b/atmospheric_explorer/api/data_interface/eac4/eac4.py index f827ae1..23f4d7d 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4.py @@ -38,6 +38,24 @@ def __new__( pressure_level: set[str] | list[str] | None = None, model_level: set[str] | list[str] | None = None, ): + """Instantiate a new EAC4Instance instance. + + Attributes: + data_variables (set[str] | list[str]): data varaibles to be downloaded from CAMS, + see https://confluence.ecmwf.int/display/CKB/CAMS%3A+Reanalysis+data+documentation#heading-CAMSglobalreanalysisEAC4Parameterlistings + dates_range (str): range of dates to consider, provided as a 'start/end' string with dates in ISO format + time_values (set[str] | list[str]): times in 'HH:MM' format. A set or a list of values can be provided. + Accepted values are [00:00, 03:00, 06:00, 09:00, 12:00, 15:00, 18:00, 21:00] + files_dir (str | None): directory where to save the data. If not provided will be built using + a dinamically generated name + area (list[int]): latitude-longitude area box to be considered, provided as a list of four values + [NORTH, WEST, SOUTH, EAST]. If not provided, full area will be considered + pressure_level (set[str] | list[str] | None): pressure levels to be considered for multilevel variables. + Can be a set or a list of levels, see documentation linked above for all possible values. + model_level (set[str] | list[str] | None): model levels to be considered for multilevel variables. + Can be a set or a list of levels, chosen in a range from 1 to 60. + See documentation linked above for all possible values. + """ params = EAC4Parameters( data_variables=data_variables, dates_range=dates_range, diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4_config.py b/atmospheric_explorer/api/data_interface/eac4/eac4_config.py index f6546a6..78d953f 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4_config.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4_config.py @@ -14,9 +14,10 @@ class EAC4Config(DatasetConfigParser, metaclass=Singleton): # pylint: disable=too-few-public-methods - """This class is needed to implement a singleton pattern so that config is loaded only once.""" + """This class is needed to implement a singleton pattern so that EAC4 dataset config is loaded only once.""" def __init__(self): + """Initialize EAC4Config instance.""" super().__init__(filename="eac4/eac4_config.yaml") @classmethod diff --git a/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py b/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py index f97c074..eebdeaa 100644 --- a/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py +++ b/atmospheric_explorer/api/data_interface/eac4/eac4_parameters.py @@ -1,6 +1,4 @@ -"""\ -This module collects classes to easily interact with data downloaded from CAMS ADS. -""" +"""This module collects classes to easily interact with data downloaded from CAMS ADS.""" # pylint: disable=too-few-public-methods # pylint: disable=too-many-arguments from __future__ import annotations @@ -12,6 +10,7 @@ class EAC4Parameters(CAMSParameters): + # pylint: disable=line-too-long """Parameters for EAC4 dataset.""" def __init__( @@ -23,6 +22,22 @@ def __init__( pressure_level: set[str] | list[str] | None = None, model_level: set[str] | list[str] | None = None, ) -> None: + """Initializes EAC4Parameters instance. + + Attributes: + data_variables (set[str] | list[str]): data varaibles to be downloaded from CAMS, + see https://confluence.ecmwf.int/display/CKB/CAMS%3A+Reanalysis+data+documentation#heading-CAMSglobalreanalysisEAC4Parameterlistings + dates_range (str): range of dates to consider, provided as a 'start/end' string with dates in ISO format + time_values (set[str] | list[str]): times in 'HH:MM' format. A set or a list of values can be provided. + Accepted values are [00:00, 03:00, 06:00, 09:00, 12:00, 15:00, 18:00, 21:00] + area (list[int]): latitude-longitude area box to be considered, provided as a list of four values + [NORTH, WEST, SOUTH, EAST]. If not provided, full area will be considered + pressure_level (set[str] | list[str] | None): pressure levels to be considered for multilevel variables. + Can be a set or a list of levels, see documentation linked above for all possible values. + model_level (set[str] | list[str] | None): model levels to be considered for multilevel variables. + Can be a set or a list of levels, chosen in a range from 1 to 60. + See documentation linked above for all possible values. + """ super().__init__(data_variables) self.dates_range = dates_range self.time_values = set(time_values) @@ -33,6 +48,7 @@ def __init__( self.model_level = set(model_level) if model_level is not None else model_level def __repr__(self) -> str: + """Parameters representation.""" return dedent( f"""\ data_variables: {self.data_variables} @@ -88,6 +104,11 @@ def model_issubset(ml1: set | None, ml2: set | None) -> bool: def subset(self: EAC4Parameters, other: EAC4Parameters) -> bool: # pylint: disable = protected-access + """Return true if the parameters of this instance are equal or a subset of other. + + Attributes: + other (EAC4Parameters): the other instance of EAC4Parameters to be compared with self + """ res = ( self._data_variables.issubset(other._data_variables) and EAC4Parameters.dates_issubset(self.dates_range, other.dates_range) @@ -102,7 +123,7 @@ def subset(self: EAC4Parameters, other: EAC4Parameters) -> bool: return res def build_call_body(self: EAC4Parameters) -> dict: - """Build the CDSAPI call body""" + """Build the CDSAPI call body.""" call_body = super().build_call_body() call_body["date"] = self.dates_range call_body["time"] = list(self.time_values) diff --git a/atmospheric_explorer/api/data_interface/ghg/__init__.py b/atmospheric_explorer/api/data_interface/ghg/__init__.py index 1906bd8..a6b397e 100644 --- a/atmospheric_explorer/api/data_interface/ghg/__init__.py +++ b/atmospheric_explorer/api/data_interface/ghg/__init__.py @@ -2,6 +2,8 @@ # pylint: disable=missing-module-docstring # ruff: noqa: F401 -from atmospheric_explorer.api.data_interface.ghg.ghg import InversionOptimisedGreenhouseGas +from atmospheric_explorer.api.data_interface.ghg.ghg import ( + InversionOptimisedGreenhouseGas, +) from atmospheric_explorer.api.data_interface.ghg.ghg_config import GHGConfig from atmospheric_explorer.api.data_interface.ghg.ghg_parameters import GHGParameters diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg.py b/atmospheric_explorer/api/data_interface/ghg/ghg.py index d87cfae..c31f819 100644 --- a/atmospheric_explorer/api/data_interface/ghg/ghg.py +++ b/atmospheric_explorer/api/data_interface/ghg/ghg.py @@ -46,6 +46,20 @@ def __new__( files_dir: str | None = None, version: str = "latest", ): + """Instantiate a new InversionOptimisedGreenhouseGas instance. + + Attributes: + data_variables (str): data varaibles to be downloaded from CAMS, + see https://ads.atmosphere.copernicus.eu/cdsapp#!/dataset/cams-global-greenhouse-gas-inversion?tab=overview + quantity (str): quantity, can be one of ['mean_column', 'surface_flux', 'concentration'] + input_observations (str): input observations, can be one of ['surface', 'satellite', 'surface_satellite'] + time_aggregation (str): time aggregation, can be one of ['instantaneous', 'daily_mean', 'monthly_mean'] + year (set[str] | list[str]): set or list of years, in 'YYYY' format + month (set[str] | list[str]): set or list of months, in 'MM' format + files_dir (str | None): directory where to save the data. If not provided will be built using + a dinamically generated name + version (str): version of the dataset, default is 'latest' + """ params = GHGParameters( data_variables=data_variables, quantity=quantity, @@ -79,7 +93,7 @@ def __init__( time_aggregation (str): time aggregation, can be one of ['instantaneous', 'daily_mean', 'monthly_mean'] year (set[str] | list[str]): set or list of years, in 'YYYY' format month (set[str] | list[str]): set or list of months, in 'MM' format - files_dir (str | None): directory where to save the data. If not provided will be built using + files_dir (str | None): directory where to save the data. If not provided will be built using a dinamically generated name version (str): version of the dataset, default is 'latest' """ diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg_config.py b/atmospheric_explorer/api/data_interface/ghg/ghg_config.py index 74edad1..6458b44 100644 --- a/atmospheric_explorer/api/data_interface/ghg/ghg_config.py +++ b/atmospheric_explorer/api/data_interface/ghg/ghg_config.py @@ -14,9 +14,10 @@ class GHGConfig(DatasetConfigParser, metaclass=Singleton): # pylint: disable=too-few-public-methods - """This class is needed to implement a singleton pattern so that config is loaded only once.""" + """This class is needed to implement a singleton pattern so that GHG dataset config is loaded only once.""" def __init__(self): + """Initialize GHGConfig instance.""" super().__init__(filename="ghg/ghg_config.yaml") @classmethod diff --git a/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py b/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py index 8fa6c0b..bc300d5 100644 --- a/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py +++ b/atmospheric_explorer/api/data_interface/ghg/ghg_parameters.py @@ -1,6 +1,4 @@ -"""\ -This module collects classes to easily interact with data downloaded from CAMS ADS. -""" +"""This module collects classes to easily interact with data downloaded from CAMS ADS.""" # pylint: disable=too-few-public-methods # pylint: disable=too-many-arguments from __future__ import annotations @@ -12,6 +10,7 @@ class GHGParameters(CAMSParameters): + # pylint: disable=line-too-long """Parameters for Global Inversion dataset.""" def __init__( @@ -24,6 +23,18 @@ def __init__( month: set[str] | list[str], version: str = "latest", ): + """Initializes GHGParameters instance. + + Attributes: + data_variables (str): data varaibles to be downloaded from CAMS, + see https://ads.atmosphere.copernicus.eu/cdsapp#!/dataset/cams-global-greenhouse-gas-inversion?tab=overview + quantity (str): quantity, can be one of ['mean_column', 'surface_flux', 'concentration'] + input_observations (str): input observations, can be one of ['surface', 'satellite', 'surface_satellite'] + time_aggregation (str): time aggregation, can be one of ['instantaneous', 'daily_mean', 'monthly_mean'] + year (set[str] | list[str]): set or list of years, in 'YYYY' format + month (set[str] | list[str]): set or list of months, in 'MM' format + version (str): version of the dataset, default is 'latest' + """ super().__init__(data_variables) self.quantity = quantity self.input_observations = input_observations @@ -33,6 +44,7 @@ def __init__( self.version = version def __repr__(self) -> str: + """Parameters representation.""" return dedent( f"""\ data_variables: {self.data_variables} @@ -46,6 +58,11 @@ def __repr__(self) -> str: ) def subset(self: GHGParameters, other: GHGParameters) -> bool: + """Return true if the parameters of this instance are equal or a subset of other. + + Attributes: + other (GHGParameters): the other instance of GHGParameters to be compared with self + """ res = ( self.data_variables == other.data_variables and self.quantity == other.quantity @@ -59,7 +76,7 @@ def subset(self: GHGParameters, other: GHGParameters) -> bool: return res def build_call_body(self: GHGParameters) -> dict: - """Build the CDSAPI call body""" + """Build the CDSAPI call body.""" call_body = super().build_call_body() call_body.update( { diff --git a/atmospheric_explorer/api/loggers/__init__.py b/atmospheric_explorer/api/loggers/__init__.py index e69de29..53f9389 100644 --- a/atmospheric_explorer/api/loggers/__init__.py +++ b/atmospheric_explorer/api/loggers/__init__.py @@ -0,0 +1 @@ +"""Module containing Atmospheric Explorer APIs to generate logs.""" diff --git a/atmospheric_explorer/api/loggers/loggers.py b/atmospheric_explorer/api/loggers/loggers.py index 06d24bf..874255f 100644 --- a/atmospheric_explorer/api/loggers/loggers.py +++ b/atmospheric_explorer/api/loggers/loggers.py @@ -43,6 +43,7 @@ class LoggerSingleton(Singleton): def __init__(cls, *args, **kwargs): # pylint: disable = unused-argument + """Create logging folder and add logger configuration to root logger.""" if not os.path.exists(cls.logs_root_dir): os.makedirs(cls.logs_root_dir) logging.config.dictConfig(cls.logging_config) diff --git a/atmospheric_explorer/api/loggers/loggers_utils.py b/atmospheric_explorer/api/loggers/loggers_utils.py index 009dcff..ac0515b 100644 --- a/atmospheric_explorer/api/loggers/loggers_utils.py +++ b/atmospheric_explorer/api/loggers/loggers_utils.py @@ -1,6 +1,4 @@ -"""\ -Module to define the main logger and a few logging utils. -""" +"""Module to define the main logger and a few logging utils.""" import os import shutil from glob import glob diff --git a/atmospheric_explorer/api/os_utils.py b/atmospheric_explorer/api/os_utils.py index 65a2c2d..9d3e8e1 100644 --- a/atmospheric_explorer/api/os_utils.py +++ b/atmospheric_explorer/api/os_utils.py @@ -1,6 +1,4 @@ -"""\ -Module to gather all utility functions and classes. -""" +"""Module to gather all utility functions and classes.""" import os import shutil @@ -9,14 +7,14 @@ def create_folder(folder: str) -> None: - """Create folder if it doesn't exists""" + """Create folder if it doesn't exists.""" if not os.path.exists(folder): atm_exp_logger.debug("Creating folder %s", folder) os.makedirs(folder) def remove_folder(folder: str) -> None: - """Remove folder if exists""" + """Remove folder if exists.""" if os.path.exists(folder): atm_exp_logger.debug("Removing folder %s", folder) shutil.rmtree(folder) diff --git a/atmospheric_explorer/api/shape_selection/shapefile.py b/atmospheric_explorer/api/shape_selection/shapefile.py index 689feca..941e5c6 100644 --- a/atmospheric_explorer/api/shape_selection/shapefile.py +++ b/atmospheric_explorer/api/shape_selection/shapefile.py @@ -31,14 +31,45 @@ def __init__( info_type: str = "admin", depth: int = 0, instance: str = "map_subunits", - ): # pylint: disable=too-many-arguments + ): + # pylint: disable=too-many-arguments + """Initializes ShapefileParameters instance. + + Attributes: + resolution (str): spatial resolution. Possible values: 10m, 50m, 110m + map_type (str): map type, e.g. cultural, physical or raster + info_type (str): shapefile type, e.g. admin, lakes, etc. You can check possible + values depending on the resolution on the webpage below + depth (int): different info_type shapefiles can have different values. + Use 0 for administrative shapefiles (countries) + instance (str): the specific shapefile to be downloaded. Example: countries_ita + + See some more details here: https://www.naturalearthdata.com/downloads/ + """ self.resolution = resolution self.map_type = map_type self.info_type = info_type self.depth = depth self.instance = instance + def __repr__(self) -> str: + """Parameters representation.""" + return dedent( + f"""\ + resolution: {self.resolution} + map_type: {self.map_type} + info_type: {self.info_type} + depth: {self.depth} + instance: {self.instance} + """ + ) + def subset(self: ShapefileParameters, other: ShapefileParameters) -> bool: + """Return true if the parameters of this instance are equal or a subset of other. + + Attributes: + other (ShapefileParameters): the other instance of ShapefileParameters to be compared with self + """ res = ( self.resolution == other.resolution and self.map_type == other.map_type @@ -85,7 +116,23 @@ def __new__( instance: str = "map_subunits", timeout: int = 10, dst_dir: str | None = None, - ): # pylint: disable=too-many-arguments + ): + # pylint: disable=too-many-arguments + """Instantiate a new ShapefilesDownloader instance. + + Attributes: + resolution (str): spatial resolution. Possible values: 10m, 50m, 110m + map_type (str): map type, e.g. cultural, physical or raster + info_type (str): shapefile type, e.g. admin, lakes, etc. You can check possible + values depending on the resolution on the webpage below + depth (int): different info_type shapefiles can have different values. + Use 0 for administrative shapefiles (countries) + instance (str): the specific shapefile to be downloaded. Example: countries_ita + timeout (int): timeout fot the GET call to Natural Earth Data + dst_dir (str): destination diractory for the donwloaded shapefile + + See some more details here: https://www.naturalearthdata.com/downloads/ + """ params = ShapefileParameters( resolution=resolution, map_type=map_type, @@ -161,7 +208,7 @@ def __init__( @property def shapefile_name(self: ShapefilesDownloader) -> str: - """Shapefile name""" + """Shapefile name.""" if self.parameters.map_type == "physical": return f"ne_{self.parameters.resolution}_{self.parameters.info_type}" return f"ne_{self.parameters.resolution}_{self.parameters.info_type}_{self.parameters.depth}_{self.parameters.instance}" # noqa: E501 @@ -257,7 +304,7 @@ def get_as_dataframe(self: ShapefilesDownloader) -> gpd.GeoDataFrame: def dissolve_shapefile_level(level: str) -> gpd.GeoDataFrame: - """Gets shapefile and dissolves it on a selection level""" + """Gets shapefile and dissolves it on a selection level.""" atm_exp_logger.debug("Dissolve shapefile to level %s", level) col = map_level_shapefile_mapping[level] sh_df = ShapefilesDownloader(instance="map_subunits").get_as_dataframe() diff --git a/atmospheric_explorer/api/singleton.py b/atmospheric_explorer/api/singleton.py index 5248282..f1df666 100644 --- a/atmospheric_explorer/api/singleton.py +++ b/atmospheric_explorer/api/singleton.py @@ -1,7 +1,8 @@ -"""\ -Module to manage constants. +"""Module to manage constants. + +This module defines a Singleton, which is used in several classes like the one that load the dataset configurations: +this way the configuration is loaded only once. -This module defines a Singleton, in this way the file constants.cfg is loaded only once. The singleton pattern was taken from here https://refactoring.guru/design-patterns/singleton/python/example#example-0 """ @@ -17,6 +18,7 @@ class Singleton(type): _instances = {} def __call__(cls, *args, **kwargs): + """Creates a new instance only if there's no other instance in _instances class attribute.""" if cls not in cls._instances: instance = super().__call__(*args, **kwargs) cls._instances[cls] = instance diff --git a/atmospheric_explorer/cli/__init__.py b/atmospheric_explorer/cli/__init__.py index e69de29..6a1a7a0 100644 --- a/atmospheric_explorer/cli/__init__.py +++ b/atmospheric_explorer/cli/__init__.py @@ -0,0 +1 @@ +"""Module containing Atmospheric Explorer CLI.""" diff --git a/atmospheric_explorer/cli/plotting/__init__.py b/atmospheric_explorer/cli/plotting/__init__.py index e69de29..3b5b3b9 100644 --- a/atmospheric_explorer/cli/plotting/__init__.py +++ b/atmospheric_explorer/cli/plotting/__init__.py @@ -0,0 +1 @@ +"""Module containing Atmospheric Explorer CLI to plot data.""" diff --git a/atmospheric_explorer/ui/Home.py b/atmospheric_explorer/ui/Home.py index 1de4931..2005c10 100644 --- a/atmospheric_explorer/ui/Home.py +++ b/atmospheric_explorer/ui/Home.py @@ -142,7 +142,8 @@ def _used_form(): ) -def page(): +def home_page(): + """Builds the Home page.""" _init() atm_exp_logger.info("Starting streamlit app") progress_bar = st.progress(0.0, "Starting app") @@ -161,4 +162,4 @@ def page(): if __name__ == "__main__": - page() + home_page() diff --git a/atmospheric_explorer/ui/pages/Anomalies Plot.py b/atmospheric_explorer/ui/pages/Anomalies Plot.py index a847da3..5c9f0f4 100644 --- a/atmospheric_explorer/ui/pages/Anomalies Plot.py +++ b/atmospheric_explorer/ui/pages/Anomalies Plot.py @@ -3,7 +3,6 @@ """ # pylint: disable=invalid-name from datetime import datetime -from textwrap import dedent import streamlit as st @@ -119,7 +118,8 @@ def _selectors(): return v_name, title -def page(): +def anomalies_page(): + """Builds the Anomalies page.""" _init() var_name, plot_title = _selectors() build_sidebar() @@ -128,9 +128,15 @@ def page(): start_date = st.session_state[ EAC4AnomaliesSessionStateKeys.EAC4_ANOMALIES_START_DATE ] - end_date = st.session_state[EAC4AnomaliesSessionStateKeys.EAC4_ANOMALIES_END_DATE] - dates_range = f"{start_date.strftime('%Y-%m-%d')}/{end_date.strftime('%Y-%m-%d')}" - time_values = st.session_state[EAC4AnomaliesSessionStateKeys.EAC4_ANOMALIES_TIMES] + end_date = st.session_state[ + EAC4AnomaliesSessionStateKeys.EAC4_ANOMALIES_END_DATE + ] + dates_range = ( + f"{start_date.strftime('%Y-%m-%d')}/{end_date.strftime('%Y-%m-%d')}" + ) + time_values = st.session_state[ + EAC4AnomaliesSessionStateKeys.EAC4_ANOMALIES_TIMES + ] shapes = st.session_state[GeneralSessionStateKeys.SELECTED_SHAPES] data_variable = st.session_state[ EAC4AnomaliesSessionStateKeys.EAC4_ANOMALIES_DATA_VARIABLE @@ -142,9 +148,7 @@ def page(): ref_end_date = st.session_state[ EAC4AnomaliesSessionStateKeys.EAC4_REFERENCE_END_DATE ] - reference_dates_range = ( - f"{ref_start_date.strftime('%Y-%m-%d')}/{ref_end_date.strftime('%Y-%m-%d')}" - ) + reference_dates_range = f"{ref_start_date.strftime('%Y-%m-%d')}/{ref_end_date.strftime('%Y-%m-%d')}" else: reference_dates_range = None with st.container(): @@ -164,4 +168,4 @@ def page(): if __name__ == "__main__": - page() + anomalies_page() diff --git a/atmospheric_explorer/ui/pages/Hovmoeller Plot.py b/atmospheric_explorer/ui/pages/Hovmoeller Plot.py index 773b39e..9b7730c 100644 --- a/atmospheric_explorer/ui/pages/Hovmoeller Plot.py +++ b/atmospheric_explorer/ui/pages/Hovmoeller Plot.py @@ -124,7 +124,8 @@ def _selectors(): return _var_selectors() -def page(): +def hovm_page(): + """Builds the Hovmoeller page.""" _init() var_name, plot_title = _selectors() build_sidebar() @@ -190,4 +191,4 @@ def page(): if __name__ == "__main__": - page() + hovm_page() diff --git a/atmospheric_explorer/ui/pages/Yearly Surface Plot.py b/atmospheric_explorer/ui/pages/Yearly Surface Plot.py index dcd0bb6..3348f7e 100644 --- a/atmospheric_explorer/ui/pages/Yearly Surface Plot.py +++ b/atmospheric_explorer/ui/pages/Yearly Surface Plot.py @@ -2,8 +2,6 @@ Module for creating GHG plots through UI """ # pylint: disable=invalid-name -from textwrap import dedent - import streamlit as st from atmospheric_explorer.api.loggers.loggers import atm_exp_logger @@ -109,7 +107,8 @@ def _selectors(): return v_name, title -def page(): +def yearly_surface_page(): + """Builds the Yearly Surface page.""" _init() var_name, plot_title = _selectors() build_sidebar() @@ -125,7 +124,9 @@ def page(): months = st.session_state[GHGSessionStateKeys.GHG_MONTHS] shapes = st.session_state[GeneralSessionStateKeys.SELECTED_SHAPES] data_variable = st.session_state[GHGSessionStateKeys.GHG_DATA_VARIABLE] - add_satellite_observations = st.session_state[GHGSessionStateKeys.GHG_ADD_SATELLITE] + add_satellite_observations = st.session_state[ + GHGSessionStateKeys.GHG_ADD_SATELLITE + ] with st.container(): with st.spinner("Downloading data and building plot"): st.plotly_chart( @@ -137,7 +138,8 @@ def page(): var_name=var_name, shapes=shapes, add_satellite_observations=( - add_satellite_observations and data_variable == "carbon_dioxide" + add_satellite_observations + and data_variable == "carbon_dioxide" ), ), use_container_width=True, @@ -145,4 +147,4 @@ def page(): if __name__ == "__main__": - page() + yearly_surface_page() diff --git a/atmospheric_explorer/ui/utils.py b/atmospheric_explorer/ui/utils.py index a49a79d..89af229 100644 --- a/atmospheric_explorer/ui/utils.py +++ b/atmospheric_explorer/ui/utils.py @@ -13,14 +13,14 @@ def local_css(filename: str | Path) -> None: """Load local css""" - logger.info("Loading local css") + atm_exp_logger.info("Loading local css") with open(filename, "r", encoding="utf-8") as style_file: st.markdown(f"", unsafe_allow_html=True) def page_init(): """Page initialization""" - logger.info("Initializing page %s", __file__) + atm_exp_logger.info("Initializing page %s", __file__) st.set_page_config( page_title="Atmospheric Explorer", page_icon=":earth_africa:", layout="wide" ) diff --git a/documentation/doc_files/doctrees/api.cache.doctree b/documentation/doc_files/doctrees/api.cache.doctree new file mode 100644 index 0000000000000000000000000000000000000000..0e8e8a6cc5aa335d2d979c5257d540f3793e3835 GIT binary patch literal 30746 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 literal 0 HcmV?d00001 diff --git a/documentation/doc_files/doctrees/api.data_interface.cams_interface.doctree b/documentation/doc_files/doctrees/api.data_interface.cams_interface.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8e9a0ca591e58720ef4c48a3678ddd60a4b67f55 GIT binary patch literal 40232 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 literal 62601 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 diff --git a/documentation/doc_files/doctrees/api.data_interface.eac4.doctree b/documentation/doc_files/doctrees/api.data_interface.eac4.doctree index 3cb96eec2f406786169f35af988faa591792f2b3..60a2102db43122cfa8ce9734093ed4b9c96f6740 100644 GIT binary patch literal 81500 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 diff --git a/documentation/doc_files/doctrees/api.data_interface.ghg.doctree b/documentation/doc_files/doctrees/api.data_interface.ghg.doctree index c40a82b24a3bd063ab91a0e46cd9b72a5f141df6..2ec9178b3f9b7149a94131e1de7d37c779b0f2c7 100644 GIT binary patch literal 64630 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 delta 9794 zcmcIqc~q2FmiIlNsG_QXLPVgdpb7*EWGTS~5f#)(;zG0&HN>@m!s1gAp)kRi290SU zL8IJ?(T=A(rZvmNHJp=9I-Yjv&SaUS)t;V7Ofu=Db7I==s4b^$7HNr$g>3nKk3jzCG>(GUefqwm(d#4FA0Y1F{7_%` zz`m2l?J*`tyVuiF=4e%E4S`vQbrAoutM5=!1kK_QfP@qrIV=?>+H_z{&0yQZM%ZBa;W+jbw%i3XlWXCj)Cq7d zISc-lnhx>DYI<|6NH_uF>`Cl>9ApnC1ktuM`25YGK}HJJ!x(X01h54q*qzT3q1iDP zu4ar!axU(QfJ4b1HX0jyHTrxSHI_qERUEi;lc399W_GSv+2r^A5Dg<-u%~lh1dNC) z;q?)4(UQ$q)ATVc0%j*<^9_RY(E5l$gW76+f6ZMNxIf_XMfLm;Kemm8;JsoVeCiY{ zV!d$GnFCcBVm(^tGa2cGD^)9PHfTC*W`DxT1Y8+4eq_ytS2KfCDs8#!l1BWhEt|(g zqJ*PWYCJiTz>%DIcE7HXqKgJPt$BP>B&>4GA?#Epd~R?w^5FHY102O&|hmN?k^`V8Jc14?*plrRB*lmHlOWCQC3CF-+0KAs0i zu&sOxHollkjWlGx7-FnN+HJK;KK_Gaxek7q=-AUrvF`C@q2K4=U+%&{n{T+H3ab)$ zLl7{i1g1wp{9A7Rj+oBpMS0&dI|Fqc7ZiULriSYT_M3sdF7L>e1EGj#)&7*04+H{7^tn^a;20H|)Yj4%Q0_tkgvgyz5FB`NtcT5jtrfYj zr87EKnJzg*NDj?qpApRwd-GuN-A>qin;s60o5(gG;c=`J3d+A_j|nCiK{)8((4ibr z#Q?(qDyQbLK2?Uhae0d;SQ+$I4L5ZX6Pqu>XGa5ie!@fSls1dMrGryd+3e3js@Q;9 zXXmsL>@6s&@ZfiA#VB()AsslqgEQ|#(jH&t!hlP;jHaM`gCB3WJt*J9uH`Ov1@ z5(`>f85Mn%h8m+G?XE1?QW-y-Ar-|GEwJ)!A~7W_1pNJ~@MwGyScKU%IjICGr}j{{ zIte;gSz*bvWSBT9#%V)}J<%&`6@PoPH=x+5s;~F9)@gN6CaojD;}=f`_hg|@!rm}t z8Lx;IzIXCK8ZVCqt8xH^H$&4c#)7VhDRCWqx%qd2oN8a-&W#Gcw%Ri3#V z*{bMQM?+~b6l>^_-kin>1(tB3x+jMIg$F`$qUC=zwbPW}Pehktb0Q9;m z2zF0CZomb4MF9V*1pc+#EE4$FkPsp&fUhQDrNxg`<^B=iwQdBSCaJ)u%yD8xd51n3 z^f^tR%wTC4mb#TJ@y!}(U%1360iy1BMn)m??z?y{d)Q&#=i`z2hSFx zXI?g6z)32~Ob=z%ck%>1>S9Wy1(y$}Tt%uqFCkc z)GG&;>iZgJ4`UqtKfH^gM5!L${0VNb()u-us0898lyJH^$s?4o1Y4vMJX8`&5TzSb z0*Yr>rK|>L+c@}-JN9Vd>Zv;y^EC)9Zdc?otM%~f=?9?m?kKoCXENK=yI>^m)>EkA zk7G>39Uf~KXTZ2H9^w{cp)VB7OQA;1-d7WBGG=w@MW|VYEd$j|7O^&Z@AU^%lSdVm zsMd~TG~IL&6A5{%%I`<4UF${!Xwo&-jxDOSQ%PDom2MDd5s$iRp3}fxe7BE(5d!zT z9tz6|Tr(V-KY>RXLPMi}x)EBM)F0Ye*dn2wO(g-XDBU=; zUlOdQ+N1EwlIQs{16Ve=d8>p8YZ$TF(mrD?W4z0t*>l`dT8*^EjU*%O13xziL*6EV zenwKj8tnrw7{c}eY(uK1a2f(}4^kQ!;+@`667m0(h=1fAoGbRsw~>Tq%p{nKcs&(S zTp4KW4U6~(1{q1}LWABjfb*$fB+;gz7Q_l^AGIt^mXYLZoF%nziAq8XqI82=K>w&SOu(VONEMCoJk_B3HWr?{(y&;I8PptiniB5^X1_*B!VU#T{4Vi z;pXNLHH)W#$HxejCdUZ3z#4+jO)(HQGgc*zu8*nD8_;~*#S3CIZM#h|L2c(#Q4R|P zGo(0?VLFyKw)*E|aUonO&t);f8pg#4x0@-MV~wm~eoW9BY&eema%FG;B@FlXoBVLF zk5Zc*G?`MH46%a|0ZgL!zTgz%@N$0+lu4Zk4xmFwza@!qs>78H!20CzK_IN6CL`+E z81ns+D2e38fFkViZ%9cxh7ho{xyakP5<`Hr?$ufy-dkx}3@-}Z_K)rn! z5M@l4{~6K`24r$v{~y_yTHYrj|ul9ZK-JB1b~QUK|j(PH9s;?N%)Z{-Habe zQRI&N%F?}*u}(P%OIDbmu<-!TG-_te(~WqJ{qN*-WUa=s5mXe`t(f&tuxu``FbZpX zciC9=yi|_!L+7P_bA=hDAc+B+Q9jIW%r~fe@)D!iJ#trAIMcy8?=X~kXtE{}0&7zR zl~(h}iZMQiC!4prp}4wGfB?2c^x}BE#Dw zR1#_trJGTU6h){ddKz6=Ib0&P9jtM}FBco(-`80wh^15455kzhk6Ohxargx;i$piq}&3Y$DE*hP`9*wio5 zS&1m!7rpzi%#c(bV|voSouVeVG=`#`PmrY=MZ12>5@z~KT-|`pRG=(@lfiDiiA=Rv z03Xm|-22(?g~R-Uoub z4+eRQ(!hB4eYMKU_z@FWTHXAx2`0DhGaW`ks#o=*N7lfiZDV1xznZ_VqVygOK<`16 za7uGoB`j$3(`>p=_`a>3UqyO&(2O<6ssBP z?=9nbvJ=y)YdSx@x02^aL0)?XuP~!m{ol87W#@2SYKG)B=|p7g znqj=*`b28Y@bfip?w3Sri3lsjZ>ClG67y`4wZDfL^j`(#)t7Kx*K@!p?kk!?w-rjh z89ySo4p51zKT}T_RMt)yRxGQaf1|>t?G?6^lu4deZ)=&Othv3cZn>wmwQLFA1bOTH zP;|o1cbMr8@s@-i^ez$S2qh8oYwf(J{EGOF7=MUG-@SH$8-noZwGYh-yT=ygu3(M=! zh^O?fAJX^X`Xa{9nT36RE$kDo|13LmAl@6v&xQ|$B@a&LF|pc-!hBX01w()#7QTA$ zRqmk95^jqXm(7K-;!un=I`fvt3fsrpfsMEE>R1u&Z|@rKBpiD5nw|mz4RUcS!t+9??| z5hI=)3lnzU!GABA9FwPNG{0Vi+Fdc{s-^{dG z!ji8&AMTGs7Ztae2dE@AGf^7YhH}kqrtn|8B1IABLt_=DGK=e0HZ?RZg;kGE#7DnF zkGh?*;hM9d=KVf<&(rEobgNFCiYLVMG<=MmEGQL*h`Mqe1rr?gjTrpgk170hw4n`d zd<7u0Bcs9wIy=6Ct5bKmAni$-6zT~w7;$1kL{*>sk7he%9BIuCTr%$Z`PI*AcS5#QzOTHG9d3 zr@$T@5aqVhQQiYCDHSu`4^EF!o&X`K4seKC*Y zBn+#(E1vcW_Lm@&4+N9X1(Od1lPiMB2Z9NYw+v)EGFe3E#yKhv+$;Dp*VU6~q+5s_HB$?nGl?kiLxMqXsho&@G>7qZ|RmppZi@2V;o_}^( z#KqMUQe>>r#nt`+35kR0-G2LVnFwN)Rb>rP)JL3t@(EYJ1NA=uR}b50hQgcMlhuvp zeG5Fd+ew!af7qR!+)lP6w~V=}S;<2tZ7n%_Ugn?Ry68%1PH5m?EaC2>O+)t~V%pt< zOq9O2Xzue5Dhc-yrJHdd5`_|_@P|_Egp5+qxf!K!cZhT;CP9Kg2_xL>p;?@t!Kf~kc(?5k1pOx^`1WLH#hoTJ9 zb$$5=dkV~M9S0rfs`=1_z7yw47$1`$;T}x`rCx?6K;?x*J~9F3U6{dA`gUJ9&iUGe zKHnD~M|Avz7Iixfp6shoUP3XH7wD5n?H2k>qfZrn+IkF4o)unt9d4x`!zeVj+c&ve zOzRd?y2WrzbeuFm&^7rgdq1sXXw%SJuMio1Ate?@b0Bi{5Y=d@-J4|G-%4tMB1j%DNUinn?gIL sLK~?<4yBNv37iyCk}{G$Ax=~c=ky?+PUb?<`Qm7i6{GsB-_2+L19NC3$^ZZW diff --git a/documentation/doc_files/doctrees/api.doctree b/documentation/doc_files/doctrees/api.doctree index b28dc557090e0eae022b343d8b53ccab05049908..89c78947b2f83503fab91db88ab0b2659fc8fa07 100644 GIT binary patch literal 20799 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 kTG3zu+Dgf@MKYf*XXQn2n4Y_Y17_NUDpF literal 34821 zcmeHQYm6jUb)J39dv|6Z_S);&G0oa$dKb^k+600ZSTKw2IBSoM4H(Hd?V9eIsjBVn z>aD8YosHwfPVi%r8$}9j1i}&oNJLOV5D`RCFdH0DA_xyD%0D2Ac}PKk2m$5s1Lr&U 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 aP5Mzg>~~0yu9Cu@=#T>-ZKYVw&;K7MPdqRH literal 0 HcmV?d00001 diff --git a/documentation/doc_files/doctrees/api.plotting.doctree b/documentation/doc_files/doctrees/api.plotting.doctree index 5813da11b21bceeea9a000db5354b4b27d947a30..04163766c51b38a3f4bad632831eaf7f3e7bc162 100644 GIT binary patch literal 89563 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
i`&#c?}>6 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 m^*K&m$tty%1xHS>C9gJ9eHpefR=4AR38>e6 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 + + + + + + api.cache package — Atmospheric Explorer 0.1.0.dev0 documentation + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/doc_files/html/api.data_interface.cams_interface.html b/documentation/doc_files/html/api.data_interface.cams_interface.html new file mode 100644 index 0000000..ddc7db4 --- /dev/null +++ b/documentation/doc_files/html/api.data_interface.cams_interface.html @@ -0,0 +1,235 @@ + + + + + + + api.data_interface.cams_interface package — Atmospheric Explorer 0.1.0.dev0 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

api.data_interface.cams_interface package

+
+

Submodules

+
+
+

api.data_interface.cams_interface.cams_interface module

+

This module collects classes to easily interact with data downloaded from CAMS ADS.

+
+
+class api.data_interface.cams_interface.cams_interface.CAMSDataInterface
+

Bases: ABC

+

Generic interface common to all CAMS datasets.

+
+
+build_call_body(parameters: CAMSParameters)
+

Build call body to be passed to cdsapi.

+
+ +
+
+classmethod clear_data_files() None
+

Clears all files inside data folder.

+
+ +
+
+data_folder: str = '/home/luigi/.atmospheric_explorer/data'
+
+ +
+
+dataset_name: str | None = None
+
+ +
+
+file_ext = None
+
+ +
+
+file_format = None
+
+ +
+
+classmethod list_data_files() list
+

Lists all files inside data folder.

+
+ +
+
+abstract read_dataset()
+

Returns the files as an xarray.Dataset.

+
+ +
+ +
+
+

api.data_interface.cams_interface.cams_parameters module

+

This module defines a generic base class to be used for the CAMS datasets parameters.

+
+
+class api.data_interface.cams_interface.cams_parameters.CAMSParameters(data_variables: str | set[str] | list[str])
+

Bases: Parameters

+

Base class to be used for CAMS dataset parameters.

+
+
+build_call_body() dict
+

Build the CDSAPI call body.

+
+ +
+
+property data_variables: str | set[str]
+

Time values are internally represented as a set, use this property to set/get its value.

+
+ +
+
+subset(other: CAMSParameters)
+

Return true if the parameters of this instance are equal or a subset of other.

+
+
+other
+

the other instance of CAMSParameters to be compared with self

+
+
Type:
+

CAMSParameters

+
+
+
+ +
+ +
+ +
+
+

Module contents

+

Module containing the generic CAMS dataset interface.

+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2023, Elisa Aliverti, Luigi Brancati, Giulia Fiantanese.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/documentation/doc_files/html/api.data_interface.eac4.html b/documentation/doc_files/html/api.data_interface.eac4.html index d45f5c0..e3beecd 100644 --- a/documentation/doc_files/html/api.data_interface.eac4.html +++ b/documentation/doc_files/html/api.data_interface.eac4.html @@ -53,10 +53,7 @@
  • EAC4Instance.file_ext
  • EAC4Instance.file_format
  • EAC4Instance.file_full_path
  • -
  • EAC4Instance.model_level
  • -
  • EAC4Instance.pressure_level
  • EAC4Instance.read_dataset()
  • -
  • EAC4Instance.time_values
  • @@ -64,7 +61,19 @@
  • api.data_interface.eac4.eac4_config module +
  • +
  • api.data_interface.eac4.eac4_parameters module @@ -108,8 +117,8 @@

    Submodules
    -class api.data_interface.eac4.eac4.EAC4Instance(data_variables: str | set[str] | list[str], dates_range: str, time_values: str | set[str] | list[str], files_dir: str | None = None, area: list[int] | None = None, pressure_level: str | set[str] | list[str] | None = None, model_level: str | set[str] | list[str] | None = None)
    -

    Bases: CAMSDataInterface

    +class api.data_interface.eac4.eac4.EAC4Instance(data_variables: set[str] | list[str], dates_range: str, time_values: set[str] | list[str], files_dir: str | None = None, area: list[int] | None = None, pressure_level: set[str] | list[str] | None = None, model_level: set[str] | list[str] | None = None) +

    Bases: CAMSDataInterface, Cached

    Interface for CAMS global reanalysis (EAC4) and CAMS global reanalysis (EAC4) monthly averaged fields datasets.

    See https://confluence.ecmwf.int/display/CKB/CAMS%3A+Reanalysis+data+documentation#heading-CAMSglobalreanalysisEAC4Parameterlistings for a full list of parameters and more details about the dataset.

    @@ -146,30 +155,12 @@

    Submodules -
    -property model_level: str | list[str] | None
    -

    Model level is internally represented as a set, use this property to set/get its value.

    -
    - -
    -
    -property pressure_level: str | list[str] | None
    -

    Pressure level is internally represented as a set, use this property to set/get its value.

    -
    -
    read_dataset() Dataset

    Returns data as an xarray.Dataset.

    -
    -
    -property time_values: str | list[str]
    -

    Time values are internally represented as a set, use this property to set/get its value.

    -
    -

    @@ -179,18 +170,70 @@

    Submodules
    class api.data_interface.eac4.eac4_config.EAC4Config(*args, **kwargs)
    -

    Bases: object

    -

    This class is needed to implement a singleton pattern so that config is loaded only once.

    +

    Bases: DatasetConfigParser

    +

    This class is needed to implement a singleton pattern so that EAC4 dataset config is loaded only once.

    classmethod convert_units_array(array: DataArray, data_variable: str) DataArray

    Converts an xarray.DataArray from its original units to the units specified in the eac4_config.yaml file.

    +
    + + +
    +

    api.data_interface.eac4.eac4_parameters module

    +

    This module collects classes to easily interact with data downloaded from CAMS ADS.

    +
    +
    +class api.data_interface.eac4.eac4_parameters.EAC4Parameters(data_variables: set[str] | list[str], dates_range: str, time_values: set[str] | list[str], area: list[int] | None = None, pressure_level: set[str] | list[str] | None = None, model_level: set[str] | list[str] | None = None)
    +

    Bases: CAMSParameters

    +

    Parameters for EAC4 dataset.

    -
    -classmethod get_config() dict
    -

    Function to get the actual config object.

    +
    +static area_issubset(area1: list, area2: list) bool
    +

    Check if the first area is a subset of the second area.

    +
    + +
    +
    +build_call_body() dict
    +

    Build the CDSAPI call body.

    +
    + +
    +
    +static dates_issubset(date_range1, date_range2)
    +

    Check if the first date range is a subset of the second date range.

    +
    + +
    +
    +static model_issubset(ml1: set | None, ml2: set | None) bool
    +

    Check if the first model level is a subset of the second model level.

    +
    + +
    +
    +static pressure_issubset(pl1: set | None, pl2: set | None) bool
    +

    Check if the first pressure level is a subset of the second pressure level.

    +
    + +
    +
    +subset(other: EAC4Parameters) bool
    +

    Return true if the parameters of this instance are equal or a subset of other.

    +
    +
    +other
    +

    the other instance of EAC4Parameters to be compared with self

    +
    +
    Type:
    +

    EAC4Parameters

    +
    +
    +
    +
    diff --git a/documentation/doc_files/html/api.data_interface.ghg.html b/documentation/doc_files/html/api.data_interface.ghg.html index 5bbf76a..050c40a 100644 --- a/documentation/doc_files/html/api.data_interface.ghg.html +++ b/documentation/doc_files/html/api.data_interface.ghg.html @@ -53,9 +53,7 @@
  • InversionOptimisedGreenhouseGas.file_ext
  • InversionOptimisedGreenhouseGas.file_format
  • InversionOptimisedGreenhouseGas.file_full_path
  • -
  • InversionOptimisedGreenhouseGas.month
  • InversionOptimisedGreenhouseGas.read_dataset()
  • -
  • InversionOptimisedGreenhouseGas.year
  • @@ -63,12 +61,20 @@
  • api.data_interface.ghg.ghg_config module
  • +
  • api.data_interface.ghg.ghg_parameters module +
  • Module contents
  • @@ -105,11 +111,11 @@

    Submodules

    api.data_interface.ghg.ghg module

    -

    This module collects classes to easily interact with GHG data downloaded from CAMS ADS.

    +

    This module collects classes to easily interact with Greenhouse gasses data downloaded from CAMS ADS.

    -class api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas(data_variables: str, quantity: str, input_observations: str, time_aggregation: str, year: str | set[str] | list[str], month: str | set[str] | list[str], files_dir: str | None = None, version: str = 'latest')
    -

    Bases: CAMSDataInterface

    +class api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas(data_variables: str, quantity: str, input_observations: str, time_aggregation: str, year: set[str] | list[str], month: set[str] | list[str], files_dir: str | None = None, version: str = 'latest') +

    Bases: CAMSDataInterface, Cached

    Interface for CAMS global inversion-optimised greenhouse gas fluxes and concentrations dataset.

    See https://ads.atmosphere.copernicus.eu/cdsapp#!/dataset/cams-global-greenhouse-gas-inversion?tab=overview for a full list of parameters and more details about the dataset

    @@ -147,12 +153,6 @@

    Submodules -
    -property month: str | list[str]
    -

    Month is internally represented as a set, use this property to set/get its value.

    -

    -
    read_dataset() Dataset
    @@ -162,12 +162,6 @@

    Submodules -
    -property year: str | list[str]
    -

    Year is internally represented as a set, use this property to set/get its value.

    -

    - @@ -177,20 +171,14 @@

    Submodules
    class api.data_interface.ghg.ghg_config.GHGConfig(*args, **kwargs)
    -

    Bases: object

    -

    This class is needed to implement a singleton pattern so that config is loaded only once.

    +

    Bases: DatasetConfigParser

    +

    This class is needed to implement a singleton pattern so that GHG dataset config is loaded only once.

    classmethod convert_units_array(array: DataArray, data_variable: str, quantity: str, time_aggregation: str) DataArray

    Converts an xarray.DataArray from its original units to the units specified in the ghg_config.yaml file.

    -
    -
    -classmethod get_config() dict
    -

    Function to get the actual config object.

    -
    -
    classmethod get_var_names(data_variable: str, quantity: str, time_aggregation: str) list[str]
    @@ -199,6 +187,40 @@

    Submodules +

    api.data_interface.ghg.ghg_parameters module

    +

    This module collects classes to easily interact with data downloaded from CAMS ADS.

    +
    +
    +class api.data_interface.ghg.ghg_parameters.GHGParameters(data_variables: str, quantity: str, input_observations: str, time_aggregation: str, year: set[str] | list[str], month: set[str] | list[str], version: str = 'latest')
    +

    Bases: CAMSParameters

    +

    Parameters for Global Inversion dataset.

    +
    +
    +build_call_body() dict
    +

    Build the CDSAPI call body.

    +
    + +
    +
    +subset(other: GHGParameters) bool
    +

    Return true if the parameters of this instance are equal or a subset of other.

    +
    +
    +other
    +

    the other instance of GHGParameters to be compared with self

    +
    +
    Type:
    +

    GHGParameters

    +
    +
    +
    + +
    + +
    +

    Module contents

    diff --git a/documentation/doc_files/html/api.data_interface.html b/documentation/doc_files/html/api.data_interface.html index ee00949..77965a4 100644 --- a/documentation/doc_files/html/api.data_interface.html +++ b/documentation/doc_files/html/api.data_interface.html @@ -46,36 +46,26 @@
  • api.data_interface package
  • -
    -

    api.data_interface.cams_interface module

    -

    This module collects classes to easily interact with data downloaded from CAMS ADS.

    -
    -
    -class api.data_interface.cams_interface.CAMSDataInterface(data_variables: str | set[str] | list[str])
    -

    Bases: ABC

    -

    Generic interface common to all CAMS datasets.

    -
    -
    -classmethod clear_data_files() None
    -

    Clears all files inside data folder.

    -
    - -
    -
    -data_folder: str = '/home/luigi/.atmospheric_explorer/data'
    -
    - -
    -
    -property data_variables: str | list[str]
    -

    Time values are internally represented as a set, use this property to set/get its value.

    +
    +

    api.data_interface.data_transformations module

    +

    Data transformations needed for the plotting APIs.

    +
    +
    +api.data_interface.data_transformations.clip_and_concat_shapes(data_frame: Dataset, shapes: Selection) Dataset
    +

    Clips data_frame keeping only shapes specified. Shapes_df must be a GeoDataFrame.

    -
    -
    -dataset_name: str | None = None
    -
    - -
    -
    -file_ext = None
    -
    - -
    -
    -file_format = None
    -
    - -
    -
    -classmethod list_data_files() list
    -

    Lists all files inside data folder.

    +
    +
    +api.data_interface.data_transformations.confidence_interval(array: list | ndarray) ndarray
    +
    +api.data_interface.data_transformations.confidence_interval(array: DataArray, dim: str) DataArray
    +

    Compute the confidence interval for an array of samples.

    -
    -
    -abstract read_dataset()
    -

    Returns the files as an xarray.Dataset.

    +
    +
    +api.data_interface.data_transformations.shifting_long(data_set=<class 'xarray.core.dataset.Dataset'>) Dataset
    +

    Shifts longitude to range [-180, +180].

    +
    +
    +api.data_interface.data_transformations.split_time_dim(dataset: Dataset, time_dim: str)
    +

    Return a new dataset where the time_dim dimension is split into times and dates dimensions.

    -
    -

    api.data_interface.config_parser module

    +
    +

    api.data_interface.dataset_config_parser module

    Module to manage constants.

    This module defines a Singleton, in this way the file constants.cfg is loaded only once. The singleton pattern was taken from here -https://refactoring.guru/design-patterns/singleton/python/example#example-0

    +https://refactoring.guru/design-patterns/singleton/python/example#example-0 .

    -
    -class api.data_interface.config_parser.ConfigMeta(*args, **kwargs)
    -

    Bases: type

    -

    This meta class is needed to implement a singleton pattern so that the config files are loaded only once.

    +
    +class api.data_interface.dataset_config_parser.DatasetConfigParser(**kwargs)
    +

    Bases: object

    +

    This class implements some basic functionalities to parse a YAML file containing the configuration for a dataset.

    +

    For reference, check the file atmospheric_explorer/api/data_interface/ghg/ghg_config.yaml.

    +

    A dataset configuration file is expected to have data structured in the following way +variables:

    +
    +
    +
    data_variable_name:

    var_name: # name of the dataset column +conversion:

    +
    +

    conversion_factor: … +conversion_unit: …

    +
    +
    +
    +
    +
    +
    +classmethod get_config() dict
    +

    Function to get the actual config object.

    +
    + +
    +
    +parse_factors(data_dict: dict)
    +

    Parse conversion factors in a dataset config file.

    +

    The file is expected to have a ‘conversion’ section.

    +
    +
    -
    -class api.data_interface.config_parser.OperationParser
    +
    +class api.data_interface.dataset_config_parser.OperationParser

    Bases: object

    Parser for arithmetic operations.

    Code for this class was taken from https://stackoverflow.com/questions/20748202/valueerror-malformed-string-when-using-ast-literal-eval

    -
    -allowed_ops = {<class 'ast.Add'>: <built-in function add>, <class 'ast.Div'>: <built-in function truediv>, <class 'ast.Mod'>: <built-in function mod>, <class 'ast.Mult'>: <built-in function mul>, <class 'ast.Sub'>: <built-in function sub>}
    +
    +allowed_ops = {<class 'ast.Add'>: <built-in function add>, <class 'ast.Div'>: <built-in function truediv>, <class 'ast.Mod'>: <built-in function mod>, <class 'ast.Mult'>: <built-in function mul>, <class 'ast.Sub'>: <built-in function sub>}
    -
    -arithmetic_eval(operation: str) str
    +
    +arithmetic_eval(operation: str) str
    arithmetic_eval(operation: int | float) int | float
    @@ -269,36 +305,6 @@

    Submodules -

    api.data_interface.data_transformations module

    -

    Data transformations needed for the plotting APIs.

    -
    -
    -api.data_interface.data_transformations.clip_and_concat_shapes(data_frame: Dataset, shapes: Selection) Dataset
    -

    Clips data_frame keeping only shapes specified. Shapes_df must be a GeoDataFrame.

    -
    - -
    -
    -api.data_interface.data_transformations.confidence_interval(array: list | ndarray) ndarray
    -
    -api.data_interface.data_transformations.confidence_interval(array: DataArray, dim: str) DataArray
    -

    Compute the confidence interval for an array of samples.

    -
    - -
    -
    -api.data_interface.data_transformations.shifting_long(data_set=<class 'xarray.core.dataset.Dataset'>) Dataset
    -

    Shifts longitude to range [-180, +180].

    -
    - -
    -
    -api.data_interface.data_transformations.split_time_dim(dataset: Dataset, time_dim: str)
    -

    Split datetime dimension into times and dates.

    -
    -

    Module contents

    diff --git a/documentation/doc_files/html/api.html b/documentation/doc_files/html/api.html index 3729855..56fe668 100644 --- a/documentation/doc_files/html/api.html +++ b/documentation/doc_files/html/api.html @@ -51,25 +51,17 @@
  • OperationNotAllowed
  • -
  • api.loggers module
  • @@ -184,7 +184,7 @@

    Submodules
    -api.plotting.yearly_flux.ghg_surface_satellite_yearly_plot(data_variable: str, var_name: str, years: list[str], months: list[str], title: str, shapes: Selection = None, add_satellite_observations: bool = True) Figure
    +api.plotting.yearly_flux.ghg_surface_satellite_yearly_plot(data_variable: str, var_name: str, years: set[str] | list[str], months: set[str] | list[str], title: str, shapes: Selection = None, add_satellite_observations: bool = True) Figure

    Generates a yearly mean plot with CI for a quantity from the CAMS Global Greenhouse Gas Inversion dataset.

    Note that we are only considering surface_flux quantities in this function.

    diff --git a/documentation/doc_files/html/api.shape_selection.html b/documentation/doc_files/html/api.shape_selection.html index 8c0a19e..af8a7f5 100644 --- a/documentation/doc_files/html/api.shape_selection.html +++ b/documentation/doc_files/html/api.shape_selection.html @@ -80,13 +80,14 @@
  • api.shape_selection.shapefile module
      +
    • ShapefileParameters +
    • ShapefilesDownloader - + - +
    • subset() (api.cache.cache.Parameters method) -

      T

      - - -
      - -

      Y

      - -
      diff --git a/documentation/doc_files/html/modules.html b/documentation/doc_files/html/modules.html index 60890f1..5b4cc70 100644 --- a/documentation/doc_files/html/modules.html +++ b/documentation/doc_files/html/modules.html @@ -76,15 +76,27 @@

      api
    • api package
      • Subpackages
          +
        • api.cache package +
        • api.data_interface package
        • +
        • api.loggers package +
        • api.plotting package
        • -
        • api.loggers module
            -
          • Loggers -
          • -
          • LoggersMeta
              -
            • LoggersMeta.logging_config
            • -
            • LoggersMeta.logs_root_dir
            • +
            • api.local_folder module
            • -
            • get_logger()
            • +
            • api.os_utils module
            • -
            • api.os_manager module
                -
              • create_folder()
              • -
              • get_local_folder()
              • -
              • remove_folder()
              • +
              • api.singleton module
              • Module contents
              • diff --git a/documentation/doc_files/html/objects.inv b/documentation/doc_files/html/objects.inv index 8124431fa2421277c1a5dcce1f3770a1174c60c3..fadf607c003bf8ebd66ce5adb339e0cd72826497 100644 GIT binary patch delta 1692 zcmV;N24nex42lkrf`83h&2Hm15WeRrRJ7L!TVPLnOTC6`VAl!aEDH2O(9+l-Mu!Sl11xkFEv38)AQ#5vpegrD(^((}kPd&Jr`xbC5luAl}gwV+5li zaBT&al9OE~;eTBWQIi)XYBImCC3;8@yviTIiap~oerH1{5%S0IRSq~rg`do9M%vho z=3g)?zP#Q2o8er|Y|-9$vFx`aS3Y7+@UC#al(Q7LJl)t3CZk&{k^Khr1UKX9^GC^X zBFr*FSRx#RH(ju5$0I4iIyhxXfF%+UPR(%cvn0?m-G9v)YQG8WLP!EIl1EHuU=b17 zOOhNAWyxU<{FJIVT#^`2NaE=PdSf*quXZqD9NED*U*P(mP3a;pd@8x#Si%O|-o@b` zN~voz#0_6wg_M$HT)3xvR7+g$3o~_L3*T4kJo^qIVyUoL;~emDA|ushBtTG1(Xkx} zIQV$Yoqx5AjZf!tvjFxu{=YfZ+i~ZlZlx+-wNEk35$}#$)vl4)=|e%^*G8B zA_c;n`{2a`zA5T!(6K!c4k)$_HBx2t@0h1|p22(1iZi|uge^4y}l zhtSkH(DlFM|JxqjKzLiVXz>}PF|Y$*wtpl!P>`yV#+y=NN#-QqXq(%Bi&wtR{ zSyTb|5xgSF5I^ju*x@^DlbQ$#pzlORC?H7fRk}({LKIl}u70K`-B=Ri155*izcWZA zQTt24DC0uyG=O%P4@W(z)kB%Jr`hYs(X1il`@TB&>kS+UHP0y}YO)s+gGMI5fJ6c) z7}>UMt@=(A+cG@?`gkaRdoK6_M}MI0O{)`l5Td_$+`Oze%gxKSYol>O@NM(*yk5?Z zkG9`_n~7t!+$^8hi&MOXUYf(N+s{k2G|1Do&N138uV9zw#%7D^ z;h$5wJ!dh}OP|y`s*YK1#do@C6AzF8C&65tD+9mP#q;<{C&S#{(oLFoXn!v1mTjJn ztL%n2m8Y3Z&d+OVc}rBxk9R=lqRGL#UJILqpN}ZZiWYRl6qC@)U#J{`$?!VQ>29sp zX#V#6t3M?*&dx66{_K!Lj-JHOR0i?6$lhOOI2=Ov-x0VZ(bE=-JafJ0#U03OWM%a^ z(J^@r+i<;g8!!*!`7-GyWPcf_?zloC1f%M7S>e<3No3HPRnNO!P3Dx+es8|6j_wH= zehOLO-B)$`hnM2uFLe~IeMQ!itUe6#YG0bwv|8W(wA4FZQuDgL80rOVf1C3*e#fi5 zp2g=y{p|URZf|pzm)TzLn|J;A`fB=Z_;V88>$@hefuHlxEyQ~6(0^;>x@)>+%C4o{ z$PIYSz{X(Six75_ycIb)XE~6 z_aI{?(ntrV-ttwofdl{VpMQm?w6a*mF!}YwPj_mfbr7eg`*Vds{%U5ZF~LmEM!y$D mWkc_32v?w=PHHnrniUKJFb|@pqA4>vTT}m}yY7EgvO9xO;9g4r delta 1426 zcmV;D1#S9@4uTAjf`7eQ&5q+X48G4(xM;6qY=J%PEzJgLfSt@>GK&Je2#U?bLS@O7 z5YxMQ{B$X`3b`mA=pK`Nh>ibBMCMDW9BSCiflJYpG5PS-7M#4m;^k+Nr{7dls zWX2-SHe}mMG{y=e!W4)Nj^JX->Ejj%VT4oZ5ip!iURGbV#DAhm5m78<@at8Ep3q@L z0Si>j+7Jq|Fb7n^V-IAOhSL~?;lWA?vd$IwqS|3W@Vc}r6FdW<4xY#)D8z6Iz6~>8 zLaC!fA2$0tyd#_fH^Sb?{?F zID4y9_JJE>N`Lteh!M}^(73tHBZx}0_eg|bhv^Vs7Pm@ZCT+ZH5;=lPU9dXek_=%M zBW+!T6_N?gY{+kN78#jw^tY=Q1B<{)mTNRJi%iHy5w=B?v#k&Oj1q+uNdd(q9Yz2+ z`h3r=My8E#3pp=<;cPPfykJsccDM4^;GIybdKHs4b$@s$OUtU*n}+{jl;ikb2I!05 zq2LapXkBZEvE-EMl&X^ZTcW-^I?#b43Ok_wNHPIZ<^p{4blETTh+Vw5y>32o}wPOk>a8Br2pA@E^5KREcdI%72!ZV1gbxx4sUq|MqJ zvC+}Og@0LDscY$LrbCcOi01-;B|Mif$MTL>E3#oM9gES}tSyVvXtXVh)mVHTir1)= zt&7>XtnG{2SiC7`kEH8Tj>h6V02ZTJ*s>^f%VftiKL35*svvdKwTA6ejCymmFGPbV zc32ciPnH)QI)dcw|Y)98eF(X z*|H(3M=uxT<~*~OcC6dHqWRUC&ea;aCtr-2vw9s>=gGMZghYb#tL55>aJn{UC-{>Y zT%r^+ymj={T#l~A!$_w_1&!`f2pdUmfC?ZJaYx77BLX=p?uDPcNV=PH^Ob-KuzjI)U=vOJ?lnbd}DnM&2 zx}%ychgNGt^L|_UWen zqk9zJ;h`pOfq@{AFV4AzUzuS)apGj~9W8FsVnGYhaoPMVT&*{xnL2nB^v3j- z9It`;qA9>#z4k%)g-3Z_THx4I3_`E2Q00Na@TO@tUamK2;p%uLzm~1e%YO~}De8m{ zBOE1Tyg@e@iiUuHo~y0lF-bPW{C|4j-4^p6V_7sRH=^vYdm|&#TI6@-s>jF9$Kce_ zhU->G+YjUMgUCb3azV%Aiiwn*>O*afUws_zkSYVI;x93eg6F!6V=jUWM}O%vvg@bP zvnh_BQUcens8<2)uO+uptIvCLmfx${DrxEs>MpahQuF0Bd*^C#e<1Txy%x? ziwkCZZG!;u-#`D#K+nokUF?fy_D@f`N;-(M!}IZhMSij6)~aADt86M!4poLeH4yGV gza4b?W2X#K*RLo!ooL7GoC}qG{$`f$e`!aT0+nUUNB{r; diff --git a/documentation/doc_files/html/py-modindex.html b/documentation/doc_files/html/py-modindex.html index da431c1..6f90f3e 100644 --- a/documentation/doc_files/html/py-modindex.html +++ b/documentation/doc_files/html/py-modindex.html @@ -85,6 +85,16 @@

                Python Module Index

                api + + +     + api.cache + + + +     + api.cache.cache +     @@ -98,18 +108,28 @@

                Python Module Index

                    - api.data_interface.cams_interface + api.data_interface.cams_interface + + + +     + api.data_interface.cams_interface.cams_interface     - api.data_interface.config_parser + api.data_interface.cams_interface.cams_parameters     api.data_interface.data_transformations + + +     + api.data_interface.dataset_config_parser +     @@ -125,6 +145,11 @@

                Python Module Index

                    api.data_interface.eac4.eac4_config + + +     + api.data_interface.eac4.eac4_parameters +     @@ -140,6 +165,11 @@

                Python Module Index

                    api.data_interface.ghg.ghg_config + + +     + api.data_interface.ghg.ghg_parameters +     @@ -148,12 +178,27 @@

                Python Module Index

                    - api.loggers + api.local_folder + + + +     + api.loggers + + + +     + api.loggers.loggers + + + +     + api.loggers.loggers_utils     - api.os_manager + api.os_utils @@ -200,6 +245,11 @@

                Python Module Index

                    api.shape_selection.shapefile + + +     + api.singleton + diff --git a/documentation/doc_files/html/searchindex.js b/documentation/doc_files/html/searchindex.js index 7e1b4d8..fcbcb37 100644 --- a/documentation/doc_files/html/searchindex.js +++ b/documentation/doc_files/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({"docnames": ["api", "api.data_interface", "api.data_interface.eac4", "api.data_interface.ghg", "api.plotting", "api.shape_selection", "index", "modules"], "filenames": ["api.rst", "api.data_interface.rst", "api.data_interface.eac4.rst", "api.data_interface.ghg.rst", "api.plotting.rst", "api.shape_selection.rst", "index.rst", "modules.rst"], "titles": ["api package", "api.data_interface package", "api.data_interface.eac4 package", "api.data_interface.ghg package", "api.plotting package", "api.shape_selection package", "Welcome to Atmospheric Explorer\u2019s documentation!", "api"], "terms": {"data_interfac": [0, 7], "eac4": [0, 1, 4], "eac4_config": [0, 1], "ghg": [0, 1, 4], "ghg_config": [0, 1], "cams_interfac": [0, 7], "camsdatainterfac": [0, 1, 2, 3], "clear_data_fil": [0, 1], "data_fold": [0, 1], "data_vari": [0, 1, 2, 3, 4], "dataset_nam": [0, 1, 2, 3], "file_ext": [0, 1, 2, 3], "file_format": [0, 1, 2, 3], "list_data_fil": [0, 1], "read_dataset": [0, 1, 2, 3], "config_pars": [0, 7], "configmeta": [0, 1], "operationpars": [0, 1], "allowed_op": [0, 1], "arithmetic_ev": [0, 1], "data_transform": [0, 7], "clip_and_concat_shap": [0, 1], "confidence_interv": [0, 1], "shifting_long": [0, 1], "split_time_dim": [0, 1], "plot": [0, 1, 7], "anomali": [0, 7], "eac4_anomalies_plot": [0, 4], "hovmoel": [0, 7], "eac4_hovmoeller_plot": [0, 4], "plot_util": [0, 7], "hex_to_rgb": [0, 4], "hovmoeller_plot": [0, 4], "line_with_ci_subplot": [0, 4], "save_plotly_to_imag": [0, 4], "sequential_colorscale_bar": [0, 4], "yearly_flux": [0, 7], "ghg_surface_satellite_yearly_plot": [0, 4], "shape_select": [0, 7], "selectionlevel": [0, 5], "contin": [0, 5], "countri": [0, 4, 5], "countries_sub": [0, 5], "gener": [0, 1, 4, 5], "organ": [0, 5], "entityselect": [0, 4, 5], "convert_select": [0, 5], "from_entities_list": [0, 5], "from_entity_select": [0, 5], "from_generic_select": [0, 5], "genericshapeselect": [0, 4, 5], "from_shap": [0, 5], "select": [0, 1, 3, 4, 5], "empti": [0, 5], "get_event_label": [0, 5], "label": [0, 4, 5], "from_out_ev": [0, 5], "selection_empti": [0, 5], "shapefil": [0, 7], "shapefilesdownload": [0, 5], "cach": [0, 5], "clear_cach": [0, 5], "download": [0, 1, 2, 3, 5], "find_cach": [0, 5], "get_as_datafram": [0, 5], "is_cach": [0, 5], "shapefile_dir": [0, 5], "shapefile_full_path": [0, 5], "shapefile_nam": [0, 5], "shapefile_url": [0, 5], "dissolve_shapefile_level": [0, 5], "gather": 0, "custom": 0, "operationnotallow": [0, 7], "base": [0, 1, 2, 3, 5], "us": [0, 1, 2, 3, 4, 5], "when": [0, 1, 5], "pars": [0, 1], "python": [0, 1], "oper": [0, 1], "from": [0, 1, 2, 3, 4, 5], "string": [0, 1], "manag": [0, 1, 5], "log": 0, "class": [0, 1, 2, 3, 5], "arg": [0, 1, 2, 3], "kwarg": [0, 1, 2, 3, 5], "object": [0, 1, 2, 3, 5], "thi": [0, 1, 2, 3, 4, 5], "i": [0, 1, 2, 3, 4, 5], "need": [0, 1, 2, 3, 5], "implement": [0, 1, 2, 3], "singleton": [0, 1, 2, 3], "pattern": [0, 1, 2, 3], "so": [0, 1, 2, 3], "load": [0, 1, 2, 3], "onli": [0, 1, 2, 3, 4], "onc": [0, 1, 2, 3], "classmethod": [0, 1, 2, 3, 5], "clear_log": [0, 7], "list": [0, 1, 2, 3, 4, 5], "clear": [0, 1, 5], "all": [0, 1, 3, 5], "file": [0, 1, 2, 3, 5], "get_logg": [0, 7], "str": [0, 1, 2, 3, 4, 5], "function": [0, 1, 2, 3, 4], "get": [0, 1, 2, 3, 5], "list_log": [0, 7], "loggersmeta": [0, 7], "type": [0, 1, 5], "meta": [0, 1], "logging_config": [0, 7], "disable_existing_logg": 0, "fals": [0, 4], "formatt": 0, "simpl": 0, "format": [0, 4], "asctim": 0, "": [0, 3], "levelnam": 0, "messag": 0, "verbos": 0, "process": 0, "d": 0, "thread": 0, "handler": 0, "consol": 0, "streamhandl": 0, "rotatingfil": 0, "backupcount": 0, "100": 0, "rotatingfilehandl": 0, "filenam": [0, 2, 3], "home": [0, 1, 2, 3], "luigi": [0, 1, 2, 3], "atmospheric_explor": [0, 1, 2, 3], "logconfig": 0, "maxbyt": 0, "51200": 0, "atmexp": 0, "level": [0, 2, 4, 5], "info": 0, "propag": 0, "0": [0, 1, 5], "qualnam": [0, 5], "root": 0, "warn": 0, "version": [0, 3], "1": [0, 5], "logs_root_dir": [0, 7], "util": [0, 4], "create_fold": [0, 7], "folder": [0, 1, 5], "none": [0, 1, 2, 3, 4, 5], "creat": 0, "doesn": 0, "t": 0, "exist": 0, "get_local_fold": [0, 7], "return": [0, 1, 2, 3, 4, 5], "where": [0, 3], "put": 0, "local": 0, "user": 0, "o": 0, "remove_fold": [0, 7], "remov": 0, "contain": [0, 1, 2, 3, 4, 5], "atmospher": [0, 1, 2, 3, 4, 5], "explor": [0, 1, 2, 3, 4, 5], "eac4inst": [1, 2], "dataset_dir": [1, 2, 3], "file_full_path": [1, 2, 3], "model_level": [1, 2, 4], "pressure_level": [1, 2, 4], "time_valu": [1, 2, 4], "eac4config": [1, 2], "convert_units_arrai": [1, 2, 3], "get_config": [1, 2, 3], "inversionoptimisedgreenhousega": [1, 3], "month": [1, 3, 4], "year": [1, 3, 4], "ghgconfig": [1, 3], "get_var_nam": [1, 3], "collect": [1, 2, 3], "easili": [1, 2, 3], "interact": [1, 2, 3, 5], "data": [1, 2, 3, 4, 5], "cam": [1, 2, 3, 4], "ad": [1, 2, 3], "set": [1, 2, 3], "abc": 1, "interfac": [1, 2, 3, 5], "common": 1, "dataset": [1, 2, 3, 4], "insid": [1, 3], "properti": [1, 2, 3, 5], "time": [1, 2, 3, 4], "valu": [1, 2, 3, 4, 5], "ar": [1, 2, 4, 5], "intern": [1, 2, 3], "repres": [1, 2, 3], "its": [1, 2, 3, 4], "abstract": 1, "an": [1, 2, 3, 4, 5], "xarrai": [1, 2, 3, 4], "constant": 1, "defin": [1, 5], "wai": 1, "cfg": 1, "The": 1, "wa": 1, "taken": [1, 5], "here": 1, "http": [1, 2, 3], "refactor": 1, "guru": 1, "design": 1, "exampl": [1, 4], "config": [1, 2, 3, 7], "parser": 1, "arithmet": 1, "code": 1, "stackoverflow": 1, "com": 1, "question": 1, "20748202": 1, "valueerror": 1, "malform": 1, "ast": 1, "liter": 1, "eval": 1, "add": [1, 3, 4, 5], "built": 1, "div": 1, "truediv": 1, "mod": 1, "mult": 1, "mul": 1, "sub": [1, 5], "int": [1, 2, 4, 5], "float": [1, 4], "pass": [1, 5], "transform": 1, "data_fram": 1, "shape": [1, 4, 5], "clip": 1, "keep": 1, "specifi": [1, 2, 3, 4], "shapes_df": 1, "must": 1, "geodatafram": [1, 5], "arrai": [1, 2, 3], "ndarrai": 1, "dataarrai": [1, 2, 3], "dim": 1, "comput": [1, 4], "confid": [1, 4], "interv": [1, 4], "sampl": 1, "data_set": 1, "core": 1, "shift": 1, "longitud": 1, "rang": 1, "180": 1, "time_dim": 1, "split": 1, "datetim": 1, "dimens": [1, 3, 4], "date": 1, "access": [1, 2, 3], "dates_rang": [2, 4], "files_dir": [2, 3], "area": 2, "global": [2, 3, 4], "reanalysi": [2, 4], "monthli": [2, 4], "averag": 2, "field": 2, "see": [2, 3], "confluenc": 2, "ecmwf": 2, "displai": 2, "ckb": 2, "3a": 2, "document": 2, "head": 2, "camsglobalreanalysiseac4parameterlist": 2, "full": [2, 3, 5], "paramet": [2, 3, 4], "more": [2, 3, 5], "detail": [2, 3], "about": [2, 3], "save": [2, 3, 4, 5], "cdsapi": [2, 3], "nc": 2, "netcdf": [2, 3], "name": [2, 3, 5], "model": [2, 4], "pressur": [2, 4], "convert": [2, 3, 4, 5], "origin": [2, 3], "unit": [2, 3, 4], "yaml": [2, 3], "dict": [2, 3, 4], "actual": [2, 3], "quantiti": [3, 4], "input_observ": [3, 4], "time_aggreg": 3, "latest": 3, "invers": [3, 4], "optimis": 3, "greenhous": [3, 4], "ga": [3, 4], "flux": [3, 4], "concentr": 3, "copernicu": 3, "eu": 3, "cdsapp": 3, "tab": 3, "overview": 3, "also": [3, 5], "extract": [3, 5], "zip": [3, 5], "which": 3, "delet": 3, "read": 3, "multi": 3, "each": 3, "correspond": [3, 4], "variabl": [3, 4], "themselv": 3, "mai": 3, "miss": 3, "It": [3, 5], "concat": 3, "column": [3, 4], "specif": 3, "aggreg": 3, "seri": 4, "var_nam": 4, "titl": 4, "reference_dates_rang": 4, "resampl": 4, "1m": 4, "figur": 4, "base_colorscal": 4, "vertic": 4, "v": [4, 5], "hex_color": 4, "tupl": 4, "hex": 4, "color": 4, "rgb": 4, "If": 4, "rgba": 4, "provid": 4, "ignor": 4, "alpha": 4, "channel": 4, "administr": 4, "entinti": 4, "xr": 4, "least": 4, "cannot": 4, "togeth": 4, "scale": 4, "z": 4, "axi": 4, "datafram": [4, 5], "add_ci": 4, "bool": [4, 5], "facet": 4, "line": 4, "yearli": 4, "mean": 4, "along": 4, "ci": 4, "pd": 4, "panda": 4, "lower": 4, "upper": 4, "measur": 4, "whether": 4, "default": [4, 5], "assign": 4, "differ": 4, "fig": 4, "path": [4, 5], "img_format": 4, "png": 4, "plotli": 4, "static": [4, 5], "imag": 4, "sequenti": 4, "colorscal": 4, "colorbar": 4, "form": 4, "add_satellite_observ": 4, "true": [4, 5], "note": 4, "we": 4, "consid": 4, "surface_flux": 4, "can": 4, "one": [4, 5], "among": 4, "carbon_dioxid": 4, "methan": 4, "nitrous_oxid": 4, "singl": 4, "show": 4, "categori": 4, "surfac": 4, "respect": 4, "satellit": 4, "flux_apo": 4, "flux_apos_bio": 4, "yyyi": 4, "mm": 4, "avail": 4, "map": 5, "start": 5, "boundari": 5, "enum": 5, "nation": 5, "divis": 5, "done": 5, "folium": 5, "entiti": 5, "between": 5, "entity_select": 5, "new": 5, "generic_shape_select": 5, "encompass": 5, "entit": 5, "touch": 5, "assum": 5, "argument": 5, "poli": 5, "polygon": 5, "multipolygon": 5, "out_ev": 5, "click": 5, "event": 5, "output": 5, "streamlit_folium": 5, "func": 5, "decor": 5, "check": 5, "donwload": 5, "resolut": 5, "50m": 5, "map_typ": 5, "cultur": 5, "info_typ": 5, "admin": 5, "depth": 5, "instanc": 5, "map_subunit": 5, "disk": 5, "natur": 5, "earth": 5, "By": 5, "world": 5, "obj": 5, "find": 5, "geopanda": 5, "directori": 5, "url": 5, "dissolv": 5, "index": 6, "modul": [6, 7], "search": 6, "page": 6, "packag": 7, "subpackag": 7, "submodul": 7, "content": 7, "except": 7, "logger": 7, "os_manag": 7}, "objects": {"": [[0, 0, 0, "-", "api"]], "api": [[0, 0, 0, "-", "config"], [1, 0, 0, "-", "data_interface"], [0, 0, 0, "-", "exceptions"], [0, 0, 0, "-", "loggers"], [0, 0, 0, "-", "os_manager"], [4, 0, 0, "-", "plotting"], [5, 0, 0, "-", "shape_selection"]], "api.data_interface": [[1, 0, 0, "-", "cams_interface"], [1, 0, 0, "-", "config_parser"], [1, 0, 0, "-", "data_transformations"], [2, 0, 0, "-", "eac4"], [3, 0, 0, "-", "ghg"]], "api.data_interface.cams_interface": [[1, 1, 1, "", "CAMSDataInterface"]], "api.data_interface.cams_interface.CAMSDataInterface": [[1, 2, 1, "", "clear_data_files"], [1, 3, 1, "", "data_folder"], [1, 4, 1, "", "data_variables"], [1, 3, 1, "", "dataset_name"], [1, 3, 1, "", "file_ext"], [1, 3, 1, "", "file_format"], [1, 2, 1, "", "list_data_files"], [1, 2, 1, "", "read_dataset"]], "api.data_interface.config_parser": [[1, 1, 1, "", "ConfigMeta"], [1, 1, 1, "", "OperationParser"]], "api.data_interface.config_parser.OperationParser": [[1, 3, 1, "", "allowed_ops"], [1, 2, 1, "", "arithmetic_eval"]], "api.data_interface.data_transformations": [[1, 5, 1, "", "clip_and_concat_shapes"], [1, 5, 1, "", "confidence_interval"], [1, 5, 1, "", "shifting_long"], [1, 5, 1, "", "split_time_dim"]], "api.data_interface.eac4": [[2, 0, 0, "-", "eac4"], [2, 0, 0, "-", "eac4_config"]], "api.data_interface.eac4.eac4": [[2, 1, 1, "", "EAC4Instance"]], "api.data_interface.eac4.eac4.EAC4Instance": [[2, 3, 1, "", "dataset_dir"], [2, 3, 1, "", "dataset_name"], [2, 2, 1, "", "download"], [2, 3, 1, "", "file_ext"], [2, 3, 1, "", "file_format"], [2, 4, 1, "", "file_full_path"], [2, 4, 1, "", "model_level"], [2, 4, 1, "", "pressure_level"], [2, 2, 1, "", "read_dataset"], [2, 4, 1, "", "time_values"]], "api.data_interface.eac4.eac4_config": [[2, 1, 1, "", "EAC4Config"]], "api.data_interface.eac4.eac4_config.EAC4Config": [[2, 2, 1, "", "convert_units_array"], [2, 2, 1, "", "get_config"]], "api.data_interface.ghg": [[3, 0, 0, "-", "ghg"], [3, 0, 0, "-", "ghg_config"]], "api.data_interface.ghg.ghg": [[3, 1, 1, "", "InversionOptimisedGreenhouseGas"]], "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas": [[3, 3, 1, "", "dataset_dir"], [3, 3, 1, "", "dataset_name"], [3, 2, 1, "", "download"], [3, 3, 1, "", "file_ext"], [3, 3, 1, "", "file_format"], [3, 4, 1, "", "file_full_path"], [3, 4, 1, "", "month"], [3, 2, 1, "", "read_dataset"], [3, 4, 1, "", "year"]], "api.data_interface.ghg.ghg_config": [[3, 1, 1, "", "GHGConfig"]], "api.data_interface.ghg.ghg_config.GHGConfig": [[3, 2, 1, "", "convert_units_array"], [3, 2, 1, "", "get_config"], [3, 2, 1, "", "get_var_names"]], "api.exceptions": [[0, 6, 1, "", "OperationNotAllowed"]], "api.loggers": [[0, 1, 1, "", "Loggers"], [0, 1, 1, "", "LoggersMeta"], [0, 5, 1, "", "get_logger"]], "api.loggers.Loggers": [[0, 2, 1, "", "clear_logs"], [0, 2, 1, "", "get_logger"], [0, 2, 1, "", "list_logs"]], "api.loggers.LoggersMeta": [[0, 3, 1, "", "logging_config"], [0, 3, 1, "", "logs_root_dir"]], "api.os_manager": [[0, 5, 1, "", "create_folder"], [0, 5, 1, "", "get_local_folder"], [0, 5, 1, "", "remove_folder"]], "api.plotting": [[4, 0, 0, "-", "anomalies"], [4, 0, 0, "-", "hovmoeller"], [4, 0, 0, "-", "plot_utils"], [4, 0, 0, "-", "yearly_flux"]], "api.plotting.anomalies": [[4, 5, 1, "", "eac4_anomalies_plot"]], "api.plotting.hovmoeller": [[4, 5, 1, "", "eac4_hovmoeller_plot"]], "api.plotting.plot_utils": [[4, 5, 1, "", "hex_to_rgb"], [4, 5, 1, "", "hovmoeller_plot"], [4, 5, 1, "", "line_with_ci_subplots"], [4, 5, 1, "", "save_plotly_to_image"], [4, 5, 1, "", "sequential_colorscale_bar"]], "api.plotting.yearly_flux": [[4, 5, 1, "", "ghg_surface_satellite_yearly_plot"]], "api.shape_selection": [[5, 0, 0, "-", "config"], [5, 0, 0, "-", "shape_selection"], [5, 0, 0, "-", "shapefile"]], "api.shape_selection.config": [[5, 1, 1, "", "SelectionLevel"]], "api.shape_selection.config.SelectionLevel": [[5, 3, 1, "", "CONTINENTS"], [5, 3, 1, "", "COUNTRIES"], [5, 3, 1, "", "COUNTRIES_SUB"], [5, 3, 1, "", "GENERIC"], [5, 3, 1, "", "ORGANIZATIONS"]], "api.shape_selection.shape_selection": [[5, 1, 1, "", "EntitySelection"], [5, 1, 1, "", "GenericShapeSelection"], [5, 1, 1, "", "Selection"], [5, 5, 1, "", "from_out_event"], [5, 5, 1, "", "selection_empty"]], "api.shape_selection.shape_selection.EntitySelection": [[5, 2, 1, "", "convert_selection"], [5, 2, 1, "", "from_entities_list"], [5, 2, 1, "", "from_entity_selection"], [5, 2, 1, "", "from_generic_selection"]], "api.shape_selection.shape_selection.GenericShapeSelection": [[5, 2, 1, "", "convert_selection"], [5, 2, 1, "", "from_shape"]], "api.shape_selection.shape_selection.Selection": [[5, 2, 1, "", "empty"], [5, 2, 1, "", "get_event_label"], [5, 4, 1, "", "labels"]], "api.shape_selection.shapefile": [[5, 1, 1, "", "ShapefilesDownloader"], [5, 5, 1, "", "dissolve_shapefile_level"]], "api.shape_selection.shapefile.ShapefilesDownloader": [[5, 2, 1, "", "cache"], [5, 2, 1, "", "clear_cache"], [5, 2, 1, "", "download"], [5, 2, 1, "", "find_cache"], [5, 2, 1, "", "get_as_dataframe"], [5, 2, 1, "", "is_cached"], [5, 4, 1, "", "shapefile_dir"], [5, 4, 1, "", "shapefile_full_path"], [5, 4, 1, "", "shapefile_name"], [5, 4, 1, "", "shapefile_url"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:attribute", "4": "py:property", "5": "py:function", "6": "py:exception"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "attribute", "Python attribute"], "4": ["py", "property", "Python property"], "5": ["py", "function", "Python function"], "6": ["py", "exception", "Python exception"]}, "titleterms": {"api": [0, 1, 2, 3, 4, 5, 7], "packag": [0, 1, 2, 3, 4, 5], "subpackag": [0, 1], "submodul": [0, 1, 2, 3, 4, 5], "config": [0, 5], "modul": [0, 1, 2, 3, 4, 5], "except": 0, "logger": 0, "os_manag": 0, "content": [0, 1, 2, 3, 4, 5], "data_interfac": [1, 2, 3], "cams_interfac": 1, "config_pars": 1, "data_transform": 1, "eac4": 2, "eac4_config": 2, "ghg": 3, "ghg_config": 3, "plot": 4, "anomali": 4, "hovmoel": 4, "plot_util": 4, "yearly_flux": 4, "shape_select": 5, "shapefil": 5, "welcom": 6, "atmospher": 6, "explor": 6, "": 6, "document": 6, "indic": 6, "tabl": 6}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"api package": [[0, "api-package"]], "Subpackages": [[0, "subpackages"], [1, "subpackages"]], "Submodules": [[0, "submodules"], [1, "submodules"], [2, "submodules"], [3, "submodules"], [4, "submodules"], [5, "submodules"]], "api.config module": [[0, "module-api.config"]], "api.exceptions module": [[0, "module-api.exceptions"]], "api.loggers module": [[0, "module-api.loggers"]], "api.os_manager module": [[0, "module-api.os_manager"]], "Module contents": [[0, "module-api"], [1, "module-api.data_interface"], [2, "module-api.data_interface.eac4"], [3, "module-api.data_interface.ghg"], [4, "module-api.plotting"], [5, "module-api.shape_selection"]], "api.data_interface package": [[1, "api-data-interface-package"]], "api.data_interface.cams_interface module": [[1, "module-api.data_interface.cams_interface"]], "api.data_interface.config_parser module": [[1, "module-api.data_interface.config_parser"]], "api.data_interface.data_transformations module": [[1, "module-api.data_interface.data_transformations"]], "api.data_interface.eac4 package": [[2, "api-data-interface-eac4-package"]], "api.data_interface.eac4.eac4 module": [[2, "module-api.data_interface.eac4.eac4"]], "api.data_interface.eac4.eac4_config module": [[2, "module-api.data_interface.eac4.eac4_config"]], "api.data_interface.ghg package": [[3, "api-data-interface-ghg-package"]], "api.data_interface.ghg.ghg module": [[3, "module-api.data_interface.ghg.ghg"]], "api.data_interface.ghg.ghg_config module": [[3, "module-api.data_interface.ghg.ghg_config"]], "api.plotting package": [[4, "api-plotting-package"]], "api.plotting.anomalies module": [[4, "module-api.plotting.anomalies"]], "api.plotting.hovmoeller module": [[4, "module-api.plotting.hovmoeller"]], "api.plotting.plot_utils module": [[4, "module-api.plotting.plot_utils"]], "api.plotting.yearly_flux module": [[4, "module-api.plotting.yearly_flux"]], "api.shape_selection package": [[5, "api-shape-selection-package"]], "api.shape_selection.config module": [[5, "module-api.shape_selection.config"]], "api.shape_selection.shape_selection module": [[5, "module-api.shape_selection.shape_selection"]], "api.shape_selection.shapefile module": [[5, "module-api.shape_selection.shapefile"]], "Welcome to Atmospheric Explorer\u2019s documentation!": [[6, "welcome-to-atmospheric-explorer-s-documentation"]], "Indices and tables": [[6, "indices-and-tables"]], "api": [[7, "api"]]}, "indexentries": {"loggers (class in api.loggers)": [[0, "api.loggers.Loggers"]], "loggersmeta (class in api.loggers)": [[0, "api.loggers.LoggersMeta"]], "operationnotallowed": [[0, "api.exceptions.OperationNotAllowed"]], "api": [[0, "module-api"]], "api.config": [[0, "module-api.config"]], "api.exceptions": [[0, "module-api.exceptions"]], "api.loggers": [[0, "module-api.loggers"]], "api.os_manager": [[0, "module-api.os_manager"]], "clear_logs() (api.loggers.loggers class method)": [[0, "api.loggers.Loggers.clear_logs"]], "create_folder() (in module api.os_manager)": [[0, "api.os_manager.create_folder"]], "get_local_folder() (in module api.os_manager)": [[0, "api.os_manager.get_local_folder"]], "get_logger() (api.loggers.loggers class method)": [[0, "api.loggers.Loggers.get_logger"]], "get_logger() (in module api.loggers)": [[0, "api.loggers.get_logger"]], "list_logs() (api.loggers.loggers class method)": [[0, "api.loggers.Loggers.list_logs"]], "logging_config (api.loggers.loggersmeta attribute)": [[0, "api.loggers.LoggersMeta.logging_config"]], "logs_root_dir (api.loggers.loggersmeta attribute)": [[0, "api.loggers.LoggersMeta.logs_root_dir"]], "module": [[0, "module-api"], [0, "module-api.config"], [0, "module-api.exceptions"], [0, "module-api.loggers"], [0, "module-api.os_manager"], [1, "module-api.data_interface"], [1, "module-api.data_interface.cams_interface"], [1, "module-api.data_interface.config_parser"], [1, "module-api.data_interface.data_transformations"], [2, "module-api.data_interface.eac4"], [2, "module-api.data_interface.eac4.eac4"], [2, "module-api.data_interface.eac4.eac4_config"], [3, "module-api.data_interface.ghg"], [3, "module-api.data_interface.ghg.ghg"], [3, "module-api.data_interface.ghg.ghg_config"], [4, "module-api.plotting"], [4, "module-api.plotting.anomalies"], [4, "module-api.plotting.hovmoeller"], [4, "module-api.plotting.plot_utils"], [4, "module-api.plotting.yearly_flux"], [5, "module-api.shape_selection"], [5, "module-api.shape_selection.config"], [5, "module-api.shape_selection.shape_selection"], [5, "module-api.shape_selection.shapefile"]], "remove_folder() (in module api.os_manager)": [[0, "api.os_manager.remove_folder"]], "camsdatainterface (class in api.data_interface.cams_interface)": [[1, "api.data_interface.cams_interface.CAMSDataInterface"]], "configmeta (class in api.data_interface.config_parser)": [[1, "api.data_interface.config_parser.ConfigMeta"]], "operationparser (class in api.data_interface.config_parser)": [[1, "api.data_interface.config_parser.OperationParser"]], "allowed_ops (api.data_interface.config_parser.operationparser attribute)": [[1, "api.data_interface.config_parser.OperationParser.allowed_ops"]], "api.data_interface": [[1, "module-api.data_interface"]], "api.data_interface.cams_interface": [[1, "module-api.data_interface.cams_interface"]], "api.data_interface.config_parser": [[1, "module-api.data_interface.config_parser"]], "api.data_interface.data_transformations": [[1, "module-api.data_interface.data_transformations"]], "arithmetic_eval() (api.data_interface.config_parser.operationparser method)": [[1, "api.data_interface.config_parser.OperationParser.arithmetic_eval"]], "clear_data_files() (api.data_interface.cams_interface.camsdatainterface class method)": [[1, "api.data_interface.cams_interface.CAMSDataInterface.clear_data_files"]], "clip_and_concat_shapes() (in module api.data_interface.data_transformations)": [[1, "api.data_interface.data_transformations.clip_and_concat_shapes"]], "confidence_interval() (in module api.data_interface.data_transformations)": [[1, "api.data_interface.data_transformations.confidence_interval"]], "data_folder (api.data_interface.cams_interface.camsdatainterface attribute)": [[1, "api.data_interface.cams_interface.CAMSDataInterface.data_folder"]], "data_variables (api.data_interface.cams_interface.camsdatainterface property)": [[1, "api.data_interface.cams_interface.CAMSDataInterface.data_variables"]], "dataset_name (api.data_interface.cams_interface.camsdatainterface attribute)": [[1, "api.data_interface.cams_interface.CAMSDataInterface.dataset_name"]], "file_ext (api.data_interface.cams_interface.camsdatainterface attribute)": [[1, "api.data_interface.cams_interface.CAMSDataInterface.file_ext"]], "file_format (api.data_interface.cams_interface.camsdatainterface attribute)": [[1, "api.data_interface.cams_interface.CAMSDataInterface.file_format"]], "list_data_files() (api.data_interface.cams_interface.camsdatainterface class method)": [[1, "api.data_interface.cams_interface.CAMSDataInterface.list_data_files"]], "read_dataset() (api.data_interface.cams_interface.camsdatainterface method)": [[1, "api.data_interface.cams_interface.CAMSDataInterface.read_dataset"]], "shifting_long() (in module api.data_interface.data_transformations)": [[1, "api.data_interface.data_transformations.shifting_long"]], "split_time_dim() (in module api.data_interface.data_transformations)": [[1, "api.data_interface.data_transformations.split_time_dim"]], "eac4config (class in api.data_interface.eac4.eac4_config)": [[2, "api.data_interface.eac4.eac4_config.EAC4Config"]], "eac4instance (class in api.data_interface.eac4.eac4)": [[2, "api.data_interface.eac4.eac4.EAC4Instance"]], "api.data_interface.eac4": [[2, "module-api.data_interface.eac4"]], "api.data_interface.eac4.eac4": [[2, "module-api.data_interface.eac4.eac4"]], "api.data_interface.eac4.eac4_config": [[2, "module-api.data_interface.eac4.eac4_config"]], "convert_units_array() (api.data_interface.eac4.eac4_config.eac4config class method)": [[2, "api.data_interface.eac4.eac4_config.EAC4Config.convert_units_array"]], "dataset_dir (api.data_interface.eac4.eac4.eac4instance attribute)": [[2, "api.data_interface.eac4.eac4.EAC4Instance.dataset_dir"]], "dataset_name (api.data_interface.eac4.eac4.eac4instance attribute)": [[2, "api.data_interface.eac4.eac4.EAC4Instance.dataset_name"]], "download() (api.data_interface.eac4.eac4.eac4instance method)": [[2, "api.data_interface.eac4.eac4.EAC4Instance.download"]], "file_ext (api.data_interface.eac4.eac4.eac4instance attribute)": [[2, "api.data_interface.eac4.eac4.EAC4Instance.file_ext"]], "file_format (api.data_interface.eac4.eac4.eac4instance attribute)": [[2, "api.data_interface.eac4.eac4.EAC4Instance.file_format"]], "file_full_path (api.data_interface.eac4.eac4.eac4instance property)": [[2, "api.data_interface.eac4.eac4.EAC4Instance.file_full_path"]], "get_config() (api.data_interface.eac4.eac4_config.eac4config class method)": [[2, "api.data_interface.eac4.eac4_config.EAC4Config.get_config"]], "model_level (api.data_interface.eac4.eac4.eac4instance property)": [[2, "api.data_interface.eac4.eac4.EAC4Instance.model_level"]], "pressure_level (api.data_interface.eac4.eac4.eac4instance property)": [[2, "api.data_interface.eac4.eac4.EAC4Instance.pressure_level"]], "read_dataset() (api.data_interface.eac4.eac4.eac4instance method)": [[2, "api.data_interface.eac4.eac4.EAC4Instance.read_dataset"]], "time_values (api.data_interface.eac4.eac4.eac4instance property)": [[2, "api.data_interface.eac4.eac4.EAC4Instance.time_values"]], "ghgconfig (class in api.data_interface.ghg.ghg_config)": [[3, "api.data_interface.ghg.ghg_config.GHGConfig"]], "inversionoptimisedgreenhousegas (class in api.data_interface.ghg.ghg)": [[3, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas"]], "api.data_interface.ghg": [[3, "module-api.data_interface.ghg"]], "api.data_interface.ghg.ghg": [[3, "module-api.data_interface.ghg.ghg"]], "api.data_interface.ghg.ghg_config": [[3, "module-api.data_interface.ghg.ghg_config"]], "convert_units_array() (api.data_interface.ghg.ghg_config.ghgconfig class method)": [[3, "api.data_interface.ghg.ghg_config.GHGConfig.convert_units_array"]], "dataset_dir (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas attribute)": [[3, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.dataset_dir"]], "dataset_name (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas attribute)": [[3, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.dataset_name"]], "download() (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas method)": [[3, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.download"]], "file_ext (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas attribute)": [[3, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.file_ext"]], "file_format (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas attribute)": [[3, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.file_format"]], "file_full_path (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas property)": [[3, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.file_full_path"]], "get_config() (api.data_interface.ghg.ghg_config.ghgconfig class method)": [[3, "api.data_interface.ghg.ghg_config.GHGConfig.get_config"]], "get_var_names() (api.data_interface.ghg.ghg_config.ghgconfig class method)": [[3, "api.data_interface.ghg.ghg_config.GHGConfig.get_var_names"]], "month (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas property)": [[3, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.month"]], "read_dataset() (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas method)": [[3, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.read_dataset"]], "year (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas property)": [[3, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.year"]], "api.plotting": [[4, "module-api.plotting"]], "api.plotting.anomalies": [[4, "module-api.plotting.anomalies"]], "api.plotting.hovmoeller": [[4, "module-api.plotting.hovmoeller"]], "api.plotting.plot_utils": [[4, "module-api.plotting.plot_utils"]], "api.plotting.yearly_flux": [[4, "module-api.plotting.yearly_flux"]], "eac4_anomalies_plot() (in module api.plotting.anomalies)": [[4, "api.plotting.anomalies.eac4_anomalies_plot"]], "eac4_hovmoeller_plot() (in module api.plotting.hovmoeller)": [[4, "api.plotting.hovmoeller.eac4_hovmoeller_plot"]], "ghg_surface_satellite_yearly_plot() (in module api.plotting.yearly_flux)": [[4, "api.plotting.yearly_flux.ghg_surface_satellite_yearly_plot"]], "hex_to_rgb() (in module api.plotting.plot_utils)": [[4, "api.plotting.plot_utils.hex_to_rgb"]], "hovmoeller_plot() (in module api.plotting.plot_utils)": [[4, "api.plotting.plot_utils.hovmoeller_plot"]], "line_with_ci_subplots() (in module api.plotting.plot_utils)": [[4, "api.plotting.plot_utils.line_with_ci_subplots"]], "save_plotly_to_image() (in module api.plotting.plot_utils)": [[4, "api.plotting.plot_utils.save_plotly_to_image"]], "sequential_colorscale_bar() (in module api.plotting.plot_utils)": [[4, "api.plotting.plot_utils.sequential_colorscale_bar"]], "continents (api.shape_selection.config.selectionlevel attribute)": [[5, "api.shape_selection.config.SelectionLevel.CONTINENTS"]], "countries (api.shape_selection.config.selectionlevel attribute)": [[5, "api.shape_selection.config.SelectionLevel.COUNTRIES"]], "countries_sub (api.shape_selection.config.selectionlevel attribute)": [[5, "api.shape_selection.config.SelectionLevel.COUNTRIES_SUB"]], "entityselection (class in api.shape_selection.shape_selection)": [[5, "api.shape_selection.shape_selection.EntitySelection"]], "generic (api.shape_selection.config.selectionlevel attribute)": [[5, "api.shape_selection.config.SelectionLevel.GENERIC"]], "genericshapeselection (class in api.shape_selection.shape_selection)": [[5, "api.shape_selection.shape_selection.GenericShapeSelection"]], "organizations (api.shape_selection.config.selectionlevel attribute)": [[5, "api.shape_selection.config.SelectionLevel.ORGANIZATIONS"]], "selection (class in api.shape_selection.shape_selection)": [[5, "api.shape_selection.shape_selection.Selection"]], "selectionlevel (class in api.shape_selection.config)": [[5, "api.shape_selection.config.SelectionLevel"]], "shapefilesdownloader (class in api.shape_selection.shapefile)": [[5, "api.shape_selection.shapefile.ShapefilesDownloader"]], "api.shape_selection": [[5, "module-api.shape_selection"]], "api.shape_selection.config": [[5, "module-api.shape_selection.config"]], "api.shape_selection.shape_selection": [[5, "module-api.shape_selection.shape_selection"]], "api.shape_selection.shapefile": [[5, "module-api.shape_selection.shapefile"]], "cache() (api.shape_selection.shapefile.shapefilesdownloader class method)": [[5, "api.shape_selection.shapefile.ShapefilesDownloader.cache"]], "clear_cache() (api.shape_selection.shapefile.shapefilesdownloader class method)": [[5, "api.shape_selection.shapefile.ShapefilesDownloader.clear_cache"]], "convert_selection() (api.shape_selection.shape_selection.entityselection class method)": [[5, "api.shape_selection.shape_selection.EntitySelection.convert_selection"]], "convert_selection() (api.shape_selection.shape_selection.genericshapeselection class method)": [[5, "api.shape_selection.shape_selection.GenericShapeSelection.convert_selection"]], "dissolve_shapefile_level() (in module api.shape_selection.shapefile)": [[5, "api.shape_selection.shapefile.dissolve_shapefile_level"]], "download() (api.shape_selection.shapefile.shapefilesdownloader method)": [[5, "api.shape_selection.shapefile.ShapefilesDownloader.download"]], "empty() (api.shape_selection.shape_selection.selection method)": [[5, "api.shape_selection.shape_selection.Selection.empty"]], "find_cache() (api.shape_selection.shapefile.shapefilesdownloader class method)": [[5, "api.shape_selection.shapefile.ShapefilesDownloader.find_cache"]], "from_entities_list() (api.shape_selection.shape_selection.entityselection class method)": [[5, "api.shape_selection.shape_selection.EntitySelection.from_entities_list"]], "from_entity_selection() (api.shape_selection.shape_selection.entityselection class method)": [[5, "api.shape_selection.shape_selection.EntitySelection.from_entity_selection"]], "from_generic_selection() (api.shape_selection.shape_selection.entityselection class method)": [[5, "api.shape_selection.shape_selection.EntitySelection.from_generic_selection"]], "from_out_event() (in module api.shape_selection.shape_selection)": [[5, "api.shape_selection.shape_selection.from_out_event"]], "from_shape() (api.shape_selection.shape_selection.genericshapeselection class method)": [[5, "api.shape_selection.shape_selection.GenericShapeSelection.from_shape"]], "get_as_dataframe() (api.shape_selection.shapefile.shapefilesdownloader method)": [[5, "api.shape_selection.shapefile.ShapefilesDownloader.get_as_dataframe"]], "get_event_label() (api.shape_selection.shape_selection.selection static method)": [[5, "api.shape_selection.shape_selection.Selection.get_event_label"]], "is_cached() (api.shape_selection.shapefile.shapefilesdownloader class method)": [[5, "api.shape_selection.shapefile.ShapefilesDownloader.is_cached"]], "labels (api.shape_selection.shape_selection.selection property)": [[5, "api.shape_selection.shape_selection.Selection.labels"]], "selection_empty() (in module api.shape_selection.shape_selection)": [[5, "api.shape_selection.shape_selection.selection_empty"]], "shapefile_dir (api.shape_selection.shapefile.shapefilesdownloader property)": [[5, "api.shape_selection.shapefile.ShapefilesDownloader.shapefile_dir"]], "shapefile_full_path (api.shape_selection.shapefile.shapefilesdownloader property)": [[5, "api.shape_selection.shapefile.ShapefilesDownloader.shapefile_full_path"]], "shapefile_name (api.shape_selection.shapefile.shapefilesdownloader property)": [[5, "api.shape_selection.shapefile.ShapefilesDownloader.shapefile_name"]], "shapefile_url (api.shape_selection.shapefile.shapefilesdownloader property)": [[5, "api.shape_selection.shapefile.ShapefilesDownloader.shapefile_url"]]}}) +Search.setIndex({"docnames": ["api", "api.cache", "api.data_interface", "api.data_interface.cams_interface", "api.data_interface.eac4", "api.data_interface.ghg", "api.shape_selection", "index"], "filenames": ["api.rst", "api.cache.rst", "api.data_interface.rst", "api.data_interface.cams_interface.rst", "api.data_interface.eac4.rst", "api.data_interface.ghg.rst", "api.shape_selection.rst", "index.rst"], "titles": ["api package", "api.cache package", "api.data_interface package", "api.data_interface.cams_interface package", "api.data_interface.eac4 package", "api.data_interface.ghg package", "api.shape_selection package", "Welcome to Atmospheric Explorer\u2019s documentation!"], "terms": {"cach": [0, 4, 5, 6], "clear_cach": [0, 1], "find_cach": [0, 1], "init_cach": [0, 1], "is_cach": [0, 1], "paramet": [0, 1, 3, 4, 5, 6], "subset": [0, 1, 2, 3, 4, 5, 6], "data_interfac": 0, "cams_interfac": [0, 2], "cams_paramet": [0, 2], "eac4": [0, 1, 2], "eac4_config": [0, 2], "eac4_paramet": [0, 2], "ghg": [0, 1, 2], "ghg_config": [0, 2], "ghg_paramet": [0, 2], "data_transform": 0, "clip_and_concat_shap": [0, 2], "confidence_interv": [0, 2], "shifting_long": [0, 2], "split_time_dim": [0, 2], "dataset_config_pars": 0, "datasetconfigpars": [0, 2, 4, 5], "get_config": [0, 2], "parse_factor": [0, 2], "operationpars": [0, 2], "allowed_op": [0, 2], "arithmetic_ev": [0, 2], "logger": 0, "get_logg": 0, "loggersingleton": 0, "logging_config": 0, "logs_root_dir": 0, "loggers_util": 0, "clear_log": 0, "list_log": 0, "plot": [0, 2], "anomali": 0, "eac4_anomalies_plot": 0, "hovmoel": 0, "eac4_hovmoeller_plot": 0, "plot_util": 0, "hex_to_rgb": 0, "hovmoeller_plot": 0, "line_with_ci_subplot": 0, "save_plotly_to_imag": 0, "sequential_colorscale_bar": 0, "yearly_flux": 0, "ghg_surface_satellite_yearly_plot": 0, "shape_select": 0, "selectionlevel": [0, 6], "contin": [0, 6], "countri": [0, 6], "countries_sub": [0, 6], "gener": [0, 3, 6], "organ": [0, 6], "entityselect": [0, 6], "convert_select": [0, 6], "from_entities_list": [0, 6], "from_entity_select": [0, 6], "from_generic_select": [0, 6], "genericshapeselect": [0, 6], "from_shap": [0, 6], "select": [0, 2, 5, 6], "empti": [0, 6], "get_event_label": [0, 6], "label": [0, 6], "from_out_ev": [0, 6], "selection_empti": [0, 6], "shapefil": 0, "shapefileparamet": [0, 6], "other": [0, 1, 2, 3, 4, 5, 6], "shapefilesdownload": [0, 6], "download": [0, 1, 2, 3, 4, 5, 6], "get_as_datafram": [0, 6], "shapefile_dir": [0, 6], "shapefile_full_path": [0, 6], "shapefile_nam": [0, 6], "shapefile_url": [0, 6], "dissolve_shapefile_level": [0, 6], "gather": 0, "custom": 0, "operationnotallow": 0, "base": [0, 1, 2, 3, 4, 5, 6], "us": [0, 1, 2, 3, 4, 5, 6], "when": [0, 1, 2, 6], "pars": [0, 2], "python": [0, 2], "oper": [0, 2], "from": [0, 1, 2, 3, 4, 5, 6], "string": [0, 2], "all": [0, 1, 3, 5, 6], "util": 0, "function": [0, 1, 2, 5], "class": [0, 1, 2, 3, 4, 5, 6], "get_local_fold": 0, "return": [0, 1, 2, 3, 4, 5, 6], "folder": [0, 3, 6], "where": [0, 1, 2, 5], "put": 0, "local": 0, "file": [0, 1, 2, 3, 4, 5, 6], "user": 0, "": [0, 5], "o": 0, "create_fold": 0, "str": [0, 2, 3, 4, 5, 6], "none": [0, 1, 3, 4, 5, 6], "creat": 0, "doesn": [0, 1], "t": [0, 1], "exist": 0, "remove_fold": 0, "remov": 0, "manag": [0, 2, 6], "constant": [0, 2], "thi": [0, 1, 2, 3, 4, 5, 6], "defin": [0, 1, 2, 3, 6], "which": [0, 5], "i": [0, 1, 2, 4, 5, 6], "sever": 0, "like": 0, "one": [0, 1, 6], "load": [0, 2, 4, 5], "dataset": [0, 1, 2, 3, 4, 5], "configur": [0, 2], "wai": [0, 2], "onli": [0, 1, 2, 4, 5], "onc": [0, 2, 4, 5], "The": [0, 2], "pattern": [0, 2, 4, 5], "wa": [0, 2], "taken": [0, 2, 6], "here": [0, 2], "http": [0, 2, 4, 5], "refactor": [0, 2], "guru": [0, 2], "design": [0, 2], "exampl": [0, 1, 2], "0": [0, 2, 6], "type": [0, 3, 4, 5, 6], "meta": 0, "need": [0, 1, 2, 4, 5, 6], "implement": [0, 2, 4, 5], "contain": [0, 1, 2, 3, 4, 5, 6], "atmospher": [0, 2, 4, 5, 6], "explor": [0, 2, 4, 5, 6], "object": [1, 2, 6], "few": 1, "method": 1, "allow": 1, "anoth": 1, "instanc": [1, 3, 4, 5, 6], "attribut": 1, "To": 1, "inherit": 1, "__new__": 1, "instanti": 1, "correspond": [1, 5], "specif": [1, 5], "cam": [1, 3, 4, 5], "pass": [1, 2, 3, 6], "last": 1, "decor": [1, 6], "new": [1, 2, 6], "__init__": 1, "static": [1, 4, 6], "self": [1, 3, 4, 5, 6], "classmethod": [1, 2, 3, 4, 5, 6], "clear": [1, 3], "find": 1, "obj": 1, "ha": 1, "superset": 1, "kwarg": [1, 2, 4, 5], "func": [1, 6], "wrapper": 1, "check": [1, 2, 4, 6], "alreadi": 1, "If": 1, "ye": 1, "retur": 1, "run": 1, "initi": 1, "bool": [1, 4, 5, 6], "abc": [1, 3], "abstract": [1, 3], "determin": 1, "wether": 1, "make": 1, "up": 1, "ar": [1, 3, 4, 5, 6], "each": [1, 5], "call": [1, 3, 4, 5], "memori": 1, "order": 1, "avoid": 1, "same": 1, "multipl": 1, "time": [1, 2, 3, 5], "In": 1, "we": 1, "two": 1, "should": 1, "have": [1, 2], "subclass": 1, "its": [1, 3, 4, 5], "cdsapi": [1, 3, 4, 5], "also": [1, 5, 6], "expect": [1, 2], "argument": [1, 6], "true": [1, 3, 4, 5, 6], "some": [1, 2], "see": [1, 4, 5], "an": [1, 2, 3, 4, 5, 6], "note": 1, "keep": [1, 2], "track": 1, "dure": 1, "session": 1, "ignor": 1, "preiousli": 1, "mean": 1, "work": 1, "ui": 1, "while": 1, "cli": 1, "sinc": 1, "command": 1, "itself": 1, "camsdatainterfac": [2, 3, 4, 5], "build_call_bodi": [2, 3, 4, 5], "clear_data_fil": [2, 3], "data_fold": [2, 3], "dataset_nam": [2, 3, 4, 5], "file_ext": [2, 3, 4, 5], "file_format": [2, 3, 4, 5], "list_data_fil": [2, 3], "read_dataset": [2, 3, 4, 5], "camsparamet": [2, 3, 4, 5], "data_vari": [2, 3, 4, 5], "eac4inst": [2, 4], "dataset_dir": [2, 4, 5], "file_full_path": [2, 4, 5], "eac4config": [2, 4], "convert_units_arrai": [2, 4, 5], "eac4paramet": [2, 4], "area_issubset": [2, 4], "dates_issubset": [2, 4], "model_issubset": [2, 4], "pressure_issubset": [2, 4], "inversionoptimisedgreenhousega": [2, 5], "ghgconfig": [2, 5], "get_var_nam": [2, 5], "ghgparamet": [2, 5], "data": [2, 3, 4, 5, 6], "transform": 2, "data_fram": 2, "shape": [2, 6], "clip": 2, "specifi": [2, 4, 5], "shapes_df": 2, "must": 2, "geodatafram": [2, 6], "arrai": [2, 4, 5], "list": [2, 3, 4, 5, 6], "ndarrai": 2, "dataarrai": [2, 4, 5], "dim": 2, "comput": 2, "confid": 2, "interv": 2, "sampl": 2, "data_set": 2, "xarrai": [2, 3, 4, 5], "core": 2, "shift": 2, "longitud": 2, "rang": [2, 4], "180": 2, "time_dim": 2, "dimens": [2, 5], "split": 2, "date": [2, 4], "singleton": [2, 4, 5], "cfg": 2, "basic": 2, "yaml": [2, 4, 5], "For": 2, "refer": 2, "atmospheric_explor": [2, 3, 4, 5], "A": 2, "structur": 2, "follow": 2, "variabl": [2, 5], "data_variable_nam": 2, "var_nam": 2, "name": [2, 4, 5, 6], "column": [2, 5], "convers": 2, "conversion_factor": 2, "conversion_unit": 2, "dict": [2, 3, 4, 5], "get": [2, 3, 6], "actual": 2, "config": [2, 4, 5], "data_dict": 2, "factor": 2, "section": 2, "parser": 2, "arithmet": 2, "code": 2, "stackoverflow": 2, "com": 2, "question": 2, "20748202": 2, "valueerror": 2, "malform": 2, "ast": 2, "liter": 2, "eval": 2, "add": [2, 5, 6], "built": 2, "div": 2, "truediv": 2, "mod": 2, "mult": 2, "mul": 2, "sub": [2, 6], "int": [2, 4, 6], "float": 2, "access": [2, 4, 5], "collect": [3, 4, 5], "easili": [3, 4, 5], "interact": [3, 4, 5, 6], "ad": [3, 4, 5], "interfac": [3, 4, 5, 6], "common": 3, "build": [3, 4, 5], "bodi": [3, 4, 5], "insid": [3, 5], "home": [3, 4, 5], "luigi": [3, 4, 5], "set": [3, 4, 5], "properti": [3, 4, 5, 6], "valu": [3, 6], "intern": 3, "repres": 3, "equal": [3, 4, 5, 6], "compar": [3, 4, 5, 6], "dates_rang": 4, "time_valu": 4, "files_dir": [4, 5], "area": 4, "pressure_level": 4, "model_level": 4, "global": [4, 5], "reanalysi": 4, "monthli": 4, "averag": 4, "field": 4, "confluenc": 4, "ecmwf": 4, "displai": 4, "ckb": 4, "3a": 4, "document": 4, "head": 4, "camsglobalreanalysiseac4parameterlist": 4, "full": [4, 5, 6], "more": [4, 5, 6], "detail": [4, 5], "about": [4, 5], "save": [4, 5, 6], "filenam": [4, 5], "nc": 4, "netcdf": [4, 5], "arg": [4, 5], "so": [4, 5], "convert": [4, 5, 6], "origin": [4, 5], "unit": [4, 5], "area1": 4, "area2": 4, "first": 4, "second": 4, "date_range1": 4, "date_range2": 4, "ml1": 4, "ml2": 4, "model": 4, "level": [4, 6], "pl1": 4, "pl2": 4, "pressur": 4, "greenhous": 5, "gass": 5, "quantiti": 5, "input_observ": 5, "time_aggreg": 5, "year": 5, "month": 5, "version": 5, "latest": 5, "invers": 5, "optimis": 5, "ga": 5, "flux": 5, "concentr": 5, "copernicu": 5, "eu": 5, "cdsapp": 5, "tab": 5, "overview": 5, "extract": [5, 6], "zip": [5, 6], "delet": 5, "read": 5, "multi": 5, "themselv": 5, "mai": 5, "miss": 5, "It": [5, 6], "concat": 5, "aggreg": 5, "map": 6, "qualnam": 6, "start": 6, "1": 6, "boundari": 6, "enum": 6, "nation": 6, "divis": 6, "done": 6, "folium": 6, "datafram": 6, "entiti": 6, "between": 6, "v": 6, "entity_select": 6, "generic_shape_select": 6, "encompass": 6, "entit": 6, "touch": 6, "assum": 6, "poli": 6, "polygon": 6, "multipolygon": 6, "out_ev": 6, "click": 6, "event": 6, "output": 6, "streamlit_folium": 6, "donwload": 6, "resolut": 6, "50m": 6, "map_typ": 6, "cultur": 6, "info_typ": 6, "admin": 6, "depth": 6, "map_subunit": 6, "natur": 6, "earth": 6, "timeout": 6, "10": 6, "dst_dir": 6, "disk": 6, "By": 6, "default": 6, "world": 6, "geopanda": 6, "directori": 6, "path": 6, "url": 6, "dissolv": 6, "index": 7, "modul": 7, "search": 7, "page": 7}, "objects": {"": [[0, 0, 0, "-", "api"]], "api": [[1, 0, 0, "-", "cache"], [0, 0, 0, "-", "config"], [2, 0, 0, "-", "data_interface"], [0, 0, 0, "-", "exceptions"], [0, 0, 0, "-", "local_folder"], [0, 0, 0, "-", "os_utils"], [6, 0, 0, "-", "shape_selection"], [0, 0, 0, "-", "singleton"]], "api.cache": [[1, 0, 0, "-", "cache"]], "api.cache.cache": [[1, 1, 1, "", "Cached"], [1, 1, 1, "", "Parameters"]], "api.cache.cache.Cached": [[1, 2, 1, "", "cache"], [1, 2, 1, "", "clear_cache"], [1, 2, 1, "", "find_cache"], [1, 2, 1, "", "init_cache"], [1, 2, 1, "", "is_cached"]], "api.cache.cache.Parameters": [[1, 2, 1, "", "subset"]], "api.data_interface": [[3, 0, 0, "-", "cams_interface"], [2, 0, 0, "-", "data_transformations"], [2, 0, 0, "-", "dataset_config_parser"], [4, 0, 0, "-", "eac4"], [5, 0, 0, "-", "ghg"]], "api.data_interface.cams_interface": [[3, 0, 0, "-", "cams_interface"], [3, 0, 0, "-", "cams_parameters"]], "api.data_interface.cams_interface.cams_interface": [[3, 1, 1, "", "CAMSDataInterface"]], "api.data_interface.cams_interface.cams_interface.CAMSDataInterface": [[3, 2, 1, "", "build_call_body"], [3, 2, 1, "", "clear_data_files"], [3, 3, 1, "", "data_folder"], [3, 3, 1, "", "dataset_name"], [3, 3, 1, "", "file_ext"], [3, 3, 1, "", "file_format"], [3, 2, 1, "", "list_data_files"], [3, 2, 1, "", "read_dataset"]], "api.data_interface.cams_interface.cams_parameters": [[3, 1, 1, "", "CAMSParameters"]], "api.data_interface.cams_interface.cams_parameters.CAMSParameters": [[3, 2, 1, "", "build_call_body"], [3, 4, 1, "", "data_variables"], [3, 3, 1, "", "other"], [3, 2, 1, "", "subset"]], "api.data_interface.data_transformations": [[2, 5, 1, "", "clip_and_concat_shapes"], [2, 5, 1, "", "confidence_interval"], [2, 5, 1, "", "shifting_long"], [2, 5, 1, "", "split_time_dim"]], "api.data_interface.dataset_config_parser": [[2, 1, 1, "", "DatasetConfigParser"], [2, 1, 1, "", "OperationParser"]], "api.data_interface.dataset_config_parser.DatasetConfigParser": [[2, 2, 1, "", "get_config"], [2, 2, 1, "", "parse_factors"]], "api.data_interface.dataset_config_parser.OperationParser": [[2, 3, 1, "", "allowed_ops"], [2, 2, 1, "", "arithmetic_eval"]], "api.data_interface.eac4": [[4, 0, 0, "-", "eac4"], [4, 0, 0, "-", "eac4_config"], [4, 0, 0, "-", "eac4_parameters"]], "api.data_interface.eac4.eac4": [[4, 1, 1, "", "EAC4Instance"]], "api.data_interface.eac4.eac4.EAC4Instance": [[4, 3, 1, "", "dataset_dir"], [4, 3, 1, "", "dataset_name"], [4, 2, 1, "", "download"], [4, 3, 1, "", "file_ext"], [4, 3, 1, "", "file_format"], [4, 4, 1, "", "file_full_path"], [4, 2, 1, "", "read_dataset"]], "api.data_interface.eac4.eac4_config": [[4, 1, 1, "", "EAC4Config"]], "api.data_interface.eac4.eac4_config.EAC4Config": [[4, 2, 1, "", "convert_units_array"]], "api.data_interface.eac4.eac4_parameters": [[4, 1, 1, "", "EAC4Parameters"]], "api.data_interface.eac4.eac4_parameters.EAC4Parameters": [[4, 2, 1, "", "area_issubset"], [4, 2, 1, "", "build_call_body"], [4, 2, 1, "", "dates_issubset"], [4, 2, 1, "", "model_issubset"], [4, 3, 1, "", "other"], [4, 2, 1, "", "pressure_issubset"], [4, 2, 1, "", "subset"]], "api.data_interface.ghg": [[5, 0, 0, "-", "ghg"], [5, 0, 0, "-", "ghg_config"], [5, 0, 0, "-", "ghg_parameters"]], "api.data_interface.ghg.ghg": [[5, 1, 1, "", "InversionOptimisedGreenhouseGas"]], "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas": [[5, 3, 1, "", "dataset_dir"], [5, 3, 1, "", "dataset_name"], [5, 2, 1, "", "download"], [5, 3, 1, "", "file_ext"], [5, 3, 1, "", "file_format"], [5, 4, 1, "", "file_full_path"], [5, 2, 1, "", "read_dataset"]], "api.data_interface.ghg.ghg_config": [[5, 1, 1, "", "GHGConfig"]], "api.data_interface.ghg.ghg_config.GHGConfig": [[5, 2, 1, "", "convert_units_array"], [5, 2, 1, "", "get_var_names"]], "api.data_interface.ghg.ghg_parameters": [[5, 1, 1, "", "GHGParameters"]], "api.data_interface.ghg.ghg_parameters.GHGParameters": [[5, 2, 1, "", "build_call_body"], [5, 3, 1, "", "other"], [5, 2, 1, "", "subset"]], "api.exceptions": [[0, 6, 1, "", "OperationNotAllowed"]], "api.local_folder": [[0, 5, 1, "", "get_local_folder"]], "api.os_utils": [[0, 5, 1, "", "create_folder"], [0, 5, 1, "", "remove_folder"]], "api.shape_selection": [[6, 0, 0, "-", "config"], [6, 0, 0, "-", "shape_selection"], [6, 0, 0, "-", "shapefile"]], "api.shape_selection.config": [[6, 1, 1, "", "SelectionLevel"]], "api.shape_selection.config.SelectionLevel": [[6, 3, 1, "", "CONTINENTS"], [6, 3, 1, "", "COUNTRIES"], [6, 3, 1, "", "COUNTRIES_SUB"], [6, 3, 1, "", "GENERIC"], [6, 3, 1, "", "ORGANIZATIONS"]], "api.shape_selection.shape_selection": [[6, 1, 1, "", "EntitySelection"], [6, 1, 1, "", "GenericShapeSelection"], [6, 1, 1, "", "Selection"], [6, 5, 1, "", "from_out_event"], [6, 5, 1, "", "selection_empty"]], "api.shape_selection.shape_selection.EntitySelection": [[6, 2, 1, "", "convert_selection"], [6, 2, 1, "", "from_entities_list"], [6, 2, 1, "", "from_entity_selection"], [6, 2, 1, "", "from_generic_selection"]], "api.shape_selection.shape_selection.GenericShapeSelection": [[6, 2, 1, "", "convert_selection"], [6, 2, 1, "", "from_shape"]], "api.shape_selection.shape_selection.Selection": [[6, 2, 1, "", "empty"], [6, 2, 1, "", "get_event_label"], [6, 4, 1, "", "labels"]], "api.shape_selection.shapefile": [[6, 1, 1, "", "ShapefileParameters"], [6, 1, 1, "", "ShapefilesDownloader"], [6, 5, 1, "", "dissolve_shapefile_level"]], "api.shape_selection.shapefile.ShapefileParameters": [[6, 3, 1, "", "other"], [6, 2, 1, "", "subset"]], "api.shape_selection.shapefile.ShapefilesDownloader": [[6, 2, 1, "", "download"], [6, 2, 1, "", "get_as_dataframe"], [6, 4, 1, "", "shapefile_dir"], [6, 4, 1, "", "shapefile_full_path"], [6, 4, 1, "", "shapefile_name"], [6, 4, 1, "", "shapefile_url"]], "api.singleton": [[0, 1, 1, "", "Singleton"]]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:attribute", "4": "py:property", "5": "py:function", "6": "py:exception"}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "attribute", "Python attribute"], "4": ["py", "property", "Python property"], "5": ["py", "function", "Python function"], "6": ["py", "exception", "Python exception"]}, "titleterms": {"api": [0, 1, 2, 3, 4, 5, 6], "packag": [0, 1, 2, 3, 4, 5, 6], "subpackag": [0, 2], "submodul": [0, 1, 2, 3, 4, 5, 6], "config": [0, 6], "modul": [0, 1, 2, 3, 4, 5, 6], "except": 0, "local_fold": 0, "os_util": 0, "singleton": 0, "content": [0, 1, 2, 3, 4, 5, 6], "cach": 1, "data_interfac": [2, 3, 4, 5], "data_transform": 2, "dataset_config_pars": 2, "cams_interfac": 3, "cams_paramet": 3, "eac4": 4, "eac4_config": 4, "eac4_paramet": 4, "ghg": 5, "ghg_config": 5, "ghg_paramet": 5, "shape_select": 6, "shapefil": 6, "welcom": 7, "atmospher": 7, "explor": 7, "": 7, "document": 7, "indic": 7, "tabl": 7}, "envversion": {"sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx": 60}, "alltitles": {"Welcome to Atmospheric Explorer\u2019s documentation!": [[7, "welcome-to-atmospheric-explorer-s-documentation"]], "Indices and tables": [[7, "indices-and-tables"]], "api package": [[0, "api-package"]], "Subpackages": [[0, "subpackages"], [2, "subpackages"]], "Submodules": [[0, "submodules"], [2, "submodules"], [1, "submodules"], [3, "submodules"], [4, "submodules"], [5, "submodules"], [6, "submodules"]], "api.config module": [[0, "module-api.config"]], "api.exceptions module": [[0, "module-api.exceptions"]], "api.local_folder module": [[0, "module-api.local_folder"]], "api.os_utils module": [[0, "module-api.os_utils"]], "api.singleton module": [[0, "module-api.singleton"]], "Module contents": [[0, "module-api"], [2, "module-api.data_interface"], [1, "module-api.cache"], [3, "module-api.data_interface.cams_interface"], [4, "module-api.data_interface.eac4"], [5, "module-api.data_interface.ghg"], [6, "module-api.shape_selection"]], "api.data_interface package": [[2, "api-data-interface-package"]], "api.data_interface.data_transformations module": [[2, "module-api.data_interface.data_transformations"]], "api.data_interface.dataset_config_parser module": [[2, "module-api.data_interface.dataset_config_parser"]], "api.cache package": [[1, "api-cache-package"]], "api.cache.cache module": [[1, "module-api.cache.cache"]], "api.data_interface.cams_interface package": [[3, "api-data-interface-cams-interface-package"]], "api.data_interface.cams_interface.cams_interface module": [[3, "module-api.data_interface.cams_interface.cams_interface"]], "api.data_interface.cams_interface.cams_parameters module": [[3, "module-api.data_interface.cams_interface.cams_parameters"]], "api.data_interface.eac4 package": [[4, "api-data-interface-eac4-package"]], "api.data_interface.eac4.eac4 module": [[4, "module-api.data_interface.eac4.eac4"]], "api.data_interface.eac4.eac4_config module": [[4, "module-api.data_interface.eac4.eac4_config"]], "api.data_interface.eac4.eac4_parameters module": [[4, "module-api.data_interface.eac4.eac4_parameters"]], "api.data_interface.ghg package": [[5, "api-data-interface-ghg-package"]], "api.data_interface.ghg.ghg module": [[5, "module-api.data_interface.ghg.ghg"]], "api.data_interface.ghg.ghg_config module": [[5, "module-api.data_interface.ghg.ghg_config"]], "api.data_interface.ghg.ghg_parameters module": [[5, "module-api.data_interface.ghg.ghg_parameters"]], "api.shape_selection package": [[6, "api-shape-selection-package"]], "api.shape_selection.config module": [[6, "module-api.shape_selection.config"]], "api.shape_selection.shape_selection module": [[6, "module-api.shape_selection.shape_selection"]], "api.shape_selection.shapefile module": [[6, "module-api.shape_selection.shapefile"]]}, "indexentries": {"operationnotallowed": [[0, "api.exceptions.OperationNotAllowed"]], "singleton (class in api.singleton)": [[0, "api.singleton.Singleton"]], "api": [[0, "module-api"]], "api.config": [[0, "module-api.config"]], "api.exceptions": [[0, "module-api.exceptions"]], "api.local_folder": [[0, "module-api.local_folder"]], "api.os_utils": [[0, "module-api.os_utils"]], "api.singleton": [[0, "module-api.singleton"]], "create_folder() (in module api.os_utils)": [[0, "api.os_utils.create_folder"]], "get_local_folder() (in module api.local_folder)": [[0, "api.local_folder.get_local_folder"]], "module": [[0, "module-api"], [0, "module-api.config"], [0, "module-api.exceptions"], [0, "module-api.local_folder"], [0, "module-api.os_utils"], [0, "module-api.singleton"], [1, "module-api.cache"], [1, "module-api.cache.cache"], [2, "module-api.data_interface"], [2, "module-api.data_interface.data_transformations"], [2, "module-api.data_interface.dataset_config_parser"], [3, "module-api.data_interface.cams_interface"], [3, "module-api.data_interface.cams_interface.cams_interface"], [3, "module-api.data_interface.cams_interface.cams_parameters"], [4, "module-api.data_interface.eac4"], [4, "module-api.data_interface.eac4.eac4"], [4, "module-api.data_interface.eac4.eac4_config"], [4, "module-api.data_interface.eac4.eac4_parameters"], [5, "module-api.data_interface.ghg"], [5, "module-api.data_interface.ghg.ghg"], [5, "module-api.data_interface.ghg.ghg_config"], [5, "module-api.data_interface.ghg.ghg_parameters"], [6, "module-api.shape_selection"], [6, "module-api.shape_selection.config"], [6, "module-api.shape_selection.shape_selection"], [6, "module-api.shape_selection.shapefile"]], "remove_folder() (in module api.os_utils)": [[0, "api.os_utils.remove_folder"]], "cached (class in api.cache.cache)": [[1, "api.cache.cache.Cached"]], "parameters (class in api.cache.cache)": [[1, "api.cache.cache.Parameters"]], "api.cache": [[1, "module-api.cache"]], "api.cache.cache": [[1, "module-api.cache.cache"]], "cache() (api.cache.cache.cached method)": [[1, "api.cache.cache.Cached.cache"]], "clear_cache() (api.cache.cache.cached class method)": [[1, "api.cache.cache.Cached.clear_cache"]], "find_cache() (api.cache.cache.cached class method)": [[1, "api.cache.cache.Cached.find_cache"]], "init_cache() (api.cache.cache.cached static method)": [[1, "api.cache.cache.Cached.init_cache"]], "is_cached() (api.cache.cache.cached method)": [[1, "api.cache.cache.Cached.is_cached"]], "subset() (api.cache.cache.parameters method)": [[1, "api.cache.cache.Parameters.subset"]], "datasetconfigparser (class in api.data_interface.dataset_config_parser)": [[2, "api.data_interface.dataset_config_parser.DatasetConfigParser"]], "operationparser (class in api.data_interface.dataset_config_parser)": [[2, "api.data_interface.dataset_config_parser.OperationParser"]], "allowed_ops (api.data_interface.dataset_config_parser.operationparser attribute)": [[2, "api.data_interface.dataset_config_parser.OperationParser.allowed_ops"]], "api.data_interface": [[2, "module-api.data_interface"]], "api.data_interface.data_transformations": [[2, "module-api.data_interface.data_transformations"]], "api.data_interface.dataset_config_parser": [[2, "module-api.data_interface.dataset_config_parser"]], "arithmetic_eval() (api.data_interface.dataset_config_parser.operationparser method)": [[2, "api.data_interface.dataset_config_parser.OperationParser.arithmetic_eval"]], "clip_and_concat_shapes() (in module api.data_interface.data_transformations)": [[2, "api.data_interface.data_transformations.clip_and_concat_shapes"]], "confidence_interval() (in module api.data_interface.data_transformations)": [[2, "api.data_interface.data_transformations.confidence_interval"]], "get_config() (api.data_interface.dataset_config_parser.datasetconfigparser class method)": [[2, "api.data_interface.dataset_config_parser.DatasetConfigParser.get_config"]], "parse_factors() (api.data_interface.dataset_config_parser.datasetconfigparser method)": [[2, "api.data_interface.dataset_config_parser.DatasetConfigParser.parse_factors"]], "shifting_long() (in module api.data_interface.data_transformations)": [[2, "api.data_interface.data_transformations.shifting_long"]], "split_time_dim() (in module api.data_interface.data_transformations)": [[2, "api.data_interface.data_transformations.split_time_dim"]], "camsdatainterface (class in api.data_interface.cams_interface.cams_interface)": [[3, "api.data_interface.cams_interface.cams_interface.CAMSDataInterface"]], "camsparameters (class in api.data_interface.cams_interface.cams_parameters)": [[3, "api.data_interface.cams_interface.cams_parameters.CAMSParameters"]], "api.data_interface.cams_interface": [[3, "module-api.data_interface.cams_interface"]], "api.data_interface.cams_interface.cams_interface": [[3, "module-api.data_interface.cams_interface.cams_interface"]], "api.data_interface.cams_interface.cams_parameters": [[3, "module-api.data_interface.cams_interface.cams_parameters"]], "build_call_body() (api.data_interface.cams_interface.cams_interface.camsdatainterface method)": [[3, "api.data_interface.cams_interface.cams_interface.CAMSDataInterface.build_call_body"]], "build_call_body() (api.data_interface.cams_interface.cams_parameters.camsparameters method)": [[3, "api.data_interface.cams_interface.cams_parameters.CAMSParameters.build_call_body"]], "clear_data_files() (api.data_interface.cams_interface.cams_interface.camsdatainterface class method)": [[3, "api.data_interface.cams_interface.cams_interface.CAMSDataInterface.clear_data_files"]], "data_folder (api.data_interface.cams_interface.cams_interface.camsdatainterface attribute)": [[3, "api.data_interface.cams_interface.cams_interface.CAMSDataInterface.data_folder"]], "data_variables (api.data_interface.cams_interface.cams_parameters.camsparameters property)": [[3, "api.data_interface.cams_interface.cams_parameters.CAMSParameters.data_variables"]], "dataset_name (api.data_interface.cams_interface.cams_interface.camsdatainterface attribute)": [[3, "api.data_interface.cams_interface.cams_interface.CAMSDataInterface.dataset_name"]], "file_ext (api.data_interface.cams_interface.cams_interface.camsdatainterface attribute)": [[3, "api.data_interface.cams_interface.cams_interface.CAMSDataInterface.file_ext"]], "file_format (api.data_interface.cams_interface.cams_interface.camsdatainterface attribute)": [[3, "api.data_interface.cams_interface.cams_interface.CAMSDataInterface.file_format"]], "list_data_files() (api.data_interface.cams_interface.cams_interface.camsdatainterface class method)": [[3, "api.data_interface.cams_interface.cams_interface.CAMSDataInterface.list_data_files"]], "other (api.data_interface.cams_interface.cams_parameters.camsparameters attribute)": [[3, "api.data_interface.cams_interface.cams_parameters.CAMSParameters.other"]], "read_dataset() (api.data_interface.cams_interface.cams_interface.camsdatainterface method)": [[3, "api.data_interface.cams_interface.cams_interface.CAMSDataInterface.read_dataset"]], "subset() (api.data_interface.cams_interface.cams_parameters.camsparameters method)": [[3, "api.data_interface.cams_interface.cams_parameters.CAMSParameters.subset"]], "eac4config (class in api.data_interface.eac4.eac4_config)": [[4, "api.data_interface.eac4.eac4_config.EAC4Config"]], "eac4instance (class in api.data_interface.eac4.eac4)": [[4, "api.data_interface.eac4.eac4.EAC4Instance"]], "eac4parameters (class in api.data_interface.eac4.eac4_parameters)": [[4, "api.data_interface.eac4.eac4_parameters.EAC4Parameters"]], "api.data_interface.eac4": [[4, "module-api.data_interface.eac4"]], "api.data_interface.eac4.eac4": [[4, "module-api.data_interface.eac4.eac4"]], "api.data_interface.eac4.eac4_config": [[4, "module-api.data_interface.eac4.eac4_config"]], "api.data_interface.eac4.eac4_parameters": [[4, "module-api.data_interface.eac4.eac4_parameters"]], "area_issubset() (api.data_interface.eac4.eac4_parameters.eac4parameters static method)": [[4, "api.data_interface.eac4.eac4_parameters.EAC4Parameters.area_issubset"]], "build_call_body() (api.data_interface.eac4.eac4_parameters.eac4parameters method)": [[4, "api.data_interface.eac4.eac4_parameters.EAC4Parameters.build_call_body"]], "convert_units_array() (api.data_interface.eac4.eac4_config.eac4config class method)": [[4, "api.data_interface.eac4.eac4_config.EAC4Config.convert_units_array"]], "dataset_dir (api.data_interface.eac4.eac4.eac4instance attribute)": [[4, "api.data_interface.eac4.eac4.EAC4Instance.dataset_dir"]], "dataset_name (api.data_interface.eac4.eac4.eac4instance attribute)": [[4, "api.data_interface.eac4.eac4.EAC4Instance.dataset_name"]], "dates_issubset() (api.data_interface.eac4.eac4_parameters.eac4parameters static method)": [[4, "api.data_interface.eac4.eac4_parameters.EAC4Parameters.dates_issubset"]], "download() (api.data_interface.eac4.eac4.eac4instance method)": [[4, "api.data_interface.eac4.eac4.EAC4Instance.download"]], "file_ext (api.data_interface.eac4.eac4.eac4instance attribute)": [[4, "api.data_interface.eac4.eac4.EAC4Instance.file_ext"]], "file_format (api.data_interface.eac4.eac4.eac4instance attribute)": [[4, "api.data_interface.eac4.eac4.EAC4Instance.file_format"]], "file_full_path (api.data_interface.eac4.eac4.eac4instance property)": [[4, "api.data_interface.eac4.eac4.EAC4Instance.file_full_path"]], "model_issubset() (api.data_interface.eac4.eac4_parameters.eac4parameters static method)": [[4, "api.data_interface.eac4.eac4_parameters.EAC4Parameters.model_issubset"]], "other (api.data_interface.eac4.eac4_parameters.eac4parameters attribute)": [[4, "api.data_interface.eac4.eac4_parameters.EAC4Parameters.other"]], "pressure_issubset() (api.data_interface.eac4.eac4_parameters.eac4parameters static method)": [[4, "api.data_interface.eac4.eac4_parameters.EAC4Parameters.pressure_issubset"]], "read_dataset() (api.data_interface.eac4.eac4.eac4instance method)": [[4, "api.data_interface.eac4.eac4.EAC4Instance.read_dataset"]], "subset() (api.data_interface.eac4.eac4_parameters.eac4parameters method)": [[4, "api.data_interface.eac4.eac4_parameters.EAC4Parameters.subset"]], "ghgconfig (class in api.data_interface.ghg.ghg_config)": [[5, "api.data_interface.ghg.ghg_config.GHGConfig"]], "ghgparameters (class in api.data_interface.ghg.ghg_parameters)": [[5, "api.data_interface.ghg.ghg_parameters.GHGParameters"]], "inversionoptimisedgreenhousegas (class in api.data_interface.ghg.ghg)": [[5, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas"]], "api.data_interface.ghg": [[5, "module-api.data_interface.ghg"]], "api.data_interface.ghg.ghg": [[5, "module-api.data_interface.ghg.ghg"]], "api.data_interface.ghg.ghg_config": [[5, "module-api.data_interface.ghg.ghg_config"]], "api.data_interface.ghg.ghg_parameters": [[5, "module-api.data_interface.ghg.ghg_parameters"]], "build_call_body() (api.data_interface.ghg.ghg_parameters.ghgparameters method)": [[5, "api.data_interface.ghg.ghg_parameters.GHGParameters.build_call_body"]], "convert_units_array() (api.data_interface.ghg.ghg_config.ghgconfig class method)": [[5, "api.data_interface.ghg.ghg_config.GHGConfig.convert_units_array"]], "dataset_dir (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas attribute)": [[5, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.dataset_dir"]], "dataset_name (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas attribute)": [[5, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.dataset_name"]], "download() (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas method)": [[5, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.download"]], "file_ext (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas attribute)": [[5, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.file_ext"]], "file_format (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas attribute)": [[5, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.file_format"]], "file_full_path (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas property)": [[5, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.file_full_path"]], "get_var_names() (api.data_interface.ghg.ghg_config.ghgconfig class method)": [[5, "api.data_interface.ghg.ghg_config.GHGConfig.get_var_names"]], "other (api.data_interface.ghg.ghg_parameters.ghgparameters attribute)": [[5, "api.data_interface.ghg.ghg_parameters.GHGParameters.other"]], "read_dataset() (api.data_interface.ghg.ghg.inversionoptimisedgreenhousegas method)": [[5, "api.data_interface.ghg.ghg.InversionOptimisedGreenhouseGas.read_dataset"]], "subset() (api.data_interface.ghg.ghg_parameters.ghgparameters method)": [[5, "api.data_interface.ghg.ghg_parameters.GHGParameters.subset"]], "continents (api.shape_selection.config.selectionlevel attribute)": [[6, "api.shape_selection.config.SelectionLevel.CONTINENTS"]], "countries (api.shape_selection.config.selectionlevel attribute)": [[6, "api.shape_selection.config.SelectionLevel.COUNTRIES"]], "countries_sub (api.shape_selection.config.selectionlevel attribute)": [[6, "api.shape_selection.config.SelectionLevel.COUNTRIES_SUB"]], "entityselection (class in api.shape_selection.shape_selection)": [[6, "api.shape_selection.shape_selection.EntitySelection"]], "generic (api.shape_selection.config.selectionlevel attribute)": [[6, "api.shape_selection.config.SelectionLevel.GENERIC"]], "genericshapeselection (class in api.shape_selection.shape_selection)": [[6, "api.shape_selection.shape_selection.GenericShapeSelection"]], "organizations (api.shape_selection.config.selectionlevel attribute)": [[6, "api.shape_selection.config.SelectionLevel.ORGANIZATIONS"]], "selection (class in api.shape_selection.shape_selection)": [[6, "api.shape_selection.shape_selection.Selection"]], "selectionlevel (class in api.shape_selection.config)": [[6, "api.shape_selection.config.SelectionLevel"]], "shapefileparameters (class in api.shape_selection.shapefile)": [[6, "api.shape_selection.shapefile.ShapefileParameters"]], "shapefilesdownloader (class in api.shape_selection.shapefile)": [[6, "api.shape_selection.shapefile.ShapefilesDownloader"]], "api.shape_selection": [[6, "module-api.shape_selection"]], "api.shape_selection.config": [[6, "module-api.shape_selection.config"]], "api.shape_selection.shape_selection": [[6, "module-api.shape_selection.shape_selection"]], "api.shape_selection.shapefile": [[6, "module-api.shape_selection.shapefile"]], "convert_selection() (api.shape_selection.shape_selection.entityselection class method)": [[6, "api.shape_selection.shape_selection.EntitySelection.convert_selection"]], "convert_selection() (api.shape_selection.shape_selection.genericshapeselection class method)": [[6, "api.shape_selection.shape_selection.GenericShapeSelection.convert_selection"]], "dissolve_shapefile_level() (in module api.shape_selection.shapefile)": [[6, "api.shape_selection.shapefile.dissolve_shapefile_level"]], "download() (api.shape_selection.shapefile.shapefilesdownloader method)": [[6, "api.shape_selection.shapefile.ShapefilesDownloader.download"]], "empty() (api.shape_selection.shape_selection.selection method)": [[6, "api.shape_selection.shape_selection.Selection.empty"]], "from_entities_list() (api.shape_selection.shape_selection.entityselection class method)": [[6, "api.shape_selection.shape_selection.EntitySelection.from_entities_list"]], "from_entity_selection() (api.shape_selection.shape_selection.entityselection class method)": [[6, "api.shape_selection.shape_selection.EntitySelection.from_entity_selection"]], "from_generic_selection() (api.shape_selection.shape_selection.entityselection class method)": [[6, "api.shape_selection.shape_selection.EntitySelection.from_generic_selection"]], "from_out_event() (in module api.shape_selection.shape_selection)": [[6, "api.shape_selection.shape_selection.from_out_event"]], "from_shape() (api.shape_selection.shape_selection.genericshapeselection class method)": [[6, "api.shape_selection.shape_selection.GenericShapeSelection.from_shape"]], "get_as_dataframe() (api.shape_selection.shapefile.shapefilesdownloader method)": [[6, "api.shape_selection.shapefile.ShapefilesDownloader.get_as_dataframe"]], "get_event_label() (api.shape_selection.shape_selection.selection static method)": [[6, "api.shape_selection.shape_selection.Selection.get_event_label"]], "labels (api.shape_selection.shape_selection.selection property)": [[6, "api.shape_selection.shape_selection.Selection.labels"]], "other (api.shape_selection.shapefile.shapefileparameters attribute)": [[6, "api.shape_selection.shapefile.ShapefileParameters.other"]], "selection_empty() (in module api.shape_selection.shape_selection)": [[6, "api.shape_selection.shape_selection.selection_empty"]], "shapefile_dir (api.shape_selection.shapefile.shapefilesdownloader property)": [[6, "api.shape_selection.shapefile.ShapefilesDownloader.shapefile_dir"]], "shapefile_full_path (api.shape_selection.shapefile.shapefilesdownloader property)": [[6, "api.shape_selection.shapefile.ShapefilesDownloader.shapefile_full_path"]], "shapefile_name (api.shape_selection.shapefile.shapefilesdownloader property)": [[6, "api.shape_selection.shapefile.ShapefilesDownloader.shapefile_name"]], "shapefile_url (api.shape_selection.shapefile.shapefilesdownloader property)": [[6, "api.shape_selection.shapefile.ShapefilesDownloader.shapefile_url"]], "subset() (api.shape_selection.shapefile.shapefileparameters method)": [[6, "api.shape_selection.shapefile.ShapefileParameters.subset"]]}}) diff --git a/documentation/source/api.cache.rst b/documentation/source/api.cache.rst new file mode 100644 index 0000000..23dd6dc --- /dev/null +++ b/documentation/source/api.cache.rst @@ -0,0 +1,21 @@ +api.cache package +================= + +Submodules +---------- + +api.cache.cache module +---------------------- + +.. automodule:: api.cache.cache + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: api.cache + :members: + :undoc-members: + :show-inheritance: diff --git a/documentation/source/api.data_interface.cams_interface.rst b/documentation/source/api.data_interface.cams_interface.rst new file mode 100644 index 0000000..0664a7b --- /dev/null +++ b/documentation/source/api.data_interface.cams_interface.rst @@ -0,0 +1,29 @@ +api.data\_interface.cams\_interface package +=========================================== + +Submodules +---------- + +api.data\_interface.cams\_interface.cams\_interface module +---------------------------------------------------------- + +.. automodule:: api.data_interface.cams_interface.cams_interface + :members: + :undoc-members: + :show-inheritance: + +api.data\_interface.cams\_interface.cams\_parameters module +----------------------------------------------------------- + +.. automodule:: api.data_interface.cams_interface.cams_parameters + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: api.data_interface.cams_interface + :members: + :undoc-members: + :show-inheritance: diff --git a/documentation/source/api.data_interface.eac4.rst b/documentation/source/api.data_interface.eac4.rst index 047a0e4..b132f17 100644 --- a/documentation/source/api.data_interface.eac4.rst +++ b/documentation/source/api.data_interface.eac4.rst @@ -19,7 +19,14 @@ api.data\_interface.eac4.eac4\_config module :members: :undoc-members: :show-inheritance: - :exclude-members: config + +api.data\_interface.eac4.eac4\_parameters module +------------------------------------------------ + +.. automodule:: api.data_interface.eac4.eac4_parameters + :members: + :undoc-members: + :show-inheritance: Module contents --------------- diff --git a/documentation/source/api.data_interface.ghg.rst b/documentation/source/api.data_interface.ghg.rst index 2856cb9..1d4b1c4 100644 --- a/documentation/source/api.data_interface.ghg.rst +++ b/documentation/source/api.data_interface.ghg.rst @@ -19,7 +19,14 @@ api.data\_interface.ghg.ghg\_config module :members: :undoc-members: :show-inheritance: - :exclude-members: config + +api.data\_interface.ghg.ghg\_parameters module +---------------------------------------------- + +.. automodule:: api.data_interface.ghg.ghg_parameters + :members: + :undoc-members: + :show-inheritance: Module contents --------------- diff --git a/documentation/source/api.data_interface.rst b/documentation/source/api.data_interface.rst index 3a1efcd..2e6961d 100644 --- a/documentation/source/api.data_interface.rst +++ b/documentation/source/api.data_interface.rst @@ -7,32 +7,25 @@ Subpackages .. toctree:: :maxdepth: 4 + api.data_interface.cams_interface api.data_interface.eac4 api.data_interface.ghg Submodules ---------- -api.data\_interface.cams\_interface module ------------------------------------------- - -.. automodule:: api.data_interface.cams_interface - :members: - :undoc-members: - :show-inheritance: - -api.data\_interface.config\_parser module ------------------------------------------ +api.data\_interface.data\_transformations module +------------------------------------------------ -.. automodule:: api.data_interface.config_parser +.. automodule:: api.data_interface.data_transformations :members: :undoc-members: :show-inheritance: -api.data\_interface.data\_transformations module ------------------------------------------------- +api.data\_interface.dataset\_config\_parser module +-------------------------------------------------- -.. automodule:: api.data_interface.data_transformations +.. automodule:: api.data_interface.dataset_config_parser :members: :undoc-members: :show-inheritance: diff --git a/documentation/source/api.loggers.rst b/documentation/source/api.loggers.rst new file mode 100644 index 0000000..cd3bc07 --- /dev/null +++ b/documentation/source/api.loggers.rst @@ -0,0 +1,29 @@ +api.loggers package +=================== + +Submodules +---------- + +api.loggers.loggers module +-------------------------- + +.. automodule:: api.loggers.loggers + :members: + :undoc-members: + :show-inheritance: + +api.loggers.loggers\_utils module +--------------------------------- + +.. automodule:: api.loggers.loggers_utils + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: api.loggers + :members: + :undoc-members: + :show-inheritance: diff --git a/documentation/source/api.rst b/documentation/source/api.rst index e0fe674..1b1e41c 100644 --- a/documentation/source/api.rst +++ b/documentation/source/api.rst @@ -7,7 +7,9 @@ Subpackages .. toctree:: :maxdepth: 4 + api.cache api.data_interface + api.loggers api.plotting api.shape_selection @@ -30,18 +32,26 @@ api.exceptions module :undoc-members: :show-inheritance: -api.loggers module ------------------- +api.local\_folder module +------------------------ -.. automodule:: api.loggers +.. automodule:: api.local_folder :members: :undoc-members: :show-inheritance: -api.os\_manager module ----------------------- +api.os\_utils module +-------------------- -.. automodule:: api.os_manager +.. automodule:: api.os_utils + :members: + :undoc-members: + :show-inheritance: + +api.singleton module +-------------------- + +.. automodule:: api.singleton :members: :undoc-members: :show-inheritance: diff --git a/tests/api/cache/test_cached.py b/tests/api/cache/test_cached.py index 991c027..3b7036b 100644 --- a/tests/api/cache/test_cached.py +++ b/tests/api/cache/test_cached.py @@ -19,7 +19,6 @@ def subset(self: ParametersClass, other: ParametersClass) -> bool: class CachedClass(Cached): - def __new__(cls, arg): par = ParametersClass(arg) return Cached.__new__(CachedClass, par) diff --git a/tests/api/data_interface/cams_interface/test_cams_interfaces.py b/tests/api/data_interface/cams_interface/test_cams_interfaces.py index f2086e2..2be2563 100644 --- a/tests/api/data_interface/cams_interface/test_cams_interfaces.py +++ b/tests/api/data_interface/cams_interface/test_cams_interfaces.py @@ -4,8 +4,10 @@ # pylint: disable=protected-access # pylint: disable=unused-argument -from atmospheric_explorer.api.data_interface.cams_interface import CAMSDataInterface -from atmospheric_explorer.api.data_interface.cams_interface import CAMSParameters +from atmospheric_explorer.api.data_interface.cams_interface import ( + CAMSDataInterface, + CAMSParameters, +) class CAMSDataInterfaceTesting(CAMSDataInterface): diff --git a/tests/api/data_interface/eac4/test_eac4.py b/tests/api/data_interface/eac4/test_eac4.py index cfd1544..ecdb054 100644 --- a/tests/api/data_interface/eac4/test_eac4.py +++ b/tests/api/data_interface/eac4/test_eac4.py @@ -6,8 +6,7 @@ import pytest -from atmospheric_explorer.api.data_interface.eac4 import EAC4Instance -from atmospheric_explorer.api.data_interface.eac4 import EAC4Parameters +from atmospheric_explorer.api.data_interface.eac4 import EAC4Instance, EAC4Parameters @pytest.fixture(autouse=True) diff --git a/tests/api/data_interface/ghg/test_ghg_parameters.py b/tests/api/data_interface/ghg/test_ghg_parameters.py index 1e04915..8d2d75c 100644 --- a/tests/api/data_interface/ghg/test_ghg_parameters.py +++ b/tests/api/data_interface/ghg/test_ghg_parameters.py @@ -36,8 +36,8 @@ def test_build_call_body(): month={"01", "02"}, ) res = obj.build_call_body() - res['year'] = sorted(res['year']) - res['month'] = sorted(res['month']) + res["year"] = sorted(res["year"]) + res["month"] = sorted(res["month"]) assert res == { "variable": "a", "quantity": "quantity2", @@ -45,5 +45,5 @@ def test_build_call_body(): "time_aggregation": "time_aggregation", "year": ["2021", "2022"], "month": ["01", "02"], - "version": "latest" + "version": "latest", }
  • + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    api.cache package

    +
    +

    Submodules

    +
    +
    +

    api.cache.cache module

    +

    This module defines classes used for caching.

    +
    +
    +class api.cache.cache.Cached(parameters: Parameters)
    +

    Bases: object

    +

    This class defines a few methods that allow to cache another class instances.

    +

    Caching is based on the class attributes.

    +

    To cache a class, inherit from this class and define the __new__ method: +this method is only needed to instantiate the Parameters instance +corresponding to the specific CAMS dataset and pass it to Cached.__new__.

    +

    Last, one needs to decorate the new class __init__ with the static method Cached.init_cache.

    +
    +
    +cache() None
    +

    Cache self.

    +
    + +
    +
    +classmethod clear_cache()
    +

    Clear cache.

    +
    + +
    +
    +classmethod find_cache(parameters: Parameters) Cached | None
    +

    Find obj in cache that has a superset of the parameters passed in kwargs.

    +
    + +
    +
    +static init_cache(func)
    +

    Wrapper for the __init__ method of a cached class.

    +

    Checks if self is already cached. If yes, returs None. +If not, runs the __init__ and then caches the initialized instance of the class.

    +
    + +
    +
    +is_cached() bool
    +

    Check if self or a superset of it is already cached.

    +
    + +
    + +
    +
    +class api.cache.cache.Parameters
    +

    Bases: ABC

    +

    Abstract class to instantiate dataset parameters, used for caching.

    +
    +
    +abstract subset(other: Parameters) bool
    +

    Determine wether this parameters instance makes up a subset of parameters.

    +
    + +
    + +
    +
    +

    Module contents

    +

    Module containing all caching functionalities for CAMS datasets.

    +

    Datasets are downloaded when needed, and each call is cached in memory in order to avoid +downloading the same dataset multiple times.

    +

    In this submodule we define two classes:

    +
      +
    • +
      Parameters:

      Abstract base class to be inherited by the dataset parameters.

      +

      Each dataset should have a Parameters subclass where all its cdsapi parameters are defined.

      +

      This class also expects a subset instance method to be defined: +this method expects another Parameters instance as the other argument +and returns True if other has the same or a superset of the Parameters +of the self instance.

      +
      +
      +
    • +
    • +
      Cached:

      Class with some methods needed for caching a CAMS dataset call.

      +

      To cache a class, inherit from this class and define the __new__ method: +this method is only needed to instantiate the Parameters instance +corresponding to the specific CAMS dataset and pass it to Cached.__new__.

      +

      Last, one needs to decorate the new class __init__ with the static method Cached.init_cache.

      +

      See eac4 and ghg submodules as an example.

      +
      +
      +
    • +
    +

    Note that this cache only keeps track of files downloaded during a session and ignores preiously downloaded files. +This means that it only works when using the UI or the APIs, +while doesn’t work with the CLI since each command is a session by itself.

    +
    +
    + + +
    +
    + +
    +
    +
    +