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
donna/domain/ids.py
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
from typing import Any, Generic, Sequence, TypeVar
|
|
2
|
+
|
|
3
|
+
from pydantic_core import PydanticCustomError, core_schema
|
|
4
|
+
|
|
5
|
+
from donna.core.errors import ErrorsList
|
|
6
|
+
from donna.core.result import Err, Ok, Result
|
|
7
|
+
from donna.domain import errors as domain_errors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _id_crc(number: int) -> str:
|
|
11
|
+
"""Translates int into a compact string representation with a-zA-Z0-9 characters."""
|
|
12
|
+
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
13
|
+
base = len(charset)
|
|
14
|
+
|
|
15
|
+
if number == 0:
|
|
16
|
+
return charset[0]
|
|
17
|
+
|
|
18
|
+
chars = []
|
|
19
|
+
while number > 0:
|
|
20
|
+
number, rem = divmod(number, base)
|
|
21
|
+
chars.append(charset[rem])
|
|
22
|
+
|
|
23
|
+
chars.reverse()
|
|
24
|
+
return "".join(chars)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _match_pattern_parts(pattern_parts: Sequence[str], value_parts: Sequence[str]) -> bool: # noqa: CCR001
|
|
28
|
+
def match_at(p_index: int, v_index: int) -> bool: # noqa: CCR001
|
|
29
|
+
while True:
|
|
30
|
+
if p_index >= len(pattern_parts):
|
|
31
|
+
return v_index >= len(value_parts)
|
|
32
|
+
|
|
33
|
+
token = pattern_parts[p_index]
|
|
34
|
+
|
|
35
|
+
if token == "**": # noqa: S105
|
|
36
|
+
for next_index in range(v_index, len(value_parts) + 1):
|
|
37
|
+
if match_at(p_index + 1, next_index):
|
|
38
|
+
return True
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
if v_index >= len(value_parts):
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
if token != "*" and token != value_parts[v_index]: # noqa: S105
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
p_index += 1
|
|
48
|
+
v_index += 1
|
|
49
|
+
|
|
50
|
+
return match_at(0, 0)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _stringify_value(value: Any) -> str:
|
|
54
|
+
if isinstance(value, str):
|
|
55
|
+
return value
|
|
56
|
+
return repr(value)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _pydantic_type_error(type_name: str, value: Any) -> PydanticCustomError:
|
|
60
|
+
return PydanticCustomError(
|
|
61
|
+
"type_error",
|
|
62
|
+
"{type_name} must be a str, got {actual_type}",
|
|
63
|
+
{"type_name": type_name, "actual_type": type(value).__name__},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _pydantic_value_error(type_name: str, value: Any) -> PydanticCustomError:
|
|
68
|
+
return PydanticCustomError(
|
|
69
|
+
"value_error",
|
|
70
|
+
"Invalid {type_name}: {value}",
|
|
71
|
+
{"type_name": type_name, "value": _stringify_value(value)},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
TParsed = TypeVar("TParsed")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _invalid_format(id_type: str, value: Any) -> Result[TParsed, ErrorsList]:
|
|
79
|
+
return Err([domain_errors.InvalidIdFormat(id_type=id_type, value=_stringify_value(value))])
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _invalid_pattern(id_type: str, value: Any) -> Result[TParsed, ErrorsList]:
|
|
83
|
+
return Err([domain_errors.InvalidIdPattern(id_type=id_type, value=_stringify_value(value))])
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class InternalId(str):
|
|
87
|
+
__slots__ = ()
|
|
88
|
+
|
|
89
|
+
def __new__(cls, value: str) -> "InternalId":
|
|
90
|
+
if not cls.validate(value):
|
|
91
|
+
raise domain_errors.InvalidInternalId(value=value)
|
|
92
|
+
|
|
93
|
+
return super().__new__(cls, value)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def build(cls, prefix: str, value: int) -> "InternalId":
|
|
97
|
+
return cls(f"{prefix}-{value}-{_id_crc(value)}")
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def validate(cls, id: str) -> bool:
|
|
101
|
+
if not isinstance(id, str):
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
_prefix, value, crc = id.rsplit("-", maxsplit=2)
|
|
106
|
+
except ValueError:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
expected_crc = _id_crc(int(value))
|
|
111
|
+
except ValueError:
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
return crc == expected_crc
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_schema.CoreSchema: # noqa: CCR001
|
|
118
|
+
|
|
119
|
+
def validate(v: Any) -> "InternalId":
|
|
120
|
+
if isinstance(v, cls):
|
|
121
|
+
return v
|
|
122
|
+
|
|
123
|
+
if not isinstance(v, str):
|
|
124
|
+
raise _pydantic_type_error(cls.__name__, v)
|
|
125
|
+
|
|
126
|
+
if not cls.validate(v):
|
|
127
|
+
raise _pydantic_value_error(cls.__name__, v)
|
|
128
|
+
|
|
129
|
+
return cls(v)
|
|
130
|
+
|
|
131
|
+
return core_schema.json_or_python_schema(
|
|
132
|
+
json_schema=core_schema.str_schema(),
|
|
133
|
+
python_schema=core_schema.no_info_plain_validator_function(validate),
|
|
134
|
+
serialization=core_schema.to_string_ser_schema(),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class WorkUnitId(InternalId):
|
|
139
|
+
__slots__ = ()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class ActionRequestId(InternalId):
|
|
143
|
+
__slots__ = ()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class TaskId(InternalId):
|
|
147
|
+
__slots__ = ()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class Identifier(str):
|
|
151
|
+
__slots__ = ()
|
|
152
|
+
|
|
153
|
+
def __new__(cls, value: str) -> "Identifier":
|
|
154
|
+
if not cls.validate(value):
|
|
155
|
+
raise domain_errors.InvalidIdentifier(value=value)
|
|
156
|
+
|
|
157
|
+
return super().__new__(cls, value)
|
|
158
|
+
|
|
159
|
+
@classmethod
|
|
160
|
+
def validate(cls, value: str) -> bool:
|
|
161
|
+
if not isinstance(value, str):
|
|
162
|
+
return False
|
|
163
|
+
return value.isidentifier()
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_schema.CoreSchema: # noqa: CCR001
|
|
167
|
+
|
|
168
|
+
def validate(v: Any) -> "Identifier":
|
|
169
|
+
if isinstance(v, cls):
|
|
170
|
+
return v
|
|
171
|
+
|
|
172
|
+
if not isinstance(v, str):
|
|
173
|
+
raise _pydantic_type_error(cls.__name__, v)
|
|
174
|
+
|
|
175
|
+
if not cls.validate(v):
|
|
176
|
+
raise _pydantic_value_error(cls.__name__, v)
|
|
177
|
+
|
|
178
|
+
return cls(v)
|
|
179
|
+
|
|
180
|
+
return core_schema.json_or_python_schema(
|
|
181
|
+
json_schema=core_schema.str_schema(),
|
|
182
|
+
python_schema=core_schema.no_info_plain_validator_function(validate),
|
|
183
|
+
serialization=core_schema.to_string_ser_schema(),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class WorldId(Identifier):
|
|
188
|
+
__slots__ = ()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class IdPath(str):
|
|
192
|
+
__slots__ = ()
|
|
193
|
+
delimiter: str = ""
|
|
194
|
+
min_parts: int = 1
|
|
195
|
+
validate_json: bool = False
|
|
196
|
+
|
|
197
|
+
def __new__(cls, value: str | tuple[str, ...] | list[str]) -> "IdPath":
|
|
198
|
+
text = cls._coerce_to_text(value)
|
|
199
|
+
|
|
200
|
+
if not cls.validate(text):
|
|
201
|
+
raise domain_errors.InvalidIdPath(id_type=cls.__name__, value=text)
|
|
202
|
+
|
|
203
|
+
return super().__new__(cls, text)
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
def _coerce_to_text(cls, value: str | tuple[str, ...] | list[str]) -> str:
|
|
207
|
+
if isinstance(value, str):
|
|
208
|
+
return value
|
|
209
|
+
|
|
210
|
+
return cls.delimiter.join(str(part) for part in value)
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
def _split(cls, value: str) -> list[str]:
|
|
214
|
+
return value.split(cls.delimiter)
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def _validate_parts(cls, parts: Sequence[str]) -> bool:
|
|
218
|
+
return all(part.isidentifier() for part in parts)
|
|
219
|
+
|
|
220
|
+
@classmethod
|
|
221
|
+
def validate(cls, value: str) -> bool:
|
|
222
|
+
if not isinstance(value, str) or not value:
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
if not cls.delimiter:
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
parts = cls._split(value)
|
|
229
|
+
|
|
230
|
+
if any(part == "" for part in parts):
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
if len(parts) < cls.min_parts:
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
return cls._validate_parts(parts)
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def parts(self) -> tuple[str, ...]:
|
|
240
|
+
return tuple(self._split(str.__str__(self)))
|
|
241
|
+
|
|
242
|
+
@classmethod
|
|
243
|
+
def _build_pydantic_schema(cls, validate_func: Any) -> core_schema.CoreSchema:
|
|
244
|
+
str_then_validate = core_schema.no_info_after_validator_function(
|
|
245
|
+
validate_func,
|
|
246
|
+
core_schema.str_schema(),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
json_schema = str_then_validate if cls.validate_json else core_schema.str_schema()
|
|
250
|
+
|
|
251
|
+
return core_schema.json_or_python_schema(
|
|
252
|
+
json_schema=json_schema,
|
|
253
|
+
python_schema=core_schema.no_info_plain_validator_function(validate_func),
|
|
254
|
+
serialization=core_schema.to_string_ser_schema(),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
@classmethod
|
|
258
|
+
def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_schema.CoreSchema:
|
|
259
|
+
|
|
260
|
+
def validate(v: Any) -> "IdPath":
|
|
261
|
+
if isinstance(v, cls):
|
|
262
|
+
return v
|
|
263
|
+
|
|
264
|
+
if not isinstance(v, str):
|
|
265
|
+
raise _pydantic_type_error(cls.__name__, v)
|
|
266
|
+
|
|
267
|
+
if not cls.validate(v):
|
|
268
|
+
raise _pydantic_value_error(cls.__name__, v)
|
|
269
|
+
|
|
270
|
+
return cls(v)
|
|
271
|
+
|
|
272
|
+
return cls._build_pydantic_schema(validate)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
TIdPath = TypeVar("TIdPath", bound="IdPath")
|
|
276
|
+
TIdPathPattern = TypeVar("TIdPathPattern", bound="IdPathPattern[Any]")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class IdPathPattern(tuple[str, ...], Generic[TIdPath]):
|
|
280
|
+
__slots__ = ()
|
|
281
|
+
id_class: type[TIdPath]
|
|
282
|
+
|
|
283
|
+
def __str__(self) -> str:
|
|
284
|
+
return self.id_class.delimiter.join(self)
|
|
285
|
+
|
|
286
|
+
@classmethod
|
|
287
|
+
def _validate_pattern_part(cls, part: str) -> bool:
|
|
288
|
+
if part in {"*", "**"}:
|
|
289
|
+
return True
|
|
290
|
+
|
|
291
|
+
return part.isidentifier()
|
|
292
|
+
|
|
293
|
+
@classmethod
|
|
294
|
+
def parse(cls: type[TIdPathPattern], text: str) -> Result[TIdPathPattern, ErrorsList]: # noqa: CCR001
|
|
295
|
+
if not isinstance(text, str) or not text:
|
|
296
|
+
return _invalid_pattern(cls.__name__, text)
|
|
297
|
+
|
|
298
|
+
if not cls.id_class.delimiter:
|
|
299
|
+
return _invalid_pattern(cls.__name__, text)
|
|
300
|
+
|
|
301
|
+
parts = text.split(cls.id_class.delimiter)
|
|
302
|
+
|
|
303
|
+
if any(part == "" for part in parts):
|
|
304
|
+
return _invalid_pattern(cls.__name__, text)
|
|
305
|
+
|
|
306
|
+
for part in parts:
|
|
307
|
+
if not cls._validate_pattern_part(part):
|
|
308
|
+
return _invalid_pattern(cls.__name__, text)
|
|
309
|
+
|
|
310
|
+
return Ok(cls(parts))
|
|
311
|
+
|
|
312
|
+
def matches(self, value: TIdPath) -> bool:
|
|
313
|
+
return _match_pattern_parts(self, self.id_class._split(str(value)))
|
|
314
|
+
|
|
315
|
+
@classmethod
|
|
316
|
+
def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_schema.CoreSchema: # noqa: CCR001
|
|
317
|
+
|
|
318
|
+
def validate(v: Any) -> "IdPathPattern[TIdPath]":
|
|
319
|
+
if isinstance(v, cls):
|
|
320
|
+
return v
|
|
321
|
+
|
|
322
|
+
if not isinstance(v, str):
|
|
323
|
+
raise _pydantic_type_error(cls.__name__, v)
|
|
324
|
+
|
|
325
|
+
result = cls.parse(v)
|
|
326
|
+
errors = result.err()
|
|
327
|
+
if errors is not None:
|
|
328
|
+
error = errors[0]
|
|
329
|
+
raise PydanticCustomError("value_error", error.message.format(error=error))
|
|
330
|
+
|
|
331
|
+
parsed = result.ok()
|
|
332
|
+
if parsed is None:
|
|
333
|
+
raise _pydantic_value_error(cls.__name__, v)
|
|
334
|
+
|
|
335
|
+
return parsed
|
|
336
|
+
|
|
337
|
+
str_then_validate = core_schema.no_info_after_validator_function(
|
|
338
|
+
validate,
|
|
339
|
+
core_schema.str_schema(),
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
return core_schema.json_or_python_schema(
|
|
343
|
+
json_schema=str_then_validate,
|
|
344
|
+
python_schema=core_schema.no_info_plain_validator_function(validate),
|
|
345
|
+
serialization=core_schema.to_string_ser_schema(),
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class DottedPath(IdPath):
|
|
350
|
+
__slots__ = ()
|
|
351
|
+
delimiter = "."
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class ColonPath(IdPath):
|
|
355
|
+
__slots__ = ()
|
|
356
|
+
delimiter = ":"
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class ArtifactId(ColonPath):
|
|
360
|
+
__slots__ = ()
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class PythonImportPath(DottedPath):
|
|
364
|
+
__slots__ = ()
|
|
365
|
+
|
|
366
|
+
@classmethod
|
|
367
|
+
def parse(cls, text: str) -> Result["PythonImportPath", ErrorsList]:
|
|
368
|
+
if not isinstance(text, str) or not text:
|
|
369
|
+
return _invalid_format(cls.__name__, text)
|
|
370
|
+
|
|
371
|
+
if not cls.validate(text):
|
|
372
|
+
return _invalid_format(cls.__name__, text)
|
|
373
|
+
|
|
374
|
+
return Ok(cls(text))
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class FullArtifactId(ColonPath):
|
|
378
|
+
__slots__ = ()
|
|
379
|
+
min_parts = 2
|
|
380
|
+
validate_json = True
|
|
381
|
+
|
|
382
|
+
def __str__(self) -> str:
|
|
383
|
+
return f"{self.world_id}{self.delimiter}{self.artifact_id}"
|
|
384
|
+
|
|
385
|
+
@property
|
|
386
|
+
def world_id(self) -> WorldId:
|
|
387
|
+
return WorldId(self.parts[0])
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def artifact_id(self) -> ArtifactId:
|
|
391
|
+
return ArtifactId(self.delimiter.join(self.parts[1:]))
|
|
392
|
+
|
|
393
|
+
def to_full_local(self, local_id: "ArtifactSectionId") -> "FullArtifactSectionId":
|
|
394
|
+
return FullArtifactSectionId(f"{self}:{local_id}")
|
|
395
|
+
|
|
396
|
+
@classmethod
|
|
397
|
+
def parse(cls, text: str) -> Result["FullArtifactId", ErrorsList]:
|
|
398
|
+
if not isinstance(text, str) or not text:
|
|
399
|
+
return _invalid_format(f"{cls.__name__} format", text)
|
|
400
|
+
|
|
401
|
+
if not cls.delimiter:
|
|
402
|
+
return _invalid_format(f"{cls.__name__} format", text)
|
|
403
|
+
|
|
404
|
+
parts = text.split(cls.delimiter, maxsplit=1)
|
|
405
|
+
|
|
406
|
+
if len(parts) != 2:
|
|
407
|
+
return _invalid_format(f"{cls.__name__} format", text)
|
|
408
|
+
|
|
409
|
+
world_part, artifact_part = parts
|
|
410
|
+
|
|
411
|
+
if not WorldId.validate(world_part):
|
|
412
|
+
return _invalid_format(f"{cls.__name__} format", text)
|
|
413
|
+
|
|
414
|
+
if not ArtifactId.validate(artifact_part):
|
|
415
|
+
return _invalid_format(f"{cls.__name__} format", text)
|
|
416
|
+
|
|
417
|
+
return Ok(FullArtifactId(f"{world_part}{cls.delimiter}{artifact_part}"))
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
class FullArtifactIdPattern(IdPathPattern["FullArtifactId"]):
|
|
421
|
+
__slots__ = ()
|
|
422
|
+
id_class = FullArtifactId
|
|
423
|
+
|
|
424
|
+
def matches_full_id(self, full_id: "FullArtifactId") -> bool:
|
|
425
|
+
return self.matches(full_id)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
class ArtifactSectionId(Identifier):
|
|
429
|
+
__slots__ = ()
|
|
430
|
+
|
|
431
|
+
@classmethod
|
|
432
|
+
def parse(cls, text: str) -> Result["ArtifactSectionId", ErrorsList]:
|
|
433
|
+
if not isinstance(text, str) or not text:
|
|
434
|
+
return _invalid_format(cls.__name__, text)
|
|
435
|
+
|
|
436
|
+
if not cls.validate(text):
|
|
437
|
+
return _invalid_format(cls.__name__, text)
|
|
438
|
+
|
|
439
|
+
return Ok(cls(text))
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class FullArtifactSectionId(ColonPath):
|
|
443
|
+
__slots__ = ()
|
|
444
|
+
min_parts = 3
|
|
445
|
+
validate_json = True
|
|
446
|
+
|
|
447
|
+
def __str__(self) -> str:
|
|
448
|
+
return f"{self.world_id}{self.delimiter}{self.artifact_id}{self.delimiter}{self.local_id}"
|
|
449
|
+
|
|
450
|
+
@property
|
|
451
|
+
def world_id(self) -> WorldId:
|
|
452
|
+
return WorldId(self.parts[0])
|
|
453
|
+
|
|
454
|
+
@property
|
|
455
|
+
def artifact_id(self) -> ArtifactId:
|
|
456
|
+
return ArtifactId(self.delimiter.join(self.parts[1:-1]))
|
|
457
|
+
|
|
458
|
+
@property
|
|
459
|
+
def full_artifact_id(self) -> FullArtifactId:
|
|
460
|
+
return FullArtifactId(f"{self.world_id}{self.delimiter}{self.artifact_id}")
|
|
461
|
+
|
|
462
|
+
@property
|
|
463
|
+
def local_id(self) -> ArtifactSectionId:
|
|
464
|
+
return ArtifactSectionId(self.parts[-1])
|
|
465
|
+
|
|
466
|
+
@classmethod
|
|
467
|
+
def parse(cls, text: str) -> Result["FullArtifactSectionId", ErrorsList]: # noqa: CCR001
|
|
468
|
+
if not isinstance(text, str) or not text:
|
|
469
|
+
return _invalid_format(f"{cls.__name__} format", text)
|
|
470
|
+
|
|
471
|
+
if not cls.delimiter:
|
|
472
|
+
return _invalid_format(f"{cls.__name__} format", text)
|
|
473
|
+
|
|
474
|
+
try:
|
|
475
|
+
artifact_part, local_part = text.rsplit(cls.delimiter, maxsplit=1)
|
|
476
|
+
except ValueError:
|
|
477
|
+
return _invalid_format(f"{cls.__name__} format", text)
|
|
478
|
+
|
|
479
|
+
full_artifact_id_result = FullArtifactId.parse(artifact_part)
|
|
480
|
+
errors = full_artifact_id_result.err()
|
|
481
|
+
if errors is not None:
|
|
482
|
+
return Err(errors)
|
|
483
|
+
|
|
484
|
+
full_artifact_id = full_artifact_id_result.ok()
|
|
485
|
+
if full_artifact_id is None:
|
|
486
|
+
return _invalid_format(f"{cls.__name__} format", text)
|
|
487
|
+
|
|
488
|
+
local_id_result = ArtifactSectionId.parse(local_part)
|
|
489
|
+
local_errors = local_id_result.err()
|
|
490
|
+
if local_errors is not None:
|
|
491
|
+
return Err(local_errors)
|
|
492
|
+
|
|
493
|
+
local_id = local_id_result.ok()
|
|
494
|
+
if local_id is None:
|
|
495
|
+
return _invalid_format(f"{cls.__name__} format", text)
|
|
496
|
+
|
|
497
|
+
return Ok(FullArtifactSectionId(f"{full_artifact_id}{cls.delimiter}{local_id}"))
|
donna/lib/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Shared instances for standard library kind definitions."""
|
|
2
|
+
|
|
3
|
+
from donna.primitives.artifacts.specification import Specification, Text
|
|
4
|
+
from donna.primitives.artifacts.workflow import Workflow
|
|
5
|
+
from donna.primitives.directives.goto import GoTo
|
|
6
|
+
from donna.primitives.directives.task_variable import TaskVariable
|
|
7
|
+
from donna.primitives.directives.view import View
|
|
8
|
+
from donna.primitives.operations.finish_workflow import FinishWorkflow
|
|
9
|
+
from donna.primitives.operations.request_action import RequestAction
|
|
10
|
+
from donna.primitives.operations.run_script import RunScript
|
|
11
|
+
|
|
12
|
+
specification = Specification()
|
|
13
|
+
workflow = Workflow()
|
|
14
|
+
text = Text()
|
|
15
|
+
request_action = RequestAction()
|
|
16
|
+
finish = FinishWorkflow()
|
|
17
|
+
run_script = RunScript()
|
|
18
|
+
|
|
19
|
+
view = View(analyze_id="view")
|
|
20
|
+
goto = GoTo(analyze_id="goto")
|
|
21
|
+
task_variable = TaskVariable(analyze_id="task_variable")
|
donna/lib/sources.py
ADDED
donna/lib/worlds.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Shared world constructor instances for default configuration."""
|
|
2
|
+
|
|
3
|
+
from donna.world.worlds.filesystem import FilesystemWorldConstructor
|
|
4
|
+
from donna.world.worlds.python import PythonWorldConstructor
|
|
5
|
+
|
|
6
|
+
filesystem = FilesystemWorldConstructor()
|
|
7
|
+
python = PythonWorldConstructor()
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import textwrap
|
|
2
|
+
|
|
3
|
+
import pydantic
|
|
4
|
+
|
|
5
|
+
from donna.core.entities import BaseEntity
|
|
6
|
+
from donna.domain.ids import ActionRequestId, FullArtifactSectionId
|
|
7
|
+
from donna.protocol.cells import Cell
|
|
8
|
+
from donna.protocol.nodes import Node
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ActionRequest(BaseEntity):
|
|
12
|
+
id: ActionRequestId | None
|
|
13
|
+
request: str
|
|
14
|
+
operation_id: FullArtifactSectionId
|
|
15
|
+
|
|
16
|
+
# TODO: we may want to make queue items frozen later
|
|
17
|
+
model_config = pydantic.ConfigDict(frozen=False)
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def build(cls, request: str, operation_id: FullArtifactSectionId) -> "ActionRequest":
|
|
21
|
+
return cls(
|
|
22
|
+
id=None,
|
|
23
|
+
request=request,
|
|
24
|
+
operation_id=operation_id,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def node(self) -> "ActionRequestNode":
|
|
28
|
+
return ActionRequestNode(self)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ActionRequestNode(Node):
|
|
32
|
+
__slots__ = ("_action_request",)
|
|
33
|
+
|
|
34
|
+
def __init__(self, action_request: ActionRequest) -> None:
|
|
35
|
+
self._action_request = action_request
|
|
36
|
+
|
|
37
|
+
def status(self) -> Cell:
|
|
38
|
+
message = textwrap.dedent(
|
|
39
|
+
"""
|
|
40
|
+
**This is an action request for the agent. You MUST follow the instructions below.**
|
|
41
|
+
|
|
42
|
+
{request}
|
|
43
|
+
"""
|
|
44
|
+
).format(request=self._action_request.request)
|
|
45
|
+
|
|
46
|
+
return Cell.build_markdown(
|
|
47
|
+
kind="action_request",
|
|
48
|
+
content=message,
|
|
49
|
+
action_request_id=str(self._action_request.id),
|
|
50
|
+
)
|