lionagi 0.18.0__py3-none-any.whl → 0.18.2__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 (93) hide show
  1. lionagi/__init__.py +102 -59
  2. lionagi/_errors.py +0 -5
  3. lionagi/adapters/spec_adapters/__init__.py +9 -0
  4. lionagi/adapters/spec_adapters/_protocol.py +236 -0
  5. lionagi/adapters/spec_adapters/pydantic_field.py +158 -0
  6. lionagi/fields.py +83 -0
  7. lionagi/ln/__init__.py +3 -1
  8. lionagi/ln/_async_call.py +2 -2
  9. lionagi/ln/concurrency/primitives.py +4 -4
  10. lionagi/ln/concurrency/task.py +1 -0
  11. lionagi/ln/fuzzy/_fuzzy_match.py +2 -2
  12. lionagi/ln/types/__init__.py +51 -0
  13. lionagi/ln/types/_sentinel.py +154 -0
  14. lionagi/ln/{types.py → types/base.py} +108 -168
  15. lionagi/ln/types/operable.py +221 -0
  16. lionagi/ln/types/spec.py +441 -0
  17. lionagi/models/field_model.py +69 -7
  18. lionagi/models/hashable_model.py +2 -3
  19. lionagi/models/model_params.py +4 -3
  20. lionagi/operations/ReAct/ReAct.py +1 -1
  21. lionagi/operations/act/act.py +3 -3
  22. lionagi/operations/builder.py +5 -7
  23. lionagi/operations/fields.py +380 -0
  24. lionagi/operations/flow.py +4 -6
  25. lionagi/operations/node.py +4 -4
  26. lionagi/operations/operate/operate.py +123 -89
  27. lionagi/operations/operate/operative.py +198 -0
  28. lionagi/operations/operate/step.py +203 -0
  29. lionagi/operations/select/select.py +1 -1
  30. lionagi/operations/select/utils.py +7 -1
  31. lionagi/operations/types.py +7 -7
  32. lionagi/protocols/action/manager.py +5 -6
  33. lionagi/protocols/contracts.py +2 -2
  34. lionagi/protocols/generic/__init__.py +22 -0
  35. lionagi/protocols/generic/element.py +36 -127
  36. lionagi/protocols/generic/pile.py +9 -10
  37. lionagi/protocols/generic/progression.py +23 -22
  38. lionagi/protocols/graph/edge.py +6 -5
  39. lionagi/protocols/ids.py +6 -49
  40. lionagi/protocols/messages/__init__.py +3 -1
  41. lionagi/protocols/messages/base.py +7 -6
  42. lionagi/protocols/messages/instruction.py +0 -1
  43. lionagi/protocols/messages/message.py +2 -2
  44. lionagi/protocols/types.py +1 -11
  45. lionagi/service/connections/__init__.py +3 -0
  46. lionagi/service/connections/providers/claude_code_cli.py +3 -2
  47. lionagi/service/hooks/_types.py +1 -1
  48. lionagi/service/hooks/_utils.py +1 -1
  49. lionagi/service/hooks/hook_event.py +3 -8
  50. lionagi/service/hooks/hook_registry.py +5 -5
  51. lionagi/service/hooks/hooked_event.py +61 -1
  52. lionagi/service/imodel.py +24 -20
  53. lionagi/service/third_party/claude_code.py +1 -2
  54. lionagi/service/third_party/openai_models.py +24 -22
  55. lionagi/service/token_calculator.py +1 -94
  56. lionagi/session/branch.py +26 -228
  57. lionagi/session/session.py +5 -90
  58. lionagi/version.py +1 -1
  59. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/METADATA +6 -5
  60. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/RECORD +62 -82
  61. lionagi/fields/__init__.py +0 -47
  62. lionagi/fields/action.py +0 -188
  63. lionagi/fields/base.py +0 -153
  64. lionagi/fields/code.py +0 -239
  65. lionagi/fields/file.py +0 -234
  66. lionagi/fields/instruct.py +0 -135
  67. lionagi/fields/reason.py +0 -55
  68. lionagi/fields/research.py +0 -52
  69. lionagi/operations/brainstorm/__init__.py +0 -2
  70. lionagi/operations/brainstorm/brainstorm.py +0 -498
  71. lionagi/operations/brainstorm/prompt.py +0 -11
  72. lionagi/operations/instruct/__init__.py +0 -2
  73. lionagi/operations/instruct/instruct.py +0 -28
  74. lionagi/operations/plan/__init__.py +0 -6
  75. lionagi/operations/plan/plan.py +0 -386
  76. lionagi/operations/plan/prompt.py +0 -25
  77. lionagi/operations/utils.py +0 -45
  78. lionagi/protocols/forms/__init__.py +0 -2
  79. lionagi/protocols/forms/base.py +0 -85
  80. lionagi/protocols/forms/flow.py +0 -79
  81. lionagi/protocols/forms/form.py +0 -86
  82. lionagi/protocols/forms/report.py +0 -48
  83. lionagi/protocols/mail/__init__.py +0 -2
  84. lionagi/protocols/mail/exchange.py +0 -220
  85. lionagi/protocols/mail/mail.py +0 -51
  86. lionagi/protocols/mail/mailbox.py +0 -103
  87. lionagi/protocols/mail/manager.py +0 -218
  88. lionagi/protocols/mail/package.py +0 -101
  89. lionagi/protocols/operatives/__init__.py +0 -2
  90. lionagi/protocols/operatives/operative.py +0 -362
  91. lionagi/protocols/operatives/step.py +0 -227
  92. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/WHEEL +0 -0
  93. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,203 @@
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Step factory methods for creating configured Operative instances."""
5
+
6
+ from typing import TYPE_CHECKING, Literal
7
+
8
+ from lionagi.ln.types import Operable, Spec
9
+
10
+ from ..fields import get_default_field
11
+ from .operative import Operative
12
+
13
+ if TYPE_CHECKING:
14
+ from pydantic import BaseModel
15
+
16
+
17
+ class Step:
18
+ """Factory methods for common Operative patterns.
19
+
20
+ Provides methods to create Operative instances with pre-configured
21
+ field specifications for common patterns like ReAct, QA, and task execution.
22
+ """
23
+
24
+ @staticmethod
25
+ def request_operative(
26
+ *,
27
+ name: str | None = None,
28
+ operative_name: str | None = None, # backward compat
29
+ adapter: Literal["pydantic"] = "pydantic",
30
+ reason: bool = False,
31
+ actions: bool = False,
32
+ fields: dict[str, Spec] | None = None,
33
+ field_models: list | None = None, # backward compat
34
+ max_retries: int = 3,
35
+ auto_retry_parse: bool = True,
36
+ base_type: type["BaseModel"] | None = None,
37
+ # Deprecated/ignored parameters for backward compatibility
38
+ parse_kwargs: dict | None = None,
39
+ exclude_fields: list | None = None,
40
+ field_descriptions: dict | None = None,
41
+ inherit_base: bool = True,
42
+ config_dict: dict | None = None,
43
+ doc: str | None = None,
44
+ frozen: bool = False,
45
+ new_model_name: str | None = None,
46
+ parameter_fields: dict | None = None,
47
+ request_params: dict | None = None,
48
+ **kwargs,
49
+ ) -> Operative:
50
+ """Create request-configured Operative with common field patterns.
51
+
52
+ Args:
53
+ name: Operative name
54
+ operative_name: (Deprecated) Use 'name' instead
55
+ adapter: Validation framework
56
+ reason: Add reasoning trace field
57
+ actions: Add action request/response fields
58
+ fields: Additional custom field specs (dict[str, Spec])
59
+ field_models: (Deprecated) Use 'fields' instead - list of FieldModel/Spec
60
+ max_retries: Max validation retries
61
+ auto_retry_parse: Auto-retry on parse failure
62
+ base_type: Base Pydantic model to extend
63
+ parse_kwargs: (Deprecated) Ignored - parse config handled internally
64
+ exclude_fields: (Deprecated) Ignored
65
+ field_descriptions: (Deprecated) Ignored
66
+ inherit_base: (Deprecated) Ignored
67
+ config_dict: (Deprecated) Ignored
68
+ doc: (Deprecated) Ignored
69
+ frozen: (Deprecated) Ignored
70
+ new_model_name: (Deprecated) Ignored
71
+ parameter_fields: (Deprecated) Ignored
72
+ request_params: (Deprecated) Ignored
73
+
74
+ Returns:
75
+ Configured Operative instance
76
+ """
77
+ # Handle backward compatibility
78
+ name = name or operative_name
79
+
80
+ # Convert field_models list to fields dict if provided
81
+ if field_models and not fields:
82
+ from lionagi.models import FieldModel
83
+
84
+ fields = {}
85
+ for fm in field_models:
86
+ # Convert FieldModel to Spec if needed
87
+ if isinstance(fm, FieldModel):
88
+ spec = fm.to_spec()
89
+ elif isinstance(fm, Spec):
90
+ spec = fm
91
+ else:
92
+ continue # Skip invalid types
93
+
94
+ # Use spec name as key
95
+ if spec.name:
96
+ fields[spec.name] = spec
97
+
98
+ # Build fields dict to avoid duplicates (dict preserves insertion order in Python 3.7+)
99
+ fields_dict = {}
100
+
101
+ # Add common fields (convert FieldModel to Spec)
102
+ if reason:
103
+ reason_spec = get_default_field("reason").to_spec()
104
+ fields_dict["reason"] = reason_spec
105
+
106
+ if actions:
107
+ fields_dict["action_required"] = get_default_field(
108
+ "action_required"
109
+ ).to_spec()
110
+ fields_dict["action_requests"] = get_default_field(
111
+ "action_requests"
112
+ ).to_spec()
113
+ fields_dict["action_responses"] = get_default_field(
114
+ "action_responses"
115
+ ).to_spec()
116
+
117
+ # Add custom fields (will override defaults if same name)
118
+ if fields:
119
+ for field_name, spec in fields.items():
120
+ # Ensure spec has name
121
+ if not spec.name:
122
+ # Update spec with name using Spec metadata update
123
+ spec = Spec(
124
+ spec.base_type,
125
+ name=field_name,
126
+ metadata=spec.metadata,
127
+ )
128
+ fields_dict[spec.name] = spec
129
+
130
+ # Convert to list
131
+ all_fields = list(fields_dict.values())
132
+
133
+ # Create Operable with all fields
134
+ operable = Operable(
135
+ tuple(all_fields),
136
+ name=name or (base_type.__name__ if base_type else "Operative"),
137
+ )
138
+
139
+ # Request excludes action_responses
140
+ request_exclude = {"action_responses"} if actions else set()
141
+
142
+ return Operative(
143
+ name=name,
144
+ adapter=adapter,
145
+ max_retries=max_retries,
146
+ auto_retry_parse=auto_retry_parse,
147
+ base_type=base_type,
148
+ operable=operable,
149
+ request_exclude=request_exclude,
150
+ )
151
+
152
+ @staticmethod
153
+ def respond_operative(
154
+ operative: Operative,
155
+ additional_fields: dict[str, Spec] | None = None,
156
+ ) -> Operative:
157
+ """Create response type from operative.
158
+
159
+ Args:
160
+ operative: Source operative with all fields
161
+ additional_fields: Extra fields for response
162
+
163
+ Returns:
164
+ Operative with response type configured
165
+ """
166
+ # If additional fields provided, create new Operative
167
+ if additional_fields:
168
+ # Get existing fields
169
+ existing_fields = list(operative.operable.__op_fields__)
170
+
171
+ # Add new fields
172
+ for field_name, spec in additional_fields.items():
173
+ if not spec.name:
174
+ spec = Spec(
175
+ spec.base_type,
176
+ name=field_name,
177
+ metadata=spec.metadata,
178
+ )
179
+ existing_fields.append(spec)
180
+
181
+ # Create new Operable
182
+ new_operable = Operable(
183
+ tuple(existing_fields),
184
+ name=operative.name,
185
+ )
186
+
187
+ # Create new Operative
188
+ return Operative(
189
+ name=operative.name,
190
+ adapter=operative.adapter,
191
+ max_retries=operative.max_retries,
192
+ auto_retry_parse=operative.auto_retry_parse,
193
+ base_type=operative.base_type,
194
+ operable=new_operable,
195
+ request_exclude=operative.request_exclude,
196
+ )
197
+
198
+ # Otherwise just create response model
199
+ operative.create_response_model()
200
+ return operative
201
+
202
+
203
+ __all__ = ("Step",)
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any
6
6
 
7
7
  from pydantic import BaseModel
8
8
 
9
- from lionagi.fields.instruct import Instruct
9
+ from lionagi.fields import Instruct
10
10
 
11
11
  from .utils import SelectionModel, parse_selection, parse_to_representation
12
12
 
@@ -68,11 +68,17 @@ def get_choice_representation(choice: Any) -> str:
68
68
  return choice
69
69
 
70
70
  if isinstance(choice, BaseModel):
71
- return f"{choice.__class__.__name__}:\n{choice.model_json_schema(indent=2)}"
71
+ import json
72
+
73
+ schema = choice.model_json_schema()
74
+ return f"{choice.__class__.__name__}:\n{json.dumps(schema, indent=2)}"
72
75
 
73
76
  if isinstance(choice, Enum):
74
77
  return get_choice_representation(choice.value)
75
78
 
79
+ # Handle other types (int, dict, etc.) by converting to string
80
+ return str(choice)
81
+
76
82
 
77
83
  def parse_selection(selection_str: str, choices: Any):
78
84
  select_from = []
@@ -7,9 +7,9 @@ from typing import ClassVar, Literal
7
7
 
8
8
  from pydantic import BaseModel, JsonValue
9
9
 
10
- from lionagi.ln._async_call import AlcallParams
10
+ from lionagi.ln import AlcallParams
11
11
  from lionagi.ln.fuzzy import FuzzyMatchKeysParams
12
- from lionagi.ln.types import Params
12
+ from lionagi.ln.types import ModelConfig, Params
13
13
  from lionagi.protocols.action.tool import ToolRef
14
14
  from lionagi.protocols.types import ID, SenderRecipient
15
15
  from lionagi.service.imodel import iModel
@@ -43,7 +43,7 @@ class MorphParam(Params):
43
43
  transformations between message states with well-defined parameters.
44
44
  """
