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.
- {wcp_library-1.3.2 → wcp_library-1.3.4}/PKG-INFO +2 -1
- {wcp_library-1.3.2 → wcp_library-1.3.4}/pyproject.toml +2 -1
- wcp_library-1.3.4/wcp_library/informatica.py +220 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/sql/postgres.py +4 -0
- wcp_library-1.3.4/wcp_library/time.py +68 -0
- wcp_library-1.3.2/wcp_library/informatica.py +0 -112
- {wcp_library-1.3.2 → wcp_library-1.3.4}/README.md +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/__init__.py +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/__init__.py +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/credential_manager_asynchronous.py +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/credential_manager_synchronous.py +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/ftp.py +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/oracle.py +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/postgres.py +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/emailing.py +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/ftp/__init__.py +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/ftp/ftp.py +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/ftp/sftp.py +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/logging.py +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/selenium_helper.py +0 -0
- {wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/sql/__init__.py +0 -0
- {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.
|
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.
|
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
|
File without changes
|
File without changes
|
{wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/credential_manager_asynchronous.py
RENAMED
File without changes
|
{wcp_library-1.3.2 → wcp_library-1.3.4}/wcp_library/credentials/credential_manager_synchronous.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|