data-manipulation 0.45__tar.gz → 0.46__tar.gz
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.
- {data_manipulation-0.45/data_manipulation.egg-info → data_manipulation-0.46}/PKG-INFO +1 -1
- {data_manipulation-0.45 → data_manipulation-0.46}/data_manipulation/_version.py +3 -3
- data_manipulation-0.46/data_manipulation/base.py +372 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/data_manipulation/beautifulsoup_.py +33 -41
- {data_manipulation-0.45 → data_manipulation-0.46}/data_manipulation/boto3_.py +35 -42
- {data_manipulation-0.45 → data_manipulation-0.46}/data_manipulation/cryptography_.py +48 -42
- data_manipulation-0.46/data_manipulation/django_.py +140 -0
- data_manipulation-0.46/data_manipulation/geopandas_.py +44 -0
- data_manipulation-0.46/data_manipulation/kerberos_.py +51 -0
- data_manipulation-0.46/data_manipulation/mysql_connector_python_.py +98 -0
- data_manipulation-0.46/data_manipulation/openldap_.py +56 -0
- data_manipulation-0.46/data_manipulation/pandas_.py +732 -0
- data_manipulation-0.46/data_manipulation/psycopg2_.py +247 -0
- data_manipulation-0.46/data_manipulation/psycopg_.py +101 -0
- data_manipulation-0.46/data_manipulation/pyspark_.py +446 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/data_manipulation/smtplib_.py +30 -16
- data_manipulation-0.46/data_manipulation/sqlalchemy_.py +122 -0
- {data_manipulation-0.45 → data_manipulation-0.46/data_manipulation.egg-info}/PKG-INFO +1 -1
- data_manipulation-0.45/data_manipulation/base.py +0 -475
- data_manipulation-0.45/data_manipulation/django_.py +0 -127
- data_manipulation-0.45/data_manipulation/geopandas_.py +0 -42
- data_manipulation-0.45/data_manipulation/kerberos_.py +0 -45
- data_manipulation-0.45/data_manipulation/mysql_connector_python_.py +0 -96
- data_manipulation-0.45/data_manipulation/openldap_.py +0 -26
- data_manipulation-0.45/data_manipulation/pandas_.py +0 -864
- data_manipulation-0.45/data_manipulation/psycopg2_.py +0 -111
- data_manipulation-0.45/data_manipulation/psycopg_.py +0 -116
- data_manipulation-0.45/data_manipulation/pyspark_.py +0 -414
- data_manipulation-0.45/data_manipulation/sqlalchemy_.py +0 -98
- {data_manipulation-0.45 → data_manipulation-0.46}/LICENSE +0 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/MANIFEST.in +0 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/README.md +0 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/data_manipulation/__init__.py +0 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/data_manipulation/flask_.py +0 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/data_manipulation/prometheus_.py +0 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/data_manipulation.egg-info/SOURCES.txt +0 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/data_manipulation.egg-info/dependency_links.txt +0 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/data_manipulation.egg-info/requires.txt +0 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/data_manipulation.egg-info/top_level.txt +0 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/setup.cfg +0 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/setup.py +0 -0
- {data_manipulation-0.45 → data_manipulation-0.46}/versioneer.py +0 -0
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2024-
|
|
11
|
+
"date": "2024-11-24T20:58:50+0800",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.
|
|
14
|
+
"full-revisionid": "8d4f86d6f48df8cc57789006ba9a8b8ee47ab4c2",
|
|
15
|
+
"version": "0.46"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from loguru import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# DATA STRUCTURE
|
|
11
|
+
def clean_string(
|
|
12
|
+
string: str, remove_parenthesis: bool = False, remove_brackets: bool = False
|
|
13
|
+
) -> str:
|
|
14
|
+
"""Cleans and standardizes input string.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
string (str): String to clean.
|
|
18
|
+
remove_parenthesis (bool, optional): Whether to remove content within parentheses. Defaults to False.
|
|
19
|
+
remove_brackets (bool, optional): Whether to remove content within square brackets. Defaults to False.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
str: Uppercase cleaned string with standardized spacing.
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
>>> clean_string(" sHawn tesT ")
|
|
26
|
+
'SHAWN TEST'
|
|
27
|
+
>>> clean_string("shawn ( te st )")
|
|
28
|
+
'SHAWN ( TE ST )'
|
|
29
|
+
>>> clean_string("shawn ( te st )", remove_parenthesis=True)
|
|
30
|
+
'SHAWN'
|
|
31
|
+
>>> clean_string("shawn [ te st ]", remove_brackets=True)
|
|
32
|
+
'SHAWN'
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
if remove_parenthesis:
|
|
36
|
+
string = re.sub(r"\(.*\)", "", string)
|
|
37
|
+
if remove_brackets:
|
|
38
|
+
string = re.sub(r"\[.*\]", "", string)
|
|
39
|
+
|
|
40
|
+
string = string.strip().upper()
|
|
41
|
+
string = " ".join(string.split())
|
|
42
|
+
return string
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_string_case_combination(str_: str) -> list:
|
|
46
|
+
"""Generates all possible case combinations of a string.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
str_ (str): Input string to generate combinations for.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
list: List of all possible case combinations.
|
|
53
|
+
|
|
54
|
+
Examples:
|
|
55
|
+
>>> get_string_case_combination("abc")
|
|
56
|
+
['ABC', 'ABc', 'AbC', 'Abc', 'aBC', 'aBc', 'abC', 'abc']
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
return list(map("".join, itertools.product(*zip(str_.upper(), str_.lower()))))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_none_variation() -> list:
|
|
63
|
+
"""Returns a list of common variations of None/null values.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
list: List containing None and various string representations of null values.
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
>>> get_none_variation()
|
|
70
|
+
[None, 'NONE', 'NONe', 'NOnE', 'NOne', 'NoNE', 'NoNe', 'NonE', 'None', 'nONE', 'nONe', 'nOnE', 'nOne', 'noNE', 'noNe', 'nonE', 'none', 'NULL', 'NULl', 'NUlL', 'NUll', 'NuLL', 'NuLl', 'NulL', 'Null', 'nULL', 'nULl', 'nUlL', 'nUll', 'nuLL', 'nuLl', 'nulL', 'null', 'NA', 'Na', 'nA', 'na', 'N.A', 'N.a', 'N.A', 'N.a', 'n.A', 'n.a', 'n.A', 'n.a', 'N.A.', 'N.A.', 'N.a.', 'N.a.', 'N.A.', 'N.A.', 'N.a.', 'N.a.', 'n.A.', 'n.A.', 'n.a.', 'n.a.', 'n.A.', 'n.A.', 'n.a.', 'n.a.', 'NAN', 'NAn', 'NaN', 'Nan', 'nAN', 'nAn', 'naN', 'nan', 'NIL', 'NIl', 'NiL', 'Nil', 'nIL', 'nIl', 'niL', 'nil']
|
|
71
|
+
"""
|
|
72
|
+
variations = (
|
|
73
|
+
[None]
|
|
74
|
+
+ get_string_case_combination("none")
|
|
75
|
+
+ get_string_case_combination("null")
|
|
76
|
+
+ get_string_case_combination("na")
|
|
77
|
+
+ get_string_case_combination("n.a")
|
|
78
|
+
+ get_string_case_combination("n.a.")
|
|
79
|
+
+ get_string_case_combination("nan")
|
|
80
|
+
+ get_string_case_combination("nil")
|
|
81
|
+
)
|
|
82
|
+
return variations
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_country_name_variation() -> dict:
|
|
86
|
+
"""Returns a dictionary mapping country names to their codes.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
dict: Dictionary with country names as keys and country codes as values.
|
|
90
|
+
"""
|
|
91
|
+
variations = {
|
|
92
|
+
"AFRICA": "BW",
|
|
93
|
+
"BOSNIA": "BA",
|
|
94
|
+
"CZECH REPUBLIC": "CZ",
|
|
95
|
+
"MACEDONIA": "MK",
|
|
96
|
+
"REPUBLIC OF CHINA": "CN",
|
|
97
|
+
"REPUBLIC OF KOREA": "KR",
|
|
98
|
+
"RUSSIAN FEDERATION": "RU",
|
|
99
|
+
"SLAVONIC": "SK",
|
|
100
|
+
"SLOVAK REPUBLIC": "SK",
|
|
101
|
+
"TURKEY": "TR",
|
|
102
|
+
"TURKIYE": "TR",
|
|
103
|
+
"UNITED STATES": "US",
|
|
104
|
+
}
|
|
105
|
+
return variations
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_country_code_variation() -> dict:
|
|
109
|
+
"""Returns a dictionary mapping country codes to their name variations.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
dict: Dictionary with country codes as keys and lists of country name variations as values.
|
|
113
|
+
"""
|
|
114
|
+
variations = {
|
|
115
|
+
"BA": ["BOSNIA"],
|
|
116
|
+
"BW": ["AFRICA"],
|
|
117
|
+
"CN": ["REPUBLIC OF CHINA"],
|
|
118
|
+
"CZ": ["CZECH REPUBLIC"],
|
|
119
|
+
"KR": ["REPUBLIC OF KOREA"],
|
|
120
|
+
"MK": ["MACEDONIA"],
|
|
121
|
+
"RU": ["RUSSIAN FEDERATION"],
|
|
122
|
+
"SK": ["SLAVONIC", "SLOVAK REPUBLIC"],
|
|
123
|
+
"TR": ["TURKEY", "TURKIYE"],
|
|
124
|
+
"US": ["UNITED STATES"],
|
|
125
|
+
}
|
|
126
|
+
return variations
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def list_tuple_without_none(list_tuple: list | tuple) -> list | tuple:
|
|
130
|
+
"""Removes None variations from a list or tuple.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
list_tuple (list | tuple): Input list or tuple to clean.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
list | tuple: List or tuple with None variations removed.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
TypeError: If input is not a list or tuple.
|
|
140
|
+
|
|
141
|
+
Examples:
|
|
142
|
+
>>> list_tuple_without_none(["a", "none"])
|
|
143
|
+
['a']
|
|
144
|
+
>>> list_tuple_without_none(("a", "none"))
|
|
145
|
+
('a',)
|
|
146
|
+
"""
|
|
147
|
+
none_variations = get_none_variation()
|
|
148
|
+
if isinstance(list_tuple, list):
|
|
149
|
+
lt = [item for item in list_tuple if item and item not in none_variations]
|
|
150
|
+
elif isinstance(list_tuple, tuple):
|
|
151
|
+
lt = tuple(item for item in list_tuple if item and item not in none_variations)
|
|
152
|
+
else:
|
|
153
|
+
raise TypeError("Wrong datatype(s)")
|
|
154
|
+
return lt
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def string_boolean_to_int(boolean_str_rep: str) -> int:
|
|
158
|
+
"""Converts string boolean representations to integers.
|
|
159
|
+
|
|
160
|
+
Deprecated: Will be removed in Python 3.12+
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
boolean_str_rep (str): String representation of boolean value.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
int: 1 for true values, 0 for false values.
|
|
167
|
+
|
|
168
|
+
Examples:
|
|
169
|
+
>>> string_boolean_to_int("true")
|
|
170
|
+
1
|
|
171
|
+
>>> string_boolean_to_int("True")
|
|
172
|
+
1
|
|
173
|
+
"""
|
|
174
|
+
from distutils.util import strtobool
|
|
175
|
+
|
|
176
|
+
int_ = strtobool(string_str_to_str(boolean_str_rep))
|
|
177
|
+
return int_
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def string_dlt_to_dlt(dlt_str_rep: str) -> dict | list | tuple:
|
|
181
|
+
"""Converts string representation of data structures to actual Python objects.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
dlt_str_rep (str): String representation of dictionary/list/tuple.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
dict | list | tuple: Converted Python data structure.
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
>>> string_dlt_to_dlt("[1, 2, 3]")
|
|
191
|
+
[1, 2, 3]
|
|
192
|
+
>>> string_dlt_to_dlt("{'a': 1, 'b': 2}")
|
|
193
|
+
{'a': 1, 'b': 2}
|
|
194
|
+
>>> string_dlt_to_dlt("('1', '2', '3')")
|
|
195
|
+
('1', '2', '3')
|
|
196
|
+
"""
|
|
197
|
+
from ast import literal_eval
|
|
198
|
+
|
|
199
|
+
dlt = literal_eval(dlt_str_rep)
|
|
200
|
+
return dlt
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def string_str_to_str(string_str_rep: str) -> str:
|
|
204
|
+
"""Converts string representation to a clean string by removing quotes.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
string_str_rep (str): String representation to clean.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
str: Cleaned string with outer quotes removed.
|
|
211
|
+
|
|
212
|
+
Examples:
|
|
213
|
+
>>> string_str_to_str("'test'")
|
|
214
|
+
'test'
|
|
215
|
+
>>> string_str_to_str('"test"')
|
|
216
|
+
'test'
|
|
217
|
+
"""
|
|
218
|
+
str_ = string_str_rep.strip("'\"")
|
|
219
|
+
return str_
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def delete_list_indices(list_: list, indices: list) -> None:
|
|
223
|
+
"""Deletes multiple indices from a list in-place.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
list_ (list): Original list to modify.
|
|
227
|
+
indices (list): List of indices to delete.
|
|
228
|
+
|
|
229
|
+
Examples:
|
|
230
|
+
>>> values = [0, 1, 2, 3, 4]
|
|
231
|
+
>>> delete_list_indices(values, [1, 3])
|
|
232
|
+
>>> values
|
|
233
|
+
[0, 2, 4]
|
|
234
|
+
"""
|
|
235
|
+
for index in sorted(indices, reverse=True):
|
|
236
|
+
del list_[index]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# FILESYSTEM
|
|
240
|
+
def get_path_files(path: str, keywords: list) -> list:
|
|
241
|
+
"""Returns sorted list of files from given path that contain specified keywords.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
path (str): Directory path to search.
|
|
245
|
+
keywords (list): List of keywords to match in filenames.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
list: Sorted list of matching filenames.
|
|
249
|
+
|
|
250
|
+
Examples:
|
|
251
|
+
>>> get_path_files("test_base_folder", ["py"])
|
|
252
|
+
['test1.py', 'test2.py', 'test3.py', 'test4.py', 'test5.py']
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
list_ = []
|
|
256
|
+
for file in sorted(os.listdir(path)):
|
|
257
|
+
if any(word in file for word in keywords):
|
|
258
|
+
list_.append(file)
|
|
259
|
+
return list_
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def remove_path_file(path: str, keyword: str, n: int = 2) -> None:
|
|
263
|
+
"""Removes all but the n newest files matching the keyword from the specified path.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
path (str): Directory path.
|
|
267
|
+
keyword (str): Keyword to match in filenames.
|
|
268
|
+
n (int, optional): Number of newest files to keep. Defaults to 2.
|
|
269
|
+
|
|
270
|
+
Examples:
|
|
271
|
+
>>> remove_path_file("test_base_folder", ".py")
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
to_delete = get_path_files(path=path, keywords=[keyword])[:-n]
|
|
275
|
+
for file in to_delete:
|
|
276
|
+
os.remove(f"{path}/{file}")
|
|
277
|
+
logger.info(f"{path}/{file} deleted ...")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def list_to_file(filepath: str, list_: list, newline: bool = True) -> None:
|
|
281
|
+
"""Writes list contents to a file.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
filepath (str): Path to output file.
|
|
285
|
+
list_ (list): List of values to write.
|
|
286
|
+
newline (bool, optional): Whether to add newline after each item. Defaults to True.
|
|
287
|
+
|
|
288
|
+
Examples:
|
|
289
|
+
>>> list_to_file("test.txt", [1, 2, 3])
|
|
290
|
+
"""
|
|
291
|
+
f = open(filepath, "w")
|
|
292
|
+
for line in list_:
|
|
293
|
+
f.write(str(line))
|
|
294
|
+
if newline:
|
|
295
|
+
f.write("\n")
|
|
296
|
+
f.close()
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
# URLLIB
|
|
300
|
+
def create_encode_url(url: str, query_params: dict = {}) -> str:
|
|
301
|
+
"""Creates an encoded URL with query parameters.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
url (str): Base URL.
|
|
305
|
+
query_params (dict, optional): Dictionary of URL query parameters. Defaults to {}.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
str: Encoded URL with query parameters.
|
|
309
|
+
"""
|
|
310
|
+
from urllib.parse import urlencode
|
|
311
|
+
|
|
312
|
+
return f"{url}{urlencode(query_params)}"
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# SYSTEM
|
|
316
|
+
def parse_ps_aux(ps_aux_commands: str) -> List[list]:
|
|
317
|
+
"""Parses Linux ps aux command output into a list of records.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
ps_aux_commands (str): Linux ps aux command string.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
List[list]: List of lists, where each inner list represents a process record.
|
|
324
|
+
|
|
325
|
+
Examples:
|
|
326
|
+
>>> # parse_ps_aux("ps aux | egrep -i '%cpu|anaconda3' | head")
|
|
327
|
+
"""
|
|
328
|
+
output = subprocess.run(
|
|
329
|
+
ps_aux_commands,
|
|
330
|
+
capture_output=True,
|
|
331
|
+
text=True,
|
|
332
|
+
shell=True,
|
|
333
|
+
executable="/bin/bash",
|
|
334
|
+
)
|
|
335
|
+
lines = output.stdout.split("\n")
|
|
336
|
+
n_columns = len(lines[0].split()) - 1
|
|
337
|
+
rows = [line.split(None, n_columns) for line in lines if line]
|
|
338
|
+
return rows
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
if __name__ == "__main__":
|
|
342
|
+
import doctest
|
|
343
|
+
|
|
344
|
+
subprocess.run(
|
|
345
|
+
"mkdir -p test_base_folder",
|
|
346
|
+
capture_output=True,
|
|
347
|
+
shell=True,
|
|
348
|
+
text=True,
|
|
349
|
+
executable="/bin/bash",
|
|
350
|
+
)
|
|
351
|
+
subprocess.run(
|
|
352
|
+
"touch test_base_folder/test{1..5}.py",
|
|
353
|
+
capture_output=True,
|
|
354
|
+
shell=True,
|
|
355
|
+
text=True,
|
|
356
|
+
executable="/bin/bash",
|
|
357
|
+
)
|
|
358
|
+
doctest.testmod()
|
|
359
|
+
subprocess.run(
|
|
360
|
+
"rm -rf test_base_folder",
|
|
361
|
+
capture_output=True,
|
|
362
|
+
shell=True,
|
|
363
|
+
text=True,
|
|
364
|
+
executable="/bin/bash",
|
|
365
|
+
)
|
|
366
|
+
subprocess.run(
|
|
367
|
+
"rm test.txt",
|
|
368
|
+
capture_output=True,
|
|
369
|
+
shell=True,
|
|
370
|
+
text=True,
|
|
371
|
+
executable="/bin/bash",
|
|
372
|
+
)
|
|
@@ -5,24 +5,21 @@ from loguru import logger
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def preprocess(html: str) -> Optional[str]:
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Examples
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-------
|
|
24
|
-
Optional[str]
|
|
25
|
-
cleaned html
|
|
8
|
+
"""Removes whitespaces and newline characters from HTML string.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
html (str): HTML string to be cleaned.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
Optional[str]: Cleaned HTML string with normalized whitespace.
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
>>> a = "<html> <p> Something </p> </html> "
|
|
18
|
+
>>> preprocess(a)
|
|
19
|
+
'<html><p>Something</p></html>'
|
|
20
|
+
|
|
21
|
+
Note:
|
|
22
|
+
Reference: https://stackoverflow.com/questions/23241641
|
|
26
23
|
"""
|
|
27
24
|
|
|
28
25
|
# remove leading and trailing whitespaces
|
|
@@ -37,29 +34,24 @@ def preprocess(html: str) -> Optional[str]:
|
|
|
37
34
|
return html
|
|
38
35
|
|
|
39
36
|
|
|
40
|
-
def build_soup(url: str, features: str = "lxml", to_preprocess:
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Returns
|
|
60
|
-
-------
|
|
61
|
-
Optional[BeautifulSoup]
|
|
62
|
-
BeautifulSoup parsed by lxml
|
|
37
|
+
def build_soup(url: str, features: str = "lxml", to_preprocess: bool = True):
|
|
38
|
+
"""Creates a BeautifulSoup object from a given URL.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
url (str): URL to fetch and parse.
|
|
42
|
+
features (str, optional): Parser to use. Defaults to "lxml".
|
|
43
|
+
to_preprocess (bool, optional): Whether to preprocess the HTML. Defaults to True.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Optional[BeautifulSoup]: Parsed BeautifulSoup object, or None if request fails.
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
>>> a = build_soup("https://google.com")
|
|
50
|
+
>>> type(a)
|
|
51
|
+
<class 'bs4.BeautifulSoup'>
|
|
52
|
+
|
|
53
|
+
Note:
|
|
54
|
+
Requires requests and beautifulsoup4 packages.
|
|
63
55
|
"""
|
|
64
56
|
import requests
|
|
65
57
|
from bs4 import BeautifulSoup
|
|
@@ -12,34 +12,22 @@ def send_aws_ses_email(
|
|
|
12
12
|
ses_client,
|
|
13
13
|
attachment: str = None,
|
|
14
14
|
):
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
aws ses client
|
|
32
|
-
attachment : str, optional
|
|
33
|
-
attachment path, by default None
|
|
34
|
-
|
|
35
|
-
Returns
|
|
36
|
-
-------
|
|
37
|
-
dict
|
|
38
|
-
aws ses client email response or none
|
|
39
|
-
|
|
40
|
-
Reference
|
|
41
|
-
---------
|
|
42
|
-
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ses/client/send_raw_email.html
|
|
15
|
+
"""Sends an email using AWS SES service.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
sender (str): Sender's email address.
|
|
19
|
+
recipient (list): List of recipient email addresses.
|
|
20
|
+
subject (str): Email subject line.
|
|
21
|
+
body_text (str): Email body content.
|
|
22
|
+
body_type (str): MIME type of email body (e.g., 'plain', 'html').
|
|
23
|
+
ses_client: AWS SES client instance.
|
|
24
|
+
attachment (str, optional): Path to file to attach. Defaults to None.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
dict: AWS SES response dictionary if successful, None if failed.
|
|
28
|
+
|
|
29
|
+
Note:
|
|
30
|
+
Reference: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ses/client/send_raw_email.html
|
|
43
31
|
"""
|
|
44
32
|
from email.mime.application import MIMEApplication
|
|
45
33
|
from email.mime.multipart import MIMEMultipart
|
|
@@ -87,20 +75,25 @@ def list_s3_bucket_files(
|
|
|
87
75
|
bucket: str,
|
|
88
76
|
to_dateframe: bool = False,
|
|
89
77
|
):
|
|
90
|
-
"""
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
78
|
+
"""Lists all files in an S3 bucket.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
bucket (str): Name of the S3 bucket.
|
|
82
|
+
to_dateframe (bool, optional): Whether to return results as pandas DataFrame. Defaults to False.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Union[list, pd.DataFrame]: List of file keys or DataFrame containing file keys.
|
|
86
|
+
If to_dateframe is True, returns DataFrame with 'key' column.
|
|
87
|
+
If to_dateframe is False, returns list of file keys.
|
|
88
|
+
|
|
89
|
+
Examples:
|
|
90
|
+
>>> files = list_s3_bucket_files('my-bucket')
|
|
91
|
+
>>> type(files)
|
|
92
|
+
<class 'list'>
|
|
93
|
+
|
|
94
|
+
>>> df = list_s3_bucket_files('my-bucket', to_dateframe=True)
|
|
95
|
+
>>> type(df)
|
|
96
|
+
<class 'pandas.core.frame.DataFrame'>
|
|
104
97
|
"""
|
|
105
98
|
|
|
106
99
|
import boto3
|
|
@@ -5,20 +5,22 @@ from loguru import logger
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def generate_fernet_key(output_directory: str, output_filename: str) -> bytes:
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
"""Generates and saves a Fernet encryption key.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
output_directory (str): Directory path where the key file will be saved.
|
|
12
|
+
output_filename (str): Name of the key file to be created.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
bytes: Generated Fernet key in bytes format.
|
|
16
|
+
|
|
17
|
+
Raises:
|
|
18
|
+
Exception: If key generation or file writing fails.
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
>>> key = generate_fernet_key('/path/to/keys', 'encryption.key')
|
|
22
|
+
>>> isinstance(key, bytes)
|
|
23
|
+
True
|
|
22
24
|
"""
|
|
23
25
|
|
|
24
26
|
key = None
|
|
@@ -36,20 +38,22 @@ def generate_fernet_key(output_directory: str, output_filename: str) -> bytes:
|
|
|
36
38
|
|
|
37
39
|
|
|
38
40
|
def encrypt_fernet_file(keypath: str, filepath: str) -> str:
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
"""Encrypts a file using Fernet symmetric encryption.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
keypath (str): Path to the Fernet key file.
|
|
45
|
+
filepath (str): Path to the file to be encrypted.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
str: Encrypted data.
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
TypeError: If keypath or filepath are not strings.
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
>>> encrypted = encrypt_fernet_file('key.txt', 'data.txt')
|
|
55
|
+
>>> isinstance(encrypted, str)
|
|
56
|
+
True
|
|
53
57
|
"""
|
|
54
58
|
if isinstance(keypath, str) and isinstance(filepath, str):
|
|
55
59
|
fernet = Fernet(open(keypath, "rb").read())
|
|
@@ -61,20 +65,22 @@ def encrypt_fernet_file(keypath: str, filepath: str) -> str:
|
|
|
61
65
|
|
|
62
66
|
|
|
63
67
|
def decrypt_fernet_data(keypath: str, filepath: str) -> str:
|
|
64
|
-
"""
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
68
|
+
"""Decrypts a file using Fernet symmetric encryption.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
keypath (str): Path to the Fernet key file.
|
|
72
|
+
filepath (str): Path to the encrypted file.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
str: Decrypted data.
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
TypeError: If keypath or filepath are not strings.
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
>>> decrypted = decrypt_fernet_data('key.txt', 'encrypted_data.txt')
|
|
82
|
+
>>> isinstance(decrypted, str)
|
|
83
|
+
True
|
|
78
84
|
"""
|
|
79
85
|
if isinstance(keypath, str) and isinstance(filepath, str):
|
|
80
86
|
fernet = Fernet(open(keypath, "rb").read())
|