45
45
 
46
- _none_as_sentinel: ClassVar[bool] = True
46
+ _config: ClassVar[ModelConfig] = ModelConfig(none_as_sentinel=True)
47
47
 
48
48
 
49
49
  @dataclass(slots=True, frozen=True, init=False)
@@ -57,7 +57,7 @@ class ChatParam(MorphParam):
57
57
  This gets mapped to InstructionContent.prompt_context during message creation.
58
58
  """
59
59
 
60
- _none_as_sentinel: ClassVar[bool] = True
60
+ _config: ClassVar[ModelConfig] = ModelConfig(none_as_sentinel=True)
61
61
  guidance: JsonValue = None
62
62
  context: JsonValue = None
63
63
  sender: SenderRecipient = None
@@ -81,7 +81,7 @@ class InterpretParam(MorphParam):
81
81
  transforming content according to specified guidelines.
82
82
  """
83
83
 
84
- _none_as_sentinel: ClassVar[bool] = True
84
+ _config: ClassVar[ModelConfig] = ModelConfig(none_as_sentinel=True)
85
85
  domain: str = None
86
86
  style: str = None
87
87
  sample_writing: str = None
@@ -97,7 +97,7 @@ class ParseParam(MorphParam):
97
97
  fuzzy matching, and error handling strategies.
