pyegeria 5.4.0.dev14__py3-none-any.whl → 5.4.0.2__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.
- commands/cat/__init__.py +1 -17
- commands/cat/dr_egeria_md.py +6 -4
- commands/cat/list_collections.py +46 -36
- md_processing/__init__.py +5 -2
- md_processing/data/commands-working.json +34850 -0
- md_processing/data/commands.json +1750 -530
- md_processing/md_commands/product_manager_commands.py +171 -220
- md_processing/md_processing_utils/common_md_proc_utils.py +9 -0
- md_processing/md_processing_utils/common_md_utils.py +15 -2
- md_processing/md_processing_utils/md_processing_constants.py +44 -6
- pyegeria/__init__.py +8 -4
- pyegeria/_client.py +2 -1
- pyegeria/_client_new.py +688 -0
- pyegeria/_exceptions_new.py +364 -0
- pyegeria/_globals.py +3 -1
- pyegeria/_output_formats.py +196 -0
- pyegeria/_validators.py +72 -199
- pyegeria/collection_manager_omvs.py +602 -324
- pyegeria/data_designer_omvs.py +251 -203
- pyegeria/load_config.py +206 -0
- pyegeria/logging_configuration.py +204 -0
- pyegeria/output_formatter.py +162 -31
- pyegeria/utils.py +99 -61
- {pyegeria-5.4.0.dev14.dist-info → pyegeria-5.4.0.2.dist-info}/METADATA +4 -1
- {pyegeria-5.4.0.dev14.dist-info → pyegeria-5.4.0.2.dist-info}/RECORD +28 -37
- commands/cat/debug_log +0 -2806
- commands/cat/debug_log.2025-07-15_14-28-38_087378.zip +0 -0
- commands/cat/debug_log.2025-07-16_15-48-50_037087.zip +0 -0
- md_processing/dr_egeria_outbox-pycharm/.obsidian/app.json +0 -1
- md_processing/dr_egeria_outbox-pycharm/.obsidian/appearance.json +0 -1
- md_processing/dr_egeria_outbox-pycharm/.obsidian/core-plugins.json +0 -31
- md_processing/dr_egeria_outbox-pycharm/.obsidian/workspace.json +0 -177
- md_processing/dr_egeria_outbox-pycharm/monday/processed-2025-07-14 12:38-data_designer_out.md +0 -663
- md_processing/dr_egeria_outbox-pycharm/thursday/processed-2025-07-17 15:00-Derive-Dr-Gov-Defs.md +0 -719
- md_processing/dr_egeria_outbox-pycharm/thursday/processed-2025-07-17 20:13-Derive-Dr-Gov-Defs.md +0 -41
- md_processing/dr_egeria_outbox-pycharm/thursday/processed-2025-07-17 20:14-Derive-Dr-Gov-Defs.md +0 -33
- md_processing/dr_egeria_outbox-pycharm/thursday/processed-2025-07-17 20:50-Derive-Dr-Gov-Defs.md +0 -192
- md_processing/dr_egeria_outbox-pycharm/tuesday/processed-2025-07-16 19:15-gov_def2.md +0 -527
- md_processing/dr_egeria_outbox-pycharm/tuesday/processed-2025-07-17 12:08-gov_def2.md +0 -527
- md_processing/dr_egeria_outbox-pycharm/tuesday/processed-2025-07-17 14:27-gov_def2.md +0 -474
- {pyegeria-5.4.0.dev14.dist-info → pyegeria-5.4.0.2.dist-info}/LICENSE +0 -0
- {pyegeria-5.4.0.dev14.dist-info → pyegeria-5.4.0.2.dist-info}/WHEEL +0 -0
- {pyegeria-5.4.0.dev14.dist-info → pyegeria-5.4.0.2.dist-info}/entry_points.txt +0 -0
pyegeria/_client_new.py
ADDED
@@ -0,0 +1,688 @@
|
|
1
|
+
"""
|
2
|
+
SPDX-License-Identifier: Apache-2.0
|
3
|
+
Copyright Contributors to the ODPi Egeria project.
|
4
|
+
|
5
|
+
This is a simple class to create and manage a connection to an Egeria backend. It is the Superclass for the
|
6
|
+
different client capabilities. It also provides the common methods used to make restful self.session to Egeria.
|
7
|
+
|
8
|
+
"""
|
9
|
+
|
10
|
+
import asyncio
|
11
|
+
import inspect
|
12
|
+
import json
|
13
|
+
import os
|
14
|
+
import re
|
15
|
+
from datetime import datetime
|
16
|
+
|
17
|
+
import httpcore
|
18
|
+
import httpx
|
19
|
+
# from venv import logger
|
20
|
+
from loguru import logger
|
21
|
+
|
22
|
+
from httpx import AsyncClient, Response, HTTPStatusError
|
23
|
+
|
24
|
+
from pyegeria.utils import body_slimmer
|
25
|
+
from pyegeria._exceptions_new import (
|
26
|
+
PyegeriaException, PyegeriaAPIException, PyegeriaConnectionException, PyegeriaInvalidParameterException,
|
27
|
+
PyegeriaNotFoundException, PyegeriaUnknownException, PyegeriaErrorCode,
|
28
|
+
PyegeriaUnauthorizedException, PyegeriaClientException
|
29
|
+
)
|
30
|
+
from pyegeria._globals import enable_ssl_check, max_paging_size, NO_ELEMENTS_FOUND
|
31
|
+
from pyegeria._validators import (
|
32
|
+
is_json,
|
33
|
+
validate_name,
|
34
|
+
validate_server_name,
|
35
|
+
validate_url,
|
36
|
+
validate_user_id,
|
37
|
+
)
|
38
|
+
|
39
|
+
...
|
40
|
+
|
41
|
+
|
42
|
+
class Client2:
|
43
|
+
"""
|
44
|
+
An abstract class used to establish connectivity for an Egeria Client
|
45
|
+
for a particular server, platform and user.
|
46
|
+
|
47
|
+
Attributes
|
48
|
+
----------
|
49
|
+
server_name : str (required)
|
50
|
+
Name of the OMAG server to use
|
51
|
+
platform_url : str (required)
|
52
|
+
URL of the server platform to connect to
|
53
|
+
user_id : str
|
54
|
+
The identity of the user calling the method - this sets a default optionally used by the methods
|
55
|
+
when the user doesn't pass the user_id on a method call.
|
56
|
+
user_pwd : str
|
57
|
+
The password used to authenticate the server identity
|
58
|
+
|
59
|
+
Methods
|
60
|
+
-------
|
61
|
+
create_egeria_bearer_token(user_Id: str, password: str = None) -> str
|
62
|
+
Create a bearer token using the simple Egeria token service - store the bearer token in the object instance.
|
63
|
+
|
64
|
+
refresh_egeria_bearer_token()-> None
|
65
|
+
Refresh the bearer token using the attributes of the object instance.
|
66
|
+
|
67
|
+
set_bearer_token(token: str) -> None
|
68
|
+
Set the bearer token attribute in the object instance - used when the token is generated
|
69
|
+
by an external service.
|
70
|
+
|
71
|
+
get_token() -> str
|
72
|
+
Retrieve the bearer token.
|
73
|
+
|
74
|
+
make_request(request_type: str, endpoint: str, payload: str | dict = None,
|
75
|
+
time_out: int = 30) -> Response
|
76
|
+
Make an HTTP Restful request and handle potential errors and exceptions.
|
77
|
+
|
78
|
+
"""
|
79
|
+
|
80
|
+
json_header = {"Content-Type": "application/json"}
|
81
|
+
|
82
|
+
def __init__(
|
83
|
+
self,
|
84
|
+
server_name: str,
|
85
|
+
platform_url: str,
|
86
|
+
user_id: str = None,
|
87
|
+
user_pwd: str = None,
|
88
|
+
token: str = None,
|
89
|
+
token_src: str = None,
|
90
|
+
api_key: str = None,
|
91
|
+
page_size: int = max_paging_size,
|
92
|
+
):
|
93
|
+
self.server_name = validate_server_name(server_name)
|
94
|
+
self.platform_url = validate_url(platform_url)
|
95
|
+
self.user_id = user_id
|
96
|
+
self.user_pwd = user_pwd
|
97
|
+
self.page_size = page_size
|
98
|
+
self.token_src = token_src
|
99
|
+
self.token = token
|
100
|
+
self.exc_type = None
|
101
|
+
self.exc_value = None
|
102
|
+
self.exc_tb = None
|
103
|
+
|
104
|
+
#
|
105
|
+
# I'm commenting this out since you should only have to use tokens if you want - just have to
|
106
|
+
# create or set the token with the appropriate methods as desired.
|
107
|
+
# if token is None:
|
108
|
+
# token = os.environ.get("Egeria_Bearer_Token", None)
|
109
|
+
# if token is None: # No token found - so make one
|
110
|
+
# self.create_egeria_bearer_token(self.user_id, self.user_pwd)
|
111
|
+
# else:
|
112
|
+
# self.token = token
|
113
|
+
|
114
|
+
if api_key is None:
|
115
|
+
api_key = os.environ.get("API_KEY", None)
|
116
|
+
self.api_key = api_key
|
117
|
+
|
118
|
+
self.headers = {
|
119
|
+
"Content-Type": "application/json",
|
120
|
+
}
|
121
|
+
self.text_headers = {
|
122
|
+
"Content-Type": "text/plain",
|
123
|
+
}
|
124
|
+
if self.api_key is not None:
|
125
|
+
self.headers["X-Api-Key"] = self.api_key
|
126
|
+
self.text_headers["X-Api-Key"] = self.api_key
|
127
|
+
|
128
|
+
if token is not None:
|
129
|
+
self.headers["Authorization"] = f"Bearer {token}"
|
130
|
+
self.text_headers["Authorization"] = f"Bearer {token}"
|
131
|
+
|
132
|
+
v_url = validate_url(platform_url)
|
133
|
+
|
134
|
+
if v_url:
|
135
|
+
self.platform_url = platform_url
|
136
|
+
if validate_server_name(server_name):
|
137
|
+
self.server_name = server_name
|
138
|
+
self.session = AsyncClient(verify=enable_ssl_check)
|
139
|
+
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
def __enter__(self):
|
144
|
+
return self
|
145
|
+
|
146
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
147
|
+
self.session.aclose()
|
148
|
+
if exc_type is not None:
|
149
|
+
self.exc_type = exc_type
|
150
|
+
self.exc_val = exc_val
|
151
|
+
self.exc_tb = exc_tb
|
152
|
+
|
153
|
+
return False # allows exceptions to propagate
|
154
|
+
|
155
|
+
def __str__(self):
|
156
|
+
return (f"EgeriaClient(server_name={self.server_name}, platform_url={self.platform_url}, "
|
157
|
+
f"user_id={self.user_id}, page_size={self.page_size})")
|
158
|
+
|
159
|
+
async def _async_close_session(self) -> None:
|
160
|
+
"""Close the session"""
|
161
|
+
await self.session.aclose()
|
162
|
+
|
163
|
+
def close_session(self) -> None:
|
164
|
+
"""Close the session"""
|
165
|
+
loop = asyncio.get_event_loop()
|
166
|
+
loop.run_until_complete(self._async_close_session())
|
167
|
+
return
|
168
|
+
|
169
|
+
async def _async_create_egeria_bearer_token(
|
170
|
+
self, user_id: str = None, password: str = None
|
171
|
+
) -> str:
|
172
|
+
"""Create and set an Egeria Bearer Token for the user. Async version
|
173
|
+
Parameters
|
174
|
+
----------
|
175
|
+
user_id : str, opt
|
176
|
+
The user id to authenticate with. If None, then user_id from class instance used.
|
177
|
+
password : str, opt
|
178
|
+
The password for the user. If None, then user_pwd from class instance is used.
|
179
|
+
|
180
|
+
Returns
|
181
|
+
-------
|
182
|
+
token
|
183
|
+
The bearer token for the specified user.
|
184
|
+
|
185
|
+
Raises
|
186
|
+
------
|
187
|
+
InvalidParameterException
|
188
|
+
If the client passes incorrect parameters on the request - such as bad URLs or invalid values
|
189
|
+
PropertyServerException
|
190
|
+
Raised by the server when an issue arises in processing a valid request
|
191
|
+
NotAuthorizedException
|
192
|
+
The principle specified by the user_id does not have authorization for the requested action
|
193
|
+
Notes
|
194
|
+
-----
|
195
|
+
This routine creates a new bearer token for the user and updates the object with it.
|
196
|
+
It uses Egeria's mechanisms to create a token. This is useful if an Egeria token expires.
|
197
|
+
A bearer token from another source can be set with the set_bearer_token() method.
|
198
|
+
|
199
|
+
"""
|
200
|
+
if user_id is None:
|
201
|
+
validate_user_id(self.user_id)
|
202
|
+
user_id = self.user_id
|
203
|
+
if password is None:
|
204
|
+
validate_name(self.user_pwd)
|
205
|
+
password = self.user_pwd
|
206
|
+
|
207
|
+
url = f"{self.platform_url}/api/token"
|
208
|
+
data = {"userId": user_id, "password": password}
|
209
|
+
async with AsyncClient(verify=enable_ssl_check) as client:
|
210
|
+
try:
|
211
|
+
response = await client.post(url, json=data, headers=self.headers)
|
212
|
+
token = response.text
|
213
|
+
except httpx.HTTPError as e:
|
214
|
+
print(e)
|
215
|
+
return "FAILED"
|
216
|
+
|
217
|
+
if token:
|
218
|
+
self.token_src = "Egeria"
|
219
|
+
self.headers["Authorization"] = f"Bearer {token}"
|
220
|
+
self.text_headers["Authorization"] = f"Bearer {token}"
|
221
|
+
return token
|
222
|
+
else:
|
223
|
+
additional_info = {"reason": "No token returned - request issue?"}
|
224
|
+
raise PyegeriaInvalidParameterException(None, None, additional_info)
|
225
|
+
|
226
|
+
def create_egeria_bearer_token(
|
227
|
+
self, user_id: str = None, password: str = None
|
228
|
+
) -> str:
|
229
|
+
"""Create and set an Egeria Bearer Token for the user
|
230
|
+
Parameters
|
231
|
+
----------
|
232
|
+
user_id : str
|
233
|
+
The user id to authenticate with.
|
234
|
+
password : str
|
235
|
+
The password for the user.
|
236
|
+
|
237
|
+
Returns
|
238
|
+
-------
|
239
|
+
token
|
240
|
+
The bearer token for the specified user.
|
241
|
+
|
242
|
+
Raises
|
243
|
+
------
|
244
|
+
InvalidParameterException
|
245
|
+
If the client passes incorrect parameters on the request - such as bad URLs or invalid values
|
246
|
+
PropertyServerException
|
247
|
+
Raised by the server when an issue arises in processing a valid request
|
248
|
+
NotAuthorizedException
|
249
|
+
The principle specified by the user_id does not have authorization for the requested action
|
250
|
+
Notes
|
251
|
+
-----
|
252
|
+
This routine creates a new bearer token for the user and updates the object with it.
|
253
|
+
It uses Egeria's mechanisms to create a token. This is useful if an Egeria token expires.
|
254
|
+
A bearer token from another source can be set with the set_bearer_token() method.
|
255
|
+
|
256
|
+
"""
|
257
|
+
loop = asyncio.get_event_loop()
|
258
|
+
response = loop.run_until_complete(
|
259
|
+
self._async_create_egeria_bearer_token(user_id, password)
|
260
|
+
)
|
261
|
+
return response
|
262
|
+
|
263
|
+
async def _async_refresh_egeria_bearer_token(self) -> str:
|
264
|
+
"""
|
265
|
+
Refreshes the Egeria bearer token. Async version.
|
266
|
+
|
267
|
+
This method is used to refresh the bearer token used for authentication with Egeria. It checks if the token
|
268
|
+
source is 'Egeria', and if the user ID and password are valid. If all conditions are met, it calls the
|
269
|
+
`create_egeria_bearer_token` method to create a new bearer token. Otherwise,
|
270
|
+
it raises an `InvalidParameterException`.
|
271
|
+
|
272
|
+
Parameters:
|
273
|
+
|
274
|
+
Returns:
|
275
|
+
None
|
276
|
+
|
277
|
+
Raises:
|
278
|
+
InvalidParameterException: If the token source is invalid.
|
279
|
+
"""
|
280
|
+
if (
|
281
|
+
(self.token_src == "Egeria")
|
282
|
+
and validate_user_id(self.user_id)
|
283
|
+
and validate_name(self.user_pwd)
|
284
|
+
):
|
285
|
+
token = await self._async_create_egeria_bearer_token(
|
286
|
+
self.user_id, self.user_pwd
|
287
|
+
)
|
288
|
+
return token
|
289
|
+
else:
|
290
|
+
additional_info = {"reason": "Invalid token source"}
|
291
|
+
raise PyegeriaInvalidParameterException(None, None, additional_info)
|
292
|
+
|
293
|
+
|
294
|
+
def refresh_egeria_bearer_token(self) -> None:
|
295
|
+
"""
|
296
|
+
Refreshes the Egeria bearer token.
|
297
|
+
|
298
|
+
This method is used to refresh the bearer token used for authentication with Egeria. It checks if the token
|
299
|
+
source is 'Egeria', and if the user ID and password are valid. If all conditions are met, it calls the
|
300
|
+
`create_egeria_bearer_token` method to create a new bearer token. Otherwise,
|
301
|
+
it raises an `InvalidParameterException`.
|
302
|
+
|
303
|
+
Parameters:
|
304
|
+
|
305
|
+
Returns:
|
306
|
+
None
|
307
|
+
|
308
|
+
Raises:
|
309
|
+
InvalidParameterException: If the token source is invalid.
|
310
|
+
PropertyServerException
|
311
|
+
Raised by the server when an issue arises in processing a valid request
|
312
|
+
NotAuthorizedException
|
313
|
+
The principle specified by the user_id does not have authorization for the requested action
|
314
|
+
"""
|
315
|
+
loop = asyncio.get_event_loop()
|
316
|
+
token = loop.run_until_complete(self._async_refresh_egeria_bearer_token())
|
317
|
+
return token
|
318
|
+
|
319
|
+
def set_bearer_token(self, token: str) -> None:
|
320
|
+
"""Retrieve and set a Bearer Token
|
321
|
+
Parameters
|
322
|
+
----------
|
323
|
+
token: str
|
324
|
+
A bearer token supplied to the method.
|
325
|
+
|
326
|
+
Returns
|
327
|
+
-------
|
328
|
+
None
|
329
|
+
This method does not return anything.
|
330
|
+
|
331
|
+
Raises
|
332
|
+
------
|
333
|
+
InvalidParameterException
|
334
|
+
If the client passes incorrect parameters on the request - such as bad URLs or invalid values
|
335
|
+
PropertyServerException
|
336
|
+
Raised by the server when an issue arises in processing a valid request
|
337
|
+
NotAuthorizedException
|
338
|
+
The principle specified by the user_id does not have authorization for the requested action
|
339
|
+
Notes
|
340
|
+
-----
|
341
|
+
This routine sets the bearer token for the current object. The user is responsible for providing the token.
|
342
|
+
|
343
|
+
"""
|
344
|
+
validate_name(token)
|
345
|
+
self.headers["Authorization"] = f"Bearer {token}"
|
346
|
+
self.text_headers["Authorization"] = f"Bearer {token}"
|
347
|
+
|
348
|
+
def get_token(self) -> str:
|
349
|
+
"""Retrieve and return the bearer token"""
|
350
|
+
return self.text_headers["Authorization"]
|
351
|
+
|
352
|
+
def get_platform_origin(self):
|
353
|
+
"""Validate platform connectivity"""
|
354
|
+
origin_url = f"{self.platform_url}/open-metadata/platform-services/users/{self.user_id}/server-platform/origin"
|
355
|
+
response = self.make_request("GET", origin_url, is_json=False)
|
356
|
+
if response.status_code == 200:
|
357
|
+
logger.success(f"Got response from {origin_url}\n Response: {response.text}")
|
358
|
+
if response.text.split()[0] == "Egeria":
|
359
|
+
return True
|
360
|
+
else:
|
361
|
+
return False
|
362
|
+
else:
|
363
|
+
logger.info(f"Got response from {origin_url}\n status_code: {response.status_code}")
|
364
|
+
|
365
|
+
|
366
|
+
# @logger.catch
|
367
|
+
def make_request(
|
368
|
+
self,
|
369
|
+
request_type: str,
|
370
|
+
endpoint: str,
|
371
|
+
payload: str | dict = None,
|
372
|
+
time_out: int = 30,
|
373
|
+
is_json: bool = True,
|
374
|
+
) -> Response | str:
|
375
|
+
"""Make a request to the Egeria API."""
|
376
|
+
try:
|
377
|
+
loop = asyncio.get_running_loop()
|
378
|
+
coro = self._async_make_request(request_type, endpoint, payload, time_out, is_json)
|
379
|
+
return asyncio.run_coroutine_threadsafe(coro, loop).result()
|
380
|
+
except RuntimeError:
|
381
|
+
# No running loop exists; run the coroutine
|
382
|
+
return asyncio.run(self._async_make_request(request_type, endpoint, payload, time_out, is_json))
|
383
|
+
|
384
|
+
async def _async_make_request(
|
385
|
+
self,
|
386
|
+
request_type: str,
|
387
|
+
endpoint: str,
|
388
|
+
payload: str | dict = None,
|
389
|
+
time_out: int = 30,
|
390
|
+
is_json: bool = True,
|
391
|
+
) -> Response | str:
|
392
|
+
"""Make a request to the Egeria API - Async Version
|
393
|
+
Function to make an API call via the self.session Library. Raise an exception if the HTTP response code
|
394
|
+
is not 200/201. IF there is a REST communication exception, raise InvalidParameterException.
|
395
|
+
|
396
|
+
:param request_type: Type of Request.
|
397
|
+
Supported Values - GET, POST, (not PUT, PATCH, DELETE).
|
398
|
+
Type - String
|
399
|
+
:param endpoint: API Endpoint. Type - String
|
400
|
+
:param payload: API Request Parameters or Query String.
|
401
|
+
Type - String or Dict
|
402
|
+
:param time_out: Timeout in seconds. Type - Integer
|
403
|
+
:param is_json: Whether the payload is JSON or not. Type - Boolean
|
404
|
+
:return: Response. Type - JSON Formatted String
|
405
|
+
|
406
|
+
"""
|
407
|
+
context: dict = {}
|
408
|
+
context['class name'] = __class__.__name__
|
409
|
+
context['caller method'] = inspect.currentframe().f_back.f_code.co_name
|
410
|
+
response: Response
|
411
|
+
|
412
|
+
try:
|
413
|
+
|
414
|
+
response = await self.session.request(request_type, endpoint, headers=self.headers, json=payload, timeout=time_out)
|
415
|
+
response.raise_for_status()
|
416
|
+
|
417
|
+
|
418
|
+
status_code = response.status_code
|
419
|
+
|
420
|
+
except (httpx.TimeoutException, httpcore.ConnectError, httpx.ConnectError) as e:
|
421
|
+
additional_info = {"endpoint": endpoint, "error_kind": "connection"}
|
422
|
+
raise PyegeriaConnectionException(context, additional_info, e)
|
423
|
+
|
424
|
+
except (HTTPStatusError, httpx.HTTPStatusError, httpx.RequestError) as e:
|
425
|
+
# context["caught_exception"] = e
|
426
|
+
# context['HTTPStatusCode'] = e.response.status_code
|
427
|
+
additional_info = {"userid": self.user_id}
|
428
|
+
raise PyegeriaClientException(response, context, additional_info, e)
|
429
|
+
#
|
430
|
+
# except json.JSONDecodeError as e:
|
431
|
+
# # context["caught_exception"] = e
|
432
|
+
# # context['HTTPStatusCode'] = e.response.status_code
|
433
|
+
# additional_info = {"userid": self.user_id}
|
434
|
+
# raise PyegeriaClientException(response, context, additional_info )
|
435
|
+
#
|
436
|
+
# except PyegeriaAPIException as e:
|
437
|
+
# raise PyegeriaAPIException(response, context, additional_info=None)
|
438
|
+
|
439
|
+
except Exception as e:
|
440
|
+
additional_info = {"userid": self.user_id}
|
441
|
+
if 'response' in locals() and response is not None:
|
442
|
+
logger.error(f"Response error with code {response.status_code}")
|
443
|
+
else:
|
444
|
+
logger.error("Response object not available due to error")
|
445
|
+
raise PyegeriaUnknownException(None, context, additional_info, e)
|
446
|
+
|
447
|
+
if status_code in (200, 201):
|
448
|
+
try:
|
449
|
+
if is_json:
|
450
|
+
json_response = response.json()
|
451
|
+
related_http_code = json_response.get("relatedHTTPCode", 0)
|
452
|
+
if related_http_code == 200:
|
453
|
+
return response
|
454
|
+
else:
|
455
|
+
raise PyegeriaAPIException(response, context, additional_info=None)
|
456
|
+
|
457
|
+
else: # Not JSON - Text?
|
458
|
+
return response
|
459
|
+
|
460
|
+
|
461
|
+
except json.JSONDecodeError as e:
|
462
|
+
logger.error("Failed to decode JSON response from %s: %s", endpoint, response.text,
|
463
|
+
exc_info=True)
|
464
|
+
context['caught_exception'] = e
|
465
|
+
raise PyegeriaInvalidParameterException(
|
466
|
+
response, context,e = e
|
467
|
+
)
|
468
|
+
|
469
|
+
|
470
|
+
|
471
|
+
def build_global_guid_lists(self) -> None:
|
472
|
+
global template_guids, integration_guids
|
473
|
+
|
474
|
+
self.create_egeria_bearer_token(self.user_id, self.user_pwd)
|
475
|
+
# get all technology types
|
476
|
+
url = (
|
477
|
+
f"{self.platform_url}/servers/{self.server_name}/api/open-metadata/automated-curation/technology-types/"
|
478
|
+
f"by-search-string?startFrom=0&pageSize=0&startsWith=false&"
|
479
|
+
f"endsWith=false&ignoreCase=true"
|
480
|
+
)
|
481
|
+
body = {"filter": ""}
|
482
|
+
|
483
|
+
response = self.make_request("POST", url, body)
|
484
|
+
tech_types = response.json().get("elements", "no tech found")
|
485
|
+
if type(tech_types) is list:
|
486
|
+
for tech_type in tech_types:
|
487
|
+
# get tech type details
|
488
|
+
display_name = tech_type["name"]
|
489
|
+
|
490
|
+
url = f"{self.platform_url}/servers/{self.server_name}/api/open-metadata/automated-curation/technology-types/by-name"
|
491
|
+
body = {"filter": display_name}
|
492
|
+
response = self.make_request("POST", url, body)
|
493
|
+
details = response.json().get("element", "no type found")
|
494
|
+
if type(details) is str:
|
495
|
+
continue
|
496
|
+
# get templates and update the template_guids global
|
497
|
+
templates = details.get("catalogTemplates", "Not Found")
|
498
|
+
if type(templates) is str:
|
499
|
+
template_guids[display_name] = None
|
500
|
+
else:
|
501
|
+
for template in templates:
|
502
|
+
template_name = template.get("name", None)
|
503
|
+
template_guid = template["relatedElement"]["guid"]
|
504
|
+
template_guids[template_name] = template_guid
|
505
|
+
# print(f"Added {template_name} template with GUID {template_guids[template_name]}")
|
506
|
+
|
507
|
+
# Now find the integration connector guids
|
508
|
+
resource_list = details.get("resourceList", " ")
|
509
|
+
if type(resource_list) is str:
|
510
|
+
integration_guids[display_name] = None
|
511
|
+
else:
|
512
|
+
for resource in resource_list:
|
513
|
+
resource_guid = resource["relatedElement"]["guid"]
|
514
|
+
resource_type = resource["relatedElement"]["type"]["typeName"]
|
515
|
+
if resource_type == "IntegrationConnector":
|
516
|
+
integration_guids[display_name] = resource_guid
|
517
|
+
# print(f"Added {display_name} integration connector with GUID {integration_guids[display_name]}")
|
518
|
+
|
519
|
+
async def __async_get_guid__(
|
520
|
+
self,
|
521
|
+
guid: str = None,
|
522
|
+
display_name: str = None,
|
523
|
+
property_name: str = "qualifiedName",
|
524
|
+
qualified_name: str = None,
|
525
|
+
tech_type: str = None,
|
526
|
+
) -> str:
|
527
|
+
"""Helper function to return a server_guid - one of server_guid, qualified_name or display_name should
|
528
|
+
contain information. If all are None, an exception will be thrown. If all contain
|
529
|
+
values, server_guid will be used first, followed by qualified_name. If the tech_type is supplied and the
|
530
|
+
property_name is qualifiedName then the display_name will be pre-pended with the tech_type name to form a
|
531
|
+
qualifiedName.
|
532
|
+
|
533
|
+
An InvalidParameter Exception is thrown if multiple matches
|
534
|
+
are found for the given property name. If this occurs, use a qualified name for the property name.
|
535
|
+
Async version.
|
536
|
+
"""
|
537
|
+
|
538
|
+
if guid:
|
539
|
+
return guid
|
540
|
+
|
541
|
+
if qualified_name:
|
542
|
+
body = {
|
543
|
+
"class": "NameRequestBody",
|
544
|
+
"name": qualified_name,
|
545
|
+
"namePropertyName": "qualifiedName",
|
546
|
+
"forLineage": False,
|
547
|
+
"forDuplicateProcessing": False,
|
548
|
+
"effectiveTime": None,
|
549
|
+
}
|
550
|
+
url = (
|
551
|
+
f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/classification-manager/"
|
552
|
+
f"elements/guid-by-unique-name?forLineage=false&forDuplicateProcessing=false"
|
553
|
+
)
|
554
|
+
|
555
|
+
result = await self._async_make_request("POST", url, body_slimmer(body))
|
556
|
+
return result.json().get("guid", NO_ELEMENTS_FOUND)
|
557
|
+
|
558
|
+
try:
|
559
|
+
view_server = self.view_server
|
560
|
+
except AttributeError:
|
561
|
+
view_server = os.environ.get("EGERIA_VIEW_SERVER", "view-server")
|
562
|
+
|
563
|
+
if (not qualified_name) and display_name:
|
564
|
+
if (tech_type) and (property_name == "qualifiedName"):
|
565
|
+
name = f"{tech_type}::{display_name}"
|
566
|
+
body = {
|
567
|
+
"class": "NameRequestBody",
|
568
|
+
"name": name,
|
569
|
+
"namePropertyName": property_name,
|
570
|
+
"forLineage": False,
|
571
|
+
"forDuplicateProcessing": False,
|
572
|
+
"effectiveTime": None,
|
573
|
+
}
|
574
|
+
url = (
|
575
|
+
f"{self.platform_url}/servers/{view_server}/api/open-metadata/classification-manager/"
|
576
|
+
f"elements/guid-by-unique-name?forLineage=false&forDuplicateProcessing=false"
|
577
|
+
)
|
578
|
+
|
579
|
+
result = await self._async_make_request("POST", url, body_slimmer(body))
|
580
|
+
return result.json().get("guid", NO_ELEMENTS_FOUND)
|
581
|
+
else:
|
582
|
+
body = {
|
583
|
+
"class": "NameRequestBody",
|
584
|
+
"name": display_name,
|
585
|
+
"namePropertyName": property_name,
|
586
|
+
"forLineage": False,
|
587
|
+
"forDuplicateProcessing": False,
|
588
|
+
"effectiveTime": None,
|
589
|
+
}
|
590
|
+
url = (
|
591
|
+
f"{self.platform_url}/servers/{view_server}/api/open-metadata/classification-manager/"
|
592
|
+
f"elements/guid-by-unique-name?forLineage=false&forDuplicateProcessing=false"
|
593
|
+
)
|
594
|
+
|
595
|
+
result = await self._async_make_request("POST", url, body_slimmer(body))
|
596
|
+
return result.json().get("guid", NO_ELEMENTS_FOUND)
|
597
|
+
else:
|
598
|
+
additional_info = {"reason": "Neither server_guid nor server_name were provided - please provide.",
|
599
|
+
"parameters": (f"GUID={guid}, display_name={display_name}, property_name={property_name},"
|
600
|
+
f"qualified_name={qualified_name}, tech_type={tech_type}")
|
601
|
+
}
|
602
|
+
raise PyegeriaInvalidParameterException(None, None, additional_info)
|
603
|
+
|
604
|
+
def __get_guid__(
|
605
|
+
self,
|
606
|
+
guid: str = None,
|
607
|
+
display_name: str = None,
|
608
|
+
property_name: str = "qualifiedName",
|
609
|
+
qualified_name: str = None,
|
610
|
+
tech_type: str = None,
|
611
|
+
) -> str:
|
612
|
+
"""Helper function to return a server_guid - one of server_guid, qualified_name or display_name should
|
613
|
+
contain information. If all are None, an exception will be thrown. If all contain
|
614
|
+
values, server_guid will be used first, followed by qualified_name. If the tech_type is supplied and the
|
615
|
+
property_name is qualifiedName then the display_name will be pre-pended with the tech_type name to form a
|
616
|
+
qualifiedName.
|
617
|
+
|
618
|
+
An InvalidParameter Exception is thrown if multiple matches
|
619
|
+
are found for the given property name. If this occurs, use a qualified name for the property name.
|
620
|
+
Async version.
|
621
|
+
"""
|
622
|
+
loop = asyncio.get_event_loop()
|
623
|
+
result = loop.run_until_complete(
|
624
|
+
self.__async_get_guid__(
|
625
|
+
guid, display_name, property_name, qualified_name, tech_type
|
626
|
+
)
|
627
|
+
)
|
628
|
+
return result
|
629
|
+
|
630
|
+
def __create_qualified_name__(self, type: str, display_name: str, local_qualifier: str = None,
|
631
|
+
version_identifier: str = None) -> str:
|
632
|
+
"""Helper function to create a qualified name for a given type and display name.
|
633
|
+
If present, the local qualifier will be prepended to the qualified name."""
|
634
|
+
EGERIA_LOCAL_QUALIFIER = os.environ.get("EGERIA_LOCAL_QUALIFIER", local_qualifier)
|
635
|
+
# display_name = re.sub(r'\s','-',display_name.strip()) # This changes spaces between words to -; removing
|
636
|
+
if display_name is None:
|
637
|
+
additional_info = {"reason": "Display name is missing - please provide.",}
|
638
|
+
raise PyegeriaInvalidParameterException(additional_info=additional_info)
|
639
|
+
q_name = f"{type}::{display_name.strip()}"
|
640
|
+
if EGERIA_LOCAL_QUALIFIER:
|
641
|
+
q_name = f"{EGERIA_LOCAL_QUALIFIER}::{q_name}"
|
642
|
+
if version_identifier:
|
643
|
+
q_name = f"{q_name}::{version_identifier}"
|
644
|
+
return q_name
|
645
|
+
|
646
|
+
|
647
|
+
async def _async_get_element_by_guid_(self, element_guid: str) -> dict | str:
|
648
|
+
"""
|
649
|
+
Simplified, internal version of get_element_by_guid found in Classification Manager.
|
650
|
+
Retrieve an element by its guid. Async version.
|
651
|
+
|
652
|
+
Parameters
|
653
|
+
----------
|
654
|
+
element_guid: str
|
655
|
+
- unique identifier for the element
|
656
|
+
|
657
|
+
Returns
|
658
|
+
-------
|
659
|
+
dict | str
|
660
|
+
Returns a string if no element found; otherwise a dict of the element.
|
661
|
+
|
662
|
+
Raises
|
663
|
+
------
|
664
|
+
InvalidParameterException
|
665
|
+
one of the parameters is null or invalid or
|
666
|
+
PropertyServerException
|
667
|
+
There is a problem adding the element properties to the metadata repository or
|
668
|
+
UserNotAuthorizedException
|
669
|
+
the requesting user is not authorized to issue this request.
|
670
|
+
"""
|
671
|
+
|
672
|
+
body = {
|
673
|
+
"class": "EffectiveTimeQueryRequestBody",
|
674
|
+
"effectiveTime": None,
|
675
|
+
}
|
676
|
+
|
677
|
+
url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/classification-manager/elements/"
|
678
|
+
f"{element_guid}?forLineage=false&forDuplicateProcessing=false")
|
679
|
+
|
680
|
+
response: Response = await self._async_make_request("POST", url, body_slimmer(body))
|
681
|
+
|
682
|
+
elements = response.json().get("element", NO_ELEMENTS_FOUND)
|
683
|
+
|
684
|
+
return elements
|
685
|
+
|
686
|
+
|
687
|
+
if __name__ == "__main__":
|
688
|
+
print("Main-__client")
|