nctoolkit 1.3.6__tar.gz → 1.3.7__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.
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/PKG-INFO +1 -1
- nctoolkit-1.3.7/nctoolkit/__init__.py +124 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/cleanup.py +32 -28
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/nco_command.py +32 -24
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/runners.py +40 -53
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/runthis.py +14 -4
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/session.py +43 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/setup.py +1 -1
- nctoolkit-1.3.6/nctoolkit/__init__.py +0 -50
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/LICENSE +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/MANIFEST.in +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/README.md +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/add_etc.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/anomaly.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/api.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/append.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/assign.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/cdo_command.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/cellareas.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/centres.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/checks.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/clear.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/compare.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/compare_data.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/corr.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/create_ensemble.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/crop.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/deprecated.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/distgrid.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/drop.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/ensembles.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/esoteric.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/experimental.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/fill.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/flatten.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/fldstat.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/format.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/generate_grid.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/inttime.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/invert.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/masking.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/matchpoint.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/mergers.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/meridonials.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/mp_adders.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/mp_matchers.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/mp_matchups.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/phenology.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/plot.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/reduce.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/reduce_grid.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/regrid.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/remove.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/rename.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/resample.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/rollstat.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/run.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/set.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/setters.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/shape.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/shift.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/show.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/split.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/static_plot.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/strip_vars.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/subset.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/sumall.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/temp_file.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/temporal_stat.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/temporals.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/thresholds.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/to_lonlat.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/to_nc.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/toxarray.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/tozlev.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/transect.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/unify.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/utils.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/validator.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/validator_funs.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/verticals.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/zip.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit/zonals.py +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/nctoolkit.egg-info/SOURCES.txt +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/pyproject.toml +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/requirements.txt +0 -0
- {nctoolkit-1.3.6 → nctoolkit-1.3.7}/setup.cfg +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from nctoolkit.api import (
|
|
2
|
+
open_data,
|
|
3
|
+
open_geotiff,
|
|
4
|
+
from_xarray,
|
|
5
|
+
merge,
|
|
6
|
+
cor_time,
|
|
7
|
+
cor_space,
|
|
8
|
+
options,
|
|
9
|
+
DataSet,
|
|
10
|
+
open_thredds,
|
|
11
|
+
open_url,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from nctoolkit.unify import unify
|
|
16
|
+
from nctoolkit.shape import open_shape
|
|
17
|
+
from nctoolkit.static_plot import panel_plot
|
|
18
|
+
|
|
19
|
+
from nctoolkit.validator import validator
|
|
20
|
+
from nctoolkit.matchpoint import open_matchpoint
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
from nctoolkit.cleanup import cleanup, clean_all, deep_clean, temp_check
|
|
24
|
+
from nctoolkit.session import kill_active_cdo
|
|
25
|
+
|
|
26
|
+
import atexit
|
|
27
|
+
import os
|
|
28
|
+
import signal
|
|
29
|
+
|
|
30
|
+
# Forked children (multiprocessing workers, os.fork) inherit these handlers but
|
|
31
|
+
# share the session stamp; only the process that imported nctoolkit may clean
|
|
32
|
+
# up, otherwise a worker exiting would delete the parent's live temp files.
|
|
33
|
+
_owner_pid = os.getpid()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _clean_all_if_owner():
|
|
37
|
+
if os.getpid() == _owner_pid:
|
|
38
|
+
clean_all()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _stop_workers():
|
|
42
|
+
"""
|
|
43
|
+
Terminate multiprocessing children (e.g. ensemble workers) so the cdo
|
|
44
|
+
processes they are running die with them, before temp files are removed
|
|
45
|
+
"""
|
|
46
|
+
import multiprocessing
|
|
47
|
+
|
|
48
|
+
children = multiprocessing.active_children()
|
|
49
|
+
for child in children:
|
|
50
|
+
try:
|
|
51
|
+
child.terminate()
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
for child in children:
|
|
55
|
+
try:
|
|
56
|
+
child.join(2)
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
atexit.register(_clean_all_if_owner)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _install_termination_handlers():
|
|
65
|
+
"""
|
|
66
|
+
Remove temp files when the process is terminated by SIGTERM or SIGHUP
|
|
67
|
+
|
|
68
|
+
Python only runs atexit handlers on a normal exit, so a plain `kill` would
|
|
69
|
+
otherwise leave temp files behind. The handler cleans up and then
|
|
70
|
+
re-delivers the signal with its previous disposition, so the process still
|
|
71
|
+
terminates as the sender intended. Handlers someone else has already
|
|
72
|
+
installed are chained to rather than replaced.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def make_handler(previous):
|
|
76
|
+
def handler(signum, frame):
|
|
77
|
+
kill_active_cdo()
|
|
78
|
+
if os.getpid() == _owner_pid:
|
|
79
|
+
_stop_workers()
|
|
80
|
+
_clean_all_if_owner()
|
|
81
|
+
if callable(previous):
|
|
82
|
+
previous(signum, frame)
|
|
83
|
+
return
|
|
84
|
+
signal.signal(signum, signal.SIG_DFL)
|
|
85
|
+
os.kill(os.getpid(), signum)
|
|
86
|
+
|
|
87
|
+
return handler
|
|
88
|
+
|
|
89
|
+
for name in ("SIGTERM", "SIGHUP"):
|
|
90
|
+
signum = getattr(signal, name, None)
|
|
91
|
+
if signum is None:
|
|
92
|
+
continue
|
|
93
|
+
try:
|
|
94
|
+
previous = signal.getsignal(signum)
|
|
95
|
+
if previous is None or previous == signal.SIG_IGN:
|
|
96
|
+
# not set from Python, or deliberately ignored (e.g. nohup)
|
|
97
|
+
continue
|
|
98
|
+
signal.signal(signum, make_handler(previous))
|
|
99
|
+
except ValueError:
|
|
100
|
+
# signal handlers can only be set from the main thread
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
_install_termination_handlers()
|
|
105
|
+
|
|
106
|
+
from nctoolkit.create_ensemble import create_ensemble, glob
|
|
107
|
+
from nctoolkit.session import session_files
|
|
108
|
+
from nctoolkit.show import nc_variables, nc_years, nc_months, nc_times
|
|
109
|
+
|
|
110
|
+
from nctoolkit.utils import validate_version, cdo_version
|
|
111
|
+
from nctoolkit.session import session_info
|
|
112
|
+
from nctoolkit.mp_adders import match_points
|
|
113
|
+
|
|
114
|
+
session_info["cdo"] = cdo_version()
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
from importlib.metadata import version as _version
|
|
118
|
+
except ImportError:
|
|
119
|
+
from importlib_metadata import version as _version
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
__version__ = _version("nctoolkit")
|
|
123
|
+
except Exception:
|
|
124
|
+
__version__ = "999"
|
|
@@ -12,6 +12,7 @@ from nctoolkit.session import (
|
|
|
12
12
|
append_safe,
|
|
13
13
|
remove_safe,
|
|
14
14
|
get_safe,
|
|
15
|
+
kill_active_cdo,
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
|
|
@@ -78,40 +79,43 @@ def cleanup():
|
|
|
78
79
|
session_info["temp_dir"] = "/tmp/"
|
|
79
80
|
|
|
80
81
|
|
|
81
|
-
def clean_all(
|
|
82
|
+
def clean_all():
|
|
82
83
|
"""
|
|
83
84
|
Remove all temporary files created by nctoolkit in the present session
|
|
84
|
-
"""
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if y is not None:
|
|
94
|
-
return None
|
|
95
|
-
|
|
96
|
-
candidates = []
|
|
97
|
-
for directory in get_tempdirs():
|
|
98
|
-
mylist = [f for f in glob.glob(f"{directory}/*")]
|
|
99
|
-
mylist = [f for f in mylist if session_info["stamp"] in f]
|
|
100
|
-
for ff in mylist:
|
|
101
|
-
candidates.append(ff)
|
|
86
|
+
This runs at interpreter exit and from the SIGTERM/SIGHUP handler, so it
|
|
87
|
+
must not depend on the multiprocessing Manager servers behind the
|
|
88
|
+
parallel-mode session lists: if the whole process group is being killed
|
|
89
|
+
those servers can already be gone. Candidates are therefore found by
|
|
90
|
+
globbing for the session stamp in the usual temp locations as well as in
|
|
91
|
+
any directory nctoolkit has recorded.
|
|
92
|
+
"""
|
|
102
93
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
candidates
|
|
106
|
-
delete_these = set(candidates)
|
|
107
|
-
delete_these = list(delete_these)
|
|
94
|
+
# stop any cdo still writing to a temp file before deleting them
|
|
95
|
+
kill_active_cdo()
|
|
108
96
|
|
|
109
|
-
|
|
110
|
-
# files to be deleted are in a temporary folder
|
|
97
|
+
stamp = session_info["stamp"]
|
|
111
98
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
99
|
+
directories = {"/tmp", "/var/tmp", "/usr/tmp"}
|
|
100
|
+
directories.add(str(session_info["temp_dir"]).rstrip("/") or "/")
|
|
101
|
+
try:
|
|
102
|
+
directories.update(d.rstrip("/") for d in get_tempdirs())
|
|
103
|
+
except Exception:
|
|
104
|
+
# dead Manager server (e.g. the process group was killed)
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
candidates = set()
|
|
108
|
+
for directory in directories:
|
|
109
|
+
candidates.update(glob.glob(f"{directory}/*{stamp}*"))
|
|
110
|
+
|
|
111
|
+
for dd in candidates:
|
|
112
|
+
# fail-safe: only ever delete files nctoolkit named for this session
|
|
113
|
+
if "nctoolkit" not in dd or stamp not in os.path.basename(dd):
|
|
114
|
+
continue
|
|
115
|
+
try:
|
|
116
|
+
os.remove(dd)
|
|
117
|
+
except OSError:
|
|
118
|
+
pass
|
|
115
119
|
|
|
116
120
|
|
|
117
121
|
def deep_clean():
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import copy
|
|
2
|
-
import multiprocessing
|
|
3
2
|
import warnings
|
|
4
3
|
|
|
5
4
|
from nctoolkit.cleanup import cleanup
|
|
6
5
|
from nctoolkit.flatten import str_flatten
|
|
7
6
|
from nctoolkit.runthis import run_nco
|
|
8
7
|
from nctoolkit.temp_file import temp_file
|
|
9
|
-
from nctoolkit.session import
|
|
8
|
+
from nctoolkit.session import (
|
|
9
|
+
remove_safe,
|
|
10
|
+
session_info,
|
|
11
|
+
append_safe,
|
|
12
|
+
get_safe,
|
|
13
|
+
fork_pool,
|
|
14
|
+
)
|
|
10
15
|
|
|
11
16
|
|
|
12
17
|
def nco_command(self, command=None, ensemble=False):
|
|
@@ -57,26 +62,34 @@ def nco_command(self, command=None, ensemble=False):
|
|
|
57
62
|
new_files = []
|
|
58
63
|
new_commands = []
|
|
59
64
|
|
|
60
|
-
|
|
61
|
-
pool = multiprocessing.get_context("fork").Pool(cores)
|
|
62
|
-
target_list = []
|
|
63
|
-
results = dict()
|
|
64
|
-
else:
|
|
65
|
-
target_list = []
|
|
65
|
+
target_list = []
|
|
66
66
|
|
|
67
67
|
if (ensemble is False) or (len(self) == 1):
|
|
68
68
|
if cores > 1:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
69
|
+
targets = []
|
|
70
|
+
try:
|
|
71
|
+
with fork_pool(cores) as pool:
|
|
72
|
+
results = []
|
|
73
|
+
for ff in self:
|
|
74
|
+
target = temp_file(".nc")
|
|
75
|
+
|
|
76
|
+
append_safe(target)
|
|
77
|
+
targets.append(target)
|
|
78
|
+
|
|
79
|
+
the_command = f"{command} {ff} {target}"
|
|
80
|
+
the_command = the_command.replace(" ", " ")
|
|
81
|
+
|
|
82
|
+
results.append(
|
|
83
|
+
pool.apply_async(run_nco, [the_command, target])
|
|
84
|
+
)
|
|
85
|
+
new_commands.append(the_command)
|
|
86
|
+
|
|
87
|
+
for v in results:
|
|
88
|
+
target_list.append(v.get())
|
|
89
|
+
except BaseException:
|
|
90
|
+
for target in targets:
|
|
91
|
+
remove_safe(target)
|
|
92
|
+
raise
|
|
80
93
|
|
|
81
94
|
else:
|
|
82
95
|
for ff in self:
|
|
@@ -123,11 +136,6 @@ def nco_command(self, command=None, ensemble=False):
|
|
|
123
136
|
self._hold_history = copy.deepcopy(self.history)
|
|
124
137
|
|
|
125
138
|
if cores > 1 and ensemble is False:
|
|
126
|
-
pool.close()
|
|
127
|
-
pool.close()
|
|
128
|
-
for k, v in results.items():
|
|
129
|
-
target_list.append(v.get())
|
|
130
|
-
|
|
131
139
|
self.current = copy.deepcopy(target_list)
|
|
132
140
|
for cc in new_commands:
|
|
133
141
|
self.history.append(cc)
|
|
@@ -15,7 +15,8 @@ from nctoolkit.session import (
|
|
|
15
15
|
append_safe,
|
|
16
16
|
remove_safe,
|
|
17
17
|
get_protected,
|
|
18
|
-
session_warnings
|
|
18
|
+
session_warnings,
|
|
19
|
+
active_cdo,
|
|
19
20
|
)
|
|
20
21
|
from nctoolkit.temp_file import temp_file
|
|
21
22
|
|
|
@@ -120,14 +121,7 @@ def run_nco(command, target, out_file=None, overwrite=False):
|
|
|
120
121
|
target = new_target
|
|
121
122
|
append_safe(target)
|
|
122
123
|
|
|
123
|
-
out =
|
|
124
|
-
command,
|
|
125
|
-
shell=True,
|
|
126
|
-
stdin=subprocess.PIPE,
|
|
127
|
-
stdout=subprocess.PIPE,
|
|
128
|
-
stderr=subprocess.STDOUT,
|
|
129
|
-
)
|
|
130
|
-
result, ignore = out.communicate()
|
|
124
|
+
out, result = run_shell(command)
|
|
131
125
|
|
|
132
126
|
if "(Abort)" in str(result):
|
|
133
127
|
raise ValueError(
|
|
@@ -142,14 +136,7 @@ def run_nco(command, target, out_file=None, overwrite=False):
|
|
|
142
136
|
remove_safe(target)
|
|
143
137
|
target = new_target
|
|
144
138
|
|
|
145
|
-
out =
|
|
146
|
-
command,
|
|
147
|
-
shell=True,
|
|
148
|
-
stdin=subprocess.PIPE,
|
|
149
|
-
stdout=subprocess.PIPE,
|
|
150
|
-
stderr=subprocess.STDOUT,
|
|
151
|
-
)
|
|
152
|
-
result1, ignore = out.communicate()
|
|
139
|
+
out, result1 = run_shell(command)
|
|
153
140
|
if "ERROR" in str(result1):
|
|
154
141
|
remove_safe(target)
|
|
155
142
|
raise ValueError(
|
|
@@ -178,6 +165,38 @@ def run_nco(command, target, out_file=None, overwrite=False):
|
|
|
178
165
|
return target
|
|
179
166
|
|
|
180
167
|
|
|
168
|
+
def run_shell(command):
|
|
169
|
+
"""
|
|
170
|
+
Run a cdo command in its own process group and return (process, output)
|
|
171
|
+
|
|
172
|
+
The process group is registered in session.active_cdo while it runs. If
|
|
173
|
+
anything interrupts the wait (KeyboardInterrupt, an exception, or the
|
|
174
|
+
SIGTERM handler via clean_all) the whole group is killed, so cdo can't
|
|
175
|
+
keep running - and keep writing a temp file - after nctoolkit has stopped.
|
|
176
|
+
"""
|
|
177
|
+
out = subprocess.Popen(
|
|
178
|
+
command,
|
|
179
|
+
shell=True,
|
|
180
|
+
stdin=subprocess.PIPE,
|
|
181
|
+
stdout=subprocess.PIPE,
|
|
182
|
+
stderr=subprocess.STDOUT,
|
|
183
|
+
start_new_session=True,
|
|
184
|
+
)
|
|
185
|
+
active_cdo.add(out)
|
|
186
|
+
try:
|
|
187
|
+
result, ignore = out.communicate()
|
|
188
|
+
except BaseException:
|
|
189
|
+
try:
|
|
190
|
+
os.killpg(out.pid, signal.SIGKILL)
|
|
191
|
+
except (OSError, ProcessLookupError):
|
|
192
|
+
pass
|
|
193
|
+
out.wait()
|
|
194
|
+
raise
|
|
195
|
+
finally:
|
|
196
|
+
active_cdo.discard(out)
|
|
197
|
+
return out, result
|
|
198
|
+
|
|
199
|
+
|
|
181
200
|
def run_cdo(command=None, target=None, out_file=None, overwrite=False, precision=None):
|
|
182
201
|
warned = False
|
|
183
202
|
|
|
@@ -221,15 +240,7 @@ def run_cdo(command=None, target=None, out_file=None, overwrite=False, precision
|
|
|
221
240
|
raise ValueError("The command does not start with cdo!")
|
|
222
241
|
|
|
223
242
|
append_safe(target)
|
|
224
|
-
out =
|
|
225
|
-
command,
|
|
226
|
-
shell=True,
|
|
227
|
-
stdin=subprocess.PIPE,
|
|
228
|
-
stdout=subprocess.PIPE,
|
|
229
|
-
stderr=subprocess.STDOUT,
|
|
230
|
-
)
|
|
231
|
-
out.wait()
|
|
232
|
-
result, ignore = out.communicate()
|
|
243
|
+
out, result = run_shell(command)
|
|
233
244
|
|
|
234
245
|
# If it is a generic grid, it's better to not throw the CDO error which might be confusing.
|
|
235
246
|
if "generic" in result.decode("utf-8").lower():
|
|
@@ -267,15 +278,7 @@ def run_cdo(command=None, target=None, out_file=None, overwrite=False, precision
|
|
|
267
278
|
command = command.replace(target, new_target)
|
|
268
279
|
target = new_target
|
|
269
280
|
|
|
270
|
-
out =
|
|
271
|
-
command,
|
|
272
|
-
shell=True,
|
|
273
|
-
stdin=subprocess.PIPE,
|
|
274
|
-
stdout=subprocess.PIPE,
|
|
275
|
-
stderr=subprocess.STDOUT,
|
|
276
|
-
)
|
|
277
|
-
out.wait()
|
|
278
|
-
result, ignore = out.communicate()
|
|
281
|
+
out, result = run_shell(command)
|
|
279
282
|
|
|
280
283
|
if "Use the CDO option -b F32" in (result.decode("utf-8")):
|
|
281
284
|
command_chunks = command.split(" ")
|
|
@@ -293,15 +296,7 @@ def run_cdo(command=None, target=None, out_file=None, overwrite=False, precision
|
|
|
293
296
|
command = command.replace("cdo ", "cdo -b F64 ")
|
|
294
297
|
command
|
|
295
298
|
|
|
296
|
-
out =
|
|
297
|
-
command,
|
|
298
|
-
shell=True,
|
|
299
|
-
stdin=subprocess.PIPE,
|
|
300
|
-
stdout=subprocess.PIPE,
|
|
301
|
-
stderr=subprocess.STDOUT,
|
|
302
|
-
)
|
|
303
|
-
out.wait()
|
|
304
|
-
result, ignore = out.communicate()
|
|
299
|
+
out, result = run_shell(command)
|
|
305
300
|
|
|
306
301
|
if out_file is not None:
|
|
307
302
|
if "HDF5 library version mismatched error" in str(result):
|
|
@@ -490,15 +485,7 @@ def run_cdo(command=None, target=None, out_file=None, overwrite=False, precision
|
|
|
490
485
|
"HDF error when running CDO. Check if files are corrupt using the is_corrupt method, and consider running the check method"
|
|
491
486
|
)
|
|
492
487
|
|
|
493
|
-
out =
|
|
494
|
-
command,
|
|
495
|
-
shell=True,
|
|
496
|
-
stdin=subprocess.PIPE,
|
|
497
|
-
stdout=subprocess.PIPE,
|
|
498
|
-
stderr=subprocess.STDOUT,
|
|
499
|
-
)
|
|
500
|
-
out.wait()
|
|
501
|
-
result1, ignore = out.communicate()
|
|
488
|
+
out, result1 = run_shell(command)
|
|
502
489
|
if (
|
|
503
490
|
(str(result1).startswith("b'Error"))
|
|
504
491
|
or ("HDF error" in str(result1))
|
|
@@ -124,6 +124,7 @@ def run_this(os_command, self, output="one", out_file=None, suppress=False):
|
|
|
124
124
|
self._ncommands += 1
|
|
125
125
|
|
|
126
126
|
cores = session_info["cores"]
|
|
127
|
+
pool = None
|
|
127
128
|
|
|
128
129
|
# if len(self) == 1:
|
|
129
130
|
# if cores > 1:
|
|
@@ -154,11 +155,7 @@ def run_this(os_command, self, output="one", out_file=None, suppress=False):
|
|
|
154
155
|
os_command = os_command.replace(" ", " ")
|
|
155
156
|
|
|
156
157
|
if cores > 1:
|
|
157
|
-
original_sigint_handler = signal.signal(
|
|
158
|
-
signal.SIGTERM, signal.SIG_IGN
|
|
159
|
-
)
|
|
160
158
|
pool = mp.get_context('fork').Pool(cores)
|
|
161
|
-
signal.signal(signal.SIGTERM, original_sigint_handler)
|
|
162
159
|
|
|
163
160
|
target_list = []
|
|
164
161
|
results = dict()
|
|
@@ -297,6 +294,9 @@ def run_this(os_command, self, output="one", out_file=None, suppress=False):
|
|
|
297
294
|
if progress_bar:
|
|
298
295
|
if not suppress:
|
|
299
296
|
pbar.update(1)
|
|
297
|
+
pool.close()
|
|
298
|
+
pool.join()
|
|
299
|
+
pool = None
|
|
300
300
|
for mm in get_warnings():
|
|
301
301
|
warnings.warn(mm)
|
|
302
302
|
if mm in session_warnings:
|
|
@@ -457,5 +457,15 @@ def run_this(os_command, self, output="one", out_file=None, suppress=False):
|
|
|
457
457
|
|
|
458
458
|
self._format = None
|
|
459
459
|
except Exception as e:
|
|
460
|
+
if pool is not None:
|
|
461
|
+
pool.terminate()
|
|
462
|
+
pool.join()
|
|
463
|
+
pool = None
|
|
460
464
|
self.reset()
|
|
461
465
|
raise ValueError(e)
|
|
466
|
+
finally:
|
|
467
|
+
# anything not handled above (e.g. KeyboardInterrupt) must not leave
|
|
468
|
+
# the pool to the garbage collector
|
|
469
|
+
if pool is not None:
|
|
470
|
+
pool.terminate()
|
|
471
|
+
pool.join()
|
|
@@ -15,6 +15,29 @@ import multiprocessing as mp
|
|
|
15
15
|
_mp_fork_ctx = mp.get_context("fork")
|
|
16
16
|
Manager = _mp_fork_ctx.Manager
|
|
17
17
|
|
|
18
|
+
|
|
19
|
+
from contextlib import contextmanager
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@contextmanager
|
|
23
|
+
def fork_pool(cores):
|
|
24
|
+
"""
|
|
25
|
+
Context manager giving a fork Pool that is always shut down and joined
|
|
26
|
+
|
|
27
|
+
A pool left to the garbage collector is terminated and then joined with no
|
|
28
|
+
timeout, which hangs for ever if a worker survives SIGTERM. The stdlib
|
|
29
|
+
`with Pool()` only terminates, so close/join explicitly here.
|
|
30
|
+
"""
|
|
31
|
+
pool = _mp_fork_ctx.Pool(cores)
|
|
32
|
+
try:
|
|
33
|
+
yield pool
|
|
34
|
+
except BaseException:
|
|
35
|
+
pool.terminate()
|
|
36
|
+
pool.join()
|
|
37
|
+
raise
|
|
38
|
+
pool.close()
|
|
39
|
+
pool.join()
|
|
40
|
+
|
|
18
41
|
nc_safe_par = Manager().list()
|
|
19
42
|
temp_dirs_par = Manager().list()
|
|
20
43
|
nc_protected_par = Manager().list()
|
|
@@ -129,6 +152,26 @@ def get_warnings():
|
|
|
129
152
|
return session_warnings[:]
|
|
130
153
|
|
|
131
154
|
|
|
155
|
+
# cdo processes currently running on behalf of this process, so they can be
|
|
156
|
+
# killed if nctoolkit is terminated rather than left running (and writing to
|
|
157
|
+
# temp files) after Python has gone
|
|
158
|
+
active_cdo = set()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def kill_active_cdo():
|
|
162
|
+
"""
|
|
163
|
+
Function to kill any cdo processes started by this process
|
|
164
|
+
"""
|
|
165
|
+
import signal
|
|
166
|
+
|
|
167
|
+
for proc in list(active_cdo):
|
|
168
|
+
try:
|
|
169
|
+
os.killpg(proc.pid, signal.SIGKILL)
|
|
170
|
+
except (OSError, ProcessLookupError):
|
|
171
|
+
pass
|
|
172
|
+
active_cdo.clear()
|
|
173
|
+
|
|
174
|
+
|
|
132
175
|
def session_files():
|
|
133
176
|
"""
|
|
134
177
|
Function to return the session files
|
|
@@ -46,7 +46,7 @@ extras_require["complete"] = ["geoviews", "rioxarray", "cfchecker", "geocube", "
|
|
|
46
46
|
REQUIREMENTS = [i.strip() for i in open("requirements.txt").readlines()]
|
|
47
47
|
|
|
48
48
|
setup(name='nctoolkit',
|
|
49
|
-
version='1.3.
|
|
49
|
+
version='1.3.7',
|
|
50
50
|
description=DESCRIPTION,
|
|
51
51
|
long_description=LONG_DESCRIPTION,
|
|
52
52
|
long_description_content_type='text/plain',
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
from nctoolkit.api import (
|
|
2
|
-
open_data,
|
|
3
|
-
open_geotiff,
|
|
4
|
-
from_xarray,
|
|
5
|
-
merge,
|
|
6
|
-
cor_time,
|
|
7
|
-
cor_space,
|
|
8
|
-
options,
|
|
9
|
-
DataSet,
|
|
10
|
-
open_thredds,
|
|
11
|
-
open_url,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
from nctoolkit.unify import unify
|
|
16
|
-
from nctoolkit.shape import open_shape
|
|
17
|
-
from nctoolkit.static_plot import panel_plot
|
|
18
|
-
|
|
19
|
-
from nctoolkit.validator import validator
|
|
20
|
-
from nctoolkit.matchpoint import open_matchpoint
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
from nctoolkit.cleanup import cleanup, clean_all, deep_clean, temp_check
|
|
24
|
-
|
|
25
|
-
import atexit
|
|
26
|
-
import signal
|
|
27
|
-
|
|
28
|
-
atexit.register(clean_all)
|
|
29
|
-
|
|
30
|
-
signal.signal(signal.SIGTERM, clean_all)
|
|
31
|
-
|
|
32
|
-
from nctoolkit.create_ensemble import create_ensemble, glob
|
|
33
|
-
from nctoolkit.session import session_files
|
|
34
|
-
from nctoolkit.show import nc_variables, nc_years, nc_months, nc_times
|
|
35
|
-
|
|
36
|
-
from nctoolkit.utils import validate_version, cdo_version
|
|
37
|
-
from nctoolkit.session import session_info
|
|
38
|
-
from nctoolkit.mp_adders import match_points
|
|
39
|
-
|
|
40
|
-
session_info["cdo"] = cdo_version()
|
|
41
|
-
|
|
42
|
-
try:
|
|
43
|
-
from importlib.metadata import version as _version
|
|
44
|
-
except ImportError:
|
|
45
|
-
from importlib_metadata import version as _version
|
|
46
|
-
|
|
47
|
-
try:
|
|
48
|
-
__version__ = _version("nctoolkit")
|
|
49
|
-
except Exception:
|
|
50
|
-
__version__ = "999"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|