lionherd-core 1.0.0a3__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.
- lionherd_core/__init__.py +84 -0
- lionherd_core/base/__init__.py +30 -0
- lionherd_core/base/_utils.py +295 -0
- lionherd_core/base/broadcaster.py +128 -0
- lionherd_core/base/element.py +300 -0
- lionherd_core/base/event.py +322 -0
- lionherd_core/base/eventbus.py +112 -0
- lionherd_core/base/flow.py +236 -0
- lionherd_core/base/graph.py +616 -0
- lionherd_core/base/node.py +212 -0
- lionherd_core/base/pile.py +811 -0
- lionherd_core/base/progression.py +261 -0
- lionherd_core/errors.py +104 -0
- lionherd_core/libs/__init__.py +2 -0
- lionherd_core/libs/concurrency/__init__.py +60 -0
- lionherd_core/libs/concurrency/_cancel.py +85 -0
- lionherd_core/libs/concurrency/_errors.py +80 -0
- lionherd_core/libs/concurrency/_patterns.py +238 -0
- lionherd_core/libs/concurrency/_primitives.py +253 -0
- lionherd_core/libs/concurrency/_priority_queue.py +135 -0
- lionherd_core/libs/concurrency/_resource_tracker.py +66 -0
- lionherd_core/libs/concurrency/_task.py +58 -0
- lionherd_core/libs/concurrency/_utils.py +61 -0
- lionherd_core/libs/schema_handlers/__init__.py +35 -0
- lionherd_core/libs/schema_handlers/_function_call_parser.py +122 -0
- lionherd_core/libs/schema_handlers/_minimal_yaml.py +88 -0
- lionherd_core/libs/schema_handlers/_schema_to_model.py +251 -0
- lionherd_core/libs/schema_handlers/_typescript.py +153 -0
- lionherd_core/libs/string_handlers/__init__.py +15 -0
- lionherd_core/libs/string_handlers/_extract_json.py +65 -0
- lionherd_core/libs/string_handlers/_fuzzy_json.py +103 -0
- lionherd_core/libs/string_handlers/_string_similarity.py +347 -0
- lionherd_core/libs/string_handlers/_to_num.py +63 -0
- lionherd_core/ln/__init__.py +45 -0
- lionherd_core/ln/_async_call.py +314 -0
- lionherd_core/ln/_fuzzy_match.py +166 -0
- lionherd_core/ln/_fuzzy_validate.py +151 -0
- lionherd_core/ln/_hash.py +141 -0
- lionherd_core/ln/_json_dump.py +347 -0
- lionherd_core/ln/_list_call.py +110 -0
- lionherd_core/ln/_to_dict.py +373 -0
- lionherd_core/ln/_to_list.py +190 -0
- lionherd_core/ln/_utils.py +156 -0
- lionherd_core/lndl/__init__.py +62 -0
- lionherd_core/lndl/errors.py +30 -0
- lionherd_core/lndl/fuzzy.py +321 -0
- lionherd_core/lndl/parser.py +427 -0
- lionherd_core/lndl/prompt.py +137 -0
- lionherd_core/lndl/resolver.py +323 -0
- lionherd_core/lndl/types.py +287 -0
- lionherd_core/protocols.py +181 -0
- lionherd_core/py.typed +0 -0
- lionherd_core/types/__init__.py +46 -0
- lionherd_core/types/_sentinel.py +131 -0
- lionherd_core/types/base.py +341 -0
- lionherd_core/types/operable.py +133 -0
- lionherd_core/types/spec.py +313 -0
- lionherd_core/types/spec_adapters/__init__.py +10 -0
- lionherd_core/types/spec_adapters/_protocol.py +125 -0
- lionherd_core/types/spec_adapters/pydantic_field.py +177 -0
- lionherd_core-1.0.0a3.dist-info/METADATA +502 -0
- lionherd_core-1.0.0a3.dist-info/RECORD +64 -0
- lionherd_core-1.0.0a3.dist-info/WHEEL +4 -0
- lionherd_core-1.0.0a3.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Copyright (c) 2025, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pydantic import field_serializer, field_validator
|
|
9
|
+
from pydapter import (
|
|
10
|
+
Adaptable as PydapterAdaptable,
|
|
11
|
+
AsyncAdaptable as PydapterAsyncAdaptable,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from ..protocols import Adaptable, AsyncAdaptable, Deserializable, implements
|
|
15
|
+
from .element import Element
|
|
16
|
+
|
|
17
|
+
NODE_REGISTRY: dict[str, type[Node]] = {}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@implements(Deserializable, Adaptable, AsyncAdaptable)
|
|
21
|
+
class Node(Element, PydapterAdaptable, PydapterAsyncAdaptable):
|
|
22
|
+
"""Polymorphic node with arbitrary content, embeddings, pydapter integration.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
content: Arbitrary data (auto-serializes nested Elements)
|
|
26
|
+
embedding: Optional float vector
|
|
27
|
+
|
|
28
|
+
Auto-registers subclasses in NODE_REGISTRY for polymorphic deserialization.
|
|
29
|
+
|
|
30
|
+
Adapter Registration (Rust-like isolated pattern):
|
|
31
|
+
Base Node has toml/yaml built-in. Subclasses get ISOLATED registries (no inheritance):
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from pydapter.adapters import TomlAdapter, YamlAdapter
|
|
35
|
+
|
|
36
|
+
# Base Node has toml/yaml
|
|
37
|
+
Node(content="test").adapt_to("toml") # ✓ Works
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Subclasses do NOT inherit adapters
|
|
41
|
+
class MyNode(Node):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
MyNode(content="test").adapt_to("toml") # ✗ Fails (isolated registry)
|
|
46
|
+
|
|
47
|
+
# Must explicitly register on subclass
|
|
48
|
+
MyNode.register_adapter(TomlAdapter)
|
|
49
|
+
MyNode(content="test").adapt_to("toml") # ✓ Now works
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This prevents adapter pollution while keeping base Node convenient.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
content: Any = None
|
|
56
|
+
embedding: list[float] | None = None
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def __pydantic_init_subclass__(cls, **kwargs: Any) -> None:
|
|
60
|
+
"""Register subclasses with isolated adapter registries."""
|
|
61
|
+
super().__pydantic_init_subclass__(**kwargs)
|
|
62
|
+
NODE_REGISTRY[cls.__name__] = cls
|
|
63
|
+
NODE_REGISTRY[f"{cls.__module__}.{cls.__name__}"] = cls
|
|
64
|
+
|
|
65
|
+
# Force creation of isolated registry for subclass (prevents parent inheritance)
|
|
66
|
+
# This ensures each subclass has its own registry, not inheriting from Node
|
|
67
|
+
if cls is not Node:
|
|
68
|
+
# Access _registry() to trigger creation of isolated registry
|
|
69
|
+
_ = cls._registry()
|
|
70
|
+
|
|
71
|
+
@field_serializer("content")
|
|
72
|
+
def _serialize_content(self, value: Any) -> Any:
|
|
73
|
+
return value.to_dict() if isinstance(value, Element) else value
|
|
74
|
+
|
|
75
|
+
@field_validator("content", mode="before")
|
|
76
|
+
@classmethod
|
|
77
|
+
def _validate_content(cls, value: Any) -> Any:
|
|
78
|
+
if isinstance(value, dict) and "metadata" in value:
|
|
79
|
+
metadata = value.get("metadata", {})
|
|
80
|
+
lion_class = metadata.get("lion_class")
|
|
81
|
+
if lion_class:
|
|
82
|
+
if lion_class in NODE_REGISTRY or lion_class.split(".")[-1] in NODE_REGISTRY:
|
|
83
|
+
return Node.from_dict(value)
|
|
84
|
+
return Element.from_dict(value)
|
|
85
|
+
return value
|
|
86
|
+
|
|
87
|
+
@field_validator("embedding", mode="before")
|
|
88
|
+
@classmethod
|
|
89
|
+
def _validate_embedding(cls, value: Any) -> list[float] | None:
|
|
90
|
+
"""Validate embedding. Accepts list, JSON string, or None. Coerces ints to floats."""
|
|
91
|
+
if value is None:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
# Coerce JSON string to list (common from DB queries)
|
|
95
|
+
if isinstance(value, str):
|
|
96
|
+
import orjson
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
value = orjson.loads(value)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
raise ValueError(f"Failed to parse embedding JSON string: {e}")
|
|
102
|
+
|
|
103
|
+
if not isinstance(value, list):
|
|
104
|
+
raise ValueError("embedding must be a list, JSON string, or None")
|
|
105
|
+
if not value:
|
|
106
|
+
raise ValueError("embedding list cannot be empty")
|
|
107
|
+
if not all(isinstance(x, (int, float)) for x in value):
|
|
108
|
+
raise ValueError("embedding must contain only numeric values")
|
|
109
|
+
return [float(x) for x in value]
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def from_dict(cls, data: dict[str, Any], meta_key: str | None = None, **kwargs: Any) -> Node:
|
|
113
|
+
"""Deserialize with polymorphic type restoration via NODE_REGISTRY.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
data: Serialized dict
|
|
117
|
+
meta_key: Restore metadata from this key (db compatibility)
|
|
118
|
+
**kwargs: Passed to model_validate
|
|
119
|
+
"""
|
|
120
|
+
# Make a copy to avoid mutating input
|
|
121
|
+
data = data.copy()
|
|
122
|
+
|
|
123
|
+
# Restore metadata from custom key if specified
|
|
124
|
+
if meta_key and meta_key in data:
|
|
125
|
+
data["metadata"] = data.pop(meta_key)
|
|
126
|
+
# Backward compatibility: handle legacy "node_metadata" key
|
|
127
|
+
elif "node_metadata" in data and "metadata" not in data:
|
|
128
|
+
data["metadata"] = data.pop("node_metadata")
|
|
129
|
+
|
|
130
|
+
# Clean up any remaining node_metadata key to avoid validation errors
|
|
131
|
+
data.pop("node_metadata", None)
|
|
132
|
+
|
|
133
|
+
# Extract and remove lion_class from metadata (serialization-only metadata)
|
|
134
|
+
metadata = data.get("metadata", {})
|
|
135
|
+
if isinstance(metadata, dict):
|
|
136
|
+
metadata = metadata.copy()
|
|
137
|
+
data["metadata"] = metadata
|
|
138
|
+
lion_class = metadata.pop("lion_class", None)
|
|
139
|
+
else:
|
|
140
|
+
lion_class = None
|
|
141
|
+
|
|
142
|
+
if lion_class and lion_class != cls.class_name(full=True):
|
|
143
|
+
target_cls = NODE_REGISTRY.get(lion_class) or NODE_REGISTRY.get(
|
|
144
|
+
lion_class.split(".")[-1]
|
|
145
|
+
)
|
|
146
|
+
if target_cls is not None and target_cls is not cls:
|
|
147
|
+
return target_cls.from_dict(data, **kwargs)
|
|
148
|
+
|
|
149
|
+
return cls.model_validate(data, **kwargs)
|
|
150
|
+
|
|
151
|
+
def adapt_to(self, obj_key: str, many: bool = False, **kwargs: Any) -> Any:
|
|
152
|
+
"""Convert to external format via pydapter.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
obj_key: Adapter key (e.g., "toml", "yaml"). Must register adapter first!
|
|
156
|
+
many: Adapt multiple instances
|
|
157
|
+
**kwargs: Passed to adapter
|
|
158
|
+
"""
|
|
159
|
+
kwargs.setdefault("adapt_meth", "to_dict")
|
|
160
|
+
kwargs.setdefault("adapt_kw", {"mode": "db"})
|
|
161
|
+
return super().adapt_to(obj_key=obj_key, many=many, **kwargs)
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def adapt_from(cls, obj: Any, obj_key: str, many: bool = False, **kwargs: Any) -> Node:
|
|
165
|
+
"""Create from external format via pydapter (polymorphic).
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
obj: Source object
|
|
169
|
+
obj_key: Adapter key
|
|
170
|
+
many: Deserialize multiple instances
|
|
171
|
+
**kwargs: Passed to adapter
|
|
172
|
+
"""
|
|
173
|
+
kwargs.setdefault("adapt_meth", "from_dict")
|
|
174
|
+
return super().adapt_from(obj, obj_key=obj_key, many=many, **kwargs)
|
|
175
|
+
|
|
176
|
+
async def adapt_to_async(self, obj_key: str, many: bool = False, **kwargs: Any) -> Any:
|
|
177
|
+
"""Async convert to external format via pydapter.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
obj_key: Adapter key
|
|
181
|
+
many: Adapt multiple instances
|
|
182
|
+
**kwargs: Passed to adapter
|
|
183
|
+
"""
|
|
184
|
+
kwargs.setdefault("adapt_meth", "to_dict")
|
|
185
|
+
kwargs.setdefault("adapt_kw", {"mode": "db"})
|
|
186
|
+
return await super().adapt_to_async(obj_key=obj_key, many=many, **kwargs)
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
async def adapt_from_async(
|
|
190
|
+
cls, obj: Any, obj_key: str, many: bool = False, **kwargs: Any
|
|
191
|
+
) -> Node:
|
|
192
|
+
"""Async create from external format via pydapter (polymorphic).
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
obj: Source object
|
|
196
|
+
obj_key: Adapter key
|
|
197
|
+
many: Deserialize multiple instances
|
|
198
|
+
**kwargs: Passed to adapter
|
|
199
|
+
"""
|
|
200
|
+
kwargs.setdefault("adapt_meth", "from_dict")
|
|
201
|
+
return await super().adapt_from_async(obj, obj_key=obj_key, many=many, **kwargs)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
NODE_REGISTRY[Node.__name__] = Node
|
|
205
|
+
NODE_REGISTRY[Node.class_name(full=True)] = Node
|
|
206
|
+
|
|
207
|
+
from pydapter.adapters import TomlAdapter, YamlAdapter
|
|
208
|
+
|
|
209
|
+
Node.register_adapter(TomlAdapter) # type: ignore[type-abstract]
|
|
210
|
+
Node.register_adapter(YamlAdapter) # type: ignore[type-abstract]
|
|
211
|
+
|
|
212
|
+
__all__ = ("NODE_REGISTRY", "Node")
|