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.
Files changed (64) hide show
  1. lionherd_core/__init__.py +84 -0
  2. lionherd_core/base/__init__.py +30 -0
  3. lionherd_core/base/_utils.py +295 -0
  4. lionherd_core/base/broadcaster.py +128 -0
  5. lionherd_core/base/element.py +300 -0
  6. lionherd_core/base/event.py +322 -0
  7. lionherd_core/base/eventbus.py +112 -0
  8. lionherd_core/base/flow.py +236 -0
  9. lionherd_core/base/graph.py +616 -0
  10. lionherd_core/base/node.py +212 -0
  11. lionherd_core/base/pile.py +811 -0
  12. lionherd_core/base/progression.py +261 -0
  13. lionherd_core/errors.py +104 -0
  14. lionherd_core/libs/__init__.py +2 -0
  15. lionherd_core/libs/concurrency/__init__.py +60 -0
  16. lionherd_core/libs/concurrency/_cancel.py +85 -0
  17. lionherd_core/libs/concurrency/_errors.py +80 -0
  18. lionherd_core/libs/concurrency/_patterns.py +238 -0
  19. lionherd_core/libs/concurrency/_primitives.py +253 -0
  20. lionherd_core/libs/concurrency/_priority_queue.py +135 -0
  21. lionherd_core/libs/concurrency/_resource_tracker.py +66 -0
  22. lionherd_core/libs/concurrency/_task.py +58 -0
  23. lionherd_core/libs/concurrency/_utils.py +61 -0
  24. lionherd_core/libs/schema_handlers/__init__.py +35 -0
  25. lionherd_core/libs/schema_handlers/_function_call_parser.py +122 -0
  26. lionherd_core/libs/schema_handlers/_minimal_yaml.py +88 -0
  27. lionherd_core/libs/schema_handlers/_schema_to_model.py +251 -0
  28. lionherd_core/libs/schema_handlers/_typescript.py +153 -0
  29. lionherd_core/libs/string_handlers/__init__.py +15 -0
  30. lionherd_core/libs/string_handlers/_extract_json.py +65 -0
  31. lionherd_core/libs/string_handlers/_fuzzy_json.py +103 -0
  32. lionherd_core/libs/string_handlers/_string_similarity.py +347 -0
  33. lionherd_core/libs/string_handlers/_to_num.py +63 -0
  34. lionherd_core/ln/__init__.py +45 -0
  35. lionherd_core/ln/_async_call.py +314 -0
  36. lionherd_core/ln/_fuzzy_match.py +166 -0
  37. lionherd_core/ln/_fuzzy_validate.py +151 -0
  38. lionherd_core/ln/_hash.py +141 -0
  39. lionherd_core/ln/_json_dump.py +347 -0
  40. lionherd_core/ln/_list_call.py +110 -0
  41. lionherd_core/ln/_to_dict.py +373 -0
  42. lionherd_core/ln/_to_list.py +190 -0
  43. lionherd_core/ln/_utils.py +156 -0
  44. lionherd_core/lndl/__init__.py +62 -0
  45. lionherd_core/lndl/errors.py +30 -0
  46. lionherd_core/lndl/fuzzy.py +321 -0
  47. lionherd_core/lndl/parser.py +427 -0
  48. lionherd_core/lndl/prompt.py +137 -0
  49. lionherd_core/lndl/resolver.py +323 -0
  50. lionherd_core/lndl/types.py +287 -0
  51. lionherd_core/protocols.py +181 -0
  52. lionherd_core/py.typed +0 -0
  53. lionherd_core/types/__init__.py +46 -0
  54. lionherd_core/types/_sentinel.py +131 -0
  55. lionherd_core/types/base.py +341 -0
  56. lionherd_core/types/operable.py +133 -0
  57. lionherd_core/types/spec.py +313 -0
  58. lionherd_core/types/spec_adapters/__init__.py +10 -0
  59. lionherd_core/types/spec_adapters/_protocol.py +125 -0
  60. lionherd_core/types/spec_adapters/pydantic_field.py +177 -0
  61. lionherd_core-1.0.0a3.dist-info/METADATA +502 -0
  62. lionherd_core-1.0.0a3.dist-info/RECORD +64 -0
  63. lionherd_core-1.0.0a3.dist-info/WHEEL +4 -0
  64. 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")