data-manipulation 0.36__tar.gz → 0.37__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.
Files changed (32) hide show
  1. {data_manipulation-0.36 → data_manipulation-0.37}/LICENSE +4 -4
  2. {data_manipulation-0.36 → data_manipulation-0.37}/PKG-INFO +11 -1
  3. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/__init__.py +3 -0
  4. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/_version.py +3 -3
  5. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/base.py +23 -0
  6. data_manipulation-0.37/data_manipulation/boto3_.py +82 -0
  7. data_manipulation-0.37/data_manipulation/django_.py +123 -0
  8. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/pandas_.py +49 -5
  9. data_manipulation-0.37/data_manipulation/prometheus_.py +26 -0
  10. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/sqlalchemy_.py +4 -4
  11. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation.egg-info/PKG-INFO +11 -1
  12. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation.egg-info/SOURCES.txt +1 -0
  13. data_manipulation-0.36/data_manipulation/django_.py +0 -48
  14. data_manipulation-0.36/data_manipulation/prometheus_.py +0 -27
  15. {data_manipulation-0.36 → data_manipulation-0.37}/MANIFEST.in +0 -0
  16. {data_manipulation-0.36 → data_manipulation-0.37}/README.md +0 -0
  17. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/beautifulsoup_.py +0 -0
  18. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/cryptography_.py +0 -0
  19. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/flask_.py +0 -0
  20. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/geopandas_.py +0 -0
  21. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/kerberos_.py +0 -0
  22. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/mysql_connector_python_.py +0 -0
  23. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/openldap_.py +0 -0
  24. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/psycopg2_.py +0 -0
  25. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/pyspark_.py +0 -0
  26. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation/smtplib_.py +0 -0
  27. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation.egg-info/dependency_links.txt +0 -0
  28. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation.egg-info/requires.txt +0 -0
  29. {data_manipulation-0.36 → data_manipulation-0.37}/data_manipulation.egg-info/top_level.txt +0 -0
  30. {data_manipulation-0.36 → data_manipulation-0.37}/setup.cfg +0 -0
  31. {data_manipulation-0.36 → data_manipulation-0.37}/setup.py +0 -0
  32. {data_manipulation-0.36 → data_manipulation-0.37}/versioneer.py +0 -0
@@ -1,19 +1,19 @@
1
1
  BSD 3-Clause License
2
2
 
3
- Copyright (c) 2020, Ng Teng Qing (shawnngtq)
3
+ Copyright (c) 2020, Shawn Ng (shawnngtq)
4
4
  All rights reserved.
5
5
 
6
6
  Redistribution and use in source and binary forms, with or without modification,
7
7
  are permitted provided that the following conditions are met:
8
8
 
9
- * Redistributions of source code must retain the above copyright notice, this
9
+ - Redistributions of source code must retain the above copyright notice, this
10
10
  list of conditions and the following disclaimer.
11
11
 
12
- * Redistributions in binary form must reproduce the above copyright notice, this
12
+ - Redistributions in binary form must reproduce the above copyright notice, this
13
13
  list of conditions and the following disclaimer in the documentation and/or
14
14
  other materials provided with the distribution.
15
15
 
16
- * Neither the name of data_manipulation nor the names of its
16
+ - Neither the name of data_manipulation nor the names of its
17
17
  contributors may be used to endorse or promote products derived from this
18
18
  software without specific prior written permission.
19
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: data_manipulation
3
- Version: 0.36
3
+ Version: 0.37
4
4
  Summary: Powerful data manipulation
5
5
  Home-page: https://github.com/shawnngtq/data-manipulation
6
6
  Author: Shawn Ng
@@ -19,6 +19,16 @@ Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Cython
20
20
  Classifier: Topic :: Scientific/Engineering
21
21
  License-File: LICENSE
22
+ Requires-Dist: beautifulsoup4
23
+ Requires-Dist: django
24
+ Requires-Dist: lxml
25
+ Requires-Dist: matplotlib
26
+ Requires-Dist: mysql-connector-python
27
+ Requires-Dist: pandas
28
+ Requires-Dist: psycopg2
29
+ Requires-Dist: pyspark
30
+ Requires-Dist: tqdm
31
+ Requires-Dist: xlsxwriter
22
32
 
