Skip to content

Commit 36aeb19

Browse files
authored
Merge pull request #54 from NathanPB/plugin-fix
Implement new plugin hooks
2 parents f0a32c5 + 639672b commit 36aeb19

File tree

6 files changed

+120
-45
lines changed

6 files changed

+120
-45
lines changed

pythia/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def main():
7373
plugins = pythia.plugin.load_plugins(config, {})
7474
config = pythia.plugin.run_plugin_functions(
7575
pythia.plugin.PluginHook.post_config, plugins, full_config=config
76-
)
76+
).get("full_config", config)
7777
if args.quiet:
7878
config["silence"] = True
7979
else:

pythia/dssat.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
import subprocess
55
from multiprocessing.pool import Pool
66

7+
import pythia.plugin
78

89
async_error = False
910

1011

11-
def _run_dssat(details, config):
12+
def _run_dssat(details, config, plugins):
1213
logging.debug("Current WD: {}".format(os.getcwd()))
1314
run_mode = "A"
1415
if "run_mode" in config["dssat"]:
@@ -22,7 +23,20 @@ def _run_dssat(details, config):
2223
)
2324
out, err = dssat.communicate()
2425
# print("+", end="", flush=True)
25-
return details["dir"], details["file"], out, err, dssat.returncode
26+
27+
error_count = len(out.decode().split("\n")) - 1
28+
hook = pythia.plugin.PluginHook.post_run_pixel_success
29+
if error_count > 0:
30+
hook = pythia.plugin.PluginHook.post_run_pixel_failed
31+
32+
plugin_transform = pythia.plugin.run_plugin_functions(
33+
hook,
34+
plugins,
35+
input={"details": details, "config": config},
36+
output={"loc": details["dir"], "xfile": details["file"], "out": out, "err": err, "retcode": dssat.returncode}
37+
).get("output", {})
38+
39+
return plugin_transform.get("loc", details["dir"]), plugin_transform.get("xfile", details["file"]), plugin_transform.get("out", out), plugin_transform.get("err", err), plugin_transform.get("retcode", dssat.returncode)
2640

2741

2842
def _generate_run_list(config):
@@ -93,12 +107,20 @@ def execute(config, plugins):
93107
with Pool(processes=pool_size) as pool:
94108
for details in run_list: # _generate_run_list(config):
95109
if config["silence"]:
96-
pool.apply_async(_run_dssat, (details, config), callback=silent_async)
110+
pool.apply_async(_run_dssat, (details, config, plugins), callback=silent_async)
97111
else:
98-
pool.apply_async(_run_dssat, (details, config), callback=display_async)
112+
pool.apply_async(_run_dssat, (details, config, plugins), callback=display_async)
99113
pool.close()
100114
pool.join()
115+
101116
if async_error:
102117
print(
103118
"\nOne or more simulations had failures. Please check the pythia log for more details"
104119
)
120+
121+
pythia.plugin.run_plugin_functions(
122+
pythia.plugin.PluginHook.post_run_all,
123+
plugins,
124+
config=config,
125+
run_list=run_list,
126+
)

pythia/peerless.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
import pythia.util
1111

1212

13-
def build_context(args):
14-
run, ctx, config = args
13+
def build_context(run, ctx, config, plugins):
1514
if not config["silence"]:
1615
print("+", end="", flush=True)
1716
context = run.copy()
@@ -28,13 +27,25 @@ def build_context(args):
2827
else:
2928
context = None
3029
break
30+
31+
hook = pythia.plugin.PluginHook.post_peerless_pixel_success
32+
if context is None:
33+
hook = pythia.plugin.PluginHook.post_peerless_pixel_skip
34+
35+
context = pythia.plugin.run_plugin_functions(
36+
hook,
37+
plugins,
38+
context=context,
39+
args={"run": run, "config": config, "ctx": ctx},
40+
).get("context", context)
41+
3142
return context
3243

3344

34-
def _generate_context_args(runs, peers, config):
45+
def _generate_context_args(runs, peers, config, plugins):
3546
for idx, run in enumerate(runs):
3647
for peer in peers[idx]:
37-
yield run, peer, config
48+
yield run, peer, config, plugins
3849

