From a89ddbca52ad3600d111c3cdc50d3d9e927e4abc Mon Sep 17 00:00:00 2001 From: Dan Wolfson Date: Sat, 31 Oct 2026 16:05:34 -0500 Subject: [PATCH 1/7] docs(issues): log ISSUE-174, link methods discard the new relationship GUID Signed-off-by: Dan Wolfson --- PYEGERIA_ISSUES.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/PYEGERIA_ISSUES.md b/PYEGERIA_ISSUES.md index 72848087..305d52e5 100644 --- a/PYEGERIA_ISSUES.md +++ b/PYEGERIA_ISSUES.md @@ -148,7 +148,7 @@ content was changed. The numbered section list above describes the # Open Issues -**At a glance** — 3 open pyegeria items, 0 awaiting re-test, 0 open Egeria Server issues (2026-10-31: ISSUE-173 added, report specs by registered name and the `pyegeria.report_specs` entry points. 2026-10-30: ISSUE-168, released in 6.2.16, and ISSUE-169..172, fixed for 6.2.17, moved to the Appendix. 2026-10-30: ISSUE-146's `remove_note` re-test passed, ISSUE-79's folder survey completes, ISSUE-108 did not reproduce at 200 links; all three moved to the Appendix. 2026-10-29: ISSUE-159, 132 and 85 live-verified; 12 Egeria-side fixes re-confirmed or closed on earlier passes and moved to the Appendix). +**At a glance** — 4 open pyegeria items, 0 awaiting re-test, 0 open Egeria Server issues (2026-10-31: ISSUE-174 added, link methods discard relationship GUIDs. ISSUE-173 added, released in 6.2.18, report specs by registered name and the `pyegeria.report_specs` entry points. 2026-10-30: ISSUE-168, released in 6.2.16, and ISSUE-169..172, fixed for 6.2.17, moved to the Appendix. 2026-10-30: ISSUE-146's `remove_note` re-test passed, ISSUE-79's folder survey completes, ISSUE-108 did not reproduce at 200 links; all three moved to the Appendix. 2026-10-29: ISSUE-159, 132 and 85 live-verified; 12 Egeria-side fixes re-confirmed or closed on earlier passes and moved to the Appendix). **Egeria team note, 2026-10-18 (Mandy Chessell).** Saved verbatim at `~/.re-gate/EGERIA-TEAM-NOTE-2026-10-18.md`. Each entry below has a dated @@ -192,6 +192,14 @@ into the entry now, so it isn't rediscovered from scratch later. --- +### ISSUE-174: 142 `link_*` methods throw away the relationship GUID the shared helper now returns + +**Layer:** pyegeria · **Status:** open, logged 2026-10-31 for a fix and release next session · **Found:** 2026-10-31, Resource Explorer D1-b ("Link the pack's 22 schemas"). `AssetMaker.link_data_set_content` returns None, so the handler re-reads each schema after every attach to learn the new DataSetContent relationship's GUID. + +**What:** `ServerClient._async_new_relationship_request` already returns the new relationship's GUID (`GUIDResponse.guid`; that was added earlier as "purely additive"). But 142 of its 160 callers in `pyegeria/omvs` call it as a bare `await ...` and return None, as do their sync wrappers. The worst-affected modules are asset_maker (22), schema_maker (15), solution_architect (12), data_designer (11) and governance_officer (10). It matters most for multi-link relationship types such as DataSetContent: attach creates a new instance each time, and update/detach need that instance's own GUID. + +**Fix (proposed):** return the helper's result from each `_async_link_*`/`_async_attach_*`/`_async_setup_*`, and from its sync wrapper, typed `str | None` in the docstring's Returns. This is purely additive, since callers that ignore the value are unaffected. A micro-test should stub `_async_make_request` and check that a representative sample (and an AST check that no omvs method discards the helper's result) returns the GUID. Do Dr.Egeria link processors next if any re-read to find the GUID. + ### ISSUE-173: Report specs named analytic functions by dotted path and loaded package specs through env vars; added the `pyegeria.report_specs` entry-point group and deprecated both **Layer:** pyegeria · **Status:** built on branch `feat/analytic-registered-names`, no PR yet; targets 6.2.18, with removal in 6.2.19 · **Found:** 2026-10-31, the owner's ruling on Mandy's question 2 (portal analytics discovery), "yes to (b)", then "a" for report-spec registration (the Architect's R1, split note §11.3). From 21fe5bf615fb43fa938cb0c7abb8b4482ce22e95 Mon Sep 17 00:00:00 2001 From: Dan Wolfson Date: Sat, 31 Oct 2026 16:29:29 -0500 Subject: [PATCH 2/7] fix(omvs): link/attach/setup methods return the new relationship GUID (ISSUE-174) ServerClient._async_new_relationship_request returns the GUID from the server's GUIDResponse, but about 142 callers in pyegeria/omvs awaited it bare and returned None, and so did their sync wrappers. Every such method, its sync wrapper, and intermediate async methods now return the GUID. Their -> None annotations and Returns docstrings are updated. This is additive: callers that ignore the value are unaffected. Adds an AST guard test over pyegeria/omvs and a stubbed round-trip test for a sample of methods across seven clients. Claude-Session: https://claude.ai/code/session_015pdMuPiVFuZhoGLsBSFaN3 Signed-off-by: Dan Wolfson --- PYEGERIA_ISSUES.md | 4 +- pyegeria/omvs/action_author.py | 30 +-- pyegeria/omvs/actor_manager.py | 49 ++-- pyegeria/omvs/asset_maker.py | 218 ++++++++++-------- pyegeria/omvs/classification_explorer.py | 61 ++--- pyegeria/omvs/collection_manager.py | 66 +++--- pyegeria/omvs/community_matters_omvs.py | 8 +- pyegeria/omvs/connection_maker.py | 12 +- pyegeria/omvs/data_designer.py | 142 +++++++----- pyegeria/omvs/data_discovery.py | 78 ++++--- pyegeria/omvs/digital_business.py | 28 ++- pyegeria/omvs/external_links.py | 9 +- pyegeria/omvs/glossary_manager.py | 24 +- pyegeria/omvs/governance_officer.py | 132 ++++++----- pyegeria/omvs/location_arena.py | 27 ++- pyegeria/omvs/people_organizer.py | 28 ++- pyegeria/omvs/privacy_officer.py | 16 +- pyegeria/omvs/product_manager.py | 15 +- pyegeria/omvs/project_manager.py | 25 +- pyegeria/omvs/reference_data.py | 52 +++-- pyegeria/omvs/runtime_manager.py | 9 +- pyegeria/omvs/schema_maker.py | 135 ++++++----- pyegeria/omvs/security_officer.py | 32 +-- pyegeria/omvs/solution_architect.py | 124 +++++----- pyegeria/omvs/subject_area.py | 22 +- pyegeria/omvs/template_manager_omvs.py | 12 +- pyegeria/omvs/time_keeper.py | 70 +++--- pyegeria/omvs/valid_metadata.py | 9 +- .../test_issue_174_link_returns_guid.py | 162 +++++++++++++ 29 files changed, 966 insertions(+), 633 deletions(-) create mode 100644 tests/micro-tests/test_issue_174_link_returns_guid.py diff --git a/PYEGERIA_ISSUES.md b/PYEGERIA_ISSUES.md index 305d52e5..970bdfb8 100644 --- a/PYEGERIA_ISSUES.md +++ b/PYEGERIA_ISSUES.md @@ -194,12 +194,14 @@ into the entry now, so it isn't rediscovered from scratch later. ### ISSUE-174: 142 `link_*` methods throw away the relationship GUID the shared helper now returns -**Layer:** pyegeria · **Status:** open, logged 2026-10-31 for a fix and release next session · **Found:** 2026-10-31, Resource Explorer D1-b ("Link the pack's 22 schemas"). `AssetMaker.link_data_set_content` returns None, so the handler re-reads each schema after every attach to learn the new DataSetContent relationship's GUID. +**Layer:** pyegeria · **Status:** fixed 2026-10-31 on branch `fix/link-methods-return-guid`, targets 6.2.19 · **Found:** 2026-10-31, Resource Explorer D1-b ("Link the pack's 22 schemas"). `AssetMaker.link_data_set_content` returns None, so the handler re-reads each schema after every attach to learn the new DataSetContent relationship's GUID. **What:** `ServerClient._async_new_relationship_request` already returns the new relationship's GUID (`GUIDResponse.guid`; that was added earlier as "purely additive"). But 142 of its 160 callers in `pyegeria/omvs` call it as a bare `await ...` and return None, as do their sync wrappers. The worst-affected modules are asset_maker (22), schema_maker (15), solution_architect (12), data_designer (11) and governance_officer (10). It matters most for multi-link relationship types such as DataSetContent: attach creates a new instance each time, and update/detach need that instance's own GUID. **Fix (proposed):** return the helper's result from each `_async_link_*`/`_async_attach_*`/`_async_setup_*`, and from its sync wrapper, typed `str | None` in the docstring's Returns. This is purely additive, since callers that ignore the value are unaffected. A micro-test should stub `_async_make_request` and check that a representative sample (and an AST check that no omvs method discards the helper's result) returns the GUID. Do Dr.Egeria link processors next if any re-read to find the GUID. +**Fix (done):** an AST-driven rewrite of `pyegeria/omvs`. Every async method that bare-awaited the helper now returns its result. Where a log line followed the call, the result is kept in `relationship_guid` and returned after it. Every sync wrapper that bare-called such a method now returns it, and so does every intermediate async method (e.g. ClassificationExplorer's `add_*_to_element`). `-> None` annotations became `-> str | None`, and `Returns: None` docstrings now describe the GUID. `tests/micro-tests/test_issue_174_link_returns_guid.py` has an AST guard over all of `pyegeria/omvs` (transitive, async and sync) and a stubbed round-trip for 8 methods across 7 clients, both sync and async. + ### ISSUE-173: Report specs named analytic functions by dotted path and loaded package specs through env vars; added the `pyegeria.report_specs` entry-point group and deprecated both **Layer:** pyegeria · **Status:** built on branch `feat/analytic-registered-names`, no PR yet; targets 6.2.18, with removal in 6.2.19 · **Found:** 2026-10-31, the owner's ruling on Mandy's question 2 (portal analytics discovery), "yes to (b)", then "a" for report-spec registration (the Architect's R1, split note §11.3). diff --git a/pyegeria/omvs/action_author.py b/pyegeria/omvs/action_author.py index f89b597d..e9bbcce5 100644 --- a/pyegeria/omvs/action_author.py +++ b/pyegeria/omvs/action_author.py @@ -91,7 +91,7 @@ async def _async_link_governance_action_executor( governance_action_type_guid: str, governance_engine_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link a governance action type to a governance engine. Async version. Parameters @@ -105,7 +105,8 @@ async def _async_link_governance_action_executor( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -131,14 +132,14 @@ async def _async_link_governance_action_executor( ``` """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/action-author/governance-action-types/{governance_action_type_guid}/governance-engine-executor/{governance_engine_guid}/attach" - await self._async_new_relationship_request(url, ["GovernanceActionExecutorProperties"], body) + return await self._async_new_relationship_request(url, ["GovernanceActionExecutorProperties"], body) def link_governance_action_executor( self, governance_action_type_guid: str, governance_engine_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link a governance action type to a governance engine. Parameters @@ -152,7 +153,8 @@ def link_governance_action_executor( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -177,7 +179,7 @@ def link_governance_action_executor( } ``` """ - run_sync( + return run_sync( self._async_link_governance_action_executor( governance_action_type_guid, governance_engine_guid, body ) @@ -211,17 +213,17 @@ async def _async_link_target_for_governance_action( governance_action_guid: str, element_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/action-author/governance-actions/{governance_action_guid}/action-targets/{element_guid}/attach" - await self._async_new_relationship_request(url, ["TargetForGovernanceActionProperties"], body) + return await self._async_new_relationship_request(url, ["TargetForGovernanceActionProperties"], body) def link_target_for_governance_action( self, governance_action_guid: str, element_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: - run_sync( + ) -> str | None: + return run_sync( self._async_link_target_for_governance_action(governance_action_guid, element_guid, body) ) @@ -253,17 +255,17 @@ async def _async_setup_first_action_process_step( process_guid: str, process_step_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/action-author/governance-action-processes/{process_guid}/first-process-step/{process_step_guid}/attach" - await self._async_new_relationship_request(url, ["GovernanceActionProcessFlowProperties"], body) + return await self._async_new_relationship_request(url, ["GovernanceActionProcessFlowProperties"], body) def setup_first_action_process_step( self, process_guid: str, process_step_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: - run_sync( + ) -> str | None: + return run_sync( self._async_setup_first_action_process_step(process_guid, process_step_guid, body) ) diff --git a/pyegeria/omvs/actor_manager.py b/pyegeria/omvs/actor_manager.py index 064a50d6..87809783 100644 --- a/pyegeria/omvs/actor_manager.py +++ b/pyegeria/omvs/actor_manager.py @@ -433,7 +433,7 @@ def update_actor_profile(self, actor_profile_guid: str, body: dict | UpdateEleme @dynamic_catch async def _async_link_asset_to_profile(self, asset_guid: str, it_profile_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach an asset to an IT Profile. Async version. Parameters @@ -480,8 +480,9 @@ async def _async_link_asset_to_profile(self, asset_guid: str, it_profile_guid: s """ url = url = (f"{self.command_root}/assets/{asset_guid}/it-profiles/{it_profile_guid}/attach") - await self._async_new_relationship_request(url, ["RelationshipProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["RelationshipProperties"], body) logger.debug(f"Linking element {asset_guid} to IT Profile {it_profile_guid}") + return relationship_guid @dynamic_catch def link_asset_to_profile(self, asset_guid: str, it_profile_guid: str, @@ -534,7 +535,7 @@ def link_asset_to_profile(self, asset_guid: str, it_profile_guid: str, } """ - run_sync(self._async_link_asset_to_profile(asset_guid, it_profile_guid, body)) + return run_sync(self._async_link_asset_to_profile(asset_guid, it_profile_guid, body)) @dynamic_catch async def _async_detach_asset_from_profile(self, asset_guid: str, it_profile_guid: str, @@ -1605,7 +1606,7 @@ def update_actor_role(self, actor_role_guid: str, body: dict | UpdateElementRequ @dynamic_catch async def _async_link_person_role_to_profile(self, person_role_guid: str, person_profile_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a person role to a person profile. Async version. Parameters @@ -1652,8 +1653,9 @@ async def _async_link_person_role_to_profile(self, person_role_guid: str, person url = url = ( f"{self.command_root}/actor-roles/{person_role_guid}/person-role-appointments/{person_profile_guid}/attach") - await self._async_new_relationship_request(url, ["PersonRoleAppointmentProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["PersonRoleAppointmentProperties"], body) logger.debug(f"Linking person role {person_role_guid} to Person Profile {person_profile_guid}") + return relationship_guid @dynamic_catch def link_person_role_to_profile(self, person_role_guid: str, person_profile_guid: str, @@ -1700,7 +1702,7 @@ def link_person_role_to_profile(self, person_role_guid: str, person_profile_guid } """ - run_sync(self._async_link_person_role_to_profile(person_role_guid, person_profile_guid, body)) + return run_sync(self._async_link_person_role_to_profile(person_role_guid, person_profile_guid, body)) @dynamic_catch async def _async_detach_person_role_from_profile(self, person_role_guid: str, person_profile_guid: str, @@ -1795,7 +1797,7 @@ def detach_person_role_from_profile(self, person_role_guid: str, person_profile_ # @dynamic_catch async def _async_link_team_role_to_profile(self, team_role_guid: str, team_profile_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a team role to a team profile. Async version. Parameters @@ -1842,8 +1844,9 @@ async def _async_link_team_role_to_profile(self, team_role_guid: str, team_profi url = url = ( f"{self.command_root}/actor-roles/{team_role_guid}/team-role-appointments/{team_profile_guid}/attach") - await self._async_new_relationship_request(url, ["TeamRoleAppointmentProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["TeamRoleAppointmentProperties"], body) logger.debug(f"Linking team role {team_role_guid} to Team Profile {team_profile_guid}") + return relationship_guid @dynamic_catch def link_team_role_to_profile(self, team_role_guid: str, team_profile_guid: str, @@ -1890,7 +1893,7 @@ def link_team_role_to_profile(self, team_role_guid: str, team_profile_guid: str, } """ - run_sync(self._async_link_team_role_to_profile(team_role_guid, team_profile_guid, body)) + return run_sync(self._async_link_team_role_to_profile(team_role_guid, team_profile_guid, body)) @dynamic_catch async def _async_detach_team_role_from_profile(self, team_role_guid: str, team_profile_guid: str, @@ -1983,7 +1986,7 @@ def detach_team_role_from_profile(self, team_role_guid: str, team_profile_guid: # @dynamic_catch async def _async_link_it_profile_role_to_it_profile(self, it_profile_role_guid: str, it_profile_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach an IT profile role to an IT profile. Async version. Parameters @@ -2030,8 +2033,9 @@ async def _async_link_it_profile_role_to_it_profile(self, it_profile_role_guid: url = url = ( f"{self.command_root}/actor-roles/{it_profile_role_guid}/it-profile-role-appointments/{it_profile_guid}/attach") - await self._async_new_relationship_request(url, ["ITProfileRoleAppointmentProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["ITProfileRoleAppointmentProperties"], body) logger.debug(f"Linking IT Profile role {it_profile_role_guid} to IT Profile {it_profile_guid}") + return relationship_guid @dynamic_catch def link_it_profile_role_to_it_profile(self, it_profile_role_guid: str, it_profile_guid: str, @@ -2078,7 +2082,7 @@ def link_it_profile_role_to_it_profile(self, it_profile_role_guid: str, it_profi } """ - run_sync( + return run_sync( self._async_link_it_profile_role_to_it_profile(it_profile_role_guid, it_profile_guid, body)) @dynamic_catch @@ -3168,7 +3172,7 @@ def update_user_identity(self, user_identity_guid: str, body: dict | UpdateEleme @dynamic_catch async def _async_link_identity_to_profile(self, user_identity_guid: str, actor_profile_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach an actor profile to a user identity. Async version. Parameters @@ -3215,8 +3219,9 @@ async def _async_link_identity_to_profile(self, user_identity_guid: str, actor_p url = url = ( f"{self.command_root}/user-identities/{user_identity_guid}/profile-identity/{actor_profile_guid}/attach") - await self._async_new_relationship_request(url, ["ProfileIdentityProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["ProfileIdentityProperties"], body) logger.debug(f"Linking User Identity {user_identity_guid} to Actor Profile {actor_profile_guid}") + return relationship_guid @dynamic_catch def link_identity_to_profile(self, user_identity_guid: str, actor_profile_guid: str, @@ -3264,7 +3269,7 @@ def link_identity_to_profile(self, user_identity_guid: str, actor_profile_guid: } """ - run_sync(self._async_link_identity_to_profile(user_identity_guid, actor_profile_guid, body)) + return run_sync(self._async_link_identity_to_profile(user_identity_guid, actor_profile_guid, body)) @dynamic_catch async def _async_detach_identity_from_profile(self, user_identity_guid: str, actor_profile_guid: str, @@ -3357,7 +3362,7 @@ def detach_identity_from_profile(self, user_identity_guid: str, actor_profile_gu @dynamic_catch async def _async_link_assignment_scope(self, scope_element_guid: str, actor_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach an actor to an element that describes its scope. Async version. Parameters @@ -3404,8 +3409,9 @@ async def _async_link_assignment_scope(self, scope_element_guid: str, actor_guid """ url = f"{self.command_root}/elements/{scope_element_guid}/actors/{actor_guid}/attach" - await self._async_new_relationship_request(url, ["AssignmentScopeProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["AssignmentScopeProperties"], body) logger.debug(f"Linking actor {actor_guid} to assignment scope {scope_element_guid}") + return relationship_guid @dynamic_catch def link_assignment_scope(self, scope_element_guid: str, actor_guid: str, @@ -3454,7 +3460,7 @@ def link_assignment_scope(self, scope_element_guid: str, actor_guid: str, } """ - run_sync(self._async_link_assignment_scope(scope_element_guid, actor_guid, body)) + return run_sync(self._async_link_assignment_scope(scope_element_guid, actor_guid, body)) @dynamic_catch async def _async_detach_assignment_scope(self, scope_element_guid: str, actor_guid: str, @@ -5790,7 +5796,7 @@ def delete_contact_details(self, contact_details_guid: str, body: Optional[dict @dynamic_catch async def _async_link_contact_details(self, element_guid: str, contact_details_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach an element to its contact details. Async version. Parameters @@ -5836,8 +5842,9 @@ async def _async_link_contact_details(self, element_guid: str, contact_details_g """ url = f"{self.command_root}/elements/{element_guid}/contact-details/{contact_details_guid}/attach" - await self._async_new_relationship_request(url, ["ContactThroughProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["ContactThroughProperties"], body) logger.debug(f"Linking element {element_guid} to contact details {contact_details_guid}") + return relationship_guid @dynamic_catch def link_contact_details(self, element_guid: str, contact_details_guid: str, @@ -5885,7 +5892,7 @@ def link_contact_details(self, element_guid: str, contact_details_guid: str, } """ - run_sync(self._async_link_contact_details(element_guid, contact_details_guid, body)) + return run_sync(self._async_link_contact_details(element_guid, contact_details_guid, body)) @dynamic_catch async def _async_detach_contact_details(self, element_guid: str, contact_details_guid: str, diff --git a/pyegeria/omvs/asset_maker.py b/pyegeria/omvs/asset_maker.py index cd7dea68..3fa29fe7 100644 --- a/pyegeria/omvs/asset_maker.py +++ b/pyegeria/omvs/asset_maker.py @@ -2310,7 +2310,7 @@ async def _async_link_data_set_content( data_set_guid: str, data_content_asset_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Attach a data set to another asset (typically a data store) that is supplying the data. Async version. Parameters @@ -2324,7 +2324,8 @@ async def _async_link_data_set_content( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2351,7 +2352,7 @@ async def _async_link_data_set_content( } """ url = f"{self.asset_command_root}/data-sets/{data_set_guid}/data-set-content/{data_content_asset_guid}/attach" - await self._async_new_relationship_request(url, ["DataSetContentProperties"], body) + return await self._async_new_relationship_request(url, ["DataSetContentProperties"], body) @dynamic_catch def link_data_set_content( @@ -2359,7 +2360,7 @@ def link_data_set_content( data_set_guid: str, data_content_asset_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Attach a data set to another asset (typically a data store) that is supplying the data. Parameters @@ -2373,7 +2374,8 @@ def link_data_set_content( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2399,7 +2401,7 @@ def link_data_set_content( } } """ - run_sync( + return run_sync( self._async_link_data_set_content(data_set_guid, data_content_asset_guid, body) ) @@ -2586,7 +2588,7 @@ async def _async_link_report_originator( originator_guid: str, report_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Create a relationship that identifies the originator of a report. Async version. Parameters @@ -2600,7 +2602,8 @@ async def _async_link_report_originator( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2625,7 +2628,7 @@ async def _async_link_report_originator( } """ url = f"{self.asset_command_root}/elements/{originator_guid}/originated-reports/{report_guid}/attach" - await self._async_new_relationship_request(url, ["ReportOriginatorProperties"], body) + return await self._async_new_relationship_request(url, ["ReportOriginatorProperties"], body) @dynamic_catch def link_report_originator( @@ -2633,7 +2636,7 @@ def link_report_originator( originator_guid: str, report_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Create a relationship that identifies the originator of a report. Parameters @@ -2647,7 +2650,8 @@ def link_report_originator( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2671,7 +2675,7 @@ def link_report_originator( } } """ - run_sync( + return run_sync( self._async_link_report_originator(originator_guid, report_guid, body) ) @@ -2744,7 +2748,7 @@ async def _async_link_report_dependency( prior_report_guid: str, report_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Create a relationship that identifies the prior publishing of a report. Async version. Parameters @@ -2758,7 +2762,8 @@ async def _async_link_report_dependency( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2783,7 +2788,7 @@ async def _async_link_report_dependency( } """ url = f"{self.asset_command_root}/reports/{prior_report_guid}/follow-on-reports/{report_guid}/attach" - await self._async_new_relationship_request(url, ["ReportDependencyProperties"], body) + return await self._async_new_relationship_request(url, ["ReportDependencyProperties"], body) @dynamic_catch def link_report_dependency( @@ -2791,7 +2796,7 @@ def link_report_dependency( prior_report_guid: str, report_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Create a relationship that identifies the prior publishing of a report. Parameters @@ -2805,7 +2810,8 @@ def link_report_dependency( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2829,7 +2835,7 @@ def link_report_dependency( } } """ - run_sync( + return run_sync( self._async_link_report_dependency(prior_report_guid, report_guid, body) ) @@ -2902,7 +2908,7 @@ async def _async_link_report_subject( subject_guid: str, report_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Create a relationship that identifies the subject of a report. Async version. Parameters @@ -2916,7 +2922,8 @@ async def _async_link_report_subject( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2941,7 +2948,7 @@ async def _async_link_report_subject( } """ url = f"{self.asset_command_root}/elements/{subject_guid}/reports/{report_guid}/attach" - await self._async_new_relationship_request(url, ["ReportSubjectProperties"], body) + return await self._async_new_relationship_request(url, ["ReportSubjectProperties"], body) @dynamic_catch def link_report_subject( @@ -2949,7 +2956,7 @@ def link_report_subject( subject_guid: str, report_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Create a relationship that identifies the subject of a report. Parameters @@ -2963,7 +2970,8 @@ def link_report_subject( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2987,7 +2995,7 @@ def link_report_subject( } } """ - run_sync( + return run_sync( self._async_link_report_subject(subject_guid, report_guid, body) ) @@ -3430,7 +3438,7 @@ async def _async_deploy_it_asset( asset_guid: str, destination_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Create a relationship that represents the deployment of an IT infrastructure asset to a destination. Async version. @@ -3445,7 +3453,8 @@ async def _async_deploy_it_asset( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3472,7 +3481,7 @@ async def _async_deploy_it_asset( } """ url = f"{self.asset_command_root}/assets/{asset_guid}/deployed-on/{destination_guid}/attach" - await self._async_new_relationship_request(url, ["DeployedOnProperties"], body) + return await self._async_new_relationship_request(url, ["DeployedOnProperties"], body) @dynamic_catch def deploy_it_asset( @@ -3480,7 +3489,7 @@ def deploy_it_asset( asset_guid: str, destination_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Create a relationship that represents the deployment of an IT infrastructure asset to a destination. Parameters @@ -3494,7 +3503,8 @@ def deploy_it_asset( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3520,7 +3530,7 @@ def deploy_it_asset( } } """ - run_sync( + return run_sync( self._async_deploy_it_asset(asset_guid, destination_guid, body) ) @@ -3593,7 +3603,7 @@ async def _async_link_software_capability_to_asset( asset_guid: str, capability_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Create a relationship that links a software capability to an infrastructure asset. Async version. Parameters @@ -3607,7 +3617,8 @@ async def _async_link_software_capability_to_asset( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3632,7 +3643,7 @@ async def _async_link_software_capability_to_asset( } """ url = f"{self.asset_command_root}/assets/{asset_guid}/supported-software-capabilities/{capability_guid}/attach" - await self._async_new_relationship_request(url, ["SupportedSoftwareCapabilityProperties"], body) + return await self._async_new_relationship_request(url, ["SupportedSoftwareCapabilityProperties"], body) @dynamic_catch def link_software_capability_to_asset( @@ -3640,7 +3651,7 @@ def link_software_capability_to_asset( asset_guid: str, capability_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Create a relationship that links a software capability to an infrastructure asset. Parameters @@ -3654,7 +3665,8 @@ def link_software_capability_to_asset( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3678,7 +3690,7 @@ def link_software_capability_to_asset( } } """ - run_sync( + return run_sync( self._async_link_software_capability_to_asset(asset_guid, capability_guid, body) ) @@ -4722,7 +4734,7 @@ async def _async_assign_action( action_guid: str, actor_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Assign an action to an actor. Request body is optional. Async version. @@ -4737,7 +4749,7 @@ async def _async_assign_action( """ url = f"{self.asset_command_root}/actions/{action_guid}/assign/{actor_guid}" - await self._async_new_relationship_request(url, prop=["AssignmentScopeProperties"], body=body) + return await self._async_new_relationship_request(url, prop=["AssignmentScopeProperties"], body=body) @dynamic_catch def assign_action( @@ -4745,7 +4757,7 @@ def assign_action( action_guid: str, actor_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Assign an action to an actor. Sync version. @@ -4775,7 +4787,7 @@ def assign_action( "effectiveTime" : "{{$isoTimestamp}}" } """ - run_sync(self._async_assign_action(action_guid, actor_guid, body)) + return run_sync(self._async_assign_action(action_guid, actor_guid, body)) @dynamic_catch async def _async_reassign_action( @@ -4783,7 +4795,7 @@ async def _async_reassign_action( action_guid: str, actor_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Assign an action to a new actor. This will unassign all other actors previously assigned to the action. Async version. @@ -4795,7 +4807,7 @@ async def _async_reassign_action( """ url = f"{self.asset_command_root}/actions/{action_guid}/reassign/{actor_guid}" - await self._async_new_relationship_request(url, prop=["AssignmentScopeProperties"], body=body) + return await self._async_new_relationship_request(url, prop=["AssignmentScopeProperties"], body=body) @dynamic_catch def reassign_action( @@ -4803,7 +4815,7 @@ def reassign_action( action_guid: str, actor_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Assign an action to a new actor. Sync version. @@ -4830,7 +4842,7 @@ def reassign_action( "effectiveTime" : "{{$isoTimestamp}}" } """ - run_sync(self._async_reassign_action(action_guid, actor_guid, body)) + return run_sync(self._async_reassign_action(action_guid, actor_guid, body)) @dynamic_catch async def _async_unassign_action( @@ -6219,7 +6231,7 @@ async def _async_add_capability_asset_use( software_capability_guid: str, asset_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Create a CapabilityAssetUse relationship between a software capability and an asset. Async version. Parameters @@ -6233,7 +6245,8 @@ async def _async_add_capability_asset_use( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -6264,7 +6277,7 @@ async def _async_add_capability_asset_use( } """ url = f"{self.asset_command_root}/software-capabilities/{software_capability_guid}/assets/{asset_guid}/attach" - await self._async_new_relationship_request(url, ["CapabilityAssetUseProperties"], body) + return await self._async_new_relationship_request(url, ["CapabilityAssetUseProperties"], body) @dynamic_catch def add_capability_asset_use( @@ -6272,7 +6285,7 @@ def add_capability_asset_use( software_capability_guid: str, asset_guid: str, body: dict | NewRelationshipRequestBody | None = None, - ) -> None: + ) -> str | None: """Create a CapabilityAssetUse relationship between a software capability and an asset. Parameters @@ -6286,7 +6299,8 @@ def add_capability_asset_use( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -6316,7 +6330,7 @@ def add_capability_asset_use( } } """ - run_sync( + return run_sync( self._async_add_capability_asset_use(software_capability_guid, asset_guid, body) ) @@ -6943,16 +6957,16 @@ def _generate_referenceable_output( @dynamic_catch async def _async_link_api_endpoint(self, deployed_api_guid: str, endpoint_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a network endpoint to a deployed API (APIEndpoint relationship). Async version.""" url = f"{self.asset_command_root}/deployed-apis/{deployed_api_guid}/api-endpoints/{endpoint_guid}/attach" - await self._async_new_relationship_request(url, ["APIEndpointProperties"], body) + return await self._async_new_relationship_request(url, ["APIEndpointProperties"], body) @dynamic_catch def link_api_endpoint(self, deployed_api_guid: str, endpoint_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a network endpoint to a deployed API (APIEndpoint relationship).""" - run_sync(self._async_link_api_endpoint(deployed_api_guid, endpoint_guid, body)) + return run_sync(self._async_link_api_endpoint(deployed_api_guid, endpoint_guid, body)) @dynamic_catch async def _async_detach_api_endpoint(self, deployed_api_guid: str, endpoint_guid: str, @@ -6969,16 +6983,16 @@ def detach_api_endpoint(self, deployed_api_guid: str, endpoint_guid: str, @dynamic_catch async def _async_link_process_hierarchy(self, parent_process_guid: str, child_process_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a child process to its parent process (ProcessHierarchy relationship). Async version.""" url = f"{self.asset_command_root}/processes/{parent_process_guid}/process-hierarchies/{child_process_guid}/attach" - await self._async_new_relationship_request(url, ["ProcessHierarchyProperties"], body) + return await self._async_new_relationship_request(url, ["ProcessHierarchyProperties"], body) @dynamic_catch def link_process_hierarchy(self, parent_process_guid: str, child_process_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a child process to its parent process (ProcessHierarchy relationship).""" - run_sync(self._async_link_process_hierarchy(parent_process_guid, child_process_guid, body)) + return run_sync(self._async_link_process_hierarchy(parent_process_guid, child_process_guid, body)) @dynamic_catch async def _async_detach_process_hierarchy(self, parent_process_guid: str, child_process_guid: str, @@ -6995,16 +7009,16 @@ def detach_process_hierarchy(self, parent_process_guid: str, child_process_guid: @dynamic_catch async def _async_link_nested_files(self, folder_guid: str, file_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a file to the folder that contains it (NestedFile relationship). Async version.""" url = f"{self.asset_command_root}/file-folders/{folder_guid}/nested-files/{file_guid}/attach" - await self._async_new_relationship_request(url, ["NestedFileProperties"], body) + return await self._async_new_relationship_request(url, ["NestedFileProperties"], body) @dynamic_catch def link_nested_files(self, folder_guid: str, file_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a file to the folder that contains it (NestedFile relationship).""" - run_sync(self._async_link_nested_files(folder_guid, file_guid, body)) + return run_sync(self._async_link_nested_files(folder_guid, file_guid, body)) @dynamic_catch async def _async_detach_nested_file(self, folder_guid: str, file_guid: str, @@ -7021,16 +7035,16 @@ def detach_nested_file(self, folder_guid: str, file_guid: str, @dynamic_catch async def _async_link_linked_files(self, folder_guid: str, file_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a file referenced by (but not contained in) a folder (LinkedFile relationship). Async version.""" url = f"{self.asset_command_root}/file-folders/{folder_guid}/linked-files/{file_guid}/attach" - await self._async_new_relationship_request(url, ["LinkedFileProperties"], body) + return await self._async_new_relationship_request(url, ["LinkedFileProperties"], body) @dynamic_catch def link_linked_files(self, folder_guid: str, file_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a file referenced by (but not contained in) a folder (LinkedFile relationship).""" - run_sync(self._async_link_linked_files(folder_guid, file_guid, body)) + return run_sync(self._async_link_linked_files(folder_guid, file_guid, body)) @dynamic_catch async def _async_detach_linked_file(self, folder_guid: str, file_guid: str, @@ -7047,16 +7061,16 @@ def detach_linked_file(self, folder_guid: str, file_guid: str, @dynamic_catch async def _async_link_folder_hierarchy(self, parent_folder_guid: str, child_folder_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a child folder to its parent folder (FolderHierarchy relationship). Async version.""" url = f"{self.asset_command_root}/file-folders/{parent_folder_guid}/folder-hierarchies/{child_folder_guid}/attach" - await self._async_new_relationship_request(url, ["FolderHierarchyProperties"], body) + return await self._async_new_relationship_request(url, ["FolderHierarchyProperties"], body) @dynamic_catch def link_folder_hierarchy(self, parent_folder_guid: str, child_folder_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a child folder to its parent folder (FolderHierarchy relationship).""" - run_sync(self._async_link_folder_hierarchy(parent_folder_guid, child_folder_guid, body)) + return run_sync(self._async_link_folder_hierarchy(parent_folder_guid, child_folder_guid, body)) @dynamic_catch async def _async_detach_folder_hierarchy(self, parent_folder_guid: str, child_folder_guid: str, @@ -7073,16 +7087,16 @@ def detach_folder_hierarchy(self, parent_folder_guid: str, child_folder_guid: st @dynamic_catch async def _async_link_impacted_resource(self, resource_guid: str, incident_report_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a resource to an incident report that affects it (ImpactedResource relationship). Async version.""" url = f"{self.asset_command_root}/resources/{resource_guid}/impacted-resources/{incident_report_guid}/attach" - await self._async_new_relationship_request(url, ["ImpactedResourceProperties"], body) + return await self._async_new_relationship_request(url, ["ImpactedResourceProperties"], body) @dynamic_catch def link_impacted_resource(self, resource_guid: str, incident_report_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a resource to an incident report that affects it (ImpactedResource relationship).""" - run_sync(self._async_link_impacted_resource(resource_guid, incident_report_guid, body)) + return run_sync(self._async_link_impacted_resource(resource_guid, incident_report_guid, body)) @dynamic_catch async def _async_detach_impacted_resource(self, resource_guid: str, incident_report_guid: str, @@ -7099,16 +7113,16 @@ def detach_impacted_resource(self, resource_guid: str, incident_report_guid: str @dynamic_catch async def _async_link_archive_contents(self, archive_file_guid: str, collection_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a collection as the contents of an archive file (ArchiveContents relationship). Async version.""" url = f"{self.asset_command_root}/archive-files/{archive_file_guid}/archive-contents/{collection_guid}/attach" - await self._async_new_relationship_request(url, ["ArchiveContentsProperties"], body) + return await self._async_new_relationship_request(url, ["ArchiveContentsProperties"], body) @dynamic_catch def link_archive_contents(self, archive_file_guid: str, collection_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a collection as the contents of an archive file (ArchiveContents relationship).""" - run_sync(self._async_link_archive_contents(archive_file_guid, collection_guid, body)) + return run_sync(self._async_link_archive_contents(archive_file_guid, collection_guid, body)) @dynamic_catch async def _async_detach_archive_contents(self, archive_file_guid: str, collection_guid: str, @@ -7125,16 +7139,16 @@ def detach_archive_contents(self, archive_file_guid: str, collection_guid: str, @dynamic_catch async def _async_link_associated_log(self, element_guid: str, log_asset_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a log file asset to the element it logs activity for (AssociatedLog relationship). Async version.""" url = f"{self.asset_command_root}/elements/{element_guid}/associated-logs/{log_asset_guid}/attach" - await self._async_new_relationship_request(url, ["AssociatedLogProperties"], body) + return await self._async_new_relationship_request(url, ["AssociatedLogProperties"], body) @dynamic_catch def link_associated_log(self, element_guid: str, log_asset_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a log file asset to the element it logs activity for (AssociatedLog relationship).""" - run_sync(self._async_link_associated_log(element_guid, log_asset_guid, body)) + return run_sync(self._async_link_associated_log(element_guid, log_asset_guid, body)) @dynamic_catch async def _async_detach_associated_log(self, element_guid: str, log_asset_guid: str, @@ -7151,16 +7165,16 @@ def detach_associated_log(self, element_guid: str, log_asset_guid: str, @dynamic_catch async def _async_link_linked_media(self, media_file_guid: str, linked_media_file_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a related media file to another media file (LinkedMedia relationship). Async version.""" url = f"{self.asset_command_root}/media-files/{media_file_guid}/linked-media/{linked_media_file_guid}/attach" - await self._async_new_relationship_request(url, ["LinkedMediaProperties"], body) + return await self._async_new_relationship_request(url, ["LinkedMediaProperties"], body) @dynamic_catch def link_linked_media(self, media_file_guid: str, linked_media_file_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a related media file to another media file (LinkedMedia relationship).""" - run_sync(self._async_link_linked_media(media_file_guid, linked_media_file_guid, body)) + return run_sync(self._async_link_linked_media(media_file_guid, linked_media_file_guid, body)) @dynamic_catch async def _async_detach_linked_media(self, media_file_guid: str, linked_media_file_guid: str, @@ -7177,16 +7191,16 @@ def detach_linked_media(self, media_file_guid: str, linked_media_file_guid: str, @dynamic_catch async def _async_link_process_port(self, process_guid: str, port_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a port to the process it belongs to (ProcessPort relationship). Async version.""" url = f"{self.asset_command_root}/processes/{process_guid}/ports/{port_guid}/attach" - await self._async_new_relationship_request(url, ["ProcessPortProperties"], body) + return await self._async_new_relationship_request(url, ["ProcessPortProperties"], body) @dynamic_catch def link_process_port(self, process_guid: str, port_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a port to the process it belongs to (ProcessPort relationship).""" - run_sync(self._async_link_process_port(process_guid, port_guid, body)) + return run_sync(self._async_link_process_port(process_guid, port_guid, body)) @dynamic_catch async def _async_detach_process_port(self, process_guid: str, port_guid: str, @@ -7203,16 +7217,16 @@ def detach_process_port(self, process_guid: str, port_guid: str, @dynamic_catch async def _async_link_sample_data(self, element_guid: str, sample_data_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a data resource as sample data for an element (SampleData relationship). Async version.""" url = f"{self.asset_command_root}/elements/{element_guid}/sample-data/{sample_data_guid}/attach" - await self._async_new_relationship_request(url, ["SampleDataProperties"], body) + return await self._async_new_relationship_request(url, ["SampleDataProperties"], body) @dynamic_catch def link_sample_data(self, element_guid: str, sample_data_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a data resource as sample data for an element (SampleData relationship).""" - run_sync(self._async_link_sample_data(element_guid, sample_data_guid, body)) + return run_sync(self._async_link_sample_data(element_guid, sample_data_guid, body)) @dynamic_catch async def _async_detach_sample_data(self, element_guid: str, sample_data_guid: str, @@ -7229,16 +7243,16 @@ def detach_sample_data(self, element_guid: str, sample_data_guid: str, @dynamic_catch async def _async_link_port_delegation(self, delegating_from_port_guid: str, delegating_to_port_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a port to the port it delegates to (PortDelegation relationship). Async version.""" url = f"{self.asset_command_root}/ports/{delegating_from_port_guid}/port-delegations/{delegating_to_port_guid}/attach" - await self._async_new_relationship_request(url, ["PortDelegationProperties"], body) + return await self._async_new_relationship_request(url, ["PortDelegationProperties"], body) @dynamic_catch def link_port_delegation(self, delegating_from_port_guid: str, delegating_to_port_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Attach a port to the port it delegates to (PortDelegation relationship).""" - run_sync(self._async_link_port_delegation(delegating_from_port_guid, delegating_to_port_guid, body)) + return run_sync(self._async_link_port_delegation(delegating_from_port_guid, delegating_to_port_guid, body)) @dynamic_catch async def _async_detach_port_delegation(self, delegating_from_port_guid: str, delegating_to_port_guid: str, @@ -7255,17 +7269,17 @@ def detach_port_delegation(self, delegating_from_port_guid: str, delegating_to_p @dynamic_catch async def _async_link_registered_integration_connector(self, integration_group_guid: str, integration_connector_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Register an integration connector with an integration group (RegisteredIntegrationConnector relationship). Async version.""" url = (f"{self.asset_command_root}/integration-groups/{integration_group_guid}" f"/registered-integration-connectors/{integration_connector_guid}/attach") - await self._async_new_relationship_request(url, ["RegisteredIntegrationConnectorProperties"], body) + return await self._async_new_relationship_request(url, ["RegisteredIntegrationConnectorProperties"], body) @dynamic_catch def link_registered_integration_connector(self, integration_group_guid: str, integration_connector_guid: str, - body: dict | NewRelationshipRequestBody | None = None) -> None: + body: dict | NewRelationshipRequestBody | None = None) -> str | None: """Register an integration connector with an integration group (RegisteredIntegrationConnector relationship).""" - run_sync(self._async_link_registered_integration_connector( + return run_sync(self._async_link_registered_integration_connector( integration_group_guid, integration_connector_guid, body)) @dynamic_catch diff --git a/pyegeria/omvs/classification_explorer.py b/pyegeria/omvs/classification_explorer.py index b60fbe9b..fbfcdbb5 100644 --- a/pyegeria/omvs/classification_explorer.py +++ b/pyegeria/omvs/classification_explorer.py @@ -6462,7 +6462,7 @@ async def _async_add_gov_definition_to_element( definition_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Link a governance definition to an element using the GovernedBy relationship. Async version. @@ -6479,7 +6479,8 @@ async def _async_add_gov_definition_to_element( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -6509,7 +6510,7 @@ async def _async_add_gov_definition_to_element( f"" ) - await self._async_new_relationship_request( + return await self._async_new_relationship_request( url, prop=["GovernedByProperties"], body=body ) @@ -6518,7 +6519,7 @@ def add_gov_definition_to_element( definition_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Link a governance definition to an element using the GovernedBy relationship. @@ -6535,7 +6536,8 @@ def add_gov_definition_to_element( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -6560,7 +6562,7 @@ def add_gov_definition_to_element( """ - run_sync( + return run_sync( self._async_add_gov_definition_to_element( definition_guid, element_guid, @@ -6671,7 +6673,7 @@ async def _async_add_scope_to_element( scoped_by_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Link a scope to an element using the ScopedBy relationship. Async version. @@ -6688,7 +6690,8 @@ async def _async_add_scope_to_element( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -6698,14 +6701,14 @@ async def _async_add_scope_to_element( url = f"{self.classification_command_root}/elements/{element_guid}/scoped-by/{scoped_by_guid}/attach" - await self._async_new_relationship_request(url, ['ScopedBy'], body) + return await self._async_new_relationship_request(url, ['ScopedBy'], body) def add_scope_to_element( self, scoped_by_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Link a scope to an element using the ScopedBy relationship. @@ -6722,7 +6725,7 @@ def add_scope_to_element( """ - run_sync( + return run_sync( self._async_add_scope_to_element( scoped_by_guid, element_guid, @@ -6805,7 +6808,7 @@ async def _async_add_resource_to_element( resource_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Link a resource to an element using the ResourceList relationship. Async version. @@ -6827,7 +6830,8 @@ async def _async_add_resource_to_element( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -6837,14 +6841,14 @@ async def _async_add_resource_to_element( url = f"{self.classification_command_root}/elements/{element_guid}/resource-list/{resource_guid}/attach" - await self._async_new_relationship_request(url, ['ResourceList'], body) + return await self._async_new_relationship_request(url, ['ResourceList'], body) def add_resource_to_element( self, resource_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Link a resource to an element using the ResourceList relationship. @@ -6861,7 +6865,7 @@ def add_resource_to_element( """ - run_sync( + return run_sync( self._async_add_resource_to_element( resource_guid, element_guid, @@ -7204,7 +7208,7 @@ async def _async_add_more_information( more_info_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Link a more-information resource to an element using the MoreInformation relationship. Async version. @@ -7225,7 +7229,8 @@ async def _async_add_more_information( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -7235,14 +7240,14 @@ async def _async_add_more_information( url = f"{self.classification_command_root}/elements/{element_guid}/more-information/{more_info_guid}/attach" - await self._async_new_relationship_request(url, ['MoreInformation'], body) + return await self._async_new_relationship_request(url, ['MoreInformation'], body) def add_more_information( self, more_info_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Link a more-information resource to an element using the MoreInformation relationship. @@ -7259,7 +7264,7 @@ def add_more_information( """ - run_sync( + return run_sync( self._async_add_more_information( more_info_guid, element_guid, @@ -7340,7 +7345,7 @@ async def _async_assign_actor_to_element( element_guid: str, actor_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Attach an actor to an element. Async version. @@ -7357,7 +7362,8 @@ async def _async_assign_actor_to_element( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -7383,14 +7389,14 @@ async def _async_assign_actor_to_element( f"{self.classification_command_root}/elements/{element_guid}/assigned-to-actor/{actor_guid}/attach" ) - await self._async_new_relationship_request(url, body=body) + return await self._async_new_relationship_request(url, body=body) def assign_actor_to_element( self, element_guid: str, actor_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """ Attach an actor to an element. @@ -7407,7 +7413,8 @@ def assign_actor_to_element( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -7429,7 +7436,7 @@ def assign_actor_to_element( """ - run_sync( + return run_sync( self._async_assign_actor_to_element( element_guid, actor_guid, diff --git a/pyegeria/omvs/collection_manager.py b/pyegeria/omvs/collection_manager.py index 88b6c65f..b8349f49 100644 --- a/pyegeria/omvs/collection_manager.py +++ b/pyegeria/omvs/collection_manager.py @@ -3297,7 +3297,7 @@ def create_reference_list_collection(self, display_name: Optional[str] = None, @dynamic_catch async def _async_link_associated_skill_set(self, actor_guid: str, skill_set_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a skill set collection to an actor via the AssociatedSkillSet relationship. Async version. Parameters @@ -3337,12 +3337,13 @@ async def _async_link_associated_skill_set(self, actor_guid: str, skill_set_guid """ base = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/collection-manager" url = f"{base}/actors/{actor_guid}/associated-skill-sets/{skill_set_guid}/attach" - await self._async_new_relationship_request(url, ["AssociatedSkillSetProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["AssociatedSkillSetProperties"], body) logger.debug(f"Linked skill set {skill_set_guid} to actor {actor_guid}") + return relationship_guid @dynamic_catch def link_associated_skill_set(self, actor_guid: str, skill_set_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a skill set collection to an actor via the AssociatedSkillSet relationship. Parameters @@ -3380,7 +3381,7 @@ def link_associated_skill_set(self, actor_guid: str, skill_set_guid: str, } } """ - run_sync( + return run_sync( self._async_link_associated_skill_set(actor_guid, skill_set_guid, body)) @dynamic_catch @@ -5447,7 +5448,7 @@ def detach_agreement_item(self, agreement_guid: str, agreement_item_guid: str, b @dynamic_catch async def _async_link_contract(self, agreement_guid: str, external_ref_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach an agreement to an external reference element that describes the location of the contract documents. Request body is optional. Async version. @@ -5498,11 +5499,12 @@ async def _async_link_contract(self, agreement_guid: str, external_ref_guid: str """ url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/collection-manager/collections/" f"agreements/{agreement_guid}/contract-links/{external_ref_guid}/attach") - await self._async_new_relationship_request(url, "ContractLinkProperties", body) + relationship_guid = await self._async_new_relationship_request(url, "ContractLinkProperties", body) logger.info(f"Attached agreement {agreement_guid} to contract {external_ref_guid}") + return relationship_guid @dynamic_catch - def link_contract(self, agreement_guid: str, external_ref_guid: str, body: dict = None) -> None: + def link_contract(self, agreement_guid: str, external_ref_guid: str, body: dict = None) -> str | None: """ Attach an agreement to an external reference element that describes the location of the contract documents. Request body is optional. @@ -5551,7 +5553,7 @@ def link_contract(self, agreement_guid: str, external_ref_guid: str, body: dict } """ - run_sync(self._async_link_contract(agreement_guid, external_ref_guid, body)) + return run_sync(self._async_link_contract(agreement_guid, external_ref_guid, body)) @dynamic_catch @@ -5933,7 +5935,7 @@ def update_digital_subscription(self, digital_subscription_guid: str, body: dict @dynamic_catch async def _async_link_subscriber(self, subscriber_guid: str, subscription_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None)-> None: + body: Optional[dict | NewRelationshipRequestBody] = None)-> str | None: """ Attach a subscriber to a subscription. The subscriber is of type 'Referenceable' to allow digital products, team or business capabilities to be the subscriber. The subscription is an element of type DigitalSubscription. @@ -5983,8 +5985,9 @@ async def _async_link_subscriber(self, subscriber_guid: str, subscription_guid: f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/collection-manager" f"/subscribers/" f"{subscriber_guid}/subscriptions/{subscription_guid}/attach") - await self._async_new_relationship_request(url, "DigitalSubscriberProperties", body) + relationship_guid = await self._async_new_relationship_request(url, "DigitalSubscriberProperties", body) logger.info(f"Linking subscriber {subscriber_guid} to subscription {subscription_guid}") + return relationship_guid @dynamic_catch def link_subscriber(self, subscriber_guid: str, subscription_guid: str, @@ -6034,7 +6037,7 @@ def link_subscriber(self, subscriber_guid: str, subscription_guid: str, } } """ - run_sync(self._async_link_subscriber(subscriber_guid, subscription_guid, body)) + return run_sync(self._async_link_subscriber(subscriber_guid, subscription_guid, body)) @dynamic_catch @@ -6185,8 +6188,9 @@ async def _async_attach_collection(self, parent_guid: str, collection_guid: str, f"{self.platform_url}/servers/" f"{self.view_server}/api/open-metadata/collection-manager/metadata-elements/" f"{parent_guid}/collections/{collection_guid}/attach") - await self._async_new_relationship_request(url, "ResourceListProperties", body) + relationship_guid = await self._async_new_relationship_request(url, "ResourceListProperties", body) logger.info(f"Attached {collection_guid} to {parent_guid}") + return relationship_guid @dynamic_catch async def _async_attach_data_description(self, element_guid: str, collection_guid: str, @@ -6206,14 +6210,15 @@ async def _async_attach_data_description(self, element_guid: str, collection_gui f"{self.platform_url}/servers/" f"{self.view_server}/api/open-metadata/collection-manager/metadata-elements/" f"{element_guid}/data-descriptions/{collection_guid}/attach") - await self._async_new_relationship_request(url, "DataDescriptionProperties", body) + relationship_guid = await self._async_new_relationship_request(url, "DataDescriptionProperties", body) logger.info(f"Attached data description {collection_guid} to element {element_guid}") + return relationship_guid @dynamic_catch def attach_data_description(self, element_guid: str, collection_guid: str, body: dict | NewRelationshipRequestBody = None): """Attach a data description collection to an element (DataDescription relationship).""" - run_sync(self._async_attach_data_description(element_guid, collection_guid, body)) + return run_sync(self._async_attach_data_description(element_guid, collection_guid, body)) @dynamic_catch async def _async_detach_data_description(self, element_guid: str, collection_guid: str, @@ -6244,7 +6249,7 @@ def detach_data_description(self, element_guid: str, collection_guid: str, @dynamic_catch async def _async_attach_smart_query(self, results_set_guid: str, saved_query_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Connect a SavedQuery to a ResultsSet via the collection-manager-native SmartQuery attach endpoint. Async version. @@ -6263,7 +6268,8 @@ async def _async_attach_smart_query(self, results_set_guid: str, saved_query_gui Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -6289,14 +6295,15 @@ async def _async_attach_smart_query(self, results_set_guid: str, saved_query_gui f"{self.platform_url}/servers/" f"{self.view_server}/api/open-metadata/collection-manager/collections/results-sets/" f"{results_set_guid}/smart-query/{saved_query_guid}/attach") - await self._async_new_relationship_request(url, ["SmartQueryProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["SmartQueryProperties"], body) logger.info(f"Attached SmartQuery {saved_query_guid} to results set {results_set_guid}") + return relationship_guid @dynamic_catch def attach_smart_query(self, results_set_guid: str, saved_query_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Connect a SavedQuery to a ResultsSet via the collection-manager-native SmartQuery attach endpoint.""" - run_sync(self._async_attach_smart_query(results_set_guid, saved_query_guid, body)) + return run_sync(self._async_attach_smart_query(results_set_guid, saved_query_guid, body)) @dynamic_catch async def _async_detach_smart_query(self, results_set_guid: str, saved_query_guid: str, @@ -6387,7 +6394,7 @@ def attach_collection(self, parent_guid: str, collection_guid: str, "forDuplicateProcessing" : false } """ - run_sync(self._async_attach_collection(parent_guid, collection_guid, body)) + return run_sync(self._async_attach_collection(parent_guid, collection_guid, body)) @dynamic_catch @@ -6613,7 +6620,7 @@ def archive_collection(self, collection_guid: str, @dynamic_catch async def _async_add_to_collection(self, collection_guid: str, element_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None, ) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None, ) -> str | None: """Add an element to a collection. The request body is optional. Async version. Parameters @@ -6627,7 +6634,8 @@ async def _async_add_to_collection(self, collection_guid: str, element_guid: str Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -6669,12 +6677,13 @@ async def _async_add_to_collection(self, collection_guid: str, element_guid: str url = (f"{self.collection_command_root}/{collection_guid}/members/" f"{element_guid}/attach") - await self._async_new_relationship_request(url, "CollectionMembershipProperties", body) + relationship_guid = await self._async_new_relationship_request(url, "CollectionMembershipProperties", body) logger.info(f"Added {element_guid} to {collection_guid}") + return relationship_guid def add_to_collection(self, collection_guid: str, element_guid: str, - body: dict | NewRelationshipRequestBody= None, ) -> None: + body: dict | NewRelationshipRequestBody= None, ) -> str | None: """Add an element to a collection. The request body is optional. Parameters @@ -6691,7 +6700,8 @@ def add_to_collection(self, collection_guid: str, element_guid: str, Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -6730,12 +6740,12 @@ def add_to_collection(self, collection_guid: str, element_guid: str, } """ - run_sync(self._async_add_to_collection( collection_guid, element_guid, body)) + return run_sync(self._async_add_to_collection( collection_guid, element_guid, body)) def add_term_to_folder(self, folder_guid: str, term_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Add a term to a category. The request body is optional.""" - run_sync(self._async_add_to_collection(folder_guid, term_guid, body)) + return run_sync(self._async_add_to_collection(folder_guid, term_guid, body)) @dynamic_catch diff --git a/pyegeria/omvs/community_matters_omvs.py b/pyegeria/omvs/community_matters_omvs.py index 0a2468fb..43a2100a 100644 --- a/pyegeria/omvs/community_matters_omvs.py +++ b/pyegeria/omvs/community_matters_omvs.py @@ -471,20 +471,20 @@ def get_community_by_guid( @dynamic_catch async def _async_link_crowd_sourcing_contribution(self, element_guid: str, contributor_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a contributor to an element they crowd-sourced content for (CrowdSourcingContribution relationship). The relationship properties' `roleType` is a CrowdSourcingRole: PROPOSER, REVIEWER, SUPPORTER, APPROVER or OTHER. Async version.""" url = f"{self.community_command_base}/elements/{element_guid}/crowd-sourcing-contributions/{contributor_guid}/attach" - await self._async_new_relationship_request(url, ["CrowdSourcingContributionProperties"], body) + return await self._async_new_relationship_request(url, ["CrowdSourcingContributionProperties"], body) @dynamic_catch def link_crowd_sourcing_contribution(self, element_guid: str, contributor_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a contributor to an element they crowd-sourced content for (CrowdSourcingContribution relationship). The relationship properties' `roleType` is a CrowdSourcingRole: PROPOSER, REVIEWER, SUPPORTER, APPROVER or OTHER.""" - run_sync(self._async_link_crowd_sourcing_contribution(element_guid, contributor_guid, body)) + return run_sync(self._async_link_crowd_sourcing_contribution(element_guid, contributor_guid, body)) @dynamic_catch async def _async_detach_crowd_sourcing_contribution(self, element_guid: str, contributor_guid: str, diff --git a/pyegeria/omvs/connection_maker.py b/pyegeria/omvs/connection_maker.py index 153f4482..9dfe81e3 100644 --- a/pyegeria/omvs/connection_maker.py +++ b/pyegeria/omvs/connection_maker.py @@ -927,7 +927,7 @@ def get_endpoints_for_asset( async def _async_link_connection_connector_type(self, connection_guid: str, connector_type_guid: str, body: Union[NewRelationshipRequestBody, dict] = None): url = f"{self.base_url}/connections/{connection_guid}/connector-types/{connector_type_guid}/attach" - await self._async_new_relationship_request(url, ["ConnectionConnectorTypeProperties"], body) + return await self._async_new_relationship_request(url, ["ConnectionConnectorTypeProperties"], body) def link_connection_connector_type(self, connection_guid: str, connector_type_guid: str, body: Union[NewRelationshipRequestBody, dict] = None): return run_sync(self._async_link_connection_connector_type(connection_guid, connector_type_guid, body)) @@ -941,7 +941,7 @@ def detach_connection_connector_type(self, connection_guid: str, connector_type_ async def _async_link_connection_endpoint(self, connection_guid: str, endpoint_guid: str, body: Union[NewRelationshipRequestBody, dict] = None): url = f"{self.base_url}/connections/{connection_guid}/endpoints/{endpoint_guid}/attach" - await self._async_new_relationship_request(url, ["ConnectToEndpointProperties"], body) + return await self._async_new_relationship_request(url, ["ConnectToEndpointProperties"], body) def link_connection_endpoint(self, connection_guid: str, endpoint_guid: str, body: Union[NewRelationshipRequestBody, dict] = None): return run_sync(self._async_link_connection_endpoint(connection_guid, endpoint_guid, body)) @@ -955,7 +955,7 @@ def detach_connection_endpoint(self, connection_guid: str, endpoint_guid: str, b async def _async_link_embedded_connection(self, connection_guid: str, embedded_connection_guid: str, body: Union[NewRelationshipRequestBody, dict] = None): url = f"{self.base_url}/connections/{connection_guid}/embedded-connections/{embedded_connection_guid}/attach" - await self._async_new_relationship_request(url, ["EmbeddedConnectionProperties"], body) + return await self._async_new_relationship_request(url, ["EmbeddedConnectionProperties"], body) def link_embedded_connection(self, connection_guid: str, embedded_connection_guid: str, body: Union[NewRelationshipRequestBody, dict] = None): return run_sync(self._async_link_embedded_connection(connection_guid, embedded_connection_guid, body)) @@ -969,7 +969,7 @@ def detach_embedded_connection(self, connection_guid: str, embedded_connection_g async def _async_link_asset_to_connection(self, asset_guid: str, connection_guid: str, body: Union[NewRelationshipRequestBody, dict] = None): url = f"{self.base_url}/assets/{asset_guid}/connections/{connection_guid}/attach" - await self._async_new_relationship_request(url, ["ResourceConnectionProperties"], body) + return await self._async_new_relationship_request(url, ["ResourceConnectionProperties"], body) def link_asset_to_connection(self, asset_guid: str, connection_guid: str, body: Union[NewRelationshipRequestBody, dict] = None): return run_sync(self._async_link_asset_to_connection(asset_guid, connection_guid, body)) @@ -1001,7 +1001,7 @@ async def _async_link_resource_to_connection(self, element_guid: str, connection } """ url = f"{self.base_url}/elements/{element_guid}/connections/{connection_guid}/attach" - await self._async_new_relationship_request(url, ["ResourceConnectionProperties"], body) + return await self._async_new_relationship_request(url, ["ResourceConnectionProperties"], body) @dynamic_catch def link_resource_to_connection(self, element_guid: str, connection_guid: str, body: Union[NewRelationshipRequestBody, dict] = None): @@ -1021,7 +1021,7 @@ def detach_resource_from_connection(self, element_guid: str, connection_guid: st async def _async_link_endpoint_to_it_asset(self, asset_guid: str, endpoint_guid: str, body: Union[NewRelationshipRequestBody, dict] = None): url = f"{self.base_url}/assets/{asset_guid}/endpoints/{endpoint_guid}/attach" - await self._async_new_relationship_request(url, ["ServerEndpointProperties"], body) + return await self._async_new_relationship_request(url, ["ServerEndpointProperties"], body) def link_endpoint_to_it_asset(self, asset_guid: str, endpoint_guid: str, body: Union[NewRelationshipRequestBody, dict] = None): return run_sync(self._async_link_endpoint_to_it_asset(asset_guid, endpoint_guid, body)) diff --git a/pyegeria/omvs/data_designer.py b/pyegeria/omvs/data_designer.py index 00b024c2..065d81b9 100644 --- a/pyegeria/omvs/data_designer.py +++ b/pyegeria/omvs/data_designer.py @@ -182,7 +182,7 @@ def find_data_value_specifications( @dynamic_catch async def _async_link_specialized_data_value_specification( self, spec_guid: str, grain_guid: str, body: dict | None = None - ) -> None: + ) -> str | None: """ Link a specialized data value specification (grain) to a data value specification. Async version. @@ -197,7 +197,8 @@ async def _async_link_specialized_data_value_specification( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -208,12 +209,12 @@ async def _async_link_specialized_data_value_specification( f"{self.ref_data_designer_command_base}/data-value-specifications/{spec_guid}" f"/specialized-data-value-specifications/{grain_guid}/attach" ) - await self._async_new_relationship_request(url, ["DataValueHierarchyProperties"], body) + return await self._async_new_relationship_request(url, ["DataValueHierarchyProperties"], body) @dynamic_catch def link_specialized_data_value_specification( self, spec_guid: str, grain_guid: str, body: dict | None = None - ) -> None: + ) -> str | None: """ Link a specialized data value specification (grain) to a data value specification. @@ -228,14 +229,15 @@ def link_specialized_data_value_specification( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ PyegeriaException If there are issues in communications, message format, or Egeria errors. """ - run_sync(self._async_link_specialized_data_value_specification(spec_guid, grain_guid, body)) + return run_sync(self._async_link_specialized_data_value_specification(spec_guid, grain_guid, body)) @dynamic_catch async def _async_detach_specialized_data_value_specification( @@ -2226,7 +2228,7 @@ def update_data_structure(self, data_struct_guid: str, body: dict | UpdateElemen @dynamic_catch async def _async_link_member_data_field(self, parent_data_struct_guid: str, member_data_field_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Connect a data structure to a data field. Async version. @@ -2241,7 +2243,8 @@ async def _async_link_member_data_field(self, parent_data_struct_guid: str, memb Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2278,12 +2281,13 @@ async def _async_link_member_data_field(self, parent_data_struct_guid: str, memb url = (f"{self.ref_data_designer_command_base}/data-structures/{parent_data_struct_guid}" f"/member-data-fields/{member_data_field_guid}/attach") - await self._async_new_relationship_request(url, ["MemberDataFieldProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["MemberDataFieldProperties"], body) logger.info(f"Data field {member_data_field_guid} attached to Data structure {parent_data_struct_guid}.") + return relationship_guid @dynamic_catch def link_member_data_field(self, parent_data_struct_guid: str, member_data_field_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Connect a data structure to a data field. @@ -2298,7 +2302,8 @@ def link_member_data_field(self, parent_data_struct_guid: str, member_data_field Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2333,7 +2338,7 @@ def link_member_data_field(self, parent_data_struct_guid: str, member_data_field """ - run_sync( + return run_sync( self._async_link_member_data_field(parent_data_struct_guid, member_data_field_guid, body)) @dynamic_catch @@ -2444,18 +2449,19 @@ def detach_member_data_field(self, parent_data_struct_guid: str, member_data_fie @dynamic_catch async def _async_link_nested_data_field(self, parent_data_field_guid: str, nested_data_field_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Link a nested data field to a parent data field (NestedDataField relationship). Async version.""" url = (f"{self.ref_data_designer_command_base}/data-fields/{parent_data_field_guid}" f"/nested-data-fields/{nested_data_field_guid}/attach") - await self._async_new_relationship_request(url, ["NestedDataFieldProperties", "MemberDataFieldProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["NestedDataFieldProperties", "MemberDataFieldProperties"], body) logger.info(f"Nested data field {nested_data_field_guid} linked to parent {parent_data_field_guid}.") + return relationship_guid @dynamic_catch def link_nested_data_field(self, parent_data_field_guid: str, nested_data_field_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Link a nested data field to a parent data field (NestedDataField relationship).""" - run_sync(self._async_link_nested_data_field(parent_data_field_guid, nested_data_field_guid, body)) + return run_sync(self._async_link_nested_data_field(parent_data_field_guid, nested_data_field_guid, body)) @dynamic_catch async def _async_detach_nested_data_field(self, parent_data_field_guid: str, nested_data_field_guid: str, @@ -3591,7 +3597,7 @@ def update_data_classification(self, data_classification_guid: str, body: dict | @dynamic_catch async def _async_link_data_classification_to_data_field(self, data_classification_guid: str, data_field_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Connect a data classification to a data field to guide the survey action service (that checks the data quality of a data resource as part of certifying it with the supplied certification type) to the definition @@ -3611,7 +3617,8 @@ async def _async_link_data_classification_to_data_field(self, data_classificatio Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3642,13 +3649,13 @@ async def _async_link_data_classification_to_data_field(self, data_classificatio # these were the old data-class operations, now data value specifications (the common root of # DataClass and DataGrain) - a best-effort mapping, not confirmed against a live round trip. self._warn_data_classification("link_data_classification_to_data_field", "link_data_value_assignment(data_field_guid, spec_guid)") - await self._async_link_data_value_assignment(data_field_guid, data_classification_guid, + return await self._async_link_data_value_assignment(data_field_guid, data_classification_guid, self._as_value_assignment(body)) @dynamic_catch def link_data_classification_to_data_field(self, data_classification_guid: str, data_field_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Connect a data classification to a data field to guide the survey action service (that checks the data quality of a data resource as part of certifying it with the supplied certification type) to the definition @@ -3668,7 +3675,8 @@ def link_data_classification_to_data_field(self, data_classification_guid: str, Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3695,7 +3703,7 @@ def link_data_classification_to_data_field(self, data_classification_guid: str, """ - run_sync( + return run_sync( self._async_link_data_classification_to_data_field(data_classification_guid, data_field_guid, body)) @dynamic_catch @@ -3806,7 +3814,7 @@ def detach_data_classification_from_data_field(self, data_classification_guid: s # @dynamic_catch async def _async_link_data_class_definition(self, data_definition_guid: str, data_class_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Connect an element that is part of a data design to a data class to show that the data class should be used as the specification for the data values when interpreting the data definition. Request body is optional. @@ -3823,7 +3831,8 @@ async def _async_link_data_class_definition(self, data_definition_guid: str, dat Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3853,12 +3862,13 @@ async def _async_link_data_class_definition(self, data_definition_guid: str, dat url = (f"{self.ref_data_designer_command_base}/data-definitions/{data_definition_guid}" f"/data-value-specification-definition/{data_class_guid}/attach") - await self._async_new_relationship_request(url, ["DataValueDefinitionProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["DataValueDefinitionProperties"], body) logger.info(f"Data class {data_class_guid} attached to Data definition {data_definition_guid}.") + return relationship_guid @dynamic_catch def link_data_class_definition(self, data_definition_guid: str, data_class_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Connect an element that is part of a data design to a data class to show that the data class should be used as the specification for the data values when interpreting the data definition. Request body is optional. @@ -3875,7 +3885,8 @@ def link_data_class_definition(self, data_definition_guid: str, data_class_guid: Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3902,7 +3913,7 @@ def link_data_class_definition(self, data_definition_guid: str, data_class_guid: """ - run_sync(self._async_link_data_class_definition(data_definition_guid, data_class_guid, body)) + return run_sync(self._async_link_data_class_definition(data_definition_guid, data_class_guid, body)) @dynamic_catch async def _async_detach_data_class_definition(self, data_definition_guid: str, data_class_guid: str, @@ -4007,18 +4018,19 @@ def detach_data_class_definition(self, data_definition_guid: str, data_class_gui @dynamic_catch async def _async_link_nested_data_class(self, parent_data_class_guid: str, child_data_class_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Link a child data class to a parent via DataClassComposition relationship. Async version.""" url = (f"{self.ref_data_designer_command_base}/data-classes/{parent_data_class_guid}" f"/nested-data-classes/{child_data_class_guid}/attach") - await self._async_new_relationship_request(url, ["DataClassCompositionProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["DataClassCompositionProperties"], body) logger.info(f"Nested data class {child_data_class_guid} linked to parent {parent_data_class_guid}.") + return relationship_guid @dynamic_catch def link_nested_data_class(self, parent_data_class_guid: str, child_data_class_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Link a child data class to a parent via DataClassComposition relationship.""" - run_sync(self._async_link_nested_data_class(parent_data_class_guid, child_data_class_guid, body)) + return run_sync(self._async_link_nested_data_class(parent_data_class_guid, child_data_class_guid, body)) @dynamic_catch async def _async_detach_nested_data_class(self, parent_data_class_guid: str, child_data_class_guid: str, @@ -4038,7 +4050,7 @@ def detach_nested_data_class(self, parent_data_class_guid: str, child_data_class @dynamic_catch async def _async_link_semantic_definition(self, data_definition_guid: str, glossary_term_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Connect an element that is part of a data design to a glossary term to show that the term should be used as the semantic definition for the data values when interpreting the data definition. Request body is optional. @@ -4055,7 +4067,8 @@ async def _async_link_semantic_definition(self, data_definition_guid: str, gloss Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -4085,12 +4098,13 @@ async def _async_link_semantic_definition(self, data_definition_guid: str, gloss url = (f"{self.ref_data_designer_command_base}/data-definitions/{data_definition_guid}" f"/semantic-definition/{glossary_term_guid}/attach") - await self._async_new_relationship_request(url, ["SemanticDefinitionProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["SemanticDefinitionProperties"], body) logger.info(f"Data class {data_definition_guid} attached to term definition {glossary_term_guid}.") + return relationship_guid @dynamic_catch def link_semantic_definition(self, data_definition_guid: str, glossary_term_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Connect an element that is part of a data design to a glossary term to show that the term should be used as the semantic definition for the data values when interpreting the data definition. Request body is optional. @@ -4107,7 +4121,8 @@ def link_semantic_definition(self, data_definition_guid: str, glossary_term_guid Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -4134,7 +4149,7 @@ def link_semantic_definition(self, data_definition_guid: str, glossary_term_guid """ - run_sync(self._async_link_semantic_definition(data_definition_guid, glossary_term_guid, body)) + return run_sync(self._async_link_semantic_definition(data_definition_guid, glossary_term_guid, body)) @dynamic_catch async def _async_detach_semantic_definition(self, data_definition_guid: str, glossary_term_guid: str, @@ -4239,7 +4254,7 @@ def detach_semantic_definition(self, data_definition_guid: str, glossary_term_gu async def _async_link_certification_type_to_data_structure(self, certification_type_guid: str, - data_structure_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + data_structure_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Connect a certification type to a data structure to guide the survey action service (that checks the data quality of a data resource as part of certifying it with the supplied certification type) to the definition @@ -4259,7 +4274,8 @@ async def _async_link_certification_type_to_data_structure(self, certification_t Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -4288,12 +4304,13 @@ async def _async_link_certification_type_to_data_structure(self, certification_t url = (f"{self.ref_data_designer_command_base}/certification-types/{certification_type_guid}" f"/data-structure-definition/{data_structure_guid}/attach") - await self._async_new_relationship_request(url, ["CertificationTypeProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["CertificationTypeProperties"], body) logger.info(f"Certification type {certification_type_guid} linked to {data_structure_guid}.") + return relationship_guid def link_certification_type_to_data_structure(self, certification_type_guid: str, data_structure_guid: str, - body: dict |NewRelationshipRequestBody = None) -> None: + body: dict |NewRelationshipRequestBody = None) -> str | None: """ Connect a certification type to a data structure to guide the survey action service (that checks the data quality of a data resource as part of certifying it with the supplied certification type) to the definition @@ -4313,7 +4330,8 @@ def link_certification_type_to_data_structure(self, certification_type_guid: str Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -4340,7 +4358,7 @@ def link_certification_type_to_data_structure(self, certification_type_guid: str """ - run_sync( + return run_sync( self._async_link_certification_type_to_data_structure(certification_type_guid, data_structure_guid, body)) async def _async_detach_certification_type_from_data_structure(self, certification_type_guid: str, @@ -4797,20 +4815,21 @@ def _extract_additional_data_grain_properties(self, element, columns_struct): return None @dynamic_catch - async def _async_link_data_value_assignment(self, element_guid: str, spec_guid: str, body: dict = None) -> None: + async def _async_link_data_value_assignment(self, element_guid: str, spec_guid: str, body: dict = None) -> str | None: """ Link a Data Value Specification to an element (DataValueAssignment relationship). Async version. """ url = f"{self.ref_data_designer_command_base}/elements/{element_guid}/data-value-specifications/{spec_guid}/attach" - await self._async_new_relationship_request(url, ["DataValueAssignmentProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["DataValueAssignmentProperties"], body) logger.info(f"Data Value Specification {spec_guid} assigned to element {element_guid}.") + return relationship_guid @dynamic_catch - def link_data_value_assignment(self, element_guid: str, spec_guid: str, body: dict = None) -> None: + def link_data_value_assignment(self, element_guid: str, spec_guid: str, body: dict = None) -> str | None: """ Link a Data Value Specification to an element (DataValueAssignment relationship). Sync version. """ - run_sync(self._async_link_data_value_assignment(element_guid, spec_guid, body)) + return run_sync(self._async_link_data_value_assignment(element_guid, spec_guid, body)) @dynamic_catch async def _async_detach_data_value_assignment(self, element_guid: str, spec_guid: str, body: dict = None) -> None: @@ -4891,17 +4910,18 @@ def update_data_value_specification(self, guid: str, body: dict | UpdateElementR @dynamic_catch async def _async_link_linked_data_field(self, data_field_one_guid: str, data_field_two_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach two related data fields (LinkedDataField relationship). Async version.""" url = f"{self.ref_data_designer_command_base}/data-fields/{data_field_one_guid}/linked-data-fields/{data_field_two_guid}/attach" - await self._async_new_relationship_request(url, ["LinkedDataFieldProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["LinkedDataFieldProperties"], body) logger.info(f"Data field {data_field_two_guid} linked to data field {data_field_one_guid}.") + return relationship_guid @dynamic_catch def link_linked_data_field(self, data_field_one_guid: str, data_field_two_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach two related data fields (LinkedDataField relationship).""" - run_sync(self._async_link_linked_data_field(data_field_one_guid, data_field_two_guid, body)) + return run_sync(self._async_link_linked_data_field(data_field_one_guid, data_field_two_guid, body)) @dynamic_catch async def _async_detach_linked_data_field(self, data_field_one_guid: str, data_field_two_guid: str, @@ -4922,17 +4942,18 @@ def detach_linked_data_field(self, data_field_one_guid: str, data_field_two_guid @dynamic_catch async def _async_link_schema_attribute_definition(self, data_field_guid: str, schema_attribute_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a data field to the schema attribute that defines it (SchemaAttributeDefinition relationship). Async version.""" url = f"{self.ref_data_designer_command_base}/data-fields/{data_field_guid}/schema-attribute-definitions/{schema_attribute_guid}/attach" - await self._async_new_relationship_request(url, ["SchemaAttributeDefinitionProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["SchemaAttributeDefinitionProperties"], body) logger.info(f"Schema attribute {schema_attribute_guid} linked to data field {data_field_guid}.") + return relationship_guid @dynamic_catch def link_schema_attribute_definition(self, data_field_guid: str, schema_attribute_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a data field to the schema attribute that defines it (SchemaAttributeDefinition relationship).""" - run_sync(self._async_link_schema_attribute_definition(data_field_guid, schema_attribute_guid, body)) + return run_sync(self._async_link_schema_attribute_definition(data_field_guid, schema_attribute_guid, body)) @dynamic_catch async def _async_detach_schema_attribute_definition(self, data_field_guid: str, schema_attribute_guid: str, @@ -4953,17 +4974,18 @@ def detach_schema_attribute_definition(self, data_field_guid: str, schema_attrib @dynamic_catch async def _async_link_schema_type_definition(self, data_structure_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a data structure to the schema type that defines it (SchemaTypeDefinition relationship). Async version.""" url = f"{self.ref_data_designer_command_base}/data-structures/{data_structure_guid}/schema-type-definitions/{schema_type_guid}/attach" - await self._async_new_relationship_request(url, ["SchemaTypeDefinitionProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["SchemaTypeDefinitionProperties"], body) logger.info(f"Schema type {schema_type_guid} linked to data structure {data_structure_guid}.") + return relationship_guid @dynamic_catch def link_schema_type_definition(self, data_structure_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a data structure to the schema type that defines it (SchemaTypeDefinition relationship).""" - run_sync(self._async_link_schema_type_definition(data_structure_guid, schema_type_guid, body)) + return run_sync(self._async_link_schema_type_definition(data_structure_guid, schema_type_guid, body)) @dynamic_catch async def _async_detach_schema_type_definition(self, data_structure_guid: str, schema_type_guid: str, diff --git a/pyegeria/omvs/data_discovery.py b/pyegeria/omvs/data_discovery.py index 04c4ced9..68ae9c4c 100644 --- a/pyegeria/omvs/data_discovery.py +++ b/pyegeria/omvs/data_discovery.py @@ -328,7 +328,7 @@ async def _async_attach_annotation_to_report( survey_report_guid: str, annotation_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link an annotation to its survey report. Async version. Parameters @@ -342,17 +342,18 @@ async def _async_attach_annotation_to_report( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/data-discovery/survey-reports/{survey_report_guid}/new-annotations/{annotation_guid}/attach" - await self._async_new_relationship_request(url, ["ReportedAnnotationProperties"], body) + return await self._async_new_relationship_request(url, ["ReportedAnnotationProperties"], body) def attach_annotation_to_report( self, survey_report_guid: str, annotation_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link an annotation to its survey report. Parameters @@ -366,9 +367,10 @@ def attach_annotation_to_report( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. """ - run_sync( + return run_sync( self._async_attach_annotation_to_report(survey_report_guid, annotation_guid, body) ) @@ -426,7 +428,7 @@ async def _async_link_annotation_to_described_element( annotation_guid: str, element_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link an annotation to the element it describes. Async version. Parameters @@ -440,17 +442,18 @@ async def _async_link_annotation_to_described_element( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/data-discovery/elements/{element_guid}/associated-annotations/{annotation_guid}/attach" - await self._async_new_relationship_request(url, ["AssociatedAnnotationProperties"], body) + return await self._async_new_relationship_request(url, ["AssociatedAnnotationProperties"], body) def link_annotation_to_described_element( self, annotation_guid: str, element_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link an annotation to the element it describes. Parameters @@ -464,9 +467,10 @@ def link_annotation_to_described_element( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. """ - run_sync( + return run_sync( self._async_link_annotation_to_described_element(annotation_guid, element_guid, body) ) @@ -497,7 +501,7 @@ async def _async_link_annotation_to_its_predecessor( annotation_guid: str, predecessor_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link an annotation to its predecessor. Async version. Parameters @@ -511,17 +515,18 @@ async def _async_link_annotation_to_its_predecessor( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/data-discovery/annotations/{predecessor_guid}/later-annotations/{annotation_guid}/attach" - await self._async_new_relationship_request(url, ["AnnotationExtensionProperties"], body) + return await self._async_new_relationship_request(url, ["AnnotationExtensionProperties"], body) def link_annotation_to_its_predecessor( self, annotation_guid: str, predecessor_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link an annotation to its predecessor. Parameters @@ -535,9 +540,10 @@ def link_annotation_to_its_predecessor( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. """ - run_sync( + return run_sync( self._async_link_annotation_to_its_predecessor(annotation_guid, predecessor_guid, body) ) @@ -595,7 +601,7 @@ async def _async_link_resource_profile_data( annotation_guid: str, element_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link resource profile data to an annotation. Async version. Parameters @@ -609,17 +615,18 @@ async def _async_link_resource_profile_data( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/data-discovery/annotations/{annotation_guid}/resource-profile-data-assets/{element_guid}/attach" - await self._async_new_relationship_request(url, ["ResourceProfileDataProperties"], body) + return await self._async_new_relationship_request(url, ["ResourceProfileDataProperties"], body) def link_resource_profile_data( self, annotation_guid: str, element_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link resource profile data to an annotation. Parameters @@ -633,9 +640,10 @@ def link_resource_profile_data( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. """ - run_sync( + return run_sync( self._async_link_resource_profile_data(annotation_guid, element_guid, body) ) @@ -693,7 +701,7 @@ async def _async_link_annotation_match( annotation_guid: str, element_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link an annotation match. Async version. Parameters @@ -707,17 +715,18 @@ async def _async_link_annotation_match( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/data-discovery/annotations/{annotation_guid}/matched-elements/{element_guid}/attach" - await self._async_new_relationship_request(url, ["AnnotationMatchProperties"], body) + return await self._async_new_relationship_request(url, ["AnnotationMatchProperties"], body) def link_annotation_match( self, annotation_guid: str, element_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link an annotation match. Parameters @@ -731,9 +740,10 @@ def link_annotation_match( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. """ - run_sync( + return run_sync( self._async_link_annotation_match(annotation_guid, element_guid, body) ) @@ -2005,14 +2015,14 @@ def detach_analysis_report_from_asset(self, analysis_report_guid: str, asset_gui @dynamic_catch - async def _async_link_request_for_action_target(self, annotation_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + async def _async_link_request_for_action_target(self, annotation_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Link a request for action target. Async version.""" url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/data-discovery/annotations/{annotation_guid}/request-for-action-targets/{element_guid}/attach" - await self._async_new_relationship_request(url, ["RequestForActionTargetProperties"], body) + return await self._async_new_relationship_request(url, ["RequestForActionTargetProperties"], body) - def link_request_for_action_target(self, annotation_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + def link_request_for_action_target(self, annotation_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Link a request for action target.""" - run_sync(self._async_link_request_for_action_target(annotation_guid, element_guid, body)) + return run_sync(self._async_link_request_for_action_target(annotation_guid, element_guid, body)) @dynamic_catch async def _async_detach_request_for_action_target(self, annotation_guid: str, element_guid: str, body: Optional[dict | DeleteRelationshipRequestBody] = None) -> None: diff --git a/pyegeria/omvs/digital_business.py b/pyegeria/omvs/digital_business.py index 09085c6d..961a919e 100644 --- a/pyegeria/omvs/digital_business.py +++ b/pyegeria/omvs/digital_business.py @@ -759,7 +759,7 @@ async def _async_link_business_capability_dependency( business_capability_guid: str, supporting_capability_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """Link dependent business capabilities. Async version. Parameters @@ -773,7 +773,8 @@ async def _async_link_business_capability_dependency( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -788,14 +789,14 @@ async def _async_link_business_capability_dependency( f"{self.digital_business_command_root}/business-capabilities/" f"{business_capability_guid}/dependencies/{supporting_capability_guid}/attach" ) - await self._async_new_relationship_request(url=url, prop=['BusinessCapabilityDependencyProperties'],body=body) + return await self._async_new_relationship_request(url=url, prop=['BusinessCapabilityDependencyProperties'],body=body) def link_business_capability_dependency( self, business_capability_guid: str, supporting_capability_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """Link dependent business capabilities. Sync version. Parameters @@ -809,14 +810,15 @@ def link_business_capability_dependency( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ PyegeriaException If there are issues in communications, message format, or Egeria errors. """ - run_sync( + return run_sync( self._async_link_business_capability_dependency( business_capability_guid, supporting_capability_guid, body ) @@ -902,7 +904,7 @@ async def _async_link_digital_support( business_capability_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """Attach a business capability to an element that provides digital support. Async version. Parameters @@ -916,7 +918,8 @@ async def _async_link_digital_support( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -927,14 +930,14 @@ async def _async_link_digital_support( f"{self.digital_business_command_root}/business-capabilities/" f"{business_capability_guid}/digital-support/{element_guid}/attach" ) - await self._async_new_relationship_request(url=url, prop=['DigitalSupportProperties'],body=body) + return await self._async_new_relationship_request(url=url, prop=['DigitalSupportProperties'],body=body) def link_digital_support( self, business_capability_guid: str, element_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """Attach a business capability to an element that provides digital support. Sync version. Parameters @@ -948,14 +951,15 @@ def link_digital_support( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ PyegeriaException If there are issues in communications, message format, or Egeria errors. """ - run_sync( + return run_sync( self._async_link_digital_support(business_capability_guid, element_guid, body) ) diff --git a/pyegeria/omvs/external_links.py b/pyegeria/omvs/external_links.py index b74dda82..1c9e5532 100644 --- a/pyegeria/omvs/external_links.py +++ b/pyegeria/omvs/external_links.py @@ -2446,17 +2446,18 @@ def _generate_external_identifier_output(self, elements: dict | list[dict], sear @dynamic_catch async def _async_link_external_id_to_element(self, element_guid: str, external_id_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach an external identifier to the element it identifies (ExternalIdLink relationship). Async version.""" url = f"{self.command_root}/elements/{element_guid}/external-ids/{external_id_guid}/attach" - await self._async_new_relationship_request(url, ["ExternalIdLinkProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["ExternalIdLinkProperties"], body) logger.info(f"External id {external_id_guid} linked to element {element_guid}.") + return relationship_guid @dynamic_catch def link_external_id_to_element(self, element_guid: str, external_id_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach an external identifier to the element it identifies (ExternalIdLink relationship).""" - run_sync(self._async_link_external_id_to_element(element_guid, external_id_guid, body)) + return run_sync(self._async_link_external_id_to_element(element_guid, external_id_guid, body)) @dynamic_catch async def _async_detach_external_id_from_element(self, element_guid: str, external_id_guid: str, diff --git a/pyegeria/omvs/glossary_manager.py b/pyegeria/omvs/glossary_manager.py index 44bf3f22..0a190306 100644 --- a/pyegeria/omvs/glossary_manager.py +++ b/pyegeria/omvs/glossary_manager.py @@ -2848,7 +2848,7 @@ def create_question(self, display_name: Optional[str] = None, @dynamic_catch async def _async_add_relationship_between_terms( self, term1_guid: str, term2_guid: str, relationship_type: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """Add a relationship between terms. Async Version. Parameters @@ -2865,7 +2865,8 @@ async def _async_add_relationship_between_terms( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2914,13 +2915,14 @@ async def _async_add_relationship_between_terms( f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/glossary-manager/glossaries/" f"terms/{term1_guid}/relationships/{relationship_type}/terms/{term2_guid}" ) - await self._async_new_relationship_request(url, ["GlossaryTermRelationshipProperties"],body) + relationship_guid = await self._async_new_relationship_request(url, ["GlossaryTermRelationshipProperties"],body) logger.info(f"Added relationship between {term1_guid} and {term2_guid} of type {relationship_type}") + return relationship_guid @dynamic_catch def add_relationship_between_terms( self, term1_guid: str, term2_guid: str, relationship_type: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """Add a relationship between terms. Parameters @@ -2936,7 +2938,8 @@ def add_relationship_between_terms( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2969,7 +2972,7 @@ def add_relationship_between_terms( } } """ - run_sync( + return run_sync( self._async_add_relationship_between_terms(term1_guid, term2_guid, relationship_type, body) ) @@ -4168,17 +4171,18 @@ def clear_is_class_word(self, term_guid: str, @dynamic_catch async def _async_link_used_in_context(self, glossary_term_guid: str, context_element_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a glossary term to the element that provides its usage context (UsedInContext relationship). Async version.""" url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/glossary-manager/glossary-terms/{glossary_term_guid}/used-in-contexts/{context_element_guid}/attach" - await self._async_new_relationship_request(url, ["UsedInContextProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["UsedInContextProperties"], body) logger.info(f"Context element {context_element_guid} linked to glossary term {glossary_term_guid}.") + return relationship_guid @dynamic_catch def link_used_in_context(self, glossary_term_guid: str, context_element_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a glossary term to the element that provides its usage context (UsedInContext relationship).""" - run_sync(self._async_link_used_in_context(glossary_term_guid, context_element_guid, body)) + return run_sync(self._async_link_used_in_context(glossary_term_guid, context_element_guid, body)) @dynamic_catch async def _async_detach_used_in_context(self, glossary_term_guid: str, context_element_guid: str, diff --git a/pyegeria/omvs/governance_officer.py b/pyegeria/omvs/governance_officer.py index 420075a4..6a5232a8 100644 --- a/pyegeria/omvs/governance_officer.py +++ b/pyegeria/omvs/governance_officer.py @@ -1113,7 +1113,7 @@ def delete_governance_definition(self, definition_guid: str, body: Optional[dict @dynamic_catch async def _async_link_peer_definitions(self, definition_guid1: str, relationship_type: str, definition_guid2: str, - body: dict = None | NewRelationshipRequestBody) -> None: + body: dict = None | NewRelationshipRequestBody) -> str | None: """ Attach two peer governance definitions. Request body is optional. Async Version. Parameters @@ -1129,7 +1129,8 @@ async def _async_link_peer_definitions(self, definition_guid1: str, relationship Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -1171,12 +1172,13 @@ async def _async_link_peer_definitions(self, definition_guid1: str, relationship f"ervers/{self.view_server}/api/open-metadata/governance-officer/governance-definitions/" f"{definition_guid1}/peer-definitions/{relationship_type}/{definition_guid2}/attach") - await self._async_new_relationship_request(url, "PeerDefinitionProperties", body) + relationship_guid = await self._async_new_relationship_request(url, "PeerDefinitionProperties", body) logger.info(f"Linked {definition_guid1} -> {definition_guid2}") + return relationship_guid @dynamic_catch def link_peer_definitions(self, definition_guid1: str, relationship_type: str, definition_guid2: str, - body: Optional[dict | NewElementRequestBody] = None) -> None: + body: Optional[dict | NewElementRequestBody] = None) -> str | None: """ Attach two peer governance definitions. Async Version. Parameters @@ -1192,7 +1194,8 @@ def link_peer_definitions(self, definition_guid1: str, relationship_type: str, d Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -1229,7 +1232,7 @@ def link_peer_definitions(self, definition_guid1: str, relationship_type: str, d } } """ - run_sync( + return run_sync( self._async_link_peer_definitions(definition_guid1, relationship_type, definition_guid2, body)) @dynamic_catch @@ -1331,7 +1334,7 @@ def detach_peer_definitions(self, definition_guid1: str, relationship_type: str, @dynamic_catch async def _async_attach_supporting_definitions(self, definition_guid1: str, relationship_type: str, definition_guid2: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a supporting governance definition. Request body is optional. The relationshipTypeNme can be: * GovernanceResponse between governance drivers (GovernanceStrategy, BusinessImperative, Regulation, @@ -1356,7 +1359,8 @@ async def _async_attach_supporting_definitions(self, definition_guid1: str, rela Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -1390,12 +1394,13 @@ async def _async_attach_supporting_definitions(self, definition_guid1: str, rela f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/" f"{self.url_marker}/governance-definitions/" f"{definition_guid1}/supporting-definitions/{relationship_type}/{definition_guid2}/attach") - await self._async_new_relationship_request(url, "SupportingDefinitionProperties", body) + relationship_guid = await self._async_new_relationship_request(url, "SupportingDefinitionProperties", body) logger.info(f"Linked {definition_guid1} -> {definition_guid2}") + return relationship_guid @dynamic_catch def attach_supporting_definitions(self, definition_guid1: str, relationship_type: str, definition_guid2: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a supporting governance definition. Request body is optional. The relationshipTypeNme can be: * GovernanceResponse between governance drivers (GovernanceStrategy, BusinessImperative, Regulation, @@ -1418,7 +1423,8 @@ def attach_supporting_definitions(self, definition_guid1: str, relationship_type Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -1448,7 +1454,7 @@ def attach_supporting_definitions(self, definition_guid1: str, relationship_type } } """ - run_sync( + return run_sync( self._async_attach_supporting_definitions(definition_guid1, relationship_type, definition_guid2, body)) @dynamic_catch @@ -1552,7 +1558,7 @@ def detach_supporting_definitions(self, definition_guid1: str, relationship_type async def _async_attach_governed_by_definition(self, element_guid: str, definition_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Link a governance definition to an element using the GovernedBy relationship. Request body is optional. Async Version. @@ -1568,7 +1574,8 @@ async def _async_attach_governed_by_definition(self, element_guid: str, definiti Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -1597,12 +1604,13 @@ async def _async_attach_governed_by_definition(self, element_guid: str, definiti f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/" f"{self.url_marker}/elements/{element_guid}/governed-by/definition/{definition_guid}/attach" ) - await self._async_new_relationship_request(url, "GovernedByProperties", body) + relationship_guid = await self._async_new_relationship_request(url, "GovernedByProperties", body) logger.info(f"Linked Governed-By {definition_guid} -> {element_guid}") + return relationship_guid @dynamic_catch def attach_governed_by_definition(self, element_guid: str, definition_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Link a governance definition to an element using the GovernedBy relationship. Request body is optional. Parameters @@ -1616,7 +1624,8 @@ def attach_governed_by_definition(self, element_guid: str, definition_guid: str, Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -1641,7 +1650,7 @@ def attach_governed_by_definition(self, element_guid: str, definition_guid: str, "effectiveTime" : "{{$isoTimestamp}}" } """ - run_sync( + return run_sync( self._async_attach_governed_by_definition(element_guid, definition_guid, body)) @dynamic_catch @@ -2396,7 +2405,7 @@ def find_governance_action_processes( @dynamic_catch async def _async_link_approved_purpose(self, element_guid: str, data_processing_purpose_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach an element to an approved purpose. Async version. Parameters @@ -2434,14 +2443,15 @@ async def _async_link_approved_purpose(self, element_guid: str, data_processing_ """ url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/{self.url_marker}/elements/" f"{element_guid}/approved-purposes/{data_processing_purpose_guid}/attach") - await self._async_new_relationship_request(url, ["ApprovedPurposeProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["ApprovedPurposeProperties"], body) logger.info(f"Linked approved purpose {data_processing_purpose_guid} to element {element_guid}") + return relationship_guid @dynamic_catch def link_approved_purpose(self, element_guid: str, data_processing_purpose_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach an element to an approved purpose.""" - run_sync(self._async_link_approved_purpose(element_guid, data_processing_purpose_guid, body)) + return run_sync(self._async_link_approved_purpose(element_guid, data_processing_purpose_guid, body)) @dynamic_catch async def _async_detach_approved_purpose(self, element_guid: str, data_processing_purpose_guid: str, @@ -3043,7 +3053,7 @@ def get_governance_action_process( @dynamic_catch async def _async_link_design_to_implementation(self, design_desc_guid: str, implementation_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a design object such as a solution component or governance definition to its implementation via the ImplementedBy relationship. Request body is optional. Async Version. https://egeria-project.org/types/7/0737-Solution-Implementation/ @@ -3059,7 +3069,8 @@ async def _async_link_design_to_implementation(self, design_desc_guid: str, impl Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3095,12 +3106,13 @@ async def _async_link_design_to_implementation(self, design_desc_guid: str, impl """ url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/{self.url_marker}/designs/" f"{design_desc_guid}/implementations/{implementation_guid}/attach") - await self._async_new_relationship_request(url, "ImplementedByProperties", body) + relationship_guid = await self._async_new_relationship_request(url, "ImplementedByProperties", body) logger.info(f"Linked design to implementation: {design_desc_guid} -> {implementation_guid}") + return relationship_guid @dynamic_catch def link_design_to_implementation(self, design_desc_guid: str, implementation_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a design object such as a solution component or governance definition to its implementation via the ImplementedBy relationship. Request body is optional. https://egeria-project.org/types/7/0737-Solution-Implementation/ @@ -3116,7 +3128,8 @@ def link_design_to_implementation(self, design_desc_guid: str, implementation_gu Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3150,7 +3163,7 @@ def link_design_to_implementation(self, design_desc_guid: str, implementation_gu } } """ - run_sync(self._async_link_design_to_implementation(design_desc_guid, implementation_guid, body)) + return run_sync(self._async_link_design_to_implementation(design_desc_guid, implementation_guid, body)) @dynamic_catch async def _async_detach_design_from_implementation(self, design_desc_guid: str, implementation_guid: str, @@ -3243,7 +3256,7 @@ def detach_design_from_implementation(self, design_desc_guid: str, implementatio @dynamic_catch async def _async_link_implementation_resource(self, design_desc_guid: str, implementation_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a design object such as a solution component or governance definition to one of its implementation resource via the ImplementationResource relationship. Request body is optional. https://egeria-project.org/types/7/0737-Solution-Implementation/ @@ -3259,7 +3272,8 @@ async def _async_link_implementation_resource(self, design_desc_guid: str, imple Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3296,12 +3310,13 @@ async def _async_link_implementation_resource(self, design_desc_guid: str, imple url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/{self.url_marker}/designs/" f"{design_desc_guid}/implementation-resources/{implementation_guid}/attach") - await self._async_new_relationship_request(url, "ImplementationResourceProperties", body) + relationship_guid = await self._async_new_relationship_request(url, "ImplementationResourceProperties", body) logger.info(f"Linked design to implementation resource: {design_desc_guid} -> {implementation_guid}") + return relationship_guid @dynamic_catch def link_implementation_resource(self, design_desc_guid: str, implementation_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a design object such as a solution component or governance definition to its implementation via the ImplementedBy relationship. Request body is optional. https://egeria-project.org/types/7/0737-Solution-Implementation/ @@ -3317,7 +3332,8 @@ def link_implementation_resource(self, design_desc_guid: str, implementation_gui Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3351,7 +3367,7 @@ def link_implementation_resource(self, design_desc_guid: str, implementation_gui } } """ - run_sync(self._async_link_implementation_resource(design_desc_guid, implementation_guid, body)) + return run_sync(self._async_link_implementation_resource(design_desc_guid, implementation_guid, body)) @dynamic_catch async def _async_detach_implementation_resource(self, design_desc_guid: str, implementation_guid: str, @@ -3446,7 +3462,7 @@ def detach_implementation_resource(self, design_desc_guid: str, implementation_g @dynamic_catch async def _async_link_governance_results(self, gov_metric_guid: str, data_asset_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a governance metric to a data asset that describes where its measurements are kept. Request body is optional. https://egeria-project.org/concepts/governance-definition/ @@ -3463,7 +3479,8 @@ async def _async_link_governance_results(self, gov_metric_guid: str, data_asset_ Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3493,12 +3510,13 @@ async def _async_link_governance_results(self, gov_metric_guid: str, data_asset_ url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/{self.url_marker}/governance-metrics/" f"{gov_metric_guid}/measurements/{data_asset_guid}/attach" ) - await self._async_new_relationship_request(url, "GovernanceResultsProperties", body) + relationship_guid = await self._async_new_relationship_request(url, "GovernanceResultsProperties", body) logger.info(f"Linked governance metric to a data asset containing its measurements.: {gov_metric_guid} -> {data_asset_guid}") + return relationship_guid @dynamic_catch def link_governance_results(self, gov_metric_guid: str, data_asset_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a governance metric to a data asset that describes where its measurements are kept. Request body is optional. https://egeria-project.org/concepts/governance-definition/ @@ -3514,7 +3532,8 @@ def link_governance_results(self, gov_metric_guid: str, data_asset_guid: str, Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3540,7 +3559,7 @@ def link_governance_results(self, gov_metric_guid: str, data_asset_guid: str, "forDuplicateProcessing" : false } """ - run_sync(self._async_link_governance_results(gov_metric_guid, data_asset_guid, body)) + return run_sync(self._async_link_governance_results(gov_metric_guid, data_asset_guid, body)) @dynamic_catch async def _async_detach_governance_results(self, gov_metric_guid: str, data_asset_guid: str, body: Optional[dict | DeleteRelationshipRequestBody] = None) -> None: @@ -3624,7 +3643,7 @@ def detach_governance_results(self, gov_metric_guid: str, data_asset_guid: str, run_sync(self._async_detach_governance_results(gov_metric_guid, data_asset_guid, body)) @dynamic_catch async def _async_add_regulator_to_regulation(self, regulation_guid: str, regulator_guid, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a regulation to a regulator (usually an organization) using the Regulator relationship. Request body is optional. https://egeria-project.org/egeria-solutions/assessing-new-regulation/overview @@ -3641,7 +3660,8 @@ async def _async_add_regulator_to_regulation(self, regulation_guid: str, regulat Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3674,12 +3694,13 @@ async def _async_add_regulator_to_regulation(self, regulation_guid: str, regulat url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/{self.url_marker}/" f"regulations/{regulation_guid}/regulators/organizations/{regulator_guid}/attach" ) - await self._async_new_relationship_request(url, "RegulatorProperties", body) + relationship_guid = await self._async_new_relationship_request(url, "RegulatorProperties", body) logger.info(f"Linked regulation to regulator: {regulation_guid} -> {regulator_guid}") + return relationship_guid @dynamic_catch def add_regulator_to_regulation(self, regulation_guid: str, regulator_guid, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a regulation to a regulator (usually an organization) using the Regulator relationship. Request body is optional. https://egeria-project.org/egeria-solutions/assessing-new-regulation/overview @@ -3695,7 +3716,8 @@ def add_regulator_to_regulation(self, regulation_guid: str, regulator_guid, Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -3724,7 +3746,7 @@ def add_regulator_to_regulation(self, regulation_guid: str, regulator_guid, } """ - run_sync(self._async_add_regulator_to_regulation(regulation_guid, regulator_guid, body)) + return run_sync(self._async_add_regulator_to_regulation(regulation_guid, regulator_guid, body)) @dynamic_catch async def _async_detach_regulator_from_regulation(self, regulation_guid: str, regulator_guid, body: Optional[dict | DeleteRelationshipRequestBody] = None) -> None: @@ -3822,17 +3844,18 @@ def detach_regulator_from_regulation(self, regulation_guid: str, regulator_guid, @dynamic_catch async def _async_link_exception(self, element_guid: str, exception_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach an exception to the element it was raised against (Exception relationship). Async version.""" url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/{self.url_marker}/elements/{element_guid}/exceptions/{exception_type_guid}/attach" - await self._async_new_relationship_request(url, ["ExceptionProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["ExceptionProperties"], body) logger.info(f"Exception {exception_type_guid} linked to element {element_guid}.") + return relationship_guid @dynamic_catch def link_exception(self, element_guid: str, exception_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach an exception to the element it was raised against (Exception relationship).""" - run_sync(self._async_link_exception(element_guid, exception_type_guid, body)) + return run_sync(self._async_link_exception(element_guid, exception_type_guid, body)) @dynamic_catch async def _async_update_exception(self, exception_guid: str, @@ -3864,17 +3887,18 @@ def detach_exception(self, exception_guid: str, @dynamic_catch async def _async_link_regulation_certification_type(self, regulation_guid: str, certification_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a certification type that demonstrates compliance with a regulation (RegulationCertificationType relationship). Async version.""" url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/{self.url_marker}/regulations/{regulation_guid}/regulation-certification-types/{certification_type_guid}/attach" - await self._async_new_relationship_request(url, ["RegulationCertificationTypeProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["RegulationCertificationTypeProperties"], body) logger.info(f"Certification type {certification_type_guid} linked to regulation {regulation_guid}.") + return relationship_guid @dynamic_catch def link_regulation_certification_type(self, regulation_guid: str, certification_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a certification type that demonstrates compliance with a regulation (RegulationCertificationType relationship).""" - run_sync(self._async_link_regulation_certification_type(regulation_guid, certification_type_guid, body)) + return run_sync(self._async_link_regulation_certification_type(regulation_guid, certification_type_guid, body)) @dynamic_catch async def _async_detach_regulation_certification_type(self, regulation_guid: str, certification_type_guid: str, diff --git a/pyegeria/omvs/location_arena.py b/pyegeria/omvs/location_arena.py index 486697e0..188dfc48 100644 --- a/pyegeria/omvs/location_arena.py +++ b/pyegeria/omvs/location_arena.py @@ -336,7 +336,7 @@ def update_location(self, location_guid: str, body: dict | UpdateElementRequestB @dynamic_catch async def _async_link_peer_locations(self, location1_guid: str, location2_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Link peer locations. Async version. Parameters @@ -350,7 +350,8 @@ async def _async_link_peer_locations(self, location1_guid: str, location2_guid: Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -372,8 +373,9 @@ async def _async_link_peer_locations(self, location1_guid: str, location2_guid: ``` """ url = f"{self.ref_location_command_base}/locations/{location1_guid}/adjacent-locations/{location2_guid}/attach" - await self._async_new_relationship_request(url, ["AdjacentLocationProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["AdjacentLocationProperties"], body) logger.info(f"Linking location {location1_guid} to location {location2_guid}") + return relationship_guid @dynamic_catch def link_peer_locations(self, location1_guid: str, location2_guid: str, @@ -391,7 +393,8 @@ def link_peer_locations(self, location1_guid: str, location2_guid: str, Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -412,7 +415,7 @@ def link_peer_locations(self, location1_guid: str, location2_guid: str, } ``` """ - run_sync(self._async_link_peer_locations(location1_guid, location2_guid, body)) + return run_sync(self._async_link_peer_locations(location1_guid, location2_guid, body)) @dynamic_catch async def _async_detach_peer_locations(self, location1_guid: str, location2_guid: str, @@ -502,7 +505,7 @@ def detach_peer_locations(self, location1_guid: str, location2_guid: str, @dynamic_catch async def _async_link_nested_location(self, location_guid: str, nested_location_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ AAttach a super location to a nested location. Async version. @@ -550,8 +553,9 @@ async def _async_link_nested_location(self, location_guid: str, nested_location_ """ url = f"{self.ref_location_command_base}/locations/{location_guid}/nested-locations/{nested_location_guid}/attach" - await self._async_new_relationship_request(url, ["NestedLocationProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["NestedLocationProperties"], body) logger.info(f"Linking element {location_guid} to nested location {nested_location_guid}") + return relationship_guid @dynamic_catch def link_nested_location(self, location_guid: str, nested_location_guid: str, @@ -600,7 +604,7 @@ def link_nested_location(self, location_guid: str, nested_location_guid: str, } """ - run_sync(self._async_link_nested_location(location_guid, nested_location_guid, body)) + return run_sync(self._async_link_nested_location(location_guid, nested_location_guid, body)) @dynamic_catch async def _async_detach_nested_location(self, location_guid: str, nested_location_guid: str, @@ -691,7 +695,7 @@ def detach_nested_location(self, location_guid: str, nested_location_guid: str, @dynamic_catch async def _async_link_known_location(self, element_guid: str, location_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach an element to its location. Async version. @@ -739,8 +743,9 @@ async def _async_link_known_location(self, element_guid: str, location_guid: str """ url = f"{self.ref_location_command_base}/elements/{element_guid}/known-locations/{location_guid}/attach" - await self._async_new_relationship_request(url, ["KnownLocationProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["KnownLocationProperties"], body) logger.info(f"Linking element {element_guid} to location {location_guid}") + return relationship_guid @dynamic_catch def link_known_location(self, element_guid: str, location_guid: str, @@ -790,7 +795,7 @@ def link_known_location(self, element_guid: str, location_guid: str, } """ - run_sync(self._async_link_known_location(element_guid, location_guid, body)) + return run_sync(self._async_link_known_location(element_guid, location_guid, body)) @dynamic_catch async def _async_detach_known_location(self, element_guid: str, location_guid: str, diff --git a/pyegeria/omvs/people_organizer.py b/pyegeria/omvs/people_organizer.py index 0776a8d8..db972aa1 100644 --- a/pyegeria/omvs/people_organizer.py +++ b/pyegeria/omvs/people_organizer.py @@ -64,7 +64,7 @@ async def _async_link_peer_person( person_one_guid: str, person_two_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link a person profile to one of its peers. Async version. Parameters @@ -78,7 +78,8 @@ async def _async_link_peer_person( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -99,14 +100,14 @@ async def _async_link_peer_person( ``` """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/people-organizer/actor-profiles/{person_one_guid}/peer-persons/{person_two_guid}/attach" - await self._async_new_relationship_request(url, ["PeerProperties"], body) + return await self._async_new_relationship_request(url, ["PeerProperties"], body) def link_peer_person( self, person_one_guid: str, person_two_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link a person profile to one of its peers. Parameters @@ -120,7 +121,8 @@ def link_peer_person( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -140,7 +142,7 @@ def link_peer_person( } ``` """ - run_sync( + return run_sync( self._async_link_peer_person(person_one_guid, person_two_guid, body) ) @@ -270,7 +272,7 @@ async def _async_link_team_structure( super_team_guid: str, subteam_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link a super team to a subteam. Async version. Parameters @@ -284,7 +286,8 @@ async def _async_link_team_structure( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -305,14 +308,14 @@ async def _async_link_team_structure( ``` """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/people-organizer/actor-profiles/{super_team_guid}/team-structures/{subteam_guid}/attach" - await self._async_new_relationship_request(url, ["TeamStructureProperties"], body) + return await self._async_new_relationship_request(url, ["TeamStructureProperties"], body) def link_team_structure( self, super_team_guid: str, subteam_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link a super team to a subteam. Parameters @@ -326,7 +329,8 @@ def link_team_structure( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -346,7 +350,7 @@ def link_team_structure( } ``` """ - run_sync( + return run_sync( self._async_link_team_structure(super_team_guid, subteam_guid, body) ) diff --git a/pyegeria/omvs/privacy_officer.py b/pyegeria/omvs/privacy_officer.py index 3f8a0646..60ed5eb8 100644 --- a/pyegeria/omvs/privacy_officer.py +++ b/pyegeria/omvs/privacy_officer.py @@ -193,16 +193,16 @@ def detach_data_processing_target( @dynamic_catch async def _async_link_data_processing_specification(self, element_guid: str, data_processing_description_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a data processing description to the element it specifies processing for (DataProcessingSpecification relationship). Async version.""" url = f"{self.privacy_officer_base_url}/elements/{element_guid}/data-processing-specifications/{data_processing_description_guid}/attach" - await self._async_new_relationship_request(url, ["DataProcessingSpecificationProperties"], body) + return await self._async_new_relationship_request(url, ["DataProcessingSpecificationProperties"], body) @dynamic_catch def link_data_processing_specification(self, element_guid: str, data_processing_description_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a data processing description to the element it specifies processing for (DataProcessingSpecification relationship).""" - run_sync(self._async_link_data_processing_specification(element_guid, data_processing_description_guid, body)) + return run_sync(self._async_link_data_processing_specification(element_guid, data_processing_description_guid, body)) @dynamic_catch async def _async_detach_data_processing_specification(self, element_guid: str, data_processing_description_guid: str, @@ -219,16 +219,16 @@ def detach_data_processing_specification(self, element_guid: str, data_processin @dynamic_catch async def _async_link_detailed_processing_action(self, parent_processing_action_guid: str, child_processing_action_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a child processing action to the parent action it details (DetailedProcessingAction relationship). Async version.""" url = f"{self.privacy_officer_base_url}/data-processing-actions/{parent_processing_action_guid}/detailed-processing-actions/{child_processing_action_guid}/attach" - await self._async_new_relationship_request(url, ["DetailedProcessingActionProperties"], body) + return await self._async_new_relationship_request(url, ["DetailedProcessingActionProperties"], body) @dynamic_catch def link_detailed_processing_action(self, parent_processing_action_guid: str, child_processing_action_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a child processing action to the parent action it details (DetailedProcessingAction relationship).""" - run_sync(self._async_link_detailed_processing_action(parent_processing_action_guid, child_processing_action_guid, body)) + return run_sync(self._async_link_detailed_processing_action(parent_processing_action_guid, child_processing_action_guid, body)) @dynamic_catch async def _async_detach_detailed_processing_action(self, parent_processing_action_guid: str, child_processing_action_guid: str, diff --git a/pyegeria/omvs/product_manager.py b/pyegeria/omvs/product_manager.py index 428f01ca..53b71646 100644 --- a/pyegeria/omvs/product_manager.py +++ b/pyegeria/omvs/product_manager.py @@ -977,7 +977,7 @@ async def _async_link_product_manager( digital_product_guid: str, product_manager_role_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """Attach a product manager role to a digital product. Async version. Parameters @@ -991,7 +991,8 @@ async def _async_link_product_manager( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -1016,15 +1017,16 @@ async def _async_link_product_manager( f"{self.product_manager_command_root}/digital-products/" f"{digital_product_guid}/product-managers/{product_manager_role_guid}/attach" ) - await self._async_new_relationship_request(url, ["AssignmentScopeProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["AssignmentScopeProperties"], body) logger.info(f"Attached digital product manager {digital_product_guid} -> {product_manager_role_guid}") + return relationship_guid def link_product_manager( self, digital_product_guid: str, product_manager_role_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None, - ) -> None: + ) -> str | None: """Attach a product manager role to a digital product. Parameters @@ -1038,7 +1040,8 @@ def link_product_manager( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -1059,7 +1062,7 @@ def link_product_manager( } ``` """ - run_sync( + return run_sync( self._async_link_product_manager( digital_product_guid, product_manager_role_guid, body ) diff --git a/pyegeria/omvs/project_manager.py b/pyegeria/omvs/project_manager.py index 391964ae..51d53a9f 100644 --- a/pyegeria/omvs/project_manager.py +++ b/pyegeria/omvs/project_manager.py @@ -1842,8 +1842,9 @@ async def _async_set_project_dependency(self, project_guid: str, url = ( f"{self.project_command_base}/{project_guid}/project-dependencies/{upstream_project_guid}/attach" ) - await self._async_new_relationship_request(url, None, body) + relationship_guid = await self._async_new_relationship_request(url, None, body) logger.info(f"Project {project_guid} depends on -> {upstream_project_guid}") + return relationship_guid @dynamic_catch def set_project_dependency(self, project_guid: str, @@ -1878,7 +1879,7 @@ def set_project_dependency(self, project_guid: str, ----- """ - run_sync( + return run_sync( self._async_set_project_dependency(project_guid, upstream_project_guid, body)) @@ -2009,8 +2010,9 @@ async def _async_set_project_hierarchy(self, project_guid: str, url = ( f"{self.project_command_base}/{parent_project_guid}/project-hierarchies/{project_guid}/attach" ) - await self._async_new_relationship_request(url, None, body) + relationship_guid = await self._async_new_relationship_request(url, None, body) logger.info(f"Project {project_guid} managed by -> {parent_project_guid}") + return relationship_guid @dynamic_catch def set_project_hierarchy(self, project_guid: str, @@ -2045,7 +2047,7 @@ def set_project_hierarchy(self, project_guid: str, ----- """ - run_sync( + return run_sync( self._async_set_project_hierarchy(project_guid, parent_project_guid, body)) @@ -2147,7 +2149,7 @@ async def _async_add_to_project_team( assignment_type: Optional[str] = None, description: Optional[str] = None, body: Optional[dict | NewRelationshipRequestBody] = None - ) -> None: + ) -> str | None: """Add an actor to a project. The request body is optional. If supplied, it contains the name of the perspective that the actor plays in the project. Async version. @@ -2166,7 +2168,8 @@ async def _async_add_to_project_team( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2195,8 +2198,9 @@ async def _async_add_to_project_team( } body_s = body_slimmer(body) - await self._async_new_relationship_request(url, ["AssignmentScopeProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["AssignmentScopeProperties"], body) logger.info(f"Added member {actor_guid} to project {project_guid}") + return relationship_guid @dynamic_catch def add_to_project_team( @@ -2206,7 +2210,7 @@ def add_to_project_team( assignment_type: Optional[str] = None, description: Optional[str] = None, body: Optional[dict | NewRelationshipRequestBody] = None - ) -> None: + ) -> str | None: """Add an actor to a project. The request body is optional. If supplied, it contains the name of the perspective that the actor plays in the project. @@ -2225,7 +2229,8 @@ def add_to_project_team( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2238,7 +2243,7 @@ def add_to_project_team( The principle specified by the user_id does not have authorization for the requested action """ - run_sync( + return run_sync( self._async_add_to_project_team( project_guid, actor_guid, diff --git a/pyegeria/omvs/reference_data.py b/pyegeria/omvs/reference_data.py index 2eb97885..9c4e5656 100644 --- a/pyegeria/omvs/reference_data.py +++ b/pyegeria/omvs/reference_data.py @@ -957,13 +957,14 @@ async def _async_link_valid_value_member(self, vv_set_guid:str, vv_member_guid: """ url = f"{self.ref_data_command_base}/valid-values/{vv_set_guid}/members/{vv_member_guid}/attach" - await self._async_new_relationship_request(url, ["ValidValueMemberProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["ValidValueMemberProperties"], body) logger.info(f"Linked valid value definition {vv_set_guid} to {vv_member_guid}") + return relationship_guid @dynamic_catch def link_valid_value_definition( self, - vv_set_guid: str, vv_member_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + vv_set_guid: str, vv_member_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Link a valid value definition to another valid value definition. Parameters @@ -1005,7 +1006,7 @@ def link_valid_value_definition( } """ - run_sync(self._async_link_valid_value_member(vv_set_guid, vv_member_guid, body)) + return run_sync(self._async_link_valid_value_member(vv_set_guid, vv_member_guid, body)) @dynamic_catch async def _async_detach_valid_value_member(self, vv_set_guid: str, vv_member_guid: str, @@ -1099,7 +1100,7 @@ def detach_valid_value_definition( @dynamic_catch async def _async_link_valid_values_assignment( - self, element_guid: str, vv_def_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + self, element_guid: str, vv_def_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a valid value to a consumer - probably a schema element or data set. Async version. Parameters @@ -1140,12 +1141,13 @@ async def _async_link_valid_values_assignment( """ url = f"{self.ref_data_command_base}/elements/{element_guid}/valid-values-assignment/{vv_def_guid}/attach" - await self._async_new_relationship_request(url, ["ValidValuesAssignmentProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["ValidValuesAssignmentProperties"], body) logger.info(f"Linked valid value definition {vv_def_guid} to consumer {element_guid}") + return relationship_guid @dynamic_catch def link_valid_values_assignment( - self, element_guid: str, vv_def_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + self, element_guid: str, vv_def_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a valid value to a consumer - probably a schema element or data set. Parameters @@ -1185,7 +1187,7 @@ def link_valid_values_assignment( } """ - run_sync(self._async_link_valid_values_assignment(element_guid, vv_def_guid, body)) + return run_sync(self._async_link_valid_values_assignment(element_guid, vv_def_guid, body)) @dynamic_catch async def _async_detach_valid_values_assignment( @@ -1278,7 +1280,7 @@ def detach_valid_values_assignment( @dynamic_catch async def _async_link_reference_value_assignment( - self, element_guid: str, vv_def_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + self, element_guid: str, vv_def_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a valid value to a tagged element. Async version. Parameters @@ -1323,12 +1325,13 @@ async def _async_link_reference_value_assignment( """ url = f"{self.ref_data_command_base}/elements/{element_guid}/reference-value-assignment/{vv_def_guid}/attach" - await self._async_new_relationship_request(url, ["ReferenceValueAssignmentProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["ReferenceValueAssignmentProperties"], body) logger.info(f"Linked reference value {vv_def_guid} to tagged element {element_guid}") + return relationship_guid @dynamic_catch def link_reference_value_assignment( - self, element_guid: str, vv_def_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + self, element_guid: str, vv_def_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Attach a valid value to a tagged element. Parameters @@ -1372,7 +1375,7 @@ def link_reference_value_assignment( } """ - run_sync(self._async_link_reference_value_assignment(element_guid, vv_def_guid, body)) + return run_sync(self._async_link_reference_value_assignment(element_guid, vv_def_guid, body)) @dynamic_catch async def _async_detach_reference_value_assignment( @@ -1660,7 +1663,7 @@ def detach_valid_value_implementation( @dynamic_catch async def _async_link_associated_valid_values( self, vv_def_one_guid: str, vv_def_two_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Link two valid value definitions as associated (related) values. Async version. Parameters @@ -1702,13 +1705,14 @@ async def _async_link_associated_valid_values( """ url = f"{self.ref_data_command_base}/valid-values/{vv_def_one_guid}/associated-valid-values/{vv_def_two_guid}/attach" - await self._async_new_relationship_request(url, ["ValidValueAssociationProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["ValidValueAssociationProperties"], body) logger.info(f"Linked associated valid values {vv_def_one_guid} and {vv_def_two_guid}") + return relationship_guid @dynamic_catch def link_associated_valid_values( self, vv_def_one_guid: str, vv_def_two_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Link two valid value definitions as associated (related) values. Parameters @@ -1749,7 +1753,7 @@ def link_associated_valid_values( } """ - run_sync(self._async_link_associated_valid_values(vv_def_one_guid, vv_def_two_guid, body)) + return run_sync(self._async_link_associated_valid_values(vv_def_one_guid, vv_def_two_guid, body)) @dynamic_catch async def _async_detach_associated_valid_values( @@ -1845,7 +1849,7 @@ def detach_associated_valid_values( @dynamic_catch async def _async_link_consistent_valid_values( self, vv_def_one_guid: str, vv_def_two_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Link two valid value definitions as consistent with one another. Async version. Parameters @@ -1884,13 +1888,14 @@ async def _async_link_consistent_valid_values( """ url = f"{self.ref_data_command_base}/valid-values/{vv_def_one_guid}/consistent-valid-values/{vv_def_two_guid}/attach" - await self._async_new_relationship_request(url, None, body) + relationship_guid = await self._async_new_relationship_request(url, None, body) logger.info(f"Linked consistent valid values {vv_def_one_guid} and {vv_def_two_guid}") + return relationship_guid @dynamic_catch def link_consistent_valid_values( self, vv_def_one_guid: str, vv_def_two_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Link two valid value definitions as consistent with one another. Parameters @@ -1926,7 +1931,7 @@ def link_consistent_valid_values( } """ - run_sync(self._async_link_consistent_valid_values(vv_def_one_guid, vv_def_two_guid, body)) + return run_sync(self._async_link_consistent_valid_values(vv_def_one_guid, vv_def_two_guid, body)) @dynamic_catch async def _async_detach_consistent_valid_values( @@ -2022,7 +2027,7 @@ def detach_consistent_valid_values( @dynamic_catch async def _async_link_mapped_valid_values( self, vv_def_one_guid: str, vv_def_two_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Link two valid value definitions as a mapping from one to the other. Async version. Parameters @@ -2068,13 +2073,14 @@ async def _async_link_mapped_valid_values( """ url = f"{self.ref_data_command_base}/valid-values/{vv_def_one_guid}/mapped-valid-values/{vv_def_two_guid}/attach" - await self._async_new_relationship_request(url, ["ValidValuesMappingProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["ValidValuesMappingProperties"], body) logger.info(f"Linked mapped valid values {vv_def_one_guid} and {vv_def_two_guid}") + return relationship_guid @dynamic_catch def link_mapped_valid_values( self, vv_def_one_guid: str, vv_def_two_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Link two valid value definitions as a mapping from one to the other. Parameters @@ -2119,7 +2125,7 @@ def link_mapped_valid_values( } """ - run_sync(self._async_link_mapped_valid_values(vv_def_one_guid, vv_def_two_guid, body)) + return run_sync(self._async_link_mapped_valid_values(vv_def_one_guid, vv_def_two_guid, body)) @dynamic_catch async def _async_detach_mapped_valid_values( diff --git a/pyegeria/omvs/runtime_manager.py b/pyegeria/omvs/runtime_manager.py index d1bf890e..46453d21 100644 --- a/pyegeria/omvs/runtime_manager.py +++ b/pyegeria/omvs/runtime_manager.py @@ -3644,7 +3644,7 @@ def get_metadata_repository_cohort_by_guid( @dynamic_catch async def _async_link_cohort_member(self, cohort_guid: str, cohort_member_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Connect a cohort to one of its members. Async version. Parameters @@ -3686,14 +3686,15 @@ async def _async_link_cohort_member(self, cohort_guid: str, cohort_member_guid: ``` """ url = f"{self.runtime_command_root}/metadata-repository-cohorts/{cohort_guid}/cohort-members/{cohort_member_guid}/attach" - await self._async_new_relationship_request(url, ["MetadataCohortPeerProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["MetadataCohortPeerProperties"], body) logger.info(f"Linked cohort member {cohort_member_guid} to cohort {cohort_guid}") + return relationship_guid @dynamic_catch def link_cohort_member(self, cohort_guid: str, cohort_member_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Connect a cohort to one of its members.""" - run_sync(self._async_link_cohort_member(cohort_guid, cohort_member_guid, body)) + return run_sync(self._async_link_cohort_member(cohort_guid, cohort_member_guid, body)) @dynamic_catch async def _async_detach_cohort_member(self, cohort_guid: str, cohort_member_guid: str, diff --git a/pyegeria/omvs/schema_maker.py b/pyegeria/omvs/schema_maker.py index f36e179f..c08ddc6f 100644 --- a/pyegeria/omvs/schema_maker.py +++ b/pyegeria/omvs/schema_maker.py @@ -522,18 +522,19 @@ def get_schema_attribute_by_guid(self, guid: str, **kwargs) -> dict | str: @dynamic_catch async def _async_link_nested_schema_attribute(self, schema_attribute_guid: str, nested_schema_attribute_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a nested schema attribute to its parent schema attribute (NestedSchemaAttribute relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"schema-attributes/{schema_attribute_guid}/nested-schema-attributes/{nested_schema_attribute_guid}/attach") - await self._async_new_relationship_request(url, ["NestedSchemaAttributeProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["NestedSchemaAttributeProperties"], body) logger.info(f"Nested schema attribute {nested_schema_attribute_guid} linked to parent {schema_attribute_guid}.") + return relationship_guid @dynamic_catch def link_nested_schema_attribute(self, schema_attribute_guid: str, nested_schema_attribute_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a nested schema attribute to its parent schema attribute (NestedSchemaAttribute relationship).""" - run_sync(self._async_link_nested_schema_attribute(schema_attribute_guid, nested_schema_attribute_guid, body)) + return run_sync(self._async_link_nested_schema_attribute(schema_attribute_guid, nested_schema_attribute_guid, body)) @dynamic_catch async def _async_detach_nested_schema_attribute(self, schema_attribute_guid: str, nested_schema_attribute_guid: str, @@ -555,18 +556,19 @@ def detach_nested_schema_attribute(self, schema_attribute_guid: str, nested_sche @dynamic_catch async def _async_link_attribute_for_schema(self, schema_type_guid: str, schema_attribute_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a schema attribute to the schema type that it belongs to (AttributeForSchema relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"schema-types/{schema_type_guid}/attribute-for-schema/{schema_attribute_guid}/attach") - await self._async_new_relationship_request(url, ["AttributeForSchemaProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["AttributeForSchemaProperties"], body) logger.info(f"Schema attribute {schema_attribute_guid} linked to schema type {schema_type_guid}.") + return relationship_guid @dynamic_catch def link_attribute_for_schema(self, schema_type_guid: str, schema_attribute_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a schema attribute to the schema type that it belongs to (AttributeForSchema relationship).""" - run_sync(self._async_link_attribute_for_schema(schema_type_guid, schema_attribute_guid, body)) + return run_sync(self._async_link_attribute_for_schema(schema_type_guid, schema_attribute_guid, body)) @dynamic_catch async def _async_detach_attribute_for_schema(self, schema_type_guid: str, schema_attribute_guid: str, @@ -588,18 +590,19 @@ def detach_attribute_for_schema(self, schema_type_guid: str, schema_attribute_gu @dynamic_catch async def _async_link_foreign_key(self, primary_key_column_guid: str, foreign_key_column_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a foreign key column to the primary key column that it refers to (ForeignKey relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"schema-attributes/{primary_key_column_guid}/foreign-keys/{foreign_key_column_guid}/attach") - await self._async_new_relationship_request(url, ["ForeignKeyProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["ForeignKeyProperties"], body) logger.info(f"Foreign key column {foreign_key_column_guid} linked to primary key column {primary_key_column_guid}.") + return relationship_guid @dynamic_catch def link_foreign_key(self, primary_key_column_guid: str, foreign_key_column_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a foreign key column to the primary key column that it refers to (ForeignKey relationship).""" - run_sync(self._async_link_foreign_key(primary_key_column_guid, foreign_key_column_guid, body)) + return run_sync(self._async_link_foreign_key(primary_key_column_guid, foreign_key_column_guid, body)) @dynamic_catch async def _async_detach_foreign_key(self, primary_key_column_guid: str, foreign_key_column_guid: str, @@ -621,18 +624,19 @@ def detach_foreign_key(self, primary_key_column_guid: str, foreign_key_column_gu @dynamic_catch async def _async_link_external_schema_type(self, schema_element_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a schema element to a reusable, externally-defined schema type (LinkedExternalSchemaType relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"schema-elements/{schema_element_guid}/linked-external-schema-types/{schema_type_guid}/attach") - await self._async_new_relationship_request(url, ["LinkedExternalSchemaTypeProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["LinkedExternalSchemaTypeProperties"], body) logger.info(f"External schema type {schema_type_guid} linked to schema element {schema_element_guid}.") + return relationship_guid @dynamic_catch def link_external_schema_type(self, schema_element_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a schema element to a reusable, externally-defined schema type (LinkedExternalSchemaType relationship).""" - run_sync(self._async_link_external_schema_type(schema_element_guid, schema_type_guid, body)) + return run_sync(self._async_link_external_schema_type(schema_element_guid, schema_type_guid, body)) @dynamic_catch async def _async_detach_external_schema_type(self, schema_element_guid: str, schema_type_guid: str, @@ -654,18 +658,19 @@ def detach_external_schema_type(self, schema_element_guid: str, schema_type_guid @dynamic_catch async def _async_link_map_from_schema_type(self, schema_element_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach the 'from' end of a map schema type (MapFromElementType relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"schema-elements/{schema_element_guid}/map-from-element-types/{schema_type_guid}/attach") - await self._async_new_relationship_request(url, ["MapFromElementTypeProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["MapFromElementTypeProperties"], body) logger.info(f"Map-from schema type {schema_type_guid} linked to schema element {schema_element_guid}.") + return relationship_guid @dynamic_catch def link_map_from_schema_type(self, schema_element_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach the 'from' end of a map schema type (MapFromElementType relationship).""" - run_sync(self._async_link_map_from_schema_type(schema_element_guid, schema_type_guid, body)) + return run_sync(self._async_link_map_from_schema_type(schema_element_guid, schema_type_guid, body)) @dynamic_catch async def _async_detach_map_from_schema_type(self, schema_element_guid: str, schema_type_guid: str, @@ -687,18 +692,19 @@ def detach_map_from_schema_type(self, schema_element_guid: str, schema_type_guid @dynamic_catch async def _async_link_map_to_schema_type(self, schema_element_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach the 'to' end of a map schema type (MapToElementType relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"schema-elements/{schema_element_guid}/map-to-element-types/{schema_type_guid}/attach") - await self._async_new_relationship_request(url, ["MapToElementTypeProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["MapToElementTypeProperties"], body) logger.info(f"Map-to schema type {schema_type_guid} linked to schema element {schema_element_guid}.") + return relationship_guid @dynamic_catch def link_map_to_schema_type(self, schema_element_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach the 'to' end of a map schema type (MapToElementType relationship).""" - run_sync(self._async_link_map_to_schema_type(schema_element_guid, schema_type_guid, body)) + return run_sync(self._async_link_map_to_schema_type(schema_element_guid, schema_type_guid, body)) @dynamic_catch async def _async_detach_map_to_schema_type(self, schema_element_guid: str, schema_type_guid: str, @@ -720,18 +726,19 @@ def detach_map_to_schema_type(self, schema_element_guid: str, schema_type_guid: @dynamic_catch async def _async_link_graph_edge(self, graph_edge_guid: str, graph_vertex_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a graph edge to one of the vertices it connects (GraphEdgeLink relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"graph-edges/{graph_edge_guid}/graph-vertices/{graph_vertex_guid}/attach") - await self._async_new_relationship_request(url, ["GraphEdgeLinkProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["GraphEdgeLinkProperties"], body) logger.info(f"Graph edge {graph_edge_guid} linked to graph vertex {graph_vertex_guid}.") + return relationship_guid @dynamic_catch def link_graph_edge(self, graph_edge_guid: str, graph_vertex_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a graph edge to one of the vertices it connects (GraphEdgeLink relationship).""" - run_sync(self._async_link_graph_edge(graph_edge_guid, graph_vertex_guid, body)) + return run_sync(self._async_link_graph_edge(graph_edge_guid, graph_vertex_guid, body)) @dynamic_catch async def _async_detach_graph_edge(self, graph_edge_guid: str, graph_vertex_guid: str, @@ -753,18 +760,19 @@ def detach_graph_edge(self, graph_edge_guid: str, graph_vertex_guid: str, @dynamic_catch async def _async_link_query_target(self, schema_element_guid: str, query_target_schema_element_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a query target to a derived schema element (DerivedSchemaTypeQueryTarget relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"schema-elements/{schema_element_guid}/query-targets/{query_target_schema_element_guid}/attach") - await self._async_new_relationship_request(url, ["DerivedSchemaTypeQueryTargetProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["DerivedSchemaTypeQueryTargetProperties"], body) logger.info(f"Query target {query_target_schema_element_guid} linked to schema element {schema_element_guid}.") + return relationship_guid @dynamic_catch def link_query_target(self, schema_element_guid: str, query_target_schema_element_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a query target to a derived schema element (DerivedSchemaTypeQueryTarget relationship).""" - run_sync(self._async_link_query_target(schema_element_guid, query_target_schema_element_guid, body)) + return run_sync(self._async_link_query_target(schema_element_guid, query_target_schema_element_guid, body)) @dynamic_catch async def _async_detach_query_target(self, schema_element_guid: str, query_target_schema_element_guid: str, @@ -786,18 +794,19 @@ def detach_query_target(self, schema_element_guid: str, query_target_schema_elem @dynamic_catch async def _async_link_schema(self, element_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a schema type to the element it describes (SchemaTypeAssociation relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"elements/{element_guid}/schema-types/{schema_type_guid}/attach") - await self._async_new_relationship_request(url, ["SchemaProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["SchemaProperties"], body) logger.info(f"Schema type {schema_type_guid} linked to element {element_guid}.") + return relationship_guid @dynamic_catch def link_schema(self, element_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a schema type to the element it describes (SchemaTypeAssociation relationship).""" - run_sync(self._async_link_schema(element_guid, schema_type_guid, body)) + return run_sync(self._async_link_schema(element_guid, schema_type_guid, body)) @dynamic_catch async def _async_detach_schema(self, element_guid: str, schema_type_guid: str, @@ -819,18 +828,19 @@ def detach_schema(self, element_guid: str, schema_type_guid: str, @dynamic_catch async def _async_link_relational_db_schema(self, database_schema_type_list_guid: str, relational_db_schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a relational database schema to the schema type list it belongs to (RelationalDBSchema relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"relational-db-schema-type-lists/{database_schema_type_list_guid}/relational-db-schemas/{relational_db_schema_type_guid}/attach") - await self._async_new_relationship_request(url, ["RelationalDBSchemaProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["RelationalDBSchemaProperties"], body) logger.info(f"Relational DB schema {relational_db_schema_type_guid} linked to schema type list {database_schema_type_list_guid}.") + return relationship_guid @dynamic_catch def link_relational_db_schema(self, database_schema_type_list_guid: str, relational_db_schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a relational database schema to the schema type list it belongs to (RelationalDBSchema relationship).""" - run_sync(self._async_link_relational_db_schema( + return run_sync(self._async_link_relational_db_schema( database_schema_type_list_guid, relational_db_schema_type_guid, body)) @dynamic_catch @@ -853,18 +863,19 @@ def detach_relational_db_schema(self, database_schema_type_list_guid: str, relat @dynamic_catch async def _async_link_api_operations(self, api_schema_type_guid: str, api_operation_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach an API operation to the API schema type it belongs to (APIOperations relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"api-schema-types/{api_schema_type_guid}/api-operations/{api_operation_guid}/attach") - await self._async_new_relationship_request(url, ["APIOperationsProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["APIOperationsProperties"], body) logger.info(f"API operation {api_operation_guid} linked to API schema type {api_schema_type_guid}.") + return relationship_guid @dynamic_catch def link_api_operations(self, api_schema_type_guid: str, api_operation_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach an API operation to the API schema type it belongs to (APIOperations relationship).""" - run_sync(self._async_link_api_operations(api_schema_type_guid, api_operation_guid, body)) + return run_sync(self._async_link_api_operations(api_schema_type_guid, api_operation_guid, body)) @dynamic_catch async def _async_detach_api_operations(self, api_schema_type_guid: str, api_operation_guid: str, @@ -886,18 +897,19 @@ def detach_api_operations(self, api_schema_type_guid: str, api_operation_guid: s @dynamic_catch async def _async_link_api_header(self, api_operation_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a schema type as the header for an API operation (APIHeader relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"api-operations/{api_operation_guid}/api-headers/{schema_type_guid}/attach") - await self._async_new_relationship_request(url, ["APIHeaderProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["APIHeaderProperties"], body) logger.info(f"API header schema type {schema_type_guid} linked to API operation {api_operation_guid}.") + return relationship_guid @dynamic_catch def link_api_header(self, api_operation_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a schema type as the header for an API operation (APIHeader relationship).""" - run_sync(self._async_link_api_header(api_operation_guid, schema_type_guid, body)) + return run_sync(self._async_link_api_header(api_operation_guid, schema_type_guid, body)) @dynamic_catch async def _async_detach_api_header(self, api_operation_guid: str, schema_type_guid: str, @@ -919,18 +931,19 @@ def detach_api_header(self, api_operation_guid: str, schema_type_guid: str, @dynamic_catch async def _async_link_api_request(self, api_operation_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a schema type as the request for an API operation (APIRequest relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"api-operations/{api_operation_guid}/api-requests/{schema_type_guid}/attach") - await self._async_new_relationship_request(url, ["APIRequestProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["APIRequestProperties"], body) logger.info(f"API request schema type {schema_type_guid} linked to API operation {api_operation_guid}.") + return relationship_guid @dynamic_catch def link_api_request(self, api_operation_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a schema type as the request for an API operation (APIRequest relationship).""" - run_sync(self._async_link_api_request(api_operation_guid, schema_type_guid, body)) + return run_sync(self._async_link_api_request(api_operation_guid, schema_type_guid, body)) @dynamic_catch async def _async_detach_api_request(self, api_operation_guid: str, schema_type_guid: str, @@ -952,18 +965,19 @@ def detach_api_request(self, api_operation_guid: str, schema_type_guid: str, @dynamic_catch async def _async_link_api_response(self, api_operation_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a schema type as the response for an API operation (APIResponse relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"api-operations/{api_operation_guid}/api-responses/{schema_type_guid}/attach") - await self._async_new_relationship_request(url, ["APIResponseProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["APIResponseProperties"], body) logger.info(f"API response schema type {schema_type_guid} linked to API operation {api_operation_guid}.") + return relationship_guid @dynamic_catch def link_api_response(self, api_operation_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a schema type as the response for an API operation (APIResponse relationship).""" - run_sync(self._async_link_api_response(api_operation_guid, schema_type_guid, body)) + return run_sync(self._async_link_api_response(api_operation_guid, schema_type_guid, body)) @dynamic_catch async def _async_detach_api_response(self, api_operation_guid: str, schema_type_guid: str, @@ -985,18 +999,19 @@ def detach_api_response(self, api_operation_guid: str, schema_type_guid: str, @dynamic_catch async def _async_link_schema_type_option(self, schema_element_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach an optional schema type to a schema element (SchemaTypeOption relationship). Async version.""" url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/schema-maker/" f"schema-elements/{schema_element_guid}/schema-type-options/{schema_type_guid}/attach") - await self._async_new_relationship_request(url, ["SchemaTypeOptionProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["SchemaTypeOptionProperties"], body) logger.info(f"Schema type option {schema_type_guid} linked to schema element {schema_element_guid}.") + return relationship_guid @dynamic_catch def link_schema_type_option(self, schema_element_guid: str, schema_type_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach an optional schema type to a schema element (SchemaTypeOption relationship).""" - run_sync(self._async_link_schema_type_option(schema_element_guid, schema_type_guid, body)) + return run_sync(self._async_link_schema_type_option(schema_element_guid, schema_type_guid, body)) @dynamic_catch async def _async_detach_schema_type_option(self, schema_element_guid: str, schema_type_guid: str, diff --git a/pyegeria/omvs/security_officer.py b/pyegeria/omvs/security_officer.py index e9986837..38388e97 100644 --- a/pyegeria/omvs/security_officer.py +++ b/pyegeria/omvs/security_officer.py @@ -637,16 +637,16 @@ def find_security_groups( @dynamic_catch async def _async_link_resource_permissions(self, secrets_collection_guid: str, security_access_control_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a security access control to a secrets collection it grants permission for (ResourcePermissions relationship). Async version.""" url = f"{self.security_officer_base_url}/secrets-collections/{secrets_collection_guid}/resource-permissions/{security_access_control_guid}/attach" - await self._async_new_relationship_request(url, ["ResourcePermissionsProperties"], body) + return await self._async_new_relationship_request(url, ["ResourcePermissionsProperties"], body) @dynamic_catch def link_resource_permissions(self, secrets_collection_guid: str, security_access_control_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a security access control to a secrets collection it grants permission for (ResourcePermissions relationship).""" - run_sync(self._async_link_resource_permissions(secrets_collection_guid, security_access_control_guid, body)) + return run_sync(self._async_link_resource_permissions(secrets_collection_guid, security_access_control_guid, body)) @dynamic_catch async def _async_detach_resource_permissions(self, secrets_collection_guid: str, security_access_control_guid: str, @@ -663,16 +663,16 @@ def detach_resource_permissions(self, secrets_collection_guid: str, security_acc @dynamic_catch async def _async_link_secrets_collection_security_list(self, secrets_collection_guid: str, security_list_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a security list to a secrets collection it governs (SecretsCollectionSecurityList relationship). Async version.""" url = f"{self.security_officer_base_url}/secrets-collections/{secrets_collection_guid}/security-lists/{security_list_guid}/attach" - await self._async_new_relationship_request(url, ["SecretsCollectionSecurityListProperties"], body) + return await self._async_new_relationship_request(url, ["SecretsCollectionSecurityListProperties"], body) @dynamic_catch def link_secrets_collection_security_list(self, secrets_collection_guid: str, security_list_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a security list to a secrets collection it governs (SecretsCollectionSecurityList relationship).""" - run_sync(self._async_link_secrets_collection_security_list(secrets_collection_guid, security_list_guid, body)) + return run_sync(self._async_link_secrets_collection_security_list(secrets_collection_guid, security_list_guid, body)) @dynamic_catch async def _async_detach_secrets_collection_security_list(self, secrets_collection_guid: str, security_list_guid: str, @@ -689,16 +689,16 @@ def detach_secrets_collection_security_list(self, secrets_collection_guid: str, @dynamic_catch async def _async_link_user_account(self, secrets_collection_guid: str, user_identity_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a user identity to a secrets collection holding its account secrets (UserAccount relationship). Async version.""" url = f"{self.security_officer_base_url}/secrets-collections/{secrets_collection_guid}/user-accounts/{user_identity_guid}/attach" - await self._async_new_relationship_request(url, ["UserAccountProperties"], body) + return await self._async_new_relationship_request(url, ["UserAccountProperties"], body) @dynamic_catch def link_user_account(self, secrets_collection_guid: str, user_identity_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a user identity to a secrets collection holding its account secrets (UserAccount relationship).""" - run_sync(self._async_link_user_account(secrets_collection_guid, user_identity_guid, body)) + return run_sync(self._async_link_user_account(secrets_collection_guid, user_identity_guid, body)) @dynamic_catch async def _async_detach_user_account(self, secrets_collection_guid: str, user_identity_guid: str, @@ -1087,7 +1087,7 @@ def get_statement_of_applicability_by_guid( @dynamic_catch async def _async_link_control_applicability(self, soa_guid: str, control_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Link a control to a statement of applicability (ControlApplicability relationship). Async version. ControlApplicability is not multi-link: linking a control that is already linked updates the @@ -1104,11 +1104,11 @@ async def _async_link_control_applicability(self, soa_guid: str, control_guid: s """ url = (f"{self.security_officer_base_url}/statements-of-applicability/{soa_guid}" f"/control-applicability/{control_guid}/attach") - await self._async_new_relationship_request(url, ["ControlApplicabilityProperties"], body) + return await self._async_new_relationship_request(url, ["ControlApplicabilityProperties"], body) @dynamic_catch def link_control_applicability(self, soa_guid: str, control_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Link a control to a statement of applicability (ControlApplicability relationship). ControlApplicability is not multi-link: linking a control that is already linked updates the @@ -1123,7 +1123,7 @@ def link_control_applicability(self, soa_guid: str, control_guid: str, body : dict | NewRelationshipRequestBody, optional Request body whose properties class is ControlApplicabilityProperties. """ - run_sync(self._async_link_control_applicability(soa_guid, control_guid, body)) + return run_sync(self._async_link_control_applicability(soa_guid, control_guid, body)) @dynamic_catch async def _async_detach_control_applicability(self, soa_guid: str, control_guid: str, diff --git a/pyegeria/omvs/solution_architect.py b/pyegeria/omvs/solution_architect.py index 1c387db2..f642b6ee 100644 --- a/pyegeria/omvs/solution_architect.py +++ b/pyegeria/omvs/solution_architect.py @@ -1261,7 +1261,7 @@ def update_info_supply_chain(self, guid: str, body: dict | UpdateElementRequestB run_sync(self._async_update_info_supply_chain(guid, body)) @dynamic_catch - async def _async_link_peer_info_supply_chains(self, peer1_guid: str, peer2_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + async def _async_link_peer_info_supply_chains(self, peer1_guid: str, peer2_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Connect two peer information supply chains. The linked elements are of type 'Referenceable' to allow significant data stores to be included in the definition of the information supply chain. Request body is optional. Async Version. @@ -1277,7 +1277,8 @@ async def _async_link_peer_info_supply_chains(self, peer1_guid: str, peer2_guid: Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -1312,11 +1313,12 @@ async def _async_link_peer_info_supply_chains(self, peer1_guid: str, peer2_guid: url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/solution-architect/" f"information-supply-chains/{peer1_guid}/peer-links/{peer2_guid}/attach") - await self._async_new_relationship_request(url, ["InformationSupplyChainLinkProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["InformationSupplyChainLinkProperties"], body) logger.info(f"Linked Supply Chains {peer1_guid} -> {peer2_guid}") + return relationship_guid @dynamic_catch - def link_peer_info_supply_chains(self, peer1_guid: str, peer2_guid: str, body: dict | NewRelationshipRequestBody) -> None: + def link_peer_info_supply_chains(self, peer1_guid: str, peer2_guid: str, body: dict | NewRelationshipRequestBody) -> str | None: """ Connect two peer information supply chains. The linked elements are of type 'Referenceable' to allow significant data stores to be included in the definition of the information supply chain. Request body is optional. @@ -1332,7 +1334,8 @@ def link_peer_info_supply_chains(self, peer1_guid: str, peer2_guid: str, body: d Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -1363,7 +1366,7 @@ def link_peer_info_supply_chains(self, peer1_guid: str, peer2_guid: str, body: d } } """ - run_sync(self._async_link_peer_info_supply_chains(peer1_guid, peer2_guid, body)) + return run_sync(self._async_link_peer_info_supply_chains(peer1_guid, peer2_guid, body)) @dynamic_catch async def _async_unlink_peer_info_supply_chains(self, peer1_guid: str, peer2_guid: str, @@ -2753,7 +2756,7 @@ def update_solution_blueprint(self, guid: str, body: dict | UpdateElementRequest @dynamic_catch async def _async_link_solution_design(self, blueprint_guid: str, element_guid: str, - body: dict | NewRelationshipRequestBody) -> None: + body: dict | NewRelationshipRequestBody) -> str | None: """ Connect a solution blueprint to a referenceable element. Async Version. Parameters @@ -2767,7 +2770,8 @@ async def _async_link_solution_design(self, blueprint_guid: str, element_guid: s Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2803,11 +2807,12 @@ async def _async_link_solution_design(self, blueprint_guid: str, element_guid: s url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/solution-architect/" f"elements/{element_guid}/solution-designs/{blueprint_guid}/attach") - await self._async_new_relationship_request(url, ["SolutionDesignProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["SolutionDesignProperties"], body) logger.info(f"Linked referenceable element to blueprint {element_guid} -> {blueprint_guid}") + return relationship_guid @dynamic_catch - def link_solution_design(self, blueprint_guid: str, element_guid: str, body: dict | NewRelationshipRequestBody) -> None: + def link_solution_design(self, blueprint_guid: str, element_guid: str, body: dict | NewRelationshipRequestBody) -> str | None: """ Connect a solution blueprint to a referenceable element. Parameters @@ -2821,7 +2826,8 @@ def link_solution_design(self, blueprint_guid: str, element_guid: str, body: dic Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -2852,7 +2858,7 @@ def link_solution_design(self, blueprint_guid: str, element_guid: str, body: dic "forDuplicateProcessing" : false } """ - run_sync(self._async_link_solution_design(blueprint_guid, element_guid, body)) + return run_sync(self._async_link_solution_design(blueprint_guid, element_guid, body)) @dynamic_catch async def _async_detach_solution_design(self, blueprint_guid: str, element_guid: str, @@ -4013,7 +4019,7 @@ def update_solution_component(self, guid: str, body: dict | UpdateElementRequest run_sync(self._async_update_solution_component(guid, body)) @dynamic_catch - async def _async_link_subcomponent(self, component_guid: str, sub_component_guid: str, body: dict | NewRelationshipRequestBody) -> None: + async def _async_link_subcomponent(self, component_guid: str, sub_component_guid: str, body: dict | NewRelationshipRequestBody) -> str | None: """ Attach a solution component to a solution component. Async Version. Parameters @@ -4027,7 +4033,8 @@ async def _async_link_subcomponent(self, component_guid: str, sub_component_guid Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -4055,11 +4062,12 @@ async def _async_link_subcomponent(self, component_guid: str, sub_component_guid url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/solution-architect/" f"solution-components/{component_guid}/subcomponents/{sub_component_guid}/attach") - await self._async_new_relationship_request(url, ["SolutionCompositionProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["SolutionCompositionProperties"], body) logger.info(f"Linked Subcomponent {component_guid} -> {sub_component_guid}") + return relationship_guid @dynamic_catch - def link_subcomponent(self, component_guid: str, sub_component_guid: str, body: dict | NewRelationshipRequestBody) -> None: + def link_subcomponent(self, component_guid: str, sub_component_guid: str, body: dict | NewRelationshipRequestBody) -> str | None: """ Attach a solution component to a solution component. Parameters @@ -4073,7 +4081,8 @@ def link_subcomponent(self, component_guid: str, sub_component_guid: str, body: Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -4097,7 +4106,7 @@ def link_subcomponent(self, component_guid: str, sub_component_guid: str, body: "forDuplicateProcessing" : false } """ - run_sync(self._async_link_subcomponent(component_guid, sub_component_guid, body)) + return run_sync(self._async_link_subcomponent(component_guid, sub_component_guid, body)) @dynamic_catch async def _async_detach_sub_component(self, parent_component_guid: str, member_component_guid: str, body: dict |DeleteRelationshipRequestBody = None) -> None: @@ -5723,7 +5732,7 @@ def update_solution_role(self, guid: str, body: dict | UpdateElementRequestBody) run_sync(self._async_update_solution_role(guid, body)) @dynamic_catch - async def _async_link_component_to_actor(self, role_guid: str, component_guid: str, body: dict | NewRelationshipRequestBody) -> None: + async def _async_link_component_to_actor(self, role_guid: str, component_guid: str, body: dict | NewRelationshipRequestBody) -> str | None: """ Attach a solution component to a solution role. Async Version. Parameters @@ -5737,7 +5746,8 @@ async def _async_link_component_to_actor(self, role_guid: str, component_guid: s Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -5772,11 +5782,12 @@ async def _async_link_component_to_actor(self, role_guid: str, component_guid: s url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/solution-architect/" f"solution-roles/{role_guid}/solution-component-actors/{component_guid}/attach") - await self._async_new_relationship_request(url, ["SolutionComponentActorProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["SolutionComponentActorProperties"], body) logger.info(f"Linked Role to Component {role_guid} -> {component_guid}") + return relationship_guid @dynamic_catch - def link_component_to_actor(self, role_guid: str, component_guid: str, body: dict | NewRelationshipRequestBody) -> None: + def link_component_to_actor(self, role_guid: str, component_guid: str, body: dict | NewRelationshipRequestBody) -> str | None: """ Attach a solution component to a solution role. Parameters @@ -5790,7 +5801,8 @@ def link_component_to_actor(self, role_guid: str, component_guid: str, body: dic Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -5822,7 +5834,7 @@ def link_component_to_actor(self, role_guid: str, component_guid: str, body: dic } """ - run_sync(self._async_link_component_to_actor(role_guid, component_guid, body)) + return run_sync(self._async_link_component_to_actor(role_guid, component_guid, body)) @dynamic_catch async def _async_detach_component_actor(self, role_guid: str, component_guid: str, body: Optional[dict | DeleteRelationshipRequestBody] = None) -> None: @@ -6693,16 +6705,16 @@ def get_concept_model_element_by_guid(self, guid: str, body: Optional[dict | Get @dynamic_catch async def _async_link_concept_design(self, element_guid: str, concept_model_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach an element to the concept model element that designs it (ConceptDesign relationship). Async version.""" url = f"{self.solution_architect_command_root}/elements/{element_guid}/concept-designs/{concept_model_guid}/attach" - await self._async_new_relationship_request(url, ["ConceptDesignProperties"], body) + return await self._async_new_relationship_request(url, ["ConceptDesignProperties"], body) @dynamic_catch def link_concept_design(self, element_guid: str, concept_model_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach an element to the concept model element that designs it (ConceptDesign relationship).""" - run_sync(self._async_link_concept_design(element_guid, concept_model_guid, body)) + return run_sync(self._async_link_concept_design(element_guid, concept_model_guid, body)) @dynamic_catch async def _async_detach_concept_design(self, element_guid: str, concept_model_guid: str, @@ -6719,16 +6731,16 @@ def detach_concept_design(self, element_guid: str, concept_model_guid: str, @dynamic_catch async def _async_link_concept_bead_relationship_end(self, concept_bead_relationship_guid: str, concept_bead_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a concept bead as one end of a concept bead relationship (ConceptBeadRelationshipEnd relationship). Async version.""" url = f"{self.solution_architect_command_root}/concept-bead-relationships/{concept_bead_relationship_guid}/relationship-ends/{concept_bead_guid}/attach" - await self._async_new_relationship_request(url, ["ConceptBeadRelationshipEndProperties"], body) + return await self._async_new_relationship_request(url, ["ConceptBeadRelationshipEndProperties"], body) @dynamic_catch def link_concept_bead_relationship_end(self, concept_bead_relationship_guid: str, concept_bead_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a concept bead as one end of a concept bead relationship (ConceptBeadRelationshipEnd relationship).""" - run_sync(self._async_link_concept_bead_relationship_end(concept_bead_relationship_guid, concept_bead_guid, body)) + return run_sync(self._async_link_concept_bead_relationship_end(concept_bead_relationship_guid, concept_bead_guid, body)) @dynamic_catch async def _async_detach_concept_bead_relationship_end(self, concept_bead_relationship_guid: str, concept_bead_guid: str, @@ -6745,16 +6757,16 @@ def detach_concept_bead_relationship_end(self, concept_bead_relationship_guid: s @dynamic_catch async def _async_link_typed_by_concept_bead(self, concept_bead_attribute_guid: str, concept_bead_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a concept bead attribute to the concept bead that types it (TypedByConceptBead relationship). Async version.""" url = f"{self.solution_architect_command_root}/concept-bead-attributes/{concept_bead_attribute_guid}/typed-by/{concept_bead_guid}/attach" - await self._async_new_relationship_request(url, ["TypedByConceptBeadProperties"], body) + return await self._async_new_relationship_request(url, ["TypedByConceptBeadProperties"], body) @dynamic_catch def link_typed_by_concept_bead(self, concept_bead_attribute_guid: str, concept_bead_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a concept bead attribute to the concept bead that types it (TypedByConceptBead relationship).""" - run_sync(self._async_link_typed_by_concept_bead(concept_bead_attribute_guid, concept_bead_guid, body)) + return run_sync(self._async_link_typed_by_concept_bead(concept_bead_attribute_guid, concept_bead_guid, body)) @dynamic_catch async def _async_detach_typed_by_concept_bead(self, concept_bead_attribute_guid: str, concept_bead_guid: str, @@ -6771,16 +6783,16 @@ def detach_typed_by_concept_bead(self, concept_bead_attribute_guid: str, concept @dynamic_catch async def _async_link_is_a_concept_bead(self, concept_bead_guid: str, general_concept_bead_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a concept bead to the more general concept bead it is a kind of (IsAConceptBead relationship). Async version.""" url = f"{self.solution_architect_command_root}/concept-beads/{concept_bead_guid}/is-a/{general_concept_bead_guid}/attach" - await self._async_new_relationship_request(url, ["IsAConceptBeadProperties"], body) + return await self._async_new_relationship_request(url, ["IsAConceptBeadProperties"], body) @dynamic_catch def link_is_a_concept_bead(self, concept_bead_guid: str, general_concept_bead_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a concept bead to the more general concept bead it is a kind of (IsAConceptBead relationship).""" - run_sync(self._async_link_is_a_concept_bead(concept_bead_guid, general_concept_bead_guid, body)) + return run_sync(self._async_link_is_a_concept_bead(concept_bead_guid, general_concept_bead_guid, body)) @dynamic_catch async def _async_detach_is_a_concept_bead(self, concept_bead_guid: str, general_concept_bead_guid: str, @@ -6797,16 +6809,16 @@ def detach_is_a_concept_bead(self, concept_bead_guid: str, general_concept_bead_ @dynamic_catch async def _async_link_concept_bead_attribute_link(self, concept_bead_guid: str, concept_bead_attribute_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach an attribute to the concept bead it belongs to (ConceptBeadAttributeLink relationship). Async version.""" url = f"{self.solution_architect_command_root}/concept-beads/{concept_bead_guid}/attribute-links/{concept_bead_attribute_guid}/attach" - await self._async_new_relationship_request(url, ["ConceptBeadAttributeLinkProperties"], body) + return await self._async_new_relationship_request(url, ["ConceptBeadAttributeLinkProperties"], body) @dynamic_catch def link_concept_bead_attribute_link(self, concept_bead_guid: str, concept_bead_attribute_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach an attribute to the concept bead it belongs to (ConceptBeadAttributeLink relationship).""" - run_sync(self._async_link_concept_bead_attribute_link(concept_bead_guid, concept_bead_attribute_guid, body)) + return run_sync(self._async_link_concept_bead_attribute_link(concept_bead_guid, concept_bead_attribute_guid, body)) @dynamic_catch async def _async_detach_concept_bead_attribute_link(self, concept_bead_guid: str, concept_bead_attribute_guid: str, @@ -6823,16 +6835,16 @@ def detach_concept_bead_attribute_link(self, concept_bead_guid: str, concept_bea @dynamic_catch async def _async_link_concept_bead_extension(self, concept_bead_guid: str, extension_concept_bead_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a concept bead to another concept bead that extends it (ConceptBeadExtension relationship). Async version.""" url = f"{self.solution_architect_command_root}/concept-beads/{concept_bead_guid}/extensions/{extension_concept_bead_guid}/attach" - await self._async_new_relationship_request(url, ["ConceptBeadExtensionProperties"], body) + return await self._async_new_relationship_request(url, ["ConceptBeadExtensionProperties"], body) @dynamic_catch def link_concept_bead_extension(self, concept_bead_guid: str, extension_concept_bead_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a concept bead to another concept bead that extends it (ConceptBeadExtension relationship).""" - run_sync(self._async_link_concept_bead_extension(concept_bead_guid, extension_concept_bead_guid, body)) + return run_sync(self._async_link_concept_bead_extension(concept_bead_guid, extension_concept_bead_guid, body)) @dynamic_catch async def _async_detach_concept_bead_extension(self, concept_bead_guid: str, extension_concept_bead_guid: str, @@ -6982,16 +6994,16 @@ def get_solution_port_by_guid(self, solution_port_guid: str, body: Optional[dict @dynamic_catch async def _async_link_solution_component_port(self, solution_component_guid: str, solution_port_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a solution port to the solution component it belongs to (SolutionComponentPort relationship). Async version.""" url = f"{self.solution_architect_command_root}/solution-components/{solution_component_guid}/solution-ports/{solution_port_guid}/attach" - await self._async_new_relationship_request(url, ["SolutionComponentPortProperties"], body) + return await self._async_new_relationship_request(url, ["SolutionComponentPortProperties"], body) @dynamic_catch def link_solution_component_port(self, solution_component_guid: str, solution_port_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a solution port to the solution component it belongs to (SolutionComponentPort relationship).""" - run_sync(self._async_link_solution_component_port(solution_component_guid, solution_port_guid, body)) + return run_sync(self._async_link_solution_component_port(solution_component_guid, solution_port_guid, body)) @dynamic_catch async def _async_detach_solution_component_port(self, solution_component_guid: str, solution_port_guid: str, @@ -7008,16 +7020,16 @@ def detach_solution_component_port(self, solution_component_guid: str, solution_ @dynamic_catch async def _async_link_solution_port_delegation(self, aligns_to_port_guid: str, delegation_port_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a solution port to the port it delegates to (PortDelegation relationship). Async version.""" url = f"{self.solution_architect_command_root}/solution-ports/{aligns_to_port_guid}/port-delegations/{delegation_port_guid}/attach" - await self._async_new_relationship_request(url, ["SolutionPortDelegationProperties"], body) + return await self._async_new_relationship_request(url, ["SolutionPortDelegationProperties"], body) @dynamic_catch def link_solution_port_delegation(self, aligns_to_port_guid: str, delegation_port_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a solution port to the port it delegates to (PortDelegation relationship).""" - run_sync(self._async_link_solution_port_delegation(aligns_to_port_guid, delegation_port_guid, body)) + return run_sync(self._async_link_solution_port_delegation(aligns_to_port_guid, delegation_port_guid, body)) @dynamic_catch async def _async_detach_solution_port_delegation(self, aligns_to_port_guid: str, delegation_port_guid: str, diff --git a/pyegeria/omvs/subject_area.py b/pyegeria/omvs/subject_area.py index 8ccf6bc7..fef4bca5 100644 --- a/pyegeria/omvs/subject_area.py +++ b/pyegeria/omvs/subject_area.py @@ -407,7 +407,7 @@ async def _async_link_subject_area_hierarchy( parent_subject_area_guid: str, nested_subject_area_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link subject areas in a hierarchy. Async version. Parameters @@ -421,7 +421,8 @@ async def _async_link_subject_area_hierarchy( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -441,14 +442,14 @@ async def _async_link_subject_area_hierarchy( ``` """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/subject-area/subject-areas/{parent_subject_area_guid}/subject-area-hierarchies/{nested_subject_area_guid}/attach" - await self._async_new_relationship_request(url, ["SubjectAreaHierarchyProperties"], body) + return await self._async_new_relationship_request(url, ["SubjectAreaHierarchyProperties"], body) def link_subject_area_hierarchy( self, parent_subject_area_guid: str, nested_subject_area_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link subject areas in a hierarchy. Parameters @@ -462,7 +463,8 @@ def link_subject_area_hierarchy( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -481,7 +483,7 @@ def link_subject_area_hierarchy( } ``` """ - run_sync( + return run_sync( self._async_link_subject_area_hierarchy( parent_subject_area_guid, nested_subject_area_guid, body ) @@ -1039,16 +1041,16 @@ def get_subject_area_by_guid( @dynamic_catch async def _async_link_subject_areas(self, parent_subject_area_guid: str, nested_subject_area_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a nested subject area to its parent subject area (SubjectAreaHierarchy relationship). Async version.""" url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/subject-area/subject-areas/{parent_subject_area_guid}/subject-area-hierarchies/{nested_subject_area_guid}/attach" - await self._async_new_relationship_request(url, ["SubjectAreaHierarchyProperties"], body) + return await self._async_new_relationship_request(url, ["SubjectAreaHierarchyProperties"], body) @dynamic_catch def link_subject_areas(self, parent_subject_area_guid: str, nested_subject_area_guid: str, - body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a nested subject area to its parent subject area (SubjectAreaHierarchy relationship).""" - run_sync(self._async_link_subject_areas(parent_subject_area_guid, nested_subject_area_guid, body)) + return run_sync(self._async_link_subject_areas(parent_subject_area_guid, nested_subject_area_guid, body)) @dynamic_catch async def _async_detach_subject_areas(self, parent_subject_area_guid: str, nested_subject_area_guid: str, diff --git a/pyegeria/omvs/template_manager_omvs.py b/pyegeria/omvs/template_manager_omvs.py index f435e3d7..1da6eed3 100644 --- a/pyegeria/omvs/template_manager_omvs.py +++ b/pyegeria/omvs/template_manager_omvs.py @@ -53,7 +53,7 @@ def __init__( self.command_root = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/template-manager" @dynamic_catch - async def _async_link_sourced_from(self, element_guid: str, template_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + async def _async_link_sourced_from(self, element_guid: str, template_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a template to an element that was created from it. Async version. @@ -83,10 +83,10 @@ async def _async_link_sourced_from(self, element_guid: str, template_guid: str, } """ url = f"{self.command_root}/elements/{element_guid}/sourced-from/{template_guid}/attach" - await self._async_new_relationship_request(url, body=body) + return await self._async_new_relationship_request(url, body=body) @dynamic_catch - def link_sourced_from(self, element_guid: str, template_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + def link_sourced_from(self, element_guid: str, template_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a template to an element that was created from it.""" return run_sync(self._async_link_sourced_from(element_guid, template_guid, body)) @@ -113,7 +113,7 @@ def detach_sourced_from(self, element_guid: str, template_guid: str, body: Optio return run_sync(self._async_detach_sourced_from(element_guid, template_guid, body)) @dynamic_catch - async def _async_link_catalog_template(self, element_guid: str, template_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + async def _async_link_catalog_template(self, element_guid: str, template_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a template to an element that was created from it. Async version. @@ -127,10 +127,10 @@ async def _async_link_catalog_template(self, element_guid: str, template_guid: s Relationship details. """ url = f"{self.command_root}/elements/{element_guid}/catalog-template/{template_guid}/attach" - await self._async_new_relationship_request(url, body=body) + return await self._async_new_relationship_request(url, body=body) @dynamic_catch - def link_catalog_template(self, element_guid: str, template_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + def link_catalog_template(self, element_guid: str, template_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """Attach a template to an element that was created from it.""" return run_sync(self._async_link_catalog_template(element_guid, template_guid, body)) diff --git a/pyegeria/omvs/time_keeper.py b/pyegeria/omvs/time_keeper.py index 936a7da7..7e5c12f2 100644 --- a/pyegeria/omvs/time_keeper.py +++ b/pyegeria/omvs/time_keeper.py @@ -449,7 +449,7 @@ async def _async_link_dependent_context_events( parent_context_event_guid: str, child_context_event_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link dependent context events. Async version. Parameters @@ -463,7 +463,8 @@ async def _async_link_dependent_context_events( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -485,14 +486,14 @@ async def _async_link_dependent_context_events( ``` """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/time-keeper/context-events/{parent_context_event_guid}/dependent-context-events/{child_context_event_guid}/attach" - await self._async_new_relationship_request(url, ["DependentContextEventProperties"], body) + return await self._async_new_relationship_request(url, ["DependentContextEventProperties"], body) def link_dependent_context_events( self, parent_context_event_guid: str, child_context_event_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link dependent context events. Parameters @@ -506,7 +507,8 @@ def link_dependent_context_events( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -527,7 +529,7 @@ def link_dependent_context_events( } ``` """ - run_sync( + return run_sync( self._async_link_dependent_context_events( parent_context_event_guid, child_context_event_guid, body ) @@ -623,7 +625,7 @@ async def _async_link_related_context_events( context_event_one_guid: str, context_event_two_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link related context events. Async version. Parameters @@ -637,7 +639,8 @@ async def _async_link_related_context_events( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -659,14 +662,14 @@ async def _async_link_related_context_events( ``` """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/time-keeper/context-events/{context_event_one_guid}/related-context-events/{context_event_two_guid}/attach" - await self._async_new_relationship_request(url, ['ContextEventEvidence'],body) + return await self._async_new_relationship_request(url, ['ContextEventEvidence'],body) def link_related_context_events( self, context_event_one_guid: str, context_event_two_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link related context events. Parameters @@ -680,7 +683,8 @@ def link_related_context_events( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -701,7 +705,7 @@ def link_related_context_events( } ``` """ - run_sync( + return run_sync( self._async_link_related_context_events(context_event_one_guid, context_event_two_guid, body) ) @@ -790,7 +794,7 @@ def detach_related_context_events( @dynamic_catch async def _async_link_context_event_evidence( self, context_event_guid: str, evidence_guid: str, body: dict | NewRelationshipRequestBody - ) -> None: + ) -> str | None: """Link context event evidence. Async version. Parameters @@ -804,7 +808,8 @@ async def _async_link_context_event_evidence( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -826,11 +831,11 @@ async def _async_link_context_event_evidence( ``` """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/time-keeper/context-events/{context_event_guid}/evidence/{evidence_guid}/attach" - await self._async_new_relationship_request(url, ["ContextEventEvidenceProperties"], body) + return await self._async_new_relationship_request(url, ["ContextEventEvidenceProperties"], body) def link_context_event_evidence( self, context_event_guid: str, evidence_guid: str, body: dict | NewRelationshipRequestBody - ) -> None: + ) -> str | None: """Link context event evidence. Parameters @@ -844,7 +849,8 @@ def link_context_event_evidence( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -865,7 +871,7 @@ def link_context_event_evidence( } ``` """ - run_sync( + return run_sync( self._async_link_context_event_evidence(context_event_guid, evidence_guid, body) ) @@ -948,7 +954,7 @@ def detach_context_event_evidence( @dynamic_catch async def _async_link_context_event_impact( self, context_event_guid: str, impacted_element_guid: str, body: dict | NewRelationshipRequestBody - ) -> None: + ) -> str | None: """Link context event impact. Async version. Parameters @@ -962,7 +968,8 @@ async def _async_link_context_event_impact( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -984,11 +991,11 @@ async def _async_link_context_event_impact( ``` """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/time-keeper/context-events/{context_event_guid}/impacts/{impacted_element_guid}/attach" - await self._async_new_relationship_request(url, ['ContextEventImpact'],body) + return await self._async_new_relationship_request(url, ['ContextEventImpact'],body) def link_context_event_impact( self, context_event_guid: str, impacted_element_guid: str, body: dict | NewRelationshipRequestBody - ) -> None: + ) -> str | None: """Link context event impact. Parameters @@ -1002,7 +1009,8 @@ def link_context_event_impact( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -1023,7 +1031,7 @@ def link_context_event_impact( } ``` """ - run_sync( + return run_sync( self._async_link_context_event_impact(context_event_guid, impacted_element_guid, body) ) @@ -1109,7 +1117,7 @@ async def _async_link_context_event_timeline_effect( timeline_affected_element_guid: str, context_event_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link context event timeline effect. Async version. Parameters @@ -1123,7 +1131,8 @@ async def _async_link_context_event_timeline_effect( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -1145,14 +1154,14 @@ async def _async_link_context_event_timeline_effect( ``` """ url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/time-keeper/elements/{timeline_affected_element_guid}/context-events-describing-timeline-effects/{context_event_guid}/attach" - await self._async_new_relationship_request(url, ["ContextEventForTimelineEffectsProperties"], body) + return await self._async_new_relationship_request(url, ["ContextEventForTimelineEffectsProperties"], body) def link_context_event_timeline_effect( self, timeline_affected_element_guid: str, context_event_guid: str, body: dict | NewRelationshipRequestBody, - ) -> None: + ) -> str | None: """Link context event timeline effect. Parameters @@ -1166,7 +1175,8 @@ def link_context_event_timeline_effect( Returns ------- - None + str | None + The GUID of the new relationship, or None if the server did not return one. Raises ------ @@ -1187,7 +1197,7 @@ def link_context_event_timeline_effect( } ``` """ - run_sync( + return run_sync( self._async_link_context_event_timeline_effect( timeline_affected_element_guid, context_event_guid, body ) diff --git a/pyegeria/omvs/valid_metadata.py b/pyegeria/omvs/valid_metadata.py index 5ae7ae6f..c434975c 100644 --- a/pyegeria/omvs/valid_metadata.py +++ b/pyegeria/omvs/valid_metadata.py @@ -2717,7 +2717,7 @@ def delete_specification_property(self, spec_property_guid: str, body: dict | De @dynamic_catch async def _async_link_specification_property( - self, element_guid: str, vv_def_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + self, element_guid: str, vv_def_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Link an element to a valid value that represents one of its specification properties. Async version. Parameters @@ -2757,12 +2757,13 @@ async def _async_link_specification_property( """ url = (f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/valid-metadata/" f"elements/{element_guid}/specification-properties/{vv_def_guid}/attach") - await self._async_new_relationship_request(url, ["SpecificationPropertyAssignmentProperties"], body) + relationship_guid = await self._async_new_relationship_request(url, ["SpecificationPropertyAssignmentProperties"], body) logger.info(f"Linked specification property {vv_def_guid} to element {element_guid}") + return relationship_guid @dynamic_catch def link_specification_property( - self, element_guid: str, vv_def_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> None: + self, element_guid: str, vv_def_guid: str, body: Optional[dict | NewRelationshipRequestBody] = None) -> str | None: """ Link an element to a valid value that represents one of its specification properties. Parameters @@ -2800,7 +2801,7 @@ def link_specification_property( } """ - run_sync(self._async_link_specification_property(element_guid, vv_def_guid, body)) + return run_sync(self._async_link_specification_property(element_guid, vv_def_guid, body)) @dynamic_catch async def _async_detach_specification_property( diff --git a/tests/micro-tests/test_issue_174_link_returns_guid.py b/tests/micro-tests/test_issue_174_link_returns_guid.py new file mode 100644 index 00000000..d0e85ea3 --- /dev/null +++ b/tests/micro-tests/test_issue_174_link_returns_guid.py @@ -0,0 +1,162 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright Contributors to the ODPi Egeria project. +""" +ISSUE-174: link_*/attach_*/setup_* methods must return the new relationship's +GUID. + +``ServerClient._async_new_relationship_request`` returns the GUID from the +server's ``GUIDResponse``, but 142 of its callers in ``pyegeria/omvs`` used to +call it as a bare ``await`` and return None, and so did their sync wrappers. +Resource Explorer had to re-read each schema after +``AssetMaker.link_data_set_content`` to learn the DataSetContent relationship's +GUID. That matters most for multi-link types, where update and detach need +the GUID of the specific instance. + +Two checks: + - an AST guard: no method in ``pyegeria/omvs`` drops the result of the + helper, or of any method that (transitively) returns it, and no sync + wrapper drops the result of such an async method; + - a sample of real methods, async and sync, across several clients, return + the GUID the stubbed server sends back. + +No live server needed: ``_async_make_request`` is stubbed. +""" +import ast +import pathlib + +import pytest + +from pyegeria.omvs.asset_maker import AssetMaker +from pyegeria.omvs.classification_explorer import ClassificationExplorer +from pyegeria.omvs.connection_maker import ConnectionMaker +from pyegeria.omvs.data_designer import DataDesigner +from pyegeria.omvs.governance_officer import GovernanceOfficer +from pyegeria.omvs.schema_maker import SchemaMaker +from pyegeria.omvs.solution_architect import SolutionArchitect + +HELPER = "_async_new_relationship_request" +OMVS_DIR = pathlib.Path(__file__).resolve().parents[2] / "pyegeria" / "omvs" +REL_GUID = "new-relationship-guid-0174" + + +# ---------------------------------------------------------------- AST guard + +def _self_call_name(node): + if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute) \ + and isinstance(node.func.value, ast.Name) and node.func.value.id == "self": + return node.func.attr + return None + + +def _returning_methods(funcs): + """Names of async methods that return the helper's result, transitively.""" + returning = {HELPER} + changed = True + while changed: + changed = False + for fn in funcs: + if not isinstance(fn, ast.AsyncFunctionDef) or fn.name in returning: + continue + for node in ast.walk(fn): + if isinstance(node, ast.Return) and isinstance(node.value, ast.Await) \ + and _self_call_name(node.value.value) in returning: + returning.add(fn.name) + changed = True + break + if isinstance(node, ast.Assign) and isinstance(node.value, ast.Await) \ + and _self_call_name(node.value.value) in returning: + # e.g. relationship_guid = await self._async_new_relationship_request(...) + returning.add(fn.name) + changed = True + break + return returning + + +def _discards(path): + tree = ast.parse(path.read_text()) + funcs = [n for n in ast.walk(tree) if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))] + returning = _returning_methods(funcs) + found = [] + for fn in funcs: + for node in ast.walk(fn): + if not isinstance(node, ast.Expr): + continue + value = node.value + if isinstance(value, ast.Await) and _self_call_name(value.value) in returning: + found.append(f"{path.name}:{node.lineno} {fn.name} awaits " + f"{_self_call_name(value.value)} and drops the GUID") + elif isinstance(value, ast.Call) and not isinstance(fn, ast.AsyncFunctionDef): + for arg in value.args: + if _self_call_name(arg) in returning: + found.append(f"{path.name}:{node.lineno} {fn.name} runs " + f"{_self_call_name(arg)} and drops the GUID") + return found + + +def test_no_omvs_method_discards_the_relationship_guid(): + found = [] + for path in sorted(OMVS_DIR.glob("*.py")): + found.extend(_discards(path)) + assert not found, "Methods that drop the new relationship GUID:\n" + "\n".join(found) + + +def test_guard_catches_a_discarding_method(tmp_path): + bad = tmp_path / "bad.py" + bad.write_text( + "class C:\n" + " async def _async_link_x(self, url):\n" + f" await self.{HELPER}(url)\n" + " async def _async_link_y(self, url):\n" + f" return await self.{HELPER}(url)\n" + " def link_y(self, url):\n" + " run_sync(self._async_link_y(url))\n" + ) + found = _discards(bad) + assert len(found) == 2 + assert "_async_link_x" in found[0] and "link_y" in found[1] + + +# ---------------------------------------------------------- live-shaped sample + +class _Response: + def json(self): + return {"class": "GUIDResponse", "relatedHTTPCode": 200, "guid": REL_GUID} + + +def _client(cls, monkeypatch): + monkeypatch.setattr(cls, "check_connection", lambda self: "test") + client = cls("view-server", "https://localhost:1", "user", "pwd") + calls = [] + + async def fake_make_request(method, url, body=None, *args, **kwargs): + calls.append((method, url)) + return _Response() + + client._async_make_request = fake_make_request + client._calls = calls + return client + + +SYNC_CASES = [ + (AssetMaker, "link_data_set_content", ("data-set-guid", "content-guid")), + (SchemaMaker, "link_nested_schema_attribute", ("parent-guid", "child-guid")), + (SchemaMaker, "link_attribute_for_schema", ("schema-type-guid", "attribute-guid")), + (DataDesigner, "link_member_data_field", ("struct-guid", "field-guid")), + (GovernanceOfficer, "link_peer_definitions", ("def-guid-1", "GovernanceDriverLink", "def-guid-2")), + (ClassificationExplorer, "add_scope_to_element", ("scope-guid", "element-guid")), + (SolutionArchitect, "link_subcomponent", ("component-guid", "sub-guid", None)), + (ConnectionMaker, "link_connection_endpoint", ("connection-guid", "endpoint-guid")), +] + + +@pytest.mark.parametrize("cls,method,args", SYNC_CASES, ids=[c[1] for c in SYNC_CASES]) +def test_sync_link_methods_return_the_relationship_guid(cls, method, args, monkeypatch): + client = _client(cls, monkeypatch) + assert getattr(client, method)(*args) == REL_GUID + assert client._calls and client._calls[0][0] == "POST" + + +@pytest.mark.parametrize("cls,method,args", SYNC_CASES, ids=[c[1] for c in SYNC_CASES]) +async def test_async_link_methods_return_the_relationship_guid(cls, method, args, monkeypatch): + client = _client(cls, monkeypatch) + assert await getattr(client, f"_async_{method}")(*args) == REL_GUID From 787a819a8062cf46ba8bb50725c8a5a21f6c5bc4 Mon Sep 17 00:00:00 2001 From: Dan Wolfson Date: Sat, 31 Oct 2026 16:54:05 -0500 Subject: [PATCH 3/7] feat(view)!: remove the report-spec sources and dotted paths deprecated in 6.2.18 (ISSUE-173) - A report spec's analytic_function, or metric_path, must be a registered name. Anything else raises AnalyticFunctionRefused before any import, naming the registered name to use when there is one. - refresh_report_specs() no longer reads config.json's "Pyegeria Report Spec Modules", PYEGERIA_REPORT_SPEC_MODULES, PYEGERIA_REPORT_FORMATS_JSON or PYEGERIA_REPORT_FORMATS_MODULES. If one is still set, it logs one WARNING naming it, so a stale setting doesn't quietly do nothing. - The trellis.report_specs and trellis.analytics entry-point alias groups are no longer read. - register_report_specs() is the way for code that isn't an installed package to add specs. Registering a label again from the same source is now a no-op, so a reloaded handler can call it safely. A label held by another source still raises. Tests that used the removed sources or dotted paths as fixtures now use the user specs directory and registered names. Docs and CLAUDE.md are updated to match. Claude-Session: https://claude.ai/code/session_015pdMuPiVFuZhoGLsBSFaN3 Signed-off-by: Dan Wolfson --- CLAUDE.md | 6 +- docs/output-formats-and-report-specs.md | 34 ++--- pyegeria/core/config.py | 11 +- pyegeria/view/analytic_demo_specs.py | 6 +- pyegeria/view/analytic_registry.py | 7 +- pyegeria/view/base_report_formats.py | 123 ++++++++---------- pyegeria/view/format_set_executor.py | 31 ++--- .../test_exec_report_spec_token.py | 4 +- .../test_format_set_executor_token.py | 4 +- ...est_issue_138_140_149_analytic_envelope.py | 12 +- .../test_issue_171_state_aware_report_spec.py | 10 +- .../test_issue_172_analytic_entry_points.py | 20 +-- .../test_issue_173_registered_names.py | 51 +++----- ...test_issue_173_report_spec_entry_points.py | 76 +++++++++-- tests/micro-tests/test_overview_metrics.py | 14 +- .../test_report_registry_config_tier.py | 47 +++---- 16 files changed, 236 insertions(+), 220 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 51c60312..d898b64e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -268,12 +268,12 @@ Changes to `pyegeria/` (SDK API, config, report formatting) can break both. - `create_egeria_bearer_token()` / `set_bearer_token()` propagate tokens to every subclient. **`pyegeria/view/`** — output formatting and report spec registry -- `base_report_formats.py`: two separate dicts merged by `get_report_registry()` — `generated_format_sets` (auto-generated by `refresh_specs`; do not hand-edit) and `base_report_specs` (hand-maintained BUILTINS, e.g. `Referenceable`; safe to edit directly, `refresh_specs` never touches it). `get_report_registry()` also auto-loads a CONFIG tier on first call: every `pyegeria.report_specs` entry point (alias `trellis.report_specs`; a callable returning a FormatSetDict, or the dict itself — pyegeria's own analytic demo specs load this way, so after changing entry points run `uv sync`), then every `*.json` in the user report specs directory (`PYEGERIA_USER_REPORT_SPECS_DIR` > config.json `"Pyegeria User Report Specs Dir"` > `~/.pyegeria/report_specs`). config.json's `"Pyegeria Report Spec Modules"`, `PYEGERIA_REPORT_SPEC_MODULES` and `PYEGERIA_REPORT_FORMATS_JSON`/`_MODULES` still load but are deprecated: one DeprecationWarning per refresh in 6.2.18, removed in 6.2.19 (ISSUE-173). Each entry loads independently: a failing or label-colliding entry is skipped with a warning (and returned by `refresh_report_specs()`), never blocking the others. **Gotcha:** `config/config.json` is only auto-discovered if `PYEGERIA_CONFIG_DIRECTORY` (or `PYEGERIA_ROOT_PATH`) is set — a bare shell with neither falls back to defaults silently, so a probe script that just imports `settings` may not see config.json values at all; use the `PYEGERIA_USER_REPORT_SPECS_DIR` env var directly when in doubt. +- `base_report_formats.py`: two separate dicts merged by `get_report_registry()` — `generated_format_sets` (auto-generated by `refresh_specs`; do not hand-edit) and `base_report_specs` (hand-maintained BUILTINS, e.g. `Referenceable`; safe to edit directly, `refresh_specs` never touches it). `get_report_registry()` also auto-loads a CONFIG tier on first call: every `pyegeria.report_specs` entry point (a callable returning a FormatSetDict, or the dict itself — pyegeria's own analytic demo specs load this way, so after changing entry points run `uv sync`), then every `*.json` in the user report specs directory (`PYEGERIA_USER_REPORT_SPECS_DIR` > config.json `"Pyegeria User Report Specs Dir"` > `~/.pyegeria/report_specs`). config.json's `"Pyegeria Report Spec Modules"`, `PYEGERIA_REPORT_SPEC_MODULES` and `PYEGERIA_REPORT_FORMATS_JSON`/`_MODULES` were removed in 6.2.19 (ISSUE-173): no longer read, one WARNING log per refresh if still set. Code that isn't an installed package uses `register_report_specs(specs, source=...)` (RUNTIME tier; survives a refresh; same label from the same source is a no-op). Each entry loads independently: a failing or label-colliding entry is skipped with a warning (and returned by `refresh_report_specs()`), never blocking the others. **Gotcha:** `config/config.json` is only auto-discovered if `PYEGERIA_CONFIG_DIRECTORY` (or `PYEGERIA_ROOT_PATH`) is set — a bare shell with neither falls back to defaults silently, so a probe script that just imports `settings` may not see config.json values at all; use the `PYEGERIA_USER_REPORT_SPECS_DIR` env var directly when in doubt. - `output_formatter.py`: `generate_output()` — materializes elements into MD/LIST/DICT/REPORT formats. - `_output_format_models.py`: Pydantic models `Column`/`Format`/`FormatSet`/`ActionParameter` — define new specs with these, not raw dicts. `ActionParameter` has two independent execution paths: `function`/`find_method` (element-query, formatted per-column) and `analytic_function`/`extra_find` (a plain function returning an already-aggregated result — see `output-formats-and-report-specs.md`'s "Analytic functions" section). - `format_set_executor.py`: `exec_report_spec()` — runs either path; `SERIES`/`BAR`/`PIE` output formats wrap an analytic function's result as a Vega-Lite chart (dispatched before normal Format-row lookup, so no `FormatSet` needs to declare those as a `Format` row). -- `analytic_registry.py` / `analytic_demo_specs.py`: the catalog of analytic functions (`AnalyticFunctionSpec.generic` marks whether what it counts is a parameter or hardcoded) and one real, executable demo `FormatSet` per registered function. Installed packages add entries through the `pyegeria.analytics` entry-point group (`trellis.analytics` as an alias), read lazily on first registry use (ISSUE-172); a spec's optional `declaration` carries result kind, measures with unit/as-of/basis and the four absence sentences (ISSUE-170). -- **Gotcha — `analytic_function` can't name just any module** (ISSUE-169): it must be a registered name, a path a registered entry declares, or a path under `PYEGERIA_ANALYTIC_MODULE_PREFIXES` (default `pyegeria`, `trellis_analytics`); anything else raises `AnalyticFunctionRefused` before import. Report specs (including `metric_trend`'s `metric_path`) should use the registered name: a dotted path there warns with a DeprecationWarning in 6.2.18 and is refused from 6.2.19 (ISSUE-173). A test that injects a fake module must add it to that env var. And a `not_established`/`not_applicable` result comes back as `kind: "absent"` with a sentence in `content`, not as `data` (ISSUE-171). +- `analytic_registry.py` / `analytic_demo_specs.py`: the catalog of analytic functions (`AnalyticFunctionSpec.generic` marks whether what it counts is a parameter or hardcoded) and one real, executable demo `FormatSet` per registered function. Installed packages add entries through the `pyegeria.analytics` entry-point group, read lazily on first registry use (ISSUE-172); a spec's optional `declaration` carries result kind, measures with unit/as-of/basis and the four absence sentences (ISSUE-170). +- **Gotcha — `analytic_function` can't name just any module** (ISSUE-169): it must be a registered name, a path a registered entry declares, or a path under `PYEGERIA_ANALYTIC_MODULE_PREFIXES` (default `pyegeria`, `trellis_analytics`); anything else raises `AnalyticFunctionRefused` before import. Report specs (including `metric_trend`'s `metric_path`) must use the registered name: from 6.2.19 a dotted path there raises `AnalyticFunctionRefused` (ISSUE-173). A test that injects a fake module must add it to that env var and, if a report spec names it, register it by name too. And a `not_established`/`not_applicable` result comes back as `kind: "absent"` with a sentence in `content`, not as `data` (ISSUE-171). - `_output_dashboard_sheet_models.py`: `DashboardSheet`/`Placement` — a local (not yet Egeria-native) model for user-authored dashboards, built via Dr.Egeria's `Create Dashboard Sheet`/`Link Report to Dashboard Sheet`/`Add Text on Dashboard Sheet` (`md_processing/v2/dashboard_sheet.py`). - Generated report specs follow `{Type}-DrE-{Basic|Advanced}` naming; carry an optional `family` string for discovery. Columns are the Create command's (input) attributes; display-only server-returned columns (e.g. the ISC mermaid graphs) go in `GENERATED_EXTRA_COLUMNS` in `commands/tech/gen_report_specs.py`, never in the compact spec. diff --git a/docs/output-formats-and-report-specs.md b/docs/output-formats-and-report-specs.md index 515e2c26..81d0195a 100644 --- a/docs/output-formats-and-report-specs.md +++ b/docs/output-formats-and-report-specs.md @@ -528,33 +528,35 @@ Every report-spec lookup (`select_report_spec`, `get_report_spec_match`, `list_r |---|---| | `BUILTINS` | `base_report_specs`, hand-maintained in `base_report_formats.py` | | `GENERATED` | `generated_format_sets`, written by `refresh_specs` | -| `CONFIG` | Installed packages' entry points, your JSON files, and (deprecated) loaders named in config (below) | -| `RUNTIME` | Anything registered in code with `register_report_specs(...)` | +| `CONFIG` | Installed packages' entry points and your JSON files (below) | +| `RUNTIME` | Anything registered in code with `register_report_specs(...)`; survives `refresh_report_specs()` | -**Adding specs (the `CONFIG` tier).** There are two supported sources, plus some deprecated ones: +**Adding specs (the `CONFIG` tier).** There are two sources: - **A package's entry points (ISSUE-173).** A package contributes report specs by installing. Declare an entry point in the `pyegeria.report_specs` group that names a callable returning a `FormatSetDict` (or a plain `dict` of label to `FormatSet`), or such a dict itself: ```toml [project.entry-points."pyegeria.report_specs"] my_pkg = "my_pkg.specs:report_specs" ``` - The `trellis.report_specs` group is read too, as an alias. Entry points load first. pyegeria's own analytic demo specs load this way (`analytic_demos`), so an editable install needs `uv sync` (or `pip install -e .`) after a change to entry points. + Entry points load first. pyegeria's own analytic demo specs load this way (`analytic_demos`), so an editable install needs `uv sync` (or `pip install -e .`) after a change to entry points. - **The user report specs directory.** Every `*.json` file in it is loaded, in alphabetical order, after the list above. Subdirectories and non-JSON files are ignored. The directory is resolved in this order: 1. the `PYEGERIA_USER_REPORT_SPECS_DIR` env var (or the older `PYEGERIA_USER_FORMAT_SETS_DIR`); 2. config.json's `"Pyegeria User Report Specs Dir"`; 3. `~/.pyegeria/report_specs`. - If the directory doesn't exist, it's skipped quietly. A file that is also named explicitly in a deprecated list below is loaded only once. + If the directory doesn't exist, it's skipped quietly. -- **Deprecated in 6.2.18, removed in 6.2.19:** config.json's `"Pyegeria Report Spec Modules"` list, and the env vars `PYEGERIA_REPORT_SPEC_MODULES`, `PYEGERIA_REPORT_FORMATS_JSON` and `PYEGERIA_REPORT_FORMATS_MODULES`. They still load, after the entry points. When any is set, each refresh issues one `DeprecationWarning` and one WARNING log line naming them. Move a package's loader to an entry point, and JSON files to the user report specs directory. A config entry that names the same loader as an installed entry point (such as an older config.json listing `pyegeria.view.analytic_demo_specs:get_analytic_demo_specs`) is treated as that entry point, not as a clash, but still warns. +- **Removed in 6.2.19 (ISSUE-173):** config.json's `"Pyegeria Report Spec Modules"` list, the env vars `PYEGERIA_REPORT_SPEC_MODULES`, `PYEGERIA_REPORT_FORMATS_JSON` and `PYEGERIA_REPORT_FORMATS_MODULES`, and the `trellis.report_specs` alias group. They warned in 6.2.18 and are no longer read. If one of the first four is still set, each refresh logs one WARNING naming it, so a stale setting doesn't quietly do nothing. Move a package's loader to an entry point, and JSON files to the user report specs directory. -Each entry in a deprecated list is one of: +**Code that isn't an installed package** (a web app's own tiles, a notebook) can't declare an entry point. It registers its specs in the `RUNTIME` tier instead: -- **A path ending in `.json`** (`~` is expanded). The file holds a dictionary of FormatSets keyed by label; see [JSON structure for user specs](#json-structure-for-user-specs). `save_report_specs(...)` writes files in this shape. -- **A loader reference**, `"pkg.mod:func"` or `"pkg.mod.func"`. The function is imported and called with no arguments, and must return a `FormatSetDict` or a plain `dict` of `FormatSet`s. +```python +from pyegeria.view.base_report_formats import register_report_specs +register_report_specs(my_specs, source="my_app") +``` -`PYEGERIA_REPORT_FORMATS_JSON` takes JSON paths only and `PYEGERIA_REPORT_FORMATS_MODULES` loaders only; both are read after the user report specs directory. +Registering a label again from the same `source` replaces it and is not an error, so it's safe on every handler load. A label that another source, or a built-in, generated or `CONFIG` spec, already holds raises `ReportFormatCollision`, and nothing from that call is registered. `unregister_report_spec(label)` releases a label. **When entries are loaded.** The `CONFIG` tier loads automatically the first time anything calls `get_report_registry()`, so no consumer needs to register anything. It loads once per process. If you add or edit a file while a process is running, call `refresh_report_specs()` to reload. `load_user_report_specs()` does the same reload and also copies the directory's files into the legacy `report_specs` dict, for older code that reads that dict directly. @@ -572,7 +574,7 @@ for source, reason in refresh_report_specs(): print(f"{source} was not loaded: {reason}") ``` -**Gotcha: `config.json` must be discoverable.** pyegeria only finds `config/config.json` if `PYEGERIA_CONFIG_DIRECTORY` (or `PYEGERIA_ROOT_PATH`) is set. Otherwise its `"Pyegeria User Report Specs Dir"` setting (and the deprecated `"Pyegeria Report Spec Modules"`) is silently ignored. If you're not sure, use `PYEGERIA_USER_REPORT_SPECS_DIR`. +**Gotcha: `config.json` must be discoverable.** pyegeria only finds `config/config.json` if `PYEGERIA_CONFIG_DIRECTORY` (or `PYEGERIA_ROOT_PATH`) is set. Otherwise its `"Pyegeria User Report Specs Dir"` setting is silently ignored. If you're not sure, use `PYEGERIA_USER_REPORT_SPECS_DIR`. **Changed behavior in the user specs directory.** Before, directory files could override a built-in spec, and a later file overwrote an earlier file's label. Neither is true any more, because the directory is now part of the `CONFIG` tier: a file that reuses an existing label is skipped with a warning. To change a built-in spec, save it under a new label. Also, before this fix the directory defaulted to `../`, the parent of the current working directory; it now defaults to `~/.pyegeria/report_specs`. @@ -609,7 +611,7 @@ User JSON files contain a dictionary of FormatSets keyed by label. The shape mir } ``` -Drop this file into your user report specs directory, or add its path to `"Pyegeria Report Spec Modules"` / `PYEGERIA_REPORT_SPEC_MODULES`, as described in [Where pyegeria looks for user specs](#where-pyegeria-looks-for-user-specs). +Drop this file into your user report specs directory, as described in [Where pyegeria looks for user specs](#where-pyegeria-looks-for-user-specs). ### Notes on keys and labels @@ -947,7 +949,7 @@ Tip: You can combine `find_report_specs` to discover candidates and then `run_re A `FormatSet`'s `action` (`ActionParameter`, in `pyegeria/view/_output_format_models.py`) has **two independent execution paths**, mirroring the two function slots Dr.Egeria commands have always carried (`find_method`/`find_constraints` and `extra_find`/`extra_constraints`): - `function` (from `find_method`): an element-query call, typically a `pyegeria.omvs.*` method (e.g. `MetadataExpert.find_metadata_elements_with_string`) — returns a list of Egeria elements, formatted per-column by the `FormatSet`'s `Format.attributes` (the TABLE/DICT/REPORT/MERMAID path already documented above). -- `analytic_function` (from `extra_find`): a registered analytic function's name (a dotted path is deprecated; see below), naming a plain Python function — typically in `pyegeria.view.overview_metrics` — that takes an already-constructed, already-authenticated client as its first argument (or first two, for a function needing two client roles — see `_bind_client_args` in `pyegeria/view/format_set_executor.py`) and returns an already-aggregated scalar/dict/list result, **not** raw elements. Deliberately no fixed required/optional-param whitelist: every caller-supplied param is forwarded as-is (`**kwargs`); Python's own `TypeError` on an unexpected keyword is the validation. Which names and paths are accepted is described under [Which functions a report spec may name](#which-functions-a-report-spec-may-name). +- `analytic_function` (from `extra_find`): a registered analytic function's name (a dotted path is refused from 6.2.19; see below), naming a plain Python function — typically in `pyegeria.view.overview_metrics` — that takes an already-constructed, already-authenticated client as its first argument (or first two, for a function needing two client roles — see `_bind_client_args` in `pyegeria/view/format_set_executor.py`) and returns an already-aggregated scalar/dict/list result, **not** raw elements. Deliberately no fixed required/optional-param whitelist: every caller-supplied param is forwarded as-is (`**kwargs`); Python's own `TypeError` on an unexpected keyword is the validation. Which names and paths are accepted is described under [Which functions a report spec may name](#which-functions-a-report-spec-may-name). A `FormatSet` can declare either path, or both (in which case the analytic path only runs for `SERIES`/`BAR`/`PIE` output formats — see below — and the find path handles everything else, unchanged). @@ -1021,13 +1023,13 @@ A name defined twice raises `AnalyticRegistryCollision`, except that an installe A report spec is data: it can come from a user's report-spec directory or a config file. So `exec_report_spec` does not import whatever it names (ISSUE-169). `analytic_function`, an action's `fetch`/`analytic` steps and `analytic_binders` factories are resolved in this order: 1. **A registered name** resolves to that entry's `function`. This is the form report specs use. -2. **A dotted path that a registered entry declares** is trusted, but deprecated in a report spec (below). +2. **A dotted path that a registered entry declares** is trusted, but not in a report spec (below). 3. **Any other dotted path** must be in a module under an allowed prefix. The defaults are `pyegeria` and `trellis_analytics`. Set `PYEGERIA_ANALYTIC_MODULE_PREFIXES` (comma-separated) to change the list. A prefix is a package boundary: `pyegeria` admits `pyegeria.view.x`, not `pyegeria_extras.x`. These resolve as before and log at DEBUG. 4. **Anything else** raises `AnalyticFunctionRefused` (a `ValueError`) before anything is imported. To use a function from your own package, register it (any tier above, usually an entry point), or add your package to `PYEGERIA_ANALYTIC_MODULE_PREFIXES`. Registering is better: the function then appears in the registry for authoring tools, and it can carry a declaration. -**Report specs name functions by registered name (ISSUE-173).** In 6.2.18 a report spec's `analytic_function`, or a function-valued parameter such as `metric_trend`'s `metric_path`, that is a dotted path (steps 2 and 3) still resolves, but raises a `DeprecationWarning` naming the registered name to use (or, if none, saying to register it), and logs it once per path at WARNING. 6.2.19 will refuse them. A path refused at step 4 raises without the deprecation warning. Registry entries themselves (`AnalyticFunctionSpec.function`, an action's `fetch`/`analytic`) and `analytic_binders` factories keep their dotted paths: those are code, not report-spec data. `metric_path` used to be imported with no check at all; it now goes through this resolver. +**Report specs name functions by registered name (ISSUE-173).** A report spec's `analytic_function`, or a function-valued parameter such as `metric_trend`'s `metric_path`, must be a registered name. From 6.2.19 anything else, including a dotted path that steps 2 and 3 would accept, raises `AnalyticFunctionRefused` before any import. The message names the registered name to use or, if there is none, says to register the function. (6.2.18 still resolved dotted paths with a `DeprecationWarning`.) Steps 2 and 3 still apply to code rather than report-spec data: registry entries themselves (`AnalyticFunctionSpec.function`, an action's `fetch`/`analytic`) and `analytic_binders` factories keep their dotted paths. #### Declaring what results mean @@ -1071,7 +1073,7 @@ A package can add analytic functions and steps to the registry by installing (IS my_pkg = "my_pkg.manifests:ANALYTICS" ``` -- **Alias group:** trellis-analytics' `trellis.analytics` group is also read, during the library split. +- **No alias group:** the `trellis.analytics` alias was removed in 6.2.19; declare entry points in `pyegeria.analytics`. - **When it's read:** lazily, on the first `get_analytic_registry()` call, and cached; `refresh_analytic_functions()` forgets the cache. - **What loads:** only the manifests load then. Each function's own module is imported on its first run, so keep the manifest module free of heavy imports. - **Resolver:** a registered function is trusted whatever its module, so installing is all a package needs to do. diff --git a/pyegeria/core/config.py b/pyegeria/core/config.py index b218c5c3..12685aac 100644 --- a/pyegeria/core/config.py +++ b/pyegeria/core/config.py @@ -132,13 +132,10 @@ class EnvironmentConfig(BaseModel): pyegeria_publishing_root: str = Field(default="/dr-egeria-outbox", alias="Pyegeria Publishing Root") # Renamed: Format Sets -> Report Specs pyegeria_user_report_specs_dir: str = Field(default="~/.pyegeria/report_specs", alias="Pyegeria User Report Specs Dir") - # Extra report-spec sources auto-loaded into the CONFIG tier of - # get_report_registry() -- each entry is either a JSON file path or a - # "pkg.mod:func"/"pkg.mod.func" loader callable returning a FormatSetDict - # (or a plain dict of FormatSets), e.g. - # "pyegeria.view.analytic_demo_specs:get_analytic_demo_specs". See - # base_report_formats.refresh_report_specs(), which reads this list (and, - # for backward compat, the PYEGERIA_REPORT_FORMATS_JSON/_MODULES env vars). + # No longer read (removed in 6.2.19, ISSUE-173). Kept so an older config.json + # that still lists modules parses, and so refresh_report_specs() can warn + # that the setting is ignored. Use the `pyegeria.report_specs` entry-point + # group or register_report_specs() instead. pyegeria_report_spec_modules: List[str] = Field(default_factory=list, alias="Pyegeria Report Spec Modules") model_config = ConfigDict(populate_by_name=True, extra='allow') diff --git a/pyegeria/view/analytic_demo_specs.py b/pyegeria/view/analytic_demo_specs.py index f339ecea..1e4ae144 100644 --- a/pyegeria/view/analytic_demo_specs.py +++ b/pyegeria/view/analytic_demo_specs.py @@ -332,9 +332,9 @@ def _demo( def get_analytic_demo_specs() -> Dict[str, FormatSet]: - """Loader entry point -- usable directly, or via - PYEGERIA_REPORT_FORMATS_MODULES=pyegeria.view.analytic_demo_specs:get_analytic_demo_specs - (see base_report_formats.refresh_report_specs).""" + """Loader for pyegeria's `analytic_demos` entry point in the + `pyegeria.report_specs` group (see base_report_formats.refresh_report_specs); + also usable directly.""" return dict(_DEMOS) diff --git a/pyegeria/view/analytic_registry.py b/pyegeria/view/analytic_registry.py index 619e5d23..a9fdf157 100644 --- a/pyegeria/view/analytic_registry.py +++ b/pyegeria/view/analytic_registry.py @@ -59,7 +59,6 @@ 'AnalyticRegistryCollision', 'refresh_analytic_functions', 'ANALYTIC_ENTRY_POINT_GROUP', - 'ANALYTIC_ENTRY_POINT_ALIASES', 'analytic_entry_point_errors', 'get_analytic_registry', 'analytic_registry_payload', @@ -627,13 +626,11 @@ def _strict() -> AnalyticParam: _CONFIG_ANALYTIC_FUNCTIONS = AnalyticFunctionDict() # Installed packages register analytic functions and steps through this entry-point -# group (ISSUE-172). trellis-analytics' group is read as an alias during the library -# split. An entry point names an AnalyticFunctionSpec (or subclass), a list of them, or +# group (ISSUE-172; the `trellis.analytics` alias was removed in 6.2.19). An entry point names an AnalyticFunctionSpec (or subclass), a list of them, or # a callable returning either. Read lazily on first use of the registry; only the # manifests are loaded then -- a function's own module is imported on its first run, # by format_set_executor._resolve_analytic_function. ANALYTIC_ENTRY_POINT_GROUP = "pyegeria.analytics" -ANALYTIC_ENTRY_POINT_ALIASES = ("trellis.analytics",) _ENTRY_POINT_ANALYTIC_FUNCTIONS: Optional[AnalyticFunctionDict] = None # None = not read yet _ENTRY_POINT_ERRORS: Dict[str, str] = {} @@ -705,7 +702,7 @@ def _read_entry_points() -> Tuple[AnalyticFunctionDict, Dict[str, str]]: in the returned errors, naming it, and the rest still load.""" found = AnalyticFunctionDict() errors: Dict[str, str] = {} - for group in (ANALYTIC_ENTRY_POINT_GROUP, *ANALYTIC_ENTRY_POINT_ALIASES): + for group in (ANALYTIC_ENTRY_POINT_GROUP,): for ep in entry_points(group=group): label = f"{group}:{ep.name}" try: diff --git a/pyegeria/view/base_report_formats.py b/pyegeria/view/base_report_formats.py index 7a6da176..b7790fc7 100644 --- a/pyegeria/view/base_report_formats.py +++ b/pyegeria/view/base_report_formats.py @@ -79,11 +79,10 @@ import os import re as _re -import warnings from datetime import datetime, timezone from importlib.metadata import entry_points from pathlib import Path -from typing import Any, Callable, List, Optional, Tuple, Union +from typing import Any, Callable, Dict, List, Optional, Tuple, Union from loguru import logger @@ -3832,19 +3831,6 @@ def _load_json_file(path: str) -> FormatSetDict: return FormatSetDict.load_from_json(str(p)) -def _load_module_loader(m: str) -> FormatSetDict: - """Resolve a "pkg.mod:func" or "pkg.mod.func" string to a loader callable, - call it, and coerce the result to a FormatSetDict.""" - if ":" in m: - pkg, func = m.split(":", 1) - else: - pkg, func = m.rsplit(".", 1) - mod = __import__(pkg, fromlist=[func]) - loader = getattr(mod, func) - loaded = loader() - return loaded if isinstance(loaded, FormatSetDict) else FormatSetDict(loaded) - - def _split_env_list(name: str) -> List[str]: return [p.strip() for p in os.getenv(name, "").split(",") if p.strip()] @@ -3852,12 +3838,14 @@ def _split_env_list(name: str) -> List[str]: # Installed packages contribute report specs through this entry-point group (ISSUE-173), # the same way they contribute analytic functions through `pyegeria.analytics`. REPORT_SPEC_ENTRY_POINT_GROUP = "pyegeria.report_specs" -REPORT_SPEC_ENTRY_POINT_ALIASES = ("trellis.report_specs",) -_DEPRECATED_SPEC_SOURCES_MESSAGE = ( - "Loading report specs through {sources} is deprecated and will be removed in the next release. " - "Register a package's specs through the 'pyegeria.report_specs' entry-point group instead, " - "or put JSON files in the user report specs directory.") +# Sources removed in 6.2.19 (ISSUE-173). They are no longer read; a refresh that +# finds one still set logs a warning, so a stale setting doesn't silently do nothing. +_REMOVED_SPEC_SOURCES_MESSAGE = ( + "{sources} {verb} set but no longer read: report spec sources other than entry points and " + "the user report specs directory were removed in pyegeria 6.2.19. Register a package's specs " + "through the 'pyegeria.report_specs' entry-point group, call register_report_specs() at " + "runtime, or put JSON files in the user report specs directory.") def _load_entry_point(ep) -> FormatSetDict: @@ -3869,33 +3857,29 @@ def _load_entry_point(ep) -> FormatSetDict: def _report_spec_entry_points() -> list: found = [] - for group in (REPORT_SPEC_ENTRY_POINT_GROUP, *REPORT_SPEC_ENTRY_POINT_ALIASES): - found.extend((group, ep) for ep in entry_points(group=group)) - return found + return [(REPORT_SPEC_ENTRY_POINT_GROUP, ep) for ep in entry_points(group=REPORT_SPEC_ENTRY_POINT_GROUP)] def refresh_report_specs() -> List[Tuple[str, str]]: - """Reload formats from configured JSON files and optional modules. + """Reload the CONFIG tier of report specs. Sources, in order: - - Every entry point in the `pyegeria.report_specs` group (and its alias - `trellis.report_specs`). Its value is a callable returning a - FormatSetDict (or a dict of label to FormatSet), or such a dict itself. - This is how pyegeria's own analytic demo specs load (ISSUE-173). - - Deprecated (ISSUE-173; removed in the next release), with one - DeprecationWarning per refresh when any is set: - `settings.Environment.pyegeria_report_spec_modules` (config.json's - "Pyegeria Report Spec Modules" list, or the OS env var - PYEGERIA_REPORT_SPEC_MODULES as a comma-separated string) -- each - entry is a JSON file path (ending ".json") or a "pkg.mod:func"/ - "pkg.mod.func" loader callable. + - Every entry point in the `pyegeria.report_specs` group. Its value is a + callable returning a FormatSetDict (or a dict of label to FormatSet), + or such a dict itself. This is how pyegeria's own analytic demo specs + load (ISSUE-173). - Every *.json file in the user report specs directory, alphabetically (PYEGERIA_USER_REPORT_SPECS_DIR / PYEGERIA_USER_FORMAT_SETS_DIR, else config.json's "Pyegeria User Report Specs Dir", else ~/.pyegeria/report_specs). - - Deprecated, as above: PYEGERIA_REPORT_FORMATS_JSON (comma-separated - JSON file paths) and PYEGERIA_REPORT_FORMATS_MODULES (comma-separated - module callables). + + Code that isn't an installed package registers specs at runtime with + `register_report_specs()` instead; the RUNTIME tier survives a refresh. + + Removed in 6.2.19: config.json's "Pyegeria Report Spec Modules" list, + PYEGERIA_REPORT_SPEC_MODULES, PYEGERIA_REPORT_FORMATS_JSON, + PYEGERIA_REPORT_FORMATS_MODULES and the `trellis.report_specs` alias group. + If any of the first four is still set, a refresh logs one warning naming it. Each entry is loaded independently: an entry that fails to load, or that defines a label already present (in an earlier entry, or in the BUILTINS/ @@ -3908,42 +3892,22 @@ def refresh_report_specs() -> List[Tuple[str, str]]: try: from pyegeria.core.config import settings - configured = list(settings.Environment.pyegeria_report_spec_modules or []) + config_list = list(settings.Environment.pyegeria_report_spec_modules or []) except Exception: # noqa: BLE001 -- settings may not be initialized in every context - configured = [] - config_list = list(configured) - configured.extend(_split_env_list("PYEGERIA_REPORT_SPEC_MODULES")) - legacy_json = _split_env_list("PYEGERIA_REPORT_FORMATS_JSON") - legacy_modules = _split_env_list("PYEGERIA_REPORT_FORMATS_MODULES") + config_list = [] + still_set = [name for name, used in ( + ('config.json "Pyegeria Report Spec Modules"', config_list), + ("PYEGERIA_REPORT_SPEC_MODULES", _split_env_list("PYEGERIA_REPORT_SPEC_MODULES")), + ("PYEGERIA_REPORT_FORMATS_JSON", _split_env_list("PYEGERIA_REPORT_FORMATS_JSON")), + ("PYEGERIA_REPORT_FORMATS_MODULES", _split_env_list("PYEGERIA_REPORT_FORMATS_MODULES"))) if used] + if still_set: + logger.warning(_REMOVED_SPEC_SOURCES_MESSAGE.format( + sources=", ".join(still_set), verb="is" if len(still_set) == 1 else "are")) entries: List[Tuple[str, Any, Callable[[Any], FormatSetDict]]] = [] - ep_values = set() for group, ep in _report_spec_entry_points(): - ep_values.add(ep.value.replace(" ", "")) entries.append((f"ENTRY_POINT:{group}:{ep.name}", ep, _load_entry_point)) - # A config entry naming a loader an entry point already provides (e.g. an older - # config.json listing pyegeria's demo specs) is the same source, not a clash. - configured = [e for e in configured if e.replace(" ", "") not in ep_values] - legacy_modules = [m for m in legacy_modules if m.replace(" ", "") not in ep_values] - - deprecated = [name for name, used in ( - ('config.json "Pyegeria Report Spec Modules"', config_list), - ("PYEGERIA_REPORT_SPEC_MODULES", _split_env_list("PYEGERIA_REPORT_SPEC_MODULES")), - ("PYEGERIA_REPORT_FORMATS_JSON", legacy_json), - ("PYEGERIA_REPORT_FORMATS_MODULES", legacy_modules)) if used] - if deprecated: - message = _DEPRECATED_SPEC_SOURCES_MESSAGE.format(sources=", ".join(deprecated)) - warnings.warn(message, DeprecationWarning, stacklevel=2) - logger.warning(message) - - for entry in configured: - if entry.endswith(".json"): - entries.append((f"JSON:{entry}", entry, _load_json_file)) - else: - entries.append((f"MODULE:{entry}", entry, _load_module_loader)) entries.extend((f"JSON:{p}", p, _load_json_file) for p in _user_report_spec_files()) - entries.extend((f"JSON:{p}", p, _load_json_file) for p in legacy_json) - entries.extend((f"MODULE:{m}", m, _load_module_loader) for m in legacy_modules) # A CONFIG label that duplicates another tier would make get_report_registry() # itself raise, taking every report spec down with it -- reject it here instead. @@ -4163,26 +4127,43 @@ def find_report_specs( return sorted(results, key=lambda d: d.get("report_spec", "")) +# Which `source` registered each RUNTIME label, so a repeat registration from the +# same source (a reloaded web handler, a re-run notebook cell) is a no-op. +_RUNTIME_REPORT_SOURCES: Dict[str, str] = {} + + def register_report_specs(new_formats: Union[FormatSetDict, dict], *, source: str = "runtime") -> None: - """Dynamically add report formats at runtime. Raises on duplicate label.""" - global _RUNTIME_REPORT_FORMATS + """Add report specs to the RUNTIME tier. + + This is how code that isn't an installed package (and so can't declare a + `pyegeria.report_specs` entry point) registers its specs, e.g. a web app's + own tiles. The RUNTIME tier survives `refresh_report_specs()`. + + Registering a label again from the same `source` replaces that spec and is + not an error, so calling this on every handler load is safe. A label that + another source, or a built-in, generated or config spec, already defines + raises ReportFormatCollision and nothing from this call is registered. + """ if not isinstance(new_formats, FormatSetDict): new_formats = FormatSetDict(new_formats) existing = get_report_registry() for label in new_formats.keys(): - if label in existing.keys(): + if label in existing.keys() and _RUNTIME_REPORT_SOURCES.get(label) != source: raise ReportFormatCollision( f"Report format label '{label}' already exists; cannot register from {source}") for k, v in new_formats.items(): _RUNTIME_REPORT_FORMATS[k] = v + _RUNTIME_REPORT_SOURCES[k] = source def unregister_report_spec(label: str) -> bool: + _RUNTIME_REPORT_SOURCES.pop(label, None) return bool(_RUNTIME_REPORT_FORMATS.pop(label, None)) def clear_runtime_report_specs() -> None: _RUNTIME_REPORT_FORMATS.clear() + _RUNTIME_REPORT_SOURCES.clear() def list_report_specs() -> list[str]: diff --git a/pyegeria/view/format_set_executor.py b/pyegeria/view/format_set_executor.py index 1e384274..c2867d7f 100644 --- a/pyegeria/view/format_set_executor.py +++ b/pyegeria/view/format_set_executor.py @@ -18,7 +18,6 @@ import json import os import sys -import warnings from typing import Any, Dict, Optional, Union from loguru import logger @@ -161,33 +160,25 @@ class to resolve here, just the function itself. return _import_callable(func_decl) -_DOTTED_PATH_WARNED: set = set() - - def resolve_report_spec_function(func_decl: str): """Resolve a function a report spec names: its `analytic_function`, or a function-valued parameter such as metric_trend's `metric_path`. - Report specs name analytic functions by registered name (ISSUE-173). A - dotted path still resolves, under the same checks as - `_resolve_analytic_function`, but warns with a DeprecationWarning and one log - line per path, naming the registered name to use. Dotted paths are refused - from the next release. + A report spec names an analytic function by its registered name (ISSUE-173). + Anything else, including a dotted path, raises AnalyticFunctionRefused before + any import; the message names the registered name to use when one exists. + Dotted paths were deprecated in 6.2.18 and are refused from 6.2.19. """ - func = _resolve_analytic_function(func_decl) # a refused path raises before any warning registry = get_analytic_registry_functions() - if func_decl in registry: - return func + if isinstance(func_decl, str) and func_decl in registry: + return _resolve_analytic_function(func_decl) names = sorted(n for n, s in registry.items() if s.function == func_decl) - instead = (f"use the registered name '{names[0]}'" if names else + instead = (f"use its registered name '{names[0]}'" if names else "register it through the 'pyegeria.analytics' entry-point group and use its name") - message = (f"Report spec names analytic function '{func_decl}' by dotted path; {instead}. " - f"Dotted paths in report specs are deprecated and will be refused in the next release.") - warnings.warn(message, DeprecationWarning, stacklevel=2) - if func_decl not in _DOTTED_PATH_WARNED: - _DOTTED_PATH_WARNED.add(func_decl) - logger.warning(message) - return func + raise AnalyticFunctionRefused( + f"Report spec names analytic function {func_decl!r}, which is not a registered name; {instead}. " + f"Report specs name analytic functions by registered name only (dotted paths are refused " + f"from pyegeria 6.2.19).") # Parameter names an analytic function uses for its leading client argument(s) diff --git a/tests/micro-tests/test_exec_report_spec_token.py b/tests/micro-tests/test_exec_report_spec_token.py index b3fbd145..99df9e5a 100644 --- a/tests/micro-tests/test_exec_report_spec_token.py +++ b/tests/micro-tests/test_exec_report_spec_token.py @@ -55,7 +55,7 @@ def test_run_analytic_function_uses_token_when_given(fake_egeria_tech, monkeypat """When a token is supplied, set_bearer_token(token) is used -- not create_egeria_bearer_token().""" monkeypatch.setattr( - fse, "_resolve_analytic_function", lambda decl: decl + fse, "resolve_report_spec_function", lambda decl: decl ) monkeypatch.setattr( fse, "_bind_client_args", lambda func, client: (client,) @@ -78,7 +78,7 @@ def test_run_analytic_function_mints_token_when_none_given(fake_egeria_tech, mon """Backward compatible: with no token, the old create_egeria_bearer_token() path from user/user_pass is used unchanged.""" monkeypatch.setattr( - fse, "_resolve_analytic_function", lambda decl: decl + fse, "resolve_report_spec_function", lambda decl: decl ) monkeypatch.setattr( fse, "_bind_client_args", lambda func, client: (client,) diff --git a/tests/micro-tests/test_format_set_executor_token.py b/tests/micro-tests/test_format_set_executor_token.py index 6f691ded..1c45e132 100644 --- a/tests/micro-tests/test_format_set_executor_token.py +++ b/tests/micro-tests/test_format_set_executor_token.py @@ -70,7 +70,7 @@ def _analytic(client): return {"count": 3, "token_seen": client.token} monkeypatch.setattr(fse, "select_report_spec", lambda name, out: _ANALYTIC_FMT) monkeypatch.setattr(fse, "get_report_registry", lambda: {}) - monkeypatch.setattr(fse, "_resolve_analytic_function", lambda decl: _analytic) + monkeypatch.setattr(fse, "resolve_report_spec_function", lambda decl: _analytic) monkeypatch.setattr(fse, "EgeriaTech", _RecordingClient) @@ -147,7 +147,7 @@ def test_analytic_routine_without_client_param_builds_no_client(monkeypatch): def _local_only(window=0): return {"count": 7, "window": window} - monkeypatch.setattr(fse, "_resolve_analytic_function", lambda decl: _local_only) + monkeypatch.setattr(fse, "resolve_report_spec_function", lambda decl: _local_only) result = fse.exec_report_spec( "Anything", output_format="DICT", view_server="vs", view_url="https://unreachable", user="svc", user_pass="svc-pw", params={"window": 2}, diff --git a/tests/micro-tests/test_issue_138_140_149_analytic_envelope.py b/tests/micro-tests/test_issue_138_140_149_analytic_envelope.py index bbbfd5f8..9cfc70f8 100644 --- a/tests/micro-tests/test_issue_138_140_149_analytic_envelope.py +++ b/tests/micro-tests/test_issue_138_140_149_analytic_envelope.py @@ -20,7 +20,8 @@ from pydantic import BaseModel, ValidationError import pyegeria.view.format_set_executor as fse -from pyegeria.view.analytic_registry import AnalyticFunctionSpec +from pyegeria.view.analytic_registry import ( + AnalyticFunctionSpec, clear_runtime_analytic_functions, register_analytic_functions) MOD = "fake_trellis_analytics" @@ -81,10 +82,19 @@ def no_egeria(*a, **k): monkeypatch.setattr(fse, "EgeriaTech", no_egeria) monkeypatch.setattr(fse, "get_report_spec_heading", lambda name: "Heading") + clear_runtime_analytic_functions() yield + clear_runtime_analytic_functions() def _use_spec(monkeypatch, action: dict): + # Report specs name functions by registered name (ISSUE-173): register the + # fake module's function under its own short name and use that. + path = action.get("analytic_function") + if path and path.startswith(f"{MOD}."): + name = f"t138-{path.rsplit('.', 1)[1]}" + register_analytic_functions({name: {"name": name, "function": path}}) + action = {**action, "analytic_function": name} fmt = {"target_type": None, "action": {"function": None, **action}} monkeypatch.setattr(fse, "select_report_spec", lambda name, fmt_type: fmt) diff --git a/tests/micro-tests/test_issue_171_state_aware_report_spec.py b/tests/micro-tests/test_issue_171_state_aware_report_spec.py index a14bb5a8..9b397b32 100644 --- a/tests/micro-tests/test_issue_171_state_aware_report_spec.py +++ b/tests/micro-tests/test_issue_171_state_aware_report_spec.py @@ -7,7 +7,8 @@ import pytest import pyegeria.view.format_set_executor as fse -from pyegeria.view.analytic_registry import clear_runtime_analytic_functions, register_analytic_functions +from pyegeria.view.analytic_registry import ( + clear_runtime_analytic_functions, get_analytic_registry, register_analytic_functions) MOD = "fake_issue_171_analytics" @@ -66,7 +67,12 @@ def _declare(func_name: str): def _run(monkeypatch, func_name: str, output_format: str = "DICT"): - fmt = {"action": {"analytic_function": f"{MOD}.{func_name}"}, "target_type": "Referenceable"} + # Report specs name functions by registered name (ISSUE-173); an undeclared + # function is registered with no declaration. + name = f"cov-{func_name}" + if name not in get_analytic_registry(): + register_analytic_functions({name: {"name": name, "function": f"{MOD}.{func_name}"}}) + fmt = {"action": {"analytic_function": name}, "target_type": "Referenceable"} monkeypatch.setattr(fse, "select_report_spec", lambda name, fmt_type: fmt) return fse.exec_report_spec("coverage", output_format=output_format) diff --git a/tests/micro-tests/test_issue_172_analytic_entry_points.py b/tests/micro-tests/test_issue_172_analytic_entry_points.py index 96bd5bf8..4862c139 100644 --- a/tests/micro-tests/test_issue_172_analytic_entry_points.py +++ b/tests/micro-tests/test_issue_172_analytic_entry_points.py @@ -1,6 +1,7 @@ """ISSUE-172: installed packages register analytic functions and steps through the -`pyegeria.analytics` entry-point group (trellis.analytics as an alias), read lazily -on first use of the registry, with a broken entry point reported, not fatal.""" +`pyegeria.analytics` entry-point group, read lazily on first use of the registry, +with a broken entry point reported, not fatal. The `trellis.analytics` alias group +was removed in 6.2.19.""" import sys import pytest @@ -43,10 +44,10 @@ def fake_entry_points(group): reg.clear_runtime_analytic_functions() -def test_functions_and_steps_from_both_groups_are_registered(installed): +def test_functions_and_steps_are_registered(installed): groups, _ = installed groups[reg.ANALYTIC_ENTRY_POINT_GROUP].append(FakeEntryPoint("one", "ep_pkg:ROUTINE", ROUTINE)) - groups["trellis.analytics"].append(FakeEntryPoint("two", "ep_pkg:steps", lambda: [STEP])) + groups[reg.ANALYTIC_ENTRY_POINT_GROUP].append(FakeEntryPoint("two", "ep_pkg:steps", lambda: [STEP])) registry = reg.get_analytic_registry() assert registry["ep-routine"].function == "ep_pkg.routines.coverage" assert registry["ep-step"].kind == "step" @@ -104,18 +105,17 @@ def test_installed_name_clashing_with_a_builtin_is_skipped_not_fatal(installed): assert any("was skipped" in e for e in reg.analytic_entry_point_errors().values()) -def test_same_manifest_under_both_groups_is_not_a_clash(installed): - groups, _ = installed - groups[reg.ANALYTIC_ENTRY_POINT_GROUP].append(FakeEntryPoint("one", "ep_pkg:ROUTINE", ROUTINE)) +def test_trellis_alias_group_is_no_longer_read(installed): + groups, calls = installed groups["trellis.analytics"].append(FakeEntryPoint("one", "ep_pkg:ROUTINE", ROUTINE)) - assert "ep-routine" in reg.get_analytic_registry() - assert reg.analytic_entry_point_errors() == {} + assert "ep-routine" not in reg.get_analytic_registry() + assert calls == [reg.ANALYTIC_ENTRY_POINT_GROUP] def test_runtime_register_skipping_existing_names_still_works(installed): """trellis-analytics' register() checks get_analytic_registry() and skips names present.""" groups, _ = installed - groups["trellis.analytics"].append(FakeEntryPoint("one", "ep_pkg:ROUTINE", ROUTINE)) + groups[reg.ANALYTIC_ENTRY_POINT_GROUP].append(FakeEntryPoint("one", "ep_pkg:ROUTINE", ROUTINE)) existing = set(reg.get_analytic_registry()) new = {n: m for n, m in {"ep-routine": ROUTINE}.items() if n not in existing} assert new == {} diff --git a/tests/micro-tests/test_issue_173_registered_names.py b/tests/micro-tests/test_issue_173_registered_names.py index 391f8863..2f2dfdc9 100644 --- a/tests/micro-tests/test_issue_173_registered_names.py +++ b/tests/micro-tests/test_issue_173_registered_names.py @@ -1,6 +1,6 @@ -"""ISSUE-173: report specs name analytic functions by registered name. A dotted path -still resolves this release, with a DeprecationWarning and a log line naming what -to use instead; pyegeria's own specs use names only.""" +"""ISSUE-173: report specs name analytic functions by registered name. Dotted +paths warned in 6.2.18 and are refused from 6.2.19, with a message naming what to +use instead; pyegeria's own specs use names only.""" import warnings import pytest @@ -15,48 +15,39 @@ @pytest.fixture(autouse=True) def _clean(monkeypatch): monkeypatch.delenv("PYEGERIA_ANALYTIC_MODULE_PREFIXES", raising=False) - monkeypatch.setattr(fse, "_DOTTED_PATH_WARNED", set()) clear_runtime_analytic_functions() yield clear_runtime_analytic_functions() -@pytest.fixture -def log_lines(): - from loguru import logger - lines = [] - sink = logger.add(lambda m: lines.append(m.record), level="WARNING") - yield lines - logger.remove(sink) - - -def test_registered_name_resolves_without_warning(log_lines): +def test_registered_name_resolves_without_warning(): with warnings.catch_warnings(): warnings.simplefilter("error") assert fse.resolve_report_spec_function("governed_coverage") is overview_metrics.governed_coverage - assert log_lines == [] -def test_registered_dotted_path_warns_and_names_the_registered_name(log_lines): - path = "pyegeria.view.overview_metrics.governed_coverage" - with pytest.warns(DeprecationWarning, match="use the registered name 'governed_coverage'"): - assert fse.resolve_report_spec_function(path) is overview_metrics.governed_coverage - with pytest.warns(DeprecationWarning): - fse.resolve_report_spec_function(path) - mine = [r for r in log_lines if path in r["message"]] - assert len(mine) == 1 and mine[0]["level"].name == "WARNING" # logged once, warned every time +def test_registered_dotted_path_is_refused_and_names_the_registered_name(monkeypatch): + imported = [] + monkeypatch.setattr(fse, "_import_callable", lambda path: imported.append(path)) + with pytest.raises(fse.AnalyticFunctionRefused, match="use its registered name 'governed_coverage'"): + fse.resolve_report_spec_function("pyegeria.view.overview_metrics.governed_coverage") + assert imported == [] # refused before any import -def test_unregistered_dotted_path_warns_to_register_it(): - with pytest.warns(DeprecationWarning, match="'pyegeria.analytics' entry-point group"): +def test_unregistered_dotted_path_is_refused_and_says_to_register_it(): + with pytest.raises(fse.AnalyticFunctionRefused, match="'pyegeria.analytics' entry-point group"): fse.resolve_report_spec_function("pyegeria.core.utils.body_slimmer") -def test_refused_path_raises_without_a_deprecation_warning(): - with warnings.catch_warnings(): - warnings.simplefilter("error") - with pytest.raises(fse.AnalyticFunctionRefused): - fse.resolve_report_spec_function("os.system") +def test_disallowed_path_is_refused(): + with pytest.raises(fse.AnalyticFunctionRefused): + fse.resolve_report_spec_function("os.system") + + +@pytest.mark.parametrize("bad", ["", None, "not_a_registered_name"]) +def test_non_name_is_refused(bad): + with pytest.raises(fse.AnalyticFunctionRefused): + fse.resolve_report_spec_function(bad) def test_metric_trend_metric_path_goes_through_the_allowlist(): diff --git a/tests/micro-tests/test_issue_173_report_spec_entry_points.py b/tests/micro-tests/test_issue_173_report_spec_entry_points.py index 2bec418e..6a43909b 100644 --- a/tests/micro-tests/test_issue_173_report_spec_entry_points.py +++ b/tests/micro-tests/test_issue_173_report_spec_entry_points.py @@ -1,7 +1,8 @@ """ISSUE-173: installed packages contribute report specs through the -`pyegeria.report_specs` entry-point group (alias `trellis.report_specs`). The -env-var and config.json sources still load this release, with one -DeprecationWarning per refresh.""" +`pyegeria.report_specs` entry-point group, and other code through +`register_report_specs()`. The env-var and config.json sources and the +`trellis.report_specs` alias warned in 6.2.18 and are removed in 6.2.19: a +refresh no longer reads them, and logs a warning if one is still set.""" import warnings from importlib.metadata import EntryPoint @@ -44,6 +45,15 @@ def _ep(name: str, value: str, group: str = brf.REPORT_SPEC_ENTRY_POINT_GROUP) - return group, EntryPoint(name=name, value=value, group=group) +@pytest.fixture +def log_lines(): + from loguru import logger + lines = [] + sink = logger.add(lambda m: lines.append(m.record["message"]), level="WARNING") + yield lines + logger.remove(sink) + + @pytest.fixture def isolated(monkeypatch, tmp_path): monkeypatch.setattr(settings.Environment, "pyegeria_report_spec_modules", []) @@ -54,7 +64,9 @@ def isolated(monkeypatch, tmp_path): monkeypatch.setattr(brf, "_CONFIG_REPORT_FORMATS", FormatSetDict()) monkeypatch.setattr(brf, "_config_report_specs_loaded", True) monkeypatch.setattr(brf, "_report_spec_entry_points", lambda: []) - return monkeypatch + brf.clear_runtime_report_specs() + yield monkeypatch + brf.clear_runtime_report_specs() def _entry_points(isolated, *eps): @@ -72,7 +84,7 @@ def _refresh_no_deprecation(): def test_entry_point_specs_load_in_each_shape(isolated): mod = __name__ _entry_points(isolated, _ep("tile", f"{mod}:tile_specs"), _ep("plain", f"{mod}:tile_specs_plain_dict"), - _ep("object", f"{mod}:TILE_OBJECT", group="trellis.report_specs")) + _ep("object", f"{mod}:TILE_OBJECT")) assert _refresh_no_deprecation() == [] registry = brf.get_report_registry() assert {"EP-Tile", "EP-Plain", "EP-Object"} <= set(registry) @@ -88,9 +100,16 @@ def test_broken_or_clashing_entry_point_is_skipped_and_the_rest_load(isolated): assert "EP-Tile" in brf.get_report_registry() +def test_only_the_pyegeria_group_is_read(monkeypatch): + asked = [] + monkeypatch.setattr(brf, "entry_points", lambda group: asked.append(group) or []) + assert brf._report_spec_entry_points() == [] + assert asked == [brf.REPORT_SPEC_ENTRY_POINT_GROUP] # no trellis.report_specs alias + + @pytest.mark.parametrize("source", [ "PYEGERIA_REPORT_SPEC_MODULES", "PYEGERIA_REPORT_FORMATS_MODULES", "PYEGERIA_REPORT_FORMATS_JSON", "config.json"]) -def test_each_deprecated_source_still_loads_and_warns(isolated, tmp_path, source): +def test_each_removed_source_is_not_read_and_logs_a_warning(isolated, tmp_path, log_lines, source): if source == "PYEGERIA_REPORT_FORMATS_JSON": path = tmp_path / "legacy" / "specs.json" path.parent.mkdir() @@ -101,17 +120,46 @@ def test_each_deprecated_source_still_loads_and_warns(isolated, tmp_path, source else: isolated.setenv(source, f"{__name__}:tile_specs") name = 'config.json "Pyegeria Report Spec Modules"' if source == "config.json" else source - with pytest.warns(DeprecationWarning, match=f"{name}.*'pyegeria.report_specs' entry-point group"): - assert brf.refresh_report_specs() == [] + assert _refresh_no_deprecation() == [] + assert "EP-Tile" not in brf.get_report_registry() + warned = [m for m in log_lines if "no longer read" in m] + assert len(warned) == 1 and name in warned[0] and "register_report_specs()" in warned[0] + + +def test_no_warning_when_no_removed_source_is_set(isolated, log_lines): + _refresh_no_deprecation() + assert not [m for m in log_lines if "no longer read" in m] + + +def test_runtime_registration_survives_a_refresh(isolated): + brf.register_report_specs(tile_specs(), source="portal") + _refresh_no_deprecation() assert "EP-Tile" in brf.get_report_registry() -def test_config_entry_naming_an_entry_point_loader_is_the_same_source(isolated): - _entry_points(isolated, _ep("tile", f"{__name__}:tile_specs")) - isolated.setattr(settings.Environment, "pyegeria_report_spec_modules", [f"{__name__}:tile_specs"]) - with pytest.warns(DeprecationWarning): # still told to drop the key... - skipped = brf.refresh_report_specs() - assert skipped == [] # ...but it is not reported as a clash with itself +def test_registering_again_from_the_same_source_is_a_no_op(isolated): + brf.register_report_specs(tile_specs(), source="portal") + brf.register_report_specs(tile_specs(), source="portal") # e.g. a reloaded handler + assert "EP-Tile" in brf.get_report_registry() + + +def test_registering_a_label_from_another_source_raises(isolated): + brf.register_report_specs(tile_specs(), source="portal") + with pytest.raises(brf.ReportFormatCollision): + brf.register_report_specs(tile_specs(), source="someone-else") + + +def test_registering_a_builtin_label_raises_and_registers_nothing(isolated): + specs = FormatSetDict({"EP-New": _spec("EP-New"), "Referenceable": _spec("clash")}) + with pytest.raises(brf.ReportFormatCollision): + brf.register_report_specs(specs, source="portal") + assert "EP-New" not in brf.get_report_registry() + + +def test_unregister_lets_another_source_take_the_label(isolated): + brf.register_report_specs(tile_specs(), source="portal") + assert brf.unregister_report_spec("EP-Tile") + brf.register_report_specs(tile_specs(), source="someone-else") assert "EP-Tile" in brf.get_report_registry() diff --git a/tests/micro-tests/test_overview_metrics.py b/tests/micro-tests/test_overview_metrics.py index c6acddfd..cd9dea04 100644 --- a/tests/micro-tests/test_overview_metrics.py +++ b/tests/micro-tests/test_overview_metrics.py @@ -577,7 +577,7 @@ def test_metric_trend_calls_target_once_per_point_and_merges_dict_result(): mgr = _mgr() mgr.find_metadata_elements.return_value = [{}] * 3 series = om.metric_trend( - mgr, MagicMock(), "pyegeria.view.overview_metrics.people_counts", + mgr, MagicMock(), "people_counts", window="7d", points=3, ) assert len(series) == 3 @@ -587,7 +587,7 @@ def test_metric_trend_calls_target_once_per_point_and_merges_dict_result(): def test_metric_trend_scalar_result_stored_under_value(): mgr = _mgr(count_metadata_elements=MagicMock(return_value=5)) series = om.metric_trend( - mgr, MagicMock(), "pyegeria.view.overview_metrics.count_elements", + mgr, MagicMock(), "count_elements", window="7d", points=2, metric_params={"type_name": "Asset"}, ) assert len(series) == 2 @@ -602,15 +602,17 @@ def _boom(mgr, as_of=None): raise RuntimeError("boom") import pyegeria.view.overview_metrics as om_module + from pyegeria.view.analytic_registry import clear_runtime_analytic_functions, register_analytic_functions om_module._boom_for_test = _boom + # metric_path is a registered name from 6.2.19 (ISSUE-173) + register_analytic_functions({"boom-for-test": { + "name": "boom-for-test", "function": "pyegeria.view.overview_metrics._boom_for_test"}}) try: - series = om.metric_trend( - mgr, MagicMock(), "pyegeria.view.overview_metrics._boom_for_test", - window="7d", points=2, - ) + series = om.metric_trend(mgr, MagicMock(), "boom-for-test", window="7d", points=2) assert len(series) == 2 assert all(p["value"] is None for p in series) finally: + clear_runtime_analytic_functions() del om_module._boom_for_test diff --git a/tests/micro-tests/test_report_registry_config_tier.py b/tests/micro-tests/test_report_registry_config_tier.py index 99da5a05..64d81fee 100644 --- a/tests/micro-tests/test_report_registry_config_tier.py +++ b/tests/micro-tests/test_report_registry_config_tier.py @@ -48,25 +48,27 @@ def isolated(monkeypatch, user_dir): return monkeypatch -def test_bad_entry_does_not_skip_later_entries(isolated, tmp_path): - good_a = _write_specs(tmp_path / "a.json", "ConfigTier-A") - good_b = _write_specs(tmp_path / "b.json", "ConfigTier-B") - missing = str(tmp_path / "missing.json") - isolated.setenv("PYEGERIA_REPORT_SPEC_MODULES", ",".join([good_a, missing, "no.such.module:loader", good_b])) +# Entries are fed in as files in the user report specs directory, which loads +# alphabetically. (These tests used PYEGERIA_REPORT_SPEC_MODULES and friends +# before those sources were removed in 6.2.19.) + +def test_bad_entry_does_not_skip_later_entries(isolated, user_dir): + _write_specs(user_dir / "a.json", "ConfigTier-A") + (user_dir / "b-broken.json").write_text("{ not json") + _write_specs(user_dir / "c.json", "ConfigTier-C") skipped = brf.refresh_report_specs() - assert [s for s, _ in skipped] == [f"JSON:{missing}", "MODULE:no.such.module:loader"] + assert [s for s, _ in skipped] == [f"JSON:{user_dir / 'b-broken.json'}"] registry = brf.get_report_registry() assert "ConfigTier-A" in registry - assert "ConfigTier-B" in registry + assert "ConfigTier-C" in registry -def test_colliding_entry_skipped_whole_others_kept(isolated, tmp_path): - first = _write_specs(tmp_path / "first.json", "ConfigTier-Shared", "ConfigTier-OnlyFirst") - second = _write_specs(tmp_path / "second.json", "ConfigTier-Shared", "ConfigTier-OnlySecond") - third = _write_specs(tmp_path / "third.json", "ConfigTier-Third") - isolated.setattr(settings.Environment, "pyegeria_report_spec_modules", [first, second, third]) +def test_colliding_entry_skipped_whole_others_kept(isolated, user_dir): + _write_specs(user_dir / "1-first.json", "ConfigTier-Shared", "ConfigTier-OnlyFirst") + second = _write_specs(user_dir / "2-second.json", "ConfigTier-Shared", "ConfigTier-OnlySecond") + _write_specs(user_dir / "3-third.json", "ConfigTier-Third") skipped = brf.refresh_report_specs() @@ -77,11 +79,10 @@ def test_colliding_entry_skipped_whole_others_kept(isolated, tmp_path): assert "ConfigTier-OnlySecond" not in registry -def test_entry_duplicating_builtin_is_skipped_and_registry_still_works(isolated, tmp_path): +def test_entry_duplicating_builtin_is_skipped_and_registry_still_works(isolated, user_dir): builtin_label = next(iter(brf.base_report_specs.keys())) - clash = _write_specs(tmp_path / "clash.json", builtin_label, "ConfigTier-WithClash") - ok = _write_specs(tmp_path / "ok.json", "ConfigTier-Ok") - isolated.setenv("PYEGERIA_REPORT_SPEC_MODULES", f"{clash},{ok}") + clash = _write_specs(user_dir / "clash.json", builtin_label, "ConfigTier-WithClash") + _write_specs(user_dir / "ok.json", "ConfigTier-Ok") skipped = brf.refresh_report_specs() @@ -91,10 +92,8 @@ def test_entry_duplicating_builtin_is_skipped_and_registry_still_works(isolated, assert "ConfigTier-WithClash" not in registry -def test_all_entries_good_returns_no_skips(isolated, tmp_path): - good = _write_specs(tmp_path / "good.json", "ConfigTier-Good") - isolated.setenv("PYEGERIA_REPORT_FORMATS_JSON", good) - isolated.setenv("PYEGERIA_REPORT_FORMATS_MODULES", "pyegeria.view.analytic_demo_specs:get_analytic_demo_specs") +def test_all_entries_good_returns_no_skips(isolated, user_dir): + _write_specs(user_dir / "good.json", "ConfigTier-Good") assert brf.refresh_report_specs() == [] assert "ConfigTier-Good" in brf.get_report_registry() @@ -129,11 +128,3 @@ def test_user_dir_falls_back_to_config_setting(isolated, user_dir): assert brf.refresh_report_specs() == [] assert "UserDir-FromConfig" in brf.get_report_registry() - - -def test_file_both_listed_and_in_user_dir_loads_once(isolated, user_dir): - path = _write_specs(user_dir / "both.json", "UserDir-Both") - isolated.setenv("PYEGERIA_REPORT_SPEC_MODULES", path) - - assert brf.refresh_report_specs() == [] - assert "UserDir-Both" in brf.get_report_registry() From c09b3843e59e5e93c9cc0786e1f4507a039a53e4 Mon Sep 17 00:00:00 2001 From: Dan Wolfson Date: Sat, 31 Oct 2026 16:54:06 -0500 Subject: [PATCH 4/7] refactor(view): move the report-spec models to Pydantic V2 validators and serialisation - @root_validator(pre=True) becomes @model_validator(mode="before"), and @validator(..., pre=True) becomes @field_validator(..., mode="before"), in _output_format_models.py and _output_dashboard_sheet_models.py. - The dict() overrides keep their output, including Format's legacy "columns" key. They build on model_dump() instead of the deprecated BaseModel.dict(). Nested models without an override use model_dump(). - models.get_defined_fields uses model_fields instead of __fields__. Importing and serialising the registry now raises no PydanticDeprecatedSince20 warnings. A full dump of all 423 registered specs, through both model_dump() and dict(), plus select_report_spec over 400 label/format pairs, is byte-identical before and after. Adds test_report_specs_registered_and_pydantic_v2.py: every registered spec names registered functions only (the Trellis Coordinator's specs_check.py), no V1 deprecation warnings, the columns key is kept, and no V1 validators are left in these files. Claude-Session: https://claude.ai/code/session_015pdMuPiVFuZhoGLsBSFaN3 Signed-off-by: Dan Wolfson --- pyegeria/models/models.py | 2 +- .../view/_output_dashboard_sheet_models.py | 9 ++- pyegeria/view/_output_format_models.py | 38 ++++++---- pyegeria/view/base_report_formats.py | 4 +- ...report_specs_registered_and_pydantic_v2.py | 74 +++++++++++++++++++ 5 files changed, 105 insertions(+), 22 deletions(-) create mode 100644 tests/micro-tests/test_report_specs_registered_and_pydantic_v2.py diff --git a/pyegeria/models/models.py b/pyegeria/models/models.py index 6aae331c..5789c6b3 100644 --- a/pyegeria/models/models.py +++ b/pyegeria/models/models.py @@ -930,7 +930,7 @@ class ActionRequestBody(RequestBody): def get_defined_fields(model): return { field_name: field - for field_name, field in model.__fields__.items() + for field_name, field in model.model_fields.items() if field_name in model.__annotations__ # Only fields defined in the current model } diff --git a/pyegeria/view/_output_dashboard_sheet_models.py b/pyegeria/view/_output_dashboard_sheet_models.py index 4839dba1..d8eea72d 100644 --- a/pyegeria/view/_output_dashboard_sheet_models.py +++ b/pyegeria/view/_output_dashboard_sheet_models.py @@ -51,7 +51,7 @@ import json import os from typing import Dict, List, Literal, Optional, Union -from pydantic import BaseModel, Field, validator +from pydantic import BaseModel, Field, field_validator from loguru import logger __all__ = [ @@ -181,7 +181,8 @@ class DashboardSheet(BaseModel): family: Optional[str] = None placements: List[Union[Placement, Dict]] = Field(default_factory=list) - @validator('placements', pre=True) + @field_validator('placements', mode='before') + @classmethod def validate_placements(cls, v): """Convert dictionary placements to Placement objects.""" result = [] @@ -194,9 +195,9 @@ def validate_placements(cls, v): def dict(self, *args, **kwargs): """Override dict method to convert Placement objects back to dictionaries.""" - result = super().dict(*args, **kwargs) + result = self.model_dump(*args, **kwargs) result['placements'] = [ - p if isinstance(p, dict) else p.dict() for p in self.placements + p if isinstance(p, dict) else p.model_dump() for p in self.placements ] return result diff --git a/pyegeria/view/_output_format_models.py b/pyegeria/view/_output_format_models.py index db5ef062..aea0c6c8 100644 --- a/pyegeria/view/_output_format_models.py +++ b/pyegeria/view/_output_format_models.py @@ -68,7 +68,7 @@ import os from pathlib import Path from typing import Dict, List, Optional, Union, Any -from pydantic import BaseModel, Field, model_validator, validator, root_validator +from pydantic import BaseModel, Field, field_validator, model_validator from loguru import logger __all__ = [ 'Column', @@ -161,7 +161,8 @@ class Format(BaseModel): types: List[str] attributes: List[Union[Column, Dict[str, Any]]] - @root_validator(pre=True) + @model_validator(mode="before") + @classmethod def _migrate_columns_to_attributes(cls, values): """Support legacy 'columns' by migrating to 'attributes' when loading.""" if isinstance(values, dict): @@ -169,7 +170,8 @@ def _migrate_columns_to_attributes(cls, values): values['attributes'] = values.pop('columns') return values - @validator('attributes', pre=True) + @field_validator('attributes', mode='before') + @classmethod def validate_attributes(cls, v): """Convert dictionary attributes to Attribute/Column objects.""" result = [] @@ -184,9 +186,9 @@ def dict(self, *args, **kwargs): """Override dict method to convert Attribute objects back to dictionaries. Emits both 'attributes' (preferred) and 'columns' (deprecated) for backward compatibility. """ - result = super().dict(*args, **kwargs) + result = self.model_dump(*args, **kwargs) result['attributes'] = [ - attr if isinstance(attr, dict) else attr.dict() + attr if isinstance(attr, dict) else attr.model_dump() for attr in self.attributes ] # Backward-compat alias @@ -258,7 +260,8 @@ class ActionParameter(BaseModel): analytic_spec_params: Dict[str, Any] = Field(default_factory=dict) analytic_binders: Dict[str, str] = Field(default_factory=dict) - @root_validator(pre=True) + @model_validator(mode="before") + @classmethod def _migrate_legacy_user_params(cls, values): """Migrate legacy 'user_params' into 'required_params' when loading from older dict/json.""" if isinstance(values, dict): @@ -285,7 +288,8 @@ class QuestionSpec(BaseModel): perspectives: A list of perspective names for whom these questions are relevant questions: Example natural-language questions this report spec can answer """ - @root_validator(pre=True) + @model_validator(mode="before") + @classmethod def _migrate_legacy_roles(cls, values): """Migrate legacy 'roles' field to 'perspectives' when loading from older dict/json.""" if isinstance(values, dict) and 'perspectives' not in values and 'roles' in values: @@ -322,7 +326,8 @@ class FormatSet(BaseModel): # Optional: example questions and perspectives this report spec can address question_spec: Optional[List[Union[QuestionSpec, Dict[str, Any]]]] = None - @root_validator(pre=True) + @model_validator(mode="before") + @classmethod def _migrate_legacy_fields(cls, values): """Migrate legacy fields from older saved JSON (entity_type -> target_type).""" if isinstance(values, dict): @@ -330,7 +335,8 @@ def _migrate_legacy_fields(cls, values): values['target_type'] = values.pop('entity_type') return values - @validator('formats', pre=True) + @field_validator('formats', mode='before') + @classmethod def validate_formats(cls, v): """Convert dictionary formats to Format objects.""" result = [] @@ -341,7 +347,8 @@ def validate_formats(cls, v): result.append(item) return result - @validator('question_spec', pre=True) + @field_validator('question_spec', mode='before') + @classmethod def validate_question_spec(cls, v): """Convert dictionary items to QuestionSpec objects when provided.""" if v is None: @@ -354,7 +361,8 @@ def validate_question_spec(cls, v): out.append(item) return out - @validator('action', 'get_additional_props', pre=True) + @field_validator('action', 'get_additional_props', mode='before') + @classmethod def validate_action_like(cls, v): """Convert dictionary action-like fields to ActionParameter objects. Accepts legacy list shape.""" if v is None: @@ -371,20 +379,20 @@ def validate_action_like(cls, v): def dict(self, *args, **kwargs): """Override dict method to convert nested objects back to dictionaries.""" - result = super().dict(*args, **kwargs) + result = self.model_dump(*args, **kwargs) result['formats'] = [ format if isinstance(format, dict) else format.dict() for format in self.formats ] if self.action is not None: - result['action'] = self.action if isinstance(self.action, dict) else self.action.dict() + result['action'] = self.action if isinstance(self.action, dict) else self.action.model_dump() if self.get_additional_props is not None: result['get_additional_props'] = ( - self.get_additional_props if isinstance(self.get_additional_props, dict) else self.get_additional_props.dict() + self.get_additional_props if isinstance(self.get_additional_props, dict) else self.get_additional_props.model_dump() ) if self.question_spec is not None: result['question_spec'] = [ - item if isinstance(item, dict) else item.dict() for item in self.question_spec + item if isinstance(item, dict) else item.model_dump() for item in self.question_spec ] return result diff --git a/pyegeria/view/base_report_formats.py b/pyegeria/view/base_report_formats.py index b7790fc7..9617642d 100644 --- a/pyegeria/view/base_report_formats.py +++ b/pyegeria/view/base_report_formats.py @@ -4517,9 +4517,9 @@ def _select_from_registry(registry: FormatSetDict, kind: str, output_type: str) "_report_spec_name": resolved_key, } if element.action: - output_struct["action"] = element.action.dict() + output_struct["action"] = element.action.model_dump() if element.get_additional_props: - output_struct["get_additional_props"] = element.get_additional_props.dict() + output_struct["get_additional_props"] = element.get_additional_props.model_dump() if output_type == "ANY": return output_struct diff --git a/tests/micro-tests/test_report_specs_registered_and_pydantic_v2.py b/tests/micro-tests/test_report_specs_registered_and_pydantic_v2.py new file mode 100644 index 00000000..7fe36ff5 --- /dev/null +++ b/tests/micro-tests/test_report_specs_registered_and_pydantic_v2.py @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright Contributors to the ODPi Egeria project. +""" +6.2.19 release checks for the report-spec registry. + +- Every function a registered report spec names (an action's + `analytic_function`, or a `metric_path` anywhere in its params) is a + registered analytic-function name. Dotted paths are refused from 6.2.19 + (ISSUE-173), so a spec that still used one would fail at run time, not at + load. This is the Trellis Coordinator's specs_check.py as a test. +- The report-spec models load and serialise without Pydantic V1 deprecation + warnings (they would break on Pydantic V3), and `Format.dict()` still emits + the legacy `columns` key that callers read. +""" +import pathlib +import re +import warnings + +import pytest +from pydantic.warnings import PydanticDeprecatedSince20 + +from pyegeria.view.analytic_registry import get_analytic_registry +from pyegeria.view.base_report_formats import get_report_registry + +pytestmark = pytest.mark.unit + +VIEW_DIR = pathlib.Path(__file__).resolve().parents[2] / "pyegeria" / "view" +MODEL_FILES = ["_output_format_models.py", "_output_dashboard_sheet_models.py", "base_report_formats.py"] + + +def _function_refs(obj, label, refs): + if isinstance(obj, dict): + for key, value in obj.items(): + if key in ("analytic_function", "metric_path") and isinstance(value, str) and value: + refs.append((label, key, value)) + else: + _function_refs(value, label, refs) + elif isinstance(obj, (list, tuple)): + for item in obj: + _function_refs(item, label, refs) + + +def test_every_report_spec_names_registered_functions_only(): + analytics = get_analytic_registry() + registry = get_report_registry() + assert len(registry) > 300, "the report-spec registry lost specs" + refs = [] + for label, fs in registry.items(): + _function_refs(fs.model_dump(), label, refs) + assert refs, "expected shipped report specs that name analytic functions" + bad = [r for r in refs if "." in r[2] or r[2] not in analytics] + assert bad == [] + + +def test_report_spec_models_raise_no_pydantic_v1_deprecation(): + with warnings.catch_warnings(): + warnings.simplefilter("error", PydanticDeprecatedSince20) + registry = get_report_registry() + for fs in registry.values(): + fs.dict() + fs.model_dump() + + +def test_format_dict_keeps_the_legacy_columns_key(): + fs = next(fs for fs in get_report_registry().values() if fs.formats) + fmt = fs.dict()["formats"][0] + assert fmt["columns"] == fmt["attributes"] + + +@pytest.mark.parametrize("name", MODEL_FILES) +def test_no_pydantic_v1_validators_in_report_spec_models(name): + src = (VIEW_DIR / name).read_text() + assert not re.search(r"@(root_)?validator\b", src) + assert "super().dict(" not in src From 2522f3321c4ad4b7e5fecbe661354e3373dd8c39 Mon Sep 17 00:00:00 2001 From: Dan Wolfson Date: Sat, 31 Oct 2026 16:56:39 -0500 Subject: [PATCH 5/7] feat(core): find_note_logs takes graph_query_depth as a named parameter At the default depth, each log's noteLogEntries are inlined, and page_size bounds logs rather than entries. On a server with two large system logs, listing them took about 30 s; with graph_query_depth=0 it takes 0.3 s. Until now the value reached the request only through **kwargs. It's now in both signatures and documented, with the default (3) unchanged. Claude-Session: https://claude.ai/code/session_015pdMuPiVFuZhoGLsBSFaN3 Signed-off-by: Dan Wolfson --- pyegeria/core/_server_client.py | 14 ++++-- .../test_find_note_logs_graph_query_depth.py | 47 +++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 tests/micro-tests/test_find_note_logs_graph_query_depth.py diff --git a/pyegeria/core/_server_client.py b/pyegeria/core/_server_client.py index f137c10a..199c86e4 100644 --- a/pyegeria/core/_server_client.py +++ b/pyegeria/core/_server_client.py @@ -2377,6 +2377,7 @@ async def _async_find_note_logs( output_format: str = "JSON", report_spec: Optional[str | dict] = None, body: Optional[dict | SearchStringRequestBody] = None, + graph_query_depth: int = 3, **kwargs ) -> dict | str: @@ -2407,6 +2408,10 @@ async def _async_find_note_logs( Output format set for the response. body : dict | SearchStringRequestBody, optional Body of the request. Details of the body override other parameters if present. + graph_query_depth : int, default 3 + How far to follow relationships from each log. At the default, each log's + entries (noteLogEntries) are inlined, and page_size bounds logs, not entries, + so a log with many entries makes the call slow. Pass 0 to list logs only. **kwargs : dict, optional Additional parameters supported by the underlying find request: @@ -2488,7 +2493,8 @@ async def _async_find_note_logs( 'page_size': page_size, 'output_format': output_format, 'report_spec': report_spec, - 'body': body + 'body': body, + 'graph_query_depth': graph_query_depth, } # Merge with any additional kwargs, removing None values params.update(kwargs) @@ -2512,10 +2518,11 @@ def find_note_logs( page_size: int = 0, output_format: str = "JSON", report_spec: Optional[str | dict] = None, body: Optional[dict | SearchStringRequestBody] = None, + graph_query_depth: int = 3, **kwargs ) -> dict | str: """ - Return the list of comments containing the supplied string. Async Version. + Return the list of note logs containing the supplied string. Parameters ---------- @@ -2555,7 +2562,8 @@ def find_note_logs( """ resp = run_sync( self._async_find_note_logs(search_string, classification_names, metadata_element_subtypes, starts_with, - ends_with, ignore_case, start_from, page_size, output_format, report_spec, body, **kwargs) + ends_with, ignore_case, start_from, page_size, output_format, report_spec, body, + graph_query_depth=graph_query_depth, **kwargs) ) return resp diff --git a/tests/micro-tests/test_find_note_logs_graph_query_depth.py b/tests/micro-tests/test_find_note_logs_graph_query_depth.py new file mode 100644 index 00000000..9b4cd8df --- /dev/null +++ b/tests/micro-tests/test_find_note_logs_graph_query_depth.py @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright Contributors to the ODPi Egeria project. +"""find_note_logs takes graph_query_depth as a named parameter. + +At the default depth each log's noteLogEntries are inlined, and page_size bounds +logs, not entries, so listing logs on a server with two large system logs took +about 30 s (Trellis Coordinator, 2026-10-31). graph_query_depth=0 lists logs +only; it used to reach the request only through **kwargs. The default (3) is +unchanged. +""" +import json + +import pytest + +from pyegeria.omvs.asset_maker import AssetMaker + +pytestmark = pytest.mark.unit + + +class _Response: + def json(self): + return {"elements": []} + + +def _client(monkeypatch, bodies): + monkeypatch.setattr(AssetMaker, "check_connection", lambda self: "test") + client = AssetMaker("view-server", "https://localhost:1", "user", "pwd") + + async def fake_make_request(method, url, body=None, *args, **kwargs): + bodies.append(json.loads(body) if isinstance(body, str) else body) + return _Response() + + client._async_make_request = fake_make_request + return client + + +@pytest.mark.parametrize("kwargs,expected", [({}, 3), ({"graph_query_depth": 0}, 0)]) +def test_graph_query_depth_reaches_the_request(monkeypatch, kwargs, expected): + bodies = [] + _client(monkeypatch, bodies).find_note_logs("*", **kwargs) + assert bodies[0]["graphQueryDepth"] == expected + + +async def test_async_graph_query_depth_reaches_the_request(monkeypatch): + bodies = [] + await _client(monkeypatch, bodies)._async_find_note_logs("*", graph_query_depth=0) + assert bodies[0]["graphQueryDepth"] == 0 From 2745e38d9ee4831fc612a26e280844e0b08144b1 Mon Sep 17 00:00:00 2001 From: Dan Wolfson Date: Sat, 31 Oct 2026 16:57:42 -0500 Subject: [PATCH 6/7] docs: ISSUE-173 to the Appendix; AGENTS.md and view README match the 6.2.19 removals Claude-Session: https://claude.ai/code/session_015pdMuPiVFuZhoGLsBSFaN3 Signed-off-by: Dan Wolfson --- AGENTS.md | 2 +- PYEGERIA_ISSUES.md | 45 +++++++++++++++++++++-------------------- pyegeria/view/README.md | 2 +- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index dd7868a9..f55596f3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,7 +32,7 @@ - `pyegeria/omvs/*`: service implementations (40+ modules); new API behavior should usually be added here first. - `pyegeria/models/`: Pydantic models for Egeria API request/response bodies (`models.py`, `collection_models.py`). - `pyegeria/view/_output_format_models.py`: Pydantic models (`Column`/`Format`/`FormatSet`/`ActionParameter`) backing the report spec registry. -- `pyegeria/view/output_formatter.py` + `pyegeria/view/base_report_formats.py`: materialization/rendering and report spec registry; generated specs live in `base_report_formats.py` under `generated_format_sets` (do not hand-edit), hand-maintained ones under `base_report_specs` (safe to edit). `get_report_registry()` auto-loads an additional CONFIG tier on first call: `pyegeria.report_specs` entry points (alias `trellis.report_specs`; pyegeria's own analytic demos load this way) and the user report specs directory, plus the deprecated `PYEGERIA_REPORT_SPEC_MODULES`/config.json list (warns in 6.2.18, removed in 6.2.19; ISSUE-173). Report specs name analytic functions by registered name; a dotted path is deprecated. +- `pyegeria/view/output_formatter.py` + `pyegeria/view/base_report_formats.py`: materialization/rendering and report spec registry; generated specs live in `base_report_formats.py` under `generated_format_sets` (do not hand-edit), hand-maintained ones under `base_report_specs` (safe to edit). `get_report_registry()` auto-loads an additional CONFIG tier on first call: `pyegeria.report_specs` entry points (pyegeria's own analytic demos load this way) and the user report specs directory; other code registers specs with `register_report_specs()`. `PYEGERIA_REPORT_SPEC_MODULES`, `PYEGERIA_REPORT_FORMATS_*`, config.json's list and the `trellis.*` alias groups were removed in 6.2.19 (ISSUE-173). Report specs name analytic functions by registered name; a dotted path raises `AnalyticFunctionRefused`. - `pyegeria/view/format_set_executor.py`: side-effect-free FormatSet runner used by the MCP adapter and the `ViewProcessor` in Dr.Egeria; also runs `ActionParameter.analytic_function` (the `extra_find` path) and wraps its result as a `SERIES`/`BAR`/`PIE` Vega-Lite chart when requested. - `pyegeria/view/analytic_registry.py` + `pyegeria/view/analytic_demo_specs.py`: catalog of analytic functions (`extra_find` targets) and one real demo `FormatSet` per function. A report spec may only name a registered function or a module under `PYEGERIA_ANALYTIC_MODULE_PREFIXES` (ISSUE-169); packages register through the `pyegeria.analytics` entry-point group (ISSUE-172). - `pyegeria/view/_output_dashboard_sheet_models.py`: `DashboardSheet`/`Placement` — local (pre-Egeria-native) model for user-authored dashboards; built via `md_processing/v2/dashboard_sheet.py`'s processors (`Create Dashboard Sheet`, `Link Report to Dashboard Sheet`, `Add Text on Dashboard Sheet`). diff --git a/PYEGERIA_ISSUES.md b/PYEGERIA_ISSUES.md index 970bdfb8..d62d424f 100644 --- a/PYEGERIA_ISSUES.md +++ b/PYEGERIA_ISSUES.md @@ -148,7 +148,7 @@ content was changed. The numbered section list above describes the # Open Issues -**At a glance** — 4 open pyegeria items, 0 awaiting re-test, 0 open Egeria Server issues (2026-10-31: ISSUE-174 added, link methods discard relationship GUIDs. ISSUE-173 added, released in 6.2.18, report specs by registered name and the `pyegeria.report_specs` entry points. 2026-10-30: ISSUE-168, released in 6.2.16, and ISSUE-169..172, fixed for 6.2.17, moved to the Appendix. 2026-10-30: ISSUE-146's `remove_note` re-test passed, ISSUE-79's folder survey completes, ISSUE-108 did not reproduce at 200 links; all three moved to the Appendix. 2026-10-29: ISSUE-159, 132 and 85 live-verified; 12 Egeria-side fixes re-confirmed or closed on earlier passes and moved to the Appendix). +**At a glance** — 3 open pyegeria items, 0 awaiting re-test, 0 open Egeria Server issues (2026-10-31: ISSUE-174 added and fixed for 6.2.19, link methods now return relationship GUIDs. ISSUE-173 released in 6.2.18, with its removals done for 6.2.19, and moved to the Appendix. 2026-10-30: ISSUE-168, released in 6.2.16, and ISSUE-169..172, fixed for 6.2.17, moved to the Appendix. 2026-10-30: ISSUE-146's `remove_note` re-test passed, ISSUE-79's folder survey completes, ISSUE-108 did not reproduce at 200 links; all three moved to the Appendix. 2026-10-29: ISSUE-159, 132 and 85 live-verified; 12 Egeria-side fixes re-confirmed or closed on earlier passes and moved to the Appendix). **Egeria team note, 2026-10-18 (Mandy Chessell).** Saved verbatim at `~/.re-gate/EGERIA-TEAM-NOTE-2026-10-18.md`. Each entry below has a dated @@ -172,6 +172,7 @@ re-test candidates moved into *Awaiting re-test* below the open sections. create-from-template) and 7 (`viewElementGUID`) are still open. **Open pyegeria items** +- ISSUE-174: link methods discarded the new relationship GUID (fixed for 6.2.19; moves to the Appendix on release) - ISSUE-155: pyegeria gaps against Egeria `main` since 6.2 (omvs_audit + source diff, V6.2..05a5e982) - ISSUE-150: Dr.Egeria commands for an Analytic Function's Egeria declaration: a GovernanceActionType that Resource Explorer runs as an engine host, plus a synchronous path that bypasses Egeria @@ -202,27 +203,6 @@ into the entry now, so it isn't rediscovered from scratch later. **Fix (done):** an AST-driven rewrite of `pyegeria/omvs`. Every async method that bare-awaited the helper now returns its result. Where a log line followed the call, the result is kept in `relationship_guid` and returned after it. Every sync wrapper that bare-called such a method now returns it, and so does every intermediate async method (e.g. ClassificationExplorer's `add_*_to_element`). `-> None` annotations became `-> str | None`, and `Returns: None` docstrings now describe the GUID. `tests/micro-tests/test_issue_174_link_returns_guid.py` has an AST guard over all of `pyegeria/omvs` (transitive, async and sync) and a stubbed round-trip for 8 methods across 7 clients, both sync and async. -### ISSUE-173: Report specs named analytic functions by dotted path and loaded package specs through env vars; added the `pyegeria.report_specs` entry-point group and deprecated both - -**Layer:** pyegeria · **Status:** built on branch `feat/analytic-registered-names`, no PR yet; targets 6.2.18, with removal in 6.2.19 · **Found:** 2026-10-31, the owner's ruling on Mandy's question 2 (portal analytics discovery), "yes to (b)", then "a" for report-spec registration (the Architect's R1, split note §11.3). - -**What:** the Portal could only find a package's analytic tiles if something set `PYEGERIA_REPORT_SPEC_MODULES` (or config.json's `"Pyegeria Report Spec Modules"`) to that package's loader, and report specs named functions by dotted path. pyegeria's own analytic demo specs depended on config.json's key too. Separately, `metric_trend`'s `metric_path`, which a caller can override, was imported with no allowlist check, bypassing ISSUE-169. - -**Change:** -- **Report-spec entry points** (`base_report_formats.py`): `refresh_report_specs()` reads the `pyegeria.report_specs` group, and `trellis.report_specs` as an alias, first. An entry point names a callable returning a `FormatSetDict` (or a dict of label to `FormatSet`), or such a dict itself. Each entry point loads in isolation: one that fails or clashes is skipped, logged, and returned by `refresh_report_specs()`, and the rest still load. -- **pyegeria's own demo specs** now load through its own entry point (`analytic_demos` in `pyproject.toml`), and the key is gone from `config/config.json`. A config entry naming the same loader as an installed entry point is treated as that entry point, not as a clash. -- **Deprecation:** config.json's list, `PYEGERIA_REPORT_SPEC_MODULES`, `PYEGERIA_REPORT_FORMATS_JSON` and `PYEGERIA_REPORT_FORMATS_MODULES` still load. When any is set, each refresh issues one `DeprecationWarning` and one WARNING log line naming them and the replacement. -- **Registered names** (`format_set_executor.resolve_report_spec_function`): a report spec's `analytic_function`, or `metric_trend`'s `metric_path`, given as a dotted path still resolves under the ISSUE-169 checks. It now raises a `DeprecationWarning` naming the registered name to use (or saying to register it), and logs once per path. A refused path raises with no deprecation warning. `metric_path` now goes through this resolver. -- **pyegeria's own specs** now use registered names: the SoA implementation summary (`control_implementation_summary`), every analytic demo spec, and the metric-trend demo's `metric_path` (`governed_coverage`). A test checks that every shipped spec names a registered function. - -**Kept as dotted paths, on purpose:** `AnalyticFunctionSpec.function`, an action's `fetch`/`analytic` steps, and `analytic_binders` factories. They are code, not report-spec data, and binder factories have no registry to name them from. - -**Downstream:** -- egeria-workspaces-fs `PyegeriaWebHandler/report_specs_handler.py` (quickstart and freshstart) names `pyegeria.view.overview_metrics.count_elements` and `.metric_trend`, with a dotted `metric_path`; it should use `count_elements` and `metric_trend`. -- trellis-analytics `portal.py`'s tile names `trellis_analytics.portal.curation_coverage_tile`, which needs a registered name, and its `report_specs` loader moves to a `pyegeria.report_specs` entry point. - -**6.2.19:** refuse dotted paths in report specs, and stop reading the four deprecated sources. - ### ISSUE-155: pyegeria gaps against Egeria `main` since 6.2 (omvs_audit + source diff, V6.2..05a5e982) **Status 2026-10-29:** items 1 (Governance Engine moves, #460), 5 (undo/restore) and 6 (usage-activity and popularity classifications) are done and released, and item 2 is fixed under ISSUE-154. Still open: item 4 (`typeName`, which only `OpenMetadataTemplateRequestBody` carries) and item 7 (`GetOptions.viewElementGUID`, low priority). @@ -398,6 +378,27 @@ Sorted newest-first by `ISSUE-#` (2026-10-12). ISSUE-91 has two entries --- +### ISSUE-173: Report specs named analytic functions by dotted path and loaded package specs through env vars; added the `pyegeria.report_specs` entry-point group and deprecated both + +**Layer:** pyegeria · **Status:** released in 6.2.18 (#523); the removals are done for 6.2.19 · **Found:** 2026-10-31, the owner's ruling on Mandy's question 2 (portal analytics discovery), "yes to (b)", then "a" for report-spec registration (the Architect's R1, split note §11.3). + +**What:** the Portal could only find a package's analytic tiles if something set `PYEGERIA_REPORT_SPEC_MODULES` (or config.json's `"Pyegeria Report Spec Modules"`) to that package's loader, and report specs named functions by dotted path. pyegeria's own analytic demo specs depended on config.json's key too. Separately, `metric_trend`'s `metric_path`, which a caller can override, was imported with no allowlist check, bypassing ISSUE-169. + +**Change:** +- **Report-spec entry points** (`base_report_formats.py`): `refresh_report_specs()` reads the `pyegeria.report_specs` group, and `trellis.report_specs` as an alias, first. An entry point names a callable returning a `FormatSetDict` (or a dict of label to `FormatSet`), or such a dict itself. Each entry point loads in isolation: one that fails or clashes is skipped, logged, and returned by `refresh_report_specs()`, and the rest still load. +- **pyegeria's own demo specs** now load through its own entry point (`analytic_demos` in `pyproject.toml`), and the key is gone from `config/config.json`. A config entry naming the same loader as an installed entry point is treated as that entry point, not as a clash. +- **Deprecation:** config.json's list, `PYEGERIA_REPORT_SPEC_MODULES`, `PYEGERIA_REPORT_FORMATS_JSON` and `PYEGERIA_REPORT_FORMATS_MODULES` still load. When any is set, each refresh issues one `DeprecationWarning` and one WARNING log line naming them and the replacement. +- **Registered names** (`format_set_executor.resolve_report_spec_function`): a report spec's `analytic_function`, or `metric_trend`'s `metric_path`, given as a dotted path still resolves under the ISSUE-169 checks. It now raises a `DeprecationWarning` naming the registered name to use (or saying to register it), and logs once per path. A refused path raises with no deprecation warning. `metric_path` now goes through this resolver. +- **pyegeria's own specs** now use registered names: the SoA implementation summary (`control_implementation_summary`), every analytic demo spec, and the metric-trend demo's `metric_path` (`governed_coverage`). A test checks that every shipped spec names a registered function. + +**Kept as dotted paths, on purpose:** `AnalyticFunctionSpec.function`, an action's `fetch`/`analytic` steps, and `analytic_binders` factories. They are code, not report-spec data, and binder factories have no registry to name them from. + +**Downstream:** +- egeria-workspaces-fs `PyegeriaWebHandler/report_specs_handler.py` (quickstart and freshstart) names `pyegeria.view.overview_metrics.count_elements` and `.metric_trend`, with a dotted `metric_path`; it should use `count_elements` and `metric_trend`. +- trellis-analytics `portal.py`'s tile names `trellis_analytics.portal.curation_coverage_tile`, which needs a registered name, and its `report_specs` loader moves to a `pyegeria.report_specs` entry point. + +**6.2.19 (done):** a report spec's `analytic_function` or `metric_path` must be a registered name; anything else raises `AnalyticFunctionRefused` before any import. The four deprecated sources are no longer read; if one is still set, a refresh logs one WARNING naming it. The `trellis.report_specs` and `trellis.analytics` alias groups are no longer read. `register_report_specs()` is the runtime path for code that isn't an installed package (the Portal's own RE tile), and re-registering a label from the same source is now a no-op. Both downstreams were checked first: trellis-analytics declares a `pyegeria.report_specs` entry point and names its functions by registered name (#956), and the Portal moves its `PYEGERIA_REPORT_SPEC_MODULES` setdefault to `register_report_specs()` together with its move to 6.2.19. + ### ISSUE-172: Installed packages couldn't register analytic functions or steps by installing; added the `pyegeria.analytics` entry-point group, read lazily **Layer:** pyegeria · **Status:** fixed in #519, ships in 6.2.17 · **Found:** 2026-10-30, Resource Explorer's portal-extensibility evaluation (gap G3) and the Architect's `DESIGN-ANALYTIC-LIBRARY-SPLIT-2026-10-30` (LS1: "entry-point group name and manifest loader" go to pyegeria). diff --git a/pyegeria/view/README.md b/pyegeria/view/README.md index 3a16ded0..98f05c86 100644 --- a/pyegeria/view/README.md +++ b/pyegeria/view/README.md @@ -8,7 +8,7 @@ into MD/LIST/DICT/REPORT/MERMAID/chart output. | File | Role | |---|---| -| `base_report_formats.py` | Two dicts merged by `get_report_registry()`: `generated_format_sets` (auto-generated by `refresh_specs` — do not hand-edit) and `base_report_specs` (hand-maintained built-ins, e.g. `Referenceable` — `refresh_specs` never touches it). Also auto-loads a CONFIG tier from `pyegeria.report_specs` entry points and the user report specs directory; `PYEGERIA_REPORT_SPEC_MODULES` and config.json's list are deprecated (ISSUE-173). | +| `base_report_formats.py` | Two dicts merged by `get_report_registry()`: `generated_format_sets` (auto-generated by `refresh_specs` — do not hand-edit) and `base_report_specs` (hand-maintained built-ins, e.g. `Referenceable` — `refresh_specs` never touches it). Also auto-loads a CONFIG tier from `pyegeria.report_specs` entry points and the user report specs directory; code that isn't an installed package uses `register_report_specs()`. `PYEGERIA_REPORT_SPEC_MODULES` and config.json's list were removed in 6.2.19 (ISSUE-173). | | `_output_format_models.py` | Pydantic models `Column`/`Format`/`FormatSet`/`ActionParameter` — define new report specs with these, not raw dicts. | | `output_formatter.py` | `generate_output()` — materializes elements into MD/LIST/DICT/REPORT formats. | | `format_set_executor.py` | `exec_report_spec()` — runs a `FormatSet`'s query/render path; dispatches `SERIES`/`BAR`/`PIE` output formats to Vega-Lite chart rendering before normal Format-row lookup. | From b9df37cb20f4664a52f3b5e22c16074a004fdf74 Mon Sep 17 00:00:00 2001 From: Dan Wolfson Date: Sat, 31 Oct 2026 16:58:20 -0500 Subject: [PATCH 7/7] chore(release): bump version to 6.2.19 Claude-Session: https://claude.ai/code/session_015pdMuPiVFuZhoGLsBSFaN3 Signed-off-by: Dan Wolfson --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b1fdacde..2d0876c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "pyegeria" -version = "6.2.18" +version = "6.2.19" license = 'Apache-2.0' license-files = ["LICENSE"] authors = [{name = "Dan Wolfson", email = "dan.wolfson@pdr-associates.com"},] diff --git a/uv.lock b/uv.lock index 276b8fe0..ae627b10 100644 --- a/uv.lock +++ b/uv.lock @@ -2238,7 +2238,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/f8/f6/8f49005d581cff883 [[package]] name = "pyegeria" -version = "6.2.18" +version = "6.2.19" source = { editable = "." } dependencies = [ { name = "altair" },