23
33
 
24
34
  Data Manipulation is a Python package providing powerful utility functions.
@@ -1,13 +1,16 @@
1
1
  from data_manipulation import (
2
2
  base,
3
3
  beautifulsoup_,
4
+ boto3_,
4
5
  cryptography_,
5
6
  django_,
7
+ flask_,
6
8
  geopandas_,
7
9
  kerberos_,
8
10
  mysql_connector_python_,
9
11
  openldap_,
10
12
  pandas_,
13
+ prometheus_,
11
14
  psycopg2_,
12
15
  pyspark_,
13
16
  smtplib_,
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2023-10-03T09:27:54+0800",
11
+ "date": "2023-12-30T10:16:23+0800",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "263a1314653b0a2f32b9e1eea3efbf78ad990ca2",
15
- "version": "0.36"
14
+ "full-revisionid": "6f824ae7f46ddf5b2f60190bacfc2e4118d7f80f",
15
+ "version": "0.37"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -109,6 +109,7 @@ def get_country_name_variation() -> dict:
109
109
  "SLAVONIC": "SK",
110
110
  "SLOVAK REPUBLIC": "SK",
111
111
  "TURKEY": "TR",
112
+ "TURKIYE": "TR",
112
113
  "UNITED STATES": "US",
113
114
  }
114
115
  return variations
@@ -363,6 +364,28 @@ def list_to_file(filepath: str, list_: list, newline: bool = True) -> None:
363
364
  f.close()
364
365
 
365
366
 
367
+ # URLLIB
368
+ def create_encode_url(url: str, query_params: dict = {}) -> str:
369
+ """
370
+ Create encoded url
371
+
372
+ Parameters
373
+ ----------
374
+ url : str
375
+ base url
376
+ query_params : dict, optional
377
+ dictionary of url query parameters, by default {}
378
+
379
+ Returns
380
+ -------
381
+ str
382
+ encoded url
383
+ """
384
+ from urllib.parse import urlencode
385
+
386
+ return f"{url}{urlencode(query_params)}"
387
+
388
+
366
389
  # SYSTEM
367
390
  def parse_ps_aux(ps_aux_commands: str) -> List[list]:
