motoko 0.0.1__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.
Files changed (38) hide show
  1. motoko-0.0.1/.gitignore +17 -0
  2. motoko-0.0.1/.gitlab-ci.yml +41 -0
  3. motoko-0.0.1/.pre-commit-config.yaml +25 -0
  4. motoko-0.0.1/PKG-INFO +65 -0
  5. motoko-0.0.1/README.md +51 -0
  6. motoko-0.0.1/motoko/__init__.py +0 -0
  7. motoko-0.0.1/motoko/bd_study.py +115 -0
  8. motoko-0.0.1/motoko/daemon.py +13 -0
  9. motoko-0.0.1/motoko/scripts/__init__.py +0 -0
  10. motoko-0.0.1/motoko/scripts/create_studies.py +29 -0
  11. motoko-0.0.1/motoko/scripts/info.py +36 -0
  12. motoko-0.0.1/motoko/scripts/kill.py +26 -0
  13. motoko-0.0.1/motoko/scripts/launcher.py +23 -0
  14. motoko-0.0.1/motoko/scripts/main.py +37 -0
  15. motoko-0.0.1/motoko/scripts/orchestrator.py +104 -0
  16. motoko-0.0.1/motoko/task_manager.py +155 -0
  17. motoko-0.0.1/motoko/workflow.py +84 -0
  18. motoko-0.0.1/motoko.egg-info/PKG-INFO +65 -0
  19. motoko-0.0.1/motoko.egg-info/SOURCES.txt +36 -0
  20. motoko-0.0.1/motoko.egg-info/dependency_links.txt +1 -0
  21. motoko-0.0.1/motoko.egg-info/entry_points.txt +2 -0
  22. motoko-0.0.1/motoko.egg-info/requires.txt +2 -0
  23. motoko-0.0.1/motoko.egg-info/top_level.txt +1 -0
  24. motoko-0.0.1/pyproject.toml +32 -0
  25. motoko-0.0.1/setup.cfg +4 -0
  26. motoko-0.0.1/tests/local/local_test.py +71 -0
  27. motoko-0.0.1/tests/local/workflow/add/bd.yaml +6 -0
  28. motoko-0.0.1/tests/local/workflow/add/doIt.py +27 -0
  29. motoko-0.0.1/tests/local/workflow/add/launch.sh +8 -0
  30. motoko-0.0.1/tests/local/workflow/motoko.yaml +8 -0
  31. motoko-0.0.1/tests/local/workflow/mult/bd.yaml +6 -0
  32. motoko-0.0.1/tests/local/workflow/mult/doIt.py +43 -0
  33. motoko-0.0.1/tests/local/workflow/mult/launch.sh +8 -0
  34. motoko-0.0.1/tests/local/workflow/norm/bd.yaml +6 -0
  35. motoko-0.0.1/tests/local/workflow/norm/doIt.py +56 -0
  36. motoko-0.0.1/tests/local/workflow/norm/launch.sh +8 -0
  37. motoko-0.0.1/tests/local/workflow/orchestrator.py +60 -0
  38. motoko-0.0.1/uv.lock +1490 -0
