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.
Files changed (85) hide show
  1. donna/__init__.py +1 -0
  2. donna/artifacts/__init__.py +0 -0
  3. donna/artifacts/usage/__init__.py +0 -0
  4. donna/artifacts/usage/artifacts.md +224 -0
  5. donna/artifacts/usage/cli.md +117 -0
  6. donna/artifacts/usage/worlds.md +36 -0
  7. donna/artifacts/work/__init__.py +0 -0
  8. donna/artifacts/work/do_it.md +142 -0
  9. donna/artifacts/work/do_it_fast.md +98 -0
  10. donna/artifacts/work/planning.md +245 -0
  11. donna/cli/__init__.py +0 -0
  12. donna/cli/__main__.py +6 -0
  13. donna/cli/application.py +17 -0
  14. donna/cli/commands/__init__.py +0 -0
  15. donna/cli/commands/artifacts.py +110 -0
  16. donna/cli/commands/projects.py +49 -0
  17. donna/cli/commands/sessions.py +77 -0
  18. donna/cli/types.py +138 -0
  19. donna/cli/utils.py +53 -0
  20. donna/core/__init__.py +0 -0
  21. donna/core/entities.py +27 -0
  22. donna/core/errors.py +126 -0
  23. donna/core/result.py +99 -0
  24. donna/core/utils.py +37 -0
  25. donna/domain/__init__.py +0 -0
  26. donna/domain/errors.py +47 -0
  27. donna/domain/ids.py +497 -0
  28. donna/lib/__init__.py +21 -0
  29. donna/lib/sources.py +5 -0
  30. donna/lib/worlds.py +7 -0
  31. donna/machine/__init__.py +0 -0
  32. donna/machine/action_requests.py +50 -0
  33. donna/machine/artifacts.py +200 -0
  34. donna/machine/changes.py +91 -0
  35. donna/machine/errors.py +122 -0
  36. donna/machine/operations.py +31 -0
  37. donna/machine/primitives.py +77 -0
  38. donna/machine/sessions.py +215 -0
  39. donna/machine/state.py +244 -0
  40. donna/machine/tasks.py +89 -0
  41. donna/machine/templates.py +83 -0
  42. donna/primitives/__init__.py +1 -0
  43. donna/primitives/artifacts/__init__.py +0 -0
  44. donna/primitives/artifacts/specification.py +20 -0
  45. donna/primitives/artifacts/workflow.py +195 -0
  46. donna/primitives/directives/__init__.py +0 -0
  47. donna/primitives/directives/goto.py +44 -0
  48. donna/primitives/directives/task_variable.py +73 -0
  49. donna/primitives/directives/view.py +45 -0
  50. donna/primitives/operations/__init__.py +0 -0
  51. donna/primitives/operations/finish_workflow.py +37 -0
  52. donna/primitives/operations/request_action.py +89 -0
  53. donna/primitives/operations/run_script.py +250 -0
  54. donna/protocol/__init__.py +0 -0
  55. donna/protocol/cell_shortcuts.py +9 -0
  56. donna/protocol/cells.py +44 -0
  57. donna/protocol/errors.py +17 -0
  58. donna/protocol/formatters/__init__.py +0 -0
  59. donna/protocol/formatters/automation.py +25 -0
  60. donna/protocol/formatters/base.py +15 -0
  61. donna/protocol/formatters/human.py +36 -0
  62. donna/protocol/formatters/llm.py +39 -0
  63. donna/protocol/modes.py +40 -0
  64. donna/protocol/nodes.py +59 -0
  65. donna/world/__init__.py +0 -0
  66. donna/world/artifacts.py +122 -0
  67. donna/world/artifacts_discovery.py +90 -0
  68. donna/world/config.py +198 -0
  69. donna/world/errors.py +232 -0
  70. donna/world/initialization.py +42 -0
  71. donna/world/markdown.py +267 -0
  72. donna/world/sources/__init__.py +1 -0
  73. donna/world/sources/base.py +62 -0
  74. donna/world/sources/markdown.py +260 -0
  75. donna/world/templates.py +181 -0
  76. donna/world/tmp.py +33 -0
  77. donna/world/worlds/__init__.py +0 -0
  78. donna/world/worlds/base.py +68 -0
  79. donna/world/worlds/filesystem.py +189 -0
  80. donna/world/worlds/python.py +196 -0
  81. donna-0.2.0.dist-info/METADATA +44 -0
  82. donna-0.2.0.dist-info/RECORD +85 -0
  83. donna-0.2.0.dist-info/WHEEL +4 -0
  84. donna-0.2.0.dist-info/entry_points.txt +3 -0
  85. 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)
@@ -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
@@ -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)