pyegeria 5.4.0.23__py3-none-any.whl → 5.4.0.25__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 (54) hide show
  1. commands/cat/debug_log +7967 -465
  2. commands/cat/debug_log.2025-08-15_09-14-07_444802.zip +0 -0
  3. commands/cat/debug_log.2025-08-16_10-21-59_388912.zip +0 -0
  4. commands/cat/debug_log.2025-08-17_11-34-27_981852.zip +0 -0
  5. commands/cat/dr_egeria_md.py +36 -6
  6. commands/cat/logs/pyegeria.log +3 -135
  7. md_processing/.DS_Store +0 -0
  8. md_processing/__init__.py +12 -6
  9. md_processing/data/commands.json +8523 -2234
  10. md_processing/dr_egeria_inbox/gov_def.md +76 -18
  11. md_processing/dr_egeria_inbox/img.png +0 -0
  12. md_processing/dr_egeria_inbox/product.md +185 -24
  13. md_processing/dr_egeria_outbox/.obsidian/workspace.json +5 -5
  14. md_processing/dr_egeria_outbox/monday/processed-2025-08-19 07:05-product.md +426 -0
  15. md_processing/dr_egeria_outbox/monday/processed-2025-08-19 07:56-product.md +212 -0
  16. md_processing/dr_egeria_outbox/monday/processed-2025-08-19 09:43-product.md +201 -0
  17. md_processing/dr_egeria_outbox/tuesday/processed-2025-08-19 10:55-product.md +209 -0
  18. md_processing/md_commands/governance_officer_commands.py +247 -178
  19. md_processing/md_commands/product_manager_commands.py +730 -580
  20. md_processing/md_processing_utils/common_md_proc_utils.py +170 -12
  21. md_processing/md_processing_utils/common_md_utils.py +126 -28
  22. md_processing/md_processing_utils/extraction_utils.py +2 -2
  23. md_processing/md_processing_utils/md_processing_constants.py +14 -10
  24. pyegeria/.DS_Store +0 -0
  25. pyegeria/__init__.py +1 -1
  26. pyegeria/_client_new.py +61 -12
  27. pyegeria/_exceptions_new.py +6 -0
  28. pyegeria/_output_formats.py +42 -2
  29. pyegeria/collection_manager.py +79 -14
  30. pyegeria/{data_designer_omvs.py → data_designer.py} +1171 -1675
  31. pyegeria/glossary_browser.py +1259 -0
  32. pyegeria/{glossary_manager_omvs.py → glossary_manager.py} +1181 -1099
  33. pyegeria/governance_officer.py +1 -3
  34. pyegeria/load_config.py +1 -1
  35. pyegeria/models.py +37 -4
  36. pyegeria/output_formatter.py +2 -1
  37. pyegeria/project_manager.py +1952 -0
  38. pyegeria/utils.py +5 -2
  39. {pyegeria-5.4.0.23.dist-info → pyegeria-5.4.0.25.dist-info}/METADATA +1 -1
  40. {pyegeria-5.4.0.23.dist-info → pyegeria-5.4.0.25.dist-info}/RECORD +43 -44
  41. md_processing/dr_egeria_outbox/friday/processed-2025-07-18 15:00-product.md +0 -62
  42. md_processing/dr_egeria_outbox/friday/processed-2025-07-18 15:13-product.md +0 -62
  43. md_processing/dr_egeria_outbox/friday/processed-2025-07-20 13:23-product.md +0 -47
  44. md_processing/dr_egeria_outbox/friday/processed-2025-08-01 11:55-data_test3.md +0 -503
  45. md_processing/dr_egeria_outbox/monday/processed-2025-07-14 12:38-data_designer_out.md +0 -663
  46. md_processing/dr_egeria_outbox/monday/processed-2025-07-21 10:52-generated_help_report.md +0 -2744
  47. md_processing/dr_egeria_outbox/monday/processed-2025-07-21 18:38-collections.md +0 -62
  48. md_processing/dr_egeria_outbox/monday/processed-2025-08-01 11:34-gov_def.md +0 -444
  49. md_processing/dr_egeria_outbox/processed-2025-08-03 16:05-glossary_list.md +0 -37
  50. pyegeria/glossary_browser_omvs.py +0 -3840
  51. pyegeria/governance_officer_omvs.py +0 -2367
  52. {pyegeria-5.4.0.23.dist-info → pyegeria-5.4.0.25.dist-info}/LICENSE +0 -0
  53. {pyegeria-5.4.0.23.dist-info → pyegeria-5.4.0.25.dist-info}/WHEEL +0 -0
  54. {pyegeria-5.4.0.23.dist-info → pyegeria-5.4.0.25.dist-info}/entry_points.txt +0 -0
