krons 0.1.0__py3-none-any.whl → 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.
- krons/__init__.py +49 -0
- krons/agent/__init__.py +144 -0
- krons/agent/mcps/__init__.py +14 -0
- krons/agent/mcps/loader.py +287 -0
- krons/agent/mcps/wrapper.py +799 -0
- krons/agent/message/__init__.py +20 -0
- krons/agent/message/action.py +69 -0
- krons/agent/message/assistant.py +52 -0
- krons/agent/message/common.py +49 -0
- krons/agent/message/instruction.py +130 -0
- krons/agent/message/prepare_msg.py +187 -0
- krons/agent/message/role.py +53 -0
- krons/agent/message/system.py +53 -0
- krons/agent/operations/__init__.py +82 -0
- krons/agent/operations/act.py +100 -0
- krons/agent/operations/generate.py +145 -0
- krons/agent/operations/llm_reparse.py +89 -0
- krons/agent/operations/operate.py +247 -0
- krons/agent/operations/parse.py +243 -0
- krons/agent/operations/react.py +286 -0
- krons/agent/operations/specs.py +235 -0
- krons/agent/operations/structure.py +151 -0
- krons/agent/operations/utils.py +79 -0
- krons/agent/providers/__init__.py +17 -0
- krons/agent/providers/anthropic_messages.py +146 -0
- krons/agent/providers/claude_code.py +276 -0
- krons/agent/providers/gemini.py +268 -0
- krons/agent/providers/match.py +75 -0
- krons/agent/providers/oai_chat.py +174 -0
- krons/agent/third_party/__init__.py +2 -0
- krons/agent/third_party/anthropic_models.py +154 -0
- krons/agent/third_party/claude_code.py +682 -0
- krons/agent/third_party/gemini_models.py +508 -0
- krons/agent/third_party/openai_models.py +295 -0
- krons/agent/tool.py +291 -0
- krons/core/__init__.py +127 -0
- krons/core/base/__init__.py +121 -0
- {kronos/core → krons/core/base}/broadcaster.py +7 -3
- {kronos/core → krons/core/base}/element.py +15 -7
- {kronos/core → krons/core/base}/event.py +41 -8
- {kronos/core → krons/core/base}/eventbus.py +4 -2
- {kronos/core → krons/core/base}/flow.py +14 -7
- {kronos/core → krons/core/base}/graph.py +27 -11
- {kronos/core → krons/core/base}/node.py +47 -22
- {kronos/core → krons/core/base}/pile.py +26 -12
- {kronos/core → krons/core/base}/processor.py +23 -9
- {kronos/core → krons/core/base}/progression.py +5 -3
- {kronos → krons/core}/specs/__init__.py +0 -5
- {kronos → krons/core}/specs/adapters/dataclass_field.py +16 -8
- {kronos → krons/core}/specs/adapters/pydantic_adapter.py +11 -5
- {kronos → krons/core}/specs/adapters/sql_ddl.py +16 -10
- {kronos → krons/core}/specs/catalog/__init__.py +2 -2
- {kronos → krons/core}/specs/catalog/_audit.py +3 -3
- {kronos → krons/core}/specs/catalog/_common.py +2 -2
- {kronos → krons/core}/specs/catalog/_content.py +5 -5
- {kronos → krons/core}/specs/catalog/_enforcement.py +4 -4
- {kronos → krons/core}/specs/factory.py +7 -7
- {kronos → krons/core}/specs/operable.py +9 -3
- {kronos → krons/core}/specs/protocol.py +4 -2
- {kronos → krons/core}/specs/spec.py +25 -13
- {kronos → krons/core}/types/base.py +7 -5
- {kronos → krons/core}/types/db_types.py +2 -2
- {kronos → krons/core}/types/identity.py +1 -1
- {kronos → krons}/errors.py +13 -13
- {kronos → krons}/protocols.py +9 -4
- krons/resource/__init__.py +89 -0
- {kronos/services → krons/resource}/backend.py +50 -24
- {kronos/services → krons/resource}/endpoint.py +28 -14
- {kronos/services → krons/resource}/hook.py +22 -9
- {kronos/services → krons/resource}/imodel.py +50 -32
- {kronos/services → krons/resource}/registry.py +27 -25
- {kronos/services → krons/resource}/utilities/rate_limited_executor.py +10 -6
- {kronos/services → krons/resource}/utilities/rate_limiter.py +4 -2
- {kronos/services → krons/resource}/utilities/resilience.py +17 -7
- krons/resource/utilities/token_calculator.py +185 -0
- {kronos → krons}/session/__init__.py +12 -17
- krons/session/constraints.py +70 -0
- {kronos → krons}/session/exchange.py +14 -6
- {kronos → krons}/session/message.py +4 -2
- krons/session/registry.py +35 -0
- {kronos → krons}/session/session.py +165 -174
- krons/utils/__init__.py +85 -0
- krons/utils/_function_arg_parser.py +99 -0
- krons/utils/_pythonic_function_call.py +249 -0
- {kronos → krons}/utils/_to_list.py +9 -3
- {kronos → krons}/utils/_utils.py +9 -5
- {kronos → krons}/utils/concurrency/__init__.py +38 -38
- {kronos → krons}/utils/concurrency/_async_call.py +6 -4
- {kronos → krons}/utils/concurrency/_errors.py +3 -1
- {kronos → krons}/utils/concurrency/_patterns.py +3 -1
- {kronos → krons}/utils/concurrency/_resource_tracker.py +6 -2
- krons/utils/display.py +257 -0
- {kronos → krons}/utils/fuzzy/__init__.py +6 -1
- {kronos → krons}/utils/fuzzy/_fuzzy_match.py +14 -8
- {kronos → krons}/utils/fuzzy/_string_similarity.py +3 -1
- {kronos → krons}/utils/fuzzy/_to_dict.py +3 -1
- krons/utils/schemas/__init__.py +26 -0
- krons/utils/schemas/_breakdown_pydantic_annotation.py +131 -0
- krons/utils/schemas/_formatter.py +72 -0
- krons/utils/schemas/_minimal_yaml.py +151 -0
- krons/utils/schemas/_typescript.py +153 -0
- {kronos → krons}/utils/sql/_sql_validation.py +1 -1
- krons/utils/validators/__init__.py +3 -0
- krons/utils/validators/_validate_image_url.py +56 -0
- krons/work/__init__.py +126 -0
- krons/work/engine.py +333 -0
- krons/work/form.py +305 -0
- {kronos → krons/work}/operations/__init__.py +7 -4
- {kronos → krons/work}/operations/builder.py +4 -4
- {kronos/enforcement → krons/work/operations}/context.py +37 -6
- {kronos → krons/work}/operations/flow.py +17 -9
- krons/work/operations/node.py +103 -0
- krons/work/operations/registry.py +103 -0
- {kronos/specs → krons/work}/phrase.py +131 -14
- {kronos/enforcement → krons/work}/policy.py +3 -3
- krons/work/report.py +268 -0
- krons/work/rules/__init__.py +47 -0
- {kronos/enforcement → krons/work/rules}/common/boolean.py +3 -1
- {kronos/enforcement → krons/work/rules}/common/choice.py +9 -3
- {kronos/enforcement → krons/work/rules}/common/number.py +3 -1
- {kronos/enforcement → krons/work/rules}/common/string.py +9 -3
- {kronos/enforcement → krons/work/rules}/rule.py +2 -2
- {kronos/enforcement → krons/work/rules}/validator.py +21 -6
- {kronos/enforcement → krons/work}/service.py +16 -7
- krons/work/worker.py +266 -0
- {krons-0.1.0.dist-info → krons-0.2.0.dist-info}/METADATA +19 -5
- krons-0.2.0.dist-info/RECORD +154 -0
- kronos/core/__init__.py +0 -145
- kronos/enforcement/__init__.py +0 -57
- kronos/operations/node.py +0 -101
- kronos/operations/registry.py +0 -92
- kronos/services/__init__.py +0 -81
- kronos/specs/adapters/__init__.py +0 -0
- kronos/utils/__init__.py +0 -40
- krons-0.1.0.dist-info/RECORD +0 -101
- {kronos → krons/core/specs/adapters}/__init__.py +0 -0
- {kronos → krons/core}/specs/adapters/_utils.py +0 -0
- {kronos → krons/core}/specs/adapters/factory.py +0 -0
- {kronos → krons/core}/types/__init__.py +0 -0
- {kronos → krons/core}/types/_sentinel.py +0 -0
- {kronos → krons}/py.typed +0 -0
- {kronos/services → krons/resource}/utilities/__init__.py +0 -0
- {kronos/services → krons/resource}/utilities/header_factory.py +0 -0
- {kronos → krons}/utils/_hash.py +0 -0
- {kronos → krons}/utils/_json_dump.py +0 -0
- {kronos → krons}/utils/_lazy_init.py +0 -0
- {kronos → krons}/utils/_to_num.py +0 -0
- {kronos → krons}/utils/concurrency/_cancel.py +0 -0
- {kronos → krons}/utils/concurrency/_primitives.py +0 -0
- {kronos → krons}/utils/concurrency/_priority_queue.py +0 -0
- {kronos → krons}/utils/concurrency/_run_async.py +0 -0
- {kronos → krons}/utils/concurrency/_task.py +0 -0
- {kronos → krons}/utils/concurrency/_utils.py +0 -0
- {kronos → krons}/utils/fuzzy/_extract_json.py +0 -0
- {kronos → krons}/utils/fuzzy/_fuzzy_json.py +0 -0
- {kronos → krons}/utils/sql/__init__.py +0 -0
- {kronos/enforcement → krons/work/rules}/common/__init__.py +0 -0
- {kronos/enforcement → krons/work/rules}/common/mapping.py +0 -0
- {kronos/enforcement → krons/work/rules}/common/model.py +0 -0
- {kronos/enforcement → krons/work/rules}/registry.py +0 -0
- {krons-0.1.0.dist-info → krons-0.2.0.dist-info}/WHEEL +0 -0
- {krons-0.1.0.dist-info → krons-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Copyright (c) 2025 - 2026, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Core primitives with lazy loading for fast import."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
# Lazy import mapping - all modules are in krons.core.base.*
|
|
11
|
+
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
|
|
12
|
+
# broadcaster
|
|
13
|
+
"Broadcaster": ("krons.core.base.broadcaster", "Broadcaster"),
|
|
14
|
+
# element
|
|
15
|
+
"Element": ("krons.core.base.element", "Element"),
|
|
16
|
+
# event
|
|
17
|
+
"Event": ("krons.core.base.event", "Event"),
|
|
18
|
+
"EventStatus": ("krons.core.base.event", "EventStatus"),
|
|
19
|
+
"Execution": ("krons.core.base.event", "Execution"),
|
|
20
|
+
# eventbus
|
|
21
|
+
"EventBus": ("krons.core.base.eventbus", "EventBus"),
|
|
22
|
+
"Handler": ("krons.core.base.eventbus", "Handler"),
|
|
23
|
+
# flow
|
|
24
|
+
"Flow": ("krons.core.base.flow", "Flow"),
|
|
25
|
+
# graph
|
|
26
|
+
"Edge": ("krons.core.base.graph", "Edge"),
|
|
27
|
+
"EdgeCondition": ("krons.core.base.graph", "EdgeCondition"),
|
|
28
|
+
"Graph": ("krons.core.base.graph", "Graph"),
|
|
29
|
+
# node
|
|
30
|
+
"NODE_REGISTRY": ("krons.core.base.node", "NODE_REGISTRY"),
|
|
31
|
+
"PERSISTABLE_NODE_REGISTRY": ("krons.core.base.node", "PERSISTABLE_NODE_REGISTRY"),
|
|
32
|
+
"Node": ("krons.core.base.node", "Node"),
|
|
33
|
+
"NodeConfig": ("krons.core.base.node", "NodeConfig"),
|
|
34
|
+
"create_node": ("krons.core.base.node", "create_node"),
|
|
35
|
+
"generate_ddl": ("krons.core.base.node", "generate_ddl"),
|
|
36
|
+
"generate_all_ddl": ("krons.core.base.node", "generate_all_ddl"),
|
|
37
|
+
"get_fk_dependencies": ("krons.core.base.node", "get_fk_dependencies"),
|
|
38
|
+
# pile
|
|
39
|
+
"Pile": ("krons.core.base.pile", "Pile"),
|
|
40
|
+
# processor
|
|
41
|
+
"Executor": ("krons.core.base.processor", "Executor"),
|
|
42
|
+
"Processor": ("krons.core.base.processor", "Processor"),
|
|
43
|
+
# progression
|
|
44
|
+
"Progression": ("krons.core.base.progression", "Progression"),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_LOADED: dict[str, object] = {}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def __getattr__(name: str) -> object:
|
|
51
|
+
"""Lazy import attributes on first access."""
|
|
52
|
+
if name in _LOADED:
|
|
53
|
+
return _LOADED[name]
|
|
54
|
+
|
|
55
|
+
if name in _LAZY_IMPORTS:
|
|
56
|
+
from importlib import import_module
|
|
57
|
+
|
|
58
|
+
module_name, attr_name = _LAZY_IMPORTS[name]
|
|
59
|
+
module = import_module(module_name)
|
|
60
|
+
value = getattr(module, attr_name)
|
|
61
|
+
_LOADED[name] = value
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
raise AttributeError(f"module 'krons.core' has no attribute {name!r}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def __dir__() -> list[str]:
|
|
68
|
+
"""Return all available attributes for autocomplete."""
|
|
69
|
+
return list(__all__)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# TYPE_CHECKING block for static analysis
|
|
73
|
+
if TYPE_CHECKING:
|
|
74
|
+
from .broadcaster import Broadcaster
|
|
75
|
+
from .element import Element
|
|
76
|
+
from .event import Event, EventStatus, Execution
|
|
77
|
+
from .eventbus import EventBus, Handler
|
|
78
|
+
from .flow import Flow
|
|
79
|
+
from .graph import Edge, EdgeCondition, Graph
|
|
80
|
+
from .node import (
|
|
81
|
+
NODE_REGISTRY,
|
|
82
|
+
PERSISTABLE_NODE_REGISTRY,
|
|
83
|
+
Node,
|
|
84
|
+
NodeConfig,
|
|
85
|
+
create_node,
|
|
86
|
+
generate_all_ddl,
|
|
87
|
+
generate_ddl,
|
|
88
|
+
get_fk_dependencies,
|
|
89
|
+
)
|
|
90
|
+
from .pile import Pile
|
|
91
|
+
from .processor import Executor, Processor
|
|
92
|
+
from .progression import Progression
|
|
93
|
+
|
|
94
|
+
__all__ = [
|
|
95
|
+
# constants/registries
|
|
96
|
+
"NODE_REGISTRY",
|
|
97
|
+
"PERSISTABLE_NODE_REGISTRY",
|
|
98
|
+
# classes
|
|
99
|
+
"Broadcaster",
|
|
100
|
+
"Edge",
|
|
101
|
+
"EdgeCondition",
|
|
102
|
+
"Element",
|
|
103
|
+
"Event",
|
|
104
|
+
"EventBus",
|
|
105
|
+
"EventStatus",
|
|
106
|
+
"Execution",
|
|
107
|
+
"Executor",
|
|
108
|
+
"Flow",
|
|
109
|
+
"Graph",
|
|
110
|
+
"Handler",
|
|
111
|
+
"Node",
|
|
112
|
+
"NodeConfig",
|
|
113
|
+
"Pile",
|
|
114
|
+
"Processor",
|
|
115
|
+
"Progression",
|
|
116
|
+
# functions
|
|
117
|
+
"create_node",
|
|
118
|
+
"generate_all_ddl",
|
|
119
|
+
"generate_ddl",
|
|
120
|
+
"get_fk_dependencies",
|
|
121
|
+
]
|
|
@@ -8,7 +8,7 @@ import weakref
|
|
|
8
8
|
from collections.abc import Awaitable, Callable
|
|
9
9
|
from typing import Any, ClassVar
|
|
10
10
|
|
|
11
|
-
from
|
|
11
|
+
from krons.utils.concurrency import is_coro_func
|
|
12
12
|
|
|
13
13
|
logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
@@ -41,7 +41,9 @@ class Broadcaster:
|
|
|
41
41
|
return cls._instance
|
|
42
42
|
|
|
43
43
|
@classmethod
|
|
44
|
-
def subscribe(
|
|
44
|
+
def subscribe(
|
|
45
|
+
cls, callback: Callable[[Any], None] | Callable[[Any], Awaitable[None]]
|
|
46
|
+
) -> None:
|
|
45
47
|
"""Add subscriber callback (idempotent, stored as weakref).
|
|
46
48
|
|
|
47
49
|
Args:
|
|
@@ -51,7 +53,9 @@ class Broadcaster:
|
|
|
51
53
|
for weak_ref in cls._subscribers:
|
|
52
54
|
if weak_ref() is callback:
|
|
53
55
|
return
|
|
54
|
-
weak_callback: weakref.ref[
|
|
56
|
+
weak_callback: weakref.ref[
|
|
57
|
+
Callable[[Any], None] | Callable[[Any], Awaitable[None]]
|
|
58
|
+
]
|
|
55
59
|
if hasattr(callback, "__self__"):
|
|
56
60
|
weak_callback = weakref.WeakMethod(callback) # type: ignore[assignment]
|
|
57
61
|
else:
|
|
@@ -11,15 +11,15 @@ from uuid import UUID, uuid4
|
|
|
11
11
|
import orjson
|
|
12
12
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
13
13
|
|
|
14
|
-
from
|
|
14
|
+
from krons.core.types import MaybeSentinel, Unset, UnsetType, is_sentinel, is_unset
|
|
15
|
+
from krons.protocols import (
|
|
15
16
|
Deserializable,
|
|
16
17
|
Hashable,
|
|
17
18
|
Observable,
|
|
18
19
|
Serializable,
|
|
19
20
|
implements,
|
|
20
21
|
)
|
|
21
|
-
from
|
|
22
|
-
from kronos.utils import (
|
|
22
|
+
from krons.utils import (
|
|
23
23
|
coerce_created_at,
|
|
24
24
|
json_dump,
|
|
25
25
|
load_type_from_string,
|
|
@@ -68,7 +68,9 @@ class Element(BaseModel):
|
|
|
68
68
|
|
|
69
69
|
@field_validator("metadata", mode="before")
|
|
70
70
|
@classmethod
|
|
71
|
-
def _validate_meta_integrity(
|
|
71
|
+
def _validate_meta_integrity(
|
|
72
|
+
cls, val: dict[str, Any] | MaybeSentinel
|
|
73
|
+
) -> dict[str, Any]:
|
|
72
74
|
"""Validate and coerce metadata to dict. Raises ValueError if conversion fails."""
|
|
73
75
|
if is_sentinel(val, {"none"}):
|
|
74
76
|
return {}
|
|
@@ -108,7 +110,9 @@ class Element(BaseModel):
|
|
|
108
110
|
def to_dict(
|
|
109
111
|
self,
|
|
110
112
|
mode: Literal["python", "json", "db"] = "python",
|
|
111
|
-
created_at_format: (
|
|
113
|
+
created_at_format: (
|
|
114
|
+
Literal["datetime", "isoformat", "timestamp"] | UnsetType
|
|
115
|
+
) = Unset,
|
|
112
116
|
meta_key: str | UnsetType = Unset,
|
|
113
117
|
**kwargs: Any,
|
|
114
118
|
) -> dict[str, Any]:
|
|
@@ -187,7 +191,9 @@ class Element(BaseModel):
|
|
|
187
191
|
try:
|
|
188
192
|
target_cls = load_type_from_string(kron_class)
|
|
189
193
|
except ValueError as e:
|
|
190
|
-
raise ValueError(
|
|
194
|
+
raise ValueError(
|
|
195
|
+
f"Failed to deserialize class '{kron_class}': {e}"
|
|
196
|
+
) from e
|
|
191
197
|
|
|
192
198
|
if not issubclass(target_cls, Element):
|
|
193
199
|
raise ValueError(
|
|
@@ -195,7 +201,9 @@ class Element(BaseModel):
|
|
|
195
201
|
f"Cannot deserialize into {cls.__name__}"
|
|
196
202
|
)
|
|
197
203
|
|
|
198
|
-
target_func = getattr(
|
|
204
|
+
target_func = getattr(
|
|
205
|
+
target_cls.from_dict, "__func__", target_cls.from_dict
|
|
206
|
+
)
|
|
199
207
|
cls_func = getattr(cls.from_dict, "__func__", cls.from_dict)
|
|
200
208
|
if target_func is cls_func:
|
|
201
209
|
return target_cls.model_validate(data, **kwargs)
|
|
@@ -11,10 +11,17 @@ from typing import Any, final
|
|
|
11
11
|
import orjson
|
|
12
12
|
from pydantic import Field, field_serializer, field_validator
|
|
13
13
|
|
|
14
|
-
from
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
from krons.core.types import (
|
|
15
|
+
Enum,
|
|
16
|
+
MaybeSentinel,
|
|
17
|
+
MaybeUnset,
|
|
18
|
+
Unset,
|
|
19
|
+
is_sentinel,
|
|
20
|
+
is_unset,
|
|
21
|
+
)
|
|
22
|
+
from krons.errors import KronsError, KronTimeoutError, ValidationError
|
|
23
|
+
from krons.protocols import Invocable, Serializable, implements
|
|
24
|
+
from krons.utils import async_synchronized, concurrency, json_dumpb
|
|
18
25
|
|
|
19
26
|
from .element import LN_ELEMENT_FIELDS, Element
|
|
20
27
|
|
|
@@ -142,7 +149,9 @@ class Execution:
|
|
|
142
149
|
if isinstance(exc, Serializable):
|
|
143
150
|
exceptions.append(exc.to_dict())
|
|
144
151
|
elif isinstance(exc, ExceptionGroup):
|
|
145
|
-
exceptions.append(
|
|
152
|
+
exceptions.append(
|
|
153
|
+
self._serialize_exception_group(exc, depth + 1, _seen)
|
|
154
|
+
)
|
|
146
155
|
else:
|
|
147
156
|
exceptions.append(
|
|
148
157
|
{
|
|
@@ -262,11 +271,14 @@ class Event(Element):
|
|
|
262
271
|
except Exception as e:
|
|
263
272
|
if isinstance(e, ExceptionGroup):
|
|
264
273
|
retryable = all(
|
|
265
|
-
not isinstance(exc,
|
|
274
|
+
not isinstance(exc, KronsError) or exc.retryable
|
|
275
|
+
for exc in e.exceptions
|
|
266
276
|
)
|
|
267
277
|
self.execution.retryable = retryable
|
|
268
278
|
else:
|
|
269
|
-
self.execution.retryable =
|
|
279
|
+
self.execution.retryable = (
|
|
280
|
+
e.retryable if isinstance(e, KronsError) else True
|
|
281
|
+
)
|
|
270
282
|
|
|
271
283
|
self.execution.response = Unset
|
|
272
284
|
self.execution.error = e
|
|
@@ -286,7 +298,9 @@ class Event(Element):
|
|
|
286
298
|
|
|
287
299
|
async def stream(self) -> Any:
|
|
288
300
|
"""Stream execution results. Override if streaming=True."""
|
|
289
|
-
raise NotImplementedError(
|
|
301
|
+
raise NotImplementedError(
|
|
302
|
+
"Subclasses must implement stream() if streaming=True"
|
|
303
|
+
)
|
|
290
304
|
|
|
291
305
|
def as_fresh_event(self, copy_meta: bool = False) -> Event:
|
|
292
306
|
"""Clone with reset execution state (new ID, PENDING status).
|
|
@@ -314,3 +328,22 @@ class Event(Element):
|
|
|
314
328
|
"created_at": self.created_at,
|
|
315
329
|
}
|
|
316
330
|
return fresh
|
|
331
|
+
|
|
332
|
+
def assert_completed(self, *, retryable: MaybeUnset[bool] = Unset):
|
|
333
|
+
if self.execution.status != EventStatus.COMPLETED:
|
|
334
|
+
retryable_value = (
|
|
335
|
+
self.execution.retryable if is_unset(retryable) else retryable
|
|
336
|
+
)
|
|
337
|
+
retryable_value = True if retryable_value is True else False
|
|
338
|
+
exec_dict = self.execution.to_dict()
|
|
339
|
+
exec_dict.pop("response", None)
|
|
340
|
+
exec_dict.pop("retryable", None)
|
|
341
|
+
|
|
342
|
+
raise ValidationError(
|
|
343
|
+
"Event did not complete successfully.",
|
|
344
|
+
details={
|
|
345
|
+
"event_id": str(self.id),
|
|
346
|
+
**exec_dict,
|
|
347
|
+
},
|
|
348
|
+
retryable=retryable_value,
|
|
349
|
+
)
|
|
@@ -8,7 +8,7 @@ from collections import defaultdict
|
|
|
8
8
|
from collections.abc import Awaitable, Callable
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
-
from
|
|
11
|
+
from krons.utils.concurrency import gather
|
|
12
12
|
|
|
13
13
|
__all__ = ("EventBus", "Handler")
|
|
14
14
|
|
|
@@ -85,7 +85,9 @@ class EventBus:
|
|
|
85
85
|
if topic not in self._subs:
|
|
86
86
|
return
|
|
87
87
|
if handlers := self._cleanup_dead_refs(topic):
|
|
88
|
-
await gather(
|
|
88
|
+
await gather(
|
|
89
|
+
*(h(*args, **kwargs) for h in handlers), return_exceptions=True
|
|
90
|
+
)
|
|
89
91
|
|
|
90
92
|
def clear(self, topic: str | None = None) -> None:
|
|
91
93
|
"""Clear subscriptions.
|
|
@@ -9,10 +9,10 @@ from uuid import UUID
|
|
|
9
9
|
|
|
10
10
|
from pydantic import Field, PrivateAttr, field_validator, model_validator
|
|
11
11
|
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from
|
|
12
|
+
from krons.core.types import Unset, UnsetType
|
|
13
|
+
from krons.errors import ExistsError, NotFoundError
|
|
14
|
+
from krons.protocols import Serializable, implements
|
|
15
|
+
from krons.utils import extract_types, synchronized
|
|
16
16
|
|
|
17
17
|
from .element import Element
|
|
18
18
|
from .pile import Pile
|
|
@@ -99,7 +99,9 @@ class Flow(Element, Generic[E, P]):
|
|
|
99
99
|
|
|
100
100
|
# Create Pile with items and type validation (item_type/strict_type are frozen)
|
|
101
101
|
# Even if items=None, create Pile if item_type/strict_type specified
|
|
102
|
-
data["items"] = Pile(
|
|
102
|
+
data["items"] = Pile(
|
|
103
|
+
items=items, item_type=item_type, strict_type=strict_type
|
|
104
|
+
)
|
|
103
105
|
|
|
104
106
|
# Handle progressions - let field validator convert dict/list to Pile
|
|
105
107
|
if progressions is not None:
|
|
@@ -217,7 +219,10 @@ class Flow(Element, Generic[E, P]):
|
|
|
217
219
|
NotFoundError: If progression not found.
|
|
218
220
|
"""
|
|
219
221
|
name_to_delete: str | None
|
|
220
|
-
if
|
|
222
|
+
if (
|
|
223
|
+
isinstance(progression_id, str)
|
|
224
|
+
and progression_id in self._progression_names
|
|
225
|
+
):
|
|
221
226
|
uid = self._progression_names[progression_id]
|
|
222
227
|
name_to_delete = progression_id
|
|
223
228
|
else:
|
|
@@ -323,7 +328,9 @@ class Flow(Element, Generic[E, P]):
|
|
|
323
328
|
def to_dict(
|
|
324
329
|
self,
|
|
325
330
|
mode: Literal["python", "json", "db"] = "python",
|
|
326
|
-
created_at_format: (
|
|
331
|
+
created_at_format: (
|
|
332
|
+
Literal["datetime", "isoformat", "timestamp"] | UnsetType
|
|
333
|
+
) = Unset,
|
|
327
334
|
meta_key: str | UnsetType = Unset,
|
|
328
335
|
**kwargs: Any,
|
|
329
336
|
) -> dict[str, Any]:
|
|
@@ -11,10 +11,10 @@ from uuid import UUID
|
|
|
11
11
|
from pydantic import Field, PrivateAttr, field_validator, model_validator
|
|
12
12
|
from typing_extensions import override
|
|
13
13
|
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from
|
|
14
|
+
from krons.core.types import Unset, UnsetType, is_unset
|
|
15
|
+
from krons.errors import NotFoundError
|
|
16
|
+
from krons.protocols import Containable, Deserializable, Serializable, implements
|
|
17
|
+
from krons.utils import synchronized
|
|
18
18
|
|
|
19
19
|
from .element import Element
|
|
20
20
|
from .node import Node
|
|
@@ -61,7 +61,9 @@ class Edge(Element):
|
|
|
61
61
|
condition: EdgeCondition | None = Field(
|
|
62
62
|
default=None, exclude=True, description="Runtime traversal predicate"
|
|
63
63
|
)
|
|
64
|
-
properties: dict[str, Any] = Field(
|
|
64
|
+
properties: dict[str, Any] = Field(
|
|
65
|
+
default_factory=dict, description="Custom edge attributes"
|
|
66
|
+
)
|
|
65
67
|
|
|
66
68
|
@field_validator("head", "tail", mode="before")
|
|
67
69
|
@classmethod
|
|
@@ -212,12 +214,16 @@ class Graph(Element):
|
|
|
212
214
|
def get_predecessors(self, node_id: UUID | Node) -> list[Node]:
|
|
213
215
|
"""Get nodes with edges pointing to this node (in-neighbors)."""
|
|
214
216
|
nid = self._coerce_id(node_id)
|
|
215
|
-
return [
|
|
217
|
+
return [
|
|
218
|
+
self.nodes[self.edges[eid].head] for eid in self._in_edges.get(nid, set())
|
|
219
|
+
]
|
|
216
220
|
|
|
217
221
|
def get_successors(self, node_id: UUID | Node) -> list[Node]:
|
|
218
222
|
"""Get nodes this node points to (out-neighbors)."""
|
|
219
223
|
nid = self._coerce_id(node_id)
|
|
220
|
-
return [
|
|
224
|
+
return [
|
|
225
|
+
self.nodes[self.edges[eid].tail] for eid in self._out_edges.get(nid, set())
|
|
226
|
+
]
|
|
221
227
|
|
|
222
228
|
def get_node_edges(
|
|
223
229
|
self,
|
|
@@ -240,11 +246,17 @@ class Graph(Element):
|
|
|
240
246
|
|
|
241
247
|
def get_heads(self) -> list[Node]:
|
|
242
248
|
"""Get source nodes (no incoming edges)."""
|
|
243
|
-
return [
|
|
249
|
+
return [
|
|
250
|
+
self.nodes[nid] for nid, in_edges in self._in_edges.items() if not in_edges
|
|
251
|
+
]
|
|
244
252
|
|
|
245
253
|
def get_tails(self) -> list[Node]:
|
|
246
254
|
"""Get sink nodes (no outgoing edges)."""
|
|
247
|
-
return [
|
|
255
|
+
return [
|
|
256
|
+
self.nodes[nid]
|
|
257
|
+
for nid, out_edges in self._out_edges.items()
|
|
258
|
+
if not out_edges
|
|
259
|
+
]
|
|
248
260
|
|
|
249
261
|
# ==================== Graph Algorithms ====================
|
|
250
262
|
|
|
@@ -361,10 +373,14 @@ class Graph(Element):
|
|
|
361
373
|
def to_dict(
|
|
362
374
|
self,
|
|
363
375
|
mode: Literal["python", "json", "db"] = "python",
|
|
364
|
-
created_at_format: (
|
|
376
|
+
created_at_format: (
|
|
377
|
+
Literal["datetime", "isoformat", "timestamp"] | UnsetType
|
|
378
|
+
) = Unset,
|
|
365
379
|
meta_key: str | UnsetType = Unset,
|
|
366
380
|
item_meta_key: str | UnsetType = Unset,
|
|
367
|
-
item_created_at_format: (
|
|
381
|
+
item_created_at_format: (
|
|
382
|
+
Literal["datetime", "isoformat", "timestamp"] | UnsetType
|
|
383
|
+
) = Unset,
|
|
368
384
|
**kwargs: Any,
|
|
369
385
|
) -> dict[str, Any]:
|
|
370
386
|
"""Serialize graph with nodes and edges as nested Pile dicts.
|
|
@@ -15,8 +15,7 @@ from uuid import UUID
|
|
|
15
15
|
|
|
16
16
|
from pydantic import BaseModel, field_serializer, field_validator
|
|
17
17
|
|
|
18
|
-
from
|
|
19
|
-
from kronos.types import (
|
|
18
|
+
from krons.core.types import (
|
|
20
19
|
ModelConfig,
|
|
21
20
|
Params,
|
|
22
21
|
Unset,
|
|
@@ -25,8 +24,9 @@ from kronos.types import (
|
|
|
25
24
|
is_unset,
|
|
26
25
|
not_sentinel,
|
|
27
26
|
)
|
|
28
|
-
from
|
|
29
|
-
from
|
|
27
|
+
from krons.core.types.db_types import VectorMeta, extract_kron_db_meta
|
|
28
|
+
from krons.protocols import Deserializable, Serializable, implements
|
|
29
|
+
from krons.utils import compute_hash, json_dump, now_utc
|
|
30
30
|
|
|
31
31
|
from .element import Element
|
|
32
32
|
|
|
@@ -56,9 +56,13 @@ def _enable_embedding_requires_dim(config: NodeConfig) -> None:
|
|
|
56
56
|
"""Validate: embedding_enabled requires positive embedding_dim."""
|
|
57
57
|
if config.embedding_enabled:
|
|
58
58
|
if config.is_sentinel_field("embedding_dim"):
|
|
59
|
-
raise ValueError(
|
|
59
|
+
raise ValueError(
|
|
60
|
+
"embedding_dim must be specified when embedding is enabled"
|
|
61
|
+
)
|
|
60
62
|
if config.embedding_dim <= 0:
|
|
61
|
-
raise ValueError(
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"embedding_dim must be positive, got {config.embedding_dim}"
|
|
65
|
+
)
|
|
62
66
|
|
|
63
67
|
|
|
64
68
|
def _only_typed_content_can_flatten(config: NodeConfig) -> None:
|
|
@@ -231,7 +235,9 @@ class Node(Element):
|
|
|
231
235
|
cls._resolved_content_type = None
|
|
232
236
|
else:
|
|
233
237
|
cls._resolved_content_type = (
|
|
234
|
-
None
|
|
238
|
+
None
|
|
239
|
+
if config.is_sentinel_field("content_type")
|
|
240
|
+
else config.content_type
|
|
235
241
|
)
|
|
236
242
|
|
|
237
243
|
@field_serializer("content")
|
|
@@ -258,12 +264,15 @@ class Node(Element):
|
|
|
258
264
|
f"or Element.metadata for simple key-value pairs."
|
|
259
265
|
)
|
|
260
266
|
|
|
261
|
-
# Polymorphic: restore type from
|
|
267
|
+
# Polymorphic: restore type from krons_class in metadata
|
|
262
268
|
if isinstance(value, dict) and "metadata" in value:
|
|
263
269
|
metadata = value.get("metadata", {})
|
|
264
270
|
kron_class = metadata.get("kron_class")
|
|
265
271
|
if kron_class:
|
|
266
|
-
if
|
|
272
|
+
if (
|
|
273
|
+
kron_class in NODE_REGISTRY
|
|
274
|
+
or kron_class.split(".")[-1] in NODE_REGISTRY
|
|
275
|
+
):
|
|
267
276
|
return Node.from_dict(value)
|
|
268
277
|
return Element.from_dict(value)
|
|
269
278
|
return value
|
|
@@ -271,7 +280,9 @@ class Node(Element):
|
|
|
271
280
|
def to_dict(
|
|
272
281
|
self,
|
|
273
282
|
mode: Literal["python", "json", "db"] = "python",
|
|
274
|
-
created_at_format: (
|
|
283
|
+
created_at_format: (
|
|
284
|
+
Literal["datetime", "isoformat", "timestamp"] | UnsetType
|
|
285
|
+
) = Unset,
|
|
275
286
|
meta_key: str | UnsetType = Unset,
|
|
276
287
|
content_serializer: Callable[[Any], Any] | None = None,
|
|
277
288
|
**kwargs: Any,
|
|
@@ -421,14 +432,18 @@ class Node(Element):
|
|
|
421
432
|
and issubclass(content_type, BaseModel)
|
|
422
433
|
):
|
|
423
434
|
content_field_names = set(content_type.model_fields.keys())
|
|
424
|
-
content_data = {
|
|
435
|
+
content_data = {
|
|
436
|
+
k: v for k, v in data.items() if k in content_field_names
|
|
437
|
+
}
|
|
425
438
|
for k in content_field_names:
|
|
426
439
|
data.pop(k, None)
|
|
427
440
|
data["content"] = content_type(**content_data)
|
|
428
441
|
|
|
429
442
|
# Handle meta_key for DB rows
|
|
430
443
|
effective_meta_key = (
|
|
431
|
-
meta_key
|
|
444
|
+
meta_key
|
|
445
|
+
if not is_unset(meta_key)
|
|
446
|
+
else (config.meta_key if from_row else Unset)
|
|
432
447
|
)
|
|
433
448
|
|
|
434
449
|
if content_deserializer is not None:
|
|
@@ -503,7 +518,7 @@ class Node(Element):
|
|
|
503
518
|
Computed integrity_hash, or None if integrity_hashing disabled
|
|
504
519
|
|
|
505
520
|
"""
|
|
506
|
-
from
|
|
521
|
+
from krons.utils import compute_chain_hash
|
|
507
522
|
|
|
508
523
|
config = self.get_config()
|
|
509
524
|
if not config.integrity_hashing:
|
|
@@ -704,8 +719,8 @@ def create_node(
|
|
|
704
719
|
>>> Job = create_node("Job", embedding_enabled=True, embedding_dim=1536)
|
|
705
720
|
|
|
706
721
|
"""
|
|
707
|
-
from
|
|
708
|
-
from
|
|
722
|
+
from krons.core.specs.catalog import AuditSpecs, ContentSpecs
|
|
723
|
+
from krons.core.specs.operable import Operable
|
|
709
724
|
|
|
710
725
|
# Resolve embedding dimension
|
|
711
726
|
resolved_embedding_dim: int | UnsetType = Unset
|
|
@@ -723,7 +738,9 @@ def create_node(
|
|
|
723
738
|
)
|
|
724
739
|
elif embedding_enabled:
|
|
725
740
|
if embedding_dim is None or embedding_dim <= 0:
|
|
726
|
-
raise ValueError(
|
|
741
|
+
raise ValueError(
|
|
742
|
+
"embedding_dim must be positive when embedding_enabled=True"
|
|
743
|
+
)
|
|
727
744
|
resolved_embedding_dim = embedding_dim
|
|
728
745
|
has_embedding = True
|
|
729
746
|
|
|
@@ -742,7 +759,11 @@ def create_node(
|
|
|
742
759
|
include.append("embedding")
|
|
743
760
|
|
|
744
761
|
needs_update_tracking = (
|
|
745
|
-
track_updated_at
|
|
762
|
+
track_updated_at
|
|
763
|
+
or content_hashing
|
|
764
|
+
or integrity_hashing
|
|
765
|
+
or soft_delete
|
|
766
|
+
or versioning
|
|
746
767
|
)
|
|
747
768
|
if needs_update_tracking:
|
|
748
769
|
include.append("updated_at")
|
|
@@ -805,7 +826,9 @@ def _extract_base_type(annotation: Any) -> Any:
|
|
|
805
826
|
if annotation is None:
|
|
806
827
|
return None
|
|
807
828
|
|
|
808
|
-
if isinstance(annotation, types.UnionType) or get_origin(annotation) is type(
|
|
829
|
+
if isinstance(annotation, types.UnionType) or get_origin(annotation) is type(
|
|
830
|
+
int | str
|
|
831
|
+
):
|
|
809
832
|
args = get_args(annotation)
|
|
810
833
|
non_none_args = [a for a in args if a is not type(None)]
|
|
811
834
|
if non_none_args:
|
|
@@ -835,12 +858,14 @@ def generate_ddl(
|
|
|
835
858
|
ValueError: If node_cls has no table_name configured
|
|
836
859
|
|
|
837
860
|
"""
|
|
838
|
-
from
|
|
839
|
-
from
|
|
861
|
+
from krons.core.specs.catalog import AuditSpecs, ContentSpecs
|
|
862
|
+
from krons.core.specs.operable import Operable
|
|
840
863
|
|
|
841
864
|
config = node_cls.get_config()
|
|
842
865
|
if not config.is_persisted:
|
|
843
|
-
raise ValueError(
|
|
866
|
+
raise ValueError(
|
|
867
|
+
f"{node_cls.__name__} is not persistable (no table_name configured)"
|
|
868
|
+
)
|
|
844
869
|
|
|
845
870
|
# 1. Build all possible specs for this node
|
|
846
871
|
content_type = (
|
|
@@ -855,7 +880,7 @@ def generate_ddl(
|
|
|
855
880
|
|
|
856
881
|
# Flatten content: extract fields from BaseModel instead of generic JSONB
|
|
857
882
|
if config.flatten_content and content_type is not None:
|
|
858
|
-
from
|
|
883
|
+
from krons.core.specs.adapters.pydantic_adapter import PydanticSpecAdapter
|
|
859
884
|
|
|
860
885
|
if isinstance(content_type, type) and issubclass(content_type, BaseModel):
|
|
861
886
|
all_specs.extend(PydanticSpecAdapter.extract_specs(content_type))
|