krons 0.1.1__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 +56 -74
- krons/core/base/__init__.py +121 -0
- krons/core/{broadcaster.py → base/broadcaster.py} +7 -3
- krons/core/{element.py → base/element.py} +13 -5
- krons/core/{event.py → base/event.py} +39 -6
- krons/core/{eventbus.py → base/eventbus.py} +3 -1
- krons/core/{flow.py → base/flow.py} +11 -4
- krons/core/{graph.py → base/graph.py} +24 -8
- krons/core/{node.py → base/node.py} +44 -19
- krons/core/{pile.py → base/pile.py} +22 -8
- krons/core/{processor.py → base/processor.py} +21 -7
- krons/core/{progression.py → base/progression.py} +3 -1
- krons/{specs → core/specs}/__init__.py +0 -5
- krons/{specs → core/specs}/adapters/dataclass_field.py +16 -8
- krons/{specs → core/specs}/adapters/pydantic_adapter.py +11 -5
- krons/{specs → core/specs}/adapters/sql_ddl.py +14 -8
- krons/{specs → core/specs}/catalog/__init__.py +2 -2
- krons/{specs → core/specs}/catalog/_audit.py +2 -2
- krons/{specs → core/specs}/catalog/_common.py +2 -2
- krons/{specs → core/specs}/catalog/_content.py +4 -4
- krons/{specs → core/specs}/catalog/_enforcement.py +3 -3
- krons/{specs → core/specs}/factory.py +5 -5
- krons/{specs → core/specs}/operable.py +8 -2
- krons/{specs → core/specs}/protocol.py +4 -2
- krons/{specs → core/specs}/spec.py +23 -11
- krons/{types → core/types}/base.py +4 -2
- krons/{types → core/types}/db_types.py +2 -2
- krons/errors.py +13 -13
- krons/protocols.py +9 -4
- krons/resource/__init__.py +89 -0
- krons/{services → resource}/backend.py +48 -22
- krons/{services → resource}/endpoint.py +28 -14
- krons/{services → resource}/hook.py +20 -7
- krons/{services → resource}/imodel.py +46 -28
- krons/{services → resource}/registry.py +26 -24
- krons/{services → resource}/utilities/rate_limited_executor.py +7 -3
- krons/{services → resource}/utilities/rate_limiter.py +3 -1
- krons/{services → resource}/utilities/resilience.py +15 -5
- krons/resource/utilities/token_calculator.py +185 -0
- krons/session/__init__.py +12 -17
- krons/session/constraints.py +70 -0
- krons/session/exchange.py +11 -3
- krons/session/message.py +3 -1
- krons/session/registry.py +35 -0
- krons/session/session.py +165 -174
- krons/utils/__init__.py +45 -0
- krons/utils/_function_arg_parser.py +99 -0
- krons/utils/_pythonic_function_call.py +249 -0
- krons/utils/_to_list.py +9 -3
- krons/utils/_utils.py +6 -2
- krons/utils/concurrency/_async_call.py +4 -2
- krons/utils/concurrency/_errors.py +3 -1
- krons/utils/concurrency/_patterns.py +3 -1
- krons/utils/concurrency/_resource_tracker.py +6 -2
- krons/utils/display.py +257 -0
- krons/utils/fuzzy/__init__.py +6 -1
- krons/utils/fuzzy/_fuzzy_match.py +14 -8
- krons/utils/fuzzy/_string_similarity.py +3 -1
- 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
- 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
- krons/{operations → work/operations}/__init__.py +7 -4
- krons/{operations → work/operations}/builder.py +1 -1
- krons/{enforcement → work/operations}/context.py +36 -5
- krons/{operations → work/operations}/flow.py +13 -5
- krons/{operations → work/operations}/node.py +45 -43
- krons/work/operations/registry.py +103 -0
- krons/{specs → work}/phrase.py +130 -13
- krons/{enforcement → work}/policy.py +3 -3
- krons/work/report.py +268 -0
- krons/work/rules/__init__.py +47 -0
- krons/{enforcement → work/rules}/common/boolean.py +3 -1
- krons/{enforcement → work/rules}/common/choice.py +9 -3
- krons/{enforcement → work/rules}/common/number.py +3 -1
- krons/{enforcement → work/rules}/common/string.py +9 -3
- krons/{enforcement → work/rules}/rule.py +1 -1
- krons/{enforcement → work/rules}/validator.py +20 -5
- krons/{enforcement → work}/service.py +16 -7
- krons/work/worker.py +266 -0
- {krons-0.1.1.dist-info → krons-0.2.0.dist-info}/METADATA +15 -1
- krons-0.2.0.dist-info/RECORD +154 -0
- krons/enforcement/__init__.py +0 -57
- krons/operations/registry.py +0 -92
- krons/services/__init__.py +0 -81
- krons-0.1.1.dist-info/RECORD +0 -101
- /krons/{specs → core/specs}/adapters/__init__.py +0 -0
- /krons/{specs → core/specs}/adapters/_utils.py +0 -0
- /krons/{specs → core/specs}/adapters/factory.py +0 -0
- /krons/{types → core/types}/__init__.py +0 -0
- /krons/{types → core/types}/_sentinel.py +0 -0
- /krons/{types → core/types}/identity.py +0 -0
- /krons/{services → resource}/utilities/__init__.py +0 -0
- /krons/{services → resource}/utilities/header_factory.py +0 -0
- /krons/{enforcement → work/rules}/common/__init__.py +0 -0
- /krons/{enforcement → work/rules}/common/mapping.py +0 -0
- /krons/{enforcement → work/rules}/common/model.py +0 -0
- /krons/{enforcement → work/rules}/registry.py +0 -0
- {krons-0.1.1.dist-info → krons-0.2.0.dist-info}/WHEEL +0 -0
- {krons-0.1.1.dist-info → krons-0.2.0.dist-info}/licenses/LICENSE +0 -0
krons/core/__init__.py
CHANGED
|
@@ -1,55 +1,47 @@
|
|
|
1
1
|
# Copyright (c) 2025 - 2026, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
"""Core
|
|
4
|
+
"""Core module - Foundation primitives and submodules.
|
|
5
|
+
|
|
6
|
+
Re-exports base classes from core/base/ and exposes submodules:
|
|
7
|
+
- types: Sentinels, base types, DB types
|
|
8
|
+
- specs: Spec definitions, Operable, adapters
|
|
9
|
+
|
|
10
|
+
Note: Session (Message, Branch, Session, Exchange) is now at krons.session
|
|
11
|
+
"""
|
|
5
12
|
|
|
6
13
|
from __future__ import annotations
|
|
7
14
|
|
|
8
15
|
from typing import TYPE_CHECKING
|
|
9
16
|
|
|
10
|
-
# Lazy import mapping
|
|
17
|
+
# Lazy import mapping - delegates to core.base
|
|
11
18
|
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
|
|
12
|
-
#
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
"EventBus": ("krons.core.
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"generate_ddl": ("krons.core.
|
|
37
|
-
|
|
38
|
-
"PHRASE_REGISTRY": ("krons.core.phrase", "PHRASE_REGISTRY"),
|
|
39
|
-
"Phrase": ("krons.core.phrase", "Phrase"),
|
|
40
|
-
"PhraseConfig": ("krons.core.phrase", "PhraseConfig"),
|
|
41
|
-
"PhraseError": ("krons.core.phrase", "PhraseError"),
|
|
42
|
-
"RequirementNotMet": ("krons.core.phrase", "RequirementNotMet"),
|
|
43
|
-
"create_phrase": ("krons.core.phrase", "create_phrase"),
|
|
44
|
-
"get_phrase": ("krons.core.phrase", "get_phrase"),
|
|
45
|
-
"list_phrases": ("krons.core.phrase", "list_phrases"),
|
|
46
|
-
# pile
|
|
47
|
-
"Pile": ("krons.core.pile", "Pile"),
|
|
48
|
-
# processor
|
|
49
|
-
"Executor": ("krons.core.processor", "Executor"),
|
|
50
|
-
"Processor": ("krons.core.processor", "Processor"),
|
|
51
|
-
# progression
|
|
52
|
-
"Progression": ("krons.core.progression", "Progression"),
|
|
19
|
+
# Registries
|
|
20
|
+
"NODE_REGISTRY": ("krons.core.base", "NODE_REGISTRY"),
|
|
21
|
+
"PERSISTABLE_NODE_REGISTRY": ("krons.core.base", "PERSISTABLE_NODE_REGISTRY"),
|
|
22
|
+
# Classes
|
|
23
|
+
"Broadcaster": ("krons.core.base", "Broadcaster"),
|
|
24
|
+
"Edge": ("krons.core.base", "Edge"),
|
|
25
|
+
"EdgeCondition": ("krons.core.base", "EdgeCondition"),
|
|
26
|
+
"Element": ("krons.core.base", "Element"),
|
|
27
|
+
"Event": ("krons.core.base", "Event"),
|
|
28
|
+
"EventBus": ("krons.core.base", "EventBus"),
|
|
29
|
+
"EventStatus": ("krons.core.base", "EventStatus"),
|
|
30
|
+
"Execution": ("krons.core.base", "Execution"),
|
|
31
|
+
"Executor": ("krons.core.base", "Executor"),
|
|
32
|
+
"Flow": ("krons.core.base", "Flow"),
|
|
33
|
+
"Graph": ("krons.core.base", "Graph"),
|
|
34
|
+
"Handler": ("krons.core.base", "Handler"),
|
|
35
|
+
"Node": ("krons.core.base", "Node"),
|
|
36
|
+
"NodeConfig": ("krons.core.base", "NodeConfig"),
|
|
37
|
+
"Pile": ("krons.core.base", "Pile"),
|
|
38
|
+
"Processor": ("krons.core.base", "Processor"),
|
|
39
|
+
"Progression": ("krons.core.base", "Progression"),
|
|
40
|
+
# Functions
|
|
41
|
+
"create_node": ("krons.core.base", "create_node"),
|
|
42
|
+
"generate_all_ddl": ("krons.core.base", "generate_all_ddl"),
|
|
43
|
+
"generate_ddl": ("krons.core.base", "generate_ddl"),
|
|
44
|
+
"get_fk_dependencies": ("krons.core.base", "get_fk_dependencies"),
|
|
53
45
|
}
|
|
54
46
|
|
|
55
47
|
_LOADED: dict[str, object] = {}
|
|
@@ -79,41 +71,36 @@ def __dir__() -> list[str]:
|
|
|
79
71
|
|
|
80
72
|
# TYPE_CHECKING block for static analysis
|
|
81
73
|
if TYPE_CHECKING:
|
|
82
|
-
from .
|
|
83
|
-
from .element import Element
|
|
84
|
-
from .event import Event, EventStatus, Execution
|
|
85
|
-
from .eventbus import EventBus, Handler
|
|
86
|
-
from .flow import Flow
|
|
87
|
-
from .graph import Edge, EdgeCondition, Graph
|
|
88
|
-
from .node import (
|
|
89
|
-
DEFAULT_NODE_CONFIG,
|
|
74
|
+
from krons.core.base import (
|
|
90
75
|
NODE_REGISTRY,
|
|
91
76
|
PERSISTABLE_NODE_REGISTRY,
|
|
77
|
+
Broadcaster,
|
|
78
|
+
Edge,
|
|
79
|
+
EdgeCondition,
|
|
80
|
+
Element,
|
|
81
|
+
Event,
|
|
82
|
+
EventBus,
|
|
83
|
+
EventStatus,
|
|
84
|
+
Execution,
|
|
85
|
+
Executor,
|
|
86
|
+
Flow,
|
|
87
|
+
Graph,
|
|
88
|
+
Handler,
|
|
92
89
|
Node,
|
|
93
90
|
NodeConfig,
|
|
91
|
+
Pile,
|
|
92
|
+
Processor,
|
|
93
|
+
Progression,
|
|
94
94
|
create_node,
|
|
95
|
+
generate_all_ddl,
|
|
95
96
|
generate_ddl,
|
|
97
|
+
get_fk_dependencies,
|
|
96
98
|
)
|
|
97
|
-
from .phrase import (
|
|
98
|
-
PHRASE_REGISTRY,
|
|
99
|
-
Phrase,
|
|
100
|
-
PhraseConfig,
|
|
101
|
-
PhraseError,
|
|
102
|
-
RequirementNotMet,
|
|
103
|
-
create_phrase,
|
|
104
|
-
get_phrase,
|
|
105
|
-
list_phrases,
|
|
106
|
-
)
|
|
107
|
-
from .pile import Pile
|
|
108
|
-
from .processor import Executor, Processor
|
|
109
|
-
from .progression import Progression
|
|
110
99
|
|
|
111
100
|
__all__ = [
|
|
112
|
-
# constants
|
|
113
|
-
"DEFAULT_NODE_CONFIG",
|
|
101
|
+
# constants/registries
|
|
114
102
|
"NODE_REGISTRY",
|
|
115
103
|
"PERSISTABLE_NODE_REGISTRY",
|
|
116
|
-
"PHRASE_REGISTRY",
|
|
117
104
|
# classes
|
|
118
105
|
"Broadcaster",
|
|
119
106
|
"Edge",
|
|
@@ -129,17 +116,12 @@ __all__ = [
|
|
|
129
116
|
"Handler",
|
|
130
117
|
"Node",
|
|
131
118
|
"NodeConfig",
|
|
132
|
-
"Phrase",
|
|
133
|
-
"PhraseConfig",
|
|
134
|
-
"PhraseError",
|
|
135
119
|
"Pile",
|
|
136
120
|
"Processor",
|
|
137
121
|
"Progression",
|
|
138
|
-
"RequirementNotMet",
|
|
139
122
|
# functions
|
|
140
123
|
"create_node",
|
|
141
|
-
"
|
|
124
|
+
"generate_all_ddl",
|
|
142
125
|
"generate_ddl",
|
|
143
|
-
"
|
|
144
|
-
"list_phrases",
|
|
126
|
+
"get_fk_dependencies",
|
|
145
127
|
]
|
|
@@ -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 krons.utils import is_coro_func
|
|
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,6 +11,7 @@ from uuid import UUID, uuid4
|
|
|
11
11
|
import orjson
|
|
12
12
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
13
13
|
|
|
14
|
+
from krons.core.types import MaybeSentinel, Unset, UnsetType, is_sentinel, is_unset
|
|
14
15
|
from krons.protocols import (
|
|
15
16
|
Deserializable,
|
|
16
17
|
Hashable,
|
|
@@ -18,7 +19,6 @@ from krons.protocols import (
|
|
|
18
19
|
Serializable,
|
|
19
20
|
implements,
|
|
20
21
|
)
|
|
21
|
-
from krons.types import MaybeSentinel, Unset, UnsetType, is_sentinel, is_unset
|
|
22
22
|
from krons.utils import (
|
|
23
23
|
coerce_created_at,
|
|
24
24
|
json_dump,
|
|
@@ -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,9 +11,16 @@ from typing import Any, final
|
|
|
11
11
|
import orjson
|
|
12
12
|
from pydantic import Field, field_serializer, field_validator
|
|
13
13
|
|
|
14
|
-
from krons.
|
|
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
|
|
15
23
|
from krons.protocols import Invocable, Serializable, implements
|
|
16
|
-
from krons.types import Enum, MaybeSentinel, MaybeUnset, Unset, is_sentinel, is_unset
|
|
17
24
|
from krons.utils import async_synchronized, concurrency, json_dumpb
|
|
18
25
|
|
|
19
26
|
from .element import LN_ELEMENT_FIELDS, Element
|
|
@@ -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
|
+
)
|
|
@@ -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,9 +9,9 @@ from uuid import UUID
|
|
|
9
9
|
|
|
10
10
|
from pydantic import Field, PrivateAttr, field_validator, model_validator
|
|
11
11
|
|
|
12
|
+
from krons.core.types import Unset, UnsetType
|
|
12
13
|
from krons.errors import ExistsError, NotFoundError
|
|
13
14
|
from krons.protocols import Serializable, implements
|
|
14
|
-
from krons.types import Unset, UnsetType
|
|
15
15
|
from krons.utils import extract_types, synchronized
|
|
16
16
|
|
|
17
17
|
from .element import Element
|
|
@@ -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,9 +11,9 @@ 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 krons.core.types import Unset, UnsetType, is_unset
|
|
14
15
|
from krons.errors import NotFoundError
|
|
15
16
|
from krons.protocols import Containable, Deserializable, Serializable, implements
|
|
16
|
-
from krons.types import Unset, UnsetType, is_unset
|
|
17
17
|
from krons.utils import synchronized
|
|
18
18
|
|
|
19
19
|
from .element import Element
|
|
@@ -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.
|