ClimateGraph 0.4.0__tar.gz
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.
- climategraph-0.4.0/ClimateGraph/__init__.py +9 -0
- climategraph-0.4.0/ClimateGraph/appkernel.py +148 -0
- climategraph-0.4.0/ClimateGraph/cli.py +63 -0
- climategraph-0.4.0/ClimateGraph/data/__init__.py +8 -0
- climategraph-0.4.0/ClimateGraph/data/data.py +508 -0
- climategraph-0.4.0/ClimateGraph/data/point_surface.py +91 -0
- climategraph-0.4.0/ClimateGraph/data/regular_grid.py +25 -0
- climategraph-0.4.0/ClimateGraph/data/satellite_swath.py +21 -0
- climategraph-0.4.0/ClimateGraph/domain/__init__.py +11 -0
- climategraph-0.4.0/ClimateGraph/domain/domain.py +181 -0
- climategraph-0.4.0/ClimateGraph/domain/domains.py +279 -0
- climategraph-0.4.0/ClimateGraph/plot/__init__.py +11 -0
- climategraph-0.4.0/ClimateGraph/plot/plot.py +402 -0
- climategraph-0.4.0/ClimateGraph/plot/plots.py +1054 -0
- climategraph-0.4.0/ClimateGraph/plot/primitive.py +99 -0
- climategraph-0.4.0/ClimateGraph/plot/primitives.py +160 -0
- climategraph-0.4.0/ClimateGraph/reader/__init__.py +15 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/__init__.py +14 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/point_surface/__init__.py +7 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/point_surface/csv_base.py +135 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/point_surface/default.py +36 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/point_surface/dmc.py +10 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/point_surface/sinca.py +10 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/point_surface/single_file.py +60 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/point_surface/station_per_file.py +55 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/point_surface/variable_per_file.py +91 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/reader.py +353 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/regular_grid/__init__.py +7 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/regular_grid/chimere.py +24 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/regular_grid/default.py +51 -0
- climategraph-0.4.0/ClimateGraph/reader/reader/regular_grid/wrf.py +53 -0
- climategraph-0.4.0/ClimateGraph/utils/__init__.py +1 -0
- climategraph-0.4.0/ClimateGraph/utils/control_model.py +292 -0
- climategraph-0.4.0/ClimateGraph/utils/dataset_utils.py +287 -0
- climategraph-0.4.0/ClimateGraph/utils/general_utils.py +259 -0
- climategraph-0.4.0/ClimateGraph/utils/parser.py +269 -0
- climategraph-0.4.0/ClimateGraph/utils/registry.py +81 -0
- climategraph-0.4.0/ClimateGraph/utils/resample_engine.py +156 -0
- climategraph-0.4.0/ClimateGraph.egg-info/PKG-INFO +177 -0
- climategraph-0.4.0/ClimateGraph.egg-info/SOURCES.txt +52 -0
- climategraph-0.4.0/ClimateGraph.egg-info/dependency_links.txt +1 -0
- climategraph-0.4.0/ClimateGraph.egg-info/entry_points.txt +2 -0
- climategraph-0.4.0/ClimateGraph.egg-info/requires.txt +43 -0
- climategraph-0.4.0/ClimateGraph.egg-info/top_level.txt +1 -0
- climategraph-0.4.0/LICENSE +21 -0
- climategraph-0.4.0/PKG-INFO +177 -0
- climategraph-0.4.0/README.md +111 -0
- climategraph-0.4.0/pyproject.toml +156 -0
- climategraph-0.4.0/setup.cfg +4 -0
- climategraph-0.4.0/tests/test_against_manual_filter.py +78 -0
- climategraph-0.4.0/tests/test_appkernel.py +104 -0
- climategraph-0.4.0/tests/test_cli.py +81 -0
- climategraph-0.4.0/tests/test_parser.py +240 -0
- climategraph-0.4.0/tests/test_parser_2.py +46 -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()
|
|
@@ -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"]
|