wcp-library 1.0.0__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.
- wcp_library/__init__.py +14 -0
- wcp_library/async_credentials/__init__.py +49 -0
- wcp_library/async_credentials/api.py +0 -0
- wcp_library/async_credentials/oracle.py +131 -0
- wcp_library/async_credentials/postgres.py +130 -0
- wcp_library/async_sql/__init__.py +35 -0
- wcp_library/async_sql/oracle.py +242 -0
- wcp_library/async_sql/postgres.py +217 -0
- wcp_library/credentials/__init__.py +49 -0
- wcp_library/credentials/api.py +0 -0
- wcp_library/credentials/oracle.py +125 -0
- wcp_library/credentials/postgres.py +124 -0
- wcp_library/emailing.py +90 -0
- wcp_library/informatica.py +112 -0
- wcp_library/logging.py +51 -0
- wcp_library/sql/__init__.py +35 -0
- wcp_library/sql/oracle.py +249 -0
- wcp_library/sql/postgres.py +226 -0
- wcp_library-1.0.0.dist-info/METADATA +46 -0
- wcp_library-1.0.0.dist-info/RECORD +21 -0
- wcp_library-1.0.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,217 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
import pandas as pd
|
5
|
+
from psycopg.sql import SQL
|
6
|
+
from psycopg_pool import AsyncConnectionPool
|
7
|
+
|
8
|
+
from WCP_Library.async_sql import retry
|
9
|
+
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
|
13
|
+
async def connect_warehouse(username: str, password: str, hostname: str, port: int, database: str) -> AsyncConnectionPool:
|
14
|
+
"""
|
15
|
+
Create Warehouse Connection
|
16
|
+
|
17
|
+
:param username: username
|
18
|
+
:param password: password
|
19
|
+
:param hostname: hostname
|
20
|
+
:param port: port
|
21
|
+
:param database: database
|
22
|
+
:return: session_pool
|
23
|
+
"""
|
24
|
+
|
25
|
+
url = f"postgres://{username}:{password}@{hostname}:{port}/{database}"
|
26
|
+
|
27
|
+
session_pool = AsyncConnectionPool(
|
28
|
+
conninfo=url,
|
29
|
+
min_size=2,
|
30
|
+
max_size=5,
|
31
|
+
)
|
32
|
+
await session_pool.open()
|
33
|
+
return session_pool
|
34
|
+
|
35
|
+
|
36
|
+
class SQLConnection(object):
|
37
|
+
"""
|
38
|
+
SQL Connection Class
|
39
|
+
|
40
|
+
:return: None
|
41
|
+
"""
|
42
|
+
|
43
|
+
def __init__(self):
|
44
|
+
self._username: Optional[str] = None
|
45
|
+
self._password: Optional[str] = None
|
46
|
+
self._hostname: Optional[str] = None
|
47
|
+
self._port: Optional[int] = None
|
48
|
+
self._database: Optional[str] = None
|
49
|
+
self._session_pool: Optional[AsyncConnectionPool] = None
|
50
|
+
|
51
|
+
self._retry_count = 0
|
52
|
+
self.retry_limit = 50
|
53
|
+
self.retry_error_codes = ['08001', '08004']
|
54
|
+
|
55
|
+
@retry
|
56
|
+
async def _connect(self) -> None:
|
57
|
+
"""
|
58
|
+
Connect to the warehouse
|
59
|
+
|
60
|
+
:return: None
|
61
|
+
"""
|
62
|
+
|
63
|
+
self._session_pool = await connect_warehouse(self._username, self._password, self._hostname, self._port, self._database)
|
64
|
+
|
65
|
+
async def set_user(self, credentials_dict: dict) -> None:
|
66
|
+
"""
|
67
|
+
Set the user credentials and connect
|
68
|
+
|
69
|
+
:param credentials_dict: dictionary of connection details
|
70
|
+
:return: None
|
71
|
+
"""
|
72
|
+
|
73
|
+
self._username: Optional[str] = credentials_dict['UserName']
|
74
|
+
self._password: Optional[str] = credentials_dict['Password']
|
75
|
+
self._hostname: Optional[str] = credentials_dict['Host']
|
76
|
+
self._port: Optional[int] = int(credentials_dict['Port'])
|
77
|
+
self._database: Optional[str] = credentials_dict['Database']
|
78
|
+
|
79
|
+
await self._connect()
|
80
|
+
|
81
|
+
async def close_connection(self) -> None:
|
82
|
+
"""
|
83
|
+
Close the connection
|
84
|
+
|
85
|
+
:return: None
|
86
|
+
"""
|
87
|
+
|
88
|
+
await self._session_pool.close()
|
89
|
+
|
90
|
+
@retry
|
91
|
+
async def execute(self, query: SQL | str) -> None:
|
92
|
+
"""
|
93
|
+
Execute the query
|
94
|
+
|
95
|
+
:param query: query
|
96
|
+
:return: None
|
97
|
+
"""
|
98
|
+
|
99
|
+
async with self._session_pool.connection() as connection:
|
100
|
+
await connection.execute(query)
|
101
|
+
|
102
|
+
@retry
|
103
|
+
async def safe_execute(self, query: SQL | str, packed_values: dict) -> None:
|
104
|
+
"""
|
105
|
+
Execute the query without SQL Injection possibility, to be used with external facing projects.
|
106
|
+
|
107
|
+
:param query: query
|
108
|
+
:param packed_values: dictionary of values
|
109
|
+
:return: None
|
110
|
+
"""
|
111
|
+
|
112
|
+
async with self._session_pool.connection() as connection:
|
113
|
+
await connection.execute(query, packed_values)
|
114
|
+
|
115
|
+
@retry
|
116
|
+
async def execute_multiple(self, queries: list[list[SQL | str, dict]]) -> None:
|
117
|
+
"""
|
118
|
+
Execute multiple queries
|
119
|
+
|
120
|
+
:param queries: list of queries
|
121
|
+
:return: None
|
122
|
+
"""
|
123
|
+
|
124
|
+
async with self._session_pool.connection() as connection:
|
125
|
+
for item in queries:
|
126
|
+
query = item[0]
|
127
|
+
packed_values = item[1]
|
128
|
+
if packed_values:
|
129
|
+
await connection.execute(query, packed_values)
|
130
|
+
else:
|
131
|
+
await connection.execute(query)
|
132
|
+
|
133
|
+
@retry
|
134
|
+
async def execute_many(self, query: SQL | str, dictionary: list[dict]) -> None:
|
135
|
+
"""
|
136
|
+
Execute many queries
|
137
|
+
|
138
|
+
:param query: query
|
139
|
+
:param dictionary: dictionary of values
|
140
|
+
:return: None
|
141
|
+
"""
|
142
|
+
|
143
|
+
async with self._session_pool.connection() as connection:
|
144
|
+
await connection.executemany(query, dictionary)
|
145
|
+
|
146
|
+
@retry
|
147
|
+
async def fetch_data(self, query: SQL | str, packed_data=None):
|
148
|
+
"""
|
149
|
+
Fetch the data from the query
|
150
|
+
|
151
|
+
:param query: query
|
152
|
+
:param packed_data: packed data
|
153
|
+
:return: rows
|
154
|
+
"""
|
155
|
+
|
156
|
+
async with self._session_pool.connection() as connection:
|
157
|
+
cursor = connection.cursor()
|
158
|
+
if packed_data:
|
159
|
+
await cursor.execute(query, packed_data)
|
160
|
+
else:
|
161
|
+
await cursor.execute(query)
|
162
|
+
rows = await cursor.fetchall()
|
163
|
+
return rows
|
164
|
+
|
165
|
+
@retry
|
166
|
+
async def export_DF_to_warehouse(self, dfObj: pd.DataFrame, outputTableName: str, columns: list, remove_nan=False) -> None:
|
167
|
+
"""
|
168
|
+
Export the DataFrame to the warehouse
|
169
|
+
|
170
|
+
:param dfObj: DataFrame
|
171
|
+
:param outputTableName: output table name
|
172
|
+
:param columns: list of columns
|
173
|
+
:param remove_nan: remove NaN values
|
174
|
+
:return: None
|
175
|
+
"""
|
176
|
+
|
177
|
+
col = ', '.join(columns)
|
178
|
+
param_list = []
|
179
|
+
for column in columns:
|
180
|
+
param_list.append(f"%({column})s")
|
181
|
+
params = ', '.join(param_list)
|
182
|
+
|
183
|
+
main_dict = dfObj.to_dict('records')
|
184
|
+
if remove_nan:
|
185
|
+
for val, item in enumerate(main_dict):
|
186
|
+
for sub_item, value in item.items():
|
187
|
+
if pd.isna(value):
|
188
|
+
main_dict[val][sub_item] = None
|
189
|
+
else:
|
190
|
+
main_dict[val][sub_item] = value
|
191
|
+
|
192
|
+
query = """INSERT INTO {} ({}) VALUES ({})""".format(outputTableName, col, params)
|
193
|
+
await self.execute_many(query, main_dict)
|
194
|
+
|
195
|
+
@retry
|
196
|
+
async def truncate_table(self, tableName: str) -> None:
|
197
|
+
"""
|
198
|
+
Truncate the table
|
199
|
+
|
200
|
+
:param tableName: table name
|
201
|
+
:return: None
|
202
|
+
"""
|
203
|
+
|
204
|
+
truncateQuery = """TRUNCATE TABLE {}""".format(tableName)
|
205
|
+
await self.execute(truncateQuery)
|
206
|
+
|
207
|
+
@retry
|
208
|
+
async def empty_table(self, tableName: str) -> None:
|
209
|
+
"""
|
210
|
+
Empty the table
|
211
|
+
|
212
|
+
:param tableName: table name
|
213
|
+
:return: None
|
214
|
+
"""
|
215
|
+
|
216
|
+
deleteQuery = """DELETE FROM {}""".format(tableName)
|
217
|
+
await self.execute(deleteQuery)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import secrets
|
2
|
+
import string
|
3
|
+
|
4
|
+
|
5
|
+
class MissingCredentialsError(KeyError):
|
6
|
+
pass
|
7
|
+
|
8
|
+
|
9
|
+
def generate_password(length: int=12, use_nums: bool=True, use_special: bool=True, special_chars_override: list=None, force_num: bool=True, force_spec: bool=True) -> str:
|
10
|
+
"""
|
11
|
+
Function to generate a random password
|
12
|
+
|
13
|
+
:param length:
|
14
|
+
:param use_nums: Allows the use of numbers
|
15
|
+
:param use_special: Allows the use of special characters
|
16
|
+
:param special_chars_override: List of special characters to use
|
17
|
+
:param force_num: Requires the password to contain at least one number
|
18
|
+
:param force_spec: Requires the password to contain at least one special character
|
19
|
+
:return: Password
|
20
|
+
"""
|
21
|
+
|
22
|
+
letters = string.ascii_letters
|
23
|
+
digits = string.digits
|
24
|
+
if special_chars_override:
|
25
|
+
special_chars = special_chars_override
|
26
|
+
else:
|
27
|
+
special_chars = string.punctuation
|
28
|
+
|
29
|
+
alphabet = letters
|
30
|
+
if use_nums:
|
31
|
+
alphabet += digits
|
32
|
+
if use_special:
|
33
|
+
alphabet += special_chars
|
34
|
+
|
35
|
+
pwd = ''
|
36
|
+
for i in range(length):
|
37
|
+
pwd += ''.join(secrets.choice(alphabet))
|
38
|
+
|
39
|
+
if (use_nums and force_num) and (use_special and force_spec):
|
40
|
+
while pwd[0].isdigit() or not any(char.isdigit() for char in pwd) or not any(char in pwd for char in special_chars):
|
41
|
+
pwd = generate_password(length, use_nums, use_special, special_chars_override, force_num, force_spec)
|
42
|
+
elif use_nums and force_num:
|
43
|
+
while pwd[0].isdigit() or not any(char.isdigit() for char in pwd):
|
44
|
+
pwd = generate_password(length, use_nums, use_special, special_chars_override, force_num, force_spec)
|
45
|
+
elif use_special and force_spec:
|
46
|
+
while not any(char in pwd for char in special_chars):
|
47
|
+
pwd = generate_password(length, use_nums, use_special, special_chars_override, force_num, force_spec)
|
48
|
+
|
49
|
+
return pwd
|
File without changes
|
@@ -0,0 +1,125 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from yarl import URL
|
5
|
+
|
6
|
+
from WCP_Library.credentials import MissingCredentialsError
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
|
11
|
+
class OracleCredentialManager:
|
12
|
+
def __init__(self, passwordState_api_key: str):
|
13
|
+
self.password_url = URL("https://vault.wcap.ca/api/passwords/")
|
14
|
+
self.api_key = passwordState_api_key
|
15
|
+
self.headers = {"APIKey": self.api_key, 'Reason': 'Python Script Access'}
|
16
|
+
self._password_list_id = 207
|
17
|
+
|
18
|
+
def _get_credentials(self) -> dict:
|
19
|
+
"""
|
20
|
+
Get all credentials from the password list
|
21
|
+
|
22
|
+
:return: Dictionary of credentials
|
23
|
+
"""
|
24
|
+
|
25
|
+
logger.debug("Getting credentials from PasswordState")
|
26
|
+
url = (self.password_url / str(self._password_list_id)).with_query("QueryAll")
|
27
|
+
passwords = requests.get(str(url), headers=self.headers).json()
|
28
|
+
|
29
|
+
if not passwords:
|
30
|
+
raise MissingCredentialsError("No credentials found in this Password List")
|
31
|
+
|
32
|
+
password_dict = {}
|
33
|
+
for password in passwords:
|
34
|
+
password_info = {'PasswordID': password['PasswordID'], 'UserName': password['UserName'], 'Password': password['Password']}
|
35
|
+
for field in password['GenericFieldInfo']:
|
36
|
+
password_info[field['DisplayName']] = field['Value'].lower() if field['DisplayName'].lower() == 'username' else field['Value']
|
37
|
+
password_dict[password['UserName'].lower()] = password_info
|
38
|
+
logger.debug("Credentials retrieved")
|
39
|
+
return password_dict
|
40
|
+
|
41
|
+
def get_credentials(self, username: str) -> dict:
|
42
|
+
"""
|
43
|
+
Get the credentials for a specific username
|
44
|
+
|
45
|
+
:param username:
|
46
|
+
:return: Dictionary of credentials
|
47
|
+
"""
|
48
|
+
|
49
|
+
logger.debug(f"Getting credentials for {username}")
|
50
|
+
credentials = self._get_credentials()
|
51
|
+
|
52
|
+
try:
|
53
|
+
return_credential = credentials[username.lower()]
|
54
|
+
except KeyError:
|
55
|
+
raise MissingCredentialsError(f"Credentials for {username} not found in this Password List")
|
56
|
+
logger.debug(f"Credentials for {username} retrieved")
|
57
|
+
return return_credential
|
58
|
+
|
59
|
+
def update_credential(self, credentials_dict: dict) -> bool:
|
60
|
+
"""
|
61
|
+
Update the credentials for a specific username
|
62
|
+
|
63
|
+
Credentials dictionary must have the following keys:
|
64
|
+
- PasswordID
|
65
|
+
- UserName
|
66
|
+
- Password
|
67
|
+
|
68
|
+
The dictionary should be obtained from the get_credentials method and modified accordingly
|
69
|
+
|
70
|
+
:param credentials_dict:
|
71
|
+
:return: True if successful, False otherwise
|
72
|
+
"""
|
73
|
+
|
74
|
+
logger.debug(f"Updating credentials for {credentials_dict['UserName']}")
|
75
|
+
url = (self.password_url / str(self._password_list_id)).with_query("QueryAll")
|
76
|
+
passwords = requests.get(str(url), headers=self.headers).json()
|
77
|
+
|
78
|
+
relevant_credential_entry = [x for x in passwords if x['UserName'] == credentials_dict['UserName']][0]
|
79
|
+
for field in relevant_credential_entry['GenericFieldInfo']:
|
80
|
+
if field['DisplayName'] in credentials_dict:
|
81
|
+
credentials_dict[field['GenericFieldID']] = credentials_dict[field['DisplayName']]
|
82
|
+
credentials_dict.pop(field['DisplayName'])
|
83
|
+
|
84
|
+
response = requests.put(str(self.password_url), json=credentials_dict, headers=self.headers)
|
85
|
+
if response.status_code == 200:
|
86
|
+
logger.debug(f"Credentials for {credentials_dict['UserName']} updated")
|
87
|
+
return True
|
88
|
+
else:
|
89
|
+
logger.error(f"Failed to update credentials for {credentials_dict['UserName']}")
|
90
|
+
return False
|
91
|
+
|
92
|
+
def new_credentials(self, credentials_dict: dict) -> bool:
|
93
|
+
"""
|
94
|
+
Create a new credential entry
|
95
|
+
|
96
|
+
Credentials dictionary must have the following keys:
|
97
|
+
- UserName
|
98
|
+
- Password
|
99
|
+
- Host
|
100
|
+
- Port
|
101
|
+
- Service or SID
|
102
|
+
|
103
|
+
:param credentials_dict:
|
104
|
+
:return: True if successful, False otherwise
|
105
|
+
"""
|
106
|
+
|
107
|
+
data = {
|
108
|
+
"PasswordListID": self._password_list_id,
|
109
|
+
"Title": credentials_dict['UserName'].upper() if "Title" not in credentials_dict else credentials_dict['Title'].upper(),
|
110
|
+
"Notes": credentials_dict['Notes'] if 'Notes' in credentials_dict else None,
|
111
|
+
"UserName": credentials_dict['UserName'].lower(),
|
112
|
+
"Password": credentials_dict['Password'],
|
113
|
+
"GenericField1": credentials_dict['Host'],
|
114
|
+
"GenericField2": credentials_dict['Port'],
|
115
|
+
"GenericField3": credentials_dict['Service'] if 'Service' in credentials_dict else None,
|
116
|
+
"GenericField4": credentials_dict['SID'] if 'SID' in credentials_dict else None
|
117
|
+
}
|
118
|
+
|
119
|
+
response = requests.post(str(self.password_url), json=data, headers=self.headers)
|
120
|
+
if response.status_code == 201:
|
121
|
+
logger.debug(f"New credentials for {credentials_dict['UserName']} created")
|
122
|
+
return True
|
123
|
+
else:
|
124
|
+
logger.error(f"Failed to create new credentials for {credentials_dict['UserName']}")
|
125
|
+
return False
|
@@ -0,0 +1,124 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from yarl import URL
|
5
|
+
|
6
|
+
from WCP_Library.credentials import MissingCredentialsError
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
|
11
|
+
class PostgresCredentialManager:
|
12
|
+
def __init__(self, passwordState_api_key: str):
|
13
|
+
self.password_url = URL("https://vault.wcap.ca/api/passwords/")
|
14
|
+
self.api_key = passwordState_api_key
|
15
|
+
self.headers = {"APIKey": self.api_key, 'Reason': 'Python Script Access'}
|
16
|
+
self._password_list_id = 207
|
17
|
+
|
18
|
+
def _get_credentials(self) -> dict:
|
19
|
+
"""
|
20
|
+
Get all credentials from the password list
|
21
|
+
|
22
|
+
:return: Dictionary of credentials
|
23
|
+
"""
|
24
|
+
|
25
|
+
logger.debug("Getting credentials from PasswordState")
|
26
|
+
url = (self.password_url / str(self._password_list_id)).with_query("QueryAll")
|
27
|
+
passwords = requests.get(str(url), headers=self.headers).json()
|
28
|
+
|
29
|
+
if not passwords:
|
30
|
+
raise MissingCredentialsError("No credentials found in this Password List")
|
31
|
+
|
32
|
+
password_dict = {}
|
33
|
+
for password in passwords:
|
34
|
+
password_info = {'PasswordID': password['PasswordID'], 'UserName': password['UserName'], 'Password': password['Password']}
|
35
|
+
for field in password['GenericFieldInfo']:
|
36
|
+
password_info[field['DisplayName']] = field['Value'].lower() if field['DisplayName'].lower() == 'username' else field['Value']
|
37
|
+
password_dict[password['UserName'].lower()] = password_info
|
38
|
+
logger.debug("Credentials retrieved")
|
39
|
+
return password_dict
|
40
|
+
|
41
|
+
def get_credentials(self, username: str) -> dict:
|
42
|
+
"""
|
43
|
+
Get the credentials for a specific username
|
44
|
+
|
45
|
+
:param username:
|
46
|
+
:return: Dictionary of credentials
|
47
|
+
"""
|
48
|
+
|
49
|
+
logger.debug(f"Getting credentials for {username}")
|
50
|
+
credentials = self._get_credentials()
|
51
|
+
|
52
|
+
try:
|
53
|
+
return_credential = credentials[username.lower()]
|
54
|
+
except KeyError:
|
55
|
+
raise MissingCredentialsError(f"Credentials for {username} not found in this Password List")
|
56
|
+
logger.debug(f"Credentials for {username} retrieved")
|
57
|
+
return return_credential
|
58
|
+
|
59
|
+
def update_credential(self, credentials_dict: dict) -> bool:
|
60
|
+
"""
|
61
|
+
Update the credentials for a specific username
|
62
|
+
|
63
|
+
Credentials dictionary must have the following keys:
|
64
|
+
- PasswordID
|
65
|
+
- UserName
|
66
|
+
- Password
|
67
|
+
|
68
|
+
The dictionary should be obtained from the get_credentials method and modified accordingly
|
69
|
+
|
70
|
+
:param credentials_dict:
|
71
|
+
:return: True if successful, False otherwise
|
72
|
+
"""
|
73
|
+
|
74
|
+
logger.debug(f"Updating credentials for {credentials_dict['UserName']}")
|
75
|
+
url = (self.password_url / str(self._password_list_id)).with_query("QueryAll")
|
76
|
+
passwords = requests.get(str(url), headers=self.headers).json()
|
77
|
+
|
78
|
+
relevant_credential_entry = [x for x in passwords if x['UserName'] == credentials_dict['UserName']][0]
|
79
|
+
for field in relevant_credential_entry['GenericFieldInfo']:
|
80
|
+
if field['DisplayName'] in credentials_dict:
|
81
|
+
credentials_dict[field['GenericFieldID']] = credentials_dict[field['DisplayName']]
|
82
|
+
credentials_dict.pop(field['DisplayName'])
|
83
|
+
|
84
|
+
response = requests.put(str(self.password_url), json=credentials_dict, headers=self.headers)
|
85
|
+
if response.status_code == 200:
|
86
|
+
logger.debug(f"Credentials for {credentials_dict['UserName']} updated")
|
87
|
+
return True
|
88
|
+
else:
|
89
|
+
logger.error(f"Failed to update credentials for {credentials_dict['UserName']}")
|
90
|
+
return False
|
91
|
+
|
92
|
+
def new_credentials(self, credentials_dict: dict) -> bool:
|
93
|
+
"""
|
94
|
+
Create a new credential entry
|
95
|
+
|
96
|
+
Credentials dictionary must have the following keys:
|
97
|
+
- UserName
|
98
|
+
- Password
|
99
|
+
- Host
|
100
|
+
- Port
|
101
|
+
- Database
|
102
|
+
|
103
|
+
:param credentials_dict:
|
104
|
+
:return: True if successful, False otherwise
|
105
|
+
"""
|
106
|
+
|
107
|
+
data = {
|
108
|
+
"PasswordListID": self._password_list_id,
|
109
|
+
"Title": credentials_dict['UserName'].upper() if "Title" not in credentials_dict else credentials_dict['Title'].upper(),
|
110
|
+
"Notes": credentials_dict['Notes'] if 'Notes' in credentials_dict else None,
|
111
|
+
"UserName": credentials_dict['UserName'].lower(),
|
112
|
+
"Password": credentials_dict['Password'],
|
113
|
+
"GenericField1": credentials_dict['Host'],
|
114
|
+
"GenericField2": credentials_dict['Port'],
|
115
|
+
"GenericField3": credentials_dict['Database']
|
116
|
+
}
|
117
|
+
|
118
|
+
response = requests.post(str(self.password_url), json=data, headers=self.headers)
|
119
|
+
if response.status_code == 201:
|
120
|
+
logger.debug(f"New credentials for {credentials_dict['UserName']} created")
|
121
|
+
return True
|
122
|
+
else:
|
123
|
+
logger.error(f"Failed to create new credentials for {credentials_dict['UserName']}")
|
124
|
+
return False
|
wcp_library/emailing.py
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
import smtplib
|
2
|
+
from email import encoders
|
3
|
+
from email.mime.base import MIMEBase
|
4
|
+
from email.mime.multipart import MIMEMultipart
|
5
|
+
from email.utils import formatdate
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
|
9
|
+
def send_email(sender: str, recipients: list, subject: str, message=None):
|
10
|
+
"""
|
11
|
+
Function to send an email
|
12
|
+
|
13
|
+
:param sender:
|
14
|
+
:param recipients:
|
15
|
+
:param subject:
|
16
|
+
:param message:
|
17
|
+
:return:
|
18
|
+
"""
|
19
|
+
|
20
|
+
msg = MIMEMultipart()
|
21
|
+
msg['From'] = sender
|
22
|
+
msg['To'] = ", ".join(recipients)
|
23
|
+
msg['Date'] = formatdate(localtime=True)
|
24
|
+
msg['Subject'] = subject
|
25
|
+
msg.attach(message)
|
26
|
+
|
27
|
+
smtpServer = 'mail.wcap.ca'
|
28
|
+
server = smtplib.SMTP(smtpServer, 25)
|
29
|
+
server.ehlo()
|
30
|
+
server.sendmail(sender, recipients, msg.as_string())
|
31
|
+
server.quit()
|
32
|
+
|
33
|
+
|
34
|
+
def email_reporting(subject: str, message: str):
|
35
|
+
"""
|
36
|
+
Function to email the reporting team from the Python email
|
37
|
+
|
38
|
+
:param subject:
|
39
|
+
:param message:
|
40
|
+
:return:
|
41
|
+
"""
|
42
|
+
|
43
|
+
msg = MIMEMultipart()
|
44
|
+
msg['From'] = "Python@wcap.ca"
|
45
|
+
msg['To'] = "Reporting@wcap.ca"
|
46
|
+
msg['Date'] = formatdate(localtime=True)
|
47
|
+
msg['Subject'] = subject
|
48
|
+
msg.attach(message)
|
49
|
+
|
50
|
+
smtpServer = 'mail.wcap.ca'
|
51
|
+
server = smtplib.SMTP(smtpServer, 25)
|
52
|
+
server.ehlo()
|
53
|
+
server.sendmail("Python@wcap.ca", 'Reporting@wcap.ca', msg.as_string())
|
54
|
+
server.quit()
|
55
|
+
|
56
|
+
|
57
|
+
def email_with_attachments(sender: str, recipients: list, subject: str, message=None, attachments: list[Path]=None):
|
58
|
+
"""
|
59
|
+
Function to send an email with attachments
|
60
|
+
|
61
|
+
File paths must be passed as a list of Path (pathlib.Path) objects
|
62
|
+
|
63
|
+
:param sender:
|
64
|
+
:param recipients:
|
65
|
+
:param subject:
|
66
|
+
:param message:
|
67
|
+
:param attachments:
|
68
|
+
:return:
|
69
|
+
"""
|
70
|
+
|
71
|
+
msg = MIMEMultipart()
|
72
|
+
msg['From'] = sender
|
73
|
+
msg['To'] = ", ".join(recipients)
|
74
|
+
msg['Date'] = formatdate(localtime=True)
|
75
|
+
msg['Subject'] = subject
|
76
|
+
msg.attach(message)
|
77
|
+
|
78
|
+
for attachment in attachments:
|
79
|
+
part = MIMEBase('application', "octet-stream")
|
80
|
+
with open(attachment, 'rb') as file:
|
81
|
+
part.set_payload(file.read())
|
82
|
+
encoders.encode_base64(part)
|
83
|
+
part.add_header('Content-Disposition', 'attachment; filename={}'.format(attachment.name))
|
84
|
+
msg.attach(part)
|
85
|
+
|
86
|
+
smtpServer = 'mail.wcap.ca'
|
87
|
+
server = smtplib.SMTP(smtpServer, 25)
|
88
|
+
server.ehlo()
|
89
|
+
server.sendmail(sender, recipients, msg.as_string())
|
90
|
+
server.quit()
|