98
98
  """
99
99
 
100
- _none_as_sentinel: ClassVar[bool] = True
100
+ _config: ClassVar[ModelConfig] = ModelConfig(none_as_sentinel=True)
101
101
  response_format: type[BaseModel] | dict = None
102
102
  fuzzy_match_params: FuzzyMatchKeysParams | dict = None
103
103
  handle_validation: HandleValidation = "raise"
@@ -114,7 +114,7 @@ class ActionParam(MorphParam):
114
114
  for action-based operations.
115
115
  """
116
116
 
117
- _none_as_sentinel: ClassVar[bool] = True
117
+ _config: ClassVar[ModelConfig] = ModelConfig(none_as_sentinel=True)
118
118
  action_call_params: AlcallParams = None
119
119
  tools: ToolRef = None
120
120
  strategy: Literal["concurrent", "sequential"] = "concurrent"
@@ -4,7 +4,8 @@
4
4
  import logging
5
5
  from typing import Any
6
6
 
7
- from lionagi.fields.action import ActionRequestModel
7
+ from pydantic import BaseModel
8
+
8
9
  from lionagi.protocols._concepts import Manager
9
10
  from lionagi.protocols.messages.action_request import ActionRequest
10
11
  from lionagi.utils import to_list
@@ -121,7 +122,7 @@ class ActionManager(Manager):
121
122
  self.register_tool(t, update=update)