@@ -0,0 +1,17 @@
1
+ BD-*-runs
2
+ .bd
3
+ *.txt
4
+ *.pyc
5
+ *.xyz
6
+ *.mtp
7
+ *.cfg
8
+ *.als
9
+ examples/**/*.yaml
10
+ !examples/alotf/**/*.mtp
11
+ !examples/alotf/**/*.cfg
12
+ !examples/alotf/**/*.yaml
13
+ examples/**/databases/*
14
+ examples/**/db/*
15
+ !files/**/*
16
+ *.egg-info
17
+ tests/db
@@ -0,0 +1,41 @@
1
+
2
+ stages:
3
+ - version
4
+ - build
5
+ - test
6
+ - code_quality
7
+ - deploy
8
+
9
+ #-------------------------------------------------------------------------------
10
+ test:
11
+ stage: test
12
+ image: python:3.10.9
13
+ script:
14
+ - pip install pytest pep8 pyright
15
+ - pip install .
16
+ - python3 -m pytest
17
+
18
+
19
+ package:
20
+ stage: build
21
+ image: python:latest
22
+ script:
23
+ - pip install build
24
+ - python3 -m build
25
+ artifacts:
26
+ when: on_success
27
+ paths:
28
+ - dist/
29
+ expire_in: 30 mins
30
+
31
+ python_pypi:
32
+ stage: deploy
33
+ image: python:latest
34
+ needs:
35
+ - job: package
36
+ script:
37
+ - pip install twine
38
+ - TWINE_PASSWORD=${PYPI_TOKEN} TWINE_USERNAME=__token__
39
+ python3 -m twine upload --verbose dist/*
40
+ only:
41
+ - tags
@@ -0,0 +1,25 @@
1
+ # See https://pre-commit.com for more information
2
+ # See https://pre-commit.com/hooks.html for more hooks
3
+ repos:
4
+ - repo: https://github.com/pre-commit/pre-commit-hooks
5
+ rev: v4.5.0
6
+ hooks:
7
+ - id: trailing-whitespace
8
+ - id: end-of-file-fixer
9
+ - id: check-json
10
+ - id: check-yaml
11
+ - repo: https://github.com/astral-sh/ruff-pre-commit
12
+ rev: v0.11.8
13
+ hooks:
14
+ - id: ruff # Linter
15
+ args: [ --fix ]
16
+ - id: ruff-format # Formatter
17
+ - id: ruff # Check annotations and docstrings
18
+ args: [ --select, "D,ANN0,ANN2", --fix, --unsafe-fixes ]
19
+ stages: [ manual ]
20
+ - repo: https://github.com/codespell-project/codespell
21
+ rev: v2.2.6
22
+ hooks:
23
+ - id: codespell
24
+ additional_dependencies:
25
+ - tomli
motoko-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: motoko
3
+ Version: 0.0.1
4
+ Summary: Versatile Workflow Manager
5
+ Author-email: Guillaume Anciaux <guillaume.anciaux@epfl.ch>, Max Ludwig Hodapp <MaxLudwig.Hodapp@mcl.at>
6
+ License: GPL-3.0-or-later
7
+ Project-URL: homepage, https://gitlab.com/blackdynamite/motoko
8
+ Project-URL: repository, https://gitlab.com/blackdynamite/motoko
9
+ Project-URL: documentation, https://gitlab.com/blackdynamite/motoko
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: ase<4.0.0,>=3.24.0
13
+ Requires-Dist: blackdynamite>=1.1.9
14
+
15
+ # Install the package in development mode with dependencies
16
+
17
+ ```
18
+ pip install -e .
19
+ ```
20
+
21
+
22
+ # Run the tests
23
+
24
+ ```
25
+ pytest
26
+ ```
27
+
28
+
29
+ # How to use Kusanagi
30
+
31
+ ## Command line interface (CLI)
32
+
33
+ Kusanagi is provided with a few command lines.
34
+ In a Kusanagi directory equipped with a `motoko.yaml`file,
35
+ you can initialize the work flow with:
36
+
37
+ ```
38
+ motoko create workflow_dir
39
+ cd workflow_dir
40
+ ```
41
+
42
+ where `workflow_dir` is such a directory
43
+
44
+ You can then fetch for info of the current state:
45
+
46
+ ```
47
+ motoko info
48
+ ```
49
+
50
+ and to be more verbose
51
+
52
+ ```
53
+ motoko info --verbose
54
+ ```
55
+
56
+ You can finally kill every running daemon with:
57
+
58
+ ```
59
+ motoko kill
60
+ ```
61
+
62
+
63
+ ## Python interface
64
+
65
+ TODO
motoko-0.0.1/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Install the package in development mode with dependencies
2
+
3
+ ```
4
+ pip install -e .
5
+ ```
6
+
7
+
8
+ # Run the tests
9
+
10
+ ```
11
+ pytest
12
+ ```
13
+
14
+
15
+ # How to use Kusanagi
16
+
17
+ ## Command line interface (CLI)
18
+
19
+ Kusanagi is provided with a few command lines.
20
+ In a Kusanagi directory equipped with a `motoko.yaml`file,
21
+ you can initialize the work flow with:
22
+
23
+ ```
24
+ motoko create workflow_dir
25
+ cd workflow_dir
26
+ ```
27
+
28
+ where `workflow_dir` is such a directory
29
+
30
+ You can then fetch for info of the current state:
31
+
32
+ ```
33
+ motoko info
34
+ ```
35
+
36
+ and to be more verbose
37
+
38
+ ```
39
+ motoko info --verbose
40
+ ```
41
+
42
+ You can finally kill every running daemon with:
43
+
44
+ ```
45
+ motoko kill
46
+ ```
47
+
48
+
49
+ ## Python interface
50
+
51
+ TODO
File without changes
@@ -0,0 +1,115 @@
1
+ """Scripts for creating and launching BlackDynamite studies"""
2
+
3
+ import os
4
+ import subprocess
5
+ import shutil
6
+ from BlackDynamite.scripts import createDB, bd_zeo_server
7
+ from BlackDynamite import bdparser
8
+
9
+ ################################################################
10
+
11
+
12
+ def create_bd_study(task_manager):
13
+ if not os.path.exists(task_manager.study_dir):
14
+ raise RuntimeError(f"not existing study dir {task_manager.study_dir}")
15
+
16
+ # Stop any existing BD server and delete files
17
+ if os.path.exists(task_manager.study_dir):
18
+ subprocess.run(["canYouDigIt", "server", "stop"], cwd=task_manager.study_dir)
19
+ # make a bd command to do the cleaning (to be working in remate also)
20
+ subprocess.run(["git", "clean", "-fxd", task_manager.study_dir])
21
+
22
+ os.makedirs(task_manager.bd_dir, exist_ok=True)
23
+
24
+ cwd = os.getcwd()
25
+ os.chdir(task_manager.study_dir)
26
+ # TODO: BD seems not to allow to create servers using tcp => fix this in BD
27
+ createDB.main(["--truerun", "--study", task_manager.study])
28
+ bd_zeo_server.main(["--action", "stop"])
29
+ bd_zeo_server.main(
30
+ [
31
+ "--action",
32
+ "start",
33
+ "--host",
34
+ task_manager.host,
35
+ "--study",
36
+ task_manager.study,
37
+ ]
38
+ )
39
+ os.chdir(cwd)
40
+
41
+
42
+ ################################################################
43
+ def remote_launch(study, study_dir, study_files, host, ssh, local_dir):
44
+ try:
45
+ # Remove previous database
46
+ cmd = f"mkdir -p {study_dir}; "
47
+ cmd += f"cd {study_dir}; "
48
+ cmd += "canYouDigIt server stop; "
49
+ cmd += "rm -rf * .bd"
50
+ ssh.cmd(cmd)
51
+ ssh.copy(study_files, study_dir)
52
+ # Initialize database
53
+ cmd = f"cd {study_dir}; "
54
+ cmd += "canYouDigIt init --truerun; "
55
+ cmd += "canYouDigIt server stop; "
56
+ cmd += f"canYouDigIt server start --host zeo://{host} --study {study}"
57
+ ssh.cmd(cmd)
58
+ except subprocess.CalledProcessError as e:
59
+ print(e.stderr)
60
+
61
+ # Assume that we want to access from the current work dir, create jobs/runs etc., we
62
+ # need all files and a dummy .db folder in it
63
+ if local_dir is not None:
64
+ local_study_dir = local_dir + "/" + study
65
+ os.makedirs(local_study_dir + "/.bd", exist_ok=True)
66
+ for f in study_files:
67
+ try:
68
+ shutil.copy(f, local_study_dir)
69
+ except shutil.SameFileError:
70
+ pass
71
+
72
+
73
+ ################################################################
74
+ def create_bd_studies(
75
+ workflow,
76
+ validated=None,
77
+ ):
78
+ if validated is None or not validated:
79
+ validated = bdparser.validate_question(
80
+ "Reset and drop content of Kusanagi database", {"yes": False}, False
81
+ )
82
+ if validated is False:
83
+ return
84
+
85
+ for name, task_manager in workflow.task_managers.items():
86
+ create_bd_study(task_manager)
87
+
88
+
89
+ ################################################################
90
+ class SSH:
91
+ def __init__(self, host, user, port=None, profile=""):
92
+ self.host = host
93
+ self.user = user
94
+ self.profile = profile
95
+
96
+ self.remote = f"{user}@{host}"
97
+
98
+ self.ssh_cmd = ["ssh"]
99
+ self.scp_cmd = ["scp"]
100
+ if port is not None:
101
+ self.ssh_cmd += ["-p", f"{port}"]
102
+ self.scp_cmd += ["-P", f"{port}"]
103
+
104
+ def cmd(self, cmd):
105
+ if self.profile == "":
106
+ result = subprocess.run(self.ssh_cmd + [self.remote, cmd])
107
+ else:
108
+ result = subprocess.run(
109
+ self.ssh_cmd + [self.remote, f"source {self.profile}; {cmd}"]
110
+ )
111
+ print(result.stdout)
112
+
113
+ def copy(self, files, dest):
114
+ result = subprocess.run(self.scp_cmd + files + [f"{self.remote}:{dest}"])
115
+ print(result.stdout)
@@ -0,0 +1,13 @@
1
+ import subprocess
2
+ import json
3
+ import os
4
+
5
+
6
+ def start_daemon(script, inputs):
7
+ cmd = ["python", script.__file__, json.dumps(inputs)]
8
+ print("Execute: ", *cmd)
9
+ return subprocess.Popen(
10
+ cmd,
11
+ stdout=subprocess.PIPE,
12
+ cwd=os.getcwd(),
13
+ )
File without changes
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ from motoko.workflow import Workflow
5
+
6
+ command = "create"
7
+ command_help = "Create the sub studies of the workflow"
8
+
9
+
10
+ def populate_arg_parser(parser):
11
+ parser.add_argument(
12
+ "--validated",
13
+ action="store_true",
14
+ help="Answer yes to validation questions",
15
+ )
16
+ parser.add_argument(
17
+ "directory",
18
+ type=str,
19
+ default="./",
20
+ help="Directory where the description of the workflow is provided",
21
+ )
22
+
23
+
24
+ def main(args):
25
+ fullpath = os.path.join(args.directory, "motoko.yaml")
26
+
27
+ wf = Workflow(fullpath)
28
+ wf.create(args.validated)
29
+ return wf
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import subprocess
4
+ from motoko.workflow import Workflow
5
+
6
+ command = "info"
7
+ command_help = "Get info from sub-studies"
8
+
9
+
10
+ def populate_arg_parser(parser):
11
+ parser.add_argument("--verbose", action="store_true", help="show verbose details")
12
+
13
+
14
+ def main(args):
15
+ fullpath = "motoko.yaml"
16
+ wf = Workflow(fullpath)
17
+
18
+ for name, task_manager in wf.task_managers.items():
19
+ print("*" * 30)
20
+ print(f"TaskManager: {name}")
21
+ print("*" * 30)
22
+ if not args.verbose:
23
+ subprocess.call("canYouDigIt info", cwd=task_manager.study_dir, shell=True)
24
+
25
+ else:
26
+ subprocess.call(
27
+ "canYouDigIt jobs info", cwd=task_manager.study_dir, shell=True
28
+ )
29
+ subprocess.call(
30
+ "canYouDigIt runs info", cwd=task_manager.study_dir, shell=True
31
+ )
32
+ subprocess.call(
33
+ "canYouDigIt launch_daemon --status",
34
+ cwd=task_manager.study_dir,
35
+ shell=True,
36
+ )
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import subprocess
4
+
5
+ from motoko.workflow import Workflow
6
+
7
+ command = "kill"
8
+ command_help = "Kill all the running daemons"
9
+
10
+
11
+ def populate_arg_parser(parser):
12
+ parser.add_argument("--verbose", action="store_true", help="show verbose details")
13
+
14
+
15
+ def main(args):
16
+ fullpath = "motoko.yaml"
17
+
18
+ wf = Workflow(fullpath)
19
+
20
+ for name, task_manager in wf.task_managers.items():
21
+ print(f"Kill daemons in study: {task_manager.study}")
22
+ cmds = ["canYouDigIt launch_daemon --stop", "canYouDigIt server stop"]
23
+ for cmd in cmds:
24
+ if args.verbose:
25
+ print(f"({task_manager.study}) {cmd}")
26
+ subprocess.call(cmd, cwd=task_manager.study_dir, shell=True)
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from motoko.workflow import Workflow
4
+
5
+ command = "launcher"
6
+ command_help = "Spawn the launcher daemon"
7
+
8
+
9
+ def populate_arg_parser(parser):
10
+ pass
11
+
12
+
13
+ def main(args):
14
+ fullpath = "motoko.yaml"
15
+ wf = Workflow(fullpath)
16
+ wf.start_launcher_daemons()
17
+
18
+ # for name, task_manager in wf.task_managers.items():
19
+ # subprocess.call(
20
+ # "canYouDigIt launch_daemon --start --detach",
21
+ # cwd=task_manager.study_dir,
22
+ # shell=True,
23
+ # )
@@ -0,0 +1,37 @@
1
+ import argparse
2
+
3
+ from motoko.scripts import create_studies, info, kill, launcher, orchestrator
4
+
5
+ commands = [create_studies, info, kill, launcher, orchestrator]
6
+
7
+
8
+ def main() -> None:
9
+ """Entry point for the command line interface."""
10
+ args = parse_args()
11
+
12
+ for command in commands:
13
+ if args.command == command.command:
14
+ command.main(args)
15
+ break
16
+
17
+
18
+ def parse_args():
19
+ parser = argparse.ArgumentParser()
20
+ parser.prog = "motoko"
21
+
22
+ # Create subparsers for each command
23
+ command_parsers = parser.add_subparsers(dest="command", help="command to run")
24
+ command_parsers.required = True
25
+
26
+ for command in commands:
27
+ command_parser = command_parsers.add_parser(
28
+ command.command, help=command.command_help
29
+ )
30
+ command.populate_arg_parser(command_parser)
31
+
32
+ args = parser.parse_args()
33
+ return args
34
+
35
+
36
+ if __name__ == "__main__":
37
+ main()
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import sys
5
+ import importlib.util
6
+ from BlackDynamite.scripts.launch_daemon import find_pids
7
+
8
+ from motoko.workflow import Workflow
9
+
10
+ command = "orchestrator"
11
+ command_help = "Start the workflow"
12
+
13
+
14
+ def populate_arg_parser(parser):
15
+ subparsers = parser.add_subparsers(dest="what")
16
+ parser_start = subparsers.add_parser("start", help="start orchestrator daemon")
17
+
18
+ parser_start.add_argument(
19
+ "--detach",
20
+ "-d",
21
+ action="store_true",
22
+ help="For starting: run in detach/daemon mode with Zdaemon manager",
23
+ )
24
+ parser_start.add_argument(
25
+ "--wait",
26
+ type=str,
27
+ default=5,
28
+ help="Waiting time between state checks in seconds",
29
+ )
30
+
31
+ # Add arguments specific to workflow
32
+
33
+ fullpath = "motoko.yaml"
34
+ wf = Workflow(fullpath)
35
+
36
+ fname, _ = wf.orchestrator_script.split(".")
37
+ file_path = os.path.join(wf.directory, fname + ".py")
38
+ module_name = "orchestrator"
39
+ print(f"loading {file_path}")
40
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
41
+ module = importlib.util.module_from_spec(spec)
42
+ sys.modules[module_name] = module
43
+ spec.loader.exec_module(module)
44
+ parser_function = getattr(module, "populate_arg_parser")
45
+ parser_function(parser_start)
46
+
47
+ # parser_stop = subparsers.add_parser("stop", help="stop orchestrator daemon")
48
+ # parser_stat = subparsers.add_parser(
49
+ # "status", help="get status of detached orchestrator daemon"
50
+ # )
51
+
52
+
53
+ def main(args):
54
+ fullpath = "motoko.yaml"
55
+ wf = Workflow(fullpath)
56
+ wf_root_dir = wf.directory
57
+ wf_conf_dir = os.path.join(wf_root_dir, ".wf_conf")
58
+
59
+ conf_fname = os.path.join(wf_conf_dir, "orch_daemon.conf")
60
+
61
+ if args.what == "start" and not args.detach:
62
+ params = vars(args)
63
+ wf.execute(**params)
64
+ sys.exit(0)
65
+
66
+ elif args.what == "start" and args.detach:
67
+ exclude = ["--detach", "-d"]
68
+ argv = sys.argv[3:]
69
+ clargs = " ".join([a for a in argv if a not in exclude])
70
+
71
+ prog = f"motoko {command} start {clargs}"
72
+
73
+ zdaemon_conf = f"""
74
+ <runner>
75
+ program {prog}
76
+ socket-name {wf_conf_dir}/orch_daemon.socket
77
+ transcript {wf_conf_dir}/orch.log
78
+ exit-codes 0
79
+ </runner>
80
+ """
81
+
82
+ os.makedirs(wf_conf_dir, exist_ok=True)
83
+ with open(conf_fname, "w") as f:
84
+ f.write(zdaemon_conf)
85
+
86
+ os.system(f"zdaemon -C {conf_fname} start")
87
+ os.system(f"zdaemon -C {conf_fname} status")
88
+
89
+ elif args.what == "status":
90
+ print("ZDaemon status:")
91
+ os.system(f"zdaemon -C {conf_fname} status")
92
+
93
+ pids = find_pids(wf_root_dir)
94
+ print(f"Running orchestrator pids: {pids}")
95
+
96
+ elif args.what == "stop":
97
+ os.system(f"zdaemon -C {conf_fname} stop")
98
+ pids = find_pids(wf_root_dir)
99
+ if pids:
100
+ print(f"Killing: {pids}")
101
+ pids = [str(e) for e in pids]
102
+ os.system(f"kill -9 {' '.join(pids)}")
103
+
104
+ # shutil.rmtree(wf_conf_dir)