lionagi 0.7.0__py3-none-any.whl → 0.7.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.
- lionagi/operations/ReAct/ReAct.py +2 -2
- lionagi/operations/_act/act.py +10 -3
- lionagi/operations/communicate/communicate.py +0 -59
- lionagi/operations/interpret/interpret.py +1 -2
- lionagi/operations/operate/operate.py +10 -5
- lionagi/operations/parse/parse.py +0 -36
- lionagi/operations/plan/plan.py +3 -3
- lionagi/operatives/action/manager.py +105 -82
- lionagi/operatives/action/request_response_model.py +31 -0
- lionagi/operatives/action/tool.py +50 -20
- lionagi/protocols/_concepts.py +1 -1
- lionagi/protocols/adapters/adapter.py +25 -0
- lionagi/protocols/adapters/json_adapter.py +107 -27
- lionagi/protocols/adapters/pandas_/csv_adapter.py +55 -11
- lionagi/protocols/adapters/pandas_/excel_adapter.py +52 -10
- lionagi/protocols/adapters/pandas_/pd_dataframe_adapter.py +54 -4
- lionagi/protocols/adapters/pandas_/pd_series_adapter.py +40 -0
- lionagi/protocols/generic/element.py +1 -1
- lionagi/protocols/generic/pile.py +5 -8
- lionagi/protocols/graph/edge.py +1 -1
- lionagi/protocols/graph/graph.py +16 -8
- lionagi/protocols/graph/node.py +1 -1
- lionagi/protocols/mail/exchange.py +126 -15
- lionagi/protocols/mail/mail.py +33 -0
- lionagi/protocols/mail/mailbox.py +62 -0
- lionagi/protocols/mail/manager.py +97 -41
- lionagi/protocols/mail/package.py +57 -3
- lionagi/protocols/messages/action_request.py +77 -26
- lionagi/protocols/messages/action_response.py +55 -26
- lionagi/protocols/messages/assistant_response.py +50 -15
- lionagi/protocols/messages/base.py +36 -0
- lionagi/protocols/messages/instruction.py +175 -145
- lionagi/protocols/messages/manager.py +152 -56
- lionagi/protocols/messages/message.py +61 -25
- lionagi/protocols/messages/system.py +54 -19
- lionagi/service/imodel.py +24 -0
- lionagi/session/branch.py +40 -32
- lionagi/utils.py +1 -0
- lionagi/version.py +1 -1
- {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/METADATA +1 -1
- {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/RECORD +43 -43
- {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/WHEEL +0 -0
- {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/licenses/LICENSE +0 -0
@@ -994,8 +994,8 @@ class Pile(Element, Collective[E], Generic[E]):
|
|
994
994
|
path_or_buf,
|
995
995
|
*,
|
996
996
|
use_pd: bool = False,
|
997
|
+
many: bool = False,
|
997
998
|
mode="w",
|
998
|
-
verbose=False,
|
999
999
|
**kwargs,
|
1000
1000
|
):
|
1001
1001
|
"""Export collection to JSON file.
|
@@ -1007,15 +1007,9 @@ class Pile(Element, Collective[E], Generic[E]):
|
|
1007
1007
|
verbose: Print confirmation message.
|
1008
1008
|
**kwargs: Additional arguments for json.dump() or DataFrame.to_json().
|
1009
1009
|
"""
|
1010
|
-
|
1011
1010
|
if use_pd:
|
1012
1011
|
return self.to_df().to_json(mode=mode, **kwargs)
|
1013
|
-
|
1014
|
-
with open(path_or_buf, mode) as f:
|
1015
|
-
json.dump(dict_, f, **kwargs)
|
1016
|
-
|
1017
|
-
if verbose:
|
1018
|
-
print(f"Saved Pile to {path_or_buf}")
|
1012
|
+
return self.adapt_to(".json", fp=path_or_buf, mode=mode, many=many)
|
1019
1013
|
|
1020
1014
|
|
1021
1015
|
def pile(
|
@@ -1076,3 +1070,6 @@ def to_list_type(value: Any, /) -> list[Any]:
|
|
1076
1070
|
if isinstance(value, list | tuple | set | deque | Generator):
|
1077
1071
|
return list(value)
|
1078
1072
|
return [value]
|
1073
|
+
|
1074
|
+
|
1075
|
+
# File: lionagi/protocols/generic/pile.py
|
lionagi/protocols/graph/edge.py
CHANGED
lionagi/protocols/graph/graph.py
CHANGED
@@ -204,13 +204,14 @@ class Graph(Element, Relational):
|
|
204
204
|
def to_networkx(self, **kwargs) -> Any:
|
205
205
|
"""Convert the graph to a NetworkX graph object."""
|
206
206
|
try:
|
207
|
-
|
207
|
+
from networkx import DiGraph # type: ignore
|
208
|
+
|
208
209
|
except ImportError:
|
209
|
-
|
210
|
-
"NetworkX is not installed. Please install it with `pip install networkx`."
|
211
|
-
)
|
210
|
+
from lionagi.libs.package.imports import check_import
|
212
211
|
|
213
|
-
|
212
|
+
DiGraph = check_import("networkx", import_name="DiGraph")
|
213
|
+
|
214
|
+
g = DiGraph(**kwargs)
|
214
215
|
for node in self.internal_nodes:
|
215
216
|
node_info = node.to_dict()
|
216
217
|
node_info.pop("id")
|
@@ -238,9 +239,13 @@ class Graph(Element, Relational):
|
|
238
239
|
import matplotlib.pyplot as plt # type: ignore
|
239
240
|
import networkx as nx # type: ignore
|
240
241
|
except ImportError:
|
241
|
-
|
242
|
-
|
243
|
-
)
|
242
|
+
from lionagi.libs.package.imports import check_import
|
243
|
+
|
244
|
+
check_import("matplotlib")
|
245
|
+
check_import("networkx")
|
246
|
+
|
247
|
+
import matplotlib.pyplot as plt # type: ignore
|
248
|
+
import networkx as nx # type: ignore
|
244
249
|
|
245
250
|
g = self.to_networkx(**kwargs)
|
246
251
|
pos = nx.spring_layout(g)
|
@@ -288,3 +293,6 @@ class Graph(Element, Relational):
|
|
288
293
|
|
289
294
|
def __contains__(self, item: object) -> bool:
|
290
295
|
return item in self.internal_nodes or item in self.internal_edges
|
296
|
+
|
297
|
+
|
298
|
+
# File: lionagi/protocols/graph/graph.py
|
lionagi/protocols/graph/node.py
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
+
"""
|
6
|
+
Provides the `Exchange` class, which orchestrates mail flows among
|
7
|
+
sources that implement `Communicatable`. It collects pending outgoing
|
8
|
+
mail from each source and delivers them to the appropriate recipients.
|
9
|
+
"""
|
10
|
+
|
5
11
|
import asyncio
|
6
12
|
from typing import Any
|
7
13
|
|
@@ -15,8 +21,34 @@ from .mailbox import Mailbox
|
|
15
21
|
|
16
22
|
|
17
23
|
class Exchange:
|
24
|
+
"""
|
25
|
+
Manages mail exchange operations among a set of sources that are
|
26
|
+
`Communicatable`. Each source has an associated `Mailbox` to store
|
27
|
+
inbound and outbound mail.
|
28
|
+
|
29
|
+
Attributes
|
30
|
+
----------
|
31
|
+
sources : Pile[Communicatable]
|
32
|
+
The communicatable sources participating in the exchange.
|
33
|
+
buffer : dict[IDType, list[Mail]]
|
34
|
+
A temporary holding area for mail messages before they reach
|
35
|
+
their recipient's mailbox.
|
36
|
+
mailboxes : dict[IDType, Mailbox]
|
37
|
+
Maps each source's ID to its Mailbox.
|
38
|
+
_execute_stop : bool
|
39
|
+
A flag indicating whether to stop the asynchronous execution loop.
|
40
|
+
"""
|
18
41
|
|
19
42
|
def __init__(self, sources: ID[Communicatable].ItemSeq = None):
|
43
|
+
"""
|
44
|
+
Initialize an `Exchange` instance.
|
45
|
+
|
46
|
+
Parameters
|
47
|
+
----------
|
48
|
+
sources : ID[Communicatable].ItemSeq, optional
|
49
|
+
One or more communicatable sources to manage. If provided,
|
50
|
+
they are immediately added.
|
51
|
+
"""
|
20
52
|
self.sources: Pile[Communicatable] = Pile(
|
21
53
|
item_type={Communicatable}, strict_type=False
|
22
54
|
)
|
@@ -26,7 +58,20 @@ class Exchange:
|
|
26
58
|
self.add_source(sources)
|
27
59
|
self._execute_stop: bool = False
|
28
60
|
|
29
|
-
def add_source(self, sources: ID[Communicatable].ItemSeq):
|
61
|
+
def add_source(self, sources: ID[Communicatable].ItemSeq) -> None:
|
62
|
+
"""
|
63
|
+
Register new communicatable sources for mail exchange.
|
64
|
+
|
65
|
+
Parameters
|
66
|
+
----------
|
67
|
+
sources : ID[Communicatable].ItemSeq
|
68
|
+
The source(s) to be added.
|
69
|
+
|
70
|
+
Raises
|
71
|
+
------
|
72
|
+
ValueError
|
73
|
+
If the given sources already exist in this exchange.
|
74
|
+
"""
|
30
75
|
if sources in self.sources:
|
31
76
|
raise ValueError(
|
32
77
|
f"Source {sources} already exists in the mail manager."
|
@@ -37,8 +82,21 @@ class Exchange:
|
|
37
82
|
self.mailboxes[source.id] = source.mailbox
|
38
83
|
self.buffer.update({source.id: [] for source in sources})
|
39
84
|
|
40
|
-
def delete_source(self, sources: ID[Communicatable].ItemSeq):
|
41
|
-
"""
|
85
|
+
def delete_source(self, sources: ID[Communicatable].ItemSeq) -> None:
|
86
|
+
"""
|
87
|
+
Remove specified sources from the exchange, clearing any pending
|
88
|
+
mail associated with them.
|
89
|
+
|
90
|
+
Parameters
|
91
|
+
----------
|
92
|
+
sources : ID[Communicatable].ItemSeq
|
93
|
+
The source(s) to remove.
|
94
|
+
|
95
|
+
Raises
|
96
|
+
------
|
97
|
+
ValueError
|
98
|
+
If the given sources do not exist in this exchange.
|
99
|
+
"""
|
42
100
|
if not sources in self.sources:
|
43
101
|
raise ValueError(
|
44
102
|
f"Source {sources} does not exist in the mail manager."
|
@@ -57,13 +115,47 @@ class Exchange:
|
|
57
115
|
item: Any,
|
58
116
|
request_source: Any = None,
|
59
117
|
) -> Mail:
|
118
|
+
"""
|
119
|
+
Helper method to create a new Mail instance.
|
120
|
+
|
121
|
+
Parameters
|
122
|
+
----------
|
123
|
+
sender : ID[Communicatable]
|
124
|
+
The ID (or Communicatable) identifying the mail sender.
|
125
|
+
recipient : ID[Communicatable]
|
126
|
+
The ID (or Communicatable) identifying the mail recipient.
|
127
|
+
category : PackageCategory | str
|
128
|
+
A classification for the package contents.
|
129
|
+
item : Any
|
130
|
+
The actual item/data to be sent.
|
131
|
+
request_source : Any, optional
|
132
|
+
Additional context about the request origin, if any.
|
133
|
+
|
134
|
+
Returns
|
135
|
+
-------
|
136
|
+
Mail
|
137
|
+
A newly created Mail object ready for sending.
|
138
|
+
"""
|
60
139
|
package = Package(
|
61
140
|
category=category, item=item, request_source=request_source
|
62
141
|
)
|
63
142
|
return Mail(sender=sender, recipient=recipient, package=package)
|
64
143
|
|
65
|
-
def collect(self, sender: ID[Communicatable]):
|
66
|
-
"""
|
144
|
+
def collect(self, sender: ID[Communicatable]) -> None:
|
145
|
+
"""
|
146
|
+
Collect all outbound mail from a specific sender, moving it
|
147
|
+
to the exchange buffer.
|
148
|
+
|
149
|
+
Parameters
|
150
|
+
----------
|
151
|
+
sender : ID[Communicatable]
|
152
|
+
The ID of the source from which mail is collected.
|
153
|
+
|
154
|
+
Raises
|
155
|
+
------
|
156
|
+
ValueError
|
157
|
+
If the sender is not part of this exchange.
|
158
|
+
"""
|
67
159
|
if sender not in self.sources:
|
68
160
|
raise ValueError(f"Sender source {sender} does not exist.")
|
69
161
|
|
@@ -73,7 +165,21 @@ class Exchange:
|
|
73
165
|
mail: Mail = sender_mailbox.pile_.popleft()
|
74
166
|
self.buffer[mail.recipient].append(mail)
|
75
167
|
|
76
|
-
def deliver(self, recipient: ID[Communicatable]):
|
168
|
+
def deliver(self, recipient: ID[Communicatable]) -> None:
|
169
|
+
"""
|
170
|
+
Deliver all mail in the buffer addressed to a specific recipient.
|
171
|
+
|
172
|
+
Parameters
|
173
|
+
----------
|
174
|
+
recipient : ID[Communicatable]
|
175
|
+
The ID of the source to receive mail.
|
176
|
+
|
177
|
+
Raises
|
178
|
+
------
|
179
|
+
ValueError
|
180
|
+
If the recipient is not part of this exchange or if mail
|
181
|
+
references an unknown sender.
|
182
|
+
"""
|
77
183
|
if recipient not in self.sources:
|
78
184
|
raise ValueError(f"Recipient source {recipient} does not exist.")
|
79
185
|
|
@@ -90,27 +196,32 @@ class Exchange:
|
|
90
196
|
recipient_mailbox.append_in(mail)
|
91
197
|
|
92
198
|
def collect_all(self) -> None:
|
93
|
-
"""
|
199
|
+
"""
|
200
|
+
Collect mail from every source in this exchange.
|
201
|
+
"""
|
94
202
|
for source in self.sources:
|
95
203
|
self.collect(sender=source.id)
|
96
204
|
|
97
205
|
def deliver_all(self) -> None:
|
98
|
-
"""
|
206
|
+
"""
|
207
|
+
Deliver mail to every source in this exchange.
|
208
|
+
"""
|
99
209
|
for source in self.sources:
|
100
210
|
self.deliver(recipient=source.id)
|
101
211
|
|
102
212
|
async def execute(self, refresh_time: int = 1) -> None:
|
103
213
|
"""
|
104
|
-
|
105
|
-
|
106
|
-
This method runs in a loop, collecting and sending mail at
|
107
|
-
regular intervals.
|
214
|
+
Continuously collect and deliver mail in an asynchronous loop.
|
108
215
|
|
109
|
-
|
110
|
-
|
111
|
-
|
216
|
+
Parameters
|
217
|
+
----------
|
218
|
+
refresh_time : int, optional
|
219
|
+
Number of seconds to wait between each cycle. Defaults to 1.
|
112
220
|
"""
|
113
221
|
while not self._execute_stop:
|
114
222
|
self.collect_all()
|
115
223
|
self.deliver_all()
|
116
224
|
await asyncio.sleep(refresh_time)
|
225
|
+
|
226
|
+
|
227
|
+
# File: lionagi/protocols/mail/exchange.py
|
lionagi/protocols/mail/mail.py
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
+
"""
|
6
|
+
Defines the `Mail` class, which is a `Sendable` element representing
|
7
|
+
a single piece of mail, carrying a `Package` between a sender
|
8
|
+
and recipient.
|
9
|
+
"""
|
10
|
+
|
5
11
|
from pydantic import field_validator
|
6
12
|
|
7
13
|
from .._concepts import Sendable
|
@@ -11,6 +17,20 @@ from .package import Package, PackageCategory
|
|
11
17
|
|
12
18
|
|
13
19
|
class Mail(Element, Sendable):
|
20
|
+
"""
|
21
|
+
A single mail message that can be sent between communicatable entities.
|
22
|
+
It includes a sender, recipient, and a package that describes the
|
23
|
+
mail's content.
|
24
|
+
|
25
|
+
Attributes
|
26
|
+
----------
|
27
|
+
sender : IDType
|
28
|
+
The ID representing the mail sender.
|
29
|
+
recipient : IDType
|
30
|
+
The ID representing the mail recipient.
|
31
|
+
package : Package
|
32
|
+
The package (category + payload) contained in this mail.
|
33
|
+
"""
|
14
34
|
|
15
35
|
sender: IDType
|
16
36
|
recipient: IDType
|
@@ -18,8 +38,21 @@ class Mail(Element, Sendable):
|
|
18
38
|
|
19
39
|
@field_validator("sender", "recipient")
|
20
40
|
def _validate_sender_recipient(cls, value):
|
41
|
+
"""
|
42
|
+
Validate that the sender and recipient fields are correct IDTypes.
|
43
|
+
"""
|
21
44
|
return IDType.validate(value)
|
22
45
|
|
23
46
|
@property
|
24
47
|
def category(self) -> PackageCategory:
|
48
|
+
"""
|
49
|
+
Shortcut for retrieving the category from the underlying package.
|
50
|
+
|
51
|
+
Returns
|
52
|
+
-------
|
53
|
+
PackageCategory
|
54
|
+
"""
|
25
55
|
return self.package.category
|
56
|
+
|
57
|
+
|
58
|
+
# File: lionagi/protocols/mail/mail.py
|
@@ -2,6 +2,11 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
+
"""
|
6
|
+
Implements a simple mailbox system for each Communicatable entity.
|
7
|
+
Holds inbound and outbound mail, stored internally in a `Pile`.
|
8
|
+
"""
|
9
|
+
|
5
10
|
from lionagi.protocols.generic.element import IDType
|
6
11
|
|
7
12
|
from ..generic.pile import Pile, Progression
|
@@ -11,37 +16,94 @@ __all__ = ("Mailbox",)
|
|
11
16
|
|
12
17
|
|
13
18
|
class Mailbox:
|
19
|
+
"""
|
20
|
+
A mailbox that accumulates inbound and outbound mail for a single
|
21
|
+
communicatable source.
|
22
|
+
|
23
|
+
Attributes
|
24
|
+
----------
|
25
|
+
pile_ : Pile[Mail]
|
26
|
+
A concurrency-safe collection storing all mail items.
|
27
|
+
pending_ins : dict[IDType, Progression]
|
28
|
+
Maps each sender's ID to a progression of inbound mail.
|
29
|
+
pending_outs : Progression
|
30
|
+
A progression of mail items waiting to be sent (outbound).
|
31
|
+
"""
|
14
32
|
|
15
33
|
def __init__(self):
|
34
|
+
"""
|
35
|
+
Initialize an empty Mailbox with separate tracks for inbound
|
36
|
+
and outbound mail.
|
37
|
+
"""
|
16
38
|
self.pile_ = Pile(item_type=Mail, strict_type=True)
|
17
39
|
self.pending_ins: dict[IDType, Progression] = {}
|
18
40
|
self.pending_outs = Progression()
|
19
41
|
|
20
42
|
def __contains__(self, item: Mail) -> bool:
|
43
|
+
"""
|
44
|
+
Check if a mail item is currently in this mailbox.
|
45
|
+
"""
|
21
46
|
return item in self.pile_
|
22
47
|
|
23
48
|
@property
|
24
49
|
def senders(self) -> list[str]:
|
50
|
+
"""
|
51
|
+
List of sender IDs that have inbound mail in this mailbox.
|
52
|
+
|
53
|
+
Returns
|
54
|
+
-------
|
55
|
+
list[str]
|
56
|
+
"""
|
25
57
|
return list(self.pending_ins.keys())
|
26
58
|
|
27
59
|
def append_in(self, item: Mail, /):
|
60
|
+
"""
|
61
|
+
Add a mail item to the inbound queue for the item's sender.
|
62
|
+
|
63
|
+
Parameters
|
64
|
+
----------
|
65
|
+
item : Mail
|
66
|
+
"""
|
28
67
|
if item.sender not in self.pending_ins:
|
29
68
|
self.pending_ins[item.sender] = Progression()
|
30
69
|
self.pending_ins[item.sender].include(item)
|
31
70
|
self.pile_.include(item)
|
32
71
|
|
33
72
|
def append_out(self, item: Mail, /):
|
73
|
+
"""
|
74
|
+
Add a mail item to the outbound (pending_outs) queue.
|
75
|
+
|
76
|
+
Parameters
|
77
|
+
----------
|
78
|
+
item : Mail
|
79
|
+
"""
|
34
80
|
self.pending_outs.include(item)
|
35
81
|
self.pile_.include(item)
|
36
82
|
|
37
83
|
def exclude(self, item: Mail, /):
|
84
|
+
"""
|
85
|
+
Remove a mail item from all internal references (inbound, outbound, and pile).
|
86
|
+
|
87
|
+
Parameters
|
88
|
+
----------
|
89
|
+
item : Mail
|
90
|
+
"""
|
38
91
|
self.pile_.exclude(item)
|
39
92
|
self.pending_outs.exclude(item)
|
40
93
|
for v in self.pending_ins.values():
|
41
94
|
v.exclude(item)
|
42
95
|
|
43
96
|
def __bool__(self) -> bool:
|
97
|
+
"""
|
98
|
+
Indicates if the mailbox contains any mail.
|
99
|
+
"""
|
44
100
|
return bool(self.pile_)
|
45
101
|
|
46
102
|
def __len__(self) -> int:
|
103
|
+
"""
|
104
|
+
Number of mail items in this mailbox.
|
105
|
+
"""
|
47
106
|
return len(self.pile_)
|
107
|
+
|
108
|
+
|
109
|
+
# File: lionagi/protocols/mail/mailbox.py
|
@@ -2,6 +2,11 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
+
"""
|
6
|
+
Defines the `MailManager` class, which coordinates mail operations
|
7
|
+
across multiple sources in a more abstract or high-level manner.
|
8
|
+
"""
|
9
|
+
|
5
10
|
import asyncio
|
6
11
|
from collections import deque
|
7
12
|
from typing import Any
|
@@ -17,19 +22,29 @@ from .mail import Mail, Package, PackageCategory
|
|
17
22
|
|
18
23
|
class MailManager(Manager):
|
19
24
|
"""
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
25
|
+
A manager for mail operations across various observable sources
|
26
|
+
within LionAGI. Unlike `Exchange`, this class can manage the state
|
27
|
+
of multiple sources in a more general or higher-level context,
|
28
|
+
storing mail queues in a dictionary rather than individual buffers.
|
29
|
+
|
30
|
+
Attributes
|
31
|
+
----------
|
32
|
+
sources : Pile[Observable]
|
33
|
+
A concurrency-safe collection of known sources.
|
34
|
+
mails : dict[str, dict[str, deque]]
|
35
|
+
A nested mapping of recipient -> sender -> queue of mail.
|
36
|
+
execute_stop : bool
|
37
|
+
Controls the asynchronous execution loop; set to True to exit.
|
24
38
|
"""
|
25
39
|
|
26
40
|
def __init__(self, sources: ID.Item | ID.ItemSeq = None) -> None:
|
27
41
|
"""
|
28
42
|
Initialize a MailManager instance.
|
29
43
|
|
30
|
-
|
31
|
-
|
32
|
-
|
44
|
+
Parameters
|
45
|
+
----------
|
46
|
+
sources : ID.Item | ID.ItemSeq, optional
|
47
|
+
Initial source(s) to manage. Each source must be an Observable.
|
33
48
|
"""
|
34
49
|
self.sources: Pile[Observable] = Pile()
|
35
50
|
self.mails: dict[str, dict[str, deque]] = {}
|
@@ -40,14 +55,17 @@ class MailManager(Manager):
|
|
40
55
|
|
41
56
|
def add_sources(self, sources: ID.Item | ID.ItemSeq, /) -> None:
|
42
57
|
"""
|
43
|
-
|
58
|
+
Register new sources in the MailManager.
|
44
59
|
|
45
|
-
|
46
|
-
|
47
|
-
|
60
|
+
Parameters
|
61
|
+
----------
|
62
|
+
sources : ID.Item | ID.ItemSeq
|
63
|
+
A single source or multiple sources to be added.
|
48
64
|
|
49
|
-
Raises
|
50
|
-
|
65
|
+
Raises
|
66
|
+
------
|
67
|
+
ValueError
|
68
|
+
If adding the sources fails for any reason.
|
51
69
|
"""
|
52
70
|
try:
|
53
71
|
sources = to_list_type(sources)
|
@@ -66,17 +84,25 @@ class MailManager(Manager):
|
|
66
84
|
request_source: Any = None,
|
67
85
|
) -> Mail:
|
68
86
|
"""
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
87
|
+
Factory method to generate a Mail object.
|
88
|
+
|
89
|
+
Parameters
|
90
|
+
----------
|
91
|
+
sender : ID.Ref
|
92
|
+
Reference (ID or object) for the sender.
|
93
|
+
recipient : ID.Ref
|
94
|
+
Reference (ID or object) for the recipient.
|
95
|
+
category : PackageCategory | str
|
96
|
+
The category of this package.
|
97
|
+
package : Any
|
98
|
+
The payload or content in the mail.
|
99
|
+
request_source : Any, optional
|
100
|
+
Additional context about the request source.
|
101
|
+
|
102
|
+
Returns
|
103
|
+
-------
|
104
|
+
Mail
|
105
|
+
A new mail object with specified sender, recipient, and package.
|
80
106
|
"""
|
81
107
|
pack = Package(
|
82
108
|
category=category, package=package, request_source=request_source
|
@@ -85,13 +111,17 @@ class MailManager(Manager):
|
|
85
111
|
|
86
112
|
def delete_source(self, source_id: IDType) -> None:
|
87
113
|
"""
|
88
|
-
|
114
|
+
Remove a source from the manager, discarding any associated mail.
|
89
115
|
|
90
|
-
|
91
|
-
|
116
|
+
Parameters
|
117
|
+
----------
|
118
|
+
source_id : IDType
|
119
|
+
The ID of the source to be removed.
|
92
120
|
|
93
|
-
Raises
|
94
|
-
|
121
|
+
Raises
|
122
|
+
------
|
123
|
+
ItemNotFoundError
|
124
|
+
If the given source ID is not present.
|
95
125
|
"""
|
96
126
|
if source_id not in self.sources:
|
97
127
|
raise ItemNotFoundError(f"Source {source_id} does not exist.")
|
@@ -99,7 +129,19 @@ class MailManager(Manager):
|
|
99
129
|
self.mails.pop(source_id)
|
100
130
|
|
101
131
|
def collect(self, sender: IDType) -> None:
|
102
|
-
"""
|
132
|
+
"""
|
133
|
+
Collect outbound mail from a single source.
|
134
|
+
|
135
|
+
Parameters
|
136
|
+
----------
|
137
|
+
sender : IDType
|
138
|
+
The ID of the sender whose outbound mail is retrieved.
|
139
|
+
|
140
|
+
Raises
|
141
|
+
------
|
142
|
+
ItemNotFoundError
|
143
|
+
If the sender is not recognized.
|
144
|
+
"""
|
103
145
|
if sender not in self.sources:
|
104
146
|
raise ItemNotFoundError(f"Sender source {sender} does not exist.")
|
105
147
|
mailbox: Exchange = (
|
@@ -120,7 +162,19 @@ class MailManager(Manager):
|
|
120
162
|
self.mails[mail.recipient][mail.sender].append(mail)
|
121
163
|
|
122
164
|
def send(self, recipient: IDType) -> None:
|
123
|
-
"""
|
165
|
+
"""
|
166
|
+
Send any pending mail to a specified recipient.
|
167
|
+
|
168
|
+
Parameters
|
169
|
+
----------
|
170
|
+
recipient : IDType
|
171
|
+
The ID of the recipient to which mail should be delivered.
|
172
|
+
|
173
|
+
Raises
|
174
|
+
------
|
175
|
+
ItemNotFoundError
|
176
|
+
If the recipient ID is not recognized.
|
177
|
+
"""
|
124
178
|
if recipient not in self.sources:
|
125
179
|
raise ItemNotFoundError(
|
126
180
|
f"Recipient source {recipient} does not exist."
|
@@ -139,25 +193,27 @@ class MailManager(Manager):
|
|
139
193
|
mailbox.include(mail, direction="in")
|
140
194
|
|
141
195
|
def collect_all(self) -> None:
|
142
|
-
"""
|
196
|
+
"""
|
197
|
+
Collect outbound mail from all known sources.
|
198
|
+
"""
|
143
199
|
for source in self.sources:
|
144
200
|
self.collect(sender=source.id)
|
145
201
|
|
146
202
|
def send_all(self) -> None:
|
147
|
-
"""
|
203
|
+
"""
|
204
|
+
Send mail to all known recipients who have pending items.
|
205
|
+
"""
|
148
206
|
for source in self.sources:
|
149
207
|
self.send(recipient=source.id)
|
150
208
|
|
151
209
|
async def execute(self, refresh_time: int = 1) -> None:
|
152
210
|
"""
|
153
|
-
|
154
|
-
|
155
|
-
This method runs in a loop, collecting and sending mail at
|
156
|
-
regular intervals.
|
211
|
+
Continuously collect and send mail in an asynchronous loop.
|
157
212
|
|
158
|
-
|
159
|
-
|
160
|
-
|
213
|
+
Parameters
|
214
|
+
----------
|
215
|
+
refresh_time : int, optional
|
216
|
+
Delay (in seconds) between each collect/send cycle.
|
161
217
|
"""
|
162
218
|
while not self.execute_stop:
|
163
219
|
self.collect_all()
|
@@ -165,4 +221,4 @@ class MailManager(Manager):
|
|
165
221
|
await asyncio.sleep(refresh_time)
|
166
222
|
|
167
223
|
|
168
|
-
# File: lion_core/communication/
|
224
|
+
# File: lion_core/communication/manager.py
|