122
123
 
123
124
  def match_tool(
124
- self, action_request: ActionRequest | ActionRequestModel | dict
125
+ self, action_request: ActionRequest | BaseModel | dict
125
126
  ) -> FunctionCalling:
126
127
  """
127
128
  Convert an ActionRequest (or dict with "function"/"arguments")
@@ -134,9 +135,7 @@ class ActionManager(Manager):
134
135
  Returns:
135
136
  FunctionCalling: The event object that can be invoked.
136
137
  """
137
- if not isinstance(
138
- action_request, ActionRequest | ActionRequestModel | dict
139
- ):
138
+ if not isinstance(action_request, ActionRequest | BaseModel | dict):
140
139
  raise TypeError(f"Unsupported type {type(action_request)}")
141
140
 
142
141
  func, args = None, None
@@ -155,7 +154,7 @@ class ActionManager(Manager):
155
154
 
156
155
  async def invoke(
157
156
  self,
158
- func_call: ActionRequestModel | ActionRequest,
157
+ func_call: BaseModel | ActionRequest,
159
158
  ) -> FunctionCalling:
160
159
  """
161
160
  High-level API to parse and run a function call.
@@ -24,7 +24,7 @@ class ObservableProto(Protocol):
24
24
 
25
25
  This protocol defines the minimal contract for observable objects:
26
26
  they must have an 'id' property. The return type is permissive (Any)
27
- to maintain compatibility with V0's IDType wrapper while enabling
27
+ to maintain compatibility with V0's UUID wrapper while enabling
28
28
  V1 evolution.
29
29
 
30
30
  All V0 Element subclasses automatically satisfy this protocol without
@@ -33,7 +33,7 @@ class ObservableProto(Protocol):
33
33
 
34
34
  @property
35
35
  def id(self) -> object:
36
- """Unique identifier. Accepts IDType, UUID, or string."""
36
+ """Unique identifier. Accepts UUID, UUID, or string."""
37
37
  ...
38
38
 
39
39
 
@@ -1,2 +1,24 @@
1
1
  # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
2
  # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from .element import ID, Element
5
+ from .event import Event, EventStatus, Execution
6
+ from .log import DataLogger, DataLoggerConfig, Log
7
+ from .pile import Pile
8
+ from .processor import Executor, Processor
9
+ from .progression import Progression
10
+
11
+ __all__ = (
12
+ "Element",
13
+ "ID",
14
+ "Event",
15
+ "Execution",
16
+ "Log",
17
+ "DataLogger",
18
+ "DataLoggerConfig",
19
+ "Pile",
20
+ "Progression",
21
+ "Processor",
22
+ "Executor",
23
+ "EventStatus",
24
+ )
@@ -19,98 +19,16 @@ from pydantic import (
19
19
 
20
20
  from lionagi import ln
21
21
  from lionagi._class_registry import get_class
22
- from lionagi._errors import IDError
23
22
  from lionagi.utils import import_module, to_dict
24
23
 
25
24
  from .._concepts import Collective, Observable, Ordering
26
25
 
27
26
  __all__ = (
28
- "IDType",
29
27
  "Element",
30
- "ID",
31
28
  "validate_order",
32
- "DEFAULT_ELEMENT_SERIALIZER",
33
29
  )
34
30
 
35
31
 
36
- class IDType:
37
- """Represents a UUIDv4-based identifier.
38
-
39
- This class wraps a UUID object and provides helper methods for
40
- validating and creating UUID version 4. It also implements equality
41
- and hashing so that it can be used as dictionary keys or in sets.
42
-
43
- Attributes:
44
- _id (UUID): The wrapped UUID object.
45
- """
46
-
47
- __slots__ = ("_id",)
48
-
49
- def __init__(self, id: UUID) -> None:
50
- """Initializes an IDType instance."""
51
- self._id = id
52
-
53
- @classmethod
54
- def validate(cls, value: str | UUID | IDType) -> IDType:
55
- """Validates and converts a value into an IDType.
56
-
57
- Returns:
58
- IDType: The validated IDType object.
59
-
60
- Raises:
61
- IDError: If the provided value is not a valid UUIDv4.
62
- """
63
- if isinstance(value, IDType):
64
- return value
65
- try:
66
- return cls(UUID(str(value), version=4))
67
- except ValueError:
68
- raise IDError(f"Invalid ID: {value}") from None
69
-
70
- @classmethod
71
- def create(cls) -> IDType:
72
- """Creates a new IDType with a randomly generated UUIDv4.
73
-
74
- Returns:
75
- IDType: A new IDType instance with a random UUIDv4.
76
- """
77
- return cls(uuid4())
78
-
79
- def __str__(self) -> str:
80
- """Returns the string representation of the underlying UUID.
81
-
82
- Returns:
83
- str: The string form of this IDType's UUID.
84
- """
85
- return str(self._id)
86
-
87
- def __repr__(self) -> str:
88
- """Returns the unambiguous string representation of this IDType.
89
-
90
- Returns:
91
- str: A developer-friendly string for debugging.
92
- """
93
- return f"IDType({self._id})"
94
-
95
- def __eq__(self, other: Any) -> bool:
96
- """Checks equality with another IDType based on UUID value.
97
-
98
- Returns:
99
- bool: True if both have the same underlying UUID; False otherwise.
100
- """
101
- if not isinstance(other, IDType):
102
- return NotImplemented
103
- return self._id == other._id
104
-
105
- def __hash__(self) -> int:
106
- """Returns a hash based on the underlying UUID.
107
-
108
- Returns:
109
- int: The hash of this object, allowing IDType to be dictionary keys.
110
- """
111
- return hash(self._id)
112
-
113
-
114
32
  class Element(BaseModel, Observable):
115
33
  """Basic identifiable, timestamped element.
