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
26 changes: 23 additions & 3 deletions PYEGERIA_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ they're holding real port materialization until this exists server-side

### ISSUE-165: 19 SDK methods called endpoints Egeria doesn't serve: 13 removed upstream, 6 never existed (2 since repointed)

**Layer:** Pyegeria · **Status:** open, documented, not changed (owner to decide: remove/deprecate, repoint, or raise with the Egeria team) · **Found:** 2026-10-23 by `scripts/omvs_audit.py` JAVA-MISSING (ISSUE-164).
**Layer:** Pyegeria · **Status:** replacements implemented as deprecated wrappers and verified live (branch `fix/issue-165-replacements`); `get_platform_organization` still open · **Found:** 2026-10-23 by `scripts/omvs_audit.py` JAVA-MISSING (ISSUE-164).

**Validation.** Each URL was checked three ways:
1. **No Spring mapping** in Egeria `main` 4486d1a7f1 (2026-10-23), the checkout `omvs_audit.py` reads.
Expand Down Expand Up @@ -319,10 +319,30 @@ Three other JAVA-MISSING rows are served live, so they are Java-scan false posit

**Every call above returns a 404 today.** Before ISSUE-164, pyegeria reported a 404 as "unable to connect to the Egeria platform", so a caller could not tell "this API is gone" from "the server is down".

**Egeria team's answer (Mandy Chessell, 2026-10-23), and what pyegeria now does** (branch `fix/issue-165-replacements`). Each obsolete method is now a deprecated wrapper (`DeprecationWarning`) that delegates to the replacement, following the ISSUE-153 pattern; removal comes in a later release:

| Obsolete | Why (Mandy) | Now delegates to |
|---|---|---|
| analysis-report create/update/delete/get/find | SurveyReport now inherits from Report, so it is an Asset | `AssetMaker.create_asset(["SurveyReportProperties"])` / `update_asset` / `delete_asset` / `get_asset_by_guid` / `get_assets_by_name` / `find_assets`, with type `SurveyReport`. A body naming `AnalysisReportProperties` is rewritten to `SurveyReportProperties`. |
| `link/detach_analysis_report_to/from_asset` | use the ReportSubject relationship, `/elements/{subjectGUID}/reports/{reportGUID}/attach` | `AssetMaker.link_report_subject` / `unlink_report_subject(asset_guid, report_guid)` |
| `add/remove_element_to/from_subject_area` | SubjectArea now inherits from Collection; use collection membership | `CollectionManager.add_to_collection` / `remove_from_collection(subject_area_guid, element_guid)`. The old methods never took the subject area, so they gain an optional `subject_area_guid`; without it they raise an error naming the replacement. |
| `find_governance_action_processes` | GovernanceActionProcess now inherits from GovernanceDefinition | `find_governance_definitions(..., metadata_element_type="GovernanceActionProcess")` |
| `get_gov_def_in_context` | no longer needed: OpenMetadataRootElement returns the context by default | `get_governance_definition_by_guid`. Dr.Egeria's `View Governance Definition Context` calls it directly. |
| data-classification create/update/link/detach | "probably the old data class operations", now data value specifications (the common root of DataClass and DataGrain); **Mandy was not certain** | `create/update_data_value_specification` (a body naming `DataClassificationProperties` is rewritten to `DataClassProperties`) and `link/detach_data_value_assignment(data_field_guid, spec_guid)` (`DataValueAssignmentProperties`). A best-effort mapping. |

Still open: `get_platform_organization` (removed in `816076ab50`; not covered by the answer). Tests: `tests/micro-tests/test_issue165_replacements.py` (8).

**Live on quickstart 9443, 2026-10-24** (Egeria 81beb8e5 after the reset; one peer-cleared run as `peterprofile` with throwaway `pyeg-test::issue165::` elements, all 5 deleted and read back as NotFound by GUID, and the prefix find was empty afterwards). Each wrapper reached its replacement and the replacement had the intended effect:
- `find_governance_action_processes("*")` returned 100 GovernanceActionProcess-family elements (GovernanceActionProcess plus its Provisioning/Deleting/Subscribing subtypes).
- `get_gov_def_in_context` returned the same element as `get_governance_definition_by_guid`, including `relatedActions`, `associatedGovernanceActions` and `subscribers`, which confirms Mandy's point that the context now comes by default.
- `add_element_to_subject_area` without `subject_area_guid` raised the guidance error. With it, the term was listed in the subject area's collection members; after `remove_element_from_subject_area` the subject area had no members.
- `create_analysis_report` created a `SurveyReport`; `get_analysis_report_by_guid` and `find_analysis_reports` returned it. After `link_analysis_report_to_asset(report, term)` the report showed the term under `reportSubjects`; after the detach it didn't.
- `create_data_classification` created a `DataClass`. After `link_data_classification_to_data_field` the field showed it under `assignedDataValueSpecifications`; after the detach it didn't.

