|
17 | 17 | SearchStringRequestBody, |
18 | 18 | FilterRequestBody, GetRequestBody, ResultsRequestBody, |
19 | 19 | SearchStringRequestBody as SearchStringBody, DeleteElementRequestBody, |
20 | | - DeleteRelationshipRequestBody) |
| 20 | + DeleteRelationshipRequestBody, |
| 21 | + OpenMetadataEnumDef, OpenMetadataTypeDef, OpenMetadataTypeDefPatch) |
21 | 22 | from pyegeria.core.utils import body_slimmer, dynamic_catch |
22 | 23 | from pyegeria.core._server_client import ServerClient, max_paging_size |
23 | 24 | from pyegeria.core._globals import default_timeout, NO_ELEMENTS_FOUND |
@@ -4375,3 +4376,209 @@ def get_property_comparison_operator_list(self) -> list: |
4375 | 4376 | """Retrieve the list of valid property comparison operators.""" |
4376 | 4377 | loop = asyncio.get_event_loop() |
4377 | 4378 | return loop.run_until_complete(self._async_get_property_comparison_operator_list()) |
| 4379 | + |
| 4380 | + # |
| 4381 | + # Maintain the open metadata types that are defined through the API. These are validated against the types |
| 4382 | + # already defined, stored in the metadata access store's local repository, and announced to the rest of the |
| 4383 | + # cohort. Types from the open metadata archives cannot be changed this way. (Egeria-api-metadata-expert.http) |
| 4384 | + # |
| 4385 | + |
| 4386 | + async def _async_type_def_request(self, url: str, body: dict | Any, model: type, |
| 4387 | + params: dict | None = None) -> Any: |
| 4388 | + """POST a type-definition body (validated through `model`, with undeclared bean fields passed |
| 4389 | + through) and return the response's "guid" if it has one, else the whole response payload.""" |
| 4390 | + if isinstance(body, model): |
| 4391 | + validated = body |
| 4392 | + elif isinstance(body, dict): |
| 4393 | + validated = self._validate_body(model.model_validate, body) |
| 4394 | + else: |
| 4395 | + raise ValueError(f"body must be a dict or {model.__name__}") |
| 4396 | + response = await self._async_make_request( |
| 4397 | + "POST", url, validated.model_dump_json(indent=2, exclude_none=True), params=params) |
| 4398 | + payload = response.json() |
| 4399 | + return payload.get("guid", payload) if isinstance(payload, dict) else payload |
| 4400 | + |
| 4401 | + @dynamic_catch |
| 4402 | + async def _async_add_enum_def(self, body: dict | OpenMetadataEnumDef) -> str | dict: |
| 4403 | + """ |
| 4404 | + Add a new enum definition, which can then be used as the type of attributes in new type definitions. |
| 4405 | + Async version. |
| 4406 | +
|
| 4407 | + .. warning:: |
| 4408 | + An enum added through the API cannot be removed again: ``delete_enum_def`` currently fails against |
| 4409 | + Egeria with a 500 "unknown TypeDef" (an Egeria server bug, ISSUE-125, reproduced on Egeria main |
| 4410 | + 449ad06894, and the enum survives a restart). Do not create throwaway enums on a shared platform. |
| 4411 | +
|
| 4412 | + Parameters |
| 4413 | + ---------- |
| 4414 | + body : dict | OpenMetadataEnumDef |
| 4415 | + The enum definition. |
| 4416 | +
|
| 4417 | + Returns |
| 4418 | + ------- |
| 4419 | + str | dict |
| 4420 | + The unique identifier (GUID) of the new enum definition, or the raw response if it carries none. |
| 4421 | +
|
| 4422 | + Notes |
| 4423 | + ----- |
| 4424 | + Sample JSON body: |
| 4425 | + { |
| 4426 | + "class" : "OpenMetadataEnumDef", |
| 4427 | + "name" : "CuisineType", |
| 4428 | + "description" : "The style of cooking that a recipe belongs to.", |
| 4429 | + "elementDefs" : [ |
| 4430 | + { "ordinal" : 0, "value" : "Unclassified", "description" : "The cuisine has not been recorded." }, |
| 4431 | + { "ordinal" : 99, "value" : "Other", "description" : "Another cuisine." } |
| 4432 | + ], |
| 4433 | + "defaultValue" : { "ordinal" : 0, "value" : "Unclassified", "description" : "..." } |
| 4434 | + } |
| 4435 | + """ |
| 4436 | + url = f"{self.command_root}/open-metadata-attribute-types/enum-defs" |
| 4437 | + return await self._async_type_def_request(url, body, OpenMetadataEnumDef) |
| 4438 | + |
| 4439 | + @dynamic_catch |
| 4440 | + def add_enum_def(self, body: dict | OpenMetadataEnumDef) -> str | dict: |
| 4441 | + """Add a new enum definition, which can then be used as the type of attributes in new type definitions. |
| 4442 | + Note: it cannot be removed again through the API (ISSUE-125); see the async version's warning.""" |
| 4443 | + loop = asyncio.get_event_loop() |
| 4444 | + return loop.run_until_complete(self._async_add_enum_def(body)) |
| 4445 | + |
| 4446 | + @dynamic_catch |
| 4447 | + async def _async_add_type_def(self, body: dict | OpenMetadataTypeDef) -> str | dict: |
| 4448 | + """ |
| 4449 | + Add a new type definition for an entity, relationship or classification. Links to other types - the |
| 4450 | + supertype, attribute types, relationship ends - only need the name of the type. Async version. |
| 4451 | +
|
| 4452 | + .. note:: |
| 4453 | + Verified against a live server for entity types (with primitive and enum attributes). Relationship and |
| 4454 | + classification definitions go through the same code and models but have not been exercised live. |
| 4455 | +
|
| 4456 | + Parameters |
| 4457 | + ---------- |
| 4458 | + body : dict | OpenMetadataTypeDef |
| 4459 | + The type definition; `class` is OpenMetadataEntityDef, OpenMetadataRelationshipDef or |
| 4460 | + OpenMetadataClassificationDef. |
| 4461 | +
|
| 4462 | + Returns |
| 4463 | + ------- |
| 4464 | + str | dict |
| 4465 | + The unique identifier (GUID) of the new type definition, or the raw response if it carries none. |
| 4466 | +
|
| 4467 | + Notes |
| 4468 | + ----- |
| 4469 | + Sample JSON body: |
| 4470 | + { |
| 4471 | + "class" : "OpenMetadataEntityDef", |
| 4472 | + "name" : "Recipe", |
| 4473 | + "description" : "A description of how to prepare a dish.", |
| 4474 | + "superType" : { "name" : "Referenceable" }, |
| 4475 | + "attributeDefinitions" : [ |
| 4476 | + { "attributeName" : "cuisine", |
| 4477 | + "attributeType" : { "class" : "OpenMetadataEnumDef", "name" : "CuisineType" }, |
| 4478 | + "attributeDescription" : "The style of cooking." }, |
| 4479 | + { "attributeName" : "servings", |
| 4480 | + "attributeType" : { "class" : "OpenMetadataPrimitiveDef", "name" : "int" }, |
| 4481 | + "attributeDescription" : "The number of people the recipe serves." } |
| 4482 | + ] |
| 4483 | + } |
| 4484 | + """ |
| 4485 | + url = f"{self.command_root}/open-metadata-types" |
| 4486 | + return await self._async_type_def_request(url, body, OpenMetadataTypeDef) |
| 4487 | + |
| 4488 | + @dynamic_catch |
| 4489 | + def add_type_def(self, body: dict | OpenMetadataTypeDef) -> str | dict: |
| 4490 | + """Add a new type definition for an entity, relationship or classification.""" |
| 4491 | + loop = asyncio.get_event_loop() |
| 4492 | + return loop.run_until_complete(self._async_add_type_def(body)) |
| 4493 | + |
| 4494 | + @dynamic_catch |
| 4495 | + async def _async_update_type_def(self, body: dict | OpenMetadataTypeDefPatch) -> None: |
| 4496 | + """ |
| 4497 | + Update a type definition that was added through the API. The patch names the version it applies to |
| 4498 | + and carries only what changes. Async version. |
| 4499 | +
|
| 4500 | + .. warning:: |
| 4501 | + Against Egeria as of 2026-10-05 (including its main branch) every patch is rejected by the server |
| 4502 | + with OMRS-REPOSITORY-400-069, "mandatory field updatedBy set to null": the server builds the patch |
| 4503 | + with a converter that is never given the calling user. Nothing a client sends can supply it. See |
| 4504 | + PYEGERIA_ISSUES.md, ISSUE-124. This method matches the documented API and will work once that |
| 4505 | + is fixed upstream. |
| 4506 | +
|
| 4507 | + Parameters |
| 4508 | + ---------- |
| 4509 | + body : dict | OpenMetadataTypeDefPatch |
| 4510 | + The patch. |
| 4511 | +
|
| 4512 | + Notes |
| 4513 | + ----- |
| 4514 | + Sample JSON body: |
| 4515 | + { |
| 4516 | + "class" : "OpenMetadataTypeDefPatch", |
| 4517 | + "typeDefGUID" : "add guid here", |
| 4518 | + "typeDefName" : "Recipe", |
| 4519 | + "applyToVersion" : 1, |
| 4520 | + "attributeDefinitions" : [ |
| 4521 | + { "attributeName" : "preparationTimeMinutes", |
| 4522 | + "attributeType" : { "class" : "OpenMetadataPrimitiveDef", "name" : "int" }, |
| 4523 | + "attributeDescription" : "How long the recipe takes to prepare." } |
| 4524 | + ] |
| 4525 | + } |
| 4526 | + """ |
| 4527 | + url = f"{self.command_root}/open-metadata-types/update" |
| 4528 | + await self._async_type_def_request(url, body, OpenMetadataTypeDefPatch) |
| 4529 | + |
| 4530 | + @dynamic_catch |
| 4531 | + def update_type_def(self, body: dict | OpenMetadataTypeDefPatch) -> None: |
| 4532 | + """Update a type definition that was added through the API.""" |
| 4533 | + loop = asyncio.get_event_loop() |
| 4534 | + return loop.run_until_complete(self._async_update_type_def(body)) |
| 4535 | + |
| 4536 | + @dynamic_catch |
| 4537 | + async def _async_delete_type_def(self, type_def_guid: str, type_def_name: str) -> None: |
| 4538 | + """ |
| 4539 | + Delete a type definition that was added through the API. This fails while there are any instances of |
| 4540 | + the type, including soft-deleted ones, or while another type definition refers to it. Async version. |
| 4541 | +
|
| 4542 | + Parameters |
| 4543 | + ---------- |
| 4544 | + type_def_guid : str |
| 4545 | + Unique identifier of the type definition. |
| 4546 | + type_def_name : str |
| 4547 | + Name of the type definition (the server checks it against the GUID). |
| 4548 | + """ |
| 4549 | + url = f"{self.command_root}/open-metadata-types/guid/{type_def_guid}/delete" |
| 4550 | + await self._async_make_request("POST", url, params={"typeDefName": type_def_name}) |
| 4551 | + |
| 4552 | + @dynamic_catch |
| 4553 | + def delete_type_def(self, type_def_guid: str, type_def_name: str) -> None: |
| 4554 | + """Delete a type definition that was added through the API.""" |
| 4555 | + loop = asyncio.get_event_loop() |
| 4556 | + return loop.run_until_complete(self._async_delete_type_def(type_def_guid, type_def_name)) |
| 4557 | + |
| 4558 | + @dynamic_catch |
| 4559 | + async def _async_delete_enum_def(self, enum_def_guid: str, enum_def_name: str) -> None: |
| 4560 | + """ |
| 4561 | + Delete an enum definition that was added through the API. This fails while any type definition has an |
| 4562 | + attribute of this type. Async version. |
| 4563 | +
|
| 4564 | + .. warning:: |
| 4565 | + Observed live (2026-10-05) to fail with a 500, OMRS-CONTENT-MANAGER-500-001 "unknown TypeDef", even |
| 4566 | + after the only type using the enum had been deleted and while the enum was still listed by |
| 4567 | + ``get_attribute_types``: an enum added through the API could not be removed again. Delete enum |
| 4568 | + definitions only if you can live with that. See PYEGERIA_ISSUES.md, ISSUE-125. |
| 4569 | +
|
| 4570 | + Parameters |
| 4571 | + ---------- |
| 4572 | + enum_def_guid : str |
| 4573 | + Unique identifier of the enum definition. |
| 4574 | + enum_def_name : str |
| 4575 | + Name of the enum definition (the server checks it against the GUID). |
| 4576 | + """ |
| 4577 | + url = f"{self.command_root}/open-metadata-attribute-types/enum-defs/guid/{enum_def_guid}/delete" |
| 4578 | + await self._async_make_request("POST", url, params={"enumDefName": enum_def_name}) |
| 4579 | + |
| 4580 | + @dynamic_catch |
| 4581 | + def delete_enum_def(self, enum_def_guid: str, enum_def_name: str) -> None: |
| 4582 | + """Delete an enum definition that was added through the API.""" |
| 4583 | + loop = asyncio.get_event_loop() |
| 4584 | + return loop.run_until_complete(self._async_delete_enum_def(enum_def_guid, enum_def_name)) |
0 commit comments