116
34
 
@@ -119,7 +37,7 @@ class Element(BaseModel, Observable):
119
37
  dictionary.
120
38
 
121
39
  Attributes:
122
- id (IDType):
40
+ id (UUID):
123
41
  A unique ID based on UUIDv4 (defaults to a newly generated one).
124
42
  created_at (float):
125
43
  The creation timestamp as a float (Unix epoch). Defaults to
@@ -135,8 +53,8 @@ class Element(BaseModel, Observable):
135
53
  extra="forbid",
136
54
  )
137
55
 
138
- id: IDType = Field(
139
- default_factory=IDType.create,
56
+ id: UUID = Field(
57
+ default_factory=uuid4,
140
58
  title="ID",
141
59
  description="Unique identifier for this element.",
142
60
  frozen=True,
@@ -211,12 +129,14 @@ class Element(BaseModel, Observable):
211
129
  raise ValueError(f"Invalid created_at: {val}") from None
212
130
 
213
131
  @field_validator("id", mode="before")
214
- def _ensure_idtype(cls, val: IDType | UUID | str) -> IDType:
215
- """Ensures `id` is validated as an IDType."""
216
- return IDType.validate(val)
132
+ def _ensure_UUID(cls, val: UUID | str) -> UUID:
133
+ """Ensures `id` is validated as an UUID."""
134
+ if isinstance(val, UUID):
135
+ return val
136
+ return UUID(str(val))
217
137
 
218
138
  @field_serializer("id")
219
- def _serialize_id_type(self, val: IDType) -> str:
139
+ def _serialize_id_type(self, val: UUID) -> str:
220
140
  """Serializes the `id` field to a string."""
221
141
  return str(val)
222
142
 
@@ -328,28 +248,27 @@ class Element(BaseModel, Observable):
328
248
 
329
249
 
330
250
  DEFAULT_ELEMENT_SERIALIZER = ln.get_orjson_default(
331
- order=[IDType, Element, BaseModel],
251
+ order=[Element, BaseModel],
332
252
  additional={
333
- IDType: lambda o: str(o),
334
253
  Element: lambda o: o.to_dict(),
335
254
  BaseModel: lambda o: o.model_dump(mode="json"),
336
255
  },
337
256
  )
338
257
 
339
258
 
340
- def validate_order(order: Any) -> list[IDType]:
341
- """Validates and flattens an ordering into a list of IDType objects.
259
+ def validate_order(order: Any) -> list[UUID]:
260
+ """Validates and flattens an ordering into a list of UUID objects.
342
261
 
343
262
  This function accepts a variety of possible representations for ordering
344
263
  (e.g., a single Element, a list of Elements, a dictionary with ID keys,
345
- or a nested structure) and returns a flat list of IDType objects.
264
+ or a nested structure) and returns a flat list of UUID objects.
346
265
 
347
266
  Returns:
348
- list[IDType]: A flat list of validated IDType objects.
267
+ list[UUID]: A flat list of validated UUID objects.
349
268
 
350
269
  Raises:
351
270
  ValueError: If an invalid item is encountered or if there's a mixture
352
- of types not all convertible to IDType.
271
+ of types not all convertible to UUID.
353
272
  """
354
273
  if isinstance(order, Element):
355
274
  return [order.id]
@@ -357,89 +276,79 @@ def validate_order(order: Any) -> list[IDType]:
357
276
  order = list(order.keys())
358
277
 
359
278
  stack = [order]
360
- out: list[IDType] = []
279
+ out: list[UUID] = []
361
280
  while stack:
362
281
  cur = stack.pop()
363
282
  if cur is None:
364
283
  continue
365
284
  if isinstance(cur, Element):
366
285
  out.append(cur.id)
367
- elif isinstance(cur, IDType):
368
- out.append(cur)
369
286
  elif isinstance(cur, UUID):
370
- out.append(IDType.validate(cur))
287
+ out.append(cur)
371
288
  elif isinstance(cur, str):
372
- out.append(IDType.validate(cur))
289
+ out.append(UUID(cur))
373
290
  elif isinstance(cur, (list, tuple, set)):
374
291
  stack.extend(reversed(cur))
375
292
  else:
376
293
  raise ValueError("Invalid item in order.")
377
294
 
378
- if not out:
379
- return []
380
-
381
- # Check for consistent IDType usage
382
- first_type = type(out[0])
383
- if first_type is IDType:
384
- for item in out:
385
- if not isinstance(item, IDType):
386
- raise ValueError("Mixed types in order.")
387
- return out
388
- raise ValueError("Unrecognized type(s) in order.")
295
+ return [] if not out else out
389
296
 
390
297
 
391
298
  E = TypeVar("E", bound=Element)
392
299
 
393
300
 
394
301
  class ID(Generic[E]):
395
- """Utility class for working with IDType objects and Elements.
302
+ """Utility class for working with UUID objects and Elements.
396
303
 
397
304
  This class provides helper methods to extract IDs from Elements, strings,
398
305
  or UUIDs, and to test whether a given object can be interpreted as
399
306
  an ID.
400
307
  """
401
308
 
402
- ID: TypeAlias = IDType
309
+ ID: TypeAlias = UUID
403
310
  Item: TypeAlias = E | Element # type: ignore
404
- Ref: TypeAlias = IDType | E | str # type: ignore
405
- IDSeq: TypeAlias = Sequence[IDType] | Ordering[E] # type: ignore
311
+ Ref: TypeAlias = UUID | E | str # type: ignore
312
+ IDSeq: TypeAlias = Sequence[UUID] | Ordering[E] # type: ignore
406
313
  ItemSeq: TypeAlias = Sequence[E] | Collective[E] # type: ignore
407
314
  RefSeq: TypeAlias = ItemSeq | Sequence[Ref] | Ordering[E] # type: ignore
408
315
 
409
316
  @staticmethod
410
- def get_id(item: E) -> IDType:
411
- """Retrieves an IDType from multiple possible item forms.
317
+ def get_id(item: E) -> UUID:
318
+ """Retrieves an UUID from multiple possible item forms.
412
319
 
413
320
  Acceptable item types include:
414
321
  - Element: Uses its `id` attribute.
415
- - IDType: Returns it directly.
322
+ - UUID: Returns it directly.
416
323
  - UUID: Validates and wraps it.
417
324
  - str: Interpreted as a UUID if possible.
418
325
 
419
326
  Returns:
420
- IDType: The validated ID.
327
+ UUID: The validated ID.
421
328
 
422
329
  Raises:
423
- ValueError: If the item cannot be converted to an IDType.
330
+ ValueError: If the item cannot be converted to an UUID.
424
331
  """
332
+ if isinstance(item, UUID):
333
+ return item
425
334
  if isinstance(item, Element):
426
335
  return item.id
427
- if isinstance(item, (IDType, UUID, str)):
428
- return IDType.validate(item)
336
+ if isinstance(item, str):
337
+ return UUID(item)
429
338
  raise ValueError("Cannot get ID from item.")
430
339
 
431
340
  @staticmethod
432
341
  def is_id(item: Any) -> bool:
433
- """Checks if an item can be validated as an IDType.
342
+ """Checks if an item can be validated as an UUID.
434
343
 
435
344
  Returns:
436
- bool: True if `item` is or can be validated as an IDType;
345
+ bool: True if `item` is or can be validated as an UUID;
437
346
  otherwise, False.
438
347
  """
439
348
  try:
440
- IDType.validate(item)
349
+ ID.get_id(item) # type: ignore
441
350
  return True
442
- except IDError:
351
+ except ValueError:
443
352
  return False
444
353
 
445
354