3950

4051
def symlink_wth_soil(output_dir, config, context):
@@ -69,6 +80,7 @@ def compose_peerless(context, config, env):
6980
f.write(xfile)
7081
return context["contextWorkDir"]
7182

83+
7284
def process_context(context, plugins, config, env):
7385
if context is not None:
7486
pythia.io.make_run_directory(context["contextWorkDir"])
@@ -78,9 +90,25 @@ def process_context(context, plugins, config, env):
7890
pythia.plugin.PluginHook.post_build_context,
7991
plugins,
8092
context=context,
81-
)
82-
return os.path.abspath(compose_peerless(context, config, env))
93+
).get("context", context)
94+
compose_peerless_result = compose_peerless(context, config, env)
95+
compose_peerless_result = pythia.plugin.run_plugin_functions(
96+
pythia.plugin.PluginHook.post_compose_peerless_pixel_success,
97+
plugins,
98+
context=context,
99+
compose_peerless_result=compose_peerless_result,
100+
config=config,
101+
env=env,
102+
).get("compose_peerless_result", compose_peerless_result)
103+
return os.path.abspath(compose_peerless_result)
83104
else:
105+
pythia.plugin.run_plugin_functions(
106+
pythia.plugin.PluginHook.post_compose_peerless_pixel_skip,
107+
plugins,
108+
context=context,
109+
config=config,
110+
env=env,
111+
)
84112
if not config["silence"]:
85113
print("X", end="", flush=True)
86114

@@ -102,8 +130,8 @@ def execute(config, plugins):
102130
# Parallelize the context build (build_context), it is CPU intensive because it
103131
# runs the functions (functions.py) declared in the config files.
104132
with concurrent.futures.ProcessPoolExecutor(max_workers=pool_size) as executor:
105-
tasks = _generate_context_args(runs, peers, config)
106-
future_to_context = {executor.submit(build_context, task): task for task in tasks}
133+
tasks = _generate_context_args(runs, peers, config, plugins)
134+
future_to_context = {executor.submit(build_context, *task): task for task in tasks}
107135

108136
# process_context is mostly I/O intensive, no reason to parallelize it.
109137
for future in concurrent.futures.as_completed(future_to_context):
@@ -116,5 +144,11 @@ def execute(config, plugins):
116144
if config["exportRunlist"]:
117145
with open(os.path.join(config["workDir"], "run_list.txt"), "w") as f:
118146
[f.write(f"{x}\n") for x in runlist]
119-
if not config["silence"]:
120-
print()
147+
148+
pythia.plugin.run_plugin_functions(
149+
pythia.plugin.PluginHook.post_compose_peerless_all,
150+
plugins,
151+
run_list=runlist,
152+
config=config,
153+
env=env,
154+
)

pythia/plugin.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,17 @@ class PluginHook(Enum):
77
post_config = 100
88
pre_build_context = 200
99
post_build_context = 300
10+
post_peerless_pixel_success = 350
11+
post_peerless_pixel_skip = 351
12+
post_compose_peerless_pixel_success = 352
13+
post_compose_peerless_pixel_skip = 353
14+
post_compose_peerless_all = 354
1015
post_setup = 400
1116
pre_run = 500
1217
run_pixel = 600
13-
post_run = 700
18+
post_run_pixel_success = 650
19+
post_run_pixel_failed = 651
20+
post_run_all = 700
1421
pre_analysis = 800
1522
analyze_file = 900
1623
analyze_pixel = 1000
@@ -94,21 +101,13 @@ def load_plugins(config, plugins={}, module_prefix="pythia.plugins"):
94101

95102

