donna 0.2.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.
- donna/__init__.py +1 -0
- donna/artifacts/__init__.py +0 -0
- donna/artifacts/usage/__init__.py +0 -0
- donna/artifacts/usage/artifacts.md +224 -0
- donna/artifacts/usage/cli.md +117 -0
- donna/artifacts/usage/worlds.md +36 -0
- donna/artifacts/work/__init__.py +0 -0
- donna/artifacts/work/do_it.md +142 -0
- donna/artifacts/work/do_it_fast.md +98 -0
- donna/artifacts/work/planning.md +245 -0
- donna/cli/__init__.py +0 -0
- donna/cli/__main__.py +6 -0
- donna/cli/application.py +17 -0
- donna/cli/commands/__init__.py +0 -0
- donna/cli/commands/artifacts.py +110 -0
- donna/cli/commands/projects.py +49 -0
- donna/cli/commands/sessions.py +77 -0
- donna/cli/types.py +138 -0
- donna/cli/utils.py +53 -0
- donna/core/__init__.py +0 -0
- donna/core/entities.py +27 -0
- donna/core/errors.py +126 -0
- donna/core/result.py +99 -0
- donna/core/utils.py +37 -0
- donna/domain/__init__.py +0 -0
- donna/domain/errors.py +47 -0
- donna/domain/ids.py +497 -0
- donna/lib/__init__.py +21 -0
- donna/lib/sources.py +5 -0
- donna/lib/worlds.py +7 -0
- donna/machine/__init__.py +0 -0
- donna/machine/action_requests.py +50 -0
- donna/machine/artifacts.py +200 -0
- donna/machine/changes.py +91 -0
- donna/machine/errors.py +122 -0
- donna/machine/operations.py +31 -0
- donna/machine/primitives.py +77 -0
- donna/machine/sessions.py +215 -0
- donna/machine/state.py +244 -0
- donna/machine/tasks.py +89 -0
- donna/machine/templates.py +83 -0
- donna/primitives/__init__.py +1 -0
- donna/primitives/artifacts/__init__.py +0 -0
- donna/primitives/artifacts/specification.py +20 -0
- donna/primitives/artifacts/workflow.py +195 -0
- donna/primitives/directives/__init__.py +0 -0
- donna/primitives/directives/goto.py +44 -0
- donna/primitives/directives/task_variable.py +73 -0
- donna/primitives/directives/view.py +45 -0
- donna/primitives/operations/__init__.py +0 -0
- donna/primitives/operations/finish_workflow.py +37 -0
- donna/primitives/operations/request_action.py +89 -0
- donna/primitives/operations/run_script.py +250 -0
- donna/protocol/__init__.py +0 -0
- donna/protocol/cell_shortcuts.py +9 -0
- donna/protocol/cells.py +44 -0
- donna/protocol/errors.py +17 -0
- donna/protocol/formatters/__init__.py +0 -0
- donna/protocol/formatters/automation.py +25 -0
- donna/protocol/formatters/base.py +15 -0
- donna/protocol/formatters/human.py +36 -0
- donna/protocol/formatters/llm.py +39 -0
- donna/protocol/modes.py +40 -0
- donna/protocol/nodes.py +59 -0
- donna/world/__init__.py +0 -0
- donna/world/artifacts.py +122 -0
- donna/world/artifacts_discovery.py +90 -0
- donna/world/config.py +198 -0
- donna/world/errors.py +232 -0
- donna/world/initialization.py +42 -0
- donna/world/markdown.py +267 -0
- donna/world/sources/__init__.py +1 -0
- donna/world/sources/base.py +62 -0
- donna/world/sources/markdown.py +260 -0
- donna/world/templates.py +181 -0
- donna/world/tmp.py +33 -0
- donna/world/worlds/__init__.py +0 -0
- donna/world/worlds/base.py +68 -0
- donna/world/worlds/filesystem.py +189 -0
- donna/world/worlds/python.py +196 -0
- donna-0.2.0.dist-info/METADATA +44 -0
- donna-0.2.0.dist-info/RECORD +85 -0
- donna-0.2.0.dist-info/WHEEL +4 -0
- donna-0.2.0.dist-info/entry_points.txt +3 -0
- donna-0.2.0.dist-info/licenses/LICENSE +28 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any
|
|
2
|
+
|
|
3
|
+
from donna.core.entities import BaseEntity
|
|
4
|
+
from donna.core.errors import ErrorsList
|
|
5
|
+
from donna.core.result import Err, Ok, Result, unwrap_to_error
|
|
6
|
+
from donna.domain.ids import ArtifactSectionId, FullArtifactId, FullArtifactSectionId, PythonImportPath
|
|
7
|
+
from donna.machine.errors import (
|
|
8
|
+
ArtifactPrimarySectionMissing,
|
|
9
|
+
ArtifactSectionNotFound,
|
|
10
|
+
MultiplePrimarySectionsError,
|
|
11
|
+
)
|
|
12
|
+
from donna.protocol.cells import Cell
|
|
13
|
+
from donna.protocol.nodes import Node
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from donna.world.artifacts import ArtifactRenderContext
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ArtifactSectionConfig(BaseEntity):
|
|
20
|
+
id: ArtifactSectionId
|
|
21
|
+
kind: PythonImportPath
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ArtifactSectionMeta(BaseEntity):
|
|
25
|
+
def cells_meta(self) -> dict[str, Any]:
|
|
26
|
+
return {}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ArtifactSection(BaseEntity):
|
|
30
|
+
id: ArtifactSectionId
|
|
31
|
+
artifact_id: FullArtifactId
|
|
32
|
+
kind: PythonImportPath
|
|
33
|
+
title: str
|
|
34
|
+
description: str
|
|
35
|
+
primary: bool = False
|
|
36
|
+
|
|
37
|
+
meta: ArtifactSectionMeta
|
|
38
|
+
|
|
39
|
+
def node(self) -> "ArtifactSectionNode":
|
|
40
|
+
return ArtifactSectionNode(self)
|
|
41
|
+
|
|
42
|
+
def markdown_blocks(self) -> list[str]:
|
|
43
|
+
return [f"## {self.title}", self.description]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Artifact(BaseEntity):
|
|
47
|
+
id: FullArtifactId
|
|
48
|
+
|
|
49
|
+
sections: list[ArtifactSection]
|
|
50
|
+
|
|
51
|
+
def _primary_sections(self) -> list[ArtifactSection]:
|
|
52
|
+
return [section for section in self.sections if section.primary]
|
|
53
|
+
|
|
54
|
+
def primary_section(self) -> Result[ArtifactSection, ErrorsList]:
|
|
55
|
+
primary_sections = self._primary_sections()
|
|
56
|
+
if len(primary_sections) == 0:
|
|
57
|
+
return Err([ArtifactPrimarySectionMissing(artifact_id=self.id)])
|
|
58
|
+
if len(primary_sections) > 1:
|
|
59
|
+
return Err(
|
|
60
|
+
[
|
|
61
|
+
MultiplePrimarySectionsError(
|
|
62
|
+
artifact_id=self.id,
|
|
63
|
+
primary_sections=sorted(section.id for section in primary_sections),
|
|
64
|
+
)
|
|
65
|
+
]
|
|
66
|
+
)
|
|
67
|
+
return Ok(primary_sections[0])
|
|
68
|
+
|
|
69
|
+
def validate_artifact(self) -> Result[None, ErrorsList]: # noqa: CCR001
|
|
70
|
+
from donna.machine.primitives import resolve_primitive
|
|
71
|
+
|
|
72
|
+
primary_sections = self._primary_sections()
|
|
73
|
+
|
|
74
|
+
errors: ErrorsList = []
|
|
75
|
+
|
|
76
|
+
if len(primary_sections) == 0:
|
|
77
|
+
errors.append(ArtifactPrimarySectionMissing(artifact_id=self.id))
|
|
78
|
+
elif len(primary_sections) > 1:
|
|
79
|
+
errors.append(
|
|
80
|
+
MultiplePrimarySectionsError(
|
|
81
|
+
artifact_id=self.id,
|
|
82
|
+
primary_sections=sorted(section.id for section in primary_sections),
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
for section in self.sections:
|
|
87
|
+
primitive_result = resolve_primitive(section.kind)
|
|
88
|
+
if primitive_result.is_err():
|
|
89
|
+
errors.extend(primitive_result.unwrap_err())
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
primitive = primitive_result.unwrap()
|
|
93
|
+
result = primitive.validate_section(self, section.id)
|
|
94
|
+
|
|
95
|
+
if result.is_ok():
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
errors.extend(result.unwrap_err())
|
|
99
|
+
|
|
100
|
+
if errors:
|
|
101
|
+
return Err(errors)
|
|
102
|
+
|
|
103
|
+
return Ok(None)
|
|
104
|
+
|
|
105
|
+
def get_section(self, section_id: ArtifactSectionId | None) -> Result[ArtifactSection, ErrorsList]:
|
|
106
|
+
if section_id is None:
|
|
107
|
+
return self.primary_section()
|
|
108
|
+
for section in self.sections:
|
|
109
|
+
if section.id == section_id:
|
|
110
|
+
return Ok(section)
|
|
111
|
+
return Err([ArtifactSectionNotFound(artifact_id=self.id, section_id=section_id)])
|
|
112
|
+
|
|
113
|
+
def node(self) -> "ArtifactNode":
|
|
114
|
+
return ArtifactNode(self)
|
|
115
|
+
|
|
116
|
+
@unwrap_to_error
|
|
117
|
+
def markdown_blocks(self) -> Result[list[str], ErrorsList]:
|
|
118
|
+
primary_section = self.primary_section().unwrap()
|
|
119
|
+
blocks = [f"# {primary_section.title}", primary_section.description]
|
|
120
|
+
|
|
121
|
+
for section in self.sections:
|
|
122
|
+
if section.primary:
|
|
123
|
+
continue
|
|
124
|
+
blocks.extend(section.markdown_blocks())
|
|
125
|
+
|
|
126
|
+
return Ok(blocks)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ArtifactNode(Node):
|
|
130
|
+
__slots__ = ("_artifact",)
|
|
131
|
+
|
|
132
|
+
def __init__(self, artifact: Artifact) -> None:
|
|
133
|
+
self._artifact = artifact
|
|
134
|
+
|
|
135
|
+
def status(self) -> Cell:
|
|
136
|
+
primary_section_result = self._artifact.primary_section()
|
|
137
|
+
if primary_section_result.is_err():
|
|
138
|
+
return primary_section_result.unwrap_err()[0].node().status()
|
|
139
|
+
|
|
140
|
+
primary_section = primary_section_result.unwrap()
|
|
141
|
+
return Cell.build_meta(
|
|
142
|
+
kind="artifact_status",
|
|
143
|
+
artifact_id=str(self._artifact.id),
|
|
144
|
+
artifact_kind=str(primary_section.kind),
|
|
145
|
+
artifact_title=primary_section.title,
|
|
146
|
+
artifact_description=primary_section.description,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def info(self) -> Cell:
|
|
150
|
+
primary_section_result = self._artifact.primary_section()
|
|
151
|
+
if primary_section_result.is_err():
|
|
152
|
+
return primary_section_result.unwrap_err()[0].node().info()
|
|
153
|
+
|
|
154
|
+
primary_section = primary_section_result.unwrap()
|
|
155
|
+
blocks_result = self._artifact.markdown_blocks()
|
|
156
|
+
if blocks_result.is_err():
|
|
157
|
+
return blocks_result.unwrap_err()[0].node().info()
|
|
158
|
+
|
|
159
|
+
return Cell.build_markdown(
|
|
160
|
+
kind="artifact_info",
|
|
161
|
+
content="\n".join(blocks_result.unwrap()),
|
|
162
|
+
artifact_id=str(self._artifact.id),
|
|
163
|
+
artifact_kind=str(primary_section.kind),
|
|
164
|
+
artifact_title=primary_section.title,
|
|
165
|
+
artifact_description=primary_section.description,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def components(self) -> list["Node"]:
|
|
169
|
+
return [ArtifactSectionNode(section) for section in self._artifact.sections]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class ArtifactSectionNode(Node):
|
|
173
|
+
__slots__ = ("_section",)
|
|
174
|
+
|
|
175
|
+
def __init__(self, section: ArtifactSection) -> None:
|
|
176
|
+
self._section = section
|
|
177
|
+
|
|
178
|
+
def status(self) -> Cell:
|
|
179
|
+
return Cell.build_markdown(
|
|
180
|
+
kind="artifact_section_status",
|
|
181
|
+
content="\n".join(self._section.markdown_blocks()),
|
|
182
|
+
artifact_id=str(self._section.artifact_id),
|
|
183
|
+
section_id=str(self._section.id),
|
|
184
|
+
section_kind=str(self._section.kind),
|
|
185
|
+
section_primary=self._section.primary,
|
|
186
|
+
**self._section.meta.cells_meta(),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@unwrap_to_error
|
|
191
|
+
def resolve(
|
|
192
|
+
target_id: FullArtifactSectionId, render_context: "ArtifactRenderContext"
|
|
193
|
+
) -> Result[ArtifactSection, ErrorsList]:
|
|
194
|
+
from donna.world import artifacts as world_artifacts
|
|
195
|
+
|
|
196
|
+
artifact = world_artifacts.load_artifact(target_id.full_artifact_id, render_context).unwrap()
|
|
197
|
+
|
|
198
|
+
section = artifact.get_section(target_id.local_id).unwrap()
|
|
199
|
+
|
|
200
|
+
return Ok(section)
|
donna/machine/changes.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from donna.core.entities import BaseEntity
|
|
5
|
+
from donna.domain.ids import ActionRequestId, FullArtifactSectionId, TaskId, WorkUnitId
|
|
6
|
+
from donna.machine.action_requests import ActionRequest
|
|
7
|
+
from donna.machine.tasks import Task, WorkUnit
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from donna.machine.state import MutableState
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Change(BaseEntity, ABC):
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def apply_to(self, state: "MutableState") -> None: ... # noqa: E704
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ChangeFinishTask(Change):
|
|
19
|
+
task_id: TaskId
|
|
20
|
+
|
|
21
|
+
def apply_to(self, state: "MutableState") -> None:
|
|
22
|
+
state.finish_workflow(self.task_id)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ChangeAddWorkUnit(Change):
|
|
26
|
+
task_id: TaskId
|
|
27
|
+
operation_id: FullArtifactSectionId
|
|
28
|
+
|
|
29
|
+
def apply_to(self, state: "MutableState") -> None:
|
|
30
|
+
work_unit = WorkUnit.build(id=state.next_work_unit_id(), task_id=self.task_id, operation_id=self.operation_id)
|
|
31
|
+
state.add_work_unit(work_unit)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ChangeAddTask(Change):
|
|
35
|
+
operation_id: FullArtifactSectionId
|
|
36
|
+
|
|
37
|
+
def apply_to(self, state: "MutableState") -> None:
|
|
38
|
+
task = Task.build(state.next_task_id())
|
|
39
|
+
|
|
40
|
+
state.add_task(task)
|
|
41
|
+
|
|
42
|
+
work_unit = WorkUnit.build(id=state.next_work_unit_id(), task_id=task.id, operation_id=self.operation_id)
|
|
43
|
+
|
|
44
|
+
state.add_work_unit(work_unit)
|
|
45
|
+
|
|
46
|
+
state.mark_started()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ChangeRemoveTask(Change):
|
|
50
|
+
task_id: TaskId
|
|
51
|
+
|
|
52
|
+
def apply_to(self, state: "MutableState") -> None:
|
|
53
|
+
state.remove_task(self.task_id)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ChangeAddActionRequest(Change):
|
|
57
|
+
action_request: ActionRequest
|
|
58
|
+
|
|
59
|
+
def apply_to(self, state: "MutableState") -> None:
|
|
60
|
+
state.add_action_request(self.action_request)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ChangeRemoveActionRequest(Change):
|
|
64
|
+
action_request_id: ActionRequestId
|
|
65
|
+
|
|
66
|
+
def apply_to(self, state: "MutableState") -> None:
|
|
67
|
+
state.remove_action_request(self.action_request_id)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ChangeRemoveWorkUnit(Change):
|
|
71
|
+
work_unit_id: WorkUnitId
|
|
72
|
+
|
|
73
|
+
def apply_to(self, state: "MutableState") -> None:
|
|
74
|
+
state.remove_work_unit(self.work_unit_id)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ChangeSetTaskContext(Change):
|
|
78
|
+
task_id: TaskId
|
|
79
|
+
key: str
|
|
80
|
+
value: object
|
|
81
|
+
|
|
82
|
+
def apply_to(self, state: "MutableState") -> None:
|
|
83
|
+
target_task: Task | None = None
|
|
84
|
+
for task in state.tasks:
|
|
85
|
+
if task.id == self.task_id:
|
|
86
|
+
target_task = task
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
assert target_task is not None
|
|
90
|
+
|
|
91
|
+
target_task.context[self.key] = self.value
|
donna/machine/errors.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from donna.core import errors as core_errors
|
|
2
|
+
from donna.domain.ids import ActionRequestId, ArtifactSectionId, FullArtifactId, FullArtifactSectionId
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class InternalError(core_errors.InternalError):
|
|
6
|
+
"""Base class for internal errors in donna.machine."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PrimitiveMethodUnsupported(InternalError):
|
|
10
|
+
message: str = "Primitive '{primitive_name}' does not support {method_name}."
|
|
11
|
+
primitive_name: str
|
|
12
|
+
method_name: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SessionStateStatusInvalid(InternalError):
|
|
16
|
+
message: str = "Session state status is invalid."
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EnvironmentError(core_errors.EnvironmentError):
|
|
20
|
+
"""Base class for environment errors in donna.machine."""
|
|
21
|
+
|
|
22
|
+
cell_kind: str = "machine_error"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SessionStateNotInitialized(EnvironmentError):
|
|
26
|
+
code: str = "donna.machine.session_state_not_initialized"
|
|
27
|
+
message: str = "Session state is not initialized."
|
|
28
|
+
ways_to_fix: list[str] = ["Run Donna session start to initialize session state."]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ActionRequestNotFound(EnvironmentError):
|
|
32
|
+
code: str = "donna.machine.action_request_not_found"
|
|
33
|
+
message: str = "Action request `{error.request_id}` was not found in the current session state."
|
|
34
|
+
ways_to_fix: list[str] = ["Use an action request id from `sessions details` output."]
|
|
35
|
+
request_id: ActionRequestId
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class InvalidOperationTransition(EnvironmentError):
|
|
39
|
+
code: str = "donna.machine.invalid_operation_transition"
|
|
40
|
+
message: str = "Operation `{error.operation_id}` cannot transition to `{error.next_operation_id}`."
|
|
41
|
+
ways_to_fix: list[str] = [
|
|
42
|
+
"Check the next operation id for typos.",
|
|
43
|
+
"Use one of the allowed transitions listed in the action request.",
|
|
44
|
+
]
|
|
45
|
+
operation_id: FullArtifactSectionId
|
|
46
|
+
next_operation_id: FullArtifactSectionId
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PrimitiveInvalidImportPath(EnvironmentError):
|
|
50
|
+
code: str = "donna.machine.primitive_invalid_import_path"
|
|
51
|
+
message: str = "Primitive `{error.import_path}` is not a valid import path."
|
|
52
|
+
ways_to_fix: list[str] = ["Use a full Python import path, e.g. `package.module.primitive_instance`."]
|
|
53
|
+
import_path: str
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class PrimitiveModuleNotImportable(EnvironmentError):
|
|
57
|
+
code: str = "donna.machine.primitive_module_not_importable"
|
|
58
|
+
message: str = "Primitive module `{error.module_path}` is not importable."
|
|
59
|
+
ways_to_fix: list[str] = [
|
|
60
|
+
"Check the module path for typos.",
|
|
61
|
+
"Check specifications for the correct primitive to use.",
|
|
62
|
+
(
|
|
63
|
+
"Check the module exists and is importable in the current environment. "
|
|
64
|
+
"If not, ask the developer to install it."
|
|
65
|
+
),
|
|
66
|
+
]
|
|
67
|
+
module_path: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class PrimitiveNotAvailable(EnvironmentError):
|
|
71
|
+
code: str = "donna.machine.primitive_not_available"
|
|
72
|
+
message: str = "Primitive `{error.import_path}` is not available in module `{error.module_path}`."
|
|
73
|
+
ways_to_fix: list[str] = [
|
|
74
|
+
"Check the primitive name for typos.",
|
|
75
|
+
"Ensure you are using the correct module for the desired primitive.",
|
|
76
|
+
"Check specifications for the correct primitive to use.",
|
|
77
|
+
]
|
|
78
|
+
import_path: str
|
|
79
|
+
module_path: str
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class PrimitiveNotPrimitive(EnvironmentError):
|
|
83
|
+
code: str = "donna.machine.primitive_not_primitive"
|
|
84
|
+
message: str = "`{error.import_path}` is not a Primitive instance."
|
|
85
|
+
ways_to_fix: list[str] = [
|
|
86
|
+
"Check the primitive name for typos.",
|
|
87
|
+
"Ensure you are using the correct module for the desired primitive.",
|
|
88
|
+
"Check specifications for the correct primitive to use.",
|
|
89
|
+
"Ensure the referenced object is a `donna.machine.primitives.Primitive` instance.",
|
|
90
|
+
]
|
|
91
|
+
import_path: str
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ArtifactValidationError(EnvironmentError):
|
|
95
|
+
cell_kind: str = "artifact_validation_error"
|
|
96
|
+
artifact_id: FullArtifactId
|
|
97
|
+
section_id: ArtifactSectionId | None = None
|
|
98
|
+
|
|
99
|
+
def content_intro(self) -> str:
|
|
100
|
+
if self.section_id:
|
|
101
|
+
return f"Error in artifact '{self.artifact_id}', section '{self.section_id}'"
|
|
102
|
+
|
|
103
|
+
return f"Error in artifact '{self.artifact_id}'"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class MultiplePrimarySectionsError(ArtifactValidationError):
|
|
107
|
+
code: str = "donna.artifacts.multiple_primary_sections"
|
|
108
|
+
message: str = "Artifact must have exactly one primary section, found multiple: `{error.primary_sections}`"
|
|
109
|
+
ways_to_fix: list[str] = ["Keep a single primary section in the artifact."]
|
|
110
|
+
primary_sections: list[ArtifactSectionId]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class ArtifactPrimarySectionMissing(ArtifactValidationError):
|
|
114
|
+
code: str = "donna.artifacts.primary_section_missing"
|
|
115
|
+
message: str = "Artifact must have exactly one primary section, found none."
|
|
116
|
+
ways_to_fix: list[str] = ["Keep a single primary section in the artifact."]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class ArtifactSectionNotFound(ArtifactValidationError):
|
|
120
|
+
code: str = "donna.artifacts.section_not_found"
|
|
121
|
+
message: str = "Section `{error.section_id}` is not available in artifact `{error.artifact_id}`."
|
|
122
|
+
ways_to_fix: list[str] = ["Check the section id for typos.", "Ensure the section exists in the artifact."]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
from typing import TYPE_CHECKING, Any
|
|
3
|
+
|
|
4
|
+
from donna.domain.ids import ArtifactSectionId
|
|
5
|
+
from donna.machine.artifacts import ArtifactSectionConfig, ArtifactSectionMeta
|
|
6
|
+
from donna.machine.primitives import Primitive
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FsmMode(enum.Enum):
|
|
13
|
+
start = "start"
|
|
14
|
+
normal = "normal"
|
|
15
|
+
final = "final"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class OperationKind(Primitive):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class OperationConfig(ArtifactSectionConfig):
|
|
23
|
+
fsm_mode: FsmMode = FsmMode.normal
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OperationMeta(ArtifactSectionMeta):
|
|
27
|
+
fsm_mode: FsmMode = FsmMode.normal
|
|
28
|
+
allowed_transtions: set[ArtifactSectionId]
|
|
29
|
+
|
|
30
|
+
def cells_meta(self) -> dict[str, Any]:
|
|
31
|
+
return {"fsm_mode": self.fsm_mode.value, "allowed_transtions": [str(t) for t in self.allowed_transtions]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Iterable
|
|
3
|
+
|
|
4
|
+
from jinja2.runtime import Context
|
|
5
|
+
|
|
6
|
+
from donna.core.entities import BaseEntity
|
|
7
|
+
from donna.core.errors import ErrorsList
|
|
8
|
+
from donna.core.result import Err, Ok, Result, unwrap_to_error
|
|
9
|
+
from donna.domain.ids import ArtifactSectionId, PythonImportPath
|
|
10
|
+
from donna.machine import errors as machine_errors
|
|
11
|
+
from donna.machine.artifacts import ArtifactSectionConfig
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from donna.machine.artifacts import Artifact, ArtifactSection
|
|
15
|
+
from donna.machine.changes import Change
|
|
16
|
+
from donna.machine.tasks import Task, WorkUnit
|
|
17
|
+
from donna.world.config import SourceConfig as SourceConfigModel
|
|
18
|
+
from donna.world.config import WorldConfig
|
|
19
|
+
from donna.world.sources.base import SourceConfig as SourceConfigValue
|
|
20
|
+
from donna.world.worlds.base import World
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# TODO: Currently is is a kind of God interface. It is convinient for now.
|
|
24
|
+
# However, in future we should move these methods into specific subclasses.
|
|
25
|
+
class Primitive(BaseEntity):
|
|
26
|
+
config_class: ClassVar[type[ArtifactSectionConfig]] = ArtifactSectionConfig
|
|
27
|
+
|
|
28
|
+
def validate_section(self, artifact: "Artifact", section_id: ArtifactSectionId) -> Result[None, ErrorsList]:
|
|
29
|
+
return Ok(None)
|
|
30
|
+
|
|
31
|
+
def execute_section(self, task: "Task", unit: "WorkUnit", section: "ArtifactSection") -> Iterable["Change"]:
|
|
32
|
+
raise machine_errors.PrimitiveMethodUnsupported(
|
|
33
|
+
primitive_name=self.__class__.__name__, method_name="execute_section()"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def apply_directive(self, context: Context, *argv: Any, **kwargs: Any) -> Result[Any, ErrorsList]:
|
|
37
|
+
raise machine_errors.PrimitiveMethodUnsupported(
|
|
38
|
+
primitive_name=self.__class__.__name__, method_name="apply_directive()"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def construct_world(self, config: "WorldConfig") -> "World":
|
|
42
|
+
raise machine_errors.PrimitiveMethodUnsupported(
|
|
43
|
+
primitive_name=self.__class__.__name__, method_name="construct_world()"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def construct_source(self, config: "SourceConfigModel") -> "SourceConfigValue":
|
|
47
|
+
raise machine_errors.PrimitiveMethodUnsupported(
|
|
48
|
+
primitive_name=self.__class__.__name__, method_name="construct_source()"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@unwrap_to_error
|
|
53
|
+
def resolve_primitive(primitive_id: PythonImportPath | str) -> Result[Primitive, ErrorsList]: # noqa: CCR001
|
|
54
|
+
if isinstance(primitive_id, PythonImportPath):
|
|
55
|
+
import_path = str(primitive_id)
|
|
56
|
+
else:
|
|
57
|
+
import_path = str(PythonImportPath.parse(primitive_id).unwrap())
|
|
58
|
+
|
|
59
|
+
if "." not in import_path:
|
|
60
|
+
return Err([machine_errors.PrimitiveInvalidImportPath(import_path=import_path)])
|
|
61
|
+
|
|
62
|
+
module_path, primitive_name = import_path.rsplit(".", maxsplit=1)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
module = importlib.import_module(module_path)
|
|
66
|
+
except ModuleNotFoundError:
|
|
67
|
+
return Err([machine_errors.PrimitiveModuleNotImportable(module_path=module_path)])
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
primitive = getattr(module, primitive_name)
|
|
71
|
+
except AttributeError:
|
|
72
|
+
return Err([machine_errors.PrimitiveNotAvailable(import_path=import_path, module_path=module_path)])
|
|
73
|
+
|
|
74
|
+
if not isinstance(primitive, Primitive):
|
|
75
|
+
return Err([machine_errors.PrimitiveNotPrimitive(import_path=import_path)])
|
|
76
|
+
|
|
77
|
+
return Ok(primitive)
|