**Proposed (not made):**
1. ~~Repoint `link/detach_subject_area_hierarchy`~~ (done).
2. Mark the analysis-report, data-classification and `get_gov_def_in_context` methods deprecated, raising a clear "not supported by Egeria" error, before removing them in a later release. Dr.Egeria/CLI usage needs checking first.
3. Ask the Egeria team for the intended replacements for subject-area membership, governance-action-process search and the platform organization name.
2. ~~Mark the obsolete methods deprecated~~ (done, as delegating wrappers rather than errors, per the Egeria team's answer).
3. ~~Ask the Egeria team for the intended replacements~~ (answered, except the platform organization name).

---

Expand Down
4 changes: 3 additions & 1 deletion md_processing/v2/governance.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,9 @@ async def apply_changes(self) -> str:
return self.command.raw_block

body = set_update_body(self.command.object_type, attributes)
struct = await self.client._async_get_gov_def_in_context(guid, body=body, output_format=output_format)
# get_governance_definition_by_guid returns the context by default; the old
# retrieve-context endpoint never existed on Egeria 6 (ISSUE-165).
struct = await self.client._async_get_governance_definition_by_guid(guid, body=body, output_format=output_format)

if output_format.upper() == "DICT":
return f"\n# Context Graph for {guid}\n\n```json\n{json.dumps(struct, indent=4)}\n```"
Expand Down
57 changes: 42 additions & 15 deletions pyegeria/omvs/classification_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12290,11 +12290,38 @@ def clear_semantic_assignment_classification(
)
)

# Egeria removed elements/{guid}/subject-area-member when SubjectArea became a Collection
# (Egeria 6a49a3e23f; ISSUE-165): membership is now the CollectionMembership relationship, which
# needs the subject area's GUID. These methods are deprecated; pass subject_area_guid to have
# them delegate, or call CollectionManager.add_to_collection / remove_from_collection directly.

def _collection_client(self):
from pyegeria.omvs.collection_manager import CollectionManager
client = getattr(self, "_collection_mgr", None)
if client is None:
client = CollectionManager(self.view_server, self.platform_url, self.user_id, self.user_pwd,
token=self.token, timeout=getattr(self, "timeout", None))
self._collection_mgr = client
return client

@staticmethod
def _subject_area_moved(name: str, use: str, subject_area_guid: Optional[str]) -> None:
import warnings
warnings.warn(f"ClassificationExplorer.{name} is deprecated; subject-area membership is now collection "
f"membership - use CollectionManager.{use}(subject_area_guid, element_guid).",
DeprecationWarning, stacklevel=3)
if not subject_area_guid:
raise PyegeriaInvalidParameterException(additional_info={
"reason": f"{name}: Egeria no longer has a subject-area-member classification. Pass "
f"subject_area_guid (membership is CollectionMembership on the subject area) or call "
f"CollectionManager.{use}(subject_area_guid, element_guid)."})

async def _async_add_element_to_subject_area(
self,
element_guid: str,
body: dict | NewClassificationRequestBody,
timeout: int = default_timeout,
subject_area_guid: Optional[str] = None,
) -> None:
"""
Classify the element to assert that the definitions it represents are part of a subject area definition.
Expand Down Expand Up @@ -12338,19 +12365,20 @@ async def _async_add_element_to_subject_area(
}
"""

url = (
f"{self.classification_command_root}/elements/{element_guid}/subject-area-member"
)

await self._async_new_classification_request(
url, prop=["SubjectAreaMemberProperties"], body=body
)
self._subject_area_moved("add_element_to_subject_area", "add_to_collection", subject_area_guid)
# The old body carried SubjectAreaMemberProperties (gone); membership takes no properties here.
rel_body = None
if isinstance(body, dict):
rel_body = {k: v for k, v in body.items() if k not in ("class", "properties")}
rel_body["class"] = "NewRelationshipRequestBody"
await self._collection_client()._async_add_to_collection(subject_area_guid, element_guid, rel_body)

def add_element_to_subject_area(
self,
element_guid: str,
body: dict | NewClassificationRequestBody,
timeout: int = default_timeout,
subject_area_guid: Optional[str] = None,
) -> None:
"""
Classify the element to assert that the definitions it represents are part of a subject area definition.
Expand Down Expand Up @@ -12399,6 +12427,7 @@ def add_element_to_subject_area(
element_guid,
body,
timeout,
subject_area_guid,
)
)

Expand All @@ -12409,6 +12438,7 @@ async def _async_remove_element_from_subject_area(
for_duplicate_processing: bool = False,
effective_time: Optional[str] = None,
timeout: int = default_timeout,
subject_area_guid: Optional[str] = None,
) -> None:
"""
Remove the subject area designation from the identified element. Async version.
Expand Down Expand Up @@ -12444,15 +12474,10 @@ async def _async_remove_element_from_subject_area(

"""

url = (
f"{self.classification_command_root}/elements/{element_guid}/subject-area-member"
f"/remove"
)

body = {"class": "DeleteClassificationRequestBody", "effectiveTime": effective_time, "forLineage": for_lineage,
self._subject_area_moved("remove_element_from_subject_area", "remove_from_collection", subject_area_guid)
body = {"class": "DeleteRelationshipRequestBody", "effectiveTime": effective_time, "forLineage": for_lineage,
"forDuplicateProcessing": for_duplicate_processing}

await self._async_delete_classification_request(url, body)
await self._collection_client()._async_remove_from_collection(subject_area_guid, element_guid, body)

def remove_element_from_subject_area(
self,
Expand All @@ -12461,6 +12486,7 @@ def remove_element_from_subject_area(
for_duplicate_processing: bool = False,
effective_time: Optional[str] = None,
timeout: int = default_timeout,
subject_area_guid: Optional[str] = None,
) -> None:
"""
Remove the subject area designation from the identified element.
Expand Down Expand Up @@ -12506,6 +12532,7 @@ def remove_element_from_subject_area(
for_duplicate_processing,
effective_time,
timeout,
subject_area_guid,
)
)