96103
def run_plugin_functions(hook, plugins, **kwargs):
97-
_return = {}
98-
if hook == PluginHook.post_config:
99-
_return = {**kwargs.get("full_config", {})}
100-
elif hook == PluginHook.post_build_context:
101-
_return = {**kwargs.get("context", {})}
104+
_return = {**kwargs}
102105
if hook in plugins:
103106
for plugin_fun in plugins[hook]:
104-
if hook == PluginHook.post_config:
105-
_return = {
106-
**_return,
107-
**plugin_fun["fun"](plugin_fun.get("config", {}), _return),
108-
}
109-
elif hook == PluginHook.post_build_context:
110-
_return = {
111-
**_return,
112-
**plugin_fun["fun"](plugin_fun.get("config", {}), _return),
113-
}
107+
plugin_fun_return = plugin_fun["fun"](plugin_fun.get("config", {}), _return, **kwargs)
108+
_return = {
109+
**_return,
110+
**({} if plugin_fun_return is None else plugin_fun_return)
111+
}
112+
114113
return _return

pythia/plugins/test_plugin/__init__.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,42 @@
44

55
def initialize(config, plugins, full_config):
66
logging.info("[TEST PLUGIN] Initializing plugin")
7-
plugins = register_plugin_function(
8-
PluginHook.post_config, sample_function, config, plugins
9-
)
10-
plugins = register_plugin_function(
11-
PluginHook.post_build_context, contexted_function, config, plugins
12-
)
7+
plugins = register_plugin_function(PluginHook.post_config, sample_function, config, plugins)
8+
plugins = register_plugin_function(PluginHook.post_build_context, contexted_function, config, plugins)
9+
plugins = register_plugin_function(PluginHook.post_peerless_pixel_success, on_peerless_success, config, plugins)
10+
plugins = register_plugin_function(PluginHook.post_peerless_pixel_skip, on_peerless_skip, config, plugins)
11+
plugins = register_plugin_function(PluginHook.post_run_pixel_success, on_run_pixel_success, config, plugins)
12+
plugins = register_plugin_function(PluginHook.post_run_pixel_failed, on_run_pixel_failed, config, plugins)
1313
return plugins
1414

1515

16-
def sample_function(config={}):
16+
def sample_function(config={}, **kwargs):
1717
retval = config.get("value", 1)
1818
logging.info("[TEST PLUGIN] Running the sample_function()")
19-
return retval
19+
return {**kwargs, "config": config, "retval": retval}
2020

2121

22-
def contexted_function(config={}, context={}):
22+
def contexted_function(context={}, **kwargs):
2323
logging.info("[TEST PLUGIN] Running the contexted_function()")
2424
context["context_value"] = context.get("context_value", 2) + 1
25-
return context
25+
return {**kwargs, "context": context}
26+
27+
28+
def on_peerless_success(*args, **kwargs):
29+
logging.info("[TEST PLUGIN] peerless success")
30+
return kwargs
31+
32+
33+
def on_peerless_skip(*args, **kwargs):
34+
logging.info("[TEST PLUGIN] peerless skip")
35+
return kwargs
36+
37+
38+
def on_run_pixel_success(*args, **kwargs):
39+
logging.info("[TEST PLUGIN] run pixel success")
40+
return kwargs
41+
42+
43+
def on_run_pixel_failed(*args, **kwargs):
44+
logging.info("[TEST PLUGIN] run pixel failed")
45+
return kwargs

pythia/tests/plugin_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ def test_plugin_auto_execution():
9393
context = {"context_value": 7}
9494
context1 = run_plugin_functions(
9595
PluginHook.post_build_context, plugins1, context=context
96-
)
97-
context2 = run_plugin_functions(PluginHook.post_build_context, plugins1)
96+
).get("context")
97+
context2 = run_plugin_functions(PluginHook.post_build_context, plugins1).get("context", None)
9898
assert context1 != context
9999
assert context1["context_value"] == 8
100100
assert context2["context_value"] == 3
@@ -107,10 +107,10 @@ def test_no_plugin_does_not_change_context():
107107
context = {"hello": "there"}
108108
context1 = run_plugin_functions(
109109
PluginHook.post_build_context, plugins, context=context
110-
)
110+
).get("context")
111111
assert context == context1
112112
context2 = run_plugin_functions(
113113
PluginHook.post_build_context, plugins1, context=context
114-
)
114+
).get("context")
115115
assert context1 != context2
116116
assert context2 == {**context, **{"context_value": 3}}

0 commit comments

Comments
 (0)