data-manipulation 0.37__tar.gz → 0.40__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.37 → data_manipulation-0.40}/PKG-INFO +6 -5
  2. {data_manipulation-0.37 → data_manipulation-0.40}/README.md +1 -10
  3. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/__init__.py +1 -0
  4. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/_version.py +3 -3
  5. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/base.py +5 -1
  6. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/beautifulsoup_.py +5 -5
  7. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/boto3_.py +14 -5
  8. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/cryptography_.py +5 -2
  9. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/django_.py +7 -1
  10. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/kerberos_.py +6 -3
  11. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/mysql_connector_python_.py +7 -4
  12. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/openldap_.py +0 -1
  13. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/pandas_.py +6 -2
  14. data_manipulation-0.40/data_manipulation/prometheus_.py +0 -0
  15. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/psycopg2_.py +7 -4
  16. data_manipulation-0.40/data_manipulation/psycopg_.py +119 -0
  17. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/pyspark_.py +0 -3
  18. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/sqlalchemy_.py +5 -2
  19. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation.egg-info/PKG-INFO +7 -6
  20. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation.egg-info/SOURCES.txt +1 -0
  21. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation.egg-info/requires.txt +5 -4
  22. {data_manipulation-0.37 → data_manipulation-0.40}/setup.py +5 -4
  23. data_manipulation-0.37/data_manipulation/prometheus_.py +0 -26
  24. {data_manipulation-0.37 → data_manipulation-0.40}/LICENSE +0 -0
  25. {data_manipulation-0.37 → data_manipulation-0.40}/MANIFEST.in +0 -0
  26. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/flask_.py +0 -0
  27. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/geopandas_.py +0 -0
  28. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation/smtplib_.py +0 -0
  29. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation.egg-info/dependency_links.txt +0 -0
  30. {data_manipulation-0.37 → data_manipulation-0.40}/data_manipulation.egg-info/top_level.txt +0 -0
  31. {data_manipulation-0.37 → data_manipulation-0.40}/setup.cfg +0 -0
  32. {data_manipulation-0.37 → data_manipulation-0.40}/versioneer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: data_manipulation
3
- Version: 0.37
3
+ Version: 0.40
4
4
  Summary: Powerful data manipulation
5
5
  Home-page: https://github.com/shawnngtq/data-manipulation
6
6
  Author: Shawn Ng
@@ -20,15 +20,16 @@ Classifier: Programming Language :: Cython
20
20
  Classifier: Topic :: Scientific/Engineering
21
21
  License-File: LICENSE
22
22
  Requires-Dist: beautifulsoup4
23
+ Requires-Dist: boto3
23
24
  Requires-Dist: django
24
- Requires-Dist: lxml
25
- Requires-Dist: matplotlib
25
+ Requires-Dist: flask
26
+ Requires-Dist: geopandas
26
27
  Requires-Dist: mysql-connector-python
27
28
  Requires-Dist: pandas
29
+ Requires-Dist: psycopg
28
30
  Requires-Dist: psycopg2
29
31
  Requires-Dist: pyspark
30
- Requires-Dist: tqdm
31
- Requires-Dist: xlsxwriter
32
+ Requires-Dist: sqlalchemy
32
33
 
33
34
 
34
35
  Data Manipulation is a Python package providing powerful utility functions.
@@ -25,16 +25,7 @@ pip install data-manipulation
25
25
 
26
26
  ## Dependencies
27
27
 
28
- The dependencies will be installed automatically along with this package
29
-
30
- - beautifulsoup4
31
- - django
32
- - numpy
33
- - pandas
34
- - ipython
35
- - requests
36
- - psycopg2
37
- - pyspark
28
+ The dependencies will be installed automatically along with this package, in `setup.py`
38
29
 
39
30
  ## License
40
31
 
