pyegeria 5.4.0.24__py3-none-any.whl → 5.4.0.26__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 (50) hide show
  1. commands/cat/debug_log +7373 -1452
  2. commands/cat/dr_egeria_md.py +21 -4
  3. commands/cat/logs/pyegeria.log +4 -0
  4. md_processing/.DS_Store +0 -0
  5. md_processing/__init__.py +7 -3
  6. md_processing/data/commands.json +1683 -2801
  7. md_processing/dr_egeria_inbox/product.md +69 -20
  8. md_processing/dr_egeria_outbox/.obsidian/workspace.json +5 -5
  9. md_processing/dr_egeria_outbox/monday/processed-2025-08-19 07:05-product.md +426 -0
  10. md_processing/dr_egeria_outbox/monday/processed-2025-08-19 07:56-product.md +212 -0
  11. md_processing/dr_egeria_outbox/monday/processed-2025-08-19 09:43-product.md +201 -0
  12. md_processing/dr_egeria_outbox/tuesday/processed-2025-08-19 10:55-product.md +209 -0
  13. md_processing/md_commands/governance_officer_commands.py +1 -73
  14. md_processing/md_commands/product_manager_commands.py +453 -211
  15. md_processing/md_processing_utils/common_md_proc_utils.py +60 -5
  16. md_processing/md_processing_utils/common_md_utils.py +21 -9
  17. md_processing/md_processing_utils/extraction_utils.py +2 -2
  18. md_processing/md_processing_utils/md_processing_constants.py +8 -7
  19. pyegeria/.DS_Store +0 -0
  20. pyegeria/__init__.py +4 -300
  21. pyegeria/_client_new.py +59 -11
  22. pyegeria/_output_formats.py +43 -0
  23. pyegeria/collection_manager.py +79 -14
  24. pyegeria/{data_designer_omvs.py → data_designer.py} +1171 -1675
  25. pyegeria/egeria_cat_client.py +2 -2
  26. pyegeria/egeria_client.py +4 -4
  27. pyegeria/egeria_tech_client.py +1 -1
  28. pyegeria/glossary_browser.py +1259 -0
  29. pyegeria/{glossary_manager_omvs.py → glossary_manager.py} +1181 -1099
  30. pyegeria/models.py +9 -3
  31. pyegeria/output_formatter.py +2 -1
  32. pyegeria/project_manager.py +1743 -0
  33. pyegeria/solution_architect_omvs.py +1 -1
  34. pyegeria/utils.py +4 -1
  35. {pyegeria-5.4.0.24.dist-info → pyegeria-5.4.0.26.dist-info}/METADATA +1 -1
  36. {pyegeria-5.4.0.24.dist-info → pyegeria-5.4.0.26.dist-info}/RECORD +39 -43
  37. commands/cat/debug_log.2025-08-15_09-14-07_444802.zip +0 -0
  38. commands/cat/debug_log.2025-08-16_10-21-59_388912.zip +0 -0
  39. md_processing/dr_egeria_outbox/monday/processed-2025-07-14 12:38-data_designer_out.md +0 -663
  40. md_processing/dr_egeria_outbox/monday/processed-2025-07-21 10:52-generated_help_report.md +0 -2744
  41. md_processing/dr_egeria_outbox/monday/processed-2025-07-21 18:38-collections.md +0 -62
  42. md_processing/dr_egeria_outbox/monday/processed-2025-08-01 11:34-gov_def.md +0 -444
  43. md_processing/dr_egeria_outbox/monday/processed-2025-08-17 21:04-product.md +0 -97
  44. pyegeria/collection_manager_omvs.py +0 -6541
  45. pyegeria/glossary_browser_omvs.py +0 -3840
  46. pyegeria/governance_officer_omvs.py +0 -2367
  47. pyegeria/project_manager_omvs.py +0 -1933
  48. {pyegeria-5.4.0.24.dist-info → pyegeria-5.4.0.26.dist-info}/LICENSE +0 -0
  49. {pyegeria-5.4.0.24.dist-info → pyegeria-5.4.0.26.dist-info}/WHEEL +0 -0
  50. {pyegeria-5.4.0.24.dist-info → pyegeria-5.4.0.26.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)