Expand Down
57 changes: 42 additions & 15 deletions pyegeria/omvs/data_designer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3294,6 +3294,27 @@ def get_data_grain_by_guid(self, guid: str = None, element_type: Optional[str] =
self._async_get_data_value_specification_by_guid(
guid=guid, element_type=element_type or "DataGrain", body=body, output_format=output_format, report_spec=report_spec, **kwargs))

@staticmethod
def _warn_data_classification(name: str, use: str) -> None:
import warnings
warnings.warn(f"DataDesigner.{name} is deprecated; Egeria has no data-classification API - use {use} "
"(data value specifications cover DataClass and DataGrain).", DeprecationWarning, stacklevel=3)

@staticmethod
def _as_data_class(body):
# DataClassificationProperties never existed in Egeria; the nearest type is DataClass.
if isinstance(body, dict) and isinstance(body.get("properties"), dict) \
and body["properties"].get("class") == "DataClassificationProperties":
body = {**body, "properties": {**body["properties"], "class": "DataClassProperties"}}
return body

@staticmethod
def _as_value_assignment(body):
if isinstance(body, dict) and isinstance(body.get("properties"), dict) \
and body["properties"].get("class") == "DataFieldClassificationProperties":
body = {**body, "properties": {**body["properties"], "class": "DataValueAssignmentProperties"}}
return body

@dynamic_catch
async def _async_create_data_classification(self, body: dict | NewElementRequestBody) -> str:
"""
Expand Down Expand Up @@ -3373,9 +3394,11 @@ async def _async_create_data_classification(self, body: dict | NewElementRequest

"""

url = f"{self.ref_data_designer_command_base}/data-classifications"

return await self._async_create_element_body_request(url, ["DataClassification"], body)
# Egeria has no data-classifications endpoints (never in 6.x Java; ISSUE-165). Per the Egeria team
# 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("create_data_classification", "create_data_value_specification")
return await self._async_create_data_value_specification(self._as_data_class(body))

@dynamic_catch
def create_data_classification(self, body: dict | NewElementRequestBody) -> str:
Expand Down Expand Up @@ -3525,9 +3548,11 @@ async def _async_update_data_classification(self, data_classification_guid: str,

"""

url = f"{self.ref_data_designer_command_base}/data-classifications/{data_classification_guid}/update"
await self._async_update_element_body_request(url, ["DataClassification"], body)
logger.info(f"Data classification {data_classification_guid} updated.")
# Egeria has no data-classifications endpoints (never in 6.x Java; ISSUE-165). Per the Egeria team
# 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("update_data_classification", "update_data_value_specification")
await self._async_update_data_value_specification(data_classification_guid, self._as_data_class(body))

@dynamic_catch
def update_data_classification(self, data_classification_guid: str, body: dict | UpdateElementRequestBody) -> None:
Expand Down Expand Up @@ -3646,10 +3671,12 @@ async def _async_link_data_classification_to_data_field(self, data_classificatio

"""

url = (f"{self.ref_data_designer_command_base}/data-classifications/{data_classification_guid}"
f"/data-fields/{data_field_guid}/attach")
await self._async_new_relationship_request(url, ["DataFieldClassificationProperties"], body)
logger.info(f"Data classification {data_classification_guid} linked to data field {data_field_guid}.")
# Egeria has no data-classifications endpoints (never in 6.x Java; ISSUE-165). Per the Egeria team
# 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,
self._as_value_assignment(body))


@dynamic_catch
Expand Down Expand Up @@ -3751,11 +3778,11 @@ async def _async_detach_data_classification_from_data_field(self, data_classific

"""

url = (f"{self.ref_data_designer_command_base}/data-classifications/{data_classification_guid}"
f"/data-fields/{data_field_guid}/detach")

await self._async_delete_relationship_request(url, body, cascade_delete)
logger.info(f"Data classification {data_classification_guid} detached from data field {data_field_guid}.")
# Egeria has no data-classifications endpoints (never in 6.x Java; ISSUE-165). Per the Egeria team
# 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("detach_data_classification_from_data_field", "detach_data_value_assignment(data_field_guid, spec_guid)")
await self._async_detach_data_value_assignment(data_field_guid, data_classification_guid, body)


@dynamic_catch
Expand Down
Loading
Loading