wcp-library 1.3.2__tar.gz → 1.3.4__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 (22) hide show
  1. {wcp_library-1.3.2 → wcp_library-1.3.4}/PKG-INFO +2 -1
  2. {wcp_library-1.3.2 → wcp_library-1.3.4}/pyproject.toml +2 -1
  3. wcp_library-1.3.4/wcp_library/informatica.py +220 -0
  4. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/sql/postgres.py +4 -0
  5. wcp_library-1.3.4/wcp_library/time.py +68 -0
  6. wcp_library-1.3.2/wcp_library/informatica.py +0 -112
  7. {wcp_library-1.3.2 → wcp_library-1.3.4}/README.md +0 -0
  8. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/__init__.py +0 -0
  9. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/__init__.py +0 -0
  10. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/credential_manager_asynchronous.py +0 -0
  11. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/credential_manager_synchronous.py +0 -0
  12. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/ftp.py +0 -0
  13. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/oracle.py +0 -0
  14. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/postgres.py +0 -0
  15. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/emailing.py +0 -0
  16. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/ftp/__init__.py +0 -0
  17. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/ftp/ftp.py +0 -0
  18. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/ftp/sftp.py +0 -0
  19. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/logging.py +0 -0
  20. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/selenium_helper.py +0 -0
  21. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/sql/__init__.py +0 -0
  22. {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/sql/oracle.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wcp-library
3
- Version: 1.3.2
3
+ Version: 1.3.4
4
4
  Summary: Common utilites for internal development at WCP
5
5
  Home-page: https://github.com/Whitecap-DNA/WCP-Library
6
6
  Author: Mitch-Petersen
@@ -19,6 +19,7 @@ Requires-Dist: psycopg (>=3.2.3,<4.0.0)
19
19
  Requires-Dist: psycopg-binary (>=3.2.3,<4.0.0)
20
20
  Requires-Dist: psycopg-pool (>=3.2.3,<4.0.0)
21
21
  Requires-Dist: pycryptodome (>=3.21.0,<4.0.0)
22
+ Requires-Dist: pytz (>=2024.2,<2025.0)
22
23
  Requires-Dist: requests (>=2.32.3,<3.0.0)
23
24
  Requires-Dist: selenium (>=4.27.1,<5.0.0)
24
25
  Requires-Dist: webdriver-manager (>=4.0.2,<5.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "wcp-library"
3
- version = "1.3.2"
3
+ version = "1.3.4"
4
4
  description = "Common utilites for internal development at WCP"
5
5
  authors = ["Mitch-Petersen <mitch.petersen@wcap.ca>"]
6
6
  readme = "README.md"
@@ -24,6 +24,7 @@ requests = "^2.32.3"
24
24
  selenium = "^4.27.1"
25
25
  webdriver-manager = "^4.0.2"
26
26
  yarl = "^1.17.1"
27
+ pytz = "^2024.2"
27
28
 
28
29
  [build-system]
29
30
  requires = ["poetry-core"]
@@ -0,0 +1,220 @@
1
+ import logging
2
+ import json
3
+ import time
4
+ from datetime import datetime
5
+ from typing import Optional
6
+
7
+ import requests
8
+ from yarl import URL
9
+
10
+ from wcp_library.time import convert_tz
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class InformaticaError(Exception):
16
+ pass
17
+
18
+
19
+ class InformaticaSession:
20
+ def __init__(self, username: str, password: str):
21
+ self.username: str = username
22
+ self.password: str = password
23
+ self._session_id: Optional[str] = None
24
+ self._server_url: Optional[URL] = None
25
+
26
+ self._get_session_id()
27
+
28
+ def _get_session_id(self) -> None:
29
+ """
30
+ Authenticate with username and password
31
+
32
+ :return: icSessionId, serverUrl
33
+ """
34
+
35
+ data = {'@type': 'login', 'username': self.username, 'password': self.password}
36
+ url = "https://dm-us.informaticacloud.com/ma/api/v2/user/login"
37
+ headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
38
+ response = requests.post(url, data=json.dumps(data), headers=headers)
39
+
40
+ logger.debug('\tInformatica API Login Response Status Code: ' + str(response.status_code))
41
+
42
+ if response.status_code == 200:
43
+ logger.info('\tInformatica API Login Successful')
44
+ self._session_id = response.json()["icSessionId"]
45
+ self._server_url = URL(response.json()["serverUrl"])
46
+ else:
47
+ raise InformaticaError(f'\tInformatica API Login call failed: {response.status_code}')
48
+
49
+ def get_tasks(self, task_type: str) -> dict:
50
+ """
51
+ Use this method to get a list of tasks of a specified type. This may be used to determine the TaskID of a task.
52
+ Task Types: https://jsapi.apiary.io/apis/cloudrestapi/reference/job/list-of-tasks/login.html
53
+ AVS-Contact validation task
54
+ DMASK-Data masking task
55
+ DQA-Data assessment task
56
+ DRS-Data replication task
57
+ DSS-Data synchronization task
58
+ MTT-Mapping configuration task
59
+ PCS-PowerCenter task
60
+
61
+ :param taskType: Task Type
62
+ :return: Task List
63
+ """
64
+
65
+ task_list_url = self._server_url / "api/v2/task"
66
+ headers = {'icSessionId': self._session_id}
67
+ response = requests.get(str(task_list_url), headers=headers, params={'type': task_type})
68
+
69
+ logger.debug('\tRetrieved list of all Tasks')
70
+
71
+ if response.status_code == 200:
72
+ return json.loads(response.content)
73
+ else:
74
+ raise InformaticaError(f'\tFailed to get list of Tasks: {response.status_code}')
75
+
76
+ def get_task_id(self, task_name: str, task_type: str) -> str:
77
+ """
78
+ Use this method to get the TaskID of a specified task. This may be used to run a task.
79
+
80
+ :param task_name: Task Name
81
+ :param task_type: Task Type
82
+ :return: Task ID
83
+ """
84
+
85
+ tasks = self.get_tasks(task_type)
86
+ for task in tasks:
87
+ if task['name'] == task_name:
88
+ return task['id']
89
+
90
+ raise InformaticaError(f'\tFailed to find TaskID for the Task Name specified: {task_name}')
91
+
92
+
93
+ def is_task_running(self, task_id: str) -> tuple[bool, datetime]:
94
+ """
95
+ Use this method to determine if a task is currently running.
96
+
97
+ :param task_id: Task ID
98
+ :return: Tuple of running status and startTime
99
+ """
100
+
101
+ task_status_url = self._server_url / f"api/v2/task/{task_id}/status"
102
+ headers = {'icSessionId': self._session_id}
103
+ response = requests.get(str(task_status_url), headers=headers)
104
+
105
+ logger.debug(f'\tRetrieved status of Task {task_id}')
106
+
107
+ if response.status_code == 200:
108
+ task_status = json.loads(response.content)
109
+ utc_time = datetime.strptime(task_status['startTimeUTC'], '%Y-%m-%dT%H:%M:%S.%fZ')
110
+ local_time = convert_tz(utc_time, 'UTC')
111
+ return task_status['status'] == 'RUNNING', local_time
112
+ else:
113
+ raise InformaticaError(f'\tFailed to get status of Task: {response.status_code}')
114
+
115
+ def run_job(self, task_id: str, task_type: str) -> str:
116
+ """
117
+ Use this method to run a task.
118
+
119
+ :param task_id: Task ID
120
+ :param task_type: Task Type
121
+ :return: Run ID
122
+ """
123
+
124
+ job_start_url = self._server_url / "api/v2/job"
125
+ headers = {'Content-Type': 'application/json', 'icSessionId': self._session_id, 'Accept': 'application/json'}
126
+ data = {'@type': 'job', 'taskId': task_id, 'taskType': task_type}
127
+ response = requests.post(str(job_start_url), data=json.dumps(data), headers=headers)
128
+
129
+ if response.status_code == 200:
130
+ logger.info('Starting Informatica Job...')
131
+ response_dict = json.loads(response.content)
132
+ runID = response_dict['runId']
133
+ return runID
134
+
135
+ else:
136
+ raise InformaticaError(f"Failed to start Informatica Job: {response.status_code}")
137
+
138
+ def wait_until_job_finish(self, run_id: str) -> datetime:
139
+ """
140
+ Use this method to wait until a job finishes running.
141
+
142
+ :param run_id:
143
+ :return: End Time
144
+ """
145
+
146
+ job_status_url = self._server_url / f"api/v2/activity/activityLog"
147
+ headers = {'icSessionId': self._session_id}
148
+
149
+ while True:
150
+ response = requests.get(str(job_status_url), headers=headers, params={'runId': run_id})
151
+ if response.status_code == 200:
152
+ response_dict = json.loads(response.content)
153
+
154
+ if not response_dict['endTimeUtc']:
155
+ time.sleep(30)
156
+ continue
157
+ if response_dict['state'] == 1:
158
+ logger.info('\tJob completed successfully')
159
+ elif response_dict['state'] == 2:
160
+ logger.info('\tJob completed with errors')
161
+ elif response_dict['state'] == 3:
162
+ raise InformaticaError('Job failed')
163
+ return convert_tz(datetime.strptime(response_dict['endTimeUtc'], '%Y-%m-%dT%H:%M:%S.%fZ'), 'UTC')
164
+ else:
165
+ raise InformaticaError(f"Failed to get job status: {response.status_code}")
166
+
167
+ def get_connection_details(self) -> dict:
168
+ """
169
+ Use this method to get a list of connections.
170
+
171
+ :return: Connection List
172
+ """
173
+
174
+ connections_url = self._server_url / "api/v2/connection"
175
+ headers = {'icSessionId': self._session_id, 'Accept': 'application/json'}
176
+ response = requests.get(str(connections_url), headers=headers)
177
+
178
+ logger.debug('\tRetrieved list of all Connections')
179
+
180
+ if response.status_code == 200:
181
+ return json.loads(response.content)
182
+ else:
183
+ raise InformaticaError(f'\tFailed to get list of Connections: {response.status_code}')
184
+
185
+ def get_mapping_details(self, mapping_id: str) -> dict:
186
+ """
187
+ Use this method to get details of a specific mapping.
188
+
189
+ :param mapping_id:
190
+ :return:
191
+ """
192
+
193
+ mapping_details_url = self._server_url / f"api/v2/mapping/{mapping_id}"
194
+ headers = {'icSessionId': self._session_id, 'Accept': 'application/json'}
195
+ response = requests.get(str(mapping_details_url), headers=headers)
196
+
197
+ logger.debug(f'\tRetrieved details of Mapping {mapping_id}')
198
+
199
+ if response.status_code == 200:
200
+ return json.loads(response.content)
201
+ else:
202
+ raise InformaticaError(f'\tFailed to get details of Mapping {mapping_id}: {response.status_code}')
203
+
204
+ def get_all_mapping_details(self) -> dict:
205
+ """
206
+ Use this method to get details of all mappings.
207
+
208
+ :return:
209
+ """
210
+
211
+ mapping_details_url = self._server_url / "api/v2/mapping"
212
+ headers = {'icSessionId': self._session_id, 'Accept': 'application/json'}
213
+ response = requests.get(str(mapping_details_url), headers=headers)
214
+
215
+ logger.debug('\tRetrieved details of all Mappings')
216
+
217
+ if response.status_code == 200:
218
+ return json.loads(response.content)
219
+ else:
220
+ raise InformaticaError(f'\tFailed to get details of all Mappings: {response.status_code}')
@@ -245,6 +245,10 @@ class PostgresConnection(object):
245
245
  if remove_nan:
246
246
  dfObj = dfObj.replace({np.nan: None})
247
247
  main_dict = dfObj.to_dict('records')
248
+ for record in main_dict:
249
+ for key in record:
250
+ if record[key] == '':
251
+ record[key] = None
248
252
 
249
253
  query = """INSERT INTO {} ({}) VALUES ({})""".format(outputTableName, col, params)
250
254
  self.execute_many(query, main_dict)
@@ -0,0 +1,68 @@
1
+ from datetime import datetime
2
+
3
+ import pytz
4
+
5
+
6
+ def get_current_time(aware: bool=False, tz: str='Canada/Mountain') -> datetime:
7
+ """
8
+ Get the current time (Mountain Time)
9
+
10
+ you can find a list of timezones by printing pytz.all_timezones
11
+
12
+ :param aware:
13
+ :param tz:
14
+ :return:
15
+ """
16
+
17
+ tz = pytz.timezone(tz)
18
+ current_time = datetime.now(tz)
19
+
20
+ if not aware:
21
+ return current_time.replace(tzinfo=None)
22
+ return current_time
23
+
24
+
25
+ def convert_tz(time: datetime, original_tz: str, aware: bool=False, tz: str='Canada/Mountain') -> datetime:
26
+ """
27
+ Convert time to a different timezone
28
+
29
+ you can find a list of timezones by printing pytz.all_timezones
30
+
31
+ :param time:
32
+ :param original_tz:
33
+ :param aware:
34
+ :param tz:
35
+ :return:
36
+ """
37
+
38
+ time = time.replace(tzinfo=pytz.timezone(original_tz))
39
+ converted_time = time.astimezone(pytz.timezone(tz))
40
+ return converted_time if aware else converted_time.replace(tzinfo=None)
41
+
42
+ def get_utc_timestamp(time: datetime, original_tz: str='Canada/Mountain') -> int:
43
+ """
44
+ Get the UTC timestamp of a datetime object
45
+
46
+ you can find a list of timezones by printing pytz.all_timezones
47
+
48
+ :param time:
49
+ :param original_tz:
50
+ :return:
51
+ """
52
+
53
+ converted_time = convert_tz(time, original_tz, aware=False, tz='UTC')
54
+ return int(converted_time.timestamp())
55
+
56
+ def get_local_timestamp(time: datetime, original_tz: str='Canada/Mountain') -> int:
57
+ """
58
+ Get the local timestamp of a datetime object
59
+
60
+ you can find a list of timezones by printing pytz.all_timezones
61
+
62
+ :param time:
63
+ :param original_tz:
64
+ :return:
65
+ """
66
+
67
+ converted_time = convert_tz(time, original_tz, aware=False)
68
+ return int(converted_time.timestamp())
@@ -1,112 +0,0 @@
1
- import requests
2
- import json
3
- import sys
4
-
5
-
6
- # Taken from https://www.mydatahack.com/running-jobs-with-informatica-cloud-rest-api/
7
- def get_session_id(username, password, logging):
8
- """Authenticate with username and password and
9
- retrieve icSessionId and serverUrl that are used for Subsequent API calls"""
10
- session_id = ''
11
- data = {'@type': 'login', 'username': username, 'password': password}
12
- url = "https://dm-us.informaticacloud.com/ma/api/v2/user/login"
13
- headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}
14
- # We need to pass data in string instead of dict so that the data gets posted directly.
15
- r = requests.post(url, data=json.dumps(data), headers=headers)
16
-
17
- logging.info('\tAPI Login Response Status Code: ' + str(r.status_code))
18
-
19
- if r.status_code == 200:
20
- session_id = r.json()["icSessionId"]
21
- server_url = r.json()["serverUrl"]
22
- logging.info('\tSession Id: ' + session_id)
23
- logging.info('\tServer URL: ' + server_url)
24
- else:
25
- logging.info('API Login call failed:')
26
- logging.info(r.headers)
27
- logging.info(r.json())
28
- sys.exit(1)
29
-
30
- return session_id, server_url
31
-
32
-
33
- def get_tasks(session_id, server_url, taskType, logging):
34
- """ Use this method to get a list of tasks of a specified type. This may be used to determine the TaskID of a task.
35
- Task Types: https://jsapi.apiary.io/apis/cloudrestapi/reference/job/list-of-tasks/login.html
36
- AVS-Contact validation task
37
- DMASK-Data masking task
38
- DQA-Data assessment task
39
- DRS-Data replication task
40
- DSS-Data synchronization task
41
- MTT-Mapping configuration task
42
- PCS-PowerCenter task"""
43
- task_list_url = server_url + "/api/v2/task?type=" + taskType
44
- headers = {'icSessionId': session_id}
45
- r = requests.get(task_list_url, headers=headers)
46
-
47
- if r.status_code == 200:
48
- logging.info('\tRetrieved list of all Tasks')
49
- response_dict = json.loads(r.content)
50
- return response_dict
51
-
52
- else:
53
- logging.info('\tFailed to get list of Tasks: ' + str(r.status_code))
54
- return {}
55
-
56
-
57
- def get_task_id(response_dict, taskName, logging):
58
- for d in response_dict:
59
- if d['name'] == taskName:
60
- id = d['id']
61
- logging.info('\tTaskID: ' + id)
62
- return id
63
-
64
- logging.info('\tCould not find TaskID for the Task Name specified')
65
- return ""
66
-
67
-
68
- def get_all_mapping_details(session_id, server_url, logging):
69
- mapping_details_url = server_url + "/api/v2/mapping"
70
- headers = {'icSessionId': session_id, 'HTTP': '1.0', 'Accept': 'application/json'}
71
- r = requests.get(mapping_details_url, headers=headers)
72
-
73
- if r.status_code == 200:
74
- response_dict = json.loads(r.content)
75
- return response_dict
76
-
77
- else:
78
- logging.info('\tFailed to get Mappings: ' + str(r.status_code))
79
- return {}
80
-
81
-
82
- def get_singular_mapping_details(session_id, server_url, logging, mappingID):
83
- mapping_details_url = server_url + "/api/v2/mapping/" + mappingID
84
- headers = {'icSessionId': session_id, 'Accept': 'application/json'}
85
- r = requests.get(mapping_details_url, headers=headers)
86
-
87
- if r.status_code == 200:
88
- mapping_deets_dict = json.loads(r.content)
89
- return mapping_deets_dict
90
-
91
- else:
92
- logging.info('\tFailed to get Mapping details for mapping ' + mappingID + ': ' + str(r.status_code))
93
- return {}
94
-
95
-
96
- def get_connection_details(session_id, server_url, logging):
97
-
98
- # source_dict = {}
99
- # target_dict = {}
100
-
101
- connections_url = server_url + "/api/v2/connection"
102
- # target_connections_url = server_url + "/api/v2/mapping"
103
- headers = {'icSessionId': session_id, 'content-type': 'application/json'}
104
- r = requests.get(connections_url, headers=headers)
105
-
106
- if r.status_code == 200:
107
- response_dict = json.loads(r.content)
108
- return response_dict
109
-
110
- else:
111
- logging.info('\tFailed to get Mappings: ' + str(r.status_code))
112
- return {}
File without changes