py2ls 0.1.10.25__py3-none-any.whl → 0.1.10.27__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.
- py2ls/batman.py +57 -28
- py2ls/data/email/email_html_template.html +88 -0
- py2ls/ips.py +407 -20
- py2ls/plot.py +90 -0
- {py2ls-0.1.10.25.dist-info → py2ls-0.1.10.27.dist-info}/METADATA +7 -2
- {py2ls-0.1.10.25.dist-info → py2ls-0.1.10.27.dist-info}/RECORD +7 -6
- {py2ls-0.1.10.25.dist-info → py2ls-0.1.10.27.dist-info}/WHEEL +0 -0
py2ls/batman.py
CHANGED
@@ -68,7 +68,7 @@ def extract_kv(
|
|
68
68
|
else:
|
69
69
|
if "win" in sys.platform:
|
70
70
|
dir_data = (
|
71
|
-
"Z:\\Jianfeng\\Apps
|
71
|
+
"Z:\\Jianfeng\\Apps\\settings\\confidential_data\\gmail_login.json"
|
72
72
|
)
|
73
73
|
elif "lin" in sys.platform:
|
74
74
|
dir_data = "/Users/macjianfeng/Dropbox/github/python/py2ls/confidential_data/gmail_login.json"
|
@@ -86,23 +86,30 @@ def email_to(**kwargs):
|
|
86
86
|
def email(**kwargs):
|
87
87
|
"""
|
88
88
|
usage:
|
89
|
-
email_to(who="example@gmail.com",
|
90
|
-
|
91
|
-
|
92
|
-
|
89
|
+
email_to(who=["example@gmail.com"],
|
90
|
+
subject="test",
|
91
|
+
what="this is the body",
|
92
|
+
attachments=["/Users/test.pdf"],
|
93
|
+
cc=["cc1@example.com"],
|
94
|
+
bcc=["bcc@example.com"],
|
95
|
+
html_template="template.html")
|
93
96
|
Send an email with optional attachments.
|
94
97
|
|
95
|
-
:param who: Recipient email address
|
98
|
+
:param who: Recipient email address (string or list)
|
96
99
|
:param subject: Email subject
|
97
|
-
:param what: Email
|
100
|
+
:param what: Email body (can be HTML or plain text)
|
98
101
|
:param attachments: List of file paths to attach
|
102
|
+
:param cc: List of CC email addresses
|
103
|
+
:param bcc: List of BCC email addresses, "blind carbon copy"接受者之间互相看不到
|
104
|
+
:param html_template: Path to an HTML file for the email body
|
99
105
|
"""
|
100
106
|
who, what, subject, signature = None, None, None, None
|
101
107
|
attachments = False
|
102
108
|
pause_sec = False # default 10 seconds pause
|
103
|
-
|
109
|
+
bcc, cc = [], []
|
104
110
|
signature_styles = extract_kv(idx=1)[1] # signature list
|
105
111
|
signature = signature_styles[0] # default signature,None
|
112
|
+
read_receipt = False
|
106
113
|
verbose = True
|
107
114
|
if not kwargs:
|
108
115
|
print(
|
@@ -110,42 +117,59 @@ def email(**kwargs):
|
|
110
117
|
)
|
111
118
|
return None
|
112
119
|
# params config
|
120
|
+
for k, v in kwargs.items():
|
121
|
+
if any([i in k.lower() for i in ["who", "whe", "to", "@"]]): # 'who' or "where"
|
122
|
+
if isinstance(v, list):
|
123
|
+
who = v
|
124
|
+
else:
|
125
|
+
who = [v]
|
113
126
|
for k, v in kwargs.items():
|
114
127
|
if any(
|
115
|
-
[
|
116
|
-
|
117
|
-
"whe" in k.lower(),
|
118
|
-
"to" in k.lower(),
|
119
|
-
"@" in k.lower(),
|
120
|
-
all(["@" in k.lower(), "." in k.lower()]),
|
121
|
-
]
|
122
|
-
): # 'who' or "where"
|
123
|
-
who = v
|
124
|
-
if any(["sub" in k.lower(), "hea" in k.lower()]): # 'subject', 'header'
|
128
|
+
[i in k.lower() for i in ["sub", "hea", "top"]]
|
129
|
+
): # 'subject', 'header','topic'
|
125
130
|
subject = v
|
126
131
|
if any(
|
127
|
-
[
|
132
|
+
[i in k.lower() for i in ["wha", "con", "bod"]]
|
128
133
|
): # 'what','content','body'
|
129
134
|
what = v
|
130
|
-
if any([
|
135
|
+
if any([i in k.lower() for i in ["att", "fil"]]):
|
131
136
|
attachments = v # optional
|
132
|
-
if any(
|
133
|
-
["paus" in k.lower(), "tim" in k.lower(), "delay" in k.lower()]
|
134
|
-
): # 'attachments', 'file'
|
137
|
+
if any([i in k.lower() for i in ["paus", "tim", "delay"]]):
|
135
138
|
pause_sec = v # optional
|
136
139
|
if any(["sig" in k.lower()]): # 'attachments', 'file'
|
137
140
|
signature = v # optional
|
138
141
|
if not isinstance(v, str):
|
139
|
-
print(signature_styles)
|
140
142
|
signature = signature_styles[v]
|
141
|
-
if any([
|
143
|
+
if any([i in k.lower() for i in ["verb", "show"]]): # 'verbose', 'show'
|
142
144
|
verbose = v # verbose
|
143
|
-
|
145
|
+
if any(["cc" in k.lower() and "bcc" not in k.lower(), "ward" in k.lower()]):
|
146
|
+
if isinstance(v, list):
|
147
|
+
cc = v
|
148
|
+
else:
|
149
|
+
cc = [v]
|
150
|
+
if any([i in k.lower() for i in ["bcc", "blind", "secret"]]):
|
151
|
+
if isinstance(v, list):
|
152
|
+
bcc = v
|
153
|
+
else:
|
154
|
+
bcc = [v]
|
155
|
+
if any(["temp" in k.lower(), "html" in k.lower()]):
|
156
|
+
# Load HTML template if specified
|
157
|
+
if isinstance(v, str) and v.endswith("html"):
|
158
|
+
with open(kwargs["html_template"], "r") as f:
|
159
|
+
what = f.read()
|
144
160
|
# Create email message
|
145
161
|
message = MIMEMultipart()
|
146
162
|
message["From"] = extract_kv()[0]
|
147
|
-
|
163
|
+
print(who)
|
164
|
+
message["To"] = ", ".join(who)
|
165
|
+
|
166
|
+
if len(who) == 1:
|
167
|
+
who = ", ".join(who)
|
168
|
+
|
148
169
|
message["Subject"] = subject
|
170
|
+
# Add CC addresses if provided
|
171
|
+
if cc:
|
172
|
+
message["Cc"] = ", ".join(cc) # Join CC addresses as a comma-separated string
|
149
173
|
|
150
174
|
# the eamil's body
|
151
175
|
if what is None:
|
@@ -209,7 +233,12 @@ def email(**kwargs):
|
|
209
233
|
with smtplib.SMTP("smtp.gmail.com", 587) as server:
|
210
234
|
server.starttls()
|
211
235
|
server.login(extract_kv()[0], extract_kv()[1])
|
212
|
-
|
236
|
+
if any(bcc):
|
237
|
+
recipient_list = who + cc + bcc # Include all recipients
|
238
|
+
server.sendmail(extract_kv()[0], recipient_list, message.as_string())
|
239
|
+
else:
|
240
|
+
server.sendmail(extract_kv()[0], who, message.as_string())
|
241
|
+
|
213
242
|
if verbose:
|
214
243
|
if attachments:
|
215
244
|
print(
|
@@ -0,0 +1,88 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>Email Template</title>
|
7
|
+
<style>
|
8
|
+
body {
|
9
|
+
font-family: Arial, sans-serif;
|
10
|
+
margin: 0;
|
11
|
+
padding: 0;
|
12
|
+
background-color: #f4f4f4;
|
13
|
+
}
|
14
|
+
.email-container {
|
15
|
+
max-width: 600px;
|
16
|
+
margin: 0 auto;
|
17
|
+
background-color: #ffffff;
|
18
|
+
padding: 20px;
|
19
|
+
border-radius: 8px;
|
20
|
+
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1);
|
21
|
+
}
|
22
|
+
.email-header {
|
23
|
+
background-color: #4CAF50;
|
24
|
+
color: #ffffff;
|
25
|
+
padding: 10px;
|
26
|
+
text-align: center;
|
27
|
+
border-radius: 8px 8px 0 0;
|
28
|
+
}
|
29
|
+
.email-body {
|
30
|
+
padding: 20px;
|
31
|
+
font-size: 16px;
|
32
|
+
color: #333333;
|
33
|
+
line-height: 1.5;
|
34
|
+
}
|
35
|
+
.email-footer {
|
36
|
+
background-color: #f4f4f4;
|
37
|
+
color: #888888;
|
38
|
+
padding: 10px;
|
39
|
+
text-align: center;
|
40
|
+
font-size: 12px;
|
41
|
+
border-radius: 0 0 8px 8px;
|
42
|
+
}
|
43
|
+
a {
|
44
|
+
color: #4CAF50;
|
45
|
+
text-decoration: none;
|
46
|
+
}
|
47
|
+
a:hover {
|
48
|
+
text-decoration: underline;
|
49
|
+
}
|
50
|
+
.button {
|
51
|
+
background-color: #4CAF50;
|
52
|
+
color: #ffffff;
|
53
|
+
padding: 10px 20px;
|
54
|
+
text-align: center;
|
55
|
+
display: inline-block;
|
56
|
+
margin: 10px 0;
|
57
|
+
border-radius: 4px;
|
58
|
+
text-decoration: none;
|
59
|
+
font-size: 16px;
|
60
|
+
}
|
61
|
+
.button:hover {
|
62
|
+
background-color: #45a049;
|
63
|
+
}
|
64
|
+
</style>
|
65
|
+
</head>
|
66
|
+
<body>
|
67
|
+
<div class="email-container">
|
68
|
+
<div class="email-header">
|
69
|
+
<h1>Welcome to Our Newsletter</h1>
|
70
|
+
</div>
|
71
|
+
<div class="email-body">
|
72
|
+
<p>Dear Subscriber,</p>
|
73
|
+
<p>Thank you for signing up for our newsletter! We are excited to keep you updated with the latest news, promotions, and events. Here’s what you can expect in our upcoming editions:</p>
|
74
|
+
<ul>
|
75
|
+
<li>Exclusive promotions and discounts</li>
|
76
|
+
<li>Updates on our latest products</li>
|
77
|
+
<li>Invitations to special events</li>
|
78
|
+
</ul>
|
79
|
+
<p>Stay tuned for more exciting updates. In the meantime, feel free to visit our website for more information!</p>
|
80
|
+
<a href="https://example.com" class="button">Visit Our Website</a>
|
81
|
+
</div>
|
82
|
+
<div class="email-footer">
|
83
|
+
<p>© 2024 Company Name. All rights reserved.</p>
|
84
|
+
<p>If you no longer wish to receive these emails, please <a href="#">unsubscribe</a>.</p>
|
85
|
+
</div>
|
86
|
+
</div>
|
87
|
+
</body>
|
88
|
+
</html>
|
py2ls/ips.py
CHANGED
@@ -46,6 +46,8 @@ from collections import Counter
|
|
46
46
|
from fuzzywuzzy import fuzz, process
|
47
47
|
from langdetect import detect
|
48
48
|
from duckduckgo_search import DDGS
|
49
|
+
from typing import List, Optional, Union
|
50
|
+
from bs4 import BeautifulSoup
|
49
51
|
|
50
52
|
from . import netfinder
|
51
53
|
|
@@ -58,16 +60,14 @@ except NameError:
|
|
58
60
|
pass
|
59
61
|
|
60
62
|
|
61
|
-
|
62
|
-
|
63
63
|
# set 'dir_save'
|
64
|
-
if
|
64
|
+
if "dar" in sys.platform:
|
65
65
|
dir_save = "/Users/macjianfeng/Dropbox/Downloads/"
|
66
66
|
else:
|
67
|
-
if
|
68
|
-
dir_save= "Z:\\Jianfeng\\temp\\"
|
69
|
-
elif
|
70
|
-
dir_data="/Users/macjianfeng/Dropbox/github/python/py2ls/confidential_data/gmail_login.json"
|
67
|
+
if "win" in sys.platform:
|
68
|
+
dir_save = "Z:\\Jianfeng\\temp\\"
|
69
|
+
elif "lin" in sys.platform:
|
70
|
+
dir_data = "/Users/macjianfeng/Dropbox/github/python/py2ls/confidential_data/gmail_login.json"
|
71
71
|
|
72
72
|
|
73
73
|
def unique(lst, ascending=None):
|
@@ -272,6 +272,7 @@ def upgrade(module="py2ls"):
|
|
272
272
|
except subprocess.CalledProcessError as e:
|
273
273
|
print(f"An error occurred while upgrading py2ls: {e}")
|
274
274
|
|
275
|
+
|
275
276
|
def get_version(pkg):
|
276
277
|
import importlib.metadata
|
277
278
|
|
@@ -791,7 +792,7 @@ def str2html(text_list, strict=False):
|
|
791
792
|
|
792
793
|
return html_content
|
793
794
|
|
794
|
-
|
795
|
+
|
795
796
|
def sreplace(*args, **kwargs):
|
796
797
|
"""
|
797
798
|
sreplace(text, by=None, robust=True)
|
@@ -1249,6 +1250,119 @@ def get_encoding(fpath, alternative_encodings=None, verbose=False):
|
|
1249
1250
|
return None
|
1250
1251
|
|
1251
1252
|
|
1253
|
+
def unzip(dir_path, output_dir=None):
|
1254
|
+
"""
|
1255
|
+
Unzips or extracts various compressed file formats (.gz, .zip, .7z, .tar, .bz2, .xz, .rar).
|
1256
|
+
If the output directory already exists, it will be replaced.
|
1257
|
+
|
1258
|
+
Parameters:
|
1259
|
+
dir_path (str): Path to the compressed file.
|
1260
|
+
output_dir (str): Directory where the extracted files will be saved.
|
1261
|
+
If None, it extracts to the same directory as the file, with the same name.
|
1262
|
+
|
1263
|
+
Returns:
|
1264
|
+
str: The path to the output directory where files are extracted.
|
1265
|
+
"""
|
1266
|
+
|
1267
|
+
# Set default output directory to the same as the input file
|
1268
|
+
if output_dir is None:
|
1269
|
+
output_dir = os.path.splitext(dir_path)[0]
|
1270
|
+
|
1271
|
+
# If the output directory already exists, remove it and replace it
|
1272
|
+
if os.path.exists(output_dir):
|
1273
|
+
if os.path.isdir(output_dir): # check if it is a folder
|
1274
|
+
shutil.rmtree(output_dir) # remove folder
|
1275
|
+
else:
|
1276
|
+
os.remove(output_dir) # remove file
|
1277
|
+
|
1278
|
+
# Handle .tar.gz files
|
1279
|
+
if dir_path.endswith(".tar.gz"):
|
1280
|
+
import tarfile
|
1281
|
+
|
1282
|
+
with tarfile.open(dir_path, "r:gz") as tar_ref:
|
1283
|
+
tar_ref.extractall(output_dir)
|
1284
|
+
return output_dir
|
1285
|
+
# Handle .gz files
|
1286
|
+
if dir_path.endswith(".gz"):
|
1287
|
+
import gzip
|
1288
|
+
|
1289
|
+
output_file = os.path.splitext(dir_path)[0] # remove the .gz extension
|
1290
|
+
with gzip.open(dir_path, "rb") as gz_file:
|
1291
|
+
with open(output_file, "wb") as out_file:
|
1292
|
+
shutil.copyfileobj(gz_file, out_file)
|
1293
|
+
return output_file
|
1294
|
+
|
1295
|
+
# Handle .zip files
|
1296
|
+
elif dir_path.endswith(".zip"):
|
1297
|
+
import zipfile
|
1298
|
+
|
1299
|
+
with zipfile.ZipFile(dir_path, "r") as zip_ref:
|
1300
|
+
zip_ref.extractall(output_dir)
|
1301
|
+
return output_dir
|
1302
|
+
|
1303
|
+
# Handle .7z files (requires py7zr)
|
1304
|
+
elif dir_path.endswith(".7z"):
|
1305
|
+
import py7zr
|
1306
|
+
|
1307
|
+
with py7zr.SevenZipFile(dir_path, mode="r") as z:
|
1308
|
+
z.extractall(path=output_dir)
|
1309
|
+
return output_dir
|
1310
|
+
|
1311
|
+
# Handle .tar files
|
1312
|
+
elif dir_path.endswith(".tar"):
|
1313
|
+
import tarfile
|
1314
|
+
|
1315
|
+
with tarfile.open(dir_path, "r") as tar_ref:
|
1316
|
+
tar_ref.extractall(output_dir)
|
1317
|
+
return output_dir
|
1318
|
+
|
1319
|
+
# Handle .tar.bz2 files
|
1320
|
+
elif dir_path.endswith(".tar.bz2"):
|
1321
|
+
import tarfile
|
1322
|
+
|
1323
|
+
with tarfile.open(dir_path, "r:bz2") as tar_ref:
|
1324
|
+
tar_ref.extractall(output_dir)
|
1325
|
+
return output_dir
|
1326
|
+
|
1327
|
+
# Handle .bz2 files
|
1328
|
+
elif dir_path.endswith(".bz2"):
|
1329
|
+
import bz2
|
1330
|
+
|
1331
|
+
output_file = os.path.splitext(dir_path)[0] # remove the .bz2 extension
|
1332
|
+
with bz2.open(dir_path, "rb") as bz_file:
|
1333
|
+
with open(output_file, "wb") as out_file:
|
1334
|
+
shutil.copyfileobj(bz_file, out_file)
|
1335
|
+
return output_file
|
1336
|
+
|
1337
|
+
# Handle .xz files
|
1338
|
+
elif dir_path.endswith(".xz"):
|
1339
|
+
import lzma
|
1340
|
+
|
1341
|
+
output_file = os.path.splitext(dir_path)[0] # remove the .xz extension
|
1342
|
+
with lzma.open(dir_path, "rb") as xz_file:
|
1343
|
+
with open(output_file, "wb") as out_file:
|
1344
|
+
shutil.copyfileobj(xz_file, out_file)
|
1345
|
+
return output_file
|
1346
|
+
|
1347
|
+
# Handle .rar files (requires rarfile)
|
1348
|
+
elif dir_path.endswith(".rar"):
|
1349
|
+
import rarfile
|
1350
|
+
|
1351
|
+
with rarfile.RarFile(dir_path) as rar_ref:
|
1352
|
+
rar_ref.extractall(output_dir)
|
1353
|
+
return output_dir
|
1354
|
+
|
1355
|
+
else:
|
1356
|
+
raise ValueError(f"Unsupported file format: {os.path.splitext(dir_path)[1]}")
|
1357
|
+
|
1358
|
+
|
1359
|
+
# Example usage:
|
1360
|
+
# output_dir = unzip('data.tar.gz')
|
1361
|
+
# output_file = unzip('file.csv.gz')
|
1362
|
+
# output_dir_zip = unzip('archive.zip')
|
1363
|
+
# output_dir_7z = unzip('archive.7z')
|
1364
|
+
|
1365
|
+
|
1252
1366
|
def fload(fpath, kind=None, **kwargs):
|
1253
1367
|
"""
|
1254
1368
|
Load content from a file with specified file type.
|
@@ -1397,11 +1511,26 @@ def fload(fpath, kind=None, **kwargs):
|
|
1397
1511
|
"pdf",
|
1398
1512
|
"ipynb",
|
1399
1513
|
]
|
1400
|
-
|
1514
|
+
zip_types = ["gz", "zip", "7z", "tar", "tar.gz", "tar.bz2", "bz2", "xz", "rar"]
|
1515
|
+
supported_types = [*doc_types, *img_types, *zip_types]
|
1401
1516
|
if kind not in supported_types:
|
1402
|
-
|
1403
|
-
|
1404
|
-
|
1517
|
+
print(f'Error:\n"{kind}" is not in the supported list {supported_types}')
|
1518
|
+
# if os.path.splitext(fpath)[1][1:].lower() in zip_types:
|
1519
|
+
# keep=kwargs.get("keep", False)
|
1520
|
+
# ifile=kwargs.get("ifile",(0,0))
|
1521
|
+
# kwargs.pop("keep",None)
|
1522
|
+
# kwargs.pop("ifile",None)
|
1523
|
+
# fpath_unzip=unzip(fpath)
|
1524
|
+
# if isinstance(fpath_unzip,list):
|
1525
|
+
# fpath_unzip=fpath_unzip[ifile[0]]
|
1526
|
+
# if os.path.isdir(fpath_unzip):
|
1527
|
+
# fpath_selected=listdir(fpath_unzip,kind=kind).fpath[ifile[1]]
|
1528
|
+
# fpath_unzip=fpath_selected
|
1529
|
+
# content_unzip=fload(fpath_unzip, **kwargs)
|
1530
|
+
# if not keep:
|
1531
|
+
# os.remove(fpath_unzip)
|
1532
|
+
# return content_unzip
|
1533
|
+
|
1405
1534
|
if kind == "docx":
|
1406
1535
|
return load_docx(fpath)
|
1407
1536
|
elif kind == "txt" or kind == "md":
|
@@ -1426,10 +1555,21 @@ def fload(fpath, kind=None, **kwargs):
|
|
1426
1555
|
elif kind.lower() in img_types:
|
1427
1556
|
print(f'Image ".{kind}" is loaded.')
|
1428
1557
|
return load_img(fpath)
|
1558
|
+
elif kind.lower() in zip_types:
|
1559
|
+
keep = kwargs.get("keep", False)
|
1560
|
+
fpath_unzip = unzip(fpath)
|
1561
|
+
content_unzip = fload(fpath_unzip, **kwargs)
|
1562
|
+
if not keep:
|
1563
|
+
os.remove(fpath_unzip)
|
1564
|
+
return content_unzip
|
1429
1565
|
else:
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1566
|
+
try:
|
1567
|
+
with open(fpath, "r") as f:
|
1568
|
+
content = f.readlines()
|
1569
|
+
except:
|
1570
|
+
with open(fpath, "r") as f:
|
1571
|
+
content = f.read()
|
1572
|
+
return content
|
1433
1573
|
|
1434
1574
|
|
1435
1575
|
# Example usage
|
@@ -1812,6 +1952,7 @@ def sort_kind(df, by="name", ascending=True):
|
|
1812
1952
|
sorted_df = df.iloc[sorted_index].reset_index(drop=True)
|
1813
1953
|
return sorted_df
|
1814
1954
|
|
1955
|
+
|
1815
1956
|
def isa(content, kind):
|
1816
1957
|
"""
|
1817
1958
|
content, kind='img'
|
@@ -1840,6 +1981,8 @@ def isa(content, kind):
|
|
1840
1981
|
elif "color" in kind.lower(): # file
|
1841
1982
|
return is_str_color(content)
|
1842
1983
|
elif "html" in kind.lower():
|
1984
|
+
if content is None or not isinstance(content, str):
|
1985
|
+
return False
|
1843
1986
|
# Remove leading and trailing whitespace
|
1844
1987
|
content = content.strip()
|
1845
1988
|
# Check for common HTML tags using regex
|
@@ -1858,7 +2001,10 @@ def isa(content, kind):
|
|
1858
2001
|
print(f"{kind} was not set up correctly")
|
1859
2002
|
return False
|
1860
2003
|
|
2004
|
+
|
1861
2005
|
import sys
|
2006
|
+
|
2007
|
+
|
1862
2008
|
def get_os():
|
1863
2009
|
os_name = sys.platform
|
1864
2010
|
if "dar" in os_name:
|
@@ -2023,10 +2169,10 @@ def mkdir(*args, **kwargs):
|
|
2023
2169
|
if isinstance(arg, (str, list)):
|
2024
2170
|
if "/" in arg or "\\" in arg:
|
2025
2171
|
pardir = arg
|
2026
|
-
print(f"
|
2172
|
+
print(f'pardir: "{pardir}"')
|
2027
2173
|
else:
|
2028
2174
|
chdir = arg
|
2029
|
-
print(f"
|
2175
|
+
print(f'chdir:"{chdir}"')
|
2030
2176
|
elif isinstance(arg, bool):
|
2031
2177
|
overwrite = arg
|
2032
2178
|
print(overwrite)
|
@@ -2042,6 +2188,13 @@ def mkdir(*args, **kwargs):
|
|
2042
2188
|
pardir = os.path.normpath(pardir)
|
2043
2189
|
# Get the slash type: "/" or "\"
|
2044
2190
|
stype = "/" if "/" in pardir else "\\"
|
2191
|
+
if "mac" in get_os().lower() or "lin" in get_os().lower():
|
2192
|
+
stype = "/"
|
2193
|
+
elif "win" in get_os().lower():
|
2194
|
+
stype = "\\"
|
2195
|
+
else:
|
2196
|
+
stype = "/"
|
2197
|
+
|
2045
2198
|
# Check if the parent directory exists and is a directory path
|
2046
2199
|
if os.path.isdir(pardir):
|
2047
2200
|
os.chdir(pardir) # Set current path
|
@@ -3992,11 +4145,12 @@ format_excel(
|
|
3992
4145
|
|
3993
4146
|
from IPython.display import display, HTML, Markdown, Image
|
3994
4147
|
|
4148
|
+
|
3995
4149
|
def preview(var):
|
3996
4150
|
"""Master function to preview formatted variables in Jupyter."""
|
3997
4151
|
|
3998
4152
|
if isinstance(var, str):
|
3999
|
-
if isa(var,
|
4153
|
+
if isa(var, "html"):
|
4000
4154
|
display(HTML(var)) # Render as HTML
|
4001
4155
|
# Check if it's a valid markdown
|
4002
4156
|
elif var.startswith("#"):
|
@@ -4004,7 +4158,8 @@ def preview(var):
|
|
4004
4158
|
else:
|
4005
4159
|
# Otherwise, display as plain text
|
4006
4160
|
print(var)
|
4007
|
-
|
4161
|
+
elif isinstance(var, BeautifulSoup):
|
4162
|
+
preview(str(var))
|
4008
4163
|
elif isinstance(var, pd.DataFrame):
|
4009
4164
|
# Display pandas DataFrame
|
4010
4165
|
display(var)
|
@@ -4036,4 +4191,236 @@ def preview(var):
|
|
4036
4191
|
# preview("This is a plain text message.")
|
4037
4192
|
# preview("# This is a Markdown header")
|
4038
4193
|
# preview(pd.DataFrame({"Name": ["Alice", "Bob"], "Age": [25, 30]}))
|
4039
|
-
# preview({"key": "value", "numbers": [1, 2, 3]})
|
4194
|
+
# preview({"key": "value", "numbers": [1, 2, 3]})
|
4195
|
+
|
4196
|
+
|
4197
|
+
# ! DataFrame
|
4198
|
+
def df_as_type(
|
4199
|
+
df: pd.DataFrame,
|
4200
|
+
columns: Optional[Union[str, List[str]]] = None,
|
4201
|
+
astype: str = "datetime",
|
4202
|
+
format: Optional[str] = None,
|
4203
|
+
inplace: bool = False,
|
4204
|
+
errors: str = "coerce", # Can be "ignore", "raise", or "coerce"
|
4205
|
+
**kwargs,
|
4206
|
+
) -> Optional[pd.DataFrame]:
|
4207
|
+
"""
|
4208
|
+
Convert specified columns of a DataFrame to a specified type (e.g., datetime, float, int, numeric, timedelta).
|
4209
|
+
If columns is None, all columns in the DataFrame will be converted.
|
4210
|
+
|
4211
|
+
Parameters:
|
4212
|
+
- df: DataFrame containing the columns to convert.
|
4213
|
+
- columns: Either a single column name, a list of column names, or None to convert all columns.
|
4214
|
+
- astype: The target type to convert the columns to ('datetime', 'float', 'int', 'numeric', 'timedelta', etc.).
|
4215
|
+
- format: Optional; format to specify the datetime format (only relevant for 'datetime' conversion).
|
4216
|
+
- inplace: Whether to modify the DataFrame in place or return a new one. Defaults to False.
|
4217
|
+
- errors: Can be "ignore", "raise", or "coerce"
|
4218
|
+
- **kwargs: Additional keyword arguments to pass to the conversion function (e.g., errors='ignore' for pd.to_datetime or pd.to_numeric).
|
4219
|
+
|
4220
|
+
Returns:
|
4221
|
+
- If inplace=False: DataFrame with the specified columns (or all columns if columns=None) converted to the specified type.
|
4222
|
+
- If inplace=True: The original DataFrame is modified in place, and nothing is returned.
|
4223
|
+
"""
|
4224
|
+
astypes = [
|
4225
|
+
"datetime",
|
4226
|
+
"timedelta",
|
4227
|
+
"numeric",
|
4228
|
+
"int",
|
4229
|
+
"int8",
|
4230
|
+
"int16",
|
4231
|
+
"int32",
|
4232
|
+
"int64",
|
4233
|
+
"uint8",
|
4234
|
+
"uint16",
|
4235
|
+
"uint32",
|
4236
|
+
"uint64",
|
4237
|
+
"float",
|
4238
|
+
"float16",
|
4239
|
+
"float32",
|
4240
|
+
"float64",
|
4241
|
+
"complex",
|
4242
|
+
"complex64",
|
4243
|
+
"complex128",
|
4244
|
+
"str",
|
4245
|
+
"string",
|
4246
|
+
"bool",
|
4247
|
+
"datetime64",
|
4248
|
+
"datetime64[ns]",
|
4249
|
+
"timedelta64",
|
4250
|
+
"timedelta64[ns]",
|
4251
|
+
"category",
|
4252
|
+
"object",
|
4253
|
+
"Sparse",
|
4254
|
+
"hour",
|
4255
|
+
"minute",
|
4256
|
+
"second",
|
4257
|
+
"time",
|
4258
|
+
"week",
|
4259
|
+
"date",
|
4260
|
+
"month",
|
4261
|
+
"year",
|
4262
|
+
]
|
4263
|
+
# correct the astype input
|
4264
|
+
astype = strcmp(astype, astypes)[0]
|
4265
|
+
print(f"converting {columns} as type: {astype}")
|
4266
|
+
# If inplace is False, make a copy of the DataFrame
|
4267
|
+
if not inplace:
|
4268
|
+
df = df.copy()
|
4269
|
+
# If columns is None, apply to all columns
|
4270
|
+
if columns is None:
|
4271
|
+
columns = df.columns
|
4272
|
+
|
4273
|
+
# Ensure columns is a list
|
4274
|
+
if isinstance(columns, (str, int)):
|
4275
|
+
columns = [columns]
|
4276
|
+
|
4277
|
+
# Convert specified columns
|
4278
|
+
for column in columns:
|
4279
|
+
try:
|
4280
|
+
if astype in [
|
4281
|
+
"datetime",
|
4282
|
+
"hour",
|
4283
|
+
"minute",
|
4284
|
+
"second",
|
4285
|
+
"time",
|
4286
|
+
"week",
|
4287
|
+
"date",
|
4288
|
+
"month",
|
4289
|
+
"year",
|
4290
|
+
]:
|
4291
|
+
kwargs.pop("errors", None)
|
4292
|
+
# convert it as type: datetime
|
4293
|
+
if isinstance(column, int):
|
4294
|
+
df.iloc[:, column] = pd.to_datetime(
|
4295
|
+
df.iloc[:, column], format=format, errors=errors, **kwargs
|
4296
|
+
)
|
4297
|
+
# further convert:
|
4298
|
+
if astype == "time":
|
4299
|
+
df.iloc[:, column] = df.iloc[:, column].dt.time
|
4300
|
+
elif astype == "month":
|
4301
|
+
df.iloc[:, column] = df.iloc[:, column].dt.month
|
4302
|
+
elif astype == "year":
|
4303
|
+
df.iloc[:, column] = df.iloc[:, column].dt.year
|
4304
|
+
elif astype == "date":
|
4305
|
+
df.iloc[:, column] = df.iloc[:, column].dt.date
|
4306
|
+
elif astype == "hour":
|
4307
|
+
df.iloc[:, column] = df.iloc[:, column].dt.hour
|
4308
|
+
elif astype == "minute":
|
4309
|
+
df.iloc[:, column] = df.iloc[:, column].dt.minute
|
4310
|
+
elif astype == "second":
|
4311
|
+
df.iloc[:, column] = df.iloc[:, column].dt.second
|
4312
|
+
elif astype == "week":
|
4313
|
+
df.iloc[:, column] = df.iloc[:, column].dt.day_name()
|
4314
|
+
else:
|
4315
|
+
df[column] = (
|
4316
|
+
pd.to_datetime(
|
4317
|
+
df[column], format=format, errors=errors, **kwargs
|
4318
|
+
)
|
4319
|
+
if format
|
4320
|
+
else pd.to_datetime(df[column], errors=errors, **kwargs)
|
4321
|
+
)
|
4322
|
+
# further convert:
|
4323
|
+
if astype == "time":
|
4324
|
+
df[column] = df[column].dt.time
|
4325
|
+
elif astype == "month":
|
4326
|
+
df[column] = df[column].dt.month
|
4327
|
+
elif astype == "year":
|
4328
|
+
df[column] = df[column].dt.year
|
4329
|
+
elif astype == "date":
|
4330
|
+
df[column] = df[column].dt.date
|
4331
|
+
elif astype == "hour":
|
4332
|
+
df[column] = df[column].dt.hour
|
4333
|
+
elif astype == "minute":
|
4334
|
+
df[column] = df[column].dt.minute
|
4335
|
+
elif astype == "second":
|
4336
|
+
df[column] = df[column].dt.second
|
4337
|
+
elif astype == "week":
|
4338
|
+
df[column] = df[column].dt.day_name()
|
4339
|
+
|
4340
|
+
elif astype == "numeric":
|
4341
|
+
kwargs.pop("errors", None)
|
4342
|
+
df[column] = pd.to_numeric(df[column], errors=errors, **kwargs)
|
4343
|
+
# print(f"Successfully converted '{column}' to numeric.")
|
4344
|
+
elif astype == "timedelta":
|
4345
|
+
kwargs.pop("errors", None)
|
4346
|
+
df[column] = pd.to_timedelta(df[column], errors=errors, **kwargs)
|
4347
|
+
# print(f"Successfully converted '{column}' to timedelta.")
|
4348
|
+
else:
|
4349
|
+
# Convert to other types (e.g., float, int)
|
4350
|
+
df[column] = df[column].astype(astype)
|
4351
|
+
# print(f"Successfully converted '{column}' to {astype}.")
|
4352
|
+
except Exception as e:
|
4353
|
+
print(f"Error converting '{column}' to {astype}: {e}")
|
4354
|
+
|
4355
|
+
# Return the modified DataFrame if inplace is False
|
4356
|
+
if not inplace:
|
4357
|
+
return df
|
4358
|
+
|
4359
|
+
|
4360
|
+
# ! DataFrame
|
4361
|
+
def df_sort_values(df, column, by=None, ascending=True, inplace=False, **kwargs):
|
4362
|
+
"""
|
4363
|
+
Sort a DataFrame by a specified column based on a custom order.
|
4364
|
+
|
4365
|
+
Parameters:
|
4366
|
+
- df: DataFrame to be sorted.
|
4367
|
+
- column: The name of the column to sort by.
|
4368
|
+
- by: List specifying the custom order for sorting.
|
4369
|
+
- ascending: Boolean or list of booleans, default True.
|
4370
|
+
Sort ascending vs. descending.
|
4371
|
+
- inplace: If True, perform operation in place and return None.
|
4372
|
+
- **kwargs: Additional arguments to pass to sort_values.
|
4373
|
+
|
4374
|
+
Returns:
|
4375
|
+
- Sorted DataFrame if inplace is False, otherwise None.
|
4376
|
+
"""
|
4377
|
+
if column not in df.columns:
|
4378
|
+
raise ValueError(f"Column '{column}' does not exist in the DataFrame.")
|
4379
|
+
|
4380
|
+
if not isinstance(by, list):
|
4381
|
+
raise ValueError("custom_order must be a list.")
|
4382
|
+
|
4383
|
+
try:
|
4384
|
+
# Convert the specified column to a categorical type with the custom order
|
4385
|
+
df[column] = pd.Categorical(df[column], categories=by, ordered=True)
|
4386
|
+
if inplace: # replace the original
|
4387
|
+
df.sort_values(column, ascending=ascending, inplace=True, **kwargs)
|
4388
|
+
print(f"Successfully sorted DataFrame by '{column}'")
|
4389
|
+
return None
|
4390
|
+
else:
|
4391
|
+
sorted_df = df.sort_values(column, ascending=ascending, **kwargs)
|
4392
|
+
print(f"Successfully sorted DataFrame by '{column}' using custom order.")
|
4393
|
+
return sorted_df
|
4394
|
+
except Exception as e:
|
4395
|
+
print(f"Error sorting DataFrame by '{column}': {e}")
|
4396
|
+
return df
|
4397
|
+
|
4398
|
+
|
4399
|
+
# # Example usage:
|
4400
|
+
# # Sample DataFrame
|
4401
|
+
# data = {
|
4402
|
+
# "month": ["March", "January", "February", "April", "December"],
|
4403
|
+
# "Amount": [200, 100, 150, 300, 250],
|
4404
|
+
# }
|
4405
|
+
# df_month = pd.DataFrame(data)
|
4406
|
+
|
4407
|
+
# # Define the month order
|
4408
|
+
# month_order = [
|
4409
|
+
# "January",
|
4410
|
+
# "February",
|
4411
|
+
# "March",
|
4412
|
+
# "April",
|
4413
|
+
# "May",
|
4414
|
+
# "June",
|
4415
|
+
# "July",
|
4416
|
+
# "August",
|
4417
|
+
# "September",
|
4418
|
+
# "October",
|
4419
|
+
# "November",
|
4420
|
+
# "December",
|
4421
|
+
# ]
|
4422
|
+
# display(df_month)
|
4423
|
+
# sorted_df_month = df_sort_values(df_month, "month", month_order, ascending=True)
|
4424
|
+
# display(sorted_df_month)
|
4425
|
+
# df_sort_values(df_month, "month", month_order, ascending=True, inplace=True)
|
4426
|
+
# display(df_month)
|
py2ls/plot.py
CHANGED
@@ -17,6 +17,96 @@ from .stats import *
|
|
17
17
|
logging.getLogger("fontTools").setLevel(logging.WARNING)
|
18
18
|
|
19
19
|
|
20
|
+
def df_corr(
|
21
|
+
df,
|
22
|
+
columns="all",
|
23
|
+
tri="u",
|
24
|
+
mask=True,
|
25
|
+
k=1,
|
26
|
+
annot=True,
|
27
|
+
cmap="coolwarm",
|
28
|
+
fmt=".2f",
|
29
|
+
cluster=False, # New parameter for clustermap option
|
30
|
+
figsize=(10, 8),
|
31
|
+
row_cluster=True, # Perform clustering on rows
|
32
|
+
col_cluster=True, # Perform clustering on columns
|
33
|
+
dendrogram_ratio=(0.2, 0.1), # Adjust size of dendrograms
|
34
|
+
cbar_pos=(0.02, 0.8, 0.03, 0.2), # Adjust colorbar position
|
35
|
+
xticklabels=True, # Show column labels
|
36
|
+
yticklabels=True, # Show row labels
|
37
|
+
**kwargs,
|
38
|
+
):
|
39
|
+
# Select numeric columns or specific subset of columns
|
40
|
+
if columns == "all":
|
41
|
+
df_numeric = df.select_dtypes(include=[float, int])
|
42
|
+
else:
|
43
|
+
df_numeric = df[columns]
|
44
|
+
|
45
|
+
# Compute the correlation matrix
|
46
|
+
correlation_matrix = df_numeric.corr()
|
47
|
+
|
48
|
+
# Generate mask for the upper triangle if mask is True
|
49
|
+
if mask:
|
50
|
+
if "u" in tri.lower(): # upper => np.tril
|
51
|
+
mask_array = np.tril(np.ones_like(correlation_matrix, dtype=bool), k=k)
|
52
|
+
else: # lower => np.triu
|
53
|
+
mask_array = np.triu(np.ones_like(correlation_matrix, dtype=bool), k=k)
|
54
|
+
else:
|
55
|
+
mask_array = None
|
56
|
+
|
57
|
+
# Remove conflicting kwargs
|
58
|
+
kwargs.pop("mask", None)
|
59
|
+
kwargs.pop("annot", None)
|
60
|
+
kwargs.pop("cmap", None)
|
61
|
+
kwargs.pop("fmt", None)
|
62
|
+
|
63
|
+
kwargs.pop("clustermap", None)
|
64
|
+
kwargs.pop("row_cluster", None)
|
65
|
+
kwargs.pop("col_cluster", None)
|
66
|
+
kwargs.pop("dendrogram_ratio", None)
|
67
|
+
kwargs.pop("cbar_pos", None)
|
68
|
+
kwargs.pop("xticklabels", None)
|
69
|
+
kwargs.pop("col_cluster", None)
|
70
|
+
|
71
|
+
# Plot the heatmap or clustermap
|
72
|
+
if cluster:
|
73
|
+
# Create a clustermap
|
74
|
+
cluster_obj = sns.clustermap(
|
75
|
+
correlation_matrix,
|
76
|
+
mask=mask_array,
|
77
|
+
annot=annot,
|
78
|
+
cmap=cmap,
|
79
|
+
fmt=fmt,
|
80
|
+
figsize=figsize, # Figure size, adjusted for professional display
|
81
|
+
row_cluster=row_cluster, # Perform clustering on rows
|
82
|
+
col_cluster=col_cluster, # Perform clustering on columns
|
83
|
+
dendrogram_ratio=dendrogram_ratio, # Adjust size of dendrograms
|
84
|
+
cbar_pos=cbar_pos, # Adjust colorbar position
|
85
|
+
xticklabels=xticklabels, # Show column labels
|
86
|
+
yticklabels=yticklabels, # Show row labels
|
87
|
+
**kwargs, # Pass any additional arguments to sns.clustermap
|
88
|
+
)
|
89
|
+
|
90
|
+
return (
|
91
|
+
cluster_obj.ax_row_dendrogram,
|
92
|
+
cluster_obj.ax_col_dendrogram,
|
93
|
+
cluster_obj.ax_heatmap,
|
94
|
+
)
|
95
|
+
else:
|
96
|
+
# Create a standard heatmap
|
97
|
+
plt.figure(figsize=(10, 8))
|
98
|
+
ax = sns.heatmap(
|
99
|
+
correlation_matrix,
|
100
|
+
mask=mask_array,
|
101
|
+
annot=annot,
|
102
|
+
cmap=cmap,
|
103
|
+
fmt=fmt,
|
104
|
+
**kwargs, # Pass any additional arguments to sns.heatmap
|
105
|
+
)
|
106
|
+
# Return the Axes object for further customization if needed
|
107
|
+
return ax
|
108
|
+
|
109
|
+
|
20
110
|
def catplot(data, *args, **kwargs):
|
21
111
|
"""
|
22
112
|
catplot(data, opt=None, ax=None)
|
@@ -1,11 +1,16 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: py2ls
|
3
|
-
Version: 0.1.10.
|
3
|
+
Version: 0.1.10.27
|
4
4
|
Summary: py(thon)2(too)ls
|
5
5
|
Author: Jianfeng
|
6
6
|
Author-email: Jianfeng.Liu0413@gmail.com
|
7
|
-
Requires-Python: >=3.
|
7
|
+
Requires-Python: >=3.5,<4.0
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Programming Language :: Python :: 3.5
|
10
|
+
Classifier: Programming Language :: Python :: 3.6
|
11
|
+
Classifier: Programming Language :: Python :: 3.7
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
9
14
|
Classifier: Programming Language :: Python :: 3.10
|
10
15
|
Classifier: Programming Language :: Python :: 3.11
|
11
16
|
Classifier: Programming Language :: Python :: 3.12
|
@@ -172,13 +172,14 @@ py2ls/.gitignore,sha256=y7GvbD_zZkjPVVIue8AyiuFkDMuUbvMaV65Lgu89To8,2763
|
|
172
172
|
py2ls/LICENSE,sha256=UOZ1F5fFDe3XXvG4oNnkL1-Ecun7zpHzRxjp-XsMeAo,11324
|
173
173
|
py2ls/README.md,sha256=CwvJWAnSXnCnrVHlnEbrxxi6MbjbE_MT6DH2D53S818,11572
|
174
174
|
py2ls/__init__.py,sha256=Nn8jTIvySX7t7DMJ8VNRVctTStgXGjHldOIdZ35PdW8,165
|
175
|
-
py2ls/batman.py,sha256=
|
175
|
+
py2ls/batman.py,sha256=E7gYofbDzN7S5oCmO_dd5Z1bxxhoYMJSD6s-VaF388E,11398
|
176
176
|
py2ls/brain_atlas.py,sha256=w1o5EelRjq89zuFJUNSz4Da8HnTCwAwDAZ4NU4a-bAY,5486
|
177
177
|
py2ls/chat.py,sha256=Yr22GoIvoWhpV3m4fdwV_I0Mn77La346_ymSinR-ORA,3793
|
178
178
|
py2ls/correlators.py,sha256=RbOaJIPLCHJtUm5SFi_4dCJ7VFUPWR0PErfK3K26ad4,18243
|
179
179
|
py2ls/data/.DS_Store,sha256=iH2O541jT_5mlTPavY_d5V2prS9zhNx4Pv7yhmbwaHI,6148
|
180
180
|
py2ls/data/db2ls_sql_chtsht.json,sha256=ls9d7Sm8TLeujanWHfHlWhU85Qz1KnAizO_9X3wUH7E,6933
|
181
181
|
py2ls/data/docs_links.json,sha256=kXgbbWo0b8bfV4n6iuuUNLnZipIyLzokUO6Lzmf7nO4,101829
|
182
|
+
py2ls/data/email/email_html_template.html,sha256=UIg3aixWfdNsvVx-j2dX1M5N3G-6DgrnV1Ya1cLjiUQ,2809
|
182
183
|
py2ls/data/lang_code_iso639.json,sha256=qZiU7H2RLJjDMXK22C-jhwzLJCI5vKmampjB1ys4ek4,2157
|
183
184
|
py2ls/data/styles/example/style1.pdf,sha256=Pt_qQJ5kiCSIPiz3TWSwEffHUdj75kKXnZ4MPqpEx4I,29873
|
184
185
|
py2ls/data/styles/example/style2.pdf,sha256=0xduPLPulET38LEP2V2H_q70wqlrrBEo8ttqO-FMrfQ,25449
|
@@ -206,15 +207,15 @@ py2ls/doc.py,sha256=xN3g1OWfoaGUhikbJ0NqbN5eKy1VZVvWwRlhHMgyVEc,4243
|
|
206
207
|
py2ls/export_requirements.py,sha256=x2WgUF0jYKz9GfA1MVKN-MdsM-oQ8yUeC6Ua8oCymio,2325
|
207
208
|
py2ls/freqanalysis.py,sha256=F4218VSPbgL5tnngh6xNCYuNnfR-F_QjECUUxrPYZss,32594
|
208
209
|
py2ls/ich2ls.py,sha256=3E9R8oVpyYZXH5PiIQgT3CN5NxLe4Dwtm2LwaeacE6I,21381
|
209
|
-
py2ls/ips.py,sha256=
|
210
|
+
py2ls/ips.py,sha256=TMmk9kwxY8KWFWLbNpTxD4nad95uMpXgSqke0S-gAMo,161096
|
210
211
|
py2ls/netfinder.py,sha256=vgOOMhzwbjRuLWMAPyf_kh3HoOhsJ9dlA-tCkMf7kNU,55371
|
211
212
|
py2ls/ocr.py,sha256=5lhUbJufIKRSOL6wAWVLEo8TqMYSjoI_Q-IO-_4u3DE,31419
|
212
|
-
py2ls/plot.py,sha256=
|
213
|
+
py2ls/plot.py,sha256=vkKwppGLjErM6s1L0JOhukX54XbfKXUl6ojhVztCBN4,100538
|
213
214
|
py2ls/setuptools-70.1.0-py3-none-any.whl,sha256=2bi3cUVal8ip86s0SOvgspteEF8SKLukECi-EWmFomc,882588
|
214
215
|
py2ls/sleep_events_detectors.py,sha256=bQA3HJqv5qnYKJJEIhCyhlDtkXQfIzqksnD0YRXso68,52145
|
215
216
|
py2ls/stats.py,sha256=fJmXQ9Lq460StOn-kfEljE97cySq7876HUPTnpB5hLs,38123
|
216
217
|
py2ls/translator.py,sha256=zBeq4pYZeroqw3DT-5g7uHfVqKd-EQptT6LJ-Adi8JY,34244
|
217
218
|
py2ls/wb_detector.py,sha256=7y6TmBUj9exCZeIgBAJ_9hwuhkDh1x_-yg4dvNY1_GQ,6284
|
218
|
-
py2ls-0.1.10.
|
219
|
-
py2ls-0.1.10.
|
220
|
-
py2ls-0.1.10.
|
219
|
+
py2ls-0.1.10.27.dist-info/METADATA,sha256=vby3fz3bfQ7SzPfG49id_MWTInQE1VGqFRxRca7sahs,20040
|
220
|
+
py2ls-0.1.10.27.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
221
|
+
py2ls-0.1.10.27.dist-info/RECORD,,
|
File without changes
|