@@ -755,9 +756,21 @@ class Client2:
755
756
  return None
756
757
  return validated_body
757
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
+
758
771
  def validate_new_relationship_request(self, body: dict | NewRelationshipRequestBody,
759
772
  prop: str = None) -> NewRelationshipRequestBody | None:
760
- if isinstance(body, NewElementRequestBody):
773
+ if isinstance(body, NewRelationshipRequestBody):
761
774
  if (prop and body.properties.class_ == prop) or (prop is None):
762
775
  validated_body = body
763
776
  else:
@@ -775,6 +788,26 @@ class Client2:
775
788
 
776
789
  return validated_body
777
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
+
778
811
  def validate_delete_request(self, body: dict | DeleteRequestBody,
779
812
  cascade_delete: bool = False) -> DeleteRequestBody | None:
780
813
  if isinstance(body, DeleteRequestBody):
@@ -819,7 +852,7 @@ class Client2:
819
852
  elif status:
820
853
  body = {
821
854
  "class": "UpdateStatusRequestBody",
822
- "status": status
855
+ "newStatus": status
823
856
  }
824
857
  validated_body = UpdateStatusRequestBody.model_validate(body)
825
858
  else:
@@ -884,7 +917,7 @@ class Client2:
884
917
  logger.info(NO_ELEMENTS_FOUND)
885
918
  return NO_ELEMENTS_FOUND
886
919
 
887
- if output_format != 'JSON': # return a simplified markdown representation
920
+ if output_format.upper() != 'JSON': # return a simplified markdown representation
888
921
  # logger.info(f"Found elements, output format: {output_format} and output_format_set: {output_format_set}")
889
922
  return _gen_output(elements, search_string, _type,
890
923
  output_format, output_format_set)
@@ -939,11 +972,11 @@ class Client2:
939
972
  else:
940
973
  body = {
941
974
  "class": "GetRequestBody",
942
-
975
+ "metadataElementTypeName": _type
943
976
  }
944
977
  validated_body = GetRequestBody.model_validate(body)
945
978
 
946
- json_body = validated_body.model_dump_json(indent=2)
979
+ json_body = validated_body.model_dump_json(indent=2, exclude_none=True)
947
980
 
948
981
  response = await self._async_make_request("POST", url, json_body)
949
982
  elements = response.json().get("element", NO_ELEMENTS_FOUND)
@@ -972,7 +1005,7 @@ class Client2:
972
1005
  }
973
1006
  validated_body = ResultsRequestBody.model_validate(body)
974
1007
 
975
- json_body = validated_body.model_dump_json(indent=2)
1008
+ json_body = validated_body.model_dump_json(indent=2, exclude_none=True)
976
1009
 
977
1010
  response = await self._async_make_request("POST", url, json_body)
978
1011
  elements = response.json().get("elements", None)
@@ -996,7 +1029,15 @@ class Client2:
996
1029
  logger.info(json_body)
997
1030
  response = await self._async_make_request("POST", url, json_body)
998
1031
  logger.info(response.json())
999
- 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")
1000
1041
 
1001
1042
  async def _async_update_element_body_request(self, url: str, prop: list[str],
1002
1043
  body: dict | UpdateElementRequestBody = None) -> None:
@@ -1024,6 +1065,16 @@ class Client2:
1024
1065
  else:
1025
1066
  await self._async_make_request("POST", url)
1026
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
+
1027
1078
  async def _async_delete_request(self, url: str, body: dict | DeleteRequestBody = None,
1028
1079
  cascade_delete: bool = False) -> None:
1029
1080
  validated_body = self.validate_delete_request(body, cascade_delete)
