lionagi 0.0.111__py3-none-any.whl → 0.0.113__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/__init__.py +7 -2
- lionagi/bridge/__init__.py +7 -0
- lionagi/bridge/langchain.py +131 -0
- lionagi/bridge/llama_index.py +157 -0
- lionagi/configs/__init__.py +7 -0
- lionagi/configs/oai_configs.py +49 -0
- lionagi/configs/openrouter_config.py +49 -0
- lionagi/core/__init__.py +15 -0
- lionagi/{session/conversation.py → core/conversations.py} +10 -17
- lionagi/core/flows.py +1 -0
- lionagi/core/instruction_sets.py +1 -0
- lionagi/{session/message.py → core/messages.py} +5 -5
- lionagi/core/sessions.py +262 -0
- lionagi/datastore/__init__.py +1 -0
- lionagi/datastore/chroma.py +1 -0
- lionagi/datastore/deeplake.py +1 -0
- lionagi/datastore/elasticsearch.py +1 -0
- lionagi/datastore/lantern.py +1 -0
- lionagi/datastore/pinecone.py +1 -0
- lionagi/datastore/postgres.py +1 -0
- lionagi/datastore/qdrant.py +1 -0
- lionagi/loader/__init__.py +12 -0
- lionagi/loader/chunker.py +157 -0
- lionagi/loader/reader.py +124 -0
- lionagi/objs/__init__.py +7 -0
- lionagi/objs/messenger.py +163 -0
- lionagi/objs/tool_registry.py +247 -0
- lionagi/schema/__init__.py +11 -0
- lionagi/schema/base_condition.py +1 -0
- lionagi/schema/base_schema.py +239 -0
- lionagi/schema/base_tool.py +9 -0
- lionagi/schema/data_logger.py +94 -0
- lionagi/services/__init__.py +14 -0
- lionagi/services/anthropic.py +1 -0
- lionagi/services/anyscale.py +0 -0
- lionagi/services/azure.py +1 -0
- lionagi/{api/oai_service.py → services/base_api_service.py} +74 -148
- lionagi/services/bedrock.py +0 -0
- lionagi/services/chatcompletion.py +48 -0
- lionagi/services/everlyai.py +0 -0
- lionagi/services/gemini.py +0 -0
- lionagi/services/gpt4all.py +0 -0
- lionagi/services/huggingface.py +0 -0
- lionagi/services/litellm.py +1 -0
- lionagi/services/localai.py +0 -0
- lionagi/services/mistralai.py +0 -0
- lionagi/services/oai.py +34 -0
- lionagi/services/ollama.py +1 -0
- lionagi/services/openllm.py +0 -0
- lionagi/services/openrouter.py +32 -0
- lionagi/services/perplexity.py +0 -0
- lionagi/services/predibase.py +0 -0
- lionagi/services/rungpt.py +0 -0
- lionagi/services/service_objs.py +282 -0
- lionagi/services/vllm.py +0 -0
- lionagi/services/xinference.py +0 -0
- lionagi/structure/__init__.py +7 -0
- lionagi/structure/relationship.py +128 -0
- lionagi/structure/structure.py +160 -0
- lionagi/tests/__init__.py +0 -0
- lionagi/tests/test_flatten_util.py +426 -0
- lionagi/tools/__init__.py +0 -0
- lionagi/tools/coder.py +1 -0
- lionagi/tools/planner.py +1 -0
- lionagi/tools/prompter.py +1 -0
- lionagi/tools/sandbox.py +1 -0
- lionagi/tools/scorer.py +1 -0
- lionagi/tools/summarizer.py +1 -0
- lionagi/tools/validator.py +1 -0
- lionagi/utils/__init__.py +46 -8
- lionagi/utils/api_util.py +63 -416
- lionagi/utils/call_util.py +347 -0
- lionagi/utils/flat_util.py +540 -0
- lionagi/utils/io_util.py +102 -0
- lionagi/utils/load_utils.py +190 -0
- lionagi/utils/sys_util.py +85 -660
- lionagi/utils/tool_util.py +82 -199
- lionagi/utils/type_util.py +81 -0
- lionagi/version.py +1 -1
- {lionagi-0.0.111.dist-info → lionagi-0.0.113.dist-info}/METADATA +44 -15
- lionagi-0.0.113.dist-info/RECORD +84 -0
- lionagi/api/__init__.py +0 -8
- lionagi/api/oai_config.py +0 -16
- lionagi/session/__init__.py +0 -7
- lionagi/session/session.py +0 -380
- lionagi/utils/doc_util.py +0 -331
- lionagi/utils/log_util.py +0 -86
- lionagi-0.0.111.dist-info/RECORD +0 -20
- {lionagi-0.0.111.dist-info → lionagi-0.0.113.dist-info}/LICENSE +0 -0
- {lionagi-0.0.111.dist-info → lionagi-0.0.113.dist-info}/WHEEL +0 -0
- {lionagi-0.0.111.dist-info → lionagi-0.0.113.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,282 @@
|
|
1
|
+
import asyncio
|
2
|
+
from abc import ABC, abstractmethod
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from typing import Any, Callable, Dict, NoReturn
|
5
|
+
|
6
|
+
|
7
|
+
# should be fine ------------------------------------------------------------------
|
8
|
+
@dataclass
|
9
|
+
class StatusTracker:
|
10
|
+
"""
|
11
|
+
Class for keeping track of various task statuses.
|
12
|
+
|
13
|
+
This class serves as a simple way to monitor different types of task
|
14
|
+
outcomes and errors within a system. It uses dataclasses for easy
|
15
|
+
creation and management of state.
|
16
|
+
|
17
|
+
Attributes:
|
18
|
+
num_tasks_started:
|
19
|
+
The number of tasks that have been initiated.
|
20
|
+
num_tasks_in_progress:
|
21
|
+
The number of tasks currently being processed.
|
22
|
+
num_tasks_succeeded:
|
23
|
+
The number of tasks that have completed successfully.
|
24
|
+
num_tasks_failed:
|
25
|
+
The number of tasks that have failed.
|
26
|
+
num_rate_limit_errors:
|
27
|
+
The number of tasks that failed due to rate limiting.
|
28
|
+
num_api_errors:
|
29
|
+
The number of tasks that failed due to API errors.
|
30
|
+
num_other_errors:
|
31
|
+
The number of tasks that failed due to other errors.
|
32
|
+
"""
|
33
|
+
num_tasks_started: int = 0
|
34
|
+
num_tasks_in_progress: int = 0
|
35
|
+
num_tasks_succeeded: int = 0
|
36
|
+
num_tasks_failed: int = 0
|
37
|
+
num_rate_limit_errors: int = 0
|
38
|
+
num_api_errors: int = 0
|
39
|
+
num_other_errors: int = 0
|
40
|
+
|
41
|
+
|
42
|
+
class AsyncQueue:
|
43
|
+
"""
|
44
|
+
A queue class that handles asynchronous operations using asyncio.
|
45
|
+
|
46
|
+
This class provides an asynchronous queue that can enqueue items, process them
|
47
|
+
asynchronously, and support graceful shutdowns. It is designed to facilitate
|
48
|
+
concurrent task processing in an orderly and controlled manner.
|
49
|
+
|
50
|
+
Attributes:
|
51
|
+
queue (asyncio.Queue):
|
52
|
+
A queue to hold items for asynchronous processing.
|
53
|
+
_stop_event (asyncio.Event):
|
54
|
+
An event to signal when the queue should stop processing.
|
55
|
+
|
56
|
+
Methods:
|
57
|
+
enqueue(item):
|
58
|
+
Add an item to the queue for processing.
|
59
|
+
dequeue():
|
60
|
+
Remove and return an item from the queue.
|
61
|
+
join():
|
62
|
+
Wait until all items in the queue have been processed.
|
63
|
+
stop():
|
64
|
+
Signal to stop processing new items in the queue.
|
65
|
+
stopped():
|
66
|
+
Check if the queue has been signaled to stop.
|
67
|
+
process_requests(func):
|
68
|
+
Process items using a provided function.
|
69
|
+
"""
|
70
|
+
|
71
|
+
def __init__(self) -> None:
|
72
|
+
"""
|
73
|
+
Initializes an AsyncQueue object with an empty asyncio Queue and a stop event.
|
74
|
+
"""
|
75
|
+
self.queue = asyncio.Queue()
|
76
|
+
self._stop_event = asyncio.Event()
|
77
|
+
|
78
|
+
async def enqueue(self, item: Any) -> None:
|
79
|
+
"""
|
80
|
+
Asynchronously add an item to the queue for processing.
|
81
|
+
|
82
|
+
Parameters:
|
83
|
+
item (Any): The item to be added to the queue.
|
84
|
+
|
85
|
+
Example:
|
86
|
+
>>> async_queue = AsyncQueue()
|
87
|
+
>>> asyncio.run(async_queue.enqueue('Task 1'))
|
88
|
+
"""
|
89
|
+
await self.queue.put(item)
|
90
|
+
|
91
|
+
async def dequeue(self) -> Any:
|
92
|
+
"""
|
93
|
+
Asynchronously remove and return an item from the queue.
|
94
|
+
|
95
|
+
If the queue is empty, this method will wait until an item is available.
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
Any: The next item from the queue.
|
99
|
+
|
100
|
+
Example:
|
101
|
+
>>> async_queue = AsyncQueue()
|
102
|
+
>>> asyncio.run(async_queue.enqueue('Task 1'))
|
103
|
+
>>> asyncio.run(async_queue.dequeue())
|
104
|
+
'Task 1'
|
105
|
+
"""
|
106
|
+
return await self.queue.get()
|
107
|
+
|
108
|
+
async def join(self) -> None:
|
109
|
+
"""
|
110
|
+
Asynchronously wait until all items in the queue have been processed.
|
111
|
+
|
112
|
+
This method blocks until every item that has been enqueued is processed,
|
113
|
+
ensuring that all tasks are completed.
|
114
|
+
|
115
|
+
Example:
|
116
|
+
>>> async_queue = AsyncQueue()
|
117
|
+
>>> asyncio.run(async_queue.enqueue('Task 1'))
|
118
|
+
>>> asyncio.run(async_queue.join()) # This will block until 'Task 1' is processed.
|
119
|
+
"""
|
120
|
+
await self.queue.join()
|
121
|
+
|
122
|
+
async def stop(self) -> None:
|
123
|
+
"""
|
124
|
+
Signal the queue to stop processing new items.
|
125
|
+
|
126
|
+
Once called, the queue will not process any new items after the current ones
|
127
|
+
are completed, allowing for a graceful shutdown.
|
128
|
+
|
129
|
+
Example:
|
130
|
+
>>> async_queue = AsyncQueue()
|
131
|
+
>>> asyncio.run(async_queue.stop()) # This signals the queue to stop processing.
|
132
|
+
"""
|
133
|
+
self._stop_event.set()
|
134
|
+
|
135
|
+
def stopped(self) -> bool:
|
136
|
+
"""
|
137
|
+
Check if the queue has been signaled to stop processing.
|
138
|
+
|
139
|
+
Returns:
|
140
|
+
bool: True if a stop has been signaled, False otherwise.
|
141
|
+
|
142
|
+
Example:
|
143
|
+
>>> async_queue = AsyncQueue()
|
144
|
+
>>> asyncio.run(async_queue.stop())
|
145
|
+
>>> async_queue.stopped()
|
146
|
+
True
|
147
|
+
"""
|
148
|
+
return self._stop_event.is_set()
|
149
|
+
|
150
|
+
async def process_requests(self, func: Callable[[Any], Any]) -> None:
|
151
|
+
"""
|
152
|
+
Asynchronously process items from the queue using the provided function.
|
153
|
+
|
154
|
+
Continuously dequeues items and applies the given function to each.
|
155
|
+
The processing stops when the queue is signaled to stop or a sentinel value (`None`) is dequeued.
|
156
|
+
|
157
|
+
Parameters:
|
158
|
+
func (Callable[[Any], Any]): A coroutine function to process items from the queue.
|
159
|
+
|
160
|
+
Example:
|
161
|
+
>>> async def sample_processing(task):
|
162
|
+
... print("Processing:", task)
|
163
|
+
>>> async_queue = AsyncQueue()
|
164
|
+
>>> asyncio.run(async_queue.enqueue('Task 1'))
|
165
|
+
>>> asyncio.run(async_queue.process_requests(sample_processing))
|
166
|
+
Processing: Task 1
|
167
|
+
"""
|
168
|
+
while not self.stopped():
|
169
|
+
item = await self.dequeue()
|
170
|
+
if item is None: # Using `None` as a sentinel value to cease processing.
|
171
|
+
await self.stop()
|
172
|
+
break
|
173
|
+
await func(item)
|
174
|
+
|
175
|
+
|
176
|
+
class BaseService(ABC):
|
177
|
+
|
178
|
+
@abstractmethod
|
179
|
+
def __init__(self) -> None:
|
180
|
+
...
|
181
|
+
|
182
|
+
@abstractmethod
|
183
|
+
async def serve(self) -> Any:
|
184
|
+
...
|
185
|
+
|
186
|
+
|
187
|
+
|
188
|
+
class RateLimiter(ABC):
|
189
|
+
"""
|
190
|
+
An abstract base class for rate limiting mechanisms.
|
191
|
+
|
192
|
+
This class defines a structure for rate limiters, which are used to control the frequency
|
193
|
+
of requests sent to or received from a network interface controller or an API.
|
194
|
+
|
195
|
+
Attributes:
|
196
|
+
max_requests_per_minute (int):
|
197
|
+
Maximum number of requests permitted per minute.
|
198
|
+
max_tokens_per_minute (int):
|
199
|
+
Maximum number of tokens that can accumulate per minute.
|
200
|
+
available_request_capacity (int):
|
201
|
+
Current number of available request slots.
|
202
|
+
available_token_capacity (int):
|
203
|
+
Current number of available tokens.
|
204
|
+
|
205
|
+
Methods:
|
206
|
+
rate_limit_replenisher:
|
207
|
+
Coroutine to replenish rate limits over time.
|
208
|
+
calculate_num_token:
|
209
|
+
Method to calculate required tokens for a request.
|
210
|
+
"""
|
211
|
+
|
212
|
+
def __init__(self, max_requests_per_minute: int, max_tokens_per_minute: int) -> None:
|
213
|
+
"""
|
214
|
+
Initializes the RateLimiter with specified maximum request and token limits.
|
215
|
+
|
216
|
+
Parameters:
|
217
|
+
max_requests_per_minute (int): Maximum requests allowed per minute.
|
218
|
+
|
219
|
+
max_tokens_per_minute (int): Maximum tokens allowed to accumulate per minute.
|
220
|
+
|
221
|
+
Example:
|
222
|
+
>>> class MyRateLimiter(RateLimiter):
|
223
|
+
... async def rate_limit_replenisher(self) -> NoReturn:
|
224
|
+
... # Implementation for rate replenishment.
|
225
|
+
... def calculate_num_token(self, payload: Dict[str, Any], api_endpoint: str) -> int:
|
226
|
+
... # Implementation for token calculation.
|
227
|
+
...
|
228
|
+
>>> limiter = MyRateLimiter(100, 200)
|
229
|
+
"""
|
230
|
+
self.max_requests_per_minute = max_requests_per_minute
|
231
|
+
self.max_tokens_per_minute = max_tokens_per_minute
|
232
|
+
self.available_request_capacity = max_requests_per_minute
|
233
|
+
self.available_token_capacity = max_tokens_per_minute
|
234
|
+
|
235
|
+
@abstractmethod
|
236
|
+
async def rate_limit_replenisher(self) -> NoReturn:
|
237
|
+
"""
|
238
|
+
Asynchronously replenishes rate limit capacities.
|
239
|
+
|
240
|
+
This coroutine should be implemented to periodically restore `available_request_capacity`
|
241
|
+
and `available_token_capacity` according to specific rules defined in subclasses.
|
242
|
+
|
243
|
+
Example:
|
244
|
+
>>> class MyRateLimiter(RateLimiter):
|
245
|
+
... async def rate_limit_replenisher(self) -> NoReturn:
|
246
|
+
... while True:
|
247
|
+
... # Replenishment logic here
|
248
|
+
...
|
249
|
+
>>> limiter = MyRateLimiter(100, 200)
|
250
|
+
"""
|
251
|
+
|
252
|
+
...
|
253
|
+
|
254
|
+
@abstractmethod
|
255
|
+
def calculate_num_token(self, payload: Dict[str, Any], api_endpoint: str) -> int:
|
256
|
+
"""
|
257
|
+
Calculates required tokens for a request.
|
258
|
+
|
259
|
+
Subclasses should implement this method to determine the number of tokens needed based
|
260
|
+
on the request payload and target endpoint.
|
261
|
+
|
262
|
+
Parameters:
|
263
|
+
payload (Dict[str, Any]): Payload of the request.
|
264
|
+
|
265
|
+
api_endpoint (str): Target API endpoint for the request.
|
266
|
+
|
267
|
+
Returns:
|
268
|
+
int: Calculated number of tokens required for the request.
|
269
|
+
|
270
|
+
Example:
|
271
|
+
>>> class MyRateLimiter(RateLimiter):
|
272
|
+
... def calculate_num_token(self, payload: Dict[str, Any], api_endpoint: str) -> int:
|
273
|
+
... return len(payload.get('data', '')) // 10
|
274
|
+
...
|
275
|
+
>>> limiter = MyRateLimiter(100, 200)
|
276
|
+
>>> limiter.calculate_num_token({'data': '12345'}, 'api/send')
|
277
|
+
0
|
278
|
+
"""
|
279
|
+
|
280
|
+
...
|
281
|
+
|
282
|
+
|
lionagi/services/vllm.py
ADDED
File without changes
|
File without changes
|
@@ -0,0 +1,128 @@
|
|
1
|
+
from pydantic import Field
|
2
|
+
from typing import Dict, Optional, Any
|
3
|
+
from ..schema.base_schema import BaseNode
|
4
|
+
|
5
|
+
|
6
|
+
class Relationship(BaseNode):
|
7
|
+
"""
|
8
|
+
Relationship class represents a relationship between two nodes in a graph.
|
9
|
+
|
10
|
+
Inherits from BaseNode and adds functionality to manage conditions and relationships
|
11
|
+
between source and target nodes.
|
12
|
+
|
13
|
+
Attributes:
|
14
|
+
source_node_id (str): The identifier of the source node.
|
15
|
+
target_node_id (str): The identifier of the target node.
|
16
|
+
condition (Dict[str, Any]): A dictionary representing conditions for the relationship.
|
17
|
+
"""
|
18
|
+
|
19
|
+
source_node_id: str
|
20
|
+
target_node_id: str
|
21
|
+
condition: dict = Field(default={})
|
22
|
+
|
23
|
+
def add_condition(self, condition: Dict[str, Any]) -> None:
|
24
|
+
"""
|
25
|
+
Adds a condition to the relationship.
|
26
|
+
|
27
|
+
Parameters:
|
28
|
+
condition (Dict[str, Any]): The condition to be added.
|
29
|
+
"""
|
30
|
+
self.condition.update(condition)
|
31
|
+
|
32
|
+
def remove_condition(self, condition_key: str) -> Any:
|
33
|
+
"""
|
34
|
+
Removes a condition from the relationship.
|
35
|
+
|
36
|
+
Parameters:
|
37
|
+
condition_key (str): The key of the condition to be removed.
|
38
|
+
|
39
|
+
Returns:
|
40
|
+
Any: The value of the removed condition.
|
41
|
+
|
42
|
+
Raises:
|
43
|
+
KeyError: If the condition key is not found.
|
44
|
+
"""
|
45
|
+
if condition_key not in self.condition.keys():
|
46
|
+
raise KeyError(f'condition {condition_key} is not found')
|
47
|
+
return self.condition.pop(condition_key)
|
48
|
+
|
49
|
+
def condition_exists(self, condition_key: str) -> bool:
|
50
|
+
"""
|
51
|
+
Checks if a condition exists in the relationship.
|
52
|
+
|
53
|
+
Parameters:
|
54
|
+
condition_key (str): The key of the condition to check.
|
55
|
+
|
56
|
+
Returns:
|
57
|
+
bool: True if the condition exists, False otherwise.
|
58
|
+
"""
|
59
|
+
if condition_key in self.condition.keys():
|
60
|
+
return True
|
61
|
+
else:
|
62
|
+
return False
|
63
|
+
|
64
|
+
def get_condition(self, condition_key: Optional[str] = None) -> Any:
|
65
|
+
"""
|
66
|
+
Retrieves a specific condition or all conditions of the relationship.
|
67
|
+
|
68
|
+
Parameters:
|
69
|
+
condition_key (Optional[str]): The key of the specific condition. Defaults to None.
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
Any: The requested condition or all conditions if no key is provided.
|
73
|
+
|
74
|
+
Raises:
|
75
|
+
ValueError: If the specified condition key does not exist.
|
76
|
+
"""
|
77
|
+
if condition_key is None:
|
78
|
+
return self.condition
|
79
|
+
if self.condition_exists(condition_key=condition_key):
|
80
|
+
return self.condition[condition_key]
|
81
|
+
else:
|
82
|
+
raise ValueError(f"Condition {condition_key} does not exist")
|
83
|
+
|
84
|
+
def _source_existed(self, obj: Dict[str, Any]) -> bool:
|
85
|
+
"""
|
86
|
+
Checks if the source node exists in a given object.
|
87
|
+
|
88
|
+
Parameters:
|
89
|
+
obj (Dict[str, Any]): The object to check.
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
bool: True if the source node exists, False otherwise.
|
93
|
+
"""
|
94
|
+
return self.source_node_id in obj.keys()
|
95
|
+
|
96
|
+
def _target_existed(self, obj: Dict[str, Any]) -> bool:
|
97
|
+
"""
|
98
|
+
Checks if the target node exists in a given object.
|
99
|
+
|
100
|
+
Parameters:
|
101
|
+
obj (Dict[str, Any]): The object to check.
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
bool: True if the target node exists, False otherwise.
|
105
|
+
"""
|
106
|
+
return self.target_node_id in obj.keys()
|
107
|
+
|
108
|
+
def _is_in(self, obj: Dict[str, Any]) -> bool:
|
109
|
+
"""
|
110
|
+
Validates the existence of both source and target nodes in a given object.
|
111
|
+
|
112
|
+
Parameters:
|
113
|
+
obj (Dict[str, Any]): The object to check.
|
114
|
+
|
115
|
+
Returns:
|
116
|
+
bool: True if both nodes exist.
|
117
|
+
|
118
|
+
Raises:
|
119
|
+
ValueError: If either the source or target node does not exist.
|
120
|
+
"""
|
121
|
+
if self._source_existed(obj) and self._target_existed(obj):
|
122
|
+
return True
|
123
|
+
|
124
|
+
elif self._source_existed(obj):
|
125
|
+
raise ValueError(f"Target node {self.source_node_id} does not exist")
|
126
|
+
else :
|
127
|
+
raise ValueError(f"Source node {self.target_node_id} does not exist")
|
128
|
+
|
@@ -0,0 +1,160 @@
|
|
1
|
+
from typing import TypeVar, Dict, Optional, Any, Type, Union, List
|
2
|
+
from pydantic import Field
|
3
|
+
from ..schema.base_schema import BaseNode
|
4
|
+
from .relationship import Relationship
|
5
|
+
|
6
|
+
T = TypeVar('T', bound='BaseNode')
|
7
|
+
R = TypeVar('R', bound='Relationship')
|
8
|
+
|
9
|
+
|
10
|
+
class Structure(BaseNode):
|
11
|
+
"""
|
12
|
+
Represents the structure of a graph consisting of nodes and relationships.
|
13
|
+
"""
|
14
|
+
nodes: Dict[str, T] = Field(default_factory=dict)
|
15
|
+
relationships: Dict[str, R] = Field(default_factory=dict)
|
16
|
+
node_relationships: Dict[str, Dict[str, Dict[str, str]]] = Field(default_factory=dict)
|
17
|
+
|
18
|
+
def add_node(self, node: T) -> None:
|
19
|
+
"""
|
20
|
+
Adds a node to the structure.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
node (T): The node instance to be added.
|
24
|
+
"""
|
25
|
+
self.nodes[node.id_] = node
|
26
|
+
self.node_relationships[node.id_] = {'in': {}, 'out': {}}
|
27
|
+
|
28
|
+
def add_relationship(self, relationship: R) -> None:
|
29
|
+
"""
|
30
|
+
Adds a relationship to the structure.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
relationship (R): The relationship instance to be added.
|
34
|
+
"""
|
35
|
+
id_, source_, target_ = (
|
36
|
+
relationship.id_, relationship.source_node_id, relationship.target_node_id
|
37
|
+
)
|
38
|
+
|
39
|
+
self.relationships.update({id_ : relationship})
|
40
|
+
self.node_relationships[source_]['out'].update({id_ : target_})
|
41
|
+
self.node_relationships[target_]['in'].update({id_ : source_})
|
42
|
+
|
43
|
+
# type can be dict or list
|
44
|
+
@staticmethod
|
45
|
+
def _typed_return(type: Type[Union[Dict, List]],
|
46
|
+
obj: Optional[Dict[str, Any]] = None
|
47
|
+
) -> Union[Dict[str, Any], List[Any]]:
|
48
|
+
"""
|
49
|
+
Returns the object in the specified type format.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
type (Type[Union[Dict, List]]): The type to return the object as (dict or list).
|
53
|
+
|
54
|
+
obj (Optional[Dict[str, Any]]): The object to be converted.
|
55
|
+
|
56
|
+
Returns:
|
57
|
+
Union[Dict[str, Any], List[Any]]: The object in the specified type format.
|
58
|
+
"""
|
59
|
+
if type is list:
|
60
|
+
return list(obj.values())
|
61
|
+
return obj
|
62
|
+
|
63
|
+
def get_relationships(self, type: Type = dict) -> Union[Dict[str, R], List[R]]:
|
64
|
+
"""
|
65
|
+
Returns the relationships in the specified type format.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
type (Type): The type to return the relationships as (dict or list).
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
Union[Dict[str, R], List[R]]: The relationships in the specified type format.
|
72
|
+
"""
|
73
|
+
return self._typed_return(self.relationships, type=type)
|
74
|
+
|
75
|
+
def get_node_relationships(self, id_: str, in_out: str, type: Type = dict
|
76
|
+
) -> Union[Dict[str, str], List[str]]:
|
77
|
+
"""
|
78
|
+
Returns the relationships of a node in the specified type format.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
id_ (str): The ID of the node.
|
82
|
+
|
83
|
+
in_out (str): 'in' for incoming relationships, 'out' for outgoing relationships.
|
84
|
+
|
85
|
+
type (Type): The type to return the relationships as (dict or list).
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
Union[Dict[str, str], List[str]]: The relationships of the node in the specified type format.
|
89
|
+
"""
|
90
|
+
node_relationships = self.node_relationships[id_][in_out]
|
91
|
+
return self._typed_return(node_relationships, type=type)
|
92
|
+
|
93
|
+
def node_exist(self, node: Union[T, str]) -> bool:
|
94
|
+
"""
|
95
|
+
Checks if a node exists in the structure.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
node (Union[T, str]): The node instance or node ID to check for existence.
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
bool: True if the node exists, False otherwise.
|
102
|
+
"""
|
103
|
+
|
104
|
+
return node.id_ in self.nodes.keys()
|
105
|
+
|
106
|
+
def relationship_exist(self, relationship: R) -> bool:
|
107
|
+
"""
|
108
|
+
Checks if a relationship exists in the structure.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
relationship (R): The relationship instance to check for existence.
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
bool: True if the relationship exists, False otherwise.
|
115
|
+
"""
|
116
|
+
return relationship.id_ in self.relationships.keys()
|
117
|
+
|
118
|
+
def remove_node_relationships(self, relationship_dict: Dict[str, str], in_out: str) -> None:
|
119
|
+
"""
|
120
|
+
Removes relationships of a node from the structure.
|
121
|
+
|
122
|
+
Args:
|
123
|
+
relationship_dict (Dict[str, str]): A dictionary of relationship IDs to node IDs.
|
124
|
+
|
125
|
+
in_out (str): 'in' to remove incoming relationships, 'out' to remove outgoing relationships.
|
126
|
+
"""
|
127
|
+
for relationship_id, node_id in relationship_dict.items():
|
128
|
+
self.node_relationships[node_id][in_out].pop(relationship_id)
|
129
|
+
self.relationships.pop(relationship_id)
|
130
|
+
|
131
|
+
def remove_node(self, node: Union[T, str]) -> None:
|
132
|
+
"""
|
133
|
+
Removes a node and its associated relationships from the structure.
|
134
|
+
|
135
|
+
Args:
|
136
|
+
node (Union[T, str]): The node instance or node ID to be removed.
|
137
|
+
"""
|
138
|
+
node_id = node if isinstance(node, str) else node.id_
|
139
|
+
out_ = self.get_node_relationships(node_id, 'out')
|
140
|
+
in_ = self.get_node_relationships(node_id, 'in')
|
141
|
+
|
142
|
+
self.remove_node_relationships(out_, 'in')
|
143
|
+
self.remove_node_relationships(in_, 'out')
|
144
|
+
self.node_relationships.pop(node_id)
|
145
|
+
|
146
|
+
def remove_relationship(self, relationship: R) -> None:
|
147
|
+
"""
|
148
|
+
Removes a relationship from the structure.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
relationship (R): The relationship instance to be removed.
|
152
|
+
"""
|
153
|
+
id_, source_, target_ = (relationship.id_,
|
154
|
+
relationship.source_node_id,
|
155
|
+
relationship.target_node_id)
|
156
|
+
|
157
|
+
self.node_relationships[source_]['out'].pop(id_)
|
158
|
+
self.node_relationships[target_]['in'].pop(id_)
|
159
|
+
self.relationships.pop(id_)
|
160
|
+
|
File without changes
|