lionagi 0.18.0__py3-none-any.whl → 0.18.1__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 (78) hide show
  1. lionagi/_errors.py +0 -5
  2. lionagi/fields.py +83 -0
  3. lionagi/ln/__init__.py +3 -1
  4. lionagi/ln/concurrency/primitives.py +4 -4
  5. lionagi/ln/concurrency/task.py +1 -0
  6. lionagi/models/field_model.py +12 -4
  7. lionagi/models/hashable_model.py +2 -3
  8. lionagi/operations/ReAct/ReAct.py +1 -1
  9. lionagi/operations/act/act.py +3 -3
  10. lionagi/operations/builder.py +5 -7
  11. lionagi/operations/fields.py +380 -0
  12. lionagi/operations/flow.py +4 -6
  13. lionagi/operations/node.py +4 -4
  14. lionagi/operations/operate/operate.py +9 -7
  15. lionagi/{protocols/operatives → operations/operate}/operative.py +4 -5
  16. lionagi/{protocols/operatives → operations/operate}/step.py +34 -39
  17. lionagi/operations/select/select.py +1 -1
  18. lionagi/operations/select/utils.py +7 -1
  19. lionagi/operations/types.py +1 -1
  20. lionagi/protocols/action/manager.py +5 -6
  21. lionagi/protocols/contracts.py +2 -2
  22. lionagi/protocols/generic/__init__.py +22 -0
  23. lionagi/protocols/generic/element.py +36 -127
  24. lionagi/protocols/generic/pile.py +9 -10
  25. lionagi/protocols/generic/progression.py +23 -22
  26. lionagi/protocols/graph/edge.py +6 -5
  27. lionagi/protocols/ids.py +6 -49
  28. lionagi/protocols/messages/__init__.py +3 -1
  29. lionagi/protocols/messages/base.py +7 -6
  30. lionagi/protocols/messages/instruction.py +0 -1
  31. lionagi/protocols/types.py +1 -11
  32. lionagi/service/connections/__init__.py +3 -0
  33. lionagi/service/connections/providers/claude_code_cli.py +3 -2
  34. lionagi/service/hooks/_types.py +1 -1
  35. lionagi/service/hooks/_utils.py +1 -1
  36. lionagi/service/hooks/hook_event.py +3 -8
  37. lionagi/service/hooks/hook_registry.py +5 -5
  38. lionagi/service/hooks/hooked_event.py +61 -1
  39. lionagi/service/imodel.py +24 -20
  40. lionagi/service/third_party/claude_code.py +1 -2
  41. lionagi/service/third_party/openai_models.py +24 -22
  42. lionagi/service/token_calculator.py +1 -94
  43. lionagi/session/branch.py +26 -228
  44. lionagi/session/session.py +5 -90
  45. lionagi/version.py +1 -1
  46. {lionagi-0.18.0.dist-info → lionagi-0.18.1.dist-info}/METADATA +6 -5
  47. {lionagi-0.18.0.dist-info → lionagi-0.18.1.dist-info}/RECORD +49 -76
  48. lionagi/fields/__init__.py +0 -47
  49. lionagi/fields/action.py +0 -188
  50. lionagi/fields/base.py +0 -153
  51. lionagi/fields/code.py +0 -239
  52. lionagi/fields/file.py +0 -234
  53. lionagi/fields/instruct.py +0 -135
  54. lionagi/fields/reason.py +0 -55
  55. lionagi/fields/research.py +0 -52
  56. lionagi/operations/brainstorm/__init__.py +0 -2
  57. lionagi/operations/brainstorm/brainstorm.py +0 -498
  58. lionagi/operations/brainstorm/prompt.py +0 -11
  59. lionagi/operations/instruct/__init__.py +0 -2
  60. lionagi/operations/instruct/instruct.py +0 -28
  61. lionagi/operations/plan/__init__.py +0 -6
  62. lionagi/operations/plan/plan.py +0 -386
  63. lionagi/operations/plan/prompt.py +0 -25
  64. lionagi/operations/utils.py +0 -45
  65. lionagi/protocols/forms/__init__.py +0 -2
  66. lionagi/protocols/forms/base.py +0 -85
  67. lionagi/protocols/forms/flow.py +0 -79
  68. lionagi/protocols/forms/form.py +0 -86
  69. lionagi/protocols/forms/report.py +0 -48
  70. lionagi/protocols/mail/__init__.py +0 -2
  71. lionagi/protocols/mail/exchange.py +0 -220
  72. lionagi/protocols/mail/mail.py +0 -51
  73. lionagi/protocols/mail/mailbox.py +0 -103
  74. lionagi/protocols/mail/manager.py +0 -218
  75. lionagi/protocols/mail/package.py +0 -101
  76. lionagi/protocols/operatives/__init__.py +0 -2
  77. {lionagi-0.18.0.dist-info → lionagi-0.18.1.dist-info}/WHEEL +0 -0
  78. {lionagi-0.18.0.dist-info → lionagi-0.18.1.dist-info}/licenses/LICENSE +0 -0
