afgutils 1.0.6__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.
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.1
2
+ Name: afgutils
3
+ Version: 1.0.6
4
+ Summary: Shared support functions
5
+ Author: Araz Heydarov
6
+ Author-email: arazheyderov@gmail.com
7
+ License: copyright
8
+ Requires-Python: >=3.10
9
+
10
+ Shared support functions
@@ -0,0 +1,10 @@
1
+ import atexit
2
+ from dotenv import load_dotenv as _load_dotenv
3
+ from .db import DB, sql
4
+ from .email import send_email
5
+ from .s3 import save_to_s3, get_from_s3
6
+ from .utils import *
7
+ from .trandata import *
8
+
9
+ _load_dotenv()
10
+ atexit.register(DB.close_connections)
@@ -0,0 +1,21 @@
1
+ from os import getenv
2
+ from datetime import datetime, timezone
3
+ import hashlib
4
+ import json
5
+ import requests
6
+
7
+
8
+ def generate_token() -> str:
9
+ access_key = getenv("advanceai_access_key")
10
+ secret_key = getenv("advanceai_secret_key")
11
+ utc_now = datetime.now(timezone.utc)
12
+ time_epoch_now = int(utc_now.timestamp()*1000)
13
+ signature = hashlib.sha256(f"{access_key}{secret_key}{time_epoch_now}".encode()).hexdigest()
14
+
15
+ token_url = "https://sg-api.advance.ai/openapi/auth/ticket/v1/generate-token"
16
+ token_body = json.dumps({'accessKey': access_key,
17
+ 'signature': signature,
18
+ 'timestamp': time_epoch_now})
19
+ token_headers = {'Content-Type': 'application/json'}
20
+ token_response = requests.post(token_url, headers=token_headers, data=token_body)
21
+ return token_response.json()['data']['token']
@@ -0,0 +1,68 @@
1
+ from os import getenv
2
+ from afgutils.db import DB
3
+ import requests
4
+ from datetime import datetime
5
+
6
+
7
+ class CredoLab:
8
+ GET_TOKEN_QUERY = """
9
+ SELECT * FROM credolab.token ORDER BY id DESC LIMIT 1;
10
+ """
11
+
12
+ INSERT_TOKEN_QUERY = """
13
+ INSERT INTO credolab.token (access_token, token_type, issued, expires, refresh_token)
14
+ VALUES (?, ?, ?, ?, ?);
15
+ """
16
+
17
+ def __init__(self):
18
+ self.email = getenv('credolab_user_email')
19
+ self.password = getenv('credolab_password')
20
+ self.token = None
21
+ self.rep_conn = DB.get_connection('rep')
22
+ self.service_url = getenv('credolab_base_url')
23
+
24
+ def get_token(self):
25
+ if self.token is None:
26
+ self.token = self._get_token()
27
+ return self.token
28
+
29
+ def _get_token(self):
30
+ with self.rep_conn.cursor() as cursor:
31
+ token_details = DB.execute(cursor, self.GET_TOKEN_QUERY, fetch='one')
32
+
33
+ if token_details is None or token_details['expires'] < datetime.utcnow():
34
+ token = self._fetch_token()
35
+ else:
36
+ token = token_details['access_token']
37
+
38
+ return token
39
+
40
+ def _fetch_token(self):
41
+ url = f'{self.service_url}/v6.0/account/login'
42
+ payload = {'userEmail': self.email, 'password': self.password}
43
+ response = requests.post(url, json=payload)
44
+ if response.status_code != 200:
45
+ raise Exception(f'Failed to fetch token from CredoLab: {response.text}')
46
+ token_details = response.json()
47
+ self._save_token(token_details)
48
+ return token_details['access_token']
49
+
50
+ def _save_token(self, token: dict) -> None:
51
+
52
+ with self.rep_conn.cursor() as cursor:
53
+ DB.execute(cursor, self.INSERT_TOKEN_QUERY,
54
+ parameters=(token['access_token'], token['token_type'], token['.issued'],
55
+ token['.expires'], token['refresh_token']))
56
+ self.rep_conn.commit()
57
+ return token['access_token']
58
+
59
+ def _refresh_token(self, bearer_token: str, refresh_token: str) -> str:
60
+ url = f'{self.service_url}/v6.0/account/login/refreshToken'
61
+ payload = {'refreshToken': refresh_token}
62
+ headers = {'Authorization': f'Bearer {bearer_token}'}
63
+ response = requests.post(url, json=payload)
64
+ if response.status_code != 200:
65
+ raise Exception(f'Failed to refresh token from CredoLab: {response.text}')
66
+ token_details = response.json()
67
+ self._save_token(token_details)
68
+ return token_details['access_token']
@@ -0,0 +1,93 @@
1
+ from os import getenv
2
+ import pyodbc
3
+ from pyodbc import Cursor
4
+
5
+
6
+ class DB:
7
+ __connections = {}
8
+
9
+ @classmethod
10
+ def get_connection(cls, db_name: str):
11
+ if cls.__connections.get(db_name):
12
+ return cls.__connections.get(db_name)
13
+ return cls._create_connection(db_name)
14
+
15
+ @classmethod
16
+ def _create_connection(cls, db_name):
17
+ prefix = db_name
18
+
19
+ dsn = getenv(prefix + '_dsn')
20
+
21
+ driver = getenv(prefix + '_driver')
22
+ database = getenv(prefix + '_db')
23
+ user = getenv(prefix + '_user')
24
+ password = getenv(prefix + '_pass')
25
+ server = getenv(prefix + '_host')
26
+ port = getenv(prefix + '_port')
27
+
28
+ if dsn:
29
+ conn_str = f"DSN={dsn}"
30
+ elif prefix and driver and driver and user and password and server and port:
31
+ conn_str = (
32
+ f"DRIVER={driver};"
33
+ f"DATABASE={database};"
34
+ f"UID={user};"
35
+ f"PWD={password};"
36
+ f"SERVER={server};"
37
+ f"PORT={port};"
38
+ )
39
+ else:
40
+ raise ValueError(f"DB configurations could not be found for {db_name}")
41
+
42
+ conn = pyodbc.connect(conn_str)
43
+ cls.__connections[db_name] = conn
44
+ return conn
45
+
46
+ @classmethod
47
+ def close_connections(cls):
48
+ # print("Connections are closed")
49
+ for key in cls.__connections:
50
+ cls.__connections[key].close()
51
+ cls.__connections = {}
52
+
53
+ @staticmethod
54
+ def execute(cursor: Cursor, query: str, fetch=None, parameters=None, commit: bool=False):
55
+ # Execute the query
56
+ if parameters:
57
+ cursor.execute(query, parameters)
58
+ else:
59
+ cursor.execute(query)
60
+ if commit:
61
+ cursor.connection.commit()
62
+
63
+ if cursor.rowcount and fetch:
64
+ # Fetch the rows
65
+ if fetch == 'all':
66
+ rows = cursor.fetchall()
67
+ else:
68
+ rows = cursor.fetchone()
69
+
70
+ index_map = {index: column[0] for index, column in enumerate(cursor.description)}
71
+ if type(rows) == list:
72
+ dict_rows = []
73
+ for row in rows:
74
+ new_row = {index_map[index]: value for index, value in enumerate(row)}
75
+ dict_rows.append(new_row)
76
+ return dict_rows
77
+ else:
78
+ return {index_map[index]: value for index, value in enumerate(rows)}
79
+
80
+
81
+ def sql(sql_name, sql_version, sql_custom_folder=None):
82
+ if sql_custom_folder:
83
+ afg_sqls_folder = sql_custom_folder
84
+ else:
85
+ afg_sqls_folder = getenv("afg_sqls_folder")
86
+
87
+ sql_filename = afg_sqls_folder + sql_name + "/" + sql_name + "." + str(sql_version).zfill(3) + ".sql"
88
+
89
+ sql_file = open(sql_filename, "r")
90
+ sql_text = sql_file.read()
91
+ sql_file.close()
92
+
93
+ return sql_text
@@ -0,0 +1,23 @@
1
+ from os import getenv
2
+ import win32com.client as win32
3
+
4
+
5
+ def send_email(to: str = None, subject: str = None, message: str = "Empty Message"):
6
+ # initial check
7
+ if to is None and getenv('mail_to') is None:
8
+ raise Exception("to should be passed as parameter or defined as env variable as mail_to")
9
+ if to is None and getenv('mail_to'):
10
+ to = getenv('mail_to')
11
+ outlook = win32.Dispatch('outlook.application')
12
+ mail = outlook.CreateItem(0)
13
+ mail.To = to
14
+ mail.Body = message
15
+
16
+ # To send html content
17
+ # mail.HTMLBody = '<h2>HTML Message body</h2>'
18
+
19
+ # To attach a file to the email (optional):
20
+ # attachment = "Path to the attachment"
21
+ # mail.Attachments.Add(attachment)
22
+
23
+ mail.Send()
@@ -0,0 +1,41 @@
1
+ import boto3
2
+ from os import getenv as _getenv
3
+
4
+
5
+ def save_to_s3(s3_key: str, content_body: str, aws_access_key_id=None,
6
+ aws_secret_access_key=None, aws_region=None, s3_bucket=None):
7
+ if aws_access_key_id is None: aws_access_key_id = _getenv('aws_access_key_id')
8
+ if aws_secret_access_key is None: aws_secret_access_key = _getenv('aws_secret_access_key')
9
+ if aws_region is None: aws_region = _getenv('aws_region')
10
+ if s3_bucket is None: s3_bucket = _getenv('s3_bucket_risk')
11
+
12
+ s3_client = boto3.client('s3'
13
+ , aws_access_key_id=aws_access_key_id
14
+ , aws_secret_access_key=aws_secret_access_key
15
+ , region_name=aws_region)
16
+ s3_client.put_object(Bucket=s3_bucket
17
+ , Body=content_body
18
+ , Key=s3_key
19
+ )
20
+
21
+
22
+ def get_from_s3(s3_key, filename, aws_access_key_id=None, aws_secret_access_key=None, aws_region=None, s3_bucket=None):
23
+ if aws_access_key_id is None: aws_access_key_id = _getenv('aws_access_key_id')
24
+ if aws_secret_access_key is None: aws_secret_access_key = _getenv('aws_secret_access_key')
25
+ if aws_region is None: aws_region = _getenv('aws_region_name')
26
+ if s3_bucket is None: s3_bucket = _getenv('s3_bucket_lms')
27
+
28
+ s3_client = boto3.client(
29
+ 's3',
30
+ aws_access_key_id=aws_access_key_id,
31
+ aws_secret_access_key=aws_secret_access_key,
32
+ region_name=aws_region
33
+ )
34
+
35
+ s3_result = s3_client.download_file(
36
+ Bucket=s3_bucket,
37
+ Key=s3_key,
38
+ Filename=filename
39
+ )
40
+
41
+ return s3_result
@@ -0,0 +1,134 @@
1
+ import random
2
+ from .db import DB, sql
3
+ from os import getenv
4
+ import requests
5
+ import string
6
+
7
+ trandata_auth_url = "https://api.trandata.io/account/unauth/v1/login"
8
+
9
+
10
+ # VARS INIT
11
+ # trandata_select_token_query = """
12
+ # SELECT token from kr.token
13
+ # WHERE created_at > now() - interval '23 HOURS'
14
+ # ORDER BY created_at desc limit 1
15
+ # """
16
+ #
17
+ # trandata_insert_token_query = """INSERT INTO kr.token(token) VALUES (?)"""
18
+ # trandata_lms_query = """
19
+ # SELECT lar.client_id,
20
+ # c.firstname AS client_firstname,
21
+ # c.lastname AS client_lastname,
22
+ # c.date_of_birth AS client_dob,
23
+ # c.mobile_no AS client_mobile,
24
+ # c.email_id AS client_email,
25
+ # ci.document_key AS pan_number,
26
+ # fd.firstname AS client_father_name,
27
+ # ad.address_line_one AS client_res_address,
28
+ # ad.district_name AS client_res_vilage,
29
+ # ad.district_name AS client_res_city,
30
+ # 'xxx' AS client_res_state,
31
+ # ad.postal_code AS client_res_pincode,
32
+ # 'vietnam' AS client_res_country,
33
+ # bad.account_number AS client_bank_acc,
34
+ # ci.document_key as national_id
35
+ # FROM f_loan_application_reference lar
36
+ # LEFT JOIN f_bank_account_detail_associations bada ON bada.entity_id = lar.client_id AND bada.entity_type_enum = 1
37
+ # LEFT JOIN f_bank_account_details bad ON bad.id = bada.bank_account_detail_id -- bank account
38
+ # LEFT JOIN m_client c ON c.id = lar.client_id
39
+ # LEFT JOIN m_client_identifier ci ON ci.client_id = c.id AND ci.document_type_id = 303
40
+ # LEFT JOIN f_address_entity ae ON ae.entity_id = c.id AND ae.entity_type_enum =1 AND ae.address_type = 14
41
+ # LEFT JOIN f_address ad ON ad.id = ae.address_id
42
+ # LEFT JOIN f_family_details as fd ON fd.client_id = c.id AND fd.relationship_cv_id = 20
43
+ # WHERE lar.loan_application_reference_no=? LIMIT 1
44
+ # """
45
+
46
+ # MAIN CODE
47
+
48
+ # SQLS
49
+
50
+
51
+
52
+ # FUNCS
53
+
54
+
55
+
56
+
57
+ # Trandata
58
+ def trandata_update_token():
59
+ """
60
+ Get log in and parse the Authentication Token.
61
+ :return: token
62
+ """
63
+
64
+ rep_conn = DB.get_connection('repserv')
65
+ rep_cursor = rep_conn.cursor()
66
+
67
+ username = getenv("trandata_username")
68
+ password = getenv("trandata_password")
69
+
70
+ url = trandata_auth_url
71
+ payload = "{\n \"username\": \"" + username + "\",\n \"password\": \"" + password + "\"\n}"
72
+ headers = {
73
+ 'Content-Type': 'application/json'
74
+ }
75
+
76
+ response = requests.request("POST", url, headers=headers, data=payload)
77
+
78
+ if 300 > response.status_code >= 200:
79
+ data = response.json()
80
+ token = data["Data"]["sessionToken"]
81
+
82
+ DB.execute(
83
+ cursor=rep_cursor,
84
+ query=sql("trandata_insert_token_query", 1),
85
+ parameters=(token,)
86
+ )
87
+
88
+ rep_cursor.commit()
89
+ rep_cursor.close()
90
+
91
+ return token
92
+
93
+ raise ValueError(response.text)
94
+
95
+
96
+ def trandata_get_token():
97
+ rep_conn = DB.get_connection('repserv')
98
+ rep_cursor = rep_conn.cursor()
99
+
100
+ token_res = DB.execute(
101
+ cursor=rep_cursor,
102
+ query=sql("trandata_select_token_query", 1),
103
+ fetch='one'
104
+ )
105
+
106
+ rep_cursor.close()
107
+
108
+ if not token_res:
109
+ token = trandata_update_token()
110
+
111
+ return token
112
+
113
+ return token_res["token"]
114
+
115
+
116
+ # print(trandata_get_token())
117
+
118
+ def trandata_gen_req_id(appl_ref):
119
+ ran_text = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
120
+ result = appl_ref + "_" + ran_text
121
+ return result
122
+
123
+ # def trandata_save2boto3(acc_key_id, sec_acc_key, region, response_text, bucket, key):
124
+ # s3_client = boto3.client(
125
+ # 's3',
126
+ # aws_access_key_id=acc_key_id,
127
+ # aws_secret_access_key=sec_acc_key,
128
+ # region_name=region
129
+ # )
130
+ # s3_client.put_object(
131
+ # Bucket=bucket,
132
+ # Body=response_text,
133
+ # Key=key
134
+ # )
@@ -0,0 +1,132 @@
1
+ import time
2
+ from os import environ
3
+ from .db import DB, sql
4
+ from pandas import json_normalize
5
+ from pandas import DataFrame
6
+
7
+ session_token_cookie_name = "uwtool_session_token"
8
+
9
+
10
+ def print_log(log_line):
11
+ print(time.strftime('%Y-%m-%d %H:%M:%S'), log_line)
12
+
13
+
14
+ def ci(cursor, column_name):
15
+ column_index_cnt = -1
16
+ column_index = -1
17
+ for column in cursor.description:
18
+ column_index_cnt = column_index_cnt + 1
19
+ if column[0] == column_name:
20
+ column_index = column_index_cnt
21
+ break
22
+ if column_index == -1:
23
+ raise SystemExit("ERROR: Column index not found for column: " + column_name)
24
+ return column_index
25
+
26
+
27
+ def nvl(s, d):
28
+ if s is None:
29
+ return d
30
+ else:
31
+ return s
32
+
33
+
34
+ def iif(bool_val, ret_true, ret_false):
35
+ if bool_val:
36
+ aaa = ret_true
37
+ else:
38
+ aaa = ret_false
39
+ return aaa
40
+
41
+
42
+ def isnullorempty(v):
43
+ if v is None:
44
+ return True
45
+ if type(v) == str:
46
+ v = v.strip()
47
+ if len(v) == 0:
48
+ return True
49
+ return False
50
+
51
+
52
+ def clear_mfv(mfv):
53
+ if type(mfv) == dict:
54
+ for i in mfv.keys():
55
+ mfv[i] = None
56
+ return mfv
57
+
58
+
59
+ def get_username():
60
+ conn_repserv = DB.get_connection('repserv')
61
+ cursor_repserv = conn_repserv.cursor()
62
+
63
+ username = None
64
+
65
+ if 'HTTP_COOKIE' in environ:
66
+ for cookie in map(str.strip, environ['HTTP_COOKIE'].split(';')):
67
+ key, value = cookie.split('=')
68
+ if key == session_token_cookie_name:
69
+ uwtool_session_token = value
70
+ result = DB.execute(cursor=cursor_repserv,
71
+ query=sql("find_session", 2),
72
+ fetch='one',
73
+ parameters=uwtool_session_token)
74
+ if result: # can be replaced by "if cursor_repserv.rowcount"
75
+ username = result['username']
76
+ DB.execute(cursor_repserv, sql("update_session", 1), None, (uwtool_session_token, username))
77
+ cursor_repserv.commit()
78
+
79
+ cursor_repserv.close()
80
+
81
+ return username
82
+
83
+
84
+ def data_vector_to_sql_insert(data_vector: dict | DataFrame, insert_sql_tpl: str = None,
85
+ existing_values: list = None) -> (list, str):
86
+ column_names_insert_text = ""
87
+ item_values = []
88
+
89
+ # convert data vector into a list
90
+ if isinstance(data_vector, DataFrame):
91
+ for column_name in data_vector.columns.tolist():
92
+ column_names_insert_text = (column_names_insert_text + "," + column_name + "\r\n").replace(".", "_")
93
+ item_values.append(data_vector.loc[0, column_name])
94
+
95
+ elif type(data_vector) is dict:
96
+ pass
97
+
98
+ else:
99
+ raise TypeError("data_vector_to_sql_insert: data_vector must be a pandas.DataFrame or a dictionary")
100
+
101
+ # text-serialize any embedded lists or dictionaries
102
+ item_values = [str(item_value) if type(item_value) in (list, dict) else item_value for item_value in item_values]
103
+ # convert any string booleans to actual booleans
104
+ item_values = [_convert_string_to_boolean(item_value) if type(item_value) is str else item_value for item_value in
105
+ item_values]
106
+
107
+ # combine existing values with new values
108
+ if type(existing_values) is list:
109
+ result_items = existing_values + item_values
110
+ elif existing_values is None:
111
+ result_items = item_values
112
+ else:
113
+ raise TypeError("data_vector_to_sql_insert: existing_values must be a list or None")
114
+
115
+ # generate SQL
116
+ if type(insert_sql_tpl) is str:
117
+ result_sql = insert_sql_tpl.format(column_names_insert_text, f"{', ?' * len(item_values)}")
118
+ elif insert_sql_tpl is None:
119
+ result_sql = column_names_insert_text
120
+ else:
121
+ raise TypeError("data_vector_to_sql_insert: insert_sql_tpl must be a string or None")
122
+
123
+ return result_items, result_sql
124
+
125
+
126
+ def _convert_string_to_boolean(s: str) -> bool:
127
+ if s.lower() in ('true', 't', '1', 'y', 'yes'):
128
+ return True
129
+ elif s.lower() in ('false', 'f', '0', 'n', 'no'):
130
+ return False
131
+ else:
132
+ return s
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.1
2
+ Name: afgutils
3
+ Version: 1.0.6
4
+ Summary: Shared support functions
5
+ Author: Araz Heydarov
6
+ Author-email: arazheyderov@gmail.com
7
+ License: copyright
8
+ Requires-Python: >=3.10
9
+
10
+ Shared support functions
@@ -0,0 +1,14 @@
1
+ setup.py
2
+ afgutils/__init__.py
3
+ afgutils/advanceai_util.py
4
+ afgutils/credolab.py
5
+ afgutils/db.py
6
+ afgutils/email.py
7
+ afgutils/s3.py
8
+ afgutils/trandata.py
9
+ afgutils/utils.py
10
+ afgutils.egg-info/PKG-INFO
11
+ afgutils.egg-info/SOURCES.txt
12
+ afgutils.egg-info/dependency_links.txt
13
+ afgutils.egg-info/requires.txt
14
+ afgutils.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ requests==2.31.0
2
+ pyodbc==4.0.39
3
+ pywin32==306
4
+ python-dotenv==1.0.0
5
+ boto3==1.27.0
6
+ pandas==2.0.3
@@ -0,0 +1 @@
1
+ afgutils
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,24 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ VERSION = '1.0.6'
4
+ DESCRIPTION = 'Shared support functions'
5
+ LONG_DESCRIPTION = 'Shared support functions'
6
+
7
+ setup(
8
+ name="afgutils",
9
+ version=VERSION,
10
+ description=DESCRIPTION,
11
+ long_description=LONG_DESCRIPTION,
12
+ author="Araz Heydarov",
13
+ author_email="arazheyderov@gmail.com",
14
+ python_requires='>=3.10',
15
+ license='copyright',
16
+ packages=find_packages(),
17
+ install_requires=[
18
+ 'requests==2.31.0',
19
+ 'pyodbc==4.0.39',
20
+ 'pywin32==306',
21
+ 'python-dotenv==1.0.0',
22
+ 'boto3==1.27.0',
23
+ 'pandas==2.0.3']
24
+ )