lionagi 0.0.115__py3-none-any.whl → 0.0.204__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- lionagi/__init__.py +1 -2
- lionagi/_services/__init__.py +5 -0
- lionagi/_services/anthropic.py +79 -0
- lionagi/_services/base_service.py +414 -0
- lionagi/_services/oai.py +98 -0
- lionagi/_services/openrouter.py +44 -0
- lionagi/_services/services.py +91 -0
- lionagi/_services/transformers.py +46 -0
- lionagi/bridge/langchain.py +26 -16
- lionagi/bridge/llama_index.py +50 -20
- lionagi/configs/oai_configs.py +2 -14
- lionagi/configs/openrouter_configs.py +2 -2
- lionagi/core/__init__.py +7 -8
- lionagi/core/branch/branch.py +589 -0
- lionagi/core/branch/branch_manager.py +139 -0
- lionagi/core/branch/conversation.py +484 -0
- lionagi/core/core_util.py +59 -0
- lionagi/core/flow/flow.py +19 -0
- lionagi/core/flow/flow_util.py +62 -0
- lionagi/core/instruction_set/__init__.py +0 -5
- lionagi/core/instruction_set/instruction_set.py +343 -0
- lionagi/core/messages/messages.py +176 -0
- lionagi/core/sessions/__init__.py +0 -5
- lionagi/core/sessions/session.py +428 -0
- lionagi/loaders/chunker.py +51 -47
- lionagi/loaders/load_util.py +2 -2
- lionagi/loaders/reader.py +45 -39
- lionagi/models/imodel.py +53 -0
- lionagi/schema/async_queue.py +158 -0
- lionagi/schema/base_node.py +318 -147
- lionagi/schema/base_tool.py +31 -1
- lionagi/schema/data_logger.py +74 -38
- lionagi/schema/data_node.py +57 -6
- lionagi/structures/graph.py +132 -10
- lionagi/structures/relationship.py +58 -20
- lionagi/structures/structure.py +36 -25
- lionagi/tests/test_utils/test_api_util.py +219 -0
- lionagi/tests/test_utils/test_call_util.py +785 -0
- lionagi/tests/test_utils/test_encrypt_util.py +323 -0
- lionagi/tests/test_utils/test_io_util.py +238 -0
- lionagi/tests/test_utils/test_nested_util.py +338 -0
- lionagi/tests/test_utils/test_sys_util.py +358 -0
- lionagi/tools/tool_manager.py +186 -0
- lionagi/tools/tool_util.py +266 -3
- lionagi/utils/__init__.py +21 -61
- lionagi/utils/api_util.py +359 -71
- lionagi/utils/call_util.py +839 -264
- lionagi/utils/encrypt_util.py +283 -16
- lionagi/utils/io_util.py +178 -93
- lionagi/utils/nested_util.py +672 -0
- lionagi/utils/pd_util.py +57 -0
- lionagi/utils/sys_util.py +284 -156
- lionagi/utils/url_util.py +55 -0
- lionagi/version.py +1 -1
- {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/METADATA +21 -17
- lionagi-0.0.204.dist-info/RECORD +106 -0
- lionagi/core/conversations/__init__.py +0 -5
- lionagi/core/conversations/conversation.py +0 -107
- lionagi/core/flows/__init__.py +0 -8
- lionagi/core/flows/flow.py +0 -8
- lionagi/core/flows/flow_util.py +0 -62
- lionagi/core/instruction_set/instruction_sets.py +0 -7
- lionagi/core/sessions/sessions.py +0 -185
- lionagi/endpoints/__init__.py +0 -5
- lionagi/endpoints/audio.py +0 -17
- lionagi/endpoints/chatcompletion.py +0 -54
- lionagi/messages/__init__.py +0 -11
- lionagi/messages/instruction.py +0 -15
- lionagi/messages/message.py +0 -110
- lionagi/messages/response.py +0 -33
- lionagi/messages/system.py +0 -12
- lionagi/objs/__init__.py +0 -11
- lionagi/objs/abc_objs.py +0 -39
- lionagi/objs/async_queue.py +0 -135
- lionagi/objs/messenger.py +0 -85
- lionagi/objs/tool_manager.py +0 -253
- lionagi/services/__init__.py +0 -11
- lionagi/services/base_api_service.py +0 -230
- lionagi/services/oai.py +0 -34
- lionagi/services/openrouter.py +0 -31
- lionagi/tests/test_api_util.py +0 -46
- lionagi/tests/test_call_util.py +0 -115
- lionagi/tests/test_convert_util.py +0 -202
- lionagi/tests/test_encrypt_util.py +0 -33
- lionagi/tests/test_flat_util.py +0 -426
- lionagi/tests/test_sys_util.py +0 -0
- lionagi/utils/convert_util.py +0 -229
- lionagi/utils/flat_util.py +0 -599
- lionagi-0.0.115.dist-info/RECORD +0 -110
- /lionagi/{services → _services}/anyscale.py +0 -0
- /lionagi/{services → _services}/azure.py +0 -0
- /lionagi/{services → _services}/bedrock.py +0 -0
- /lionagi/{services → _services}/everlyai.py +0 -0
- /lionagi/{services → _services}/gemini.py +0 -0
- /lionagi/{services → _services}/gpt4all.py +0 -0
- /lionagi/{services → _services}/huggingface.py +0 -0
- /lionagi/{services → _services}/litellm.py +0 -0
- /lionagi/{services → _services}/localai.py +0 -0
- /lionagi/{services → _services}/mistralai.py +0 -0
- /lionagi/{services → _services}/ollama.py +0 -0
- /lionagi/{services → _services}/openllm.py +0 -0
- /lionagi/{services → _services}/perplexity.py +0 -0
- /lionagi/{services → _services}/predibase.py +0 -0
- /lionagi/{services → _services}/rungpt.py +0 -0
- /lionagi/{services → _services}/vllm.py +0 -0
- /lionagi/{services → _services}/xinference.py +0 -0
- /lionagi/{endpoints/assistants.py → agents/__init__.py} +0 -0
- /lionagi/{tools → agents}/planner.py +0 -0
- /lionagi/{tools → agents}/prompter.py +0 -0
- /lionagi/{tools → agents}/scorer.py +0 -0
- /lionagi/{tools → agents}/summarizer.py +0 -0
- /lionagi/{tools → agents}/validator.py +0 -0
- /lionagi/{endpoints/embeddings.py → core/branch/__init__.py} +0 -0
- /lionagi/{services/anthropic.py → core/branch/cluster.py} +0 -0
- /lionagi/{endpoints/finetune.py → core/flow/__init__.py} +0 -0
- /lionagi/{endpoints/image.py → core/messages/__init__.py} +0 -0
- /lionagi/{endpoints/moderation.py → models/__init__.py} +0 -0
- /lionagi/{endpoints/vision.py → models/base_model.py} +0 -0
- /lionagi/{objs → schema}/status_tracker.py +0 -0
- /lionagi/tests/{test_io_util.py → test_utils/__init__.py} +0 -0
- {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/LICENSE +0 -0
- {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/WHEEL +0 -0
- {lionagi-0.0.115.dist-info → lionagi-0.0.204.dist-info}/top_level.txt +0 -0
lionagi/utils/encrypt_util.py
CHANGED
@@ -1,16 +1,283 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
1
|
+
import base64
|
2
|
+
import binascii
|
3
|
+
import hashlib
|
4
|
+
import os
|
5
|
+
import zipfile
|
6
|
+
from base64 import urlsafe_b64encode
|
7
|
+
from cryptography.fernet import Fernet, InvalidToken
|
8
|
+
from cryptography.hazmat.backends import default_backend
|
9
|
+
from cryptography.hazmat.primitives import hashes
|
10
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
11
|
+
from typing import Optional
|
12
|
+
|
13
|
+
|
14
|
+
class EncrytionUtil:
|
15
|
+
"""
|
16
|
+
A utility class for handling encryption, decryption, file operations, and password strength checking.
|
17
|
+
"""
|
18
|
+
|
19
|
+
@staticmethod
|
20
|
+
def password_strength_checker(password: str) -> bool:
|
21
|
+
"""
|
22
|
+
Check the strength of a password.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
password (str): The password to check.
|
26
|
+
|
27
|
+
Returns:
|
28
|
+
bool: True if the password is strong, False otherwise.
|
29
|
+
|
30
|
+
Examples:
|
31
|
+
>>> password_strength_checker("Weakpass")
|
32
|
+
False
|
33
|
+
>>> password_strength_checker("Strongpass1")
|
34
|
+
True
|
35
|
+
"""
|
36
|
+
if len(password) < 8 or not any(char.isdigit() for char in password) or not any(char.isupper() for char in password):
|
37
|
+
return False
|
38
|
+
return True
|
39
|
+
|
40
|
+
@staticmethod
|
41
|
+
def generate_encryption_key(password: Optional[str] = None, salt: Optional[bytes] = None) -> str:
|
42
|
+
"""
|
43
|
+
Generate an encryption key from a password and salt.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
password (Optional[str]): The password to derive the key from. If None, a random key is generated.
|
47
|
+
salt (Optional[bytes]): A salt for the key derivation. If None, a random salt is used.
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
str: The generated encryption key as a URL-safe base64-encoded string.
|
51
|
+
|
52
|
+
Raises:
|
53
|
+
ValueError: If the password is too weak.
|
54
|
+
|
55
|
+
Examples:
|
56
|
+
>>> key = generate_encryption_key("Strongpass1")
|
57
|
+
>>> isinstance(key, str)
|
58
|
+
True
|
59
|
+
"""
|
60
|
+
if password:
|
61
|
+
if not EncrytionUtil.password_strength_checker(password):
|
62
|
+
raise ValueError("Password is too weak.")
|
63
|
+
if not salt:
|
64
|
+
salt = os.urandom(16)
|
65
|
+
kdf = PBKDF2HMAC(
|
66
|
+
algorithm=hashes.SHA256(),
|
67
|
+
length=32,
|
68
|
+
salt=salt,
|
69
|
+
iterations=100000,
|
70
|
+
backend=default_backend()
|
71
|
+
)
|
72
|
+
key = kdf.derive(password.encode())
|
73
|
+
return urlsafe_b64encode(key).decode()
|
74
|
+
else:
|
75
|
+
return Fernet.generate_key().decode()
|
76
|
+
|
77
|
+
@staticmethod
|
78
|
+
def encrypt(data: str, key: str) -> str:
|
79
|
+
"""
|
80
|
+
Encrypt the provided data using the provided key.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
data (str): The data to encrypt.
|
84
|
+
key (str): The encryption key.
|
85
|
+
|
86
|
+
Returns:
|
87
|
+
str: The encrypted data.
|
88
|
+
|
89
|
+
Example:
|
90
|
+
>>> encrypt("This is some test data.", "esvCExTuhddRb8kSobU_gnNRYObyTRTI2LJF4nYai5I=")
|
91
|
+
'gAAAAABloX9m_S_G1VUtbfGUDB7ooHJNt8sPiSCwnp1ehe6dExvMEqtw_ua2ELk_uUbLoB6a1XkbKLOkM4UBvwmk6sMoY-yNvE-Lv-w-VNnfzf89zH82rgI='
|
92
|
+
"""
|
93
|
+
fernet = Fernet(key.encode())
|
94
|
+
return fernet.encrypt(data.encode()).decode()
|
95
|
+
|
96
|
+
@staticmethod
|
97
|
+
def decrypt(data: str, key: str) -> str:
|
98
|
+
"""
|
99
|
+
Decrypt the provided data using the provided key.
|
100
|
+
|
101
|
+
Args:
|
102
|
+
data (str): The data to decrypt.
|
103
|
+
key (str): The encryption key.
|
104
|
+
|
105
|
+
Returns:
|
106
|
+
str: The decrypted data.
|
107
|
+
|
108
|
+
Example:
|
109
|
+
>>> decrypt('gAAAAABloX9m_S_G1VUtbfGUDB7ooHJNt8sPiSCwnp1ehe6dExvMEqtw_ua2ELk_uUbLoB6a1XkbKLOkM4UBvwmk6sMoY-yNvE-Lv-w-VNnfzf89zH82rgI=', "esvCExTuhddRb8kSobU_gnNRYObyTRTI2LJF4nYai5I=")
|
110
|
+
'This is some test data.'
|
111
|
+
"""
|
112
|
+
fernet = Fernet(key.encode())
|
113
|
+
return fernet.decrypt(data.encode()).decode()
|
114
|
+
|
115
|
+
@staticmethod
|
116
|
+
def encrypt_file(file_path: str, key: str, output_path: str = None):
|
117
|
+
"""
|
118
|
+
Encrypt a file.
|
119
|
+
|
120
|
+
Args:
|
121
|
+
file_path (str): The path of the file to encrypt.
|
122
|
+
key (str): The encryption key.
|
123
|
+
output_path (str, optional): The path to save the encrypted file. If not provided, '.enc' is appended to the input file path.
|
124
|
+
|
125
|
+
Example:
|
126
|
+
>>> encrypt_file("test_file.txt", "esvCExTuhddRb8kSobU_gnNRYObyTRTI2LJF4nYai5I=")
|
127
|
+
"""
|
128
|
+
if not output_path:
|
129
|
+
output_path = file_path + '.enc'
|
130
|
+
with open(file_path, 'rb') as file_to_encrypt:
|
131
|
+
encrypted_data = EncrytionUtil.encrypt(file_to_encrypt.read().decode(), key)
|
132
|
+
with open(output_path, 'wb') as encrypted_file:
|
133
|
+
encrypted_file.write(encrypted_data.encode())
|
134
|
+
|
135
|
+
@staticmethod
|
136
|
+
def decrypt_file(encrypted_file_path: str, key: str, output_path: str = None):
|
137
|
+
"""
|
138
|
+
Decrypt a file.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
encrypted_file_path (str): The path of the file to decrypt.
|
142
|
+
key (str): The encryption key.
|
143
|
+
output_path (str, optional): The path to save the decrypted file. If not provided, '.enc' is removed from the input file path.
|
144
|
+
|
145
|
+
Example:
|
146
|
+
>>> decrypt_file("test_file.txt.enc", "esvCExTuhddRb8kSobU_gnNRYObyTRTI2LJF4nYai5I=")
|
147
|
+
"""
|
148
|
+
if not output_path:
|
149
|
+
output_path = encrypted_file_path.replace('.enc', '')
|
150
|
+
with open(encrypted_file_path, 'rb') as encrypted_file:
|
151
|
+
decrypted_data = EncrytionUtil.decrypt(encrypted_file.read().decode(), key)
|
152
|
+
with open(output_path, 'wb') as decrypted_file:
|
153
|
+
decrypted_file.write(decrypted_data.encode())
|
154
|
+
|
155
|
+
@staticmethod
|
156
|
+
def is_encrypted(file_path: str, key: str) -> bool:
|
157
|
+
"""
|
158
|
+
Check if a file is encrypted.
|
159
|
+
|
160
|
+
Args:
|
161
|
+
file_path (str): The path of the file to check.
|
162
|
+
key (str): The encryption key.
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
bool: True if the file is encrypted, False otherwise.
|
166
|
+
|
167
|
+
Example:
|
168
|
+
>>> is_encrypted("test_file.txt.enc", "esvCExTuhddRb8kSobU_gnNRYObyTRTI2LJF4nYai5I=")
|
169
|
+
True
|
170
|
+
"""
|
171
|
+
try:
|
172
|
+
EncrytionUtil.decrypt_file(file_path, key, "temp_decrypted_file")
|
173
|
+
os.remove("temp_decrypted_file")
|
174
|
+
return True
|
175
|
+
except InvalidToken:
|
176
|
+
return False
|
177
|
+
|
178
|
+
@staticmethod
|
179
|
+
def decompress_file(file_path: str, output_path: str = None):
|
180
|
+
"""
|
181
|
+
Decompress a file.
|
182
|
+
|
183
|
+
Args:
|
184
|
+
file_path (str): The path of the file to decompress.
|
185
|
+
output_path (str, optional): The path to save the decompressed file. If not provided, the file is decompressed in the same directory.
|
186
|
+
|
187
|
+
Example:
|
188
|
+
>>> decompress_file("test_file.txt.zip")
|
189
|
+
"""
|
190
|
+
if not output_path:
|
191
|
+
output_path = os.path.dirname(file_path)
|
192
|
+
with zipfile.ZipFile(file_path, 'r') as zipf:
|
193
|
+
zipf.extractall(output_path)
|
194
|
+
|
195
|
+
@staticmethod
|
196
|
+
def compress_file(file_path: str, output_path: str = None):
|
197
|
+
"""
|
198
|
+
Compress a file.
|
199
|
+
|
200
|
+
Args:
|
201
|
+
file_path (str): The path of the file to compress.
|
202
|
+
output_path (str, optional): The path to save the compressed file. If not provided, '.zip' is appended to the input file path.
|
203
|
+
|
204
|
+
Example:
|
205
|
+
>>> compress_file("test_file.txt")
|
206
|
+
"""
|
207
|
+
if not output_path:
|
208
|
+
output_path = file_path + '.zip'
|
209
|
+
with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
210
|
+
zipf.write(file_path)
|
211
|
+
|
212
|
+
@staticmethod
|
213
|
+
def binary_to_hex(data: bytes) -> str:
|
214
|
+
"""
|
215
|
+
Convert binary data to a hexadecimal string representation.
|
216
|
+
|
217
|
+
Args:
|
218
|
+
data: A bytes object containing binary data.
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
A string containing the hexadecimal representation of the binary data.
|
222
|
+
|
223
|
+
Examples:
|
224
|
+
>>> binary_to_hex(b'\x00\x0F')
|
225
|
+
'000f'
|
226
|
+
>>> binary_to_hex(b'hello')
|
227
|
+
'68656c6c6f'
|
228
|
+
"""
|
229
|
+
return binascii.hexlify(data).decode()
|
230
|
+
|
231
|
+
@staticmethod
|
232
|
+
def create_hash(data: str, algorithm: str = 'sha256') -> str:
|
233
|
+
"""
|
234
|
+
Create a hash of the given data using the specified algorithm.
|
235
|
+
|
236
|
+
Args:
|
237
|
+
data: The string to hash.
|
238
|
+
algorithm: The hashing algorithm to use (default is 'sha256').
|
239
|
+
|
240
|
+
Returns:
|
241
|
+
The hexadecimal digest of the hash.
|
242
|
+
|
243
|
+
Examples:
|
244
|
+
>>> create_hash('hello')
|
245
|
+
'2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
|
246
|
+
"""
|
247
|
+
hasher = hashlib.new(algorithm)
|
248
|
+
hasher.update(data.encode())
|
249
|
+
return hasher.hexdigest()
|
250
|
+
|
251
|
+
@staticmethod
|
252
|
+
def decode_base64(data: str) -> str:
|
253
|
+
"""
|
254
|
+
Decode a base64 encoded string.
|
255
|
+
|
256
|
+
Args:
|
257
|
+
data: A base64 encoded string.
|
258
|
+
|
259
|
+
Returns:
|
260
|
+
A decoded string.
|
261
|
+
|
262
|
+
Examples:
|
263
|
+
>>> decode_base64('SGVsbG8sIFdvcmxkIQ==')
|
264
|
+
'Hello, World!'
|
265
|
+
"""
|
266
|
+
return base64.b64decode(data).decode()
|
267
|
+
|
268
|
+
@staticmethod
|
269
|
+
def encode_base64(data: str) -> str:
|
270
|
+
"""
|
271
|
+
Encode a string using base64 encoding.
|
272
|
+
|
273
|
+
Args:
|
274
|
+
data: A string to be encoded.
|
275
|
+
|
276
|
+
Returns:
|
277
|
+
A base64 encoded string.
|
278
|
+
|
279
|
+
Examples:
|
280
|
+
>>> encode_base64("Hello, World!")
|
281
|
+
'SGVsbG8sIFdvcmxkIQ=='
|
282
|
+
"""
|
283
|
+
return base64.b64encode(data.encode()).decode()
|
lionagi/utils/io_util.py
CHANGED
@@ -1,102 +1,187 @@
|
|
1
|
-
# used flat_util
|
2
1
|
import csv
|
3
2
|
import json
|
4
3
|
import os
|
5
4
|
import tempfile
|
5
|
+
from collections.abc import Iterable
|
6
6
|
from typing import Any, Dict, List
|
7
|
-
from .flat_util import to_list
|
8
7
|
|
9
|
-
|
10
|
-
def to_temp(input: Any,
|
11
|
-
flatten_dict: bool = False,
|
12
|
-
flat: bool = False,
|
13
|
-
dropna: bool = False):
|
8
|
+
class IOUtil:
|
14
9
|
"""
|
15
|
-
|
16
|
-
|
17
|
-
This function serializes data to a temporary JSON file, useful for transient storage or testing.
|
18
|
-
It includes options to flatten the input if it contains dictionaries or lists.
|
19
|
-
|
20
|
-
Parameters:
|
21
|
-
input (Any): The data to be converted and written to a file.
|
22
|
-
|
23
|
-
flatten_dict (bool, optional): Flatten dictionaries in the input. Defaults to False.
|
24
|
-
|
25
|
-
flat (bool, optional): Flatten lists in the input. Defaults to False.
|
26
|
-
|
27
|
-
dropna (bool, optional): Exclude 'None' values during flattening. Defaults to False.
|
28
|
-
|
29
|
-
Raises:
|
30
|
-
TypeError: If the input is not JSON serializable.
|
31
|
-
|
32
|
-
Example:
|
33
|
-
>>> temp_file = to_temp({'a': 1, 'b': [2, 3]}, flatten_dict=True)
|
34
|
-
>>> temp_file.name # Doctest: +ELLIPSIS
|
35
|
-
'/var/folders/.../tmp...'
|
10
|
+
Utility class for reading and writing data from/to files in various formats.
|
36
11
|
"""
|
37
|
-
input = to_list(input, flatten_dict, flat, dropna)
|
38
12
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
13
|
+
@staticmethod
|
14
|
+
def read_csv(filepath: str) -> List[Dict[str, Any]]:
|
15
|
+
"""
|
16
|
+
Reads a CSV file and returns its contents as a list of dictionaries.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
filepath (str): The path to the CSV file to be read.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
List[Dict[str, Any]]: A list of dictionaries, each representing a row in the CSV file.
|
23
|
+
"""
|
24
|
+
with open(filepath, 'r', newline='') as csv_file:
|
25
|
+
reader = csv.DictReader(csv_file)
|
26
|
+
return list(reader)
|
27
|
+
|
28
|
+
@staticmethod
|
29
|
+
def read_jsonl(filepath: str) -> List[Any]:
|
30
|
+
"""
|
31
|
+
Reads a JSON Lines file and returns its contents as a list.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
filepath (str): The path to the JSON Lines file to be read.
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
List[Any]: A list where each element is a JSON object from a line in the file.
|
38
|
+
"""
|
39
|
+
with open(filepath, 'r') as f:
|
40
|
+
return [json.loads(line) for line in f]
|
41
|
+
|
42
|
+
@staticmethod
|
43
|
+
def write_json(data: List[Dict[str, Any]], filepath: str) -> None:
|
44
|
+
"""
|
45
|
+
Writes a list of dictionaries to a JSON file.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
data (List[Dict[str, Any]]): The data to be written to the JSON file.
|
49
|
+
filepath (str): The path where the JSON file will be saved.
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
None: This function does not return anything.
|
53
|
+
"""
|
54
|
+
with open(filepath, 'w') as json_file:
|
55
|
+
json.dump(data, json_file, indent=4)
|
56
|
+
|
57
|
+
@staticmethod
|
58
|
+
def read_json(filepath: str) -> Any:
|
59
|
+
"""
|
60
|
+
Reads a JSON file and returns its content.
|
61
|
+
|
62
|
+
Args:
|
63
|
+
filepath (str): The path to the JSON file to be read.
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
Any: The content of the JSON file.
|
67
|
+
"""
|
68
|
+
with open(filepath, 'r') as json_file:
|
69
|
+
return json.load(json_file)
|
70
|
+
|
71
|
+
@staticmethod
|
72
|
+
def merge_csv_files(filepaths: List[str], output_filepath: str) -> None:
|
73
|
+
"""
|
74
|
+
Merges multiple CSV files into a single CSV file.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
filepaths (List[str]): A list of file paths to the CSV files to be merged.
|
78
|
+
output_filepath (str): The path where the merged CSV file will be saved.
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
None: This function does not return anything.
|
82
|
+
"""
|
83
|
+
merged_data = []
|
84
|
+
fieldnames = set()
|
85
|
+
for filepath in filepaths:
|
86
|
+
with open(filepath, 'r', newline='') as csv_file:
|
87
|
+
reader = csv.DictReader(csv_file)
|
88
|
+
if reader.fieldnames is not None:
|
89
|
+
fieldnames.update(reader.fieldnames)
|
90
|
+
for row in reader:
|
91
|
+
merged_data.append(row)
|
92
|
+
fieldnames = list(fieldnames)
|
93
|
+
with open(output_filepath, 'w', newline='') as csv_file:
|
94
|
+
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
|
95
|
+
writer.writeheader()
|
96
|
+
writer.writerows(merged_data)
|
97
|
+
|
98
|
+
@staticmethod
|
99
|
+
def to_csv(input: List[Dict[str, Any]],
|
100
|
+
filepath: str,
|
101
|
+
file_exist_ok: bool = False) -> None:
|
102
|
+
"""
|
103
|
+
Writes a list of dictionaries to a CSV file.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
input: A list of dictionaries where each dictionary represents a row in the CSV.
|
107
|
+
filepath: The path to the CSV file to write to.
|
108
|
+
file_exist_ok: If True, creates the directory for the file if it does not exist.
|
109
|
+
|
110
|
+
Raises:
|
111
|
+
FileNotFoundError: If the directory does not exist and file_exist_ok is False.
|
112
|
+
|
113
|
+
Examples:
|
114
|
+
>>> data = [{'name': 'Alice', 'age': 30}, {'name': 'Bob', 'age': 25}]
|
115
|
+
>>> to_csv(data, 'people.csv', file_exist_ok=True)
|
116
|
+
"""
|
117
|
+
if not input:
|
118
|
+
return
|
119
|
+
if not os.path.exists(os.path.dirname(filepath)) and os.path.dirname(filepath) != '':
|
120
|
+
if file_exist_ok:
|
121
|
+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
122
|
+
else:
|
123
|
+
raise FileNotFoundError(f"The directory {os.path.dirname(filepath)} does not exist.")
|
124
|
+
|
125
|
+
with open(filepath, 'w', newline='') as csv_file:
|
126
|
+
writer = csv.DictWriter(csv_file, fieldnames=input[0].keys())
|
127
|
+
writer.writeheader()
|
128
|
+
for row in input:
|
129
|
+
writer.writerow(row)
|
130
|
+
|
131
|
+
@staticmethod
|
132
|
+
def append_to_jsonl(data: Any, filepath: str) -> None:
|
133
|
+
"""
|
134
|
+
Appends a data entry to a JSON Lines file.
|
135
|
+
|
136
|
+
Args:
|
137
|
+
data: The data to append, which can be any JSON serializable object.
|
138
|
+
filepath: The path to the JSON Lines file to append to.
|
139
|
+
|
140
|
+
Examples:
|
141
|
+
>>> append_to_jsonl({'name': 'Charlie', 'age': 35}, 'people.jsonl')
|
142
|
+
"""
|
143
|
+
json_string = json.dumps(data)
|
144
|
+
with open(filepath, "a") as f:
|
145
|
+
f.write(json_string + "\n")
|
146
|
+
|
147
|
+
@staticmethod
|
148
|
+
def to_temp(input: Any) -> tempfile.NamedTemporaryFile:
|
149
|
+
"""
|
150
|
+
Writes the given input to a temporary file in JSON format.
|
151
|
+
|
152
|
+
Args:
|
153
|
+
input: The input data to write to the file. Can be a string or an iterable.
|
154
|
+
|
155
|
+
Returns:
|
156
|
+
A NamedTemporaryFile object representing the temporary file.
|
157
|
+
|
158
|
+
Raises:
|
159
|
+
TypeError: If the input data is not JSON serializable.
|
160
|
+
|
161
|
+
Examples:
|
162
|
+
>>> temp_file = to_temp("test string")
|
163
|
+
>>> with open(temp_file.name, 'r') as file:
|
164
|
+
... content = json.load(file)
|
165
|
+
>>> content
|
166
|
+
["test string"]
|
167
|
+
|
168
|
+
>>> temp_file = to_temp(["test", "string"])
|
169
|
+
>>> with open(temp_file.name, 'r') as file:
|
170
|
+
... content = json.load(file)
|
171
|
+
>>> content
|
172
|
+
["test", "string"]
|
173
|
+
"""
|
174
|
+
if isinstance(input, str):
|
175
|
+
input = [input]
|
176
|
+
elif isinstance(input, Iterable):
|
177
|
+
input = [item for item in input if item is not None]
|
178
|
+
|
179
|
+
temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False)
|
180
|
+
try:
|
181
|
+
json.dump(input, temp_file)
|
182
|
+
except TypeError as e:
|
183
|
+
temp_file.close() # Ensuring file closure before raising error
|
184
|
+
raise TypeError(f"Data provided is not JSON serializable: {e}")
|
185
|
+
temp_file.close()
|
186
|
+
return temp_file
|
187
|
+
|