@@ -12,6 +12,7 @@ from data_manipulation import (
12
12
  pandas_,
13
13
  prometheus_,
14
14
  psycopg2_,
15
+ psycopg_,
15
16
  pyspark_,
16
17
  smtplib_,
17
18
  sqlalchemy_,
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2023-12-30T10:16:23+0800",
11
+ "date": "2024-05-26T15:48:20+0800",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "6f824ae7f46ddf5b2f60190bacfc2e4118d7f80f",
15
- "version": "0.37"
14
+ "full-revisionid": "805e0463f2f7f444a10afa22b658bc31ea864fed",
15
+ "version": "0.40"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -1,6 +1,10 @@
1
+ import logging
1
2
  import subprocess
2
3
  from typing import List
3
4
 
5
+ logging.basicConfig(level=logging.INFO)
6
+ logger = logging.getLogger(__file__)
7
+
4
8
 
5
9
  # DATA STRUCTURE
6
10
  def clean_string(
@@ -336,7 +340,7 @@ def remove_path_file(path: str, keyword: str, n: int = 2) -> None:
336
340
  to_delete = get_path_files(path=path, keywords=[keyword])[:-n]
337
341
  for file in to_delete:
338
342
  os.remove(f"{path}/{file}")
339
- print(f"{path}/{file} deleted ...")
343
+ logger.info(f"{path}/{file} deleted ...")
340
344
 
341
345
 
342
346
  def list_to_file(filepath: str, list_: list, newline: bool = True) -> None:
@@ -1,6 +1,8 @@
1
+ import logging
1
2
  from typing import Optional
2
3
 
3
- from bs4 import BeautifulSoup
4
+ logging.basicConfig(level=logging.INFO)
5
+ logger = logging.getLogger(__file__)
4
6
 
5
7
 
6
8
  def preprocess(html: str) -> Optional[str]:
@@ -37,9 +39,7 @@ def preprocess(html: str) -> Optional[str]:
37
39
  return html
38
40
 
39
41
 
40
- def build_soup(
41
- url: str, features: str = "lxml", to_preprocess: str = True
42
- ) -> Optional[BeautifulSoup]:
42
+ def build_soup(url: str, features: str = "lxml", to_preprocess: str = True):
43
43
  """
44
44
  Return Beautifulsoup object from given url
45
45
 
@@ -74,7 +74,7 @@ def build_soup(
74
74
  soup = BeautifulSoup(response.text, features=features)
75
75
  return soup
76
76
  else:
77
- print(f"response status code: {response.status_code}")
77
+ logger.error(f"response status code: {response.status_code}")
78
78
 
79
79
 
80
80
  if __name__ == "__main__":
@@ -1,3 +1,9 @@
1
+ import logging
2
+
3
+ logging.basicConfig(level=logging.INFO)
4
+ logger = logging.getLogger(__file__)
5
+
6
+
1
7
  def send_aws_ses_email(
2
8
  sender: str,
3
9
  recipient: list,
@@ -29,8 +35,12 @@ def send_aws_ses_email(
29
35
 
30
36
  Returns
31
37
  -------
32
- str
38
+ dict
33
39
  aws ses client email response or none
40
+
41
+ Reference
42
+ ---------
43
+ - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ses/client/send_raw_email.html
34
44
  """
35
45
  import os
36
46
  from email.mime.application import MIMEApplication
@@ -58,7 +68,7 @@ def send_aws_ses_email(
58
68
  )
59
69
  msg.attach(part)
60
70
  except FileNotFoundError:
61
- print(f"{attachment} not found")
71
+ logger.error(f"{attachment} not found")
62
72
  return
63
73
 
64
74
  try:
@@ -69,11 +79,10 @@ def send_aws_ses_email(
69
79
  "Data": msg.as_string(),
70
80
  },
71
81
  )
82
+ return response
72
83
  except (BotoCoreError, ClientError) as error:
73
- print(f"Error: {error}")
84
+ logger.error(f"Error: {error}")
74
85
  return
75
- else:
76
- return response
77
86
 
78
87
 
79
88
  if __name__ == "__main__":
@@ -2,6 +2,9 @@ import logging
2
2
 
3
3
  from cryptography.fernet import Fernet
4
4
 
5
+ logging.basicConfig(level=logging.INFO)
6
+ logger = logging.getLogger(__file__)
7
+
5
8
 
6
9
  def generate_fernet_key(output_directory: str, output_filename: str) -> bytes:
7
10
  """
@@ -29,9 +32,9 @@ def generate_fernet_key(output_directory: str, output_filename: str) -> bytes:
29
32
  f = open(filepath, "wb")
30
33
  f.write(key)
31
34
  f.close()
32
- logging.info(f"generate_fernet_key: True")
35
+ logger.info(f"generate_fernet_key: True")
33
36
  except Exception as e:
34
- logging.error(f"generate_fernet_key: False ({e})")
37
+ logger.error(f"generate_fernet_key: False ({e})")
35
38
  return key
36
39
 
37
40
 
@@ -1,3 +1,9 @@
1
+ import logging
2
+
3
+ logging.basicConfig(level=logging.INFO)
4
+ logger = logging.getLogger(__file__)
5
+
6
+
1
7
  def init_django(django_dir: str, project_name: str) -> None:
2
8
  """
3
9
  Setup Django in Jupyter. Reference from https://gist.github.com/codingforentrepreneurs/76e570d759f83d690bf36a8a8fa4cfbe
@@ -113,7 +119,7 @@ def django_validate_phone(phone: str, region=None) -> str | None:
113
119
  try:
114
120
  return PhoneNumber.from_string(phone, region=region).as_e164
115
121
  except Exception as e:
116
- print(e)
122
+ logger.error(e)
117
123
  return
118
124
 
119
125
 
@@ -1,6 +1,9 @@
1
1
  import logging
2
2
  import subprocess
3
3
 
4
+ logging.basicConfig(level=logging.INFO)
5
+ logger = logging.getLogger(__file__)
6
+
4
7
 
5
8
  def keytab_valid(
6
9
  keytab_filepath: str,
@@ -29,11 +32,11 @@ def keytab_valid(
29
32
  )
30
33
  try:
31
34
  if output.returncode == 0:
32
- logging.info("keytab_valid: True")
35
+ logger.info("keytab_valid: True")
33
36
  else:
34
- logging.info("keytab_valid: False")
37
+ logger.info("keytab_valid: False")
35
38
  except Exception as e:
36
- logging.error(f"keytab_valid: False ({e})")
39
+ logger.error(f"keytab_valid: False ({e})")
37
40
  return output
38
41
 
39
42
 
@@ -3,6 +3,9 @@ from typing import Optional, Union
3
3
 
4
4
  import mysql.connector
5
5
 
6
+ logging.basicConfig(level=logging.INFO)
7
+ logger = logging.getLogger(__file__)
8
+
6
9
 
7
10
  def create_connection(
8
11
  host: str,
@@ -43,9 +46,9 @@ def create_connection(
43
46
  password=password,
44
47
  port=port,
45
48
  )
46
- logging.info("MySQL database connected ...")
49
+ logger.info("MySQL database connected ...")
47
50
  except mysql.connector.errors.Error as e:
48
- logging.error(f"{e}")
51
+ logger.error(f"{e}")
49
52
  return connection
50
53
 
51
54
 
@@ -79,12 +82,12 @@ def execute_query(
79
82
  cursor.execute(sql_query, data)
80
83
  if commit:
81
84
  connection.commit()
82
- logging.info("MySQL committed ...")
85
+ logger.info("MySQL committed ...")
83
86
  id = cursor.lastrowid
84
87
  cursor.close()
85
88
  return id
86
89
  except mysql.connector.errors.Error as e:
87
- logging.error(f"{e}")
90
+ logger.error(f"{e}")
88
91
 
89
92
 
90
93
  if __name__ == "__main__":
@@ -1,4 +1,3 @@
1
- import logging
2
1
  import subprocess
3
2
 
4
3
 
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import subprocess
2
3
  from itertools import combinations
3
4
 
@@ -5,6 +6,9 @@ import numpy as np
5
6
  import pandas as pd
6
7
  from IPython.display import display
7
8
 
9
+ logging.basicConfig(level=logging.INFO)
10
+ logger = logging.getLogger(__file__)
11
+
8
12
 
9
13
  # CONFIG
10
14
  def config_pandas_display():
@@ -226,7 +230,7 @@ def compare_dataframes(dataframe, dataframe2):
226
230
  try:
227
231
  pd_df2 = dataframe2[dataframe2[column].notnull()]
228
232
  except KeyError:
229
- print(f"df2 {column}: does not exist")
233
+ logger.error(f"df2 {column}: does not exist")
230
234
 
231
235
  if pd_df2 is not None:
232
236
  print(f"df2 non-null {column}: {len(pd_df2)}")
@@ -722,7 +726,7 @@ def print_dataframe_overview(dataframe, stats=False):
722
726
  print(f"Maximum: {max(dataframe[column])}")
723
727
  display(series_count(dataframe[column]).head(20))
724
728
  except:
725
- print(f"Unable to get value_counts of {column} ...\n")
729
+ logger.error(f"Unable to get value_counts of {column} ...\n")
726
730
 
727
731
 
728
732
  def series_count(series):
@@ -4,6 +4,9 @@ from typing import Optional, Union
4
4
  import pandas as pd
5
5
  import psycopg2
6
6
 
7
+ logging.basicConfig(level=logging.INFO)
8
+ logger = logging.getLogger(__file__)
9
+
7
10
 
8
11
  def create_connection(
9
12
  host: str,
@@ -39,9 +42,9 @@ def create_connection(
39
42
  f"host={host} port={port} dbname={dbname} user={user} password={password}"
40
43
  )
41
44
  connection = psycopg2.connect(connection_string)
42
- logging.info("PostgreSQL database connected ...")
45
+ logger.info("PostgreSQL database connected ...")
43
46
  except psycopg2.OperationalError as e:
44
- logging.error(f"{e}")
47
+ logger.error(f"{e}")
45
48
  return connection
46
49
 
47
50
 
@@ -70,9 +73,9 @@ def execute_sql(
70
73
  cursor.execute(sql_query, data)
71
74
  if commit:
72
75
  connection.commit()
73
- logging.info("PostgreSQL committed ...")
76
+ logger.info("PostgreSQL committed ...")
74
77
  except psycopg2.OperationalError as e:
75
- logging.error(f"{e}")
78
+ logger.error(f"{e}")
76
79
 
77
80
 
78
81
  def query_to_pandas(
@@ -0,0 +1,119 @@
1
+ import logging
2
+ from typing import Optional, Union
3
+
4
+ import pandas as pd
5
+ import psycopg
6
+
7
+ logging.basicConfig(level=logging.INFO)
8
+ logger = logging.getLogger(__file__)
9
+
10
+
11
+ def create_connection(
12
+ host: str,
13
+ dbname: str,
14
+ user: str,
15
+ password: str,
16
+ port=5432,
17
+ ) -> psycopg.Connection:
18
+ """
19
+ Return psycopg connection (https://www.psycopg.org/)
20
+
21
+ Parameters
22
+ ----------
23
+ host : str
24
+ Database host address
25
+ dbname : str
26
+ Database name
27
+ user : str
28
+ User used to authenticate
29
+ password : str
30
+ Password used to authenticate
31
+ port : int, optional
32
+ Port number, by default 5432
33
+
34
+ Returns
35
+ -------
36
+ psycopg.Connection
37
+ Connection object
38
+ """
39
+ connection = None
40
+ try:
41
+ connection_string = (
42
+ f"host={host} port={port} dbname={dbname} user={user} password={password}"
43
+ )
44
+ connection = psycopg.connect(connection_string)
45
+ logger.info("PostgreSQL database connected ...")
46
+ except psycopg.OperationalError as e:
47
+ logger.error(f"{e}")
48
+ return connection
49
+
50
+
51
+ def execute_sql(
52
+ connection: psycopg.Connection,
53
+ sql_query: str,
54
+ data: Union[dict, tuple],
55
+ commit=True,
56
+ ) -> None:
57
+ """
58
+ Execute and commit PostgreSQL query
59
+
60
+ Parameters
61
+ ----------
62
+ connection : psycopg.Connection
63
+ _description_
64
+ sql_query : str
65
+ SQL query
66
+ data : Union[dict, tuple]
67
+ Data
68
+ commit : bool, optional
69
+ Make database change persistent, by default True
70
+
71
+ Reference
72
+ ---------
73
+ When you insert data key-value with dict type, error
74
+ ProgrammingError: cannot adapt type 'dict' using placeholder '%s' (format: AUTO)
75
+ """
76
+ cursor = connection.cursor()
77
+ try:
78
+ cursor.execute(sql_query, data)
79
+ if commit:
80
+ connection.commit()
81
+ logger.info("PostgreSQL committed ...")
82
+ except psycopg.OperationalError as e:
83
+ logger.error(f"{e}")
84
+
85
+
86
+ def query_to_pandas(
87
+ connection: psycopg.Connection,
88
+ sql_query: str,
89
+ ) -> pd.DataFrame:
90
+ """
91
+ Return Pandas dataframe from result of given SQL query
92
+
93
+ Parameters
94
+ ----------
95
+ connection : psycopg.Connection
96
+ _description_
97
+ sql_query : str
98
+ SQL query
99
+
100
+ Examples
101
+ --------
102
+ >>> sql_query = '''SELECT * FROM users;'''
103
+
104
+ Returns
105
+ -------
106
+ pd.DataFrame
107
+ _description_
108
+ """
109
+
110
+ return pd.read_sql(
111
+ sql=sql_query,
112
+ con=connection,
113
+ )
114
+
115
+
116
+ if __name__ == "__main__":
117
+ import doctest
118
+
119
+ doctest.testmod()
@@ -2,8 +2,6 @@ import pandas as pd
2
2
  import pyspark
3
3
  import pyspark.sql.functions as F
4
4
 
5
- spark = pyspark.sql.SparkSession.builder.master("local").getOrCreate()
6
-
7
5
 
8
6
  # CONFIG
9
7
  def config_spark_local(autoset=True):
@@ -84,7 +82,6 @@ def config_spark_local(autoset=True):
84
82
  print(f"spark_default_parallelism: {spark_default_parallelism}")
85
83
 
86
84
  if autoset:
87
- global spark
88
85
  spark = (
89
86
  pyspark.sql.SparkSession.builder.master("local")
90
87
  .config("spark.executor.cores", str(spark_executor_cores))
@@ -2,6 +2,9 @@ import logging
2
2
 
3
3
  import sqlalchemy
4
4
 
5
+ logging.basicConfig(level=logging.INFO)
6
+ logger = logging.getLogger(__file__)
7
+
5
8
 
6
9
  def create_sqlalchemy_url(
7
10
  drivername: str,
@@ -87,9 +90,9 @@ def create_sqlalchemy_engine(
87
90
  engine = sqlalchemy.create_engine(url)
88
91
  try:
89
92
  engine.connect()
90
- logging.info("create_sqlalchemy_engine: True")
93
+ logger.info("create_sqlalchemy_engine: True")
91
94
  except Exception as e:
92
- logging.error(f"create_sqlalchemy_engine: False ({e})")
95
+ logger.error(f"create_sqlalchemy_engine: False ({e})")
93
96
  return engine
94
97
 
95
98
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
- Name: data-manipulation
3
- Version: 0.37
2
+ Name: data_manipulation
3
+ Version: 0.40
4
4
  Summary: Powerful data manipulation
5
5
  Home-page: https://github.com/shawnngtq/data-manipulation
6
6
  Author: Shawn Ng
@@ -20,15 +20,16 @@ Classifier: Programming Language :: Cython
20
20
  Classifier: Topic :: Scientific/Engineering
21
21
  License-File: LICENSE
22
22
  Requires-Dist: beautifulsoup4
23
+ Requires-Dist: boto3
23
24
  Requires-Dist: django
24
- Requires-Dist: lxml
25
- Requires-Dist: matplotlib
25
+ Requires-Dist: flask
26
+ Requires-Dist: geopandas
26
27
  Requires-Dist: mysql-connector-python
27
28
  Requires-Dist: pandas
29
+ Requires-Dist: psycopg
28
30
  Requires-Dist: psycopg2
29
31
  Requires-Dist: pyspark
30
- Requires-Dist: tqdm
31
- Requires-Dist: xlsxwriter
32
+ Requires-Dist: sqlalchemy
32
33
 
33
34
 
34
35
  Data Manipulation is a Python package providing powerful utility functions.
@@ -19,6 +19,7 @@ data_manipulation/openldap_.py
19
19
  data_manipulation/pandas_.py
20
20
  data_manipulation/prometheus_.py
21
21
  data_manipulation/psycopg2_.py
22
+ data_manipulation/psycopg_.py
22
23
  data_manipulation/pyspark_.py
23
24
  data_manipulation/smtplib_.py
24
25
  data_manipulation/sqlalchemy_.py
@@ -1,10 +1,11 @@
1
1
  beautifulsoup4
2
+ boto3
2
3
  django
3
- lxml
4
- matplotlib
4
+ flask
5
+ geopandas
5
6
  mysql-connector-python
6
7
  pandas
8
+ psycopg
7
9
  psycopg2
8
10
  pyspark
9
- tqdm
10
- xlsxwriter
11
+ sqlalchemy
@@ -23,15 +23,16 @@ CLASSIFIERS = [
23
23
 
24
24
  DEPENDENCIES = [
25
25
  "beautifulsoup4",
26
+ "boto3",
26
27
  "django",
27
- "lxml",
28
- "matplotlib",
28
+ "flask",
29
+ "geopandas",
29
30
  "mysql-connector-python",
30
31
  "pandas",
32
+ "psycopg",
31
33
  "psycopg2",
32
34
  "pyspark",
33
- "tqdm",
34
- "xlsxwriter",
35
+ "sqlalchemy",
35
36
  ]
36
37
 
37
38
  setup(
@@ -1,26 +0,0 @@
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()