pyegeria/_client_new.py CHANGED
@@ -35,7 +35,7 @@ from pyegeria._validators import (
35
35
  )
36
36
  from pyegeria.models import SearchStringRequestBody, FilterRequestBody, GetRequestBody, NewElementRequestBody, \
37
37
  TemplateRequestBody, UpdateStatusRequestBody, UpdateElementRequestBody, NewRelationshipRequestBody, \
38
- DeleteRequestBody, UpdateRelationshipRequestBody, ResultsRequestBody
38
+ DeleteRequestBody, UpdateRelationshipRequestBody, ResultsRequestBody, NewClassificationRequestBody
39
39
  from pyegeria.utils import body_slimmer
40
40
 
41
41
  ...
@@ -146,6 +146,7 @@ class Client2:
146
146
  self._update_element_request_adapter = TypeAdapter(UpdateElementRequestBody)
147
147
  self._update_status_request_adapter = TypeAdapter(UpdateStatusRequestBody)
148
148
  self._new_relationship_request_adapter = TypeAdapter(NewRelationshipRequestBody)
149
+ self._new_classification_request_adapter = TypeAdapter(NewClassificationRequestBody)
149
150
  self._delete_request_adapter = TypeAdapter(DeleteRequestBody)
150
151
  self._template_request_adapter = TypeAdapter(TemplateRequestBody)
151
152
  self._update_relationship_request_adapter = TypeAdapter(UpdateRelationshipRequestBody)
@@ -476,7 +477,8 @@ class Client2:
476
477
  except (HTTPStatusError, httpx.HTTPStatusError, httpx.RequestError) as e:
477
478
  # context["caught_exception"] = e
478
479
  # context['HTTPStatusCode'] = e.response.status_code
479
- additional_info = {"userid": self.user_id}
480
+ additional_info = {"userid": self.user_id, "reason": response.text}
481
+
480
482
  raise PyegeriaClientException(response, context, additional_info, e)
481
483
  #
482
484
  # except json.JSONDecodeError as e:
@@ -754,9 +756,21 @@ class Client2:
754
756
  return None
755
757
  return validated_body
756
758
 
759
+ def validate_new_element_from_template_request(self, body: dict | TemplateRequestBody
760
+ ) -> NewElementRequestBody | None:
761
+ if isinstance(body, TemplateRequestBody):
762
+ validated_body = body
763
+
764
+ elif isinstance(body, dict):
765
+ # if body.get("properties", {}).get("class", "") == prop:
766
+ validated_body = self._template_request_adapter.validate_python(body)
767
+ else:
768
+ return None
769
+ return validated_body
770
+
757
771
  def validate_new_relationship_request(self, body: dict | NewRelationshipRequestBody,
758
772
  prop: str = None) -> NewRelationshipRequestBody | None:
759
- if isinstance(body, NewElementRequestBody):
773
+ if isinstance(body, NewRelationshipRequestBody):
760
774
  if (prop and body.properties.class_ == prop) or (prop is None):
761
775
  validated_body = body
762
776
  else:
@@ -774,6 +788,26 @@ class Client2:
774
788
 
775
789
  return validated_body
776
790
 
791
+ def validate_new_classification_request(self, body: dict | NewClassificationRequestBody,
792
+ prop: str = None) -> NewClassificationRequestBody | None:
793
+ if isinstance(body, NewClassificationRequestBody):
794
+ if (prop and body.properties.class_ == prop) or (prop is None):
795
+ validated_body = body
796
+ else:
797
+ raise PyegeriaInvalidParameterException(additional_info=
798
+ {"reason": "unexpected property class name"})
799
+
800
+ elif isinstance(body, dict):
801
+ if body.get("properties", {}).get("class", "") == prop:
802
+ validated_body = self._new_classification_request_adapter.validate_python(body)
803
+ else:
804
+ raise PyegeriaInvalidParameterException(additional_info=
805
+ {"reason": "unexpected property class name"})
806
+ else:
807
+ return None
808
+
809
+ return validated_body
810
+
777
811
  def validate_delete_request(self, body: dict | DeleteRequestBody,
778
812
  cascade_delete: bool = False) -> DeleteRequestBody | None:
779
813
  if isinstance(body, DeleteRequestBody):
@@ -818,9 +852,9 @@ class Client2:
818
852
  elif status:
819
853
  body = {
820
854
  "class": "UpdateStatusRequestBody",
821
- "status": status
855
+ "newStatus": status
822
856
  }
823
- validated_body = UpdateStatusRequestBody.validate_python(body)
857
+ validated_body = UpdateStatusRequestBody.model_validate(body)
824
858
  else:
825
859
  raise PyegeriaInvalidParameterException(additional_info={"reason": "invalid parameters"})
826
860
 
@@ -938,11 +972,11 @@ class Client2:
938
972
  else:
939
973
  body = {
940
974
  "class": "GetRequestBody",
941
-
975
+ "metadataElementTypeName": _type
942
976
  }
943
977
  validated_body = GetRequestBody.model_validate(body)
944
978
 
945
- json_body = validated_body.model_dump_json(indent=2)
979
+ json_body = validated_body.model_dump_json(indent=2, exclude_none=True)
946
980
 
947
981
  response = await self._async_make_request("POST", url, json_body)
948
982
  elements = response.json().get("element", NO_ELEMENTS_FOUND)
@@ -971,7 +1005,7 @@ class Client2:
971
1005
  }
972
1006
  validated_body = ResultsRequestBody.model_validate(body)
973
1007
 
974
- json_body = validated_body.model_dump_json(indent=2)
1008
+ json_body = validated_body.model_dump_json(indent=2, exclude_none=True)
975
1009
 
976
1010
  response = await self._async_make_request("POST", url, json_body)
977
1011
  elements = response.json().get("elements", None)
@@ -995,7 +1029,15 @@ class Client2:
995
1029
  logger.info(json_body)
996
1030
  response = await self._async_make_request("POST", url, json_body)
997
1031
  logger.info(response.json())
998
- return response.json().get("guid")
1032
+ return response.json().get("guid", "NO_GUID_RETURNED")
1033
+
1034
+ async def _async_create_element_from_template(self, url: str, body: dict | TemplateRequestBody = None) -> str:
1035
+ validated_body = self.validate_new_element_from_template_request(body)
1036
+ json_body = validated_body.model_dump_json(indent=2, exclude_none=True)
1037
+ logger.info(json_body)
1038
+ response = await self._async_make_request("POST", url, json_body, is_json=True)
1039
+ logger.info(response.json())
1040
+ return response.json().get("guid", "NO_GUID_RETURNED")
999
1041
 
1000
1042
  async def _async_update_element_body_request(self, url: str, prop: list[str],
1001
1043
  body: dict | UpdateElementRequestBody = None) -> None:
@@ -1023,6 +1065,16 @@ class Client2:
1023
1065
  else:
1024
1066
  await self._async_make_request("POST", url)
1025
1067
 
1068
+ async def _async_new_classification_request(self, url: str, prop: str,
1069
+ body: dict | NewRelationshipRequestBody = None) -> None:
1070
+ validated_body = self.validate_new_classification_request(body, prop)
1071
+ if validated_body:
1072
+ json_body = validated_body.model_dump_json(indent=2, exclude_none=True)
1073
+ logger.info(json_body)
1074
+ await self._async_make_request("POST", url, json_body)
1075
+ else:
1076
+ await self._async_make_request("POST", url)
1077
+
1026
1078
  async def _async_delete_request(self, url: str, body: dict | DeleteRequestBody = None,
1027
1079
  cascade_delete: bool = False) -> None:
1028
1080
  validated_body = self.validate_delete_request(body, cascade_delete)
@@ -1034,9 +1086,6 @@ class Client2:
1034
1086
  await self._async_make_request("POST", url)
1035
1087
 
1036
1088
 
1037
-
1038
-
1039
-
1040
1089
  async def _async_update_relationship_request(self, url: str, prop: str,
1041
1090
  body: dict | UpdateRelationshipRequestBody = None) -> None:
1042
1091
  validated_body = self.validate_update_relationship_request(body, prop)
@@ -374,6 +374,11 @@ def print_exception_table(e: PyegeriaException):
374
374
  def print_basic_exception(e: PyegeriaException):
375
375
  """Prints the exception response"""
376
376
  related_code = e.related_http_code if hasattr(e, "related_http_code") else ""
377
+ http_reason = e.response.text if e.response else ""
378
+ table = Table(title=f"Exception: {e.__class__.__name__}", show_lines=True, header_style="bold", box=box.HEAVY_HEAD)
379
+ table.caption = e.pyegeria_code
380
+ table.add_column("Facet", justify="center")
381
+ table.add_column("Item", justify="left", width=80)
377
382
  related_response = e.response.json() if e.response else ""
378
383
  table = Table(title=f"Exception: {e.__class__.__name__}", show_lines=True, header_style="bold", box=box.HEAVY_HEAD)
379
384
  table.caption = e.pyegeria_code
@@ -382,6 +387,7 @@ def print_basic_exception(e: PyegeriaException):
382
387
 
383
388
  if isinstance(e, PyegeriaException):
384
389
  table.add_row("HTTP Code", str(e.response_code))
390
+ table.add_row("HTTP Reason", str(http_reason))
385
391
  table.add_row("Egeria Code", str(related_code))
386
392
  table.add_row("Caller Method", e.context.get("caller method", "---")) if e.context else ""
387
393
  table.add_row("Request URL", str(e.response_url))
@@ -143,6 +143,14 @@ COLLECTION_DICT = Format(
143
143
  ],
144
144
  )
145
145
 