368
391
  """
@@ -0,0 +1,82 @@
1
+ def send_aws_ses_email(
2
+ sender: str,
3
+ recipient: list,
4
+ subject: str,
5
+ body_text: str,
6
+ body_type: str,
7
+ ses_client,
8
+ attachment: str = None,
9
+ ):
10
+ """
11
+ Send AWS SES email
12
+
13
+ Parameters
14
+ ----------
15
+ sender : str
16
+ sender email
17
+ recipient : list
18
+ list of recipient emails
19
+ subject : str
20
+ email subject
21
+ body_text : str
22
+ email body
23
+ body_type : str
24
+ email body type
25
+ ses_client : _type_
26
+ aws ses client
27
+ attachment : str, optional
28
+ attachment path, by default None
29
+
30
+ Returns
31
+ -------
32
+ str
33
+ aws ses client email response or none
34
+ """
35
+ import os
36
+ from email.mime.application import MIMEApplication
37
+ from email.mime.multipart import MIMEMultipart
38
+ from email.mime.text import MIMEText
39
+
40
+ from botocore.exceptions import BotoCoreError, ClientError
41
+
42
+ msg = MIMEMultipart()
43
+ msg["Subject"] = subject
44
+ msg["From"] = sender
45
+ msg["To"] = ", ".join(recipient)
46
+
47
+ part = MIMEText(body_text, body_type)
48
+ msg.attach(part)
49
+
50
+ if attachment:
51
+ try:
52
+ with open(attachment, "rb") as f:
53
+ part = MIMEApplication(f.read())
54
+ part.add_header(
55
+ "Content-Disposition",
56
+ "attachment",
57
+ filename=os.path.basename(attachment),
58
+ )
59
+ msg.attach(part)
60
+ except FileNotFoundError:
61
+ print(f"{attachment} not found")
62
+ return
63
+
64
+ try:
65
+ response = ses_client.send_raw_email(
66
+ Source=sender,
67
+ Destinations=recipient,
68
+ RawMessage={
69
+ "Data": msg.as_string(),
70
+ },
71
+ )
72
+ except (BotoCoreError, ClientError) as error:
73
+ print(f"Error: {error}")
74
+ return
75
+ else:
76
+ return response
77
+
78
+
79
+ if __name__ == "__main__":
80
+ import doctest
81
+
82
+ doctest.testmod()
@@ -0,0 +1,123 @@
1
+ def init_django(django_dir: str, project_name: str) -> None:
2
+ """
3
+ Setup Django in Jupyter. Reference from https://gist.github.com/codingforentrepreneurs/76e570d759f83d690bf36a8a8fa4cfbe
4
+
5
+ Parameters
6
+ ----------
7
+ django_dir : str
8
+ Django project location
9
+ project_name : str
10
+ Django project name
11
+ """
12
+ import os
13
+ import sys
14
+
15
+ import django
16
+
17
+ # django_dir = os.getenv("PWD")
18
+ # os.chdir(django_dir)
19
+
20
+ project_name = project_name or os.environ.get("DJANGO_PROJECT") or None
21
+ if not project_name:
22
+ raise Exception(
23
+ """Set an environment variable: `DJANGO_PROJECT=your_project_name` or call `init_django(your_project_name)`"""
24
+ )
25
+
26
+ sys.path.insert(0, django_dir)
27
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", f"{project_name}.settings")
28
+ os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
29
+ django.setup()
30
+
31
+
32
+ def get_django_countries_dict():
33
+ """
34
+ Get django_countries package's dictionaries
35
+ code_name - "SG": "SINGAPORE"
36
+ name_code - "SINGAPORE": "SG"
37
+ """
38
+ from django_countries import countries
39
+
40
+ code_name = {k: v.upper() for k, v in dict(countries).items()}
41
+ name_code = {v: k for k, v in code_name.items()}
42
+ return code_name, name_code
43
+
44
+
45
+ def django_validate_email(email: str) -> str | None:
46
+ """
47
+ Django validate email
48
+
49
+ Parameters
50
+ ----------
51
+ email : str
52
+ email to check
53
+
54
+ Returns
55
+ -------
56
+ str | None
57
+ email or none
58
+ """
59
+ from django.core.validators import validate_email
60
+
61
+ try:
62
+ validate_email(email)
63
+ return email
64
+ except Exception as e:
65
+ return
66
+
67
+
68
+ def django_validate_url(url: str) -> str | None:
69
+ """
70
+ Django validate url
71
+
72
+ Parameters
73
+ ----------
74
+ url : str
75
+ url to validate
76
+
77
+ Returns
78
+ -------
79
+ str | None
80
+ url or None
81
+ """
82
+ from django.core.validators import URLValidator
83
+
84
+ validator = URLValidator()
85
+ try:
86
+ validator(url)
87
+ return url
88
+ except Exception as e:
89
+ return
90
+
91
+
92
+ def django_validate_phone(phone: str, region=None) -> str | None:
93
+ """
94
+ Django validate phone
95
+
96
+ Parameters
97
+ ----------
98
+ phone : str
99
+ phone to check
100
+ region : _type_, optional
101
+ phone region, by default None
102
+
103
+ Returns
104
+ -------
105
+ str | None
106
+ parsed phone or none
107
+ """
108
+ from phonenumber_field.phonenumber import PhoneNumber
109
+
110
+ try:
111
+ return PhoneNumber.from_string(phone).as_e164
112
+ except:
113
+ try:
114
+ return PhoneNumber.from_string(phone, region=region).as_e164
115
+ except Exception as e:
116
+ print(e)
117
+ return
118
+
119
+
120
+ if __name__ == "__main__":
121
+ import doctest
122
+
123
+ doctest.testmod()
@@ -364,15 +364,56 @@ def split_left_merged_dataframe(dataframe, dataframe2, columns):
364
364
  return df_both, df_left
365
365
 
366
366
 
367
+ ## AGGREGATE
368
+ def aggregate_set_without_none(column: pd.Series, nested_set: bool = False) -> set:
369
+ """
370
+ Use in pandas groupby agg, create a set without none based on input column
371
+
372
+ Parameters
373
+ ----------
374
+ column : pd.Series
375
+ columns to set
376
+ nested_set : bool, optional
377
+ nested set within set, by default False
378
+
379
+ Returns
380
+ -------
381
+ set
382
+ set without None
383
+ """
384
+ if nested_set:
385
+ output = set()
386
+ for value in column:
387
+ if isinstance(value, set):
388
+ output.update(value)
389
+ # todo, what if there is valid list / dict / tuple / complex object?
390
+ elif value:
391
+ output.add(value)
392
+ return output
393
+ else:
394
+ return {value for value in column if value is not None}
395
+
396
+
367
397
  # DATA STRUCTURE
368
- def clean_none(dataframe, clean_variation=True, non_variations=[]):
398
+ def clean_none(
399
+ dataframe: pd.DataFrame,
400
+ nan_to_none: bool = True,
401
+ clean_variation: bool = True,
402
+ none_variations: list = [],
403
+ ) -> pd.DataFrame:
369
404
  """
370
405
  Return a dataframe from given dataframe with standardized None. Deprecated as of pandas 1.3.0.
371
406
 
372
407
  Parameters
373
408
  ----------
374
- dataframe : pandas.DataFrame
375
- Dataframe
409
+ dataframe : pd.DataFrame
410
+ _description_
411
+ nan_to_none : bool, optional
412
+ replace np.NaN with None, by default True
413
+ clean_variation : bool, optional
414
+ clean custom variables, by default True
415
+ none_variations : list, optional
416
+ list of none variations, by default []
376
417
 
377
418
  Examples
378
419
  --------
@@ -410,12 +451,15 @@ def clean_none(dataframe, clean_variation=True, non_variations=[]):
410
451
 
411
452
  Returns
412
453
  -------
413
- df : pandas.DataFrame
454
+ pd.DataFrame
455
+ _description_
414
456
  """
415
457
  df = dataframe.copy()
416
458
  df = df.replace(r"^\s*$", np.nan, regex=True)
459
+ if nan_to_none:
460
+ df = df.replace(np.NaN, None, inplace=True)
417
461
  if clean_variation:
418
- df = df.replace(non_variations, np.nan)
462
+ df = df.replace(none_variations, np.nan)
419
463
  df = df.where(pd.notnull(df), None)
420
464
  return df
421
465
 
@@ -0,0 +1,26 @@
1
+ # import logging
2
+ # import subprocess
3
+
4
+ # def ldapsearch(
5
+ # search_base: str,
6
+ # ldap_uri: str,
7
+ # bind_dn: str,
8
+ # password: str,
9
+ # filter: str,
10
+ # ) -> subprocess.CompletedProcess:
11
+ # cmd = (
12
+ # "ldapsearch -b {search_base} -H {ldap_uri} -D {bind_dn} -w {password} {filter}"
13
+ # )
14
+ # output = subprocess.run(
15
+ # cmd,
16
+ # capture_output=True,
17
+ # shell=True,
18
+ # text=True,
19
+ # )
20
+ # return output
21
+
22
+
23
+ # if __name__ == "__main__":
24
+ # import doctest
25
+
26
+ # doctest.testmod()
@@ -45,7 +45,7 @@ def create_sqlalchemy_url(
45
45
  return url
46
46
 
47
47
 
48
- def check_connection_valid(
48
+ def create_sqlalchemy_engine(
49
49
  drivername: str,
50
50
  host: str,
51
51
  dbname: str,
@@ -54,7 +54,7 @@ def check_connection_valid(
54
54
  port=3306,
55
55
  ) -> sqlalchemy.engine.base.Engine:
56
56
  """
57
- Check mysql connection validity
57
+ Create SQLalchemy engine
58
58
 
59
59
  Parameters
60
60
  ----------
