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 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\settings\\confidential_data\\gmail_login.json"
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
- subject="test",
91
- what="this is the body",
92
- attachments="/Users/test.pdf")
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 what
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
- "who" in k.lower(),
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
- ["wha" in k.lower(), "con" in k.lower(), "bod" in k.lower()]
132
+ [i in k.lower() for i in ["wha", "con", "bod"]]
128
133
  ): # 'what','content','body'
129
134
  what = v
130
- if any(["att" in k.lower(), "fil" in k.lower()]): # 'attachments', 'file'
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(["verb" in k.lower(), "show" in k.lower()]): # 'verbose', 'show'
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
- message["To"] = who
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
- server.sendmail(extract_kv()[0], who, message.as_string())
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>&copy; 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 'dar' in sys.platform:
64
+ if "dar" in sys.platform:
65
65
  dir_save = "/Users/macjianfeng/Dropbox/Downloads/"
66
66
  else:
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"
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
- supported_types = [*doc_types, *img_types]
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
- raise ValueError(
1403
- f"Error:\n{kind} is not in the supported list {supported_types}"
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
- raise ValueError(
1431
- f"Error:\n{kind} is not in the supported list {supported_types}"
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"pardir{pardir}")
2172
+ print(f'pardir: "{pardir}"')
2027
2173
  else:
2028
2174
  chdir = arg
2029
- print(f"chdir{chdir}")
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,'html'):
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.25
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.10,<4.0
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=HsZuB_NspZ8CysEY8lLy78zSqd_mg0LOGBofDwQjfKQ,10101
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=yJHpxn0EQIPk0Xhh_tb7VMWkL9Eh9Hw5XzzY7TfhQOE,147612
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=yj-AfnYNr1ha_Y5EimTsUVSooFc36nE0KCQ8cP9_Trs,97601
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.25.dist-info/METADATA,sha256=LN1qZZRz26ZuwKH1RSRFFpxTR5TeIElPhveOyOaXTHA,19791
219
- py2ls-0.1.10.25.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
220
- py2ls-0.1.10.25.dist-info/RECORD,,
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,,