@@ -1035,9 +1086,6 @@ class Client2:
1035
1086
  await self._async_make_request("POST", url)
1036
1087
 
1037
1088
 
1038
-
1039
-
1040
-
1041
1089
  async def _async_update_relationship_request(self, url: str, prop: str,
1042
1090
  body: dict | UpdateRelationshipRequestBody = None) -> None:
1043
1091
  validated_body = self.validate_update_relationship_request(body, prop)
@@ -121,7 +121,14 @@ COMMON_FORMATS_ALL = Format(
121
121
  columns=COMMON_COLUMNS,
122
122
  )
123
123
 
124
+ PROJECT_COLUMNS = COMMON_COLUMNS + [
125
+ Column(name='Priority', key='priority'),
126
+ Column(name='Project Status', key='project_status'),
127
+ Column(name='Start Date', key='start_date'),
128
+ Column(name='Assigned Actors', key='assigned_actors'),
129
+ Column(name='Resources', key='resource_list'),
124
130
 
131
+ ]
125
132
  COLLECTIONS_COLUMNS = COMMON_COLUMNS + [
126
133
  Column(name='Type Name', key='type_name'),
127
134
  Column(name='Classifications', key='classifications'),
@@ -143,6 +150,14 @@ COLLECTION_DICT = Format(
143
150
  ],
144
151
  )
145
152
 
153
+ BASIC_COLLECTIONS_COLUMNS = [
154
+ Column(name='Qualified Name', key='qualified_name', format=True),
155
+ Column(name='GUID', key='guid', format=True),
156
+ Column(name='Type Name', key='type_name'),
157
+ Column(name="Containing Members", key='collection_members'),
158
+ Column(name="Member Of", key='member_of_collections')
159
+ ]
160
+
146
161
  COLLECTION_REPORT = Format(
147
162
  types=["REPORT"],
148
163
  columns=COLLECTIONS_MEMBERS_COLUMNS + [
@@ -220,6 +235,17 @@ output_format_sets = FormatSetDict({
220
235
  )
221
236
  ],
222
237
  ),
238
+ "Project": FormatSet(
239
+ heading="Project Attributes",
240
+ description="Attributes that apply to all Projects.",
241
+ annotations={},
242
+ formats=[
243
+ Format(
244
+ types=["ALL"],
245
+ columns=PROJECT_COLUMNS
246
+ )
247
+ ]
248
+ ),
223
249
  "Basic-Terms": FormatSet(
224
250
  heading="Basic Glossary Term Attributes",
225
251
  description="Attributes that apply to all Basic Glossary Terms.",
@@ -239,6 +265,8 @@ output_format_sets = FormatSetDict({
239
265
  Column(name="Effective To", key='effective_to'),
240
266
  Column(name="GUID", key='guid'),
241
267
  Column(name="Open Metadata Type Name", key='type_name'),
268
+ Column(name="Glossary", key='parent_glossary'),
269
+ Column(name="Subject Aream", key='subject_area'),
242
270
  ],
243
271
  )
244
272
  ]
@@ -257,6 +285,21 @@ output_format_sets = FormatSetDict({
257
285
  spec_params={},
258
286
  )
259
287
  ),
288
+ "BasicCollections": FormatSet(
289
+ heading="Common Collection Information",
290
+ description="Attributes generic to all Collections.",
291
+ aliases=[],
292
+ annotations=COMMON_ANNOTATIONS,
293
+ formats=[Format(
294
+ types=["ALL"],
295
+ columns=BASIC_COLLECTIONS_COLUMNS,
296
+ )], # Reusing common formats
297
+ action=ActionParameter(
298
+ function="CollectionManager.find_collections",
299
+ user_params=["search_string"],
300
+ spec_params={},
301
+ )
302
+ ),
260
303
 
261
304
  "CollectionMembers": FormatSet(
262
305
  heading="Collection Membership Information",
@@ -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: