ClimateGraph 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. ClimateGraph/__init__.py +9 -0
  2. ClimateGraph/appkernel.py +148 -0
  3. ClimateGraph/cli.py +63 -0
  4. ClimateGraph/data/__init__.py +8 -0
  5. ClimateGraph/data/data.py +508 -0
  6. ClimateGraph/data/point_surface.py +91 -0
  7. ClimateGraph/data/regular_grid.py +25 -0
  8. ClimateGraph/data/satellite_swath.py +21 -0
  9. ClimateGraph/domain/__init__.py +11 -0
  10. ClimateGraph/domain/domain.py +181 -0
  11. ClimateGraph/domain/domains.py +279 -0
  12. ClimateGraph/plot/__init__.py +11 -0
  13. ClimateGraph/plot/plot.py +402 -0
  14. ClimateGraph/plot/plots.py +1054 -0
  15. ClimateGraph/plot/primitive.py +99 -0
  16. ClimateGraph/plot/primitives.py +160 -0
  17. ClimateGraph/reader/__init__.py +15 -0
  18. ClimateGraph/reader/reader/__init__.py +14 -0
  19. ClimateGraph/reader/reader/point_surface/__init__.py +7 -0
  20. ClimateGraph/reader/reader/point_surface/csv_base.py +135 -0
  21. ClimateGraph/reader/reader/point_surface/default.py +36 -0
  22. ClimateGraph/reader/reader/point_surface/dmc.py +10 -0
  23. ClimateGraph/reader/reader/point_surface/sinca.py +10 -0
  24. ClimateGraph/reader/reader/point_surface/single_file.py +60 -0
  25. ClimateGraph/reader/reader/point_surface/station_per_file.py +55 -0
  26. ClimateGraph/reader/reader/point_surface/variable_per_file.py +91 -0
  27. ClimateGraph/reader/reader/reader.py +353 -0
  28. ClimateGraph/reader/reader/regular_grid/__init__.py +7 -0
  29. ClimateGraph/reader/reader/regular_grid/chimere.py +24 -0
  30. ClimateGraph/reader/reader/regular_grid/default.py +51 -0
  31. ClimateGraph/reader/reader/regular_grid/wrf.py +53 -0
  32. ClimateGraph/utils/__init__.py +1 -0
  33. ClimateGraph/utils/control_model.py +292 -0
  34. ClimateGraph/utils/dataset_utils.py +287 -0
  35. ClimateGraph/utils/general_utils.py +259 -0
  36. ClimateGraph/utils/parser.py +269 -0
  37. ClimateGraph/utils/registry.py +81 -0
  38. ClimateGraph/utils/resample_engine.py +156 -0
  39. climategraph-0.4.0.dist-info/METADATA +177 -0
  40. climategraph-0.4.0.dist-info/RECORD +44 -0
  41. climategraph-0.4.0.dist-info/WHEEL +5 -0
  42. climategraph-0.4.0.dist-info/entry_points.txt +2 -0
  43. climategraph-0.4.0.dist-info/licenses/LICENSE +21 -0
  44. climategraph-0.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,9 @@
1
+ """ClimateGraph: configuration-driven reading, transformation and plotting of
2
+ environmental data."""
3
+
4
+ from .appkernel import AppKernel
5
+ from .data import Data
6
+ from .plot import Plot
7
+ from .reader import Reader
8
+
9
+ __all__ = ["AppKernel", "Data", "Plot", "Reader"]
@@ -0,0 +1,148 @@
1
+ """Execution and state management.
2
+
3
+ Connects the modules of the application to each other: holds the registries of
4
+ the objects the parser built, promotes the control file's ``analysis`` block to
5
+ run-scoped settings, and dispatches the jobs those objects describe.
6
+
7
+ The main loop lives in :meth:`AppKernel.run`. Plotting is the only job type
8
+ dispatched today; analysis jobs are the intended next one.
9
+ """
10
+
11
+ import logging
12
+ from contextlib import contextmanager
13
+ from pathlib import Path
14
+
15
+ from ClimateGraph.utils.parser import Parser
16
+
17
+ log = logging.getLogger(__name__)
18
+
19
+
20
+ class AppKernel:
21
+ """ClimateGraph execution and state manager. Orquestrates the other modules."""
22
+
23
+ def __init__(self):
24
+ """Create an empty kernel. Registries and settings are filled by :meth:`run`."""
25
+ self.analysis = None
26
+ self.data = None
27
+ self.domains = None
28
+ self.plots = None
29
+
30
+ self.debug = None
31
+ self.output_path = None
32
+ self.workers = None
33
+
34
+ def read_control(self, control_path: Path):
35
+ """Read and validate a control file through the :class:`~ClimateGraph.utils.parser.Parser`.
36
+
37
+ Parameters
38
+ ----------
39
+ control_path : Path
40
+ Pathlib path to a control file.
41
+
42
+ Returns
43
+ -------
44
+ BaseModel, Dict[str, Data], Dict[str, Plot], Dict[str, Domain]
45
+ All necessary data for running a ClimateGraph execution
46
+ """
47
+ analysis, data, plots, domains = Parser.parse_control(control_path)
48
+ return analysis, data, plots, domains
49
+
50
+ def load_data(self):
51
+ """Eagerly load every registered dataset.
52
+
53
+ Not called by :meth:`run`, which relies on :class:`~ClimateGraph.data.Data`
54
+ loading lazily on first access instead.
55
+ """
56
+ for name, data_obj in self.data.items():
57
+ log.info(f"Loading '{name}' dataset.")
58
+ data_obj.load_obj()
59
+
60
+ def plot(self):
61
+ """Dispatch one plotting job per registered plot."""
62
+ for name, plot_obj in self.plots.items():
63
+ log.info(f"Plotting '{name}'.")
64
+ plot_obj.plot()
65
+
66
+ def set_analysis_data(self, analysis: dict | None = None):
67
+ """Promote the control file's ``analysis`` block to kernel settings.
68
+
69
+ Parameters
70
+ ----------
71
+ analysis : dict, optional
72
+ Mapping holding ``output_path``, ``debug`` and ``workers``. Defaults
73
+ to the block read from the control file.
74
+ """
75
+ if analysis is None:
76
+ analysis = self.analysis
77
+
78
+ self.debug = analysis.get("debug", False)
79
+ self.output_path = analysis.get("output_path", Path("./"))
80
+ self.workers = analysis.get("workers")
81
+
82
+ def _configure_logging(self):
83
+ """Scope ``--debug`` to ClimateGraph's own logger.
84
+
85
+ The only place in the codebase that calls ``logging.basicConfig``.
86
+ ``force=True`` so this always wins regardless of import order, even if
87
+ some other library configured logging before this runs.
88
+
89
+ The root logger stays at INFO so third-party libraries (matplotlib, PIL,
90
+ …) never spray DEBUG. ``--debug`` only raises the ``ClimateGraph`` package
91
+ logger to DEBUG; its records still reach the root handler (NOTSET, emits
92
+ everything), so ClimateGraph's own operation traceback shows while the
93
+ noisy libraries stay quiet.
94
+ """
95
+ logging.basicConfig(level=logging.INFO, force=True)
96
+ logging.getLogger("ClimateGraph").setLevel(
97
+ logging.DEBUG if self.debug else logging.INFO
98
+ )
99
+
100
+ def run(self, control_path: Path, debug_override: bool = False):
101
+ """Run the ClimateGraph routine.
102
+
103
+ Reads the control file, sets up the run-scoped settings, dispatches the
104
+ plotting jobs, then clears the registries.
105
+
106
+ Parameters
107
+ ----------
108
+ control_path : Path
109
+ Path of the configuration file for the ClimateGraph run.
110
+ debug_override : bool, optional
111
+ CLI ``--debug`` flag. ORed with the control file's ``debug`` value:
112
+ either source enables DEBUG, neither disables it. Default False.
113
+ """
114
+ self.analysis, self.data, self.plots, self.domains = self.read_control(
115
+ control_path
116
+ )
117
+
118
+ self.set_analysis_data()
119
+ self.debug = debug_override or self.debug
120
+ self._configure_logging()
121
+ # if self.eager : self.load_data()
122
+ with self._dask_client():
123
+ self.plot()
124
+ # self.stats() TODO: add a module for stats
125
+
126
+ self.data, self.plots, self.domains = None, None, None
127
+
128
+ @contextmanager
129
+ def _dask_client(self):
130
+ """Yield a dask client, or ``None`` when no cluster was requested.
131
+
132
+ Yields
133
+ ------
134
+ dask.distributed.Client or None
135
+ """
136
+ if not self.workers:
137
+ yield None
138
+ return
139
+ from dask.distributed import Client, LocalCluster
140
+
141
+ cluster = LocalCluster(n_workers=1, threads_per_worker=self.workers)
142
+ client = Client(cluster)
143
+ log.info(f"Dask Client started: 1 worker x {self.workers} threads.")
144
+ try:
145
+ yield client
146
+ finally:
147
+ client.close()
148
+ cluster.close()
ClimateGraph/cli.py ADDED
@@ -0,0 +1,63 @@
1
+ """Command line interface.
2
+ Exposes the main ClimateGraph reading->processing->plotting routine through the command line, and a version check useful for installation check.
3
+ Now the only existing option is creating a configuration file and passing it through the Run command.
4
+ """
5
+
6
+ from importlib.metadata import version as _package_version
7
+ from pathlib import Path
8
+ from typing import Annotated
9
+
10
+ import typer
11
+
12
+ from ClimateGraph.appkernel import AppKernel
13
+
14
+ app = typer.Typer(
15
+ name="ClimateGraph",
16
+ help="ClimateGraph Command Line Interface",
17
+ pretty_exceptions_enable=False,
18
+ )
19
+
20
+
21
+ @app.command()
22
+ def run(
23
+ control_file: Annotated[
24
+ Path,
25
+ typer.Argument(
26
+ exists=True,
27
+ dir_okay=False,
28
+ file_okay=True,
29
+ resolve_path=True,
30
+ help="File configuring the analysis run.",
31
+ ),
32
+ ],
33
+ debug: Annotated[
34
+ bool,
35
+ typer.Option(
36
+ "--debug",
37
+ help="Force DEBUG-level logging. Override with the control file's debug setting.",
38
+ ),
39
+ ] = False,
40
+ ):
41
+ """Run the ClimateGraph routine with a control file.
42
+
43
+ Parameters
44
+ ----------
45
+ control_file : Path
46
+ Path to the control file (``.yaml``, ``.yml`` or ``.json``) describing
47
+ the analysis. Typer checks that it exists and is a file, and resolves it
48
+ to an absolute path, before this function runs.
49
+ debug : bool, optional
50
+ Force DEBUG-level logging on ClimateGraph's own logger. If either the config states debug mode or this flag is on, debug mode is on.
51
+ """
52
+ appK = AppKernel()
53
+ appK.run(control_file, debug_override=debug)
54
+
55
+
56
+ @app.command()
57
+ def version():
58
+ """Print the version of ClimateGraph, as recorded in the package metadata."""
59
+ print(f"ClimateGraph version {_package_version('ClimateGraph')}")
60
+
61
+
62
+ if __name__ == "__main__":
63
+ app()
@@ -0,0 +1,8 @@
1
+ """Dataset abstraction and the topologies it supports."""
2
+
3
+ from .data import Data
4
+ from .point_surface import PointSurface
5
+ from .regular_grid import RegularGrid
6
+ from .satellite_swath import SatelliteSwath
7
+
8
+ __all__ = ["Data", "PointSurface", "RegularGrid", "SatelliteSwath"]