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
|
@@ -12,11 +12,11 @@ from uuid import UUID
|
|
|
12
12
|
from pydantic import Field, PrivateAttr, field_serializer, field_validator
|
|
13
13
|
from typing_extensions import override
|
|
14
14
|
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
19
|
-
from
|
|
15
|
+
from krons.core.types import Unset, UnsetType, is_unset
|
|
16
|
+
from krons.errors import ExistsError, NotFoundError
|
|
17
|
+
from krons.protocols import Containable, Deserializable, Serializable, implements
|
|
18
|
+
from krons.utils import extract_types, load_type_from_string, synchronized
|
|
19
|
+
from krons.utils.concurrency import Lock as AsyncLock
|
|
20
20
|
|
|
21
21
|
from .element import Element
|
|
22
22
|
from .progression import Progression
|
|
@@ -59,7 +59,9 @@ class Pile(Element, Generic[T]):
|
|
|
59
59
|
@property
|
|
60
60
|
def progression(self) -> Progression:
|
|
61
61
|
"""Read-only copy of progression order."""
|
|
62
|
-
return Progression(
|
|
62
|
+
return Progression(
|
|
63
|
+
order=list(self._progression.order), name=self._progression.name
|
|
64
|
+
)
|
|
63
65
|
|
|
64
66
|
item_type: set[type] | None = Field(
|
|
65
67
|
default=None,
|
|
@@ -104,7 +106,9 @@ class Pile(Element, Generic[T]):
|
|
|
104
106
|
NotFoundError: If order contains UUID not in items
|
|
105
107
|
TypeError: If item type validation fails
|
|
106
108
|
"""
|
|
107
|
-
super().__init__(
|
|
109
|
+
super().__init__(
|
|
110
|
+
**{"item_type": item_type, "strict_type": strict_type, **kwargs}
|
|
111
|
+
)
|
|
108
112
|
|
|
109
113
|
if items:
|
|
110
114
|
for item in items:
|
|
@@ -128,10 +132,14 @@ class Pile(Element, Generic[T]):
|
|
|
128
132
|
def to_dict(
|
|
129
133
|
self,
|
|
130
134
|
mode: Literal["python", "json", "db"] = "python",
|
|
131
|
-
created_at_format: (
|
|
135
|
+
created_at_format: (
|
|
136
|
+
Literal["datetime", "isoformat", "timestamp"] | UnsetType
|
|
137
|
+
) = Unset,
|
|
132
138
|
meta_key: str | UnsetType = Unset,
|
|
133
139
|
item_meta_key: str | UnsetType = Unset,
|
|
134
|
-
item_created_at_format: (
|
|
140
|
+
item_created_at_format: (
|
|
141
|
+
Literal["datetime", "isoformat", "timestamp"] | UnsetType
|
|
142
|
+
) = Unset,
|
|
135
143
|
**kwargs: Any,
|
|
136
144
|
) -> dict[str, Any]:
|
|
137
145
|
"""Serialize pile with items in progression order.
|
|
@@ -350,7 +358,9 @@ class Pile(Element, Generic[T]):
|
|
|
350
358
|
raise TypeError("Cannot mix int and UUID in list/tuple indexing")
|
|
351
359
|
items = [self.get(uid) for uid in keys]
|
|
352
360
|
else:
|
|
353
|
-
raise TypeError(
|
|
361
|
+
raise TypeError(
|
|
362
|
+
f"list/tuple must contain only int or UUID, got {type(first)}"
|
|
363
|
+
)
|
|
354
364
|
|
|
355
365
|
return Pile(
|
|
356
366
|
items=items,
|
|
@@ -530,7 +540,9 @@ class Pile(Element, Generic[T]):
|
|
|
530
540
|
and item_type_data
|
|
531
541
|
and isinstance(item_type_data[0], str)
|
|
532
542
|
):
|
|
533
|
-
allowed_types = {
|
|
543
|
+
allowed_types = {
|
|
544
|
+
load_type_from_string(type_str) for type_str in item_type_data
|
|
545
|
+
}
|
|
534
546
|
else:
|
|
535
547
|
allowed_types = extract_types(item_type_data)
|
|
536
548
|
|
|
@@ -549,7 +561,9 @@ class Pile(Element, Generic[T]):
|
|
|
549
561
|
"(strict_type=True)"
|
|
550
562
|
)
|
|
551
563
|
else:
|
|
552
|
-
if not any(
|
|
564
|
+
if not any(
|
|
565
|
+
issubclass(item_type_actual, t) for t in allowed_types
|
|
566
|
+
):
|
|
553
567
|
raise TypeError(
|
|
554
568
|
f"Item type {kron_class} is not a subclass of any allowed type {allowed_types}"
|
|
555
569
|
)
|
|
@@ -6,8 +6,8 @@ from __future__ import annotations
|
|
|
6
6
|
import math
|
|
7
7
|
from typing import TYPE_CHECKING, Any, ClassVar, Self
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
from
|
|
9
|
+
from krons.errors import ConfigurationError, NotFoundError, QueueFullError
|
|
10
|
+
from krons.utils import concurrency
|
|
11
11
|
|
|
12
12
|
from .event import Event, EventStatus
|
|
13
13
|
from .flow import Flow
|
|
@@ -77,13 +77,19 @@ class Processor:
|
|
|
77
77
|
if queue_capacity < 1:
|
|
78
78
|
raise ValueError("Queue capacity must be greater than 0.")
|
|
79
79
|
if queue_capacity > 10000:
|
|
80
|
-
raise ValueError(
|
|
80
|
+
raise ValueError(
|
|
81
|
+
"Queue capacity must be <= 10000 (prevent unbounded batches)."
|
|
82
|
+
)
|
|
81
83
|
|
|
82
84
|
# Validate capacity_refresh_time (prevent hot loop or starvation)
|
|
83
85
|
if capacity_refresh_time < 0.01:
|
|
84
|
-
raise ValueError(
|
|
86
|
+
raise ValueError(
|
|
87
|
+
"Capacity refresh time must be >= 0.01s (prevent CPU hot loop)."
|
|
88
|
+
)
|
|
85
89
|
if capacity_refresh_time > 3600:
|
|
86
|
-
raise ValueError(
|
|
90
|
+
raise ValueError(
|
|
91
|
+
"Capacity refresh time must be <= 3600s (prevent starvation)."
|
|
92
|
+
)
|
|
87
93
|
|
|
88
94
|
# Validate concurrency_limit
|
|
89
95
|
if concurrency_limit < 1:
|
|
@@ -105,7 +111,9 @@ class Processor:
|
|
|
105
111
|
self.concurrency_limit = concurrency_limit
|
|
106
112
|
|
|
107
113
|
# Priority queue: (priority, event_uuid) tuples, min-heap ordering
|
|
108
|
-
self.queue: concurrency.PriorityQueue[tuple[float, UUID]] =
|
|
114
|
+
self.queue: concurrency.PriorityQueue[tuple[float, UUID]] = (
|
|
115
|
+
concurrency.PriorityQueue()
|
|
116
|
+
)
|
|
109
117
|
|
|
110
118
|
self._available_capacity = queue_capacity
|
|
111
119
|
self._execution_mode = False
|
|
@@ -231,7 +239,9 @@ class Processor:
|
|
|
231
239
|
self._denial_counts.pop(event_id, None)
|
|
232
240
|
|
|
233
241
|
if self.executor:
|
|
234
|
-
await self.executor._update_progression(
|
|
242
|
+
await self.executor._update_progression(
|
|
243
|
+
next_event, EventStatus.PROCESSING
|
|
244
|
+
)
|
|
235
245
|
|
|
236
246
|
if next_event.streaming:
|
|
237
247
|
|
|
@@ -255,7 +265,9 @@ class Processor:
|
|
|
255
265
|
if self.executor:
|
|
256
266
|
await self.executor._update_progression(event)
|
|
257
267
|
|
|
258
|
-
tg.start_soon(
|
|
268
|
+
tg.start_soon(
|
|
269
|
+
self._with_semaphore, invoke_and_update(next_event)
|
|
270
|
+
)
|
|
259
271
|
|
|
260
272
|
events_processed += 1
|
|
261
273
|
self._available_capacity -= 1
|
|
@@ -270,7 +282,9 @@ class Processor:
|
|
|
270
282
|
|
|
271
283
|
if denial_count >= 3:
|
|
272
284
|
if self.executor:
|
|
273
|
-
await self.executor._update_progression(
|
|
285
|
+
await self.executor._update_progression(
|
|
286
|
+
next_event, EventStatus.ABORTED
|
|
287
|
+
)
|
|
274
288
|
self._denial_counts.pop(event_id, None)
|
|
275
289
|
else:
|
|
276
290
|
backoff = denial_count * 1.0
|
|
@@ -9,8 +9,8 @@ from uuid import UUID
|
|
|
9
9
|
|
|
10
10
|
from pydantic import Field, PrivateAttr, field_validator
|
|
11
11
|
|
|
12
|
-
from
|
|
13
|
-
from
|
|
12
|
+
from krons.errors import NotFoundError
|
|
13
|
+
from krons.protocols import Containable, implements
|
|
14
14
|
|
|
15
15
|
from .element import Element
|
|
16
16
|
|
|
@@ -177,7 +177,9 @@ class Progression(Element):
|
|
|
177
177
|
"""Set item(s) at index. Slice assignment requires list value."""
|
|
178
178
|
if isinstance(index, slice):
|
|
179
179
|
if not isinstance(value, list):
|
|
180
|
-
raise TypeError(
|
|
180
|
+
raise TypeError(
|
|
181
|
+
f"Cannot assign {type(value).__name__} to slice, expected list"
|
|
182
|
+
)
|
|
181
183
|
new_uids = [self._coerce_id(v) for v in value]
|
|
182
184
|
self.order[index] = new_uids
|
|
183
185
|
self._rebuild_members()
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
from .adapters.factory import AdapterType, get_adapter
|
|
5
5
|
from .catalog import AuditSpecs, CommonSpecs, ContentSpecs
|
|
6
6
|
from .operable import Operable
|
|
7
|
-
from .phrase import CrudOperation, CrudPattern, Phrase, phrase
|
|
8
7
|
from .protocol import SpecAdapter
|
|
9
8
|
from .spec import CommonMeta, Spec
|
|
10
9
|
|
|
@@ -14,12 +13,8 @@ __all__ = (
|
|
|
14
13
|
"CommonMeta",
|
|
15
14
|
"CommonSpecs",
|
|
16
15
|
"ContentSpecs",
|
|
17
|
-
"CrudOperation",
|
|
18
|
-
"CrudPattern",
|
|
19
16
|
"Operable",
|
|
20
|
-
"Phrase",
|
|
21
17
|
"Spec",
|
|
22
18
|
"SpecAdapter",
|
|
23
19
|
"get_adapter",
|
|
24
|
-
"phrase",
|
|
25
20
|
)
|
|
@@ -17,14 +17,14 @@ from dataclasses import field as dc_field
|
|
|
17
17
|
from dataclasses import fields
|
|
18
18
|
from typing import TYPE_CHECKING, Any
|
|
19
19
|
|
|
20
|
-
from
|
|
20
|
+
from krons.core.types._sentinel import Unset, UnsetType, is_sentinel
|
|
21
21
|
|
|
22
22
|
from ..protocol import SpecAdapter
|
|
23
23
|
from ..spec import Spec
|
|
24
24
|
from ._utils import resolve_annotation_to_base_types
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
|
-
from
|
|
27
|
+
from krons.core.types.base import DataClass, ModelConfig, Params
|
|
28
28
|
|
|
29
29
|
from ..operable import Operable
|
|
30
30
|
|
|
@@ -71,7 +71,9 @@ def _make_validator_method(validators: dict[str, list[Any]], is_frozen: bool) ->
|
|
|
71
71
|
errors.append(ValueError(f"Validation failed for '{fname}': {e}"))
|
|
72
72
|
|
|
73
73
|
if errors:
|
|
74
|
-
raise ExceptionGroup(
|
|
74
|
+
raise ExceptionGroup(
|
|
75
|
+
f"Field validation failed for {type(self).__name__}", errors
|
|
76
|
+
)
|
|
75
77
|
|
|
76
78
|
return _validate_with_field_validators
|
|
77
79
|
|
|
@@ -149,7 +151,7 @@ class DataClassSpecAdapter(SpecAdapter[dict[str, Any]]):
|
|
|
149
151
|
Returns:
|
|
150
152
|
Dynamically created dataclass with validators wired in
|
|
151
153
|
"""
|
|
152
|
-
from
|
|
154
|
+
from krons.core.types.base import DataClass, Params
|
|
153
155
|
|
|
154
156
|
use_specs = op.get_specs(include=include, exclude=exclude)
|
|
155
157
|
|
|
@@ -176,7 +178,9 @@ class DataClassSpecAdapter(SpecAdapter[dict[str, Any]]):
|
|
|
176
178
|
annotations[field_name] = spec.annotation
|
|
177
179
|
|
|
178
180
|
if "default_factory" in field_kwargs:
|
|
179
|
-
class_attrs[field_name] = dc_field(
|
|
181
|
+
class_attrs[field_name] = dc_field(
|
|
182
|
+
default_factory=field_kwargs["default_factory"]
|
|
183
|
+
)
|
|
180
184
|
elif "default" in field_kwargs:
|
|
181
185
|
class_attrs[field_name] = field_kwargs["default"]
|
|
182
186
|
|
|
@@ -221,15 +225,19 @@ class DataClassSpecAdapter(SpecAdapter[dict[str, Any]]):
|
|
|
221
225
|
return instance.to_dict()
|
|
222
226
|
|
|
223
227
|
@classmethod
|
|
224
|
-
def extract_specs(
|
|
228
|
+
def extract_specs(
|
|
229
|
+
cls, structure: type[Params] | type[DataClass]
|
|
230
|
+
) -> tuple[Spec, ...]:
|
|
225
231
|
"""Extract Specs from DataClass/Params, preserving defaults and type modifiers.
|
|
226
232
|
|
|
227
233
|
Raises:
|
|
228
234
|
TypeError: If structure is not a DataClass or Params subclass
|
|
229
235
|
"""
|
|
230
|
-
from
|
|
236
|
+
from krons.core.types.base import DataClass, Params
|
|
231
237
|
|
|
232
|
-
if not isinstance(structure, type) or not issubclass(
|
|
238
|
+
if not isinstance(structure, type) or not issubclass(
|
|
239
|
+
structure, (DataClass, Params)
|
|
240
|
+
):
|
|
233
241
|
raise TypeError(
|
|
234
242
|
f"structure must be a DataClass or Params subclass, got {type(structure)}"
|
|
235
243
|
)
|
|
@@ -20,15 +20,21 @@ from pydantic.fields import FieldInfo
|
|
|
20
20
|
from pydantic_core import PydanticUndefined
|
|
21
21
|
from pydantic_core._pydantic_core import PydanticUndefinedType
|
|
22
22
|
|
|
23
|
-
from
|
|
24
|
-
from
|
|
25
|
-
from
|
|
26
|
-
|
|
23
|
+
from krons.core.specs.protocol import SpecAdapter
|
|
24
|
+
from krons.core.specs.spec import Spec
|
|
25
|
+
from krons.core.types._sentinel import (
|
|
26
|
+
Unset,
|
|
27
|
+
UnsetType,
|
|
28
|
+
is_sentinel,
|
|
29
|
+
is_unset,
|
|
30
|
+
not_sentinel,
|
|
31
|
+
)
|
|
32
|
+
from krons.core.types.db_types import FKMeta, VectorMeta
|
|
27
33
|
|
|
28
34
|
from ._utils import resolve_annotation_to_base_types
|
|
29
35
|
|
|
30
36
|
if TYPE_CHECKING:
|
|
31
|
-
from
|
|
37
|
+
from krons.core.specs.operable import Operable
|
|
32
38
|
|
|
33
39
|
__all__ = ("PydanticSpecAdapter",)
|
|
34
40
|
|
|
@@ -27,16 +27,22 @@ from enum import StrEnum
|
|
|
27
27
|
from typing import TYPE_CHECKING, Annotated, Any, get_args, get_origin
|
|
28
28
|
from uuid import UUID
|
|
29
29
|
|
|
30
|
-
from
|
|
31
|
-
from
|
|
32
|
-
|
|
30
|
+
from krons.core.types._sentinel import Unset, UnsetType, is_sentinel
|
|
31
|
+
from krons.core.types.db_types import (
|
|
32
|
+
FK,
|
|
33
|
+
FKMeta,
|
|
34
|
+
Vector,
|
|
35
|
+
VectorMeta,
|
|
36
|
+
extract_kron_db_meta,
|
|
37
|
+
)
|
|
38
|
+
from krons.utils.sql import validate_identifier
|
|
33
39
|
|
|
34
40
|
from ..protocol import SpecAdapter
|
|
35
41
|
from ._utils import resolve_annotation_to_base_types
|
|
36
42
|
|
|
37
43
|
if TYPE_CHECKING:
|
|
38
|
-
from
|
|
39
|
-
from
|
|
44
|
+
from krons.core.specs.operable import Operable
|
|
45
|
+
from krons.core.specs.spec import Spec
|
|
40
46
|
|
|
41
47
|
__all__ = (
|
|
42
48
|
# Enums
|
|
@@ -414,9 +420,7 @@ class UniqueConstraintSpec:
|
|
|
414
420
|
for col in self.columns:
|
|
415
421
|
validate_identifier(col, "column name")
|
|
416
422
|
cols = ", ".join(f'"{c}"' for c in self.columns)
|
|
417
|
-
return (
|
|
418
|
-
f'ALTER TABLE "{schema}"."{table_name}" ADD CONSTRAINT "{self.name}" UNIQUE ({cols});'
|
|
419
|
-
)
|
|
423
|
+
return f'ALTER TABLE "{schema}"."{table_name}" ADD CONSTRAINT "{self.name}" UNIQUE ({cols});'
|
|
420
424
|
|
|
421
425
|
|
|
422
426
|
@dataclass(frozen=True, slots=True)
|
|
@@ -468,7 +472,9 @@ class TableSpec:
|
|
|
468
472
|
col_lines = col_separator.join(col_defs)
|
|
469
473
|
|
|
470
474
|
exists_clause = "IF NOT EXISTS " if if_not_exists else ""
|
|
471
|
-
return
|
|
475
|
+
return (
|
|
476
|
+
f"CREATE TABLE {exists_clause}{self.qualified_name} (\n {col_lines}\n);"
|
|
477
|
+
)
|
|
472
478
|
|
|
473
479
|
def to_full_ddl(self) -> list[str]:
|
|
474
480
|
"""Generate all DDL statements for this table.
|
|
@@ -672,7 +678,7 @@ class SchemaSpec:
|
|
|
672
678
|
Returns:
|
|
673
679
|
SchemaSpec with version hash computed from table definitions.
|
|
674
680
|
"""
|
|
675
|
-
from
|
|
681
|
+
from krons.utils import compute_hash
|
|
676
682
|
|
|
677
683
|
tables = [
|
|
678
684
|
TableSpec.from_operable(op, name, schema=schema)
|
|
@@ -9,14 +9,14 @@ Pre-defined Specs for common database patterns:
|
|
|
9
9
|
- **CommonSpecs**: name, slug, status, email, phone, tenant_id, settings
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
12
|
-
from
|
|
12
|
+
from krons.core.specs.catalog import ContentSpecs, AuditSpecs
|
|
13
13
|
|
|
14
14
|
content_specs = ContentSpecs.get_specs(dim=1536)
|
|
15
15
|
audit_specs = AuditSpecs.get_specs(use_uuid=True)
|
|
16
16
|
all_specs = content_specs + audit_specs
|
|
17
17
|
|
|
18
18
|
For custom Specs, use the factories directly:
|
|
19
|
-
from
|
|
19
|
+
from krons.core.specs.factory import create_embedding_spec, create_content_spec
|
|
20
20
|
|
|
21
21
|
my_embedding = create_embedding_spec("embedding", dim=1536)
|
|
22
22
|
my_content = create_content_spec("payload", content_type=MyModel)
|
|
@@ -10,9 +10,9 @@ from uuid import UUID
|
|
|
10
10
|
|
|
11
11
|
from pydantic import BaseModel, Field
|
|
12
12
|
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from
|
|
13
|
+
from krons.core.specs.operable import Operable
|
|
14
|
+
from krons.core.specs.spec import Spec
|
|
15
|
+
from krons.utils import now_utc
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class AuditSpecs(BaseModel):
|
|
@@ -10,8 +10,8 @@ from uuid import UUID
|
|
|
10
10
|
|
|
11
11
|
from pydantic import BaseModel
|
|
12
12
|
|
|
13
|
-
from
|
|
14
|
-
from
|
|
13
|
+
from krons.core.specs.operable import Operable
|
|
14
|
+
from krons.core.specs.spec import Spec
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class CommonSpecs(BaseModel):
|
|
@@ -11,11 +11,11 @@ from uuid import UUID, uuid4
|
|
|
11
11
|
|
|
12
12
|
from pydantic import BaseModel, Field
|
|
13
13
|
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
14
|
+
from krons.core.specs.operable import Operable
|
|
15
|
+
from krons.core.specs.spec import Spec
|
|
16
|
+
from krons.core.types._sentinel import Unset, UnsetType
|
|
17
|
+
from krons.core.types.db_types import VectorMeta
|
|
18
|
+
from krons.utils import now_utc
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class ContentSpecs(BaseModel):
|
|
@@ -10,10 +10,10 @@ from typing import Any
|
|
|
10
10
|
|
|
11
11
|
from pydantic import BaseModel, Field, field_validator
|
|
12
12
|
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
13
|
+
from krons.core.specs.operable import Operable
|
|
14
|
+
from krons.core.specs.spec import Spec
|
|
15
|
+
from krons.core.types.base import Enum
|
|
16
|
+
from krons.utils import now_utc
|
|
17
17
|
|
|
18
18
|
__all__ = (
|
|
19
19
|
"EnforcementLevel",
|
|
@@ -8,14 +8,14 @@ from __future__ import annotations
|
|
|
8
8
|
from datetime import datetime
|
|
9
9
|
from uuid import UUID, uuid4
|
|
10
10
|
|
|
11
|
-
from
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
11
|
+
from krons.core.specs.spec import Spec, not_sentinel
|
|
12
|
+
from krons.core.types import UnsetType
|
|
13
|
+
from krons.core.types._sentinel import Unset
|
|
14
|
+
from krons.core.types.base import is_sentinel
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def create_datetime_spec(name: str, *, use_default: bool) -> Spec:
|
|
18
|
-
from
|
|
18
|
+
from krons.utils._utils import coerce_created_at, now_utc
|
|
19
19
|
|
|
20
20
|
return Spec(
|
|
21
21
|
datetime,
|
|
@@ -26,7 +26,7 @@ def create_datetime_spec(name: str, *, use_default: bool) -> Spec:
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def create_uuid_spec(name: str, *, use_default: bool) -> Spec:
|
|
29
|
-
from
|
|
29
|
+
from krons.utils._utils import to_uuid
|
|
30
30
|
|
|
31
31
|
return Spec(
|
|
32
32
|
UUID,
|
|
@@ -74,7 +74,7 @@ def create_embedding_spec(
|
|
|
74
74
|
return Spec(list[float], name=name, default_factory=list)
|
|
75
75
|
return Spec(list[float], name=name)
|
|
76
76
|
|
|
77
|
-
from
|
|
77
|
+
from krons.core.specs.adapters.sql_ddl import Vector
|
|
78
78
|
|
|
79
79
|
return Spec(Vector[dim], name=name)
|
|
80
80
|
|
|
@@ -6,8 +6,14 @@ from __future__ import annotations
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from typing import TYPE_CHECKING, Any, Self
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
|
|
9
|
+
from krons.core.types._sentinel import (
|
|
10
|
+
MaybeUnset,
|
|
11
|
+
Unset,
|
|
12
|
+
UnsetType,
|
|
13
|
+
is_unset,
|
|
14
|
+
not_sentinel,
|
|
15
|
+
)
|
|
16
|
+
from krons.protocols import Allowable, Hashable, implements
|
|
11
17
|
|
|
12
18
|
from .adapters.factory import AdapterType, get_adapter
|
|
13
19
|
from .protocol import SpecAdapter
|
|
@@ -311,4 +317,4 @@ class Operable:
|
|
|
311
317
|
Returns:
|
|
312
318
|
Dict representation of the instance
|
|
313
319
|
"""
|
|
314
|
-
return self.adapter.dump_instance(instance
|
|
320
|
+
return self.adapter.dump_instance(instance)
|
|
@@ -24,7 +24,7 @@ from __future__ import annotations
|
|
|
24
24
|
from abc import ABC, abstractmethod
|
|
25
25
|
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
26
26
|
|
|
27
|
-
from
|
|
27
|
+
from krons.core.types._sentinel import Unset, UnsetType
|
|
28
28
|
|
|
29
29
|
if TYPE_CHECKING:
|
|
30
30
|
from .operable import Operable
|
|
@@ -126,7 +126,9 @@ class SpecAdapter(ABC, Generic[F]):
|
|
|
126
126
|
Raises:
|
|
127
127
|
NotImplementedError: If adapter doesn't support instance creation
|
|
128
128
|
"""
|
|
129
|
-
raise NotImplementedError(
|
|
129
|
+
raise NotImplementedError(
|
|
130
|
+
f"{cls.__name__} does not support instance validation"
|
|
131
|
+
)
|
|
130
132
|
|
|
131
133
|
@classmethod
|
|
132
134
|
def dump_instance(cls, instance: Any, **kwargs) -> dict[str, Any]:
|
|
@@ -11,16 +11,16 @@ from collections.abc import Callable
|
|
|
11
11
|
from dataclasses import dataclass
|
|
12
12
|
from typing import Annotated, Any, Self
|
|
13
13
|
|
|
14
|
-
from
|
|
15
|
-
from kronos.types._sentinel import (
|
|
14
|
+
from krons.core.types._sentinel import (
|
|
16
15
|
MaybeUndefined,
|
|
17
16
|
Undefined,
|
|
18
17
|
is_sentinel,
|
|
19
18
|
is_undefined,
|
|
20
19
|
not_sentinel,
|
|
21
20
|
)
|
|
22
|
-
from
|
|
23
|
-
from
|
|
21
|
+
from krons.core.types.base import Enum, Meta
|
|
22
|
+
from krons.protocols import Hashable, implements
|
|
23
|
+
from krons.utils.concurrency import is_coro_func
|
|
24
24
|
|
|
25
25
|
# Global cache for annotated types with bounded size
|
|
26
26
|
_MAX_CACHE_SIZE = int(os.environ.get("kron_FIELD_CACHE_SIZE", "10000"))
|
|
@@ -64,13 +64,17 @@ class CommonMeta(Enum):
|
|
|
64
64
|
errors: list[Exception] = []
|
|
65
65
|
|
|
66
66
|
if kw.get("default") and kw.get("default_factory"):
|
|
67
|
-
errors.append(
|
|
67
|
+
errors.append(
|
|
68
|
+
ValueError("Cannot provide both 'default' and 'default_factory'")
|
|
69
|
+
)
|
|
68
70
|
if (_df := kw.get("default_factory")) and not callable(_df):
|
|
69
71
|
errors.append(ValueError("'default_factory' must be callable"))
|
|
70
72
|
if _val := kw.get("validator"):
|
|
71
73
|
_val = [_val] if not isinstance(_val, list) else _val
|
|
72
74
|
if not all(callable(v) for v in _val):
|
|
73
|
-
errors.append(
|
|
75
|
+
errors.append(
|
|
76
|
+
ValueError("Validators must be a list of functions or a function")
|
|
77
|
+
)
|
|
74
78
|
|
|
75
79
|
if errors:
|
|
76
80
|
raise ExceptionGroup("Metadata validation failed", errors)
|
|
@@ -81,7 +85,7 @@ class CommonMeta(Enum):
|
|
|
81
85
|
) -> tuple[Meta, ...]:
|
|
82
86
|
"""Prepare metadata tuple from args/kw. Validates no duplicates, constraints."""
|
|
83
87
|
# Lazy import to avoid circular dependency
|
|
84
|
-
from
|
|
88
|
+
from krons.utils._to_list import to_list
|
|
85
89
|
|
|
86
90
|
seen_keys = set()
|
|
87
91
|
metas = []
|
|
@@ -192,7 +196,9 @@ class Spec:
|
|
|
192
196
|
or isinstance(base_type, types.UnionType)
|
|
193
197
|
)
|
|
194
198
|
if not is_valid_type:
|
|
195
|
-
raise ValueError(
|
|
199
|
+
raise ValueError(
|
|
200
|
+
f"base_type must be a type or type annotation, got {base_type}"
|
|
201
|
+
)
|
|
196
202
|
|
|
197
203
|
if kw.get("default_factory") and is_coro_func(kw["default_factory"]):
|
|
198
204
|
import warnings
|
|
@@ -389,7 +395,9 @@ class Spec:
|
|
|
389
395
|
|
|
390
396
|
return spec
|
|
391
397
|
|
|
392
|
-
def with_validator(
|
|
398
|
+
def with_validator(
|
|
399
|
+
self, validator: Callable[..., Any] | list[Callable[..., Any]]
|
|
400
|
+
) -> Self:
|
|
393
401
|
"""Return new Spec with validator function(s) attached."""
|
|
394
402
|
return self.with_updates(validator=validator)
|
|
395
403
|
|
|
@@ -407,7 +415,7 @@ class Spec:
|
|
|
407
415
|
if not is_undefined(fk):
|
|
408
416
|
from uuid import UUID
|
|
409
417
|
|
|
410
|
-
from
|
|
418
|
+
from krons.core.types.db_types import FKMeta
|
|
411
419
|
|
|
412
420
|
t_ = Annotated[UUID, FKMeta(fk)] # type: ignore[valid-type]
|
|
413
421
|
if self.is_listable:
|
|
@@ -431,7 +439,9 @@ class Spec:
|
|
|
431
439
|
_annotated_cache.move_to_end(cache_key)
|
|
432
440
|
return _annotated_cache[cache_key]
|
|
433
441
|
|
|
434
|
-
actual_type =
|
|
442
|
+
actual_type = (
|
|
443
|
+
Any if is_sentinel(self.base_type, {"none"}) else self.base_type
|
|
444
|
+
)
|
|
435
445
|
current_metadata = self.metadata
|
|
436
446
|
|
|
437
447
|
# Resolve FK target (explicit or Observable base_type)
|
|
@@ -440,7 +450,7 @@ class Spec:
|
|
|
440
450
|
if not is_undefined(resolved_fk):
|
|
441
451
|
from uuid import UUID
|
|
442
452
|
|
|
443
|
-
from
|
|
453
|
+
from krons.core.types.db_types import FKMeta
|
|
444
454
|
|
|
445
455
|
actual_type = UUID # FK fields are UUID references
|
|
446
456
|
extra_annotations.append(FKMeta(resolved_fk))
|
|
@@ -475,7 +485,9 @@ class Spec:
|
|
|
475
485
|
exclude = set()
|
|
476
486
|
if exclude_common:
|
|
477
487
|
exclude = exclude | set(CommonMeta.allowed())
|
|
478
|
-
return {
|
|
488
|
+
return {
|
|
489
|
+
meta.key: meta.value for meta in self.metadata if meta.key not in exclude
|
|
490
|
+
}
|
|
479
491
|
|
|
480
492
|
|
|
481
493
|
def _is_observable(cls: type) -> bool:
|