@@ -87,9 +87,9 @@ def check_connection_valid(
87
87
  engine = sqlalchemy.create_engine(url)
88
88
  try:
89
89
  engine.connect()
90
- logging.info("check_connection_valid: True")
90
+ logging.info("create_sqlalchemy_engine: True")
91
91
  except Exception as e:
92
- logging.error(f"check_connection_valid: False ({e})")
92
+ logging.error(f"create_sqlalchemy_engine: False ({e})")
93
93
  return engine
94
94
 
95
95
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: data-manipulation
3
- Version: 0.36
3
+ Version: 0.37
4
4
  Summary: Powerful data manipulation
5
5
  Home-page: https://github.com/shawnngtq/data-manipulation
6
6
  Author: Shawn Ng
@@ -19,6 +19,16 @@ Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Cython
20
20
  Classifier: Topic :: Scientific/Engineering
21
21
  License-File: LICENSE
22
+ Requires-Dist: beautifulsoup4
23
+ Requires-Dist: django
24
+ Requires-Dist: lxml
25
+ Requires-Dist: matplotlib
26
+ Requires-Dist: mysql-connector-python
27
+ Requires-Dist: pandas
28
+ Requires-Dist: psycopg2
29
+ Requires-Dist: pyspark
30
+ Requires-Dist: tqdm
31
+ Requires-Dist: xlsxwriter
22
32
 
23
33
 
24
34
  Data Manipulation is a Python package providing powerful utility functions.
@@ -8,6 +8,7 @@ data_manipulation/__init__.py
8
8
  data_manipulation/_version.py
9
9
  data_manipulation/base.py
10
10
  data_manipulation/beautifulsoup_.py
11
+ data_manipulation/boto3_.py
11
12
  data_manipulation/cryptography_.py
12
13
  data_manipulation/django_.py
13
14
  data_manipulation/flask_.py
@@ -1,48 +0,0 @@
1
- def init_django(django_dir: str, project_name: str) -> None:
2
- """
3
- Setup Django in Jupyter. Reference from https://gist.github.com/codingforentrepreneurs/76e570d759f83d690bf36a8a8fa4cfbe
4
-
5
- Parameters
6
- ----------
7
- django_dir : str
8
- Django project location
9
- project_name : str
10
- Django project name
11
- """
12
- import os
13
- import sys
14
-
15
- import django
16
-
17
- # django_dir = os.getenv("PWD")
18
- # os.chdir(django_dir)
19
-
20
- project_name = project_name or os.environ.get("DJANGO_PROJECT") or None
21
- if not project_name:
22
- raise Exception(
23
- """Set an environment variable: `DJANGO_PROJECT=your_project_name` or call `init_django(your_project_name)`"""
24
- )
25
-
26
- sys.path.insert(0, django_dir)
27
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", f"{project_name}.settings")
28
- os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
29
- django.setup()
30
-
31
-
32
- def get_django_countries_dict():
33
- """
34
- Get django_countries package's dictionaries
35
- code_name - "SG": "SINGAPORE"
36
- name_code - "SINGAPORE": "SG"
37
- """
38
- from django_countries import countries
39
-
40
- code_name = {k: v.upper() for k, v in dict(countries).items()}
41
- name_code = {v: k for k, v in code_name.items()}
42
- return code_name, name_code
43
-
44
-
45
- if __name__ == "__main__":
46
- import doctest
47
-
48
- doctest.testmod()
@@ -1,27 +0,0 @@
1
- import logging
2
- import subprocess
3
-
4
-
5
- def ldapsearch(
6
- search_base: str,
7
- ldap_uri: str,
8
- bind_dn: str,
9
- password: str,
10
- filter: str,
11
- ) -> subprocess.CompletedProcess:
12
- cmd = (
13
- "ldapsearch -b {search_base} -H {ldap_uri} -D {bind_dn} -w {password} {filter}"
14
- )
15
- output = subprocess.run(
16
- cmd,
17
- capture_output=True,
18
- shell=True,
19
- text=True,
20
- )
21
- return output
22
-
23
-
24
- if __name__ == "__main__":
25
- import doctest
26
-
27
- doctest.testmod()