@@ -10,8 +10,9 @@ using Events for synchronization and CapacityLimiter for concurrency control.
10
10
 
11
11
  import os
12
12
  from typing import TYPE_CHECKING, Any
13
+ from uuid import UUID
13
14
 
14
- from lionagi.ln._async_call import AlcallParams
15
+ from lionagi.ln import AlcallParams
15
16
  from lionagi.ln.concurrency import CapacityLimiter, ConcurrencyEvent
16
17
  from lionagi.operations.node import Operation
17
18
  from lionagi.protocols.types import EventStatus
@@ -164,13 +165,11 @@ class DependencyAwareExecutor:
164
165
  # Add to session branches collection directly
165
166
  # Check if this is a real branch (not a mock)
166
167
  try:
167
- from lionagi.protocols.types import IDType
168
-
169
168
  # Try to validate the ID
170
169
  if hasattr(branch_clone, "id"):
171
170
  branch_id = branch_clone.id
172
171
  # Only add to collections if it's a valid ID
173
- if isinstance(branch_id, (str, IDType)) or (
172
+ if isinstance(branch_id, (str, UUID)) or (
174
173
  hasattr(branch_id, "__str__")
175
174
  and not hasattr(branch_id, "_mock_name")
176
175
  ):
@@ -334,7 +333,7 @@ class DependencyAwareExecutor:
334
333
 
335
334
  # Wait for ALL sources (sources are now strings from builder.py)
336
335
  for source_id_str in sources:
337
- # Convert string back to IDType for lookup
336
+ # Convert string back to UUID for lookup
338
337
  # Check all operations to find matching ID
339
338
  for op_id in self.completion_events.keys():
340
339
  if str(op_id) == source_id_str:
@@ -367,7 +366,6 @@ class DependencyAwareExecutor:
367
366
  result, (str, int, float, bool)
368
367
  ):
369
368
  result = to_dict(result, recursive=True)
370
- # Use string representation of IDType for JSON serialization
371
369
  pred_context[f"{str(pred.id)}_result"] = result
372
370
 
373
371
  if "context" not in operation.parameters:
@@ -6,7 +6,7 @@ from uuid import UUID
6
6
  from anyio import get_cancelled_exc_class
7
7
  from pydantic import BaseModel, Field
8
8
 
9
- from lionagi.protocols.types import ID, Event, EventStatus, IDType, Node
9
+ from lionagi.protocols.types import ID, Event, EventStatus, Node
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from lionagi.session.branch import Branch
@@ -37,12 +37,12 @@ class Operation(Node, Event):
37
37
  )
38
38
 
39
39
  @property
40
- def branch_id(self) -> IDType | None:
40
+ def branch_id(self) -> UUID | None:
41
41
  if a := self.metadata.get("branch_id"):
42
42
  return ID.get_id(a)
43
43
 
44
44
  @branch_id.setter
45
- def branch_id(self, value: str | UUID | IDType | None):
45
+ def branch_id(self, value: str | UUID | None):
46
46
  if value is None:
47
47
  self.metadata.pop("branch_id", None)
48
48
  else:
@@ -54,7 +54,7 @@ class Operation(Node, Event):
54
54
  return ID.get_id(a)
55
55
 
56
56
  @graph_id.setter
57
- def graph_id(self, value: str | UUID | IDType | None):
57
+ def graph_id(self, value: str | UUID | None):
58
58
  if value is None:
59
59
  self.metadata.pop("graph_id", None)
60
60
  else:
@@ -6,19 +6,21 @@ from typing import TYPE_CHECKING, Literal
6
6
 
7
7
  from pydantic import BaseModel, JsonValue
8
8
 
9
- from lionagi.fields.instruct import Instruct
9
+ from lionagi.ln import AlcallParams
10
10
  from lionagi.ln.fuzzy import FuzzyMatchKeysParams
11
11
  from lionagi.models import FieldModel, ModelParams
12
- from lionagi.protocols.types import Instruction, Progression, SenderRecipient
13
- from lionagi.session.branch import AlcallParams
12
+ from lionagi.protocols.generic import Progression
13
+ from lionagi.protocols.messages import Instruction, SenderRecipient
14
14
 
15
+ from ..fields import Instruct
15
16
  from ..types import ActionParam, ChatParam, HandleValidation, ParseParam
16
17
 
17
18
  if TYPE_CHECKING:
18
- from lionagi.protocols.operatives.step import Operative
19
19
  from lionagi.service.imodel import iModel
20
20
  from lionagi.session.branch import Branch, ToolRef
21
21
 
22
+ from .operative import Operative
23
+
22
24
 
23
25
  def prepare_operate_kw(
24
26
  branch: "Branch",
@@ -105,7 +107,7 @@ def prepare_operate_kw(
105
107
  instruct.action_strategy = action_strategy
106
108
 
107
109
  # Build the Operative - always create it for backwards compatibility
108
- from lionagi.protocols.operatives.step import Step
110
+ from .step import Step
109
111
 
110
112
  operative = Step.request_operative(
111
113
  request_params=request_params,
@@ -229,7 +231,7 @@ async def operate(
229
231
  operative = None
230
232
 
231
233
  if model_class:
232
- from lionagi.protocols.operatives.step import Step
234
+ from .step import Step
233
235
 
234
236
  operative = Step.request_operative(
235
237
  reason=reason,
@@ -306,7 +308,7 @@ async def operate(
306
308
  result.update({"action_responses": action_response_models})
307
309
  return result
308
310
 
309
- from lionagi.protocols.operatives.step import Step
311
+ from .step import Step
310
312
 
311
313
  operative.response_model = result
312
314
  operative = Step.respond_operative(
@@ -6,10 +6,9 @@ from typing import Any
6
6
  from pydantic import BaseModel
7
7
  from pydantic.fields import FieldInfo
8
8
 
9
- from lionagi.ln import extract_json
10
- from lionagi.ln.fuzzy._fuzzy_match import fuzzy_match_keys
9
+ from lionagi.ln import extract_json, fuzzy_match_keys
10
+ from lionagi.ln.types import Undefined
11
11
  from lionagi.models import FieldModel, ModelParams, OperableModel
12
- from lionagi.utils import UNDEFINED
13
12
 
14
13
 
15
14
  class Operative:
@@ -169,7 +168,7 @@ class Operative:
169
168
  d_ = fuzzy_match_keys(
170
169
  d_, self.request_type.model_fields, handle_unmatched="raise"
171
170
  )
172
- d_ = {k: v for k, v in d_.items() if v != UNDEFINED}
171
+ d_ = {k: v for k, v in d_.items() if v != Undefined}
173
172
  self.response_model = self.request_type.model_validate(d_)
174
173
  self._should_retry = False
175
174
  except Exception:
@@ -190,7 +189,7 @@ class Operative:
190
189
  d_ = fuzzy_match_keys(
191
190
  d_, self.request_type.model_fields, handle_unmatched="force"
192
191
  )
193
- d_ = {k: v for k, v in d_.items() if v != UNDEFINED}
192
+ d_ = {k: v for k, v in d_.items() if v != Undefined}
194
193
  self.response_model = self.request_type.model_validate(d_)
195
194
  self._should_retry = False
196
195
  except Exception:
@@ -4,14 +4,10 @@
4
4
  from pydantic import BaseModel
5
5
  from pydantic.fields import FieldInfo
6
6
 
7
- from lionagi.fields.action import (
8
- ACTION_REQUESTS_FIELD,
9
- ACTION_REQUIRED_FIELD,
10
- ACTION_RESPONSES_FIELD,
11
- )
12
- from lionagi.fields.reason import REASON_FIELD
13
7
  from lionagi.models import FieldModel, ModelParams
14
- from lionagi.protocols.operatives.operative import Operative
8
+
9
+ from ..fields import get_default_field
10
+ from .operative import Operative
15
11
 
16
12
 
17
13
  class Step:
@@ -71,18 +67,17 @@ class Step:
71
67
  field_models = field_models or []
72
68
  exclude_fields = exclude_fields or []
73
69
  field_descriptions = field_descriptions or {}
74
- if reason and REASON_FIELD not in field_models:
75
- field_models.append(REASON_FIELD)
76
- if actions and ACTION_REQUESTS_FIELD not in field_models:
77
- field_models.extend(
78
- [
79
- ACTION_REQUESTS_FIELD,
80
- ACTION_REQUIRED_FIELD,
81
- ]
82
- )
83
-
70
+ if reason and (fm := get_default_field("reason")) not in field_models:
71
+ field_models.append(fm)
72
+ if (
73
+ actions
74
+ and (fm := get_default_field("action_requests"))
75
+ not in field_models
76
+ ):
77
+ fm2 = get_default_field("action_required")
78
+ field_models.extend([fm, fm2])
84
79
  if isinstance(request_params, ModelParams):
85
- request_params = request_params.model_dump()
80
+ request_params = request_params.to_dict()
86
81
 
87
82
  request_params = request_params or {}
88
83
  request_params_fields = {
@@ -143,15 +138,17 @@ class Step:
143
138
  additional_data = additional_data or {}
144
139
  field_models = field_models or []
145
140
  if hasattr(operative.response_model, "action_required"):
146
- field_models.extend(
147
- [
148
- ACTION_RESPONSES_FIELD,
149
- ACTION_REQUIRED_FIELD,
150
- ACTION_REQUESTS_FIELD,
151
- ]
152
- )
141
+ for i in {
142
+ "action_requests",
143
+ "action_required",
144
+ "action_responses",
145
+ }:
146
+ fm = get_default_field(i)
147
+ if fm not in field_models:
148
+ field_models.append(fm)
149
+
153
150
  if "reason" in type(operative.response_model).model_fields:
154
- field_models.extend([REASON_FIELD])
151
+ field_models.append(get_default_field("reason"))
155
152
 
156
153
  operative = Step._create_response_type(
157
154
  operative=operative,
@@ -201,24 +198,22 @@ class Step:
201
198
  hasattr(operative.request_type, "action_required")
202
199
  and operative.response_model.action_required
203
200
  ):
204
- field_models.extend(
205
- [
206
- ACTION_RESPONSES_FIELD,
207
- ACTION_REQUIRED_FIELD,
208
- ACTION_REQUESTS_FIELD,
209
- ]
210
- )
211
- if hasattr(operative.request_type, "reason"):
212
- field_models.extend([REASON_FIELD])
201
+ for i in {
202
+ "action_requests",
203
+ "action_required",
204
+ "action_responses",
205
+ }:
206
+ fm = get_default_field(i)
207
+ if fm not in field_models:
208
+ field_models.append(fm)
213
209
 
214
- exclude_fields = exclude_fields or []
215
- # Note: We no longer have access to request_params.exclude_fields
216
- # since Operative doesn't store ModelParams anymore
210
+ if hasattr(operative.request_type, "reason"):
211
+ field_models.append(get_default_field("reason"))
217
212
 
218
213
  operative.create_response_type(
219
214
  response_params=response_params,
220
215
  field_models=field_models,
221
- exclude_fields=exclude_fields,
216
+ exclude_fields=exclude_fields or [],
222
217
  doc=response_doc,
223
218
  config_dict=response_config_dict,
224
219
  frozen=frozen_response,
@@ -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,7 +7,7 @@ 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
12
  from lionagi.ln.types import Params
13
13
  from lionagi.protocols.action.tool import ToolRef
@@ -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