horus-runtime 0.1.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.
- horus_builtin/__init__.py +17 -0
- horus_builtin/artifact/__init__.py +17 -0
- horus_builtin/artifact/file.py +51 -0
- horus_builtin/artifact/folder.py +153 -0
- horus_builtin/artifact/json.py +59 -0
- horus_builtin/artifact/pickle.py +56 -0
- horus_builtin/event/__init__.py +17 -0
- horus_builtin/event/artifact_event.py +48 -0
- horus_builtin/event/log_subscriber.py +54 -0
- horus_builtin/event/task_event.py +33 -0
- horus_builtin/event/workflow_event.py +32 -0
- horus_builtin/executor/__init__.py +17 -0
- horus_builtin/executor/python_exec.py +64 -0
- horus_builtin/executor/python_fn.py +57 -0
- horus_builtin/executor/shell.py +99 -0
- horus_builtin/interaction/__init__.py +17 -0
- horus_builtin/interaction/cli.py +202 -0
- horus_builtin/interaction/common/__init__.py +17 -0
- horus_builtin/interaction/common/confirm.py +55 -0
- horus_builtin/interaction/common/dropdown.py +65 -0
- horus_builtin/interaction/common/file.py +62 -0
- horus_builtin/interaction/common/string.py +43 -0
- horus_builtin/middleware/task_time.py +63 -0
- horus_builtin/middleware/workflow_time.py +68 -0
- horus_builtin/py.typed +0 -0
- horus_builtin/runtime/__init__.py +17 -0
- horus_builtin/runtime/command.py +95 -0
- horus_builtin/runtime/python.py +125 -0
- horus_builtin/runtime/python_string.py +52 -0
- horus_builtin/target/__init__.py +17 -0
- horus_builtin/target/local.py +129 -0
- horus_builtin/task/__init__.py +17 -0
- horus_builtin/task/function.py +101 -0
- horus_builtin/task/horus_task.py +114 -0
- horus_builtin/transfer/__init__.py +17 -0
- horus_builtin/transfer/local_noop.py +45 -0
- horus_builtin/workflow/__init__.py +17 -0
- horus_builtin/workflow/horus_workflow.py +98 -0
- horus_runtime/__init__.py +17 -0
- horus_runtime/cli.py +45 -0
- horus_runtime/context.py +145 -0
- horus_runtime/core/__init__.py +17 -0
- horus_runtime/core/artifact/__init__.py +17 -0
- horus_runtime/core/artifact/base.py +186 -0
- horus_runtime/core/artifact/exceptions.py +32 -0
- horus_runtime/core/executor/__init__.py +17 -0
- horus_runtime/core/executor/base.py +92 -0
- horus_runtime/core/executor/exceptions.py +32 -0
- horus_runtime/core/interaction/__init__.py +17 -0
- horus_runtime/core/interaction/base.py +47 -0
- horus_runtime/core/interaction/exceptions.py +133 -0
- horus_runtime/core/interaction/renderer.py +65 -0
- horus_runtime/core/interaction/transport.py +309 -0
- horus_runtime/core/runtime/__init__.py +17 -0
- horus_runtime/core/runtime/base.py +81 -0
- horus_runtime/core/runtime/events.py +33 -0
- horus_runtime/core/target/__init__.py +17 -0
- horus_runtime/core/target/base.py +151 -0
- horus_runtime/core/task/__init__.py +17 -0
- horus_runtime/core/task/base.py +250 -0
- horus_runtime/core/task/exceptions.py +40 -0
- horus_runtime/core/task/status.py +58 -0
- horus_runtime/core/transfer/__init__.py +17 -0
- horus_runtime/core/transfer/exceptions.py +74 -0
- horus_runtime/core/transfer/strategy.py +96 -0
- horus_runtime/core/workflow/__init__.py +17 -0
- horus_runtime/core/workflow/base.py +296 -0
- horus_runtime/core/workflow/exceptions.py +43 -0
- horus_runtime/core/workflow/status.py +68 -0
- horus_runtime/event/__init__.py +17 -0
- horus_runtime/event/async_loop.py +75 -0
- horus_runtime/event/base.py +106 -0
- horus_runtime/event/bus.py +188 -0
- horus_runtime/event/subscriber.py +64 -0
- horus_runtime/event/transport.py +59 -0
- horus_runtime/i18n.py +96 -0
- horus_runtime/locale/es/LC_MESSAGES/horus_runtime.mo +0 -0
- horus_runtime/locale/es/LC_MESSAGES/horus_runtime.po +59 -0
- horus_runtime/locale/messages.pot +50 -0
- horus_runtime/logging.py +110 -0
- horus_runtime/middleware/__init__.py +17 -0
- horus_runtime/middleware/auto_middleware.py +154 -0
- horus_runtime/middleware/executor.py +49 -0
- horus_runtime/middleware/interaction.py +54 -0
- horus_runtime/middleware/runtime.py +49 -0
- horus_runtime/middleware/target.py +49 -0
- horus_runtime/middleware/task.py +47 -0
- horus_runtime/middleware/transfer.py +52 -0
- horus_runtime/middleware/workflow.py +47 -0
- horus_runtime/py.typed +0 -0
- horus_runtime/registry/__init__.py +17 -0
- horus_runtime/registry/auto_registry.py +429 -0
- horus_runtime/registry/auto_registry_product.py +179 -0
- horus_runtime/registry/exceptions.py +61 -0
- horus_runtime/version.py +36 -0
- horus_runtime-0.1.0.dist-info/METADATA +13 -0
- horus_runtime-0.1.0.dist-info/RECORD +100 -0
- horus_runtime-0.1.0.dist-info/WHEEL +4 -0
- horus_runtime-0.1.0.dist-info/entry_points.txt +48 -0
- horus_runtime-0.1.0.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
"""
|
|
19
|
+
Implementation of the FileArtifact class, which represents a local
|
|
20
|
+
file artifact in the Horus runtime.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from horus_builtin.event.artifact_event import ArtifactEventsEnum
|
|
24
|
+
from horus_runtime.core.artifact.base import BaseArtifact
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FileArtifact(BaseArtifact[str]):
|
|
28
|
+
"""
|
|
29
|
+
Represents a local file artifact.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
kind: str = "file"
|
|
33
|
+
|
|
34
|
+
def read(self) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Read and deserialize the contents of the file artifact.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
The full text content of the file.
|
|
40
|
+
"""
|
|
41
|
+
txt = self.path.read_text()
|
|
42
|
+
self._emit_event(ArtifactEventsEnum.READ)
|
|
43
|
+
return txt
|
|
44
|
+
|
|
45
|
+
def write(self, value: str) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Write text content to the file artifact path.
|
|
48
|
+
"""
|
|
49
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
self.path.write_text(value)
|
|
51
|
+
self._emit_event(ArtifactEventsEnum.WRITE)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
"""
|
|
19
|
+
Implementation of the FolderArtifact class, which represents a local
|
|
20
|
+
folder/directory artifact in the Horus runtime.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import hashlib
|
|
24
|
+
import os
|
|
25
|
+
import shutil
|
|
26
|
+
import tempfile
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
from horus_builtin.event.artifact_event import ArtifactEventsEnum
|
|
30
|
+
from horus_runtime.core.artifact.base import BaseArtifact
|
|
31
|
+
from horus_runtime.i18n import tr as _
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FolderArtifact(BaseArtifact[Path]):
|
|
35
|
+
"""
|
|
36
|
+
Represents a local folder artifact.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
kind: str = "folder"
|
|
40
|
+
|
|
41
|
+
def exists(self) -> bool:
|
|
42
|
+
"""
|
|
43
|
+
Check if the folder specified by the path exists and is a directory.
|
|
44
|
+
"""
|
|
45
|
+
return self.path.is_dir()
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def hash(self) -> str | None:
|
|
49
|
+
"""
|
|
50
|
+
Computes the hash of the folder and its contents by recursively hashing
|
|
51
|
+
all files in the folder. The hash is computed by combining the relative
|
|
52
|
+
paths and contents of all files in the folder, ensuring that changes to
|
|
53
|
+
any file or the addition/removal of files will result in a different
|
|
54
|
+
hash.
|
|
55
|
+
"""
|
|
56
|
+
if not self.exists():
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
sha256 = hashlib.sha256()
|
|
60
|
+
|
|
61
|
+
# 1. Get all files and sort them by relative path for determinism
|
|
62
|
+
# We use rglob("*") to get everything, but only hash files
|
|
63
|
+
paths = sorted([p for p in self.path.rglob("*") if p.is_file()])
|
|
64
|
+
|
|
65
|
+
for path in paths:
|
|
66
|
+
relative_path = path.relative_to(self.path).as_posix()
|
|
67
|
+
sha256.update(relative_path.encode("utf-8"))
|
|
68
|
+
|
|
69
|
+
# Hash the file contents
|
|
70
|
+
sha256.update(self.hash_file(path))
|
|
71
|
+
|
|
72
|
+
return sha256.hexdigest()
|
|
73
|
+
|
|
74
|
+
def read(self) -> Path:
|
|
75
|
+
"""
|
|
76
|
+
Return the canonical directory path for this artifact.
|
|
77
|
+
"""
|
|
78
|
+
self._emit_event(ArtifactEventsEnum.READ)
|
|
79
|
+
return self.path
|
|
80
|
+
|
|
81
|
+
def write(self, value: Path) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Materialize this folder artifact from another local directory.
|
|
84
|
+
|
|
85
|
+
WARNING: THIS WILL OVERWRITE ANY EXISTING CONTENT AT THE ARTIFACT PATH.
|
|
86
|
+
"""
|
|
87
|
+
source_path = Path(value).resolve()
|
|
88
|
+
if not source_path.is_dir():
|
|
89
|
+
raise ValueError(
|
|
90
|
+
_("Expected directory path, got %(source_path)s")
|
|
91
|
+
% {"source_path": source_path}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if self.path.exists():
|
|
95
|
+
shutil.rmtree(self.path)
|
|
96
|
+
|
|
97
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
shutil.copytree(source_path, self.path)
|
|
99
|
+
self._emit_event(ArtifactEventsEnum.WRITE)
|
|
100
|
+
|
|
101
|
+
def package(self) -> Path:
|
|
102
|
+
"""
|
|
103
|
+
Archive the folder into a zip file and return the archive path.
|
|
104
|
+
"""
|
|
105
|
+
if not self.exists():
|
|
106
|
+
raise FileNotFoundError(self.path)
|
|
107
|
+
|
|
108
|
+
# Create a temporary file to be used as the archive path.
|
|
109
|
+
# shutil.make_archive requires a base name without extension,
|
|
110
|
+
# so we create a temp file and then remove it after archiving.
|
|
111
|
+
fd, arch_p = tempfile.mkstemp()
|
|
112
|
+
os.close(fd)
|
|
113
|
+
archive_path = Path(arch_p)
|
|
114
|
+
|
|
115
|
+
shutil.make_archive(
|
|
116
|
+
base_name=str(archive_path),
|
|
117
|
+
format="zip",
|
|
118
|
+
root_dir=self.path,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# shutil.make_archive adds .zip, so remove the temp
|
|
122
|
+
# file and use the generated archive
|
|
123
|
+
archive_file = archive_path.with_suffix(".zip")
|
|
124
|
+
if archive_path.exists():
|
|
125
|
+
archive_path.unlink()
|
|
126
|
+
|
|
127
|
+
self._emit_event(ArtifactEventsEnum.PACKAGE)
|
|
128
|
+
return archive_file
|
|
129
|
+
|
|
130
|
+
def unpackage(self, package_path: Path) -> None:
|
|
131
|
+
"""
|
|
132
|
+
Extract a packaged folder archive into the canonical directory path.
|
|
133
|
+
"""
|
|
134
|
+
package_path = Path(package_path).resolve()
|
|
135
|
+
|
|
136
|
+
if self.path.exists():
|
|
137
|
+
shutil.rmtree(self.path)
|
|
138
|
+
|
|
139
|
+
self.path.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
shutil.unpack_archive(
|
|
141
|
+
filename=str(package_path),
|
|
142
|
+
extract_dir=str(self.path),
|
|
143
|
+
)
|
|
144
|
+
self._emit_event(ArtifactEventsEnum.UNPACKAGE)
|
|
145
|
+
|
|
146
|
+
def delete(self) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Deletes the artifact from its location by deleting the folder at the
|
|
149
|
+
specified path.
|
|
150
|
+
"""
|
|
151
|
+
if self.exists():
|
|
152
|
+
shutil.rmtree(self.path)
|
|
153
|
+
self._emit_event(ArtifactEventsEnum.DELETE)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
"""
|
|
19
|
+
Implementation of the FolderArtifact class, which represents a local
|
|
20
|
+
folder/directory artifact in the Horus runtime.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
from typing import Any, cast
|
|
25
|
+
|
|
26
|
+
from horus_builtin.event.artifact_event import ArtifactEventsEnum
|
|
27
|
+
from horus_runtime.core.artifact.base import BaseArtifact
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class JSONArtifact[T: Any = Any](BaseArtifact[T]):
|
|
31
|
+
"""
|
|
32
|
+
Represents a JSON-serializable Python object artifact.
|
|
33
|
+
The artifact is materialized as a JSON file on disk.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
kind: str = "json"
|
|
37
|
+
|
|
38
|
+
def read(self) -> T:
|
|
39
|
+
"""
|
|
40
|
+
Read and deserialize the JSON artifact contents.
|
|
41
|
+
|
|
42
|
+
Warning: This method assumes that the JSON file is well-formed and that
|
|
43
|
+
the contents can be deserialized into the expected type `T`. For more
|
|
44
|
+
robust handling, consider using PydanticArtifact, which provides
|
|
45
|
+
validation and error handling for deserialization.
|
|
46
|
+
"""
|
|
47
|
+
with open(self.path) as f:
|
|
48
|
+
j_contet = json.load(f)
|
|
49
|
+
self._emit_event(ArtifactEventsEnum.READ)
|
|
50
|
+
|
|
51
|
+
return cast(T, j_contet)
|
|
52
|
+
|
|
53
|
+
def write(self, value: T) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Serialize and write the JSON artifact contents.
|
|
56
|
+
"""
|
|
57
|
+
with open(self.path, "w") as f:
|
|
58
|
+
json.dump(value, f)
|
|
59
|
+
self._emit_event(ArtifactEventsEnum.WRITE)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
"""
|
|
19
|
+
Implementation of PickleArtifact, which serializes arbitrary Python objects
|
|
20
|
+
to disk using the pickle protocol.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import pickle
|
|
24
|
+
from typing import Any, cast
|
|
25
|
+
|
|
26
|
+
from horus_builtin.event.artifact_event import ArtifactEventsEnum
|
|
27
|
+
from horus_runtime.core.artifact.base import BaseArtifact
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PickleArtifact[T: Any = Any](BaseArtifact[T]):
|
|
31
|
+
"""
|
|
32
|
+
Represents a pickled Python object artifact.
|
|
33
|
+
The artifact is materialized as a pickle file on disk.
|
|
34
|
+
|
|
35
|
+
Warning: pickle is not secure against malformed or maliciously crafted
|
|
36
|
+
data. Never unpickle data from untrusted sources.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
kind: str = "pickle"
|
|
40
|
+
|
|
41
|
+
def read(self) -> T:
|
|
42
|
+
"""
|
|
43
|
+
Deserialize the artifact from the pickle file.
|
|
44
|
+
"""
|
|
45
|
+
with open(self.path, "rb") as f:
|
|
46
|
+
obj = pickle.load(f)
|
|
47
|
+
self._emit_event(ArtifactEventsEnum.READ)
|
|
48
|
+
return cast(T, obj)
|
|
49
|
+
|
|
50
|
+
def write(self, value: T) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Serialize and write the object to the pickle file.
|
|
53
|
+
"""
|
|
54
|
+
with open(self.path, "wb") as f:
|
|
55
|
+
pickle.dump(value, f)
|
|
56
|
+
self._emit_event(ArtifactEventsEnum.WRITE)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
"""
|
|
19
|
+
ArtifactEvent. Emitted when an artifact is created, updated, or deleted.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from enum import Enum
|
|
23
|
+
from typing import ClassVar
|
|
24
|
+
|
|
25
|
+
from horus_runtime.event.base import BaseEvent
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ArtifactEventsEnum(Enum):
|
|
29
|
+
"""
|
|
30
|
+
Standard artifact events.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
DELETE = "deleted"
|
|
34
|
+
PACKAGE = "packaged"
|
|
35
|
+
UNPACKAGE = "unpackaged"
|
|
36
|
+
READ = "read"
|
|
37
|
+
WRITE = "written"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ArtifactEvent(BaseEvent):
|
|
41
|
+
"""
|
|
42
|
+
Event emitted when an artifact is created, updated, or deleted.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
add_to_registry: ClassVar[bool] = True
|
|
46
|
+
event_type: str = "artifact_event"
|
|
47
|
+
event_name: ArtifactEventsEnum
|
|
48
|
+
artifact_id: str
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
"""
|
|
19
|
+
Logging subscriber for horus-runtime events.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from horus_runtime.event.base import BaseEvent
|
|
23
|
+
from horus_runtime.event.subscriber import BaseEventSubscriber
|
|
24
|
+
from horus_runtime.logging import horus_logger
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LogsSubscriber(BaseEventSubscriber):
|
|
28
|
+
"""
|
|
29
|
+
A simple event subscriber that logs all events using loguru.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
subscriber_type: str = "loguru"
|
|
33
|
+
|
|
34
|
+
def setup(self) -> None:
|
|
35
|
+
"""
|
|
36
|
+
No setup needed for this subscriber.
|
|
37
|
+
"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
def handle(self, event: BaseEvent) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Handle an incoming event by logging it using loguru's bind.
|
|
43
|
+
"""
|
|
44
|
+
# Secure the message by escaping any potential markup characters
|
|
45
|
+
safe_message = (event.message or "").replace("<", r"\<")
|
|
46
|
+
|
|
47
|
+
horus_logger.log.opt(colors=True).log(
|
|
48
|
+
event.level,
|
|
49
|
+
"<cyan>[{source}]</cyan> <yellow>[{event_type}]</yellow> "
|
|
50
|
+
"{safe_message}",
|
|
51
|
+
source=event.source,
|
|
52
|
+
event_type=event.event_type,
|
|
53
|
+
safe_message=safe_message,
|
|
54
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
"""
|
|
19
|
+
HorusTaskEvent. Emitted when a HorusTask is executed.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from horus_runtime.event.base import BaseEvent
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class HorusTaskEvent(BaseEvent):
|
|
26
|
+
"""
|
|
27
|
+
Event emitted when a HorusTask is executed.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
event_type: str = "horus_task_event"
|
|
31
|
+
|
|
32
|
+
task_id: str | None = None
|
|
33
|
+
task_name: str
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
"""
|
|
19
|
+
HorusTaskEvent. Emitted when a HorusTask is executed.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from horus_runtime.event.base import BaseEvent
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class HorusWorkflowEvent(BaseEvent):
|
|
26
|
+
"""
|
|
27
|
+
Event emitted when a HorusWorkflow is executed.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
event_type: str = "horus_workflow_event"
|
|
31
|
+
|
|
32
|
+
workflow_name: str
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
"""
|
|
19
|
+
Defines the PythonExecExecutor class, which represents an executor that runs a
|
|
20
|
+
a Python code task in-process in the Horus runtime.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
24
|
+
|
|
25
|
+
from horus_builtin.runtime.python_string import PythonCodeStringRuntime
|
|
26
|
+
from horus_runtime.context import HorusContext
|
|
27
|
+
from horus_runtime.core.executor.base import BaseExecutor, RuntimeFilterType
|
|
28
|
+
from horus_runtime.i18n import tr as _
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from horus_runtime.core.task.base import BaseTask
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PythonExecExecutor(BaseExecutor):
|
|
35
|
+
"""
|
|
36
|
+
Run the tasks locally in the horus-runtime instance.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
kind: str = "python"
|
|
40
|
+
kind_name: ClassVar[str] = "Python Exec"
|
|
41
|
+
kind_description: ClassVar[str] = _(
|
|
42
|
+
"Executes a Python code snippet in-process within the Horus runtime."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
runtimes: ClassVar[RuntimeFilterType] = (PythonCodeStringRuntime,)
|
|
46
|
+
|
|
47
|
+
async def _execute(self, task: "BaseTask") -> None:
|
|
48
|
+
"""
|
|
49
|
+
Runs the task in-process by executing the Python code specified in the
|
|
50
|
+
task's runtime.
|
|
51
|
+
"""
|
|
52
|
+
assert isinstance(task.runtime, PythonCodeStringRuntime)
|
|
53
|
+
code = await task.runtime.setup_runtime(task)
|
|
54
|
+
|
|
55
|
+
ctx = HorusContext.get_context()
|
|
56
|
+
|
|
57
|
+
scope = {
|
|
58
|
+
"ctx": ctx,
|
|
59
|
+
"task": task,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Security Warning: using exec to execute arbitrary code can be
|
|
63
|
+
# dangerous and should be done with caution.
|
|
64
|
+
exec(code, scope)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#
|
|
2
|
+
# horus-runtime
|
|
3
|
+
# Copyright (C) 2026 Temple Compute
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
#
|
|
18
|
+
"""
|
|
19
|
+
Python executor for in-memory workflows in horus-runtime. (Function
|
|
20
|
+
executor).
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from inspect import isawaitable
|
|
24
|
+
from typing import ClassVar
|
|
25
|
+
|
|
26
|
+
from horus_builtin.runtime.python import PythonFunctionRuntime
|
|
27
|
+
from horus_runtime.core.executor.base import BaseExecutor, RuntimeFilterType
|
|
28
|
+
from horus_runtime.core.task.base import BaseTask
|
|
29
|
+
from horus_runtime.i18n import tr as _
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PythonFunctionExecutor(BaseExecutor):
|
|
33
|
+
"""
|
|
34
|
+
Executor for running Python functions in-memory.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
kind: str = "python_function"
|
|
38
|
+
kind_name: ClassVar[str] = "Python Function Executor"
|
|
39
|
+
kind_description: ClassVar[str] = _(
|
|
40
|
+
"Executes a Python function in-memory within the Horus runtime."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
runtimes: ClassVar[RuntimeFilterType] = (PythonFunctionRuntime,)
|
|
44
|
+
|
|
45
|
+
async def _execute(self, task: "BaseTask") -> None:
|
|
46
|
+
"""
|
|
47
|
+
Executes the Python function specified in the task's runtime.
|
|
48
|
+
"""
|
|
49
|
+
assert isinstance(task.runtime, PythonFunctionRuntime)
|
|
50
|
+
|
|
51
|
+
# Get the function and resolved arguments from the runtime.
|
|
52
|
+
func, args = await task.runtime.setup_runtime(task)
|
|
53
|
+
|
|
54
|
+
result = func(**args)
|
|
55
|
+
|
|
56
|
+
if isawaitable(result):
|
|
57
|
+
await result
|