Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions md_processing/v2/solution_architect.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,10 +384,6 @@ async def _get_component_related_elements(self, guid: str) -> Dict[str, Any]:
for comp in response.get('nestedSolutionComponents', []):
res["sub_component_guids"].append(comp['relatedElement']['elementHeader'].get('guid'))

# subComponents
for comp in response.get('subComponents', []):
res["sub_component_guids"].append(comp['elementHeader'].get('guid'))

# usedInSolutionComponents (Parents)
for comp in response.get('usedInSolutionComponents', []):
res["parent_component_guids"].append(comp['relatedElement']['elementHeader'].get('guid'))
Expand Down
7 changes: 7 additions & 0 deletions pyegeria/core/_server_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6589,6 +6589,13 @@ async def _async_get_name_request(self, url: str, _type: str, _gen_output: Calla
body: Optional[dict | FilterRequestBody] = None,
max_mermaid_node_count=10, **kwargs) -> Any:

# _async_find_request spells this argument `metadata_element_type`; a wrapper copied from a find_*
# sibling passed that spelling here, where it fell into **kwargs and FilterRequestBody silently
# dropped it, so the by-name search ran unfiltered by type (ISSUE-131). Accept both.
legacy_type_name = kwargs.pop("metadata_element_type", None)
if metadata_element_type_name is None:
metadata_element_type_name = legacy_type_name

