lionagi 0.15.14__py3-none-any.whl → 0.16.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.
Files changed (39) hide show
  1. lionagi/libs/validate/fuzzy_match_keys.py +5 -182
  2. lionagi/libs/validate/string_similarity.py +6 -331
  3. lionagi/ln/__init__.py +56 -66
  4. lionagi/ln/_async_call.py +13 -10
  5. lionagi/ln/_hash.py +33 -8
  6. lionagi/ln/_list_call.py +2 -35
  7. lionagi/ln/_to_list.py +51 -28
  8. lionagi/ln/_utils.py +156 -0
  9. lionagi/ln/concurrency/__init__.py +39 -31
  10. lionagi/ln/concurrency/_compat.py +65 -0
  11. lionagi/ln/concurrency/cancel.py +92 -109
  12. lionagi/ln/concurrency/errors.py +17 -17
  13. lionagi/ln/concurrency/patterns.py +249 -206
  14. lionagi/ln/concurrency/primitives.py +257 -216
  15. lionagi/ln/concurrency/resource_tracker.py +42 -155
  16. lionagi/ln/concurrency/task.py +55 -73
  17. lionagi/ln/concurrency/throttle.py +3 -0
  18. lionagi/ln/concurrency/utils.py +1 -0
  19. lionagi/ln/fuzzy/__init__.py +15 -0
  20. lionagi/ln/{_extract_json.py → fuzzy/_extract_json.py} +22 -9
  21. lionagi/ln/{_fuzzy_json.py → fuzzy/_fuzzy_json.py} +14 -8
  22. lionagi/ln/fuzzy/_fuzzy_match.py +172 -0
  23. lionagi/ln/fuzzy/_fuzzy_validate.py +46 -0
  24. lionagi/ln/fuzzy/_string_similarity.py +332 -0
  25. lionagi/ln/{_models.py → types.py} +153 -4
  26. lionagi/operations/flow.py +2 -1
  27. lionagi/operations/operate/operate.py +26 -16
  28. lionagi/protocols/contracts.py +46 -0
  29. lionagi/protocols/generic/event.py +6 -6
  30. lionagi/protocols/generic/processor.py +9 -5
  31. lionagi/protocols/ids.py +82 -0
  32. lionagi/protocols/types.py +10 -12
  33. lionagi/utils.py +34 -64
  34. lionagi/version.py +1 -1
  35. {lionagi-0.15.14.dist-info → lionagi-0.16.0.dist-info}/METADATA +4 -2
  36. {lionagi-0.15.14.dist-info → lionagi-0.16.0.dist-info}/RECORD +38 -31
  37. lionagi/ln/_types.py +0 -146
  38. {lionagi-0.15.14.dist-info → lionagi-0.16.0.dist-info}/WHEEL +0 -0
  39. {lionagi-0.15.14.dist-info → lionagi-0.16.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,134 +1,117 @@
1
- """Cancellation scope implementation for structured concurrency."""
1
+ """Cancellation helpers for structured concurrency (anyio-backed)."""
2
+
3
+ from __future__ import annotations
2
4
 
3
- import time
4
5
  from collections.abc import Iterator
5
6
  from contextlib import contextmanager
6
- from types import TracebackType
7
- from typing import TypeVar
8
7
 
9
8
  import anyio
10
9
 
11
- T = TypeVar("T")
12
-
13
-
14
- class CancelScope:
15
- """A context manager for controlling cancellation of tasks."""
16
-
17
- def __init__(self, deadline: float | None = None, shield: bool = False):
18
- """Initialize a new cancel scope.
19
-
20
- Args:
21
- deadline: The time (in seconds since the epoch) when this scope should be cancelled
22
- shield: If True, this scope shields its contents from external cancellation
23
- """
24
- self._scope = None
25
- self._deadline = deadline
26
- self._shield = shield
27
- self.cancel_called = False
28
- self.cancelled_caught = False
29
-
30
- def cancel(self) -> None:
31
- """Cancel this scope.
32
-
33
- This will cause all tasks within this scope to be cancelled.
34
- """
35
- self.cancel_called = True
36
- if self._scope is not None:
37
- self._scope.cancel()
38
-
39
- def __enter__(self) -> "CancelScope":
40
- """Enter the cancel scope context.
41
-
42
- Returns:
43
- The cancel scope instance.
44
- """
45
- # Use math.inf as the default deadline (no timeout)
46
- import math
47
-
48
- deadline = self._deadline if self._deadline is not None else math.inf
49
- self._scope = anyio.CancelScope(deadline=deadline, shield=self._shield)
50
- if self.cancel_called:
51
- self._scope.cancel()
52
- self._scope.__enter__()
53
- return self
54
-
55
- def __exit__(
56
- self,
57
- exc_type: type[BaseException] | None,
58
- exc_val: BaseException | None,
59
- exc_tb: TracebackType | None,
60
- ) -> bool:
61
- """Exit the cancel scope context.
62
-
63
- Returns:
64
- True if the exception was handled, False otherwise.
65
- """
66
- if self._scope is None:
67
- return False
68
-
69
- try:
70
- result = self._scope.__exit__(exc_type, exc_val, exc_tb)
71
- self.cancelled_caught = self._scope.cancelled_caught
72
- return result
73
- finally:
74
- self._scope = None
10
+ CancelScope = anyio.CancelScope
11
+ _INF = float("inf")
12
+
13
+
14
+ __all__ = (
15
+ "CancelScope",
16
+ "fail_after",
17
+ "move_on_after",
18
+ "fail_at",
19
+ "move_on_at",
20
+ "effective_deadline",
21
+ )
75
22
 
76
23
 
77
24
  @contextmanager
78
- def move_on_after(seconds: float | None) -> Iterator[CancelScope]:
79
- """Return a context manager that cancels its contents after the given number of seconds.
25
+ def fail_after(seconds: float | None) -> Iterator[CancelScope]:
26
+ """Create a context with a timeout that raises TimeoutError on expiry.
80
27
 
81
28
  Args:
82
- seconds: The number of seconds to wait before cancelling, or None to disable the timeout
29
+ seconds: Timeout duration in seconds. None means no timeout
30
+ (but still cancellable by outer scopes).
83
31
 
84
- Returns:
85
- A cancel scope that will be cancelled after the specified time
32
+ Yields:
33
+ CancelScope that can be cancelled after the timeout.
86
34
 
87
- Example:
88
- with move_on_after(5) as scope:
89
- await long_running_operation()
90
- if scope.cancelled_caught:
91
- print("Operation timed out")
35
+ Raises:
36
+ TimeoutError: If the timeout expires before the block completes.
92
37
  """
93
- deadline = None if seconds is None else time.time() + seconds
94
- scope = CancelScope(deadline=deadline)
95
- with scope:
38
+ if seconds is None:
39
+ # No timeout, but still cancellable by outer scopes
40
+ with CancelScope() as scope:
41
+ yield scope
42
+ return
43
+ with anyio.fail_after(seconds) as scope:
96
44
  yield scope
97
45
 
98
46
 
99
47
  @contextmanager
100
- def fail_after(seconds: float | None) -> Iterator[CancelScope]:
101
- """Return a context manager that raises TimeoutError if its contents take longer than the given time.
48
+ def move_on_after(seconds: float | None) -> Iterator[CancelScope]:
49
+ """Create a context with a timeout that silently cancels on expiry.
102
50
 
103
51
  Args:
104
- seconds: The number of seconds to wait before raising TimeoutError, or None to disable the timeout
52
+ seconds: Timeout duration in seconds. None means no timeout
53
+ (but still cancellable by outer scopes).
54
+
55
+ Yields:
56
+ CancelScope with cancelled_caught attribute to check if timeout occurred.
57
+ """
58
+ if seconds is None:
59
+ # No timeout, but still cancellable by outer scopes
60
+ with CancelScope() as scope:
61
+ yield scope
62
+ return
63
+ with anyio.move_on_after(seconds) as scope:
64
+ yield scope
105
65
 
106
- Returns:
107
- A cancel scope that will raise TimeoutError after the specified time
66
+
67
+ @contextmanager
68
+ def fail_at(deadline: float | None) -> Iterator[CancelScope]:
69
+ """Create a context that raises TimeoutError at an absolute deadline.
70
+
71
+ Args:
72
+ deadline: Absolute monotonic timestamp for timeout.
73
+ None means no timeout (but still cancellable).
74
+
75
+ Yields:
76
+ CancelScope that expires at the specified deadline.
108
77
 
109
78
  Raises:
110
- TimeoutError: If the operation takes longer than the specified time
111
-
112
- Example:
113
- try:
114
- with fail_after(5):
115
- await long_running_operation()
116
- except TimeoutError:
117
- print("Operation timed out")
79
+ TimeoutError: If the deadline is reached before the block completes.
118
80
  """
119
- if seconds is None:
120
- # No timeout
121
- scope = CancelScope(shield=True)
122
- with scope:
81
+ if deadline is None:
82
+ # No timeout, but still cancellable by outer scopes
83
+ with CancelScope() as scope:
84
+ yield scope
85
+ return
86
+ now = anyio.current_time()
87
+ seconds = max(0.0, deadline - now)
88
+ with fail_after(seconds) as scope:
89
+ yield scope
90
+
91
+
92
+ @contextmanager
93
+ def move_on_at(deadline: float | None) -> Iterator[CancelScope]:
94
+ """Create a context that silently cancels at an absolute deadline.
95
+
96
+ Args:
97
+ deadline: Absolute monotonic timestamp for timeout.
98
+ None means no timeout (but still cancellable).
99
+
100
+ Yields:
101
+ CancelScope with cancelled_caught attribute to check if deadline was reached.
102
+ """
103
+ if deadline is None:
104
+ # No timeout, but still cancellable by outer scopes
105
+ with CancelScope() as scope:
123
106
  yield scope
124
- else:
125
- deadline = time.time() + seconds
126
- scope = CancelScope(deadline=deadline)
127
- try:
128
- with scope:
129
- yield scope
130
- finally:
131
- if scope.cancelled_caught:
132
- raise TimeoutError(
133
- f"Operation took longer than {seconds} seconds"
134
- )
107
+ return
108
+ now = anyio.current_time()
109
+ seconds = max(0.0, deadline - now)
110
+ with anyio.move_on_after(seconds) as scope:
111
+ yield scope
112
+
113
+
114
+ def effective_deadline() -> float | None:
115
+ """Return the ambient effective deadline, or None if unlimited."""
116
+ d = anyio.current_effective_deadline()
117
+ return None if d == _INF else d
@@ -1,4 +1,6 @@
1
- """Error handling utilities for structured concurrency."""
1
+ """Error/cancellation utilities with backend-agnostic behavior."""
2
+
3
+ from __future__ import annotations
2
4
 
3
5
  from collections.abc import Awaitable, Callable
4
6
  from typing import Any, TypeVar
@@ -8,28 +10,26 @@ import anyio
8
10
  T = TypeVar("T")
9
11
 
10
12
 
11
- def get_cancelled_exc_class() -> type[BaseException]:
12
- """Get the exception class used for cancellation.
13
+ __all__ = (
14
+ "get_cancelled_exc_class",
15
+ "is_cancelled",
16
+ "shield",
17
+ )
13
18
 
14
- Returns:
15
- The exception class used for cancellation (CancelledError for asyncio,
16
- Cancelled for trio).
17
- """
19
+
20
+ def get_cancelled_exc_class() -> type[BaseException]:
21
+ """Return the backend-native cancellation exception class."""
18
22
  return anyio.get_cancelled_exc_class()
19
23
 
20
24
 
25
+ def is_cancelled(exc: BaseException) -> bool:
26
+ """True if this is the backend-native cancellation exception."""
27
+ return isinstance(exc, anyio.get_cancelled_exc_class())
28
+
29
+
21
30
  async def shield(
22
31
  func: Callable[..., Awaitable[T]], *args: Any, **kwargs: Any
23
32
  ) -> T:
24
- """Run a coroutine function with protection from cancellation.
25
-
26
- Args:
27
- func: The coroutine function to call
28
- *args: Positional arguments to pass to the function
29
- **kwargs: Keyword arguments to pass to the function
30
-
31
- Returns:
32
- The return value of the function
33
- """
33
+ """Run ``func`` immune to outer cancellation."""
34
34
  with anyio.CancelScope(shield=True):
35
35
  return await func(*args, **kwargs)