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.
Files changed (43) hide show
  1. commands/cat/__init__.py +1 -17
  2. commands/cat/dr_egeria_md.py +6 -4
  3. commands/cat/list_collections.py +46 -36
  4. md_processing/__init__.py +5 -2
  5. md_processing/data/commands-working.json +34850 -0
  6. md_processing/data/commands.json +1750 -530
  7. md_processing/md_commands/product_manager_commands.py +171 -220
  8. md_processing/md_processing_utils/common_md_proc_utils.py +9 -0
  9. md_processing/md_processing_utils/common_md_utils.py +15 -2
  10. md_processing/md_processing_utils/md_processing_constants.py +44 -6
  11. pyegeria/__init__.py +8 -4
  12. pyegeria/_client.py +2 -1
  13. pyegeria/_client_new.py +688 -0
  14. pyegeria/_exceptions_new.py +364 -0
  15. pyegeria/_globals.py +3 -1
  16. pyegeria/_output_formats.py +196 -0
  17. pyegeria/_validators.py +72 -199
  18. pyegeria/collection_manager_omvs.py +602 -324
  19. pyegeria/data_designer_omvs.py +251 -203
  20. pyegeria/load_config.py +206 -0
  21. pyegeria/logging_configuration.py +204 -0
  22. pyegeria/output_formatter.py +162 -31
  23. pyegeria/utils.py +99 -61
  24. {pyegeria-5.4.0.dev14.dist-info → pyegeria-5.4.0.2.dist-info}/METADATA +4 -1
  25. {pyegeria-5.4.0.dev14.dist-info → pyegeria-5.4.0.2.dist-info}/RECORD +28 -37
  26. commands/cat/debug_log +0 -2806
  27. commands/cat/debug_log.2025-07-15_14-28-38_087378.zip +0 -0
  28. commands/cat/debug_log.2025-07-16_15-48-50_037087.zip +0 -0
  29. md_processing/dr_egeria_outbox-pycharm/.obsidian/app.json +0 -1
  30. md_processing/dr_egeria_outbox-pycharm/.obsidian/appearance.json +0 -1
  31. md_processing/dr_egeria_outbox-pycharm/.obsidian/core-plugins.json +0 -31
  32. md_processing/dr_egeria_outbox-pycharm/.obsidian/workspace.json +0 -177
  33. md_processing/dr_egeria_outbox-pycharm/monday/processed-2025-07-14 12:38-data_designer_out.md +0 -663
  34. md_processing/dr_egeria_outbox-pycharm/thursday/processed-2025-07-17 15:00-Derive-Dr-Gov-Defs.md +0 -719
  35. md_processing/dr_egeria_outbox-pycharm/thursday/processed-2025-07-17 20:13-Derive-Dr-Gov-Defs.md +0 -41
  36. md_processing/dr_egeria_outbox-pycharm/thursday/processed-2025-07-17 20:14-Derive-Dr-Gov-Defs.md +0 -33
  37. md_processing/dr_egeria_outbox-pycharm/thursday/processed-2025-07-17 20:50-Derive-Dr-Gov-Defs.md +0 -192
  38. md_processing/dr_egeria_outbox-pycharm/tuesday/processed-2025-07-16 19:15-gov_def2.md +0 -527
  39. md_processing/dr_egeria_outbox-pycharm/tuesday/processed-2025-07-17 12:08-gov_def2.md +0 -527
  40. md_processing/dr_egeria_outbox-pycharm/tuesday/processed-2025-07-17 14:27-gov_def2.md +0 -474
  41. {pyegeria-5.4.0.dev14.dist-info → pyegeria-5.4.0.2.dist-info}/LICENSE +0 -0
  42. {pyegeria-5.4.0.dev14.dist-info → pyegeria-5.4.0.2.dist-info}/WHEEL +0 -0
  43. {pyegeria-5.4.0.dev14.dist-info → pyegeria-5.4.0.2.dist-info}/entry_points.txt +0 -0
@@ -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")