if isinstance(body, FilterRequestBody):
validated_body = body
elif isinstance(body, dict):
Expand Down
14 changes: 7 additions & 7 deletions pyegeria/omvs/actor_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,7 @@ async def _async_get_actor_profiles_by_name(
url = str(HttpUrl(f"{self.command_root}/actor-profiles/by-name"))
params = {
"filter_string": name,
"metadata_element_type": metadata_element_type_name,
"metadata_element_type_name": metadata_element_type_name,
"metadata_element_subtypes": metadata_element_subtypes,
"include_only_relationships": include_only_relationships,
"skip_relationships": skip_relationships,
Expand Down Expand Up @@ -2514,7 +2514,7 @@ async def _async_get_actor_roles_by_name(
url = str(HttpUrl(f"{self.command_root}/actor-roles/by-name"))
params = {
"filter_string": name,
"metadata_element_type": metadata_element_type_name,
"metadata_element_type_name": metadata_element_type_name,
"metadata_element_subtypes": metadata_element_subtypes,
"include_only_relationships": include_only_relationships,
"skip_relationships": skip_relationships,
Expand Down Expand Up @@ -4193,7 +4193,7 @@ async def _async_get_user_identities_by_name(
url = str(HttpUrl(f"{self.command_root}/user-identities/by-name"))
params = {
"filter_string": name,
"metadata_element_type": metadata_element_type_name,
"metadata_element_type_name": metadata_element_type_name,
"metadata_element_subtypes": metadata_element_subtypes,
"include_only_relationships": include_only_relationships,
"skip_relationships": skip_relationships,
Expand Down Expand Up @@ -4885,7 +4885,7 @@ async def _async_get_contribution_records_by_name(
url = f"{self.command_root}/contribution-records/by-name"
params = {
"filter_string": name,
"metadata_element_type": metadata_element_type_name,
"metadata_element_type_name": metadata_element_type_name,
"metadata_element_subtypes": metadata_element_subtypes,
"include_only_relationships": include_only_relationships,
"skip_relationships": skip_relationships,
Expand Down Expand Up @@ -6069,7 +6069,7 @@ async def _async_get_contact_details_by_name(
url = f"{self.command_root}/contact-details/by-name"
params = {
"filter_string": name,
"metadata_element_type": metadata_element_type_name,
"metadata_element_type_name": metadata_element_type_name,
"metadata_element_subtypes": metadata_element_subtypes,
"include_only_relationships": include_only_relationships,
"skip_relationships": skip_relationships,
Expand Down Expand Up @@ -6875,7 +6875,7 @@ async def _async_get_perspectives_by_name(
url = str(HttpUrl(f"{self.command_root}/perspectives/by-name"))
params = {
"filter_string": name,
"metadata_element_type": metadata_element_type_name,
"metadata_element_type_name": metadata_element_type_name,
"metadata_element_subtypes": metadata_element_subtypes,
"include_only_relationships": include_only_relationships,
"skip_relationships": skip_relationships,
Expand Down Expand Up @@ -7681,7 +7681,7 @@ async def _async_get_skills_by_name(
url = str(HttpUrl(f"{self.command_root}/skills/by-name"))
params = {
"filter_string": name,
"metadata_element_type": metadata_element_type_name,
"metadata_element_type_name": metadata_element_type_name,
"metadata_element_subtypes": metadata_element_subtypes,
"include_only_relationships": include_only_relationships,
"skip_relationships": skip_relationships,
Expand Down
2 changes: 1 addition & 1 deletion pyegeria/omvs/collection_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,7 @@ async def _async_get_collections_by_name(
url = str(HttpUrl(f"{self.collection_command_root}/by-name"))
params = {
"filter_string": name,
"metadata_element_type": metadata_element_type_name,
"metadata_element_type_name": metadata_element_type_name,
"metadata_element_subtypes": metadata_element_subtypes,
"include_only_relationships": include_only_relationships,
"skip_relationships": skip_relationships,
Expand Down
2 changes: 1 addition & 1 deletion pyegeria/omvs/digital_business.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ async def _async_get_business_capabilities_by_name(
url = f"{self.digital_business_command_root}/collections/by-name"
params = {
"filter_string": name,
"metadata_element_type": metadata_element_type_name,
"metadata_element_type_name": metadata_element_type_name,
"metadata_element_subtypes": metadata_element_subtypes,
"include_only_relationships": include_only_relationships,
"skip_relationships": skip_relationships,
Expand Down
7 changes: 4 additions & 3 deletions pyegeria/omvs/governance_officer.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,12 +271,13 @@ def _extract_solution_components_properties(self, element: Union[Dict, List[Dict

# Extract sub components
sub_comp_md = ""
sub_components = element.get('subComponents', None)
# Egeria returns children as nestedSolutionComponents (SolutionComposition); there is no
# subComponents field in any REST response (ISSUE-133).
sub_components = element.get('nestedSolutionComponents', None)
if sub_components:
for sub_component in sub_components:
sub_comp_prop = sub_component['properties']
sub_comp_prop = sub_component['relatedElement']['properties']
sub_comp_name = sub_comp_prop.get("displayName", None)
sub_comp_desc = sub_comp_prop.get("description", None)
sub_comp_md += f" {sub_comp_name}"

comp_graph = element.get('mermaidGraph', None)
Expand Down
2 changes: 1 addition & 1 deletion pyegeria/omvs/location_arena.py
Original file line number Diff line number Diff line change
Expand Up @@ -1204,7 +1204,7 @@ async def _async_get_locations_by_name(
url = f"{self.ref_location_command_base}/locations/by-name"
params = {
"filter_string": name,
"metadata_element_type": metadata_element_type_name,
"metadata_element_type_name": metadata_element_type_name,
"metadata_element_subtypes": metadata_element_subtypes,
"include_only_relationships": include_only_relationships,
"skip_relationships": skip_relationships,
Expand Down
2 changes: 1 addition & 1 deletion pyegeria/omvs/runtime_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3468,7 +3468,7 @@ async def _async_get_metadata_repository_cohorts_by_name(
url = f"{self.runtime_command_root}/metadata-repository-cohorts/by-name"
params = {
"filter_string": name,
"metadata_element_type": metadata_element_type_name,
"metadata_element_type_name": metadata_element_type_name,
"metadata_element_subtypes": metadata_element_subtypes,
"graph_query_depth": graph_query_depth,
"start_from": start_from,
Expand Down
12 changes: 0 additions & 12 deletions pyegeria/omvs/solution_architect.py
Original file line number Diff line number Diff line change
Expand Up @@ -672,14 +672,6 @@ def _get_component_rel_elements_dict(self, el_struct: dict)-> dict | str:
parent_qnames.append(comp['relatedElement']['properties'].get("qualifiedName", ""))
parent_names.append(comp['relatedElement']['properties'].get('displayName', ""))

sub_components = el_struct.get("subComponents", {})
if sub_components:
for sub_component in sub_components:
sub_component_guids.append(sub_component['elementHeader']['guid'])
sub_component_qnames.append(sub_component['properties'].get("qualifiedName", ""))
sub_component_names.append(sub_component['properties'].get('displayName', ""))


mermaid = el_struct.get("mermaidGraph", {})

return {"in_components_guids": parent_guids,
Expand Down Expand Up @@ -5184,10 +5176,6 @@ def get_component_related_elements(self, guid: str, **kwargs) -> dict:
guid = comp['relatedElement']['elementHeader'].get('guid', None)
sub_component_guids.append(guid)

sub_components = response.get("subComponents",{})
for sub_component in sub_components:
sub_component_guids.append(sub_component["elementHeader"]["guid"])

parent_components = response.get('usedInSolutionComponents', None)
if parent_components is not None:
for comp in parent_components:
Expand Down
2 changes: 1 addition & 1 deletion pyegeria/omvs/subject_area.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ async def _async_get_subject_areas_by_name(
url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/subject-area/collections/by-name"
params = {
"filter_string": name,
"metadata_element_type": metadata_element_type_name,
"metadata_element_type_name": metadata_element_type_name,
"metadata_element_subtypes": metadata_element_subtypes,
"include_only_relationships": include_only_relationships,
"skip_relationships": skip_relationships,
Expand Down
2 changes: 1 addition & 1 deletion pyegeria/omvs/time_keeper.py
Original file line number Diff line number Diff line change
Expand Up @@ -1356,7 +1356,7 @@ async def _async_get_context_events_by_name(
url = f"{self.platform_url}/servers/{self.view_server}/api/open-metadata/time-keeper/context-events/by-name"
params = {
"filter_string": name,
"metadata_element_type": metadata_element_type_name,
"metadata_element_type_name": metadata_element_type_name,
"metadata_element_subtypes": metadata_element_subtypes,
"include_only_relationships": include_only_relationships,
"skip_relationships": skip_relationships,
Expand Down
121 changes: 121 additions & 0 deletions tests/micro-tests/test_issue_131_133.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"""
ISSUE-131: 13 *_by_name wrappers passed the type filter to _async_get_name_request as
`metadata_element_type` (the _async_find_request spelling); the helper reads `metadata_element_type_name`,
so the key fell into **kwargs, FilterRequestBody dropped it, and the search ran unfiltered by type.
ISSUE-133: Egeria returns a solution component's children as nestedSolutionComponents; there is no
subComponents field in any REST response.
"""
import json

import pytest

from pyegeria.omvs.actor_manager import ActorManager
from pyegeria.omvs.collection_manager import CollectionManager
from pyegeria.omvs.digital_business import DigitalBusiness
from pyegeria.omvs.governance_officer import GovernanceOfficer
from pyegeria.omvs.location_arena import LocationArena
from pyegeria.omvs.runtime_manager import RuntimeManager
from pyegeria.omvs.solution_architect import SolutionArchitect
from pyegeria.omvs.subject_area import SubjectArea
from pyegeria.omvs.time_keeper import TimeKeeper


class _Response:
def json(self):
return {"relatedHTTPCode": 200, "elements": []}


def _capture(cls, monkeypatch):
monkeypatch.setattr(cls, "check_connection", lambda self: "test")
client = cls("view-server", "https://localhost:1", "user", "pwd")
calls: list = []

async def fake_make_request(method, url, payload=None, *args, **kwargs):
calls.append({"url": url, "body": json.loads(payload) if isinstance(payload, str) else payload})
return _Response()

client._async_make_request = fake_make_request
return client, calls


# --- ISSUE-131 ------------------------------------------------------------------------------

BY_NAME_WRAPPERS = [
(ActorManager, "_async_get_actor_profiles_by_name"),
(ActorManager, "_async_get_actor_roles_by_name"),
(ActorManager, "_async_get_user_identities_by_name"),
(ActorManager, "_async_get_contribution_records_by_name"),
(ActorManager, "_async_get_contact_details_by_name"),
(ActorManager, "_async_get_perspectives_by_name"),
(ActorManager, "_async_get_skills_by_name"),
(CollectionManager, "_async_get_collections_by_name"),
(DigitalBusiness, "_async_get_business_capabilities_by_name"),
(LocationArena, "_async_get_locations_by_name"),
(RuntimeManager, "_async_get_metadata_repository_cohorts_by_name"),
(SubjectArea, "_async_get_subject_areas_by_name"),
(TimeKeeper, "_async_get_context_events_by_name"),
]


@pytest.mark.asyncio
@pytest.mark.parametrize("cls, method", BY_NAME_WRAPPERS)
async def test_by_name_wrappers_send_the_type_filter(cls, method, monkeypatch):
client, calls = _capture(cls, monkeypatch)

await getattr(client, method)("x", metadata_element_type_name="ProbeType")

assert calls[0]["body"]["metadataElementTypeName"] == "ProbeType"


@pytest.mark.asyncio
async def test_name_request_accepts_the_find_request_spelling(monkeypatch):
client, calls = _capture(CollectionManager, monkeypatch)

await client._async_get_name_request("https://localhost:1/x/by-name", _type="T", _gen_output=None,
filter_string="x", metadata_element_type="ProbeType")

assert calls[0]["body"]["metadataElementTypeName"] == "ProbeType"


@pytest.mark.asyncio
async def test_name_request_prefers_the_canonical_spelling(monkeypatch):
client, calls = _capture(CollectionManager, monkeypatch)

await client._async_get_name_request("https://localhost:1/x/by-name", _type="T", _gen_output=None,
filter_string="x", metadata_element_type_name="Canonical",
metadata_element_type="Legacy")

assert calls[0]["body"]["metadataElementTypeName"] == "Canonical"


# --- ISSUE-133 ------------------------------------------------------------------------------

def _component_with_child():
return {
"elementHeader": {"guid": "parent-guid"},
"properties": {"displayName": "Parent", "qualifiedName": "SolutionComponent::Parent"},
"nestedSolutionComponents": [{
"relatedElement": {
"elementHeader": {"guid": "child-guid"},
"properties": {"displayName": "Child", "qualifiedName": "SolutionComponent::Child"},
}
}],
}


def test_governance_officer_reads_nested_solution_components(monkeypatch):
monkeypatch.setattr(GovernanceOfficer, "check_connection", lambda self: "test")
client = GovernanceOfficer("view-server", "https://localhost:1", "user", "pwd")

props = client._extract_solution_components_properties(_component_with_child())

assert props["sub_components"].strip() == "Child"


def test_solution_architect_reads_nested_solution_components(monkeypatch):
monkeypatch.setattr(SolutionArchitect, "check_connection", lambda self: "test")
client = SolutionArchitect("view-server", "https://localhost:1", "user", "pwd")

rel = client._get_component_rel_elements_dict(_component_with_child())

assert rel["sub_component_guids"] == ["child-guid"]
Loading