lionagi 0.7.0__py3-none-any.whl → 0.7.2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|