diff --git a/docs/intro/concepts.rst b/docs/intro/concepts.rst index acf90ff..71e375f 100644 --- a/docs/intro/concepts.rst +++ b/docs/intro/concepts.rst @@ -12,7 +12,7 @@ The CSEP experiments consist in the prospective evaluation of probabilistic eart A Floating Testing Experiment encapsulates each experiment into its own runnable environment, taking advantage of version control (i.e. ``git``), open-data repositories (e.g. `Zenodo `_) and Infrastructure as Code (e.g. `Docker `_) technologies, making it reproducible, re-usable and shareable during the time scale of the evaluations. -``feCSEP`` goals +``floatCSEP`` goals ---------------- This is an application to deploy reproducible and prospective experiments of earthquake forecasting, namely a Floating Eperiment, that can operate independent of a particular testing server. With this application, researchers, institutions and users can diff --git a/docs/reference/api_reference.rst b/docs/reference/api_reference.rst index ef9c1c9..a3d7871 100644 --- a/docs/reference/api_reference.rst +++ b/docs/reference/api_reference.rst @@ -10,9 +10,9 @@ Commands The commands and entry-points with which to call `floatcsep` from the terminal are: -.. :currentmodule:: floatcsep.cmd.main +.. :currentmodule:: floatcsep.commands.main -.. automodule:: floatcsep.cmd.main +.. automodule:: floatcsep.commands.main .. autosummary:: :toctree: generated @@ -104,46 +104,25 @@ A test is defined using the :class:`Evaluation` class. Accessors --------- -.. :currentmodule:: floatcsep.accessors +.. :currentmodule:: floatcsep.utils.accessors -.. automodule:: floatcsep.accessors +.. automodule:: floatcsep.utils.accessors .. autosummary:: :toctree: generated from_zenodo from_git + download_file + check_hash -Environments ------------- +Helper Functions +---------------- -.. :currentmodule:: floatcsep.environments +.. :currentmodule:: floatcsep.utils.helpers -.. automodule:: floatcsep.environments - -.. autosummary:: - :toctree: generated - - CondaManager - CondaManager.create_environment - CondaManager.env_exists - CondaManager.install_dependencies - CondaManager.run_command - - VenvManager - CondaManager.create_environment - CondaManager.env_exists - CondaManager.install_dependencies - CondaManager.run_command - - -Utilities ---------- - -.. :currentmodule:: floatcsep.utils - -.. automodule:: floatcsep.utils +.. automodule:: floatcsep.utils.helpers .. autosummary:: :toctree: generated @@ -154,9 +133,6 @@ Utilities read_region_config timewindows_ti timewindows_td - Task - Task.run - Task.check_exist timewindow2str plot_sequential_likelihood magnitude_vs_time @@ -168,9 +144,9 @@ Utilities Readers ------- -.. :currentmodule:: floatcsep.readers +.. :currentmodule:: floatcsep.utils.readers -.. automodule:: floatcsep.readers +.. automodule:: floatcsep.utils.readers .. autosummary:: :toctree: generated @@ -184,3 +160,122 @@ Readers serialize +Environments +------------ + +.. :currentmodule:: floatcsep.infrastructure.environments + +.. automodule:: floatcsep.infrastructure.environments + +.. autosummary:: + :toctree: generated + + CondaManager + CondaManager.create_environment + CondaManager.env_exists + CondaManager.install_dependencies + CondaManager.run_command + + VenvManager + CondaManager.create_environment + CondaManager.env_exists + CondaManager.install_dependencies + CondaManager.run_command + + +Registries +---------- + +.. :currentmodule:: floatcsep.infrastructure.registries + +.. automodule:: floatcsep.infrastructure.registries + +.. autosummary:: + :toctree: generated + + FileRegistry + FileRegistry.abs + FileRegistry.absdir + FileRegistry.rel + FileRegistry.rel_dir + FileRegistry.file_exists + + ForecastRegistry + ForecastRegistry.get + ForecastRegistry.get_forecast + ForecastRegistry.dir + ForecastRegistry.fmt + ForecastRegistry.as_dict + ForecastRegistry.forecast_exist + ForecastRegistry.build_tree + ForecastRegistry.log_tree + + ExperimentRegistry + ExperimentRegistry.add_forecast_registry + ExperimentRegistry.get_forecast_registry + ExperimentRegistry.log_forecast_trees + ExperimentRegistry.get + ExperimentRegistry.get_result + ExperimentRegistry.get_test_catalog + ExperimentRegistry.get_figure + ExperimentRegistry.result_exist + ExperimentRegistry.as_dict + ExperimentRegistry.build_tree + ExperimentRegistry.log_results_tree + + +Repositories +------------ + +.. :currentmodule:: floatcsep.infrastructure.repositories + +.. automodule:: floatcsep.infrastructure.repositories + +.. autosummary:: + :toctree: generated + + ForecastRepository + ForecastRepository.factory + + CatalogForecastRepository + CatalogForecastRepository.load_forecast + CatalogForecastRepository._load_single_forecast + + GriddedForecastRepository.load_forecast + GriddedForecastRepository._get_or_load_forecast + GriddedForecastRepository._load_single_forecast + + ResultsRepository + ResultsRepository._load_result + ResultsRepository.load_results + ResultsRepository.write_result + + CatalogRepository + CatalogRepository.set_main_catalog + CatalogRepository.catalog + CatalogRepository.get_test_cat + CatalogRepository.set_test_cat + CatalogRepository.set_input_cat + + +Engine +------ + +.. :currentmodule:: floatcsep.infrastructure.engine + +.. automodule:: floatcsep.infrastructure.engine + +.. autosummary:: + :toctree: generated + + Task + Task.sign_match + Task.run + Task.check_exist + + TaskGraph + TaskGraph.ntasks + TaskGraph.add + TaskGraph.add_dependency + TaskGraph.run + TaskGraph.check_exist \ No newline at end of file diff --git a/examples/case_h/custom_report.py b/examples/case_h/custom_report.py index 219b6d2..2d4d0cf 100644 --- a/examples/case_h/custom_report.py +++ b/examples/case_h/custom_report.py @@ -1,5 +1,5 @@ -from floatcsep.report import MarkdownReport -from floatcsep.utils import timewindow2str +from floatcsep.postprocess.reporting import MarkdownReport +from floatcsep.utils.helpers import timewindow2str def main(experiment): diff --git a/floatcsep/__init__.py b/floatcsep/__init__.py index e7de331..ceb1deb 100644 --- a/floatcsep/__init__.py +++ b/floatcsep/__init__.py @@ -1,9 +1,8 @@ -from floatcsep import logger -from floatcsep import registry -from floatcsep import accessors from floatcsep import evaluation from floatcsep import experiment from floatcsep import model -from floatcsep import readers +from floatcsep.infrastructure import engine, environments, registries, repositories, logger +from floatcsep.utils import readers, accessors, helpers +from floatcsep.postprocess import reporting, plot_handler __version__ = "0.1.4" diff --git a/floatcsep/commands/__init__.py b/floatcsep/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/floatcsep/cmd/main.py b/floatcsep/commands/main.py similarity index 90% rename from floatcsep/cmd/main.py rename to floatcsep/commands/main.py index 7e449c8..7035a65 100644 --- a/floatcsep/cmd/main.py +++ b/floatcsep/commands/main.py @@ -3,9 +3,14 @@ from floatcsep import __version__ from floatcsep.experiment import Experiment, ExperimentComparison -from floatcsep.logger import setup_logger, set_console_log_level -from floatcsep.postprocess import plot_results, plot_forecasts, plot_catalogs, plot_custom -from floatcsep.report import generate_report, reproducibility_report +from floatcsep.infrastructure.logger import setup_logger, set_console_log_level +from floatcsep.postprocess.plot_handler import ( + plot_results, + plot_forecasts, + plot_catalogs, + plot_custom, +) +from floatcsep.postprocess.reporting import generate_report, reproducibility_report setup_logger() log = logging.getLogger("floatLogger") diff --git a/floatcsep/evaluation.py b/floatcsep/evaluation.py index cd0a4b2..cbf5bd9 100644 --- a/floatcsep/evaluation.py +++ b/floatcsep/evaluation.py @@ -7,8 +7,8 @@ from matplotlib import pyplot from floatcsep.model import Model -from floatcsep.registry import ExperimentRegistry -from floatcsep.utils import parse_csep_func +from floatcsep.infrastructure.registries import ExperimentRegistry +from floatcsep.utils.helpers import parse_csep_func class Evaluation: diff --git a/floatcsep/experiment.py b/floatcsep/experiment.py index 3b3187f..a105810 100644 --- a/floatcsep/experiment.py +++ b/floatcsep/experiment.py @@ -13,20 +13,18 @@ from floatcsep.evaluation import Evaluation -from floatcsep.logger import add_fhandler +from floatcsep.infrastructure.logger import add_fhandler from floatcsep.model import Model, TimeDependentModel -from floatcsep.registry import ExperimentRegistry -from floatcsep.repository import ResultsRepository, CatalogRepository -from floatcsep.utils import ( +from floatcsep.infrastructure.registries import ExperimentRegistry +from floatcsep.infrastructure.repositories import ResultsRepository, CatalogRepository +from floatcsep.utils.helpers import ( NoAliasLoader, read_time_cfg, read_region_cfg, - Task, - TaskGraph, timewindow2str, parse_nested_dicts, ) - +from floatcsep.infrastructure.engine import Task, TaskGraph log = logging.getLogger("floatLogger") @@ -150,7 +148,7 @@ def __init__( self.model_config = models if isinstance(models, str) else None self.test_config = tests if isinstance(tests, str) else None - logger = kwargs.get("logging", True) + logger = kwargs.get("logging", False) if logger: filename = "experiment.log" if logger is True else logger self.registry.logger = os.path.join(workdir, rundir, filename) diff --git a/floatcsep/infrastructure/__init__.py b/floatcsep/infrastructure/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/floatcsep/infrastructure/engine.py b/floatcsep/infrastructure/engine.py new file mode 100644 index 0000000..e75ede9 --- /dev/null +++ b/floatcsep/infrastructure/engine.py @@ -0,0 +1,146 @@ +from collections import OrderedDict + + +class Task: + + def __init__(self, instance, method, **kwargs): + """ + Base node of the workload distribution. Wraps lazily objects, methods and their + arguments for them to be executed later. For instance, can wrap a floatcsep.Model, its + method 'create_forecast' and the argument 'time_window', which can be executed later + with Task.call() when, for example, task dependencies (parent nodes) have been completed. + + Args: + instance: can be floatcsep.Experiment, floatcsep.Model, floatcsep.Evaluation + method: the instance's method to be lazily created + **kwargs: keyword arguments passed to method. + """ + + self.obj = instance + self.method = method + self.kwargs = kwargs + + self.store = None # Bool for nested tasks. DEPRECATED + + def sign_match(self, obj=None, met=None, kw_arg=None): + """ + Checks if the Task matches a given signature for simplicity. + + Purpose is to check from the outside if the Task is from a given object + (Model, Experiment, etc.), matching either name or object or description + Args: + obj: Instance or instance's name str. Instance is preferred + met: Name of the method + kw_arg: Only the value (not key) of the kwargs dictionary + + Returns: + """ + + if self.obj == obj or obj == getattr(self.obj, "name", None): + if met == self.method: + if kw_arg in self.kwargs.values(): + return True + return False + + def __str__(self): + task_str = f"{self.__class__}\n\t" f"Instance: {self.obj.__class__.__name__}\n" + a = getattr(self.obj, "name", None) + if a: + task_str += f"\tName: {a}\n" + task_str += f"\tMethod: {self.method}\n" + for i, j in self.kwargs.items(): + task_str += f"\t\t{i}: {j} \n" + + return task_str[:-2] + + def run(self): + if hasattr(self.obj, "store"): + self.obj = self.obj.store + output = getattr(self.obj, self.method)(**self.kwargs) + + if output: + self.store = output + del self.obj + + return output + + def __call__(self, *args, **kwargs): + return self.run() + + def check_exist(self): + pass + + +class TaskGraph: + """ + Context manager of floatcsep workload distribution. + + Assign tasks to a node and defines their dependencies (parent nodes). + Contains a 'tasks' dictionary whose dict_keys are the Task to be + executed with dict_values as the Task's dependencies. + """ + + def __init__(self): + + self.tasks = OrderedDict() + self._ntasks = 0 + self.name = "floatcsep.utils.TaskGraph" + + @property + def ntasks(self): + return self._ntasks + + @ntasks.setter + def ntasks(self, n): + self._ntasks = n + + def add(self, task): + """ + Simply adds a defined task to the graph. + + Args: + task: floatcsep.utils.Task + + Returns: + """ + self.tasks[task] = [] + self.ntasks += 1 + + def add_dependency(self, task, dinst=None, dmeth=None, dkw=None): + """ + Adds a dependency to a task already inserted to the TaskGraph. + + Searchs + within the pre-added tasks a signature match by their name/instance, + method and keyword_args. + + Args: + task: Task to which a dependency will be asigned + dinst: object/name of the dependency + dmeth: method of the dependency + dkw: keyword argument of the dependency + + Returns: + """ + deps = [] + for i, other_tasks in enumerate(self.tasks.keys()): + if other_tasks.sign_match(dinst, dmeth, dkw): + deps.append(other_tasks) + + self.tasks[task].extend(deps) + + def run(self): + """ + Iterates through all the graph tasks and runs them. + + Returns: + """ + for task, deps in self.tasks.items(): + task.run() + + def __call__(self, *args, **kwargs): + + return self.run() + + def check_exist(self): + pass diff --git a/floatcsep/environments.py b/floatcsep/infrastructure/environments.py similarity index 99% rename from floatcsep/environments.py rename to floatcsep/infrastructure/environments.py index fc7b506..1f9cb46 100644 --- a/floatcsep/environments.py +++ b/floatcsep/infrastructure/environments.py @@ -497,6 +497,6 @@ def check_environment_type(): if __name__ == "__main__": env = EnvironmentFactory.get_env( - "conda", model_path="../examples/case_h/models/pymock_poisson" + "conda", model_path="../../examples/case_h/models/pymock_poisson" ) env.create_environment(force=True) diff --git a/floatcsep/logger.py b/floatcsep/infrastructure/logger.py similarity index 100% rename from floatcsep/logger.py rename to floatcsep/infrastructure/logger.py diff --git a/floatcsep/registry.py b/floatcsep/infrastructure/registries.py similarity index 99% rename from floatcsep/registry.py rename to floatcsep/infrastructure/registries.py index 42838b2..cfcc1d9 100644 --- a/floatcsep/registry.py +++ b/floatcsep/infrastructure/registries.py @@ -5,7 +5,7 @@ from os.path import join, abspath, relpath, normpath, dirname, exists from typing import Sequence, Union, TYPE_CHECKING, Any -from floatcsep.utils import timewindow2str +from floatcsep.utils.helpers import timewindow2str if TYPE_CHECKING: from floatcsep.model import Model diff --git a/floatcsep/repository.py b/floatcsep/infrastructure/repositories.py similarity index 98% rename from floatcsep/repository.py rename to floatcsep/infrastructure/repositories.py index ba7779a..fd34855 100644 --- a/floatcsep/repository.py +++ b/floatcsep/infrastructure/repositories.py @@ -12,10 +12,10 @@ from csep.models import EvaluationResult from csep.utils.time_utils import decimal_year -from floatcsep.readers import ForecastParsers -from floatcsep.registry import ForecastRegistry, ExperimentRegistry -from floatcsep.utils import str2timewindow, parse_csep_func -from floatcsep.utils import timewindow2str +from floatcsep.utils.readers import ForecastParsers +from floatcsep.infrastructure.registries import ForecastRegistry, ExperimentRegistry +from floatcsep.utils.helpers import str2timewindow, parse_csep_func +from floatcsep.utils.helpers import timewindow2str log = logging.getLogger("floatLogger") diff --git a/floatcsep/model.py b/floatcsep/model.py index 5a792c5..53a4f67 100644 --- a/floatcsep/model.py +++ b/floatcsep/model.py @@ -8,12 +8,12 @@ import git from csep.core.forecasts import GriddedForecast, CatalogForecast -from floatcsep.accessors import from_zenodo, from_git -from floatcsep.environments import EnvironmentFactory -from floatcsep.readers import ForecastParsers, HDF5Serializer -from floatcsep.registry import ForecastRegistry -from floatcsep.repository import ForecastRepository -from floatcsep.utils import timewindow2str, str2timewindow, parse_nested_dicts +from floatcsep.utils.accessors import from_zenodo, from_git +from floatcsep.infrastructure.environments import EnvironmentFactory +from floatcsep.utils.readers import ForecastParsers, HDF5Serializer +from floatcsep.infrastructure.registries import ForecastRegistry +from floatcsep.infrastructure.repositories import ForecastRepository +from floatcsep.utils.helpers import timewindow2str, str2timewindow, parse_nested_dicts log = logging.getLogger("floatLogger") diff --git a/floatcsep/postprocess/__init__.py b/floatcsep/postprocess/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/floatcsep/postprocess.py b/floatcsep/postprocess/plot_handler.py similarity index 99% rename from floatcsep/postprocess.py rename to floatcsep/postprocess/plot_handler.py index aeecb35..9e3e23b 100644 --- a/floatcsep/postprocess.py +++ b/floatcsep/postprocess/plot_handler.py @@ -7,7 +7,7 @@ from cartopy import crs as ccrs from matplotlib import pyplot -from floatcsep.utils import ( +from floatcsep.utils.helpers import ( timewindow2str, magnitude_vs_time, ) diff --git a/floatcsep/report.py b/floatcsep/postprocess/reporting.py similarity index 99% rename from floatcsep/report.py rename to floatcsep/postprocess/reporting.py index 85ef608..81ec646 100644 --- a/floatcsep/report.py +++ b/floatcsep/postprocess/reporting.py @@ -7,7 +7,7 @@ import numpy from floatcsep.experiment import ExperimentComparison -from floatcsep.utils import timewindow2str, str2timewindow +from floatcsep.utils.helpers import timewindow2str, str2timewindow if TYPE_CHECKING: from floatcsep.experiment import Experiment diff --git a/floatcsep/utils/__init__.py b/floatcsep/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/floatcsep/accessors.py b/floatcsep/utils/accessors.py similarity index 95% rename from floatcsep/accessors.py rename to floatcsep/utils/accessors.py index 0602790..0b50c53 100644 --- a/floatcsep/accessors.py +++ b/floatcsep/utils/accessors.py @@ -1,8 +1,3 @@ -from datetime import datetime -from urllib import request -from urllib.parse import urlencode -from csep.utils.time_utils import datetime_to_utc_epoch, utc_now_datetime -from csep.core.catalogs import CSEPCatalog import git import requests import hashlib diff --git a/floatcsep/utils.py b/floatcsep/utils/helpers.py similarity index 85% rename from floatcsep/utils.py rename to floatcsep/utils/helpers.py index a6af139..29c5409 100644 --- a/floatcsep/utils.py +++ b/floatcsep/utils/helpers.py @@ -4,7 +4,6 @@ import logging import os import re -from collections import OrderedDict from datetime import datetime, date from typing import Union, Mapping, Sequence @@ -34,8 +33,8 @@ from csep.models import EvaluationResult # floatCSEP libraries -import floatcsep.accessors -import floatcsep.readers +import floatcsep.utils.accessors +import floatcsep.utils.readers _UNITS = ["years", "months", "weeks", "days"] @@ -77,10 +76,10 @@ def _getattr(obj_, attr_): csep.utils, csep.utils.plots, csep.core.regions, - floatcsep.utils, - floatcsep.accessors, - floatcsep.readers.HDF5Serializer, - floatcsep.readers.ForecastParsers, + floatcsep.utils.helpers, + floatcsep.utils.accessors, + floatcsep.utils.readers.HDF5Serializer, + floatcsep.utils.readers.ForecastParsers, ] for module in _target_modules: try: @@ -439,151 +438,6 @@ def iter_attr(val): return iter_attr(nested_dict) -class Task: - - def __init__(self, instance, method, **kwargs): - """ - Base node of the workload distribution. Wraps lazily objects, methods and their - arguments for them to be executed later. For instance, can wrap a floatcsep.Model, its - method 'create_forecast' and the argument 'time_window', which can be executed later - with Task.call() when, for example, task dependencies (parent nodes) have been completed. - - Args: - instance: can be floatcsep.Experiment, floatcsep.Model, floatcsep.Evaluation - method: the instance's method to be lazily created - **kwargs: keyword arguments passed to method. - """ - - self.obj = instance - self.method = method - self.kwargs = kwargs - - self.store = None # Bool for nested tasks. DEPRECATED - - def sign_match(self, obj=None, met=None, kw_arg=None): - """ - Checks if the Task matches a given signature for simplicity. - - Purpose is to check from the outside if the Task is from a given object - (Model, Experiment, etc.), matching either name or object or description - Args: - obj: Instance or instance's name str. Instance is preferred - met: Name of the method - kw_arg: Only the value (not key) of the kwargs dictionary - - Returns: - """ - - if self.obj == obj or obj == getattr(self.obj, "name", None): - if met == self.method: - if kw_arg in self.kwargs.values(): - return True - return False - - def __str__(self): - task_str = f"{self.__class__}\n\t" f"Instance: {self.obj.__class__.__name__}\n" - a = getattr(self.obj, "name", None) - if a: - task_str += f"\tName: {a}\n" - task_str += f"\tMethod: {self.method}\n" - for i, j in self.kwargs.items(): - task_str += f"\t\t{i}: {j} \n" - - return task_str[:-2] - - def run(self): - if hasattr(self.obj, "store"): - self.obj = self.obj.store - output = getattr(self.obj, self.method)(**self.kwargs) - - if output: - self.store = output - del self.obj - - return output - - def __call__(self, *args, **kwargs): - return self.run() - - def check_exist(self): - pass - - -class TaskGraph: - """ - Context manager of floatcsep workload distribution. - - Assign tasks to a node and defines their dependencies (parent nodes). - Contains a 'tasks' dictionary whose dict_keys are the Task to be - executed with dict_values as the Task's dependencies. - """ - - def __init__(self): - - self.tasks = OrderedDict() - self._ntasks = 0 - self.name = "floatcsep.utils.TaskGraph" - - @property - def ntasks(self): - return self._ntasks - - @ntasks.setter - def ntasks(self, n): - self._ntasks = n - - def add(self, task): - """ - Simply adds a defined task to the graph. - - Args: - task: floatcsep.utils.Task - - Returns: - """ - self.tasks[task] = [] - self.ntasks += 1 - - def add_dependency(self, task, dinst=None, dmeth=None, dkw=None): - """ - Adds a dependency to a task already inserted to the TaskGraph. - - Searchs - within the pre-added tasks a signature match by their name/instance, - method and keyword_args. - - Args: - task: Task to which a dependency will be asigned - dinst: object/name of the dependency - dmeth: method of the dependency - dkw: keyword argument of the dependency - - Returns: - """ - deps = [] - for i, other_tasks in enumerate(self.tasks.keys()): - if other_tasks.sign_match(dinst, dmeth, dkw): - deps.append(other_tasks) - - self.tasks[task].extend(deps) - - def run(self): - """ - Iterates through all the graph tasks and runs them. - - Returns: - """ - for task, deps in self.tasks.items(): - task.run() - - def __call__(self, *args, **kwargs): - - return self.run() - - def check_exist(self): - pass - - class NoAliasLoader(yaml.Loader): @staticmethod def ignore_aliases(self): diff --git a/floatcsep/readers.py b/floatcsep/utils/readers.py similarity index 100% rename from floatcsep/readers.py rename to floatcsep/utils/readers.py diff --git a/setup.cfg b/setup.cfg index ee8bd06..bb655ac 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,10 @@ url = https://github.com/cseptesting/floatcsep.git [options] packages = floatcsep - floatcsep.cmd + floatcsep.commands + floatcsep.infrastructure + floatcsep.postprocess + floatcsep.utils install_requires = numpy dateparser @@ -82,7 +85,7 @@ dev = [options.entry_points] console_scripts = - floatcsep = floatcsep.cmd.main:floatcsep + floatcsep = floatcsep.commands.main:floatcsep [flake8] max-line-length = 96 \ No newline at end of file diff --git a/tests/integration/test_model_interface.py b/tests/integration/test_model_interface.py index e0681e4..318a708 100644 --- a/tests/integration/test_model_interface.py +++ b/tests/integration/test_model_interface.py @@ -10,9 +10,9 @@ import csep.core.regions from csep.core.forecasts import GriddedForecast -from floatcsep.environments import EnvironmentManager +from floatcsep.infrastructure.environments import EnvironmentManager from floatcsep.model import TimeIndependentModel, TimeDependentModel -from floatcsep.utils import timewindow2str +from floatcsep.utils.helpers import timewindow2str class TestModelRegistryIntegration(TestCase): @@ -58,8 +58,8 @@ def test_time_independent_model_get_forecast_real(self): self.assertIsInstance(forecast, GriddedForecast) self.assertAlmostEqual(forecast.data[0, 0], 0.002739726027357392) # 1 / 365 days - @patch("floatcsep.environments.VenvManager.create_environment") - @patch("floatcsep.environments.CondaManager.create_environment") + @patch("floatcsep.infrastructure.environments.VenvManager.create_environment") + @patch("floatcsep.infrastructure.environments.CondaManager.create_environment") def test_time_dependent_model_stage(self, mock_venv, mock_conda): mock_venv.return_value = None mock_conda.return_value = None @@ -73,8 +73,8 @@ def test_time_dependent_model_stage(self, mock_venv, mock_conda): self.assertIn(tstrings[0], self.time_dependent_model.registry.forecasts) self.assertIn(tstrings[1], self.time_dependent_model.registry.forecasts) - @patch("floatcsep.environments.VenvManager.create_environment") - @patch("floatcsep.environments.CondaManager.create_environment") + @patch("floatcsep.infrastructure.environments.VenvManager.create_environment") + @patch("floatcsep.infrastructure.environments.CondaManager.create_environment") def test_time_dependent_model_get_forecast(self, mock_venv, mock_conda): mock_venv.return_value = None mock_conda.return_value = None @@ -228,7 +228,7 @@ def init_model(name, model_path, **kwargs): return model @patch.object(EnvironmentManager, "create_environment") - @patch("floatcsep.registry.ForecastRegistry.build_tree") + @patch("floatcsep.infrastructure.registries.ForecastRegistry.build_tree") def test_from_git(self, mock_build_tree, mock_create_environment): """clones model from git, checks with test artifacts""" mock_build_tree.return_value = None @@ -285,7 +285,7 @@ def init_model(name, model_path, **kwargs): model = TimeIndependentModel(name=name, model_path=model_path, **kwargs) return model - @patch("floatcsep.registry.ForecastRegistry.build_tree") + @patch("floatcsep.infrastructure.registries.ForecastRegistry.build_tree") def test_zenodo(self, mock_buildtree): """downloads model from zenodo, checks with test artifacts""" mock_buildtree.return_value = None diff --git a/tests/qa/test_data.py b/tests/qa/test_data.py index 541f3e2..8441d87 100644 --- a/tests/qa/test_data.py +++ b/tests/qa/test_data.py @@ -1,5 +1,4 @@ -from floatcsep.cmd import main -from floatcsep.experiment import Experiment +from floatcsep.commands import main import unittest from unittest.mock import patch @@ -34,10 +33,10 @@ def get_eval_dist(self): pass -@patch("floatcsep.cmd.main.plot_forecasts") -@patch("floatcsep.cmd.main.plot_catalogs") -@patch("floatcsep.cmd.main.plot_custom") -@patch("floatcsep.cmd.main.generate_report") +@patch("floatcsep.commands.main.plot_forecasts") +@patch("floatcsep.commands.main.plot_catalogs") +@patch("floatcsep.commands.main.plot_custom") +@patch("floatcsep.commands.main.generate_report") class RunExamples(DataTest): def test_case_a(self, *args): @@ -76,10 +75,10 @@ def test_case_g(self, *args): self.assertEqual(1, 1) -@patch("floatcsep.cmd.main.plot_forecasts") -@patch("floatcsep.cmd.main.plot_catalogs") -@patch("floatcsep.cmd.main.plot_custom") -@patch("floatcsep.cmd.main.generate_report") +@patch("floatcsep.commands.main.plot_forecasts") +@patch("floatcsep.commands.main.plot_catalogs") +@patch("floatcsep.commands.main.plot_custom") +@patch("floatcsep.commands.main.generate_report") class ReproduceExamples(DataTest): def test_case_c(self, *args): diff --git a/tests/unit/test_accessors.py b/tests/unit/test_accessors.py index 3aad522..03ffcc8 100644 --- a/tests/unit/test_accessors.py +++ b/tests/unit/test_accessors.py @@ -1,5 +1,5 @@ import os.path -from floatcsep.accessors import from_zenodo, from_git, check_hash +from floatcsep.utils.accessors import from_zenodo, from_git, check_hash import unittest from unittest import mock @@ -35,7 +35,7 @@ def tearDownClass(cls) -> None: class TestGitter(unittest.TestCase): - @mock.patch("floatcsep.accessors.git.Repo") + @mock.patch("floatcsep.utils.accessors.git.Repo") @mock.patch("git.Git") def runTest(self, mock_git, mock_repo): p = mock.PropertyMock(return_value=False) diff --git a/tests/unit/test_environments.py b/tests/unit/test_environments.py index 29cd4ba..b69280e 100644 --- a/tests/unit/test_environments.py +++ b/tests/unit/test_environments.py @@ -6,7 +6,7 @@ import shutil import hashlib import logging -from floatcsep.environments import ( +from floatcsep.infrastructure.environments import ( CondaManager, EnvironmentFactory, VenvManager, diff --git a/tests/unit/test_experiment.py b/tests/unit/test_experiment.py index c7f8c62..cce5cdc 100644 --- a/tests/unit/test_experiment.py +++ b/tests/unit/test_experiment.py @@ -1,5 +1,7 @@ import os.path import tempfile +from unittest.mock import patch + import numpy from unittest import TestCase from datetime import datetime @@ -27,6 +29,13 @@ class TestExperiment(TestCase): def setUpClass(cls) -> None: pass + def setUp(self): + self.makedirs_patch = patch("os.makedirs", autospec=True) + self.mock_makedirs = self.makedirs_patch.start() + + def tearDown(self): + self.makedirs_patch.stop() + def assertEqualExperiment(self, exp_a, exp_b): self.assertEqual(exp_a.name, exp_b.name) self.assertEqual(exp_a.registry.workdir, os.getcwd()) diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py index 5991629..92d7398 100644 --- a/tests/unit/test_model.py +++ b/tests/unit/test_model.py @@ -2,8 +2,8 @@ from unittest import TestCase from floatcsep.model import TimeIndependentModel -from floatcsep.registry import ForecastRegistry -from floatcsep.repository import GriddedForecastRepository +from floatcsep.infrastructure.registries import ForecastRegistry +from floatcsep.infrastructure.repositories import GriddedForecastRepository from unittest.mock import patch, MagicMock, mock_open from floatcsep.model import TimeDependentModel from datetime import datetime @@ -59,7 +59,7 @@ def test_from_filesystem(self): @patch("os.makedirs") @patch("floatcsep.model.TimeIndependentModel.get_source") - @patch("floatcsep.registry.ForecastRegistry.build_tree") + @patch("floatcsep.infrastructure.registries.ForecastRegistry.build_tree") def test_stage_creates_directory(self, mock_build_tree, mock_get_source, mock_makedirs): """Test stage method creates directory.""" model = self.init_model("mock", "mockfile.csv") @@ -220,7 +220,8 @@ def test_init(self): self.assertEqual(self.model.repository, self.mock_repository_instance) self.assertEqual(self.model.environment, self.mock_environment_instance) - def test_stage(self): + @patch("os.makedirs") + def test_stage(self, mk): self.model.force_stage = True # Force staging to occur self.model.stage(timewindows=["2020-01-01_2020-12-31"]) diff --git a/tests/unit/test_readers.py b/tests/unit/test_readers.py index 1ed0648..a9e4bf8 100644 --- a/tests/unit/test_readers.py +++ b/tests/unit/test_readers.py @@ -6,7 +6,7 @@ import csep.utils.datasets import pytest -from floatcsep import readers +from floatcsep.utils import readers class TestForecastParsers(unittest.TestCase): @@ -18,7 +18,7 @@ def setUpClass(cls) -> None: @classmethod def tearDownClass(cls) -> None: - fname = os.path.join(cls._dir, 'model.hdf5') + fname = os.path.join(cls._dir, "model.hdf5") if os.path.isfile(fname): os.remove(fname) diff --git a/tests/unit/test_registry.py b/tests/unit/test_registry.py index 3d19431..369081f 100644 --- a/tests/unit/test_registry.py +++ b/tests/unit/test_registry.py @@ -1,7 +1,7 @@ import unittest from datetime import datetime from unittest.mock import patch, MagicMock -from floatcsep.registry import ForecastRegistry +from floatcsep.infrastructure.registries import ForecastRegistry class TestForecastRegistry(unittest.TestCase): @@ -58,7 +58,7 @@ def test_absdir(self): result = self.registry_file.abs_dir("model.txt") self.assertTrue(result.endswith("/test/workdir")) - @patch("floatcsep.registry.exists") + @patch("floatcsep.infrastructure.registries.exists") def test_fileexists(self, mock_exists): mock_exists.return_value = True self.registry_file.get = MagicMock(return_value="/test/path/file.txt") diff --git a/tests/unit/test_repositories.py b/tests/unit/test_repositories.py index 7128bda..1cab55a 100644 --- a/tests/unit/test_repositories.py +++ b/tests/unit/test_repositories.py @@ -4,9 +4,9 @@ from csep.core.forecasts import GriddedForecast -from floatcsep.readers import ForecastParsers -from floatcsep.registry import ForecastRegistry -from floatcsep.repository import ( +from floatcsep.utils.readers import ForecastParsers +from floatcsep.infrastructure.registries import ForecastRegistry +from floatcsep.infrastructure.repositories import ( CatalogForecastRepository, GriddedForecastRepository, ResultsRepository, @@ -138,7 +138,7 @@ def test_lazy_load_behavior(self, mock_parser): self.assertEqual(forecast, "forecatto") self.assertNotIn("2023-01-02_2023-01-03", repo.forecasts) - @patch("floatcsep.registry.ForecastRegistry") + @patch("floatcsep.infrastructure.registries.ForecastRegistry") def test_equal(self, MockForecastRegistry): self.registry = MockForecastRegistry() @@ -160,7 +160,7 @@ def test_equal(self, MockForecastRegistry): class TestResultsRepository(unittest.TestCase): - @patch("floatcsep.repository.ExperimentRegistry") + @patch("floatcsep.infrastructure.repositories.ExperimentRegistry") def setUp(self, MockRegistry): self.mock_registry = MockRegistry() self.results_repo = ResultsRepository(self.mock_registry) @@ -168,7 +168,7 @@ def setUp(self, MockRegistry): def test_initialization(self): self.assertEqual(self.results_repo.registry, self.mock_registry) - @patch("floatcsep.repository.EvaluationResult.from_dict") + @patch("floatcsep.infrastructure.repositories.EvaluationResult.from_dict") @patch("builtins.open", new_callable=unittest.mock.mock_open, read_data='{"key": "value"}') def test_load_result(self, mock_open, mock_from_dict): mock_from_dict.return_value = "mocked_result" @@ -191,7 +191,7 @@ def test_write_result(self, mock_open, mock_json_dump): class TestCatalogRepository(unittest.TestCase): - @patch("floatcsep.repository.ExperimentRegistry") + @patch("floatcsep.infrastructure.repositories.ExperimentRegistry") def setUp(self, MockRegistry): self.mock_registry = MockRegistry() self.catalog_repo = CatalogRepository(self.mock_registry) @@ -199,7 +199,7 @@ def setUp(self, MockRegistry): def test_initialization(self): self.assertEqual(self.catalog_repo.registry, self.mock_registry) - @patch("floatcsep.repository.isfile", return_value=True) + @patch("floatcsep.infrastructure.repositories.isfile", return_value=True) def test_set_catalog(self, mock_isfile): # Mock the registry's rel method to return the same path for simplicity self.mock_registry.rel.return_value = "catalog_path" diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index d7398a2..92fb686 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -8,8 +8,8 @@ import csep.core.regions import floatcsep -import floatcsep.accessors -from floatcsep.utils import ( +import floatcsep.utils.accessors +from floatcsep.utils.helpers import ( parse_timedelta_string, timewindows_ti, read_time_cfg, @@ -37,7 +37,8 @@ def test_parse_csep_func(self): parse_csep_func("italy_csep_region"), csep.core.regions.italy_csep_region.__class__ ) self.assertIsInstance( - parse_csep_func("from_zenodo"), floatcsep.accessors.from_zenodo.__class__ + parse_csep_func("from_zenodo"), + floatcsep.utils.accessors.from_zenodo.__class__, ) self.assertRaises(AttributeError, parse_csep_func, "panic_button")