146
+ BASIC_COLLECTIONS_COLUMNS = [
147
+ Column(name='Qualified Name', key='qualified_name', format=True),
148
+ Column(name='GUID', key='guid', format=True),
149
+ Column(name='Type Name', key='type_name'),
150
+ Column(name="Containing Members", key='collection_members'),
151
+ Column(name="Member Of", key='member_of_collections')
152
+ ]
153
+
146
154
  COLLECTION_REPORT = Format(
147
155
  types=["REPORT"],
148
156
  columns=COLLECTIONS_MEMBERS_COLUMNS + [
@@ -163,7 +171,11 @@ GOVERNANCE_DEFINITIONS_COLUMNS = COMMON_COLUMNS + [
163
171
  Column(name="Scope", key='scope'),
164
172
  Column(name="Type", key='type_name'),
165
173
  ]
166
-
174
+ GOVERNANCE_DEFINITIONS_BASIC = [
175
+ Column(name="Type", key='type_name'),
176
+ Column(name='Qualified Name', key='qualified_name', format=True),
177
+ Column(name="GUID", key='guid', format=True),
178
+ ]
167
179
  COMMON_ANNOTATIONS = {
168
180
  "wikilinks": ["[[Commons]]"]
169
181
  }
@@ -235,6 +247,8 @@ output_format_sets = FormatSetDict({
235
247
  Column(name="Effective To", key='effective_to'),
236
248
  Column(name="GUID", key='guid'),
237
249
  Column(name="Open Metadata Type Name", key='type_name'),
250
+ Column(name="Glossary", key='parent_glossary'),
251
+ Column(name="Subject Aream", key='subject_area'),
238
252
  ],
239
253
  )
240
254
  ]
@@ -253,6 +267,21 @@ output_format_sets = FormatSetDict({
253
267
  spec_params={},
254
268
  )
255
269
  ),
270
+ "BasicCollections": FormatSet(
271
+ heading="Common Collection Information",
272
+ description="Attributes generic to all Collections.",
273
+ aliases=[],
274
+ annotations=COMMON_ANNOTATIONS,
275
+ formats=[Format(
276
+ types=["ALL"],
277
+ columns=BASIC_COLLECTIONS_COLUMNS,
278
+ )], # Reusing common formats
279
+ action=ActionParameter(
280
+ function="CollectionManager.find_collections",
281
+ user_params=["search_string"],
282
+ spec_params={},
283
+ )
284
+ ),
256
285
 
257
286
  "CollectionMembers": FormatSet(
258
287
  heading="Collection Membership Information",
@@ -406,7 +435,18 @@ output_format_sets = FormatSetDict({
406
435
  spec_params={"output_format":"DICT"},
407
436
  )
408
437
  ),
409
-
438
+ "Governance Basics": FormatSet(
439
+ heading="Basic Governance-Definitions Information",
440
+ description="Core Attributes useful to Governance-Definitions.",
441
+ aliases=["BasicGovernance"],
442
+ annotations={"wikilinks": ["[[Governance]]"]},
443
+ formats=[Format(types=["ALL"], columns=GOVERNANCE_DEFINITIONS_BASIC)],
444
+ action=ActionParameter(
445
+ function="GovernanceOfficer.find_governance_definitions",
446
+ user_params=["search_string"],
447
+ spec_params={},
448
+ )
449
+ ),
410
450
  "Governance Definitions": FormatSet(
411
451
  heading="Governance-Definitions Information",
412
452
  description="Attributes useful to Governance-Definitions.",
@@ -672,7 +672,7 @@ class CollectionManager(Client2):
672
672
  ----
673
673
  Body sample:
674
674
  {
675
- "class": "AnyTimeRequestBody",
675
+ "class": "GetRequestBody",
676
676
  "asOfTime": "{{$isoTimestamp}}",
677
677
  "effectiveTime": "{{$isoTimestamp}}",
678
678
  "forLineage": false,
@@ -680,7 +680,7 @@ class CollectionManager(Client2):
680
680
  }
681
681
  """
682
682
 
683
- url = str(HttpUrl(f"{self.collection_command_root}/{collection_guid}"))
683
+ url = str(HttpUrl(f"{self.collection_command_root}/{collection_guid}/retrieve"))
684
684
  type = element_type if element_type else "Collection"
685
685
 
686
686
  response = await self._async_get_guid_request(url, _type=type,
@@ -1414,6 +1414,26 @@ class CollectionManager(Client2):
1414
1414
  self._async_create_collection(display_name, description, category,
1415
1415
  ["ContextEvent"], body))
1416
1416
 
1417
+ @dynamic_catch
1418
+ def create_glossary_category(self, display_name: str, parent_guid: str, description: str = None ) -> str:
1419
+ """Create a new glossary category."""
1420
+ body = {
1421
+ "class": "NewRelationshipRequestBody",
1422
+ "parentGUID": parent_guid,
1423
+ "parentRelationshipTypeName": "CategoryHierarchy",
1424
+ "parentAtEnd1": True,
1425
+ "is_own_anchor": False,
1426
+ "anchor_guid": parent_guid,
1427
+ "properties": {
1428
+ "class": "GlossaryCategoryProperties",
1429
+ "displayName": display_name,
1430
+ "description": description,
1431
+ "parentCategory": parent_guid,
1432
+ },
1433
+ }
1434
+ response = self.create_collection(body=body)
1435
+ return response
1436
+
1417
1437
  @dynamic_catch
1418
1438
  async def _async_create_data_spec_collection(self, display_name: str = None, description: str = None,
1419
1439
  category: str = None, classification_name: str = None,
@@ -1520,15 +1540,16 @@ class CollectionManager(Client2):
1520
1540
  }
1521
1541
 
1522
1542
  """
1523
- validated_body = self.validate_new_element_request(body,"DataSpecProperties")
1524
-
1525
- if validated_body is None and display_name is not None:
1543
+ if body:
1544
+ validated_body = self.validate_new_element_request(body,"DataSpecProperties")
1545
+ elif display_name is not None:
1526
1546
  qualified_name = self.__create_qualified_name__("DataSpec", display_name, EGERIA_LOCAL_QUALIFIER)
1527
- print(f"\n\tDisplayName was {display_name}, classification {classification_name}\n")
1528
- initial_classifications_data = {"class" : "ClassificationProperties"}
1547
+ logger.info(f"\n\tDisplayName was {display_name}, classification {classification_name}\n")
1529
1548
  if classification_name:
1530
- initial_classification_dict = {
1531
- classification_name: InitialClassifications.model_validate(initial_classifications_data)
1549
+ initial_classification_data = {
1550
+ classification_name: {
1551
+ "class" : "ClassificationProperties"
1552
+ }
1532
1553
  }
1533
1554
  else:
1534
1555
  initial_classification_dict = None
@@ -2398,12 +2419,11 @@ class CollectionManager(Client2):
2398
2419
  """
2399
2420
 
2400
2421
  url = (f"{self.collection_command_root}/{collection_guid}/update")
2401
- await self._async_update_element_request_body(url, "DigitalProductProperties", body )
2422
+ await self._async_update_element_body_request(url, "DigitalProductProperties", body )
2402
2423
 
2403
2424
 
2404
2425
  @dynamic_catch
2405
- def update_digital_product(self, collection_guid: str, body: dict | UpdateElementRequestBody,
2406
- merge_update: bool = True) -> None:
2426
+ def update_digital_product(self, collection_guid: str, body: dict | UpdateElementRequestBody,) -> None:
2407
2427
  """ Update the properties of a digital product..
2408
2428
  Collections: https://egeria-project.org/concepts/collection
2409
2429
 
@@ -2467,7 +2487,7 @@ class CollectionManager(Client2):
2467
2487
  """
2468
2488
 
2469
2489
  return asyncio.get_event_loop().run_until_complete(
2470
- self._async_update_digital_product(collection_guid, body, merge_update))
2490
+ self._async_update_digital_product(collection_guid, body))
2471
2491
 
2472
2492
 
2473
2493
  @dynamic_catch
@@ -2512,7 +2532,7 @@ class CollectionManager(Client2):
2512
2532
  }
2513
2533
  """
2514
2534
 
2515
- url = f"{self.collection_command_root}/{collection_guid}/update-status"
2535
+ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/collection-manager/metadata-elements/{collection_guid}/update-status"
2516
2536
  await self._async_update_status_request(url, status, body)
2517
2537
 
2518
2538
  @dynamic_catch
@@ -5071,6 +5091,12 @@ class CollectionManager(Client2):
5071
5091
  loop = asyncio.get_event_loop()
5072
5092
  loop.run_until_complete(self._async_add_to_collection(collection_guid, element_guid, body))
5073
5093
 
5094
+ def add_term_to_category(self, category_guid: str, term_guid: str,
5095
+ body: dict | NewRelationshipRequestBody = None) -> None:
5096
+ """Add a term to a category. The request body is optional."""
5097
+ loop = asyncio.get_event_loop()
5098
+ loop.run_until_complete(self._async_add_to_collection(category_guid, term_guid, body))
5099
+
5074
5100
 
5075
5101
  @dynamic_catch
5076
5102
  async def _async_update_collection_membership_prop(self, collection_guid: str, element_guid: str, body: dict = None,
@@ -5296,6 +5322,45 @@ class CollectionManager(Client2):
5296
5322
  #
5297
5323
 
5298
5324
 
5325
+ def remove_term_from_category(self, category_guid: str, term_guid: str,
5326
+ body: dict | DeleteRequestBody= None) -> None:
5327
+ """Remove a term from a category.
5328
+
5329
+ Parameters
5330
+ ----------
5331
+ category_guid: str
5332
+ identity of the collection to return members for.
5333
+ term_guid: str
5334
+ Effective time of the query. If not specified will default to any time.
5335
+ body: dict, optional, defaults to None
5336
+ The body of the request to add to the collection. See notes.
5337
+
5338
+ Returns
5339
+ -------
5340
+ None
5341
+
5342
+ Raises
5343
+ ------
5344
+
5345
+ InvalidParameterException
5346
+ If the client passes incorrect parameters on the request - such as bad URLs or invalid values
5347
+ PropertyServerException
5348
+ Raised by the server when an issue arises in processing a valid request
5349
+ NotAuthorizedException
5350
+ The principle specified by the user_id does not have authorization for the requested action
5351
+
5352
+ Notes
5353
+ -----
5354
+
5355
+ """
5356
+ loop = asyncio.get_event_loop()
5357
+ loop.run_until_complete(self._async_remove_from_collection(category_guid, term_guid, body))
5358
+
5359
+ #
5360
+ #
5361
+ #
5362
+
5363
+
5299
5364
  @dynamic_catch
5300
5365
  async def _async_get_member_list(self, collection_guid: str = None, collection_name: str = None,
5301
5366
  collection_qname: str = None, ) -> list | str: