lionagi 0.0.111__py3-none-any.whl → 0.0.113__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. lionagi/__init__.py +7 -2
  2. lionagi/bridge/__init__.py +7 -0
  3. lionagi/bridge/langchain.py +131 -0
  4. lionagi/bridge/llama_index.py +157 -0
  5. lionagi/configs/__init__.py +7 -0
  6. lionagi/configs/oai_configs.py +49 -0
  7. lionagi/configs/openrouter_config.py +49 -0
  8. lionagi/core/__init__.py +15 -0
  9. lionagi/{session/conversation.py → core/conversations.py} +10 -17
  10. lionagi/core/flows.py +1 -0
  11. lionagi/core/instruction_sets.py +1 -0
  12. lionagi/{session/message.py → core/messages.py} +5 -5
  13. lionagi/core/sessions.py +262 -0
  14. lionagi/datastore/__init__.py +1 -0
  15. lionagi/datastore/chroma.py +1 -0
  16. lionagi/datastore/deeplake.py +1 -0
  17. lionagi/datastore/elasticsearch.py +1 -0
  18. lionagi/datastore/lantern.py +1 -0
  19. lionagi/datastore/pinecone.py +1 -0
  20. lionagi/datastore/postgres.py +1 -0
  21. lionagi/datastore/qdrant.py +1 -0
  22. lionagi/loader/__init__.py +12 -0
  23. lionagi/loader/chunker.py +157 -0
  24. lionagi/loader/reader.py +124 -0
  25. lionagi/objs/__init__.py +7 -0
  26. lionagi/objs/messenger.py +163 -0
  27. lionagi/objs/tool_registry.py +247 -0
  28. lionagi/schema/__init__.py +11 -0
  29. lionagi/schema/base_condition.py +1 -0
  30. lionagi/schema/base_schema.py +239 -0
  31. lionagi/schema/base_tool.py +9 -0
  32. lionagi/schema/data_logger.py +94 -0
  33. lionagi/services/__init__.py +14 -0
  34. lionagi/services/anthropic.py +1 -0
  35. lionagi/services/anyscale.py +0 -0
  36. lionagi/services/azure.py +1 -0
  37. lionagi/{api/oai_service.py → services/base_api_service.py} +74 -148
  38. lionagi/services/bedrock.py +0 -0
  39. lionagi/services/chatcompletion.py +48 -0
  40. lionagi/services/everlyai.py +0 -0
  41. lionagi/services/gemini.py +0 -0
  42. lionagi/services/gpt4all.py +0 -0
  43. lionagi/services/huggingface.py +0 -0
  44. lionagi/services/litellm.py +1 -0
  45. lionagi/services/localai.py +0 -0
  46. lionagi/services/mistralai.py +0 -0
  47. lionagi/services/oai.py +34 -0
  48. lionagi/services/ollama.py +1 -0
  49. lionagi/services/openllm.py +0 -0
  50. lionagi/services/openrouter.py +32 -0
  51. lionagi/services/perplexity.py +0 -0
  52. lionagi/services/predibase.py +0 -0
  53. lionagi/services/rungpt.py +0 -0
  54. lionagi/services/service_objs.py +282 -0
  55. lionagi/services/vllm.py +0 -0
  56. lionagi/services/xinference.py +0 -0
  57. lionagi/structure/__init__.py +7 -0
  58. lionagi/structure/relationship.py +128 -0
  59. lionagi/structure/structure.py +160 -0
  60. lionagi/tests/__init__.py +0 -0
  61. lionagi/tests/test_flatten_util.py +426 -0
  62. lionagi/tools/__init__.py +0 -0
  63. lionagi/tools/coder.py +1 -0
  64. lionagi/tools/planner.py +1 -0
  65. lionagi/tools/prompter.py +1 -0
  66. lionagi/tools/sandbox.py +1 -0
  67. lionagi/tools/scorer.py +1 -0
  68. lionagi/tools/summarizer.py +1 -0
  69. lionagi/tools/validator.py +1 -0
  70. lionagi/utils/__init__.py +46 -8
  71. lionagi/utils/api_util.py +63 -416
  72. lionagi/utils/call_util.py +347 -0
  73. lionagi/utils/flat_util.py +540 -0
  74. lionagi/utils/io_util.py +102 -0
  75. lionagi/utils/load_utils.py +190 -0
  76. lionagi/utils/sys_util.py +85 -660
  77. lionagi/utils/tool_util.py +82 -199
  78. lionagi/utils/type_util.py +81 -0
  79. lionagi/version.py +1 -1
  80. {lionagi-0.0.111.dist-info → lionagi-0.0.113.dist-info}/METADATA +44 -15
  81. lionagi-0.0.113.dist-info/RECORD +84 -0
  82. lionagi/api/__init__.py +0 -8
  83. lionagi/api/oai_config.py +0 -16
  84. lionagi/session/__init__.py +0 -7
  85. lionagi/session/session.py +0 -380
  86. lionagi/utils/doc_util.py +0 -331
  87. lionagi/utils/log_util.py +0 -86
  88. lionagi-0.0.111.dist-info/RECORD +0 -20
  89. {lionagi-0.0.111.dist-info → lionagi-0.0.113.dist-info}/LICENSE +0 -0
  90. {lionagi-0.0.111.dist-info → lionagi-0.0.113.dist-info}/WHEEL +0 -0
  91. {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
+
File without changes
File without changes
@@ -0,0 +1,7 @@
1
+ from .relationship import Relationship
2
+ from .structure import Structure
3
+
4
+ __all__=[
5
+ "Relationship",
6
+ "Structure"
7
+ ]
@@ -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