pyegeria 5.3.9.9.3__py3-none-any.whl → 5.5.3.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyegeria might be problematic. Click here for more details.
- commands/__init__.py +24 -0
- commands/cat/Dr-Egeria_md-orig.py +2 -2
- commands/cat/__init__.py +1 -17
- commands/cat/collection_actions.py +197 -0
- commands/cat/dr_egeria_command_help.py +372 -0
- commands/cat/dr_egeria_jupyter.py +7 -7
- commands/cat/dr_egeria_md.py +27 -182
- commands/cat/exp_list_glossaries.py +11 -14
- commands/cat/get_asset_graph.py +37 -267
- commands/cat/{get_collection.py → get_collection_tree.py} +10 -18
- commands/cat/get_project_dependencies.py +14 -14
- commands/cat/get_project_structure.py +15 -14
- commands/cat/get_tech_type_elements.py +16 -116
- commands/cat/glossary_actions.py +145 -298
- commands/cat/list_assets.py +3 -11
- commands/cat/list_cert_types.py +17 -63
- commands/cat/list_collections.py +46 -138
- commands/cat/list_deployed_catalogs.py +15 -27
- commands/cat/list_deployed_database_schemas.py +27 -43
- commands/cat/list_deployed_databases.py +16 -31
- commands/cat/list_deployed_servers.py +35 -54
- commands/cat/list_glossaries.py +18 -17
- commands/cat/list_projects.py +10 -12
- commands/cat/list_tech_type_elements.py +21 -37
- commands/cat/list_tech_types.py +13 -25
- commands/cat/list_terms.py +38 -79
- commands/cat/list_todos.py +4 -11
- commands/cat/list_user_ids.py +3 -10
- commands/cat/my_reports.py +559 -0
- commands/cat/run_report.py +394 -0
- commands/cat/run_report_orig.py +528 -0
- commands/cli/egeria.py +222 -247
- commands/cli/egeria_cat.py +68 -81
- commands/cli/egeria_my.py +13 -0
- commands/cli/egeria_ops.py +69 -74
- commands/cli/egeria_tech.py +17 -93
- commands/cli/ops_config.py +3 -6
- commands/{cat/list_categories.py → deprecated/list_data_designer.py} +53 -64
- commands/{cat/list_data_structures.py → deprecated/list_data_structures_full.py} +3 -6
- commands/deprecated/old_get_asset_graph.py +315 -0
- commands/my/__init__.py +0 -2
- commands/my/list_my_profile.py +27 -34
- commands/my/list_my_roles.py +1 -7
- commands/my/monitor_my_todos.py +1 -7
- commands/my/monitor_open_todos.py +6 -7
- commands/my/todo_actions.py +4 -5
- commands/ops/__init__.py +0 -2
- commands/ops/gov_server_actions.py +17 -21
- commands/ops/list_archives.py +17 -38
- commands/ops/list_catalog_targets.py +33 -40
- commands/ops/load_archive.py +35 -26
- commands/ops/{monitor_engine_activity_c.py → monitor_active_engine_activity.py} +51 -82
- commands/ops/{monitor_integ_daemon_status.py → monitor_daemon_status.py} +35 -55
- commands/ops/monitor_engine_activity.py +79 -77
- commands/ops/{monitor_gov_eng_status.py → monitor_engine_status.py} +10 -7
- commands/ops/monitor_platform_status.py +38 -50
- commands/ops/monitor_server_startup.py +6 -11
- commands/ops/monitor_server_status.py +7 -11
- commands/ops/orig_monitor_server_list.py +8 -8
- commands/ops/orig_monitor_server_status.py +1 -5
- commands/ops/refresh_integration_daemon.py +5 -5
- commands/ops/restart_integration_daemon.py +5 -5
- commands/ops/table_integ_daemon_status.py +6 -6
- commands/ops/x_engine_actions.py +7 -7
- commands/tech/__init__.py +0 -2
- commands/tech/{generic_actions.py → element_actions.py} +6 -11
- commands/tech/get_element_info.py +20 -29
- commands/tech/get_guid_info.py +23 -42
- commands/tech/get_tech_details.py +20 -35
- commands/tech/get_tech_type_template.py +28 -39
- commands/tech/list_all_om_type_elements.py +24 -30
- commands/tech/list_all_om_type_elements_x.py +22 -28
- commands/tech/list_all_related_elements.py +19 -28
- commands/tech/list_anchored_elements.py +22 -30
- commands/tech/list_asset_types.py +19 -24
- commands/tech/list_elements_by_classification_by_property_value.py +26 -32
- commands/tech/list_elements_by_property_value.py +19 -25
- commands/tech/list_elements_by_property_value_x.py +20 -28
- commands/tech/list_elements_for_classification.py +28 -41
- commands/tech/list_gov_action_processes.py +16 -27
- commands/tech/list_information_supply_chains.py +22 -30
- commands/tech/list_registered_services.py +14 -26
- commands/tech/list_related_elements_with_prop_value.py +15 -25
- commands/tech/list_related_specification.py +1 -4
- commands/tech/list_relationship_types.py +15 -25
- commands/tech/list_relationships.py +20 -36
- commands/tech/list_solution_blueprints.py +28 -33
- commands/tech/list_solution_components.py +23 -29
- commands/tech/list_solution_roles.py +21 -32
- commands/tech/list_tech_templates.py +51 -54
- commands/tech/list_valid_metadata_values.py +5 -9
- commands/tech/table_tech_templates.py +2 -6
- commands/tech/x_list_related_elements.py +1 -4
- examples/GeoSpatial Products Example.py +524 -0
- examples/Jupyter Notebooks/P-egeria-server-config.ipynb +2137 -0
- examples/Jupyter Notebooks/README.md +2 -0
- examples/Jupyter Notebooks/common/P-environment-check.ipynb +115 -0
- examples/Jupyter Notebooks/common/__init__.py +14 -0
- examples/Jupyter Notebooks/common/common-functions.ipynb +4694 -0
- examples/Jupyter Notebooks/common/environment-check.ipynb +52 -0
- examples/Jupyter Notebooks/common/globals.ipynb +184 -0
- examples/Jupyter Notebooks/common/globals.py +154 -0
- examples/Jupyter Notebooks/common/orig_globals.py +152 -0
- examples/format_sets/all_format_sets.json +910 -0
- examples/format_sets/custom_format_sets.json +268 -0
- examples/format_sets/subset_format_sets.json +187 -0
- examples/format_sets_save_load_example.py +291 -0
- examples/jacquard_data_sets.py +129 -0
- examples/output_formats_example.py +193 -0
- examples/test_jacquard_data_sets.py +54 -0
- examples/test_jacquard_data_sets_scenarios.py +94 -0
- md_processing/__init__.py +90 -0
- md_processing/command_dispatcher.py +33 -0
- md_processing/command_mapping.py +221 -0
- md_processing/data/commands/commands_data_designer.json +537 -0
- md_processing/data/commands/commands_external_reference.json +733 -0
- md_processing/data/commands/commands_feedback.json +155 -0
- md_processing/data/commands/commands_general.json +204 -0
- md_processing/data/commands/commands_glossary.json +218 -0
- md_processing/data/commands/commands_governance.json +3678 -0
- md_processing/data/commands/commands_product_manager.json +865 -0
- md_processing/data/commands/commands_project.json +642 -0
- md_processing/data/commands/commands_solution_architect.json +366 -0
- md_processing/data/commands.json +17568 -0
- md_processing/data/commands_working.json +30641 -0
- md_processing/data/gened_report_specs.py +6584 -0
- md_processing/data/generated_format_sets.json +6533 -0
- md_processing/data/generated_format_sets_old.json +4137 -0
- md_processing/data/generated_format_sets_old.py +45 -0
- md_processing/dr_egeria.py +182 -0
- md_processing/md_commands/__init__.py +3 -0
- md_processing/md_commands/data_designer_commands.py +1276 -0
- md_processing/md_commands/ext_ref_commands.py +530 -0
- md_processing/md_commands/feedback_commands.py +726 -0
- md_processing/md_commands/glossary_commands.py +684 -0
- md_processing/md_commands/governance_officer_commands.py +600 -0
- md_processing/md_commands/product_manager_commands.py +1266 -0
- md_processing/md_commands/project_commands.py +383 -0
- md_processing/md_commands/solution_architect_commands.py +1184 -0
- md_processing/md_commands/view_commands.py +295 -0
- md_processing/md_processing_utils/__init__.py +4 -0
- md_processing/md_processing_utils/common_md_proc_utils.py +1249 -0
- md_processing/md_processing_utils/common_md_utils.py +578 -0
- md_processing/md_processing_utils/determine_width.py +103 -0
- md_processing/md_processing_utils/extraction_utils.py +547 -0
- md_processing/md_processing_utils/gen_report_specs.py +643 -0
- md_processing/md_processing_utils/generate_dr_help.py +193 -0
- md_processing/md_processing_utils/generate_md_cmd_templates.py +144 -0
- md_processing/md_processing_utils/generate_md_templates.py +83 -0
- md_processing/md_processing_utils/md_processing_constants.py +1228 -0
- md_processing/md_processing_utils/message_constants.py +19 -0
- pyegeria/__init__.py +201 -443
- pyegeria/core/__init__.py +40 -0
- pyegeria/core/_base_platform_client.py +574 -0
- pyegeria/core/_base_server_client.py +573 -0
- pyegeria/core/_exceptions.py +457 -0
- pyegeria/core/_globals.py +60 -0
- pyegeria/core/_server_client.py +6073 -0
- pyegeria/core/_validators.py +257 -0
- pyegeria/core/config.py +654 -0
- pyegeria/{create_tech_guid_lists.py → core/create_tech_guid_lists.py} +0 -1
- pyegeria/core/load_config.py +37 -0
- pyegeria/core/logging_configuration.py +207 -0
- pyegeria/core/mcp_adapter.py +144 -0
- pyegeria/core/mcp_server.py +212 -0
- pyegeria/core/utils.py +405 -0
- pyegeria/deprecated/__init__.py +0 -0
- pyegeria/{_client.py → deprecated/_client.py} +62 -24
- pyegeria/{_deprecated_gov_engine.py → deprecated/_deprecated_gov_engine.py} +16 -16
- pyegeria/{classification_manager_omvs.py → deprecated/classification_manager_omvs.py} +1988 -1878
- pyegeria/deprecated/output_formatter_with_machine_keys.py +1127 -0
- pyegeria/{runtime_manager_omvs.py → deprecated/runtime_manager_omvs.py} +216 -229
- pyegeria/{valid_metadata_omvs.py → deprecated/valid_metadata_omvs.py} +93 -93
- pyegeria/{x_action_author_omvs.py → deprecated/x_action_author_omvs.py} +2 -3
- pyegeria/egeria_cat_client.py +25 -51
- pyegeria/egeria_client.py +140 -98
- pyegeria/egeria_config_client.py +48 -24
- pyegeria/egeria_tech_client.py +170 -83
- pyegeria/models/__init__.py +150 -0
- pyegeria/models/collection_models.py +168 -0
- pyegeria/models/models.py +654 -0
- pyegeria/omvs/__init__.py +84 -0
- pyegeria/omvs/action_author.py +342 -0
- pyegeria/omvs/actor_manager.py +5980 -0
- pyegeria/omvs/asset_catalog.py +842 -0
- pyegeria/omvs/asset_maker.py +2736 -0
- pyegeria/omvs/automated_curation.py +4403 -0
- pyegeria/omvs/classification_manager.py +11213 -0
- pyegeria/omvs/collection_manager.py +5780 -0
- pyegeria/omvs/community_matters_omvs.py +468 -0
- pyegeria/{core_omag_server_config.py → omvs/core_omag_server_config.py} +157 -157
- pyegeria/{data_designer_omvs.py → omvs/data_designer.py} +1991 -1691
- pyegeria/omvs/data_discovery.py +869 -0
- pyegeria/omvs/data_engineer.py +372 -0
- pyegeria/omvs/digital_business.py +1133 -0
- pyegeria/omvs/external_links.py +1752 -0
- pyegeria/omvs/feedback_manager.py +834 -0
- pyegeria/{full_omag_server_config.py → omvs/full_omag_server_config.py} +73 -69
- pyegeria/omvs/glossary_manager.py +3231 -0
- pyegeria/omvs/governance_officer.py +3009 -0
- pyegeria/omvs/lineage_linker.py +314 -0
- pyegeria/omvs/location_arena.py +1525 -0
- pyegeria/omvs/metadata_expert.py +668 -0
- pyegeria/omvs/metadata_explorer_omvs.py +2943 -0
- pyegeria/omvs/my_profile.py +1042 -0
- pyegeria/omvs/notification_manager.py +358 -0
- pyegeria/omvs/people_organizer.py +394 -0
- pyegeria/{platform_services.py → omvs/platform_services.py} +113 -193
- pyegeria/omvs/product_manager.py +1825 -0
- pyegeria/omvs/project_manager.py +1907 -0
- pyegeria/omvs/reference_data.py +1140 -0
- pyegeria/omvs/registered_info.py +334 -0
- pyegeria/omvs/runtime_manager.py +2817 -0
- pyegeria/omvs/schema_maker.py +446 -0
- pyegeria/{server_operations.py → omvs/server_operations.py} +27 -26
- pyegeria/omvs/solution_architect.py +6490 -0
- pyegeria/omvs/specification_properties.py +37 -0
- pyegeria/omvs/subject_area.py +1042 -0
- pyegeria/omvs/template_manager_omvs.py +236 -0
- pyegeria/omvs/time_keeper.py +1761 -0
- pyegeria/omvs/valid_metadata.py +3221 -0
- pyegeria/omvs/valid_metadata_lists.py +37 -0
- pyegeria/omvs/valid_type_lists.py +37 -0
- pyegeria/view/__init__.py +28 -0
- pyegeria/view/_output_format_models.py +514 -0
- pyegeria/view/_output_formats.py +14 -0
- pyegeria/view/base_report_formats.py +2719 -0
- pyegeria/view/dr_egeria_reports.py +56 -0
- pyegeria/view/format_set_executor.py +397 -0
- pyegeria/{md_processing_utils.py → view/md_processing_utils.py} +5 -5
- pyegeria/{mermaid_utilities.py → view/mermaid_utilities.py} +2 -154
- pyegeria/view/output_formatter.py +1297 -0
- pyegeria-5.5.3.3.dist-info/METADATA +218 -0
- pyegeria-5.5.3.3.dist-info/RECORD +241 -0
- {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.5.3.3.dist-info}/WHEEL +2 -1
- pyegeria-5.5.3.3.dist-info/entry_points.txt +103 -0
- pyegeria-5.5.3.3.dist-info/top_level.txt +4 -0
- commands/cat/.DS_Store +0 -0
- commands/cat/README.md +0 -16
- commands/cli/txt_custom_v2.tcss +0 -19
- commands/my/README.md +0 -17
- commands/ops/README.md +0 -24
- commands/ops/monitor_asset_events.py +0 -108
- commands/tech/README.md +0 -24
- pyegeria/.DS_Store +0 -0
- pyegeria/README.md +0 -35
- pyegeria/_globals.py +0 -47
- pyegeria/_validators.py +0 -385
- pyegeria/asset_catalog_omvs.py +0 -864
- pyegeria/automated_curation_omvs.py +0 -3765
- pyegeria/collection_manager_omvs.py +0 -2744
- pyegeria/dr.egeria spec.md +0 -9
- pyegeria/egeria_my_client.py +0 -56
- pyegeria/feedback_manager_omvs.py +0 -4573
- pyegeria/glossary_browser_omvs.py +0 -3728
- pyegeria/glossary_manager_omvs.py +0 -2440
- pyegeria/m_test.py +0 -118
- pyegeria/md_processing_helpers.py +0 -58
- pyegeria/md_processing_utils_orig.py +0 -1103
- pyegeria/metadata_explorer_omvs.py +0 -2326
- pyegeria/my_profile_omvs.py +0 -1022
- pyegeria/output_formatter.py +0 -389
- pyegeria/project_manager_omvs.py +0 -1933
- pyegeria/registered_info.py +0 -167
- pyegeria/solution_architect_omvs.py +0 -2156
- pyegeria/template_manager_omvs.py +0 -1414
- pyegeria/utils.py +0 -197
- pyegeria-5.3.9.9.3.dist-info/METADATA +0 -72
- pyegeria-5.3.9.9.3.dist-info/RECORD +0 -143
- pyegeria-5.3.9.9.3.dist-info/entry_points.txt +0 -99
- /pyegeria/{_exceptions.py → deprecated/_exceptions.py} +0 -0
- {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.5.3.3.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,1249 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This file contains general utility functions for processing Egeria Markdown
|
|
3
|
+
"""
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import re
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from loguru import logger
|
|
11
|
+
from pydantic import ValidationError
|
|
12
|
+
from rich import print
|
|
13
|
+
from rich.markdown import Markdown
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from pyegeria.core.utils import parse_to_dict
|
|
16
|
+
from pyegeria.core.config import settings
|
|
17
|
+
from md_processing.md_processing_utils.common_md_utils import (get_current_datetime_string, split_tb_string, str_to_bool, )
|
|
18
|
+
from md_processing.md_processing_utils.extraction_utils import (process_simple_attribute, extract_attribute,
|
|
19
|
+
get_element_by_name)
|
|
20
|
+
from md_processing.md_processing_utils.common_md_utils import (update_element_dictionary, set_find_body)
|
|
21
|
+
from md_processing.md_processing_utils.extraction_utils import (extract_command_plus)
|
|
22
|
+
from md_processing.md_processing_utils.md_processing_constants import (get_command_spec, add_default_upsert_attributes,
|
|
23
|
+
add_default_link_attributes)
|
|
24
|
+
from md_processing.md_processing_utils.message_constants import (ERROR, INFO, WARNING, EXISTS_REQUIRED)
|
|
25
|
+
from pyegeria import EgeriaTech, PyegeriaException
|
|
26
|
+
from pyegeria.view.base_report_formats import select_report_spec
|
|
27
|
+
from pyegeria.core._exceptions import print_basic_exception, print_validation_error, print_basic_exception
|
|
28
|
+
from pyegeria.core._globals import DEBUG_LEVEL
|
|
29
|
+
|
|
30
|
+
debug_level = DEBUG_LEVEL
|
|
31
|
+
global COMMAND_DEFINITIONS
|
|
32
|
+
|
|
33
|
+
user_config = settings.User_Profile
|
|
34
|
+
|
|
35
|
+
# Constants
|
|
36
|
+
EGERIA_WIDTH = int(os.environ.get("EGERIA_WIDTH", "200"))
|
|
37
|
+
EGERIA_USAGE_LEVEL = os.environ.get("EGERIA_USAGE_LEVEL", user_config.egeria_usage_level)
|
|
38
|
+
LOCAL_QUALIFIER = os.environ.get("EGERIA_LOCAL_QUALIFIER", None)
|
|
39
|
+
console = Console(width=EGERIA_WIDTH)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@logger.catch
|
|
46
|
+
def process_provenance_command(file_path: str, txt: [str]) -> str:
|
|
47
|
+
"""
|
|
48
|
+
Processes a provenance object_action by extracting the file path and current datetime.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
file_path: The path to the file being processed.
|
|
52
|
+
txt: The text containing the provenance object_action.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
A string containing the provenance information.
|
|
56
|
+
"""
|
|
57
|
+
now = get_current_datetime_string()
|
|
58
|
+
file_name = os.path.basename(file_path)
|
|
59
|
+
provenance = f"\n\n\n# Provenance:\n \n* Derived from processing file {file_name} on {now}\n"
|
|
60
|
+
return provenance
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@logger.catch
|
|
64
|
+
def parse_upsert_command(egeria_client: EgeriaTech, object_type: str, object_action: str, txt: str,
|
|
65
|
+
directive: str = "display", body_type: str = None) -> dict:
|
|
66
|
+
parsed_attributes, parsed_output = {}, {}
|
|
67
|
+
|
|
68
|
+
parsed_output['valid'] = True
|
|
69
|
+
parsed_output['exists'] = False
|
|
70
|
+
parsed_output['display'] = ""
|
|
71
|
+
display_name = ""
|
|
72
|
+
labels = {}
|
|
73
|
+
|
|
74
|
+
command_spec = get_command_spec(f"Create {object_type}", body_type = body_type)
|
|
75
|
+
if command_spec is None:
|
|
76
|
+
logger.error("Command not found in command spec")
|
|
77
|
+
raise Exception("Command not found in command spec")
|
|
78
|
+
distinguished_attributes = command_spec.get('Attributes', [])
|
|
79
|
+
attributes = add_default_upsert_attributes(distinguished_attributes)
|
|
80
|
+
command_display_name = command_spec.get('display_name', None)
|
|
81
|
+
command_qn_prefix = command_spec.get('qn_prefix', None)
|
|
82
|
+
|
|
83
|
+
parsed_output['display_name'] = command_display_name
|
|
84
|
+
parsed_output['qn_prefix'] = command_qn_prefix
|
|
85
|
+
|
|
86
|
+
parsed_output['is_own_anchor'] = command_spec.get('isOwnAnchor', True)
|
|
87
|
+
|
|
88
|
+
parsed_output['reason'] = ""
|
|
89
|
+
|
|
90
|
+
msg = f"\tProcessing {object_action} on a {object_type} \n"
|
|
91
|
+
logger.info(msg)
|
|
92
|
+
|
|
93
|
+
# get the version early because we may need it to construct qualified names.
|
|
94
|
+
version = process_simple_attribute(txt, {'Version', "Version Identifier", "Published Version"}, INFO)
|
|
95
|
+
parsed_output['version'] = version
|
|
96
|
+
|
|
97
|
+
for attr in attributes:
|
|
98
|
+
for key in attr:
|
|
99
|
+
try:
|
|
100
|
+
# Run some checks to see if the attribute is appropriate to the operation and usage level
|
|
101
|
+
for_update = attr[key].get('inUpdate', True)
|
|
102
|
+
level = attr[key].get('level', 'Basic')
|
|
103
|
+
msg = (f"___\nProcessing `{key}` in `{object_action}` on a `{object_type}` "
|
|
104
|
+
f"\n\twith usage level: `{EGERIA_USAGE_LEVEL}` and attribute level `{level}` and for_update `"
|
|
105
|
+
f"{for_update}`\n")
|
|
106
|
+
logger.trace(msg)
|
|
107
|
+
if for_update is False and object_action == "Update":
|
|
108
|
+
logger.trace(f"Attribute `{key}`is not allowed for `Update`", highlight=True)
|
|
109
|
+
continue
|
|
110
|
+
if EGERIA_USAGE_LEVEL == "Basic" and level != "Basic":
|
|
111
|
+
logger.trace(f"Attribute `{key}` is not supported for `{EGERIA_USAGE_LEVEL}` usage level. Skipping.",
|
|
112
|
+
highlight=True)
|
|
113
|
+
continue
|
|
114
|
+
if EGERIA_USAGE_LEVEL == "Advanced" and level in ["Expert", "Invisible"]:
|
|
115
|
+
logger.trace(f"Attribute `{key}` is not supported for `{EGERIA_USAGE_LEVEL}` usage level. Skipping.",
|
|
116
|
+
highlight=True)
|
|
117
|
+
continue
|
|
118
|
+
if EGERIA_USAGE_LEVEL == "Expert" and level == "Invisible":
|
|
119
|
+
logger.trace(f"Attribute `{key}` is not supported for `{EGERIA_USAGE_LEVEL}` usage level. Skipping.",
|
|
120
|
+
highlight=True)
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
if attr[key].get('input_required', False) is True:
|
|
124
|
+
if_missing = ERROR
|
|
125
|
+
else:
|
|
126
|
+
if_missing = INFO
|
|
127
|
+
|
|
128
|
+
# lab = [item.strip() for item in re.split(r'[;,\n]+',attr[key]['attr_labels'])]
|
|
129
|
+
lab = split_tb_string(attr[key]['attr_labels'])
|
|
130
|
+
labels: set = set()
|
|
131
|
+
labels.add(key.strip())
|
|
132
|
+
if key == 'Display Name':
|
|
133
|
+
labels.add(object_type.strip())
|
|
134
|
+
if lab is not None and lab != [""]:
|
|
135
|
+
labels.update(lab)
|
|
136
|
+
|
|
137
|
+
default_value = attr[key].get('default_value', None)
|
|
138
|
+
|
|
139
|
+
style = attr[key]['style']
|
|
140
|
+
if style in ['Simple', 'Comment']:
|
|
141
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value)
|
|
142
|
+
elif style in ['Dictionary', "Named DICT"]:
|
|
143
|
+
parsed_attributes[key] = proc_dictionary_attribute(txt, object_action, labels, if_missing, default_value)
|
|
144
|
+
if key in parsed_attributes and parsed_attributes[key] is not None:
|
|
145
|
+
if parsed_attributes[key].get('value', None) is not None:
|
|
146
|
+
if isinstance(parsed_attributes[key]['value'], dict):
|
|
147
|
+
parsed_attributes[key]['dict'] = json.dumps(parsed_attributes[key]['value'], indent=2)
|
|
148
|
+
else:
|
|
149
|
+
continue
|
|
150
|
+
else:
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
elif style == 'Valid Value':
|
|
154
|
+
parsed_attributes[key] = proc_valid_value(txt, object_action, labels,
|
|
155
|
+
attr[key].get('valid_values', None), if_missing,
|
|
156
|
+
default_value)
|
|
157
|
+
elif style == 'QN':
|
|
158
|
+
parsed_attributes[key] = proc_el_id(egeria_client, command_display_name, command_qn_prefix, labels, txt,
|
|
159
|
+
object_action, version, if_missing)
|
|
160
|
+
if key == 'Qualified Name' and parsed_attributes[key]['value'] and parsed_attributes[key][
|
|
161
|
+
'exists'] is False:
|
|
162
|
+
parsed_output['exists'] = False
|
|
163
|
+
elif style == 'ID':
|
|
164
|
+
parsed_attributes[key] = proc_el_id(egeria_client, command_display_name, command_qn_prefix, labels, txt,
|
|
165
|
+
object_action, version, if_missing)
|
|
166
|
+
|
|
167
|
+
parsed_output['guid'] = parsed_attributes[key].get('guid', None)
|
|
168
|
+
parsed_output['qualified_name'] = parsed_attributes[key].get('qualified_name', None)
|
|
169
|
+
parsed_output['exists'] = parsed_attributes[key]['exists']
|
|
170
|
+
if parsed_attributes[key]['valid'] is False:
|
|
171
|
+
parsed_output['valid'] = False
|
|
172
|
+
parsed_output['reason'] += parsed_attributes[key]['reason']
|
|
173
|
+
|
|
174
|
+
elif style == 'Reference Name':
|
|
175
|
+
parsed_attributes[key] = proc_ids(egeria_client, key, labels, txt, object_action, if_missing)
|
|
176
|
+
if ((if_missing == ERROR) and parsed_attributes[key].get("value", None) and parsed_attributes[key][
|
|
177
|
+
'exists'] is False):
|
|
178
|
+
msg = f"Reference Name `{parsed_attributes[key]['value']}` is specified but does not exist"
|
|
179
|
+
logger.error(msg)
|
|
180
|
+
parsed_output['valid'] = False
|
|
181
|
+
parsed_output['reason'] += msg
|
|
182
|
+
elif parsed_attributes[key]['valid'] is False:
|
|
183
|
+
parsed_output['valid'] = False
|
|
184
|
+
parsed_output['reason'] += parsed_attributes[key]['reason']
|
|
185
|
+
|
|
186
|
+
elif style == 'GUID':
|
|
187
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing)
|
|
188
|
+
g = parsed_attributes[key].get('value', None)
|
|
189
|
+
if g and ("___" not in g and "---" not in g):
|
|
190
|
+
parsed_output['guid'] = parsed_attributes[key]['value']
|
|
191
|
+
elif style == 'Ordered Int':
|
|
192
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing)
|
|
193
|
+
elif style == 'Simple Int':
|
|
194
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value, "int")
|
|
195
|
+
elif style == 'Simple List':
|
|
196
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value, "list")
|
|
197
|
+
name_list = parsed_attributes[key]['value']
|
|
198
|
+
# attribute = re.split(r'[;,\n]+', name_list) if name_list is not None else None
|
|
199
|
+
attribute = split_tb_string(name_list)
|
|
200
|
+
parsed_attributes[key]['value'] = attribute
|
|
201
|
+
parsed_attributes[key]['name_list'] = name_list
|
|
202
|
+
elif style == 'Parent':
|
|
203
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value)
|
|
204
|
+
elif style == 'Bool':
|
|
205
|
+
parsed_attributes[key] = proc_bool_attribute(txt, object_action, labels, if_missing, default_value)
|
|
206
|
+
elif style == "Dictionary List":
|
|
207
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value)
|
|
208
|
+
parsed_attributes[key]['list'] = json.loads(parsed_attributes[key]['value'])
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
elif style == 'Reference Name List':
|
|
212
|
+
parsed_attributes[key] = proc_name_list(egeria_client, key, txt, labels, if_missing)
|
|
213
|
+
if (parsed_attributes[key].get("value", None) and (
|
|
214
|
+
parsed_attributes[key]['exists'] is False or parsed_attributes[key]['valid'] is False)):
|
|
215
|
+
msg = (f"A Reference Name in `{parsed_attributes[key].get('name_list', None)}` is specified but "
|
|
216
|
+
f"does not exist")
|
|
217
|
+
logger.error(msg)
|
|
218
|
+
parsed_output['valid'] = False
|
|
219
|
+
parsed_output['reason'] += msg
|
|
220
|
+
else:
|
|
221
|
+
msg = f"Unknown attribute style: {style} for key `{key}`"
|
|
222
|
+
logger.error(msg)
|
|
223
|
+
sys.exit(1)
|
|
224
|
+
parsed_attributes[key]['valid'] = False
|
|
225
|
+
parsed_attributes[key]['value'] = None
|
|
226
|
+
if key == "Display Name":
|
|
227
|
+
display_name = parsed_attributes[key]['value']
|
|
228
|
+
|
|
229
|
+
value = parsed_attributes[key].get('value', None)
|
|
230
|
+
|
|
231
|
+
if value is not None:
|
|
232
|
+
if isinstance(value, list):
|
|
233
|
+
list_out = f"\n\t* {key}:\n\n"
|
|
234
|
+
for item in value:
|
|
235
|
+
list_out += f"\t{item}\n"
|
|
236
|
+
parsed_output['display'] += list_out
|
|
237
|
+
elif isinstance(value, dict):
|
|
238
|
+
list_out = f"\n\t* {key}:\n\n"
|
|
239
|
+
for k, v in value.items():
|
|
240
|
+
list_out += f"\t{k}: \n\t\t{v}\n"
|
|
241
|
+
parsed_output['display'] += list_out
|
|
242
|
+
else:
|
|
243
|
+
parsed_output['display'] += f"\n\t* {key}: \n`{value}`\n\t"
|
|
244
|
+
except PyegeriaException as e:
|
|
245
|
+
logger.error(f"PyegeriaException occurred: {e}")
|
|
246
|
+
|
|
247
|
+
print_basic_exception(e)
|
|
248
|
+
|
|
249
|
+
except ValidationError as e:
|
|
250
|
+
parsed_attributes[key]['valid'] = False
|
|
251
|
+
parsed_attributes[key]['value'] = None
|
|
252
|
+
print_validation_error(e)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
parsed_output['attributes'] = parsed_attributes
|
|
256
|
+
|
|
257
|
+
if display_name is None:
|
|
258
|
+
msg = f"No display name or name identifier found"
|
|
259
|
+
logger.error(msg)
|
|
260
|
+
parsed_output['valid'] = False
|
|
261
|
+
parsed_output['reason'] = msg
|
|
262
|
+
return parsed_output
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
if parsed_attributes.get('Parent ID', {}).get('value', None) is not None:
|
|
266
|
+
if (parsed_attributes.get('Parent Relationship Type Name',{}).get('value', None) is None) or (
|
|
267
|
+
parsed_attributes.get('Parent at End1',{}).get('value',None) is None):
|
|
268
|
+
msg = "Parent ID was found but either Parent `Relationship Type Name` or `Parent at End1` are missing"
|
|
269
|
+
logger.error(msg)
|
|
270
|
+
parsed_output['valid'] = False
|
|
271
|
+
parsed_output['reason'] = msg
|
|
272
|
+
if parsed_attributes['Parent Relationship Type Name'].get('exists', False) is False:
|
|
273
|
+
msg = "Parent ID was found but does not exist"
|
|
274
|
+
logger.error(msg)
|
|
275
|
+
parsed_output['valid'] = False
|
|
276
|
+
parsed_output['reason'] = msg
|
|
277
|
+
|
|
278
|
+
if directive in ["validate", "process"] and object_action == "Update" and not parsed_output[
|
|
279
|
+
'exists']: # check to see if provided information exists and is consistent with existing info
|
|
280
|
+
msg = f"Update request invalid, element `{display_name}` does not exist\n"
|
|
281
|
+
logger.error(msg)
|
|
282
|
+
parsed_output['valid'] = False
|
|
283
|
+
if directive in ["validate", "process"] and not parsed_output['valid'] and object_action == "Update":
|
|
284
|
+
msg = f"Request is invalid, `{object_action} {object_type}` is not valid - see previous messages\n"
|
|
285
|
+
logger.error(msg)
|
|
286
|
+
|
|
287
|
+
elif directive in ["validate",
|
|
288
|
+
"process"] and object_action == 'Create': # if the object_action is create, check that it
|
|
289
|
+
# doesn't already exist
|
|
290
|
+
if parsed_output['exists']:
|
|
291
|
+
msg = f"Element `{display_name}` cannot be created since it already exists\n"
|
|
292
|
+
logger.error(msg)
|
|
293
|
+
parsed_output['valid'] = False
|
|
294
|
+
else:
|
|
295
|
+
msg = f"Element `{display_name}` does not exist so it can be created\n"
|
|
296
|
+
logger.info(msg)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
if parsed_output.get('qualified_name',None) and "* Qualified Name" not in parsed_output['display']:
|
|
300
|
+
parsed_output['display'] += f"\n\t* Qualified Name: `{parsed_output['qualified_name']}`\n\t"
|
|
301
|
+
if parsed_output.get('guid',None):
|
|
302
|
+
parsed_output['display'] += f"\n\t* GUID: `{parsed_output['guid']}`\n\t"
|
|
303
|
+
|
|
304
|
+
return parsed_output
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@logger.catch
|
|
308
|
+
def parse_view_command(egeria_client: EgeriaTech, object_type: str, object_action: str, txt: str,
|
|
309
|
+
directive: str = "display") -> dict:
|
|
310
|
+
parsed_attributes, parsed_output = {}, {}
|
|
311
|
+
|
|
312
|
+
parsed_output['valid'] = True
|
|
313
|
+
parsed_output['exists'] = False
|
|
314
|
+
|
|
315
|
+
labels = {}
|
|
316
|
+
if object_action in ["Link", "Attach", "Unlink", "Detach"]:
|
|
317
|
+
command_spec = get_command_spec(f"Link {object_type}")
|
|
318
|
+
if command_spec is None:
|
|
319
|
+
logger.error(f"Command specification not found for 'Link {object_type}'")
|
|
320
|
+
return None
|
|
321
|
+
distinguished_attributes = command_spec.get('distinguished_attributes', None)
|
|
322
|
+
if distinguished_attributes:
|
|
323
|
+
attributes = add_default_link_attributes(distinguished_attributes)
|
|
324
|
+
else:
|
|
325
|
+
command_spec = get_command_spec(f"{object_action} {object_type}")
|
|
326
|
+
if command_spec is None:
|
|
327
|
+
logger.error(f"Command specification not found for '{object_action} {object_type}'")
|
|
328
|
+
return None
|
|
329
|
+
attributes = command_spec.get('Attributes', None)
|
|
330
|
+
|
|
331
|
+
command_display_name = command_spec.get('display_name', None)
|
|
332
|
+
|
|
333
|
+
parsed_output['reason'] = ""
|
|
334
|
+
parsed_output['display'] = ""
|
|
335
|
+
|
|
336
|
+
msg = f"\tProcessing {object_action} on {object_type} \n"
|
|
337
|
+
logger.info(msg)
|
|
338
|
+
|
|
339
|
+
# Helper: convert label to snake_case
|
|
340
|
+
def _to_snake_case(name: str) -> str:
|
|
341
|
+
name = name.strip()
|
|
342
|
+
# Replace non-alphanumeric with space, then collapse spaces to underscores
|
|
343
|
+
name = re.sub(r"[^0-9A-Za-z]+", "_", name)
|
|
344
|
+
# Lowercase and trim possible leading/trailing underscores
|
|
345
|
+
return name.strip("_").lower()
|
|
346
|
+
|
|
347
|
+
# Build known labels set from command spec
|
|
348
|
+
known_labels: set[str] = set()
|
|
349
|
+
|
|
350
|
+
# get the version early because we may need it to construct qualified names.
|
|
351
|
+
|
|
352
|
+
for attr in attributes:
|
|
353
|
+
for key in attr:
|
|
354
|
+
# Run some checks to see if the attribute is appropriate to the operation and usage level
|
|
355
|
+
|
|
356
|
+
level = attr[key].get('level', 'Basic')
|
|
357
|
+
msg = (f"___\nProcessing `{key}` in `{object_action}` on a `{object_type}` "
|
|
358
|
+
f"\n\twith usage level: `{EGERIA_USAGE_LEVEL}` ")
|
|
359
|
+
logger.trace(msg)
|
|
360
|
+
|
|
361
|
+
if EGERIA_USAGE_LEVEL == "Basic" and level != "Basic":
|
|
362
|
+
logger.trace(f"Attribute `{key}` is not supported for `{EGERIA_USAGE_LEVEL}` usage level. Skipping.",
|
|
363
|
+
highlight=True)
|
|
364
|
+
continue
|
|
365
|
+
if EGERIA_USAGE_LEVEL == "Advanced" and level in ["Expert", "Invisible"]:
|
|
366
|
+
logger.trace(f"Attribute `{key}` is not supported for `{EGERIA_USAGE_LEVEL}` usage level. Skipping.",
|
|
367
|
+
highlight=True)
|
|
368
|
+
continue
|
|
369
|
+
if EGERIA_USAGE_LEVEL == "Expert" and level == "Invisible":
|
|
370
|
+
logger.trace(f"Attribute `{key}` is not supported for `{EGERIA_USAGE_LEVEL}` usage level. Skipping.",
|
|
371
|
+
highlight=True)
|
|
372
|
+
continue
|
|
373
|
+
|
|
374
|
+
if attr[key].get('input_required', False) is True:
|
|
375
|
+
if_missing = ERROR
|
|
376
|
+
else:
|
|
377
|
+
if_missing = INFO
|
|
378
|
+
|
|
379
|
+
# lab = [item.strip() for item in re.split(r'[;,\n]+',attr[key]['attr_labels'])]
|
|
380
|
+
lab = split_tb_string(attr[key]['attr_labels'])
|
|
381
|
+
labels: set = set()
|
|
382
|
+
labels.add(key.strip())
|
|
383
|
+
|
|
384
|
+
if lab is not None and lab != [""]:
|
|
385
|
+
labels.update(lab)
|
|
386
|
+
|
|
387
|
+
# Keep track of all known labels for later filtering of kwargs
|
|
388
|
+
for lab_entry in labels:
|
|
389
|
+
if lab_entry is not None and lab_entry != "":
|
|
390
|
+
known_labels.add(lab_entry.strip())
|
|
391
|
+
|
|
392
|
+
# set these to none since not needed for view commands
|
|
393
|
+
version = None
|
|
394
|
+
command_qn_prefix = None
|
|
395
|
+
|
|
396
|
+
default_value = attr[key].get('default_value', None)
|
|
397
|
+
|
|
398
|
+
style = attr[key]['style']
|
|
399
|
+
if style in ['Simple', 'Comment']:
|
|
400
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value)
|
|
401
|
+
elif style == 'Dictionary':
|
|
402
|
+
parsed_attributes[key] = proc_dictionary_attribute(txt, object_action, labels, if_missing,
|
|
403
|
+
default_value)
|
|
404
|
+
parsed_attributes[key]['name_list'] = json.dumps(parsed_attributes[key]['value'], indent=2)
|
|
405
|
+
elif style == 'Valid Value':
|
|
406
|
+
parsed_attributes[key] = proc_valid_value(txt, object_action, labels,
|
|
407
|
+
attr[key].get('valid_values', None), if_missing,
|
|
408
|
+
default_value)
|
|
409
|
+
elif style == 'QN':
|
|
410
|
+
parsed_attributes[key] = proc_el_id(egeria_client, command_display_name, command_qn_prefix, labels, txt,
|
|
411
|
+
object_action, version, if_missing)
|
|
412
|
+
if key == 'Qualified Name' and parsed_attributes[key]['value'] and parsed_attributes[key][
|
|
413
|
+
'exists'] is False:
|
|
414
|
+
parsed_output['exists'] = False
|
|
415
|
+
elif style == 'ID':
|
|
416
|
+
parsed_attributes[key] = proc_el_id(egeria_client, command_display_name, command_qn_prefix, labels, txt,
|
|
417
|
+
object_action, version, if_missing)
|
|
418
|
+
|
|
419
|
+
parsed_output['guid'] = parsed_attributes[key].get('guid', None)
|
|
420
|
+
parsed_output['qualified_name'] = parsed_attributes[key].get('qualified_name', None)
|
|
421
|
+
parsed_output['exists'] = parsed_attributes.get(key,{}).get('exists',None)
|
|
422
|
+
if parsed_attributes.get(key,{}).get('valid',None) is False:
|
|
423
|
+
parsed_output['valid'] = False
|
|
424
|
+
parsed_output['reason'] += parsed_attributes.get(key,{}).get('reason',None)
|
|
425
|
+
|
|
426
|
+
elif style == 'Reference Name':
|
|
427
|
+
parsed_attributes[key] = proc_ids(egeria_client, key, labels, txt, object_action, if_missing)
|
|
428
|
+
if ((if_missing == ERROR) and parsed_attributes[key].get("value", None) is None):
|
|
429
|
+
msg = f"Required parameter `{parsed_attributes.get(key,{}).get('value',None)}` is missing"
|
|
430
|
+
logger.error(msg)
|
|
431
|
+
parsed_output['valid'] = False
|
|
432
|
+
parsed_output['reason'] += msg
|
|
433
|
+
elif parsed_attributes.get(key,{}).get('value',None) and parsed_attributes.get(key,{}).get('exists',None) is False:
|
|
434
|
+
msg = f"Reference Name `{parsed_attributes.get(key,{}).get('value',None)}` is specified but does not exist"
|
|
435
|
+
logger.error(msg)
|
|
436
|
+
parsed_output['valid'] = False
|
|
437
|
+
parsed_output['reason'] += msg
|
|
438
|
+
|
|
439
|
+
elif style == 'GUID':
|
|
440
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing)
|
|
441
|
+
elif style == 'Ordered Int':
|
|
442
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing)
|
|
443
|
+
elif style == 'Simple Int':
|
|
444
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value, "int")
|
|
445
|
+
elif style == 'Simple List':
|
|
446
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value)
|
|
447
|
+
name_list = parsed_attributes[key]['value']
|
|
448
|
+
# attribute = re.split(r'[;,\n]+', name_list) if name_list is not None else None
|
|
449
|
+
attribute = split_tb_string(name_list)
|
|
450
|
+
parsed_attributes[key]['value'] = attribute
|
|
451
|
+
parsed_attributes[key]['name_list'] = name_list
|
|
452
|
+
elif style == 'Parent':
|
|
453
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value)
|
|
454
|
+
elif style == 'Bool':
|
|
455
|
+
parsed_attributes[key] = proc_bool_attribute(txt, object_action, labels, if_missing, default_value)
|
|
456
|
+
elif style == "Dictionary List":
|
|
457
|
+
parsed_attributes[key] = proc_simple_attribute(txt, object_action, labels, if_missing, default_value)
|
|
458
|
+
# parsed_attributes[key]['list'] = json.loads(parsed_attributes[key]['value'])
|
|
459
|
+
|
|
460
|
+
elif style == 'Reference Name List':
|
|
461
|
+
parsed_attributes[key] = proc_name_list(egeria_client, key, txt, labels, if_missing)
|
|
462
|
+
if (parsed_attributes[key].get("value", None) and (
|
|
463
|
+
parsed_attributes[key]['exists'] is False or parsed_attributes[key]['valid'] is False)):
|
|
464
|
+
msg = (f"A Reference Name in `{parsed_attributes[key].get('name_list', None)}` is specified but "
|
|
465
|
+
f"does not exist")
|
|
466
|
+
logger.error(msg)
|
|
467
|
+
parsed_output['valid'] = False
|
|
468
|
+
parsed_output['reason'] += msg
|
|
469
|
+
else:
|
|
470
|
+
msg = f"Unknown attribute style: {style}"
|
|
471
|
+
logger.error(msg)
|
|
472
|
+
sys.exit(1)
|
|
473
|
+
parsed_attributes[key]['valid'] = False
|
|
474
|
+
parsed_attributes[key]['value'] = None
|
|
475
|
+
|
|
476
|
+
value = parsed_attributes[key].get('value', None)
|
|
477
|
+
|
|
478
|
+
if value is not None:
|
|
479
|
+
# if the value is a dict or list, get the stringifiedt
|
|
480
|
+
value = parsed_attributes[key].get('name_list', None) if isinstance(value, (dict, list)) else value
|
|
481
|
+
parsed_output['display'] += f"\n\t* {key}: `{value}`\n\t"
|
|
482
|
+
|
|
483
|
+
parsed_output['attributes'] = parsed_attributes
|
|
484
|
+
|
|
485
|
+
# Now, collect any unrecognized commands into kwargs
|
|
486
|
+
# Find all level-2 headings in the text
|
|
487
|
+
all_headings = set(re.findall(r"^\s*##\s*([^\n#]+)", txt, flags=re.MULTILINE))
|
|
488
|
+
|
|
489
|
+
kwargs: dict = {}
|
|
490
|
+
for heading in all_headings:
|
|
491
|
+
h = heading.strip()
|
|
492
|
+
if not h:
|
|
493
|
+
continue
|
|
494
|
+
if h in known_labels:
|
|
495
|
+
continue # already known/processed
|
|
496
|
+
# Parse this unknown attribute using the simple attribute logic
|
|
497
|
+
parsed = proc_simple_attribute(txt, object_action, {h}, INFO, None)
|
|
498
|
+
value = parsed.get('value', None)
|
|
499
|
+
if value is not None:
|
|
500
|
+
kwargs[_to_snake_case(h)] = value.replace('\n', '') if isinstance(value, str) else value
|
|
501
|
+
|
|
502
|
+
if kwargs:
|
|
503
|
+
parsed_output['kwargs'] = kwargs
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
if directive in ["validate", "process"] and not parsed_output['valid'] and object_action == "Update":
|
|
507
|
+
msg = f"Request is invalid, `{object_action} {object_type}` is not valid - see previous messages\n"
|
|
508
|
+
logger.error(msg)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
return parsed_output
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
@logger.catch
|
|
515
|
+
def proc_simple_attribute(txt: str, action: str, labels: set, if_missing: str = INFO, default_value=None,
|
|
516
|
+
simp_type: str = None) -> dict:
|
|
517
|
+
"""Process a simple attribute based on the provided labels and if_missing value.
|
|
518
|
+
Extract the attribute value from the text and store it in a dictionary along with valid.
|
|
519
|
+
If it doesn`t exist, mark the dictionary entry as invalid and print an error message with severity of if_missing.
|
|
520
|
+
|
|
521
|
+
Parameters:
|
|
522
|
+
----------
|
|
523
|
+
txt: str
|
|
524
|
+
The block of object_action text to extract attributes from.
|
|
525
|
+
labels: list
|
|
526
|
+
The possible attribute labels to search for. The first label will be used in messages.
|
|
527
|
+
if_missing: str, default is INFO
|
|
528
|
+
Can be one of "WARNING", "ERROR", "INFO". The severity of the missing attribute.
|
|
529
|
+
default_value: default is None
|
|
530
|
+
The default value to return if the attribute is missing.
|
|
531
|
+
"""
|
|
532
|
+
valid = True
|
|
533
|
+
|
|
534
|
+
if if_missing not in ["WARNING", "ERROR", "INFO"]:
|
|
535
|
+
msg = "Invalid severity for missing attribute"
|
|
536
|
+
logger.error(msg)
|
|
537
|
+
return {"status": ERROR, "reason": msg, "value": None, "valid": False}
|
|
538
|
+
|
|
539
|
+
if default_value == "":
|
|
540
|
+
default_value = None
|
|
541
|
+
|
|
542
|
+
attribute = extract_attribute(txt, labels)
|
|
543
|
+
|
|
544
|
+
# attribute = default_value if attribute is None else attribute.replace('\n', '')
|
|
545
|
+
attribute = default_value if attribute is None else attribute
|
|
546
|
+
if isinstance(attribute, str):
|
|
547
|
+
attribute = re.sub(r'\n+', '\n', attribute)
|
|
548
|
+
attribute = None if attribute.startswith('___') or attribute.startswith('---') else attribute
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
if attribute is None:
|
|
552
|
+
if if_missing == INFO or if_missing == WARNING:
|
|
553
|
+
msg = f"Optional attribute with labels: `{labels}` missing"
|
|
554
|
+
valid = True
|
|
555
|
+
logger.info(msg)
|
|
556
|
+
else:
|
|
557
|
+
msg = f"Missing attribute with labels `{labels}` "
|
|
558
|
+
valid = False
|
|
559
|
+
logger.error(msg)
|
|
560
|
+
return {"status": if_missing, "reason": msg, "value": None, "valid": valid, "exists": False}
|
|
561
|
+
|
|
562
|
+
if attribute and simp_type == "int" :
|
|
563
|
+
attribute = int(attribute)
|
|
564
|
+
# elif attribute and simp_type == "list":
|
|
565
|
+
# if isinstance(attribute, str):
|
|
566
|
+
# attribute = [piece.strip() for piece in re.split(r'[,\n]', attribute) if piece.strip()]
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
return {"status": INFO, "OK": None, "value": attribute, "valid": valid, "exists": True}
|
|
571
|
+
|
|
572
|
+
@logger.catch
|
|
573
|
+
def proc_dictionary_attribute(txt: str, action: str, labels: set, if_missing: str = INFO, default_value=None,
|
|
574
|
+
simp_type: str = None) -> dict:
|
|
575
|
+
"""Process a dictionary attribute based on the provided labels and if_missing value.
|
|
576
|
+
Extract the attribute value from the text and store it in a dictionary along with valid.
|
|
577
|
+
If it doesn`t exist, mark the dictionary entry as invalid and print an error message with severity of if_missing.
|
|
578
|
+
|
|
579
|
+
Parameters:
|
|
580
|
+
----------
|
|
581
|
+
txt: str
|
|
582
|
+
The block of object_action text to extract attributes from.
|
|
583
|
+
labels: list
|
|
584
|
+
The possible attribute labels to search for. The first label will be used in messages.
|
|
585
|
+
if_missing: str, default is INFO
|
|
586
|
+
Can be one of "WARNING", "ERROR", "INFO". The severity of the missing attribute.
|
|
587
|
+
default_value: default is None
|
|
588
|
+
The default value to return if the attribute is missing.
|
|
589
|
+
"""
|
|
590
|
+
valid = True
|
|
591
|
+
|
|
592
|
+
if if_missing not in ["WARNING", "ERROR", "INFO"]:
|
|
593
|
+
msg = "Invalid severity for missing attribute"
|
|
594
|
+
logger.error(msg)
|
|
595
|
+
return {"status": ERROR, "reason": msg, "value": None, "valid": False}
|
|
596
|
+
|
|
597
|
+
if default_value == "":
|
|
598
|
+
default_value = None
|
|
599
|
+
|
|
600
|
+
attr = extract_attribute(txt, labels)
|
|
601
|
+
# attribute = json.loads(attr) if attr is not None else default_value
|
|
602
|
+
attribute = parse_to_dict(attr)
|
|
603
|
+
|
|
604
|
+
if attribute is None:
|
|
605
|
+
if if_missing == INFO or if_missing == WARNING:
|
|
606
|
+
msg = f"Optional attribute with labels: `{labels}` missing"
|
|
607
|
+
valid = True
|
|
608
|
+
logger.info(msg)
|
|
609
|
+
else:
|
|
610
|
+
msg = f"Missing attribute with labels `{labels}` "
|
|
611
|
+
valid = False
|
|
612
|
+
logger.error(msg)
|
|
613
|
+
return {"status": if_missing, "reason": msg, "value": None, "valid": valid, "exists": False}
|
|
614
|
+
|
|
615
|
+
return {"status": INFO, "OK": None, "value": attribute, "valid": valid, "exists": True}
|
|
616
|
+
|
|
617
|
+
@logger.catch
|
|
618
|
+
def proc_valid_value(txt: str, action: str, labels: set, valid_values: [], if_missing: str = INFO,
|
|
619
|
+
default_value=None) -> dict:
|
|
620
|
+
"""Process a string attribute to check that it is a member of the associated value values list.
|
|
621
|
+
Extract the attribute value from the text and store it in a dictionary along with valid.
|
|
622
|
+
If it doesn`t exist, mark the dictionary entry as invalid and print an error message with severity of if_missing.
|
|
623
|
+
|
|
624
|
+
Parameters:
|
|
625
|
+
----------
|
|
626
|
+
txt: str
|
|
627
|
+
The block of object_action text to extract attributes from.
|
|
628
|
+
labels: list
|
|
629
|
+
The possible attribute labels to search for. The first label will be used in messages.
|
|
630
|
+
if_missing: str, default is INFO
|
|
631
|
+
Can be one of "WARNING", "ERROR", "INFO". The severity of the missing attribute.
|
|
632
|
+
default_value: default is None
|
|
633
|
+
The default value to return if the attribute is missing.
|
|
634
|
+
"""
|
|
635
|
+
valid = True
|
|
636
|
+
v_values = []
|
|
637
|
+
|
|
638
|
+
if if_missing not in ["WARNING", "ERROR", "INFO"]:
|
|
639
|
+
msg = "Invalid severity for missing attribute"
|
|
640
|
+
logger.error(msg)
|
|
641
|
+
return {"status": ERROR, "reason": msg, "value": None, "valid": False}
|
|
642
|
+
if valid_values is None:
|
|
643
|
+
msg = "Missing valid values list"
|
|
644
|
+
logger.error(msg)
|
|
645
|
+
return {"status": WARNING, "reason": msg, "value": None, "valid": False}
|
|
646
|
+
if isinstance(valid_values, str):
|
|
647
|
+
# v_values = [item.strip() for item in re.split(r'[;,\n]+', valid_values)]
|
|
648
|
+
v_values = split_tb_string(valid_values)
|
|
649
|
+
if isinstance(valid_values, list):
|
|
650
|
+
v_values = valid_values
|
|
651
|
+
if not isinstance(v_values, list):
|
|
652
|
+
msg = "Valid values list is not a list"
|
|
653
|
+
logger.error(msg)
|
|
654
|
+
return {"status": WARNING, "reason": msg, "value": None, "valid": False}
|
|
655
|
+
if len(v_values) == 0:
|
|
656
|
+
msg = "Valid values list is empty"
|
|
657
|
+
logger.error(msg)
|
|
658
|
+
return {"status": WARNING, "reason": msg, "value": None, "valid": False}
|
|
659
|
+
|
|
660
|
+
attribute = extract_attribute(txt, labels)
|
|
661
|
+
if default_value == "":
|
|
662
|
+
default_value = None
|
|
663
|
+
attribute = default_value if attribute is None else attribute
|
|
664
|
+
|
|
665
|
+
if attribute is None:
|
|
666
|
+
if if_missing == INFO or if_missing == WARNING:
|
|
667
|
+
msg = f"Optional attribute with labels: `{labels}` missing"
|
|
668
|
+
logger.info(msg)
|
|
669
|
+
valid = True
|
|
670
|
+
else:
|
|
671
|
+
msg = f"Missing attribute with labels `{labels}` "
|
|
672
|
+
valid = False
|
|
673
|
+
logger.error(msg)
|
|
674
|
+
return {"status": if_missing, "reason": msg, "value": None, "valid": valid, "exists": False}
|
|
675
|
+
else:
|
|
676
|
+
# Todo: look at moving validation into pydantic or another style...
|
|
677
|
+
if "Status" in labels:
|
|
678
|
+
attribute = attribute.upper()
|
|
679
|
+
if attribute not in v_values:
|
|
680
|
+
msg = f"Invalid value for attribute `{labels}` attribute is `{attribute}`"
|
|
681
|
+
logger.warning(msg)
|
|
682
|
+
return {"status": WARNING, "reason": msg, "value": attribute, "valid": False, "exists": True}
|
|
683
|
+
|
|
684
|
+
return {"status": INFO, "OK": "OK", "value": attribute, "valid": valid, "exists": True}
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
@logger.catch
|
|
688
|
+
def proc_bool_attribute(txt: str, action: str, labels: set, if_missing: str = INFO, default_value=None) -> dict:
|
|
689
|
+
"""Process a boolean attribute based on the provided labels and if_missing value.
|
|
690
|
+
Extract the attribute value from the text and store it in a dictionary along with valid.
|
|
691
|
+
If it doesn`t exist, mark the dictionary entry as invalid and print an error message with severity of if_missing.
|
|
692
|
+
|
|
693
|
+
Parameters:
|
|
694
|
+
----------
|
|
695
|
+
txt: str
|
|
696
|
+
The block of object_action text to extract attributes from.
|
|
697
|
+
labels: list
|
|
698
|
+
The possible attribute labels to search for. The first label will be used in messages.
|
|
699
|
+
if_missing: str, default is INFO
|
|
700
|
+
Can be one of "WARNING", "ERROR", "INFO". The severity of the missing attribute.
|
|
701
|
+
default_value: default is None
|
|
702
|
+
The default value to return if the attribute is missing.
|
|
703
|
+
"""
|
|
704
|
+
valid = True
|
|
705
|
+
|
|
706
|
+
if if_missing not in ["WARNING", "ERROR", "INFO"]:
|
|
707
|
+
msg = "Invalid severity for missing attribute"
|
|
708
|
+
logger.error(msg)
|
|
709
|
+
return {"status": ERROR, "reason": msg, "value": None, "valid": False}
|
|
710
|
+
|
|
711
|
+
attribute = extract_attribute(txt, labels)
|
|
712
|
+
if default_value == "":
|
|
713
|
+
default = None
|
|
714
|
+
else:
|
|
715
|
+
default = str_to_bool(default_value)
|
|
716
|
+
attribute = default if attribute is None else attribute
|
|
717
|
+
|
|
718
|
+
if attribute is None:
|
|
719
|
+
if if_missing == INFO or if_missing == WARNING:
|
|
720
|
+
msg = f"Optional attribute with labels: `{labels}` missing"
|
|
721
|
+
logger.info(msg)
|
|
722
|
+
valid = True
|
|
723
|
+
else:
|
|
724
|
+
msg = f"Missing attribute with labels `{labels}` "
|
|
725
|
+
valid = False
|
|
726
|
+
logger.error(msg)
|
|
727
|
+
return {"status": if_missing, "reason": msg, "value": None, "valid": valid, "exists": False}
|
|
728
|
+
|
|
729
|
+
if isinstance(attribute, str):
|
|
730
|
+
attribute = attribute.strip().lower()
|
|
731
|
+
if attribute in ["true", "yes", "1"]:
|
|
732
|
+
attribute = True
|
|
733
|
+
elif attribute in ["false", "no", "0"]:
|
|
734
|
+
attribute = False
|
|
735
|
+
else:
|
|
736
|
+
msg = f"Invalid value for boolean attribute `{labels}`"
|
|
737
|
+
logger.error(msg)
|
|
738
|
+
return {"status": ERROR, "reason": msg, "value": attribute, "valid": False, "exists": True}
|
|
739
|
+
|
|
740
|
+
return {"status": INFO, "OK": None, "value": attribute, "valid": valid, "exists": True}
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
@logger.catch
|
|
744
|
+
def proc_el_id(egeria_client: EgeriaTech, element_type: str, qn_prefix: str, element_labels: list[str], txt: str,
|
|
745
|
+
action: str, version: str = None, if_missing: str = INFO) -> dict:
|
|
746
|
+
"""
|
|
747
|
+
Processes display_name and qualified_name by extracting them from the input text,
|
|
748
|
+
checking if the element exists in Egeria, and validating the information. If a qualified
|
|
749
|
+
name isn't found, one will be created.
|
|
750
|
+
|
|
751
|
+
Parameters
|
|
752
|
+
----------
|
|
753
|
+
egeria_client: EgeriaTech
|
|
754
|
+
Client object for interacting with Egeria.
|
|
755
|
+
element_type: str
|
|
756
|
+
type of element to process (e.g., 'blueprint', 'category', 'term')
|
|
757
|
+
element_labels: a list of equivalent label names to use in processing the element.
|
|
758
|
+
txt: str
|
|
759
|
+
A string representing the input text to be processed for extracting element identifiers.
|
|
760
|
+
action: str
|
|
761
|
+
The action object_action to be executed (e.g., 'Create', 'Update', 'Display', ...)
|
|
762
|
+
version: str, optional = None
|
|
763
|
+
An optional version identifier used if we need to construct the qualified name
|
|
764
|
+
|
|
765
|
+
Returns: dict with keys:
|
|
766
|
+
status
|
|
767
|
+
reason
|
|
768
|
+
value - value of display name
|
|
769
|
+
valid - name we parse out
|
|
770
|
+
exists
|
|
771
|
+
qualified_name - qualified name - either that we find or the one we construct
|
|
772
|
+
guid - guid of the element if it already exists
|
|
773
|
+
"""
|
|
774
|
+
valid = True
|
|
775
|
+
exists = False
|
|
776
|
+
identifier_output = {}
|
|
777
|
+
|
|
778
|
+
element_name = extract_attribute(txt, element_labels)
|
|
779
|
+
qualified_name = extract_attribute(txt, ["Qualified Name"])
|
|
780
|
+
|
|
781
|
+
if element_name is None:
|
|
782
|
+
msg = f"Optional attribute with label`{element_type}` missing"
|
|
783
|
+
logger.info(msg)
|
|
784
|
+
identifier_output = {"status": INFO, "reason": msg, "value": None, "valid": False, "exists": False, }
|
|
785
|
+
return identifier_output
|
|
786
|
+
|
|
787
|
+
if qualified_name:
|
|
788
|
+
q_name, guid, unique, exists = get_element_by_name(egeria_client, element_type,
|
|
789
|
+
qualified_name) # Qualified name could be different if it
|
|
790
|
+
# is being updated
|
|
791
|
+
else:
|
|
792
|
+
q_name, guid, unique, exists = get_element_by_name(egeria_client, element_type, element_name)
|
|
793
|
+
qualified_name = q_name
|
|
794
|
+
|
|
795
|
+
if unique is False:
|
|
796
|
+
msg = f"Multiple elements named {element_name} found"
|
|
797
|
+
logger.error(msg)
|
|
798
|
+
identifier_output = {"status": ERROR, "reason": msg, "value": element_name, "valid": False, "exists": True, }
|
|
799
|
+
valid = False
|
|
800
|
+
|
|
801
|
+
if action == "Update" and not exists:
|
|
802
|
+
msg = f"Element {element_name} does not exist"
|
|
803
|
+
logger.error(msg)
|
|
804
|
+
identifier_output = {"status": ERROR, "reason": msg, "value": element_name, "valid": False, "exists": False, }
|
|
805
|
+
|
|
806
|
+
elif action in ["Update", "View", "Link", "Detach"] and exists:
|
|
807
|
+
msg = f"Element {element_name} exists"
|
|
808
|
+
logger.info(msg)
|
|
809
|
+
identifier_output = {
|
|
810
|
+
"status": INFO, "reason": msg, "value": element_name, "valid": True, "exists": True,
|
|
811
|
+
'qualified_name': q_name, 'guid': guid
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
elif action == "Create" and exists:
|
|
815
|
+
msg = f"Element {element_name} already exists"
|
|
816
|
+
logger.error(msg)
|
|
817
|
+
identifier_output = {
|
|
818
|
+
"status": ERROR, "reason": msg, "value": element_name, "valid": False, "exists": True,
|
|
819
|
+
'qualified_name': qualified_name, 'guid': guid,
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
elif action == "Create" and not exists and valid:
|
|
823
|
+
msg = f"{element_type} `{element_name}` does not exist"
|
|
824
|
+
logger.info(msg)
|
|
825
|
+
|
|
826
|
+
if q_name is None and qualified_name is None:
|
|
827
|
+
q_name = egeria_client.__create_qualified_name__(qn_prefix, element_name, LOCAL_QUALIFIER, version)
|
|
828
|
+
update_element_dictionary(q_name, {'display_name': element_name})
|
|
829
|
+
|
|
830
|
+
elif qualified_name:
|
|
831
|
+
update_element_dictionary(qualified_name, {'display_name': element_name})
|
|
832
|
+
q_name = qualified_name
|
|
833
|
+
|
|
834
|
+
identifier_output = {
|
|
835
|
+
"status": INFO, "reason": msg, "value": element_name, "valid": True, "exists": False,
|
|
836
|
+
'qualified_name': q_name
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return identifier_output
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
@logger.catch
|
|
843
|
+
def proc_ids(egeria_client: EgeriaTech, element_type: str, element_labels: set, txt: str, action: str,
|
|
844
|
+
if_missing: str = INFO, version: str = None) -> dict:
|
|
845
|
+
"""
|
|
846
|
+
Processes element identifiers from the input text using the labels supplied,
|
|
847
|
+
checking if the element exists in Egeria, and validating the information.
|
|
848
|
+
Only a single element is allowed.
|
|
849
|
+
|
|
850
|
+
Parameters
|
|
851
|
+
----------
|
|
852
|
+
egeria_client: EgeriaTech
|
|
853
|
+
Client object for interacting with Egeria.
|
|
854
|
+
element_type: str
|
|
855
|
+
type of element to process (e.g., 'blueprint', 'category', 'term')
|
|
856
|
+
element_labels: a list of equivalent label names to use in processing the element.
|
|
857
|
+
txt: str
|
|
858
|
+
A string representing the input text to be processed for extracting element identifiers.
|
|
859
|
+
action: str
|
|
860
|
+
The action object_action to be executed (e.g., 'Create', 'Update', 'Display', ...)
|
|
861
|
+
if_missing: str, optional = None
|
|
862
|
+
Optional version identifier used if we need to construct the qualified name
|
|
863
|
+
version: str, optional = INFO
|
|
864
|
+
What to do if the element doesn't exist. Default is INFO. Can be one of "WARNING", "ERROR", "INFO".
|
|
865
|
+
|
|
866
|
+
Returns: dict with keys:
|
|
867
|
+
status
|
|
868
|
+
reason
|
|
869
|
+
value
|
|
870
|
+
valid - name we parse out
|
|
871
|
+
exists
|
|
872
|
+
qualified_name - what we find exists
|
|
873
|
+
guid
|
|
874
|
+
"""
|
|
875
|
+
valid = True
|
|
876
|
+
exists = False
|
|
877
|
+
identifier_output = {}
|
|
878
|
+
unique = True
|
|
879
|
+
value = None
|
|
880
|
+
|
|
881
|
+
element_name = extract_attribute(txt, element_labels)
|
|
882
|
+
|
|
883
|
+
if element_name:
|
|
884
|
+
if element_type == "Tag ID": # Special case for informal tags
|
|
885
|
+
element_type = "InformalTag"
|
|
886
|
+
|
|
887
|
+
if '\n' in element_name or ',' in element_name:
|
|
888
|
+
msg = f"Element name `{element_name}` appears to be a list rather than a single element"
|
|
889
|
+
logger.error(msg)
|
|
890
|
+
return {"status": ERROR, "reason": msg, "value": None, "valid": False, "exists": False, }
|
|
891
|
+
q_name, guid, unique, exists = get_element_by_name(egeria_client, element_type, element_name)
|
|
892
|
+
value = element_name
|
|
893
|
+
else:
|
|
894
|
+
exists = False
|
|
895
|
+
q_name = None
|
|
896
|
+
unique = None
|
|
897
|
+
|
|
898
|
+
if exists is True and unique is False:
|
|
899
|
+
# Multiple elements found - so need to respecify with qualified name
|
|
900
|
+
msg = f"Multiple elements named {element_name} found"
|
|
901
|
+
logger.error(msg)
|
|
902
|
+
identifier_output = {"status": ERROR, "reason": msg, "value": element_name, "valid": False, "exists": True, }
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
elif action == EXISTS_REQUIRED or if_missing == ERROR and not exists:
|
|
906
|
+
# a required identifier doesn't exist
|
|
907
|
+
msg = f"Required {element_type} `{element_name}` does not exist"
|
|
908
|
+
logger.error(msg)
|
|
909
|
+
identifier_output = {"status": ERROR, "reason": msg, "value": element_name, "valid": False, "exists": False, }
|
|
910
|
+
elif value is None and if_missing == INFO:
|
|
911
|
+
# an optional identifier is empty
|
|
912
|
+
msg = f"Optional attribute with label`{element_type}` missing"
|
|
913
|
+
logger.info(msg)
|
|
914
|
+
identifier_output = {"status": INFO, "reason": msg, "value": None, "valid": True, "exists": False, }
|
|
915
|
+
elif value and exists is False:
|
|
916
|
+
# optional identifier specified but doesn't exist
|
|
917
|
+
msg = f"Optional attribute with label`{element_type}` specified but doesn't exist"
|
|
918
|
+
logger.error(msg)
|
|
919
|
+
identifier_output = {"status": ERROR, "reason": msg, "value": value, "valid": False, "exists": False, }
|
|
920
|
+
|
|
921
|
+
else:
|
|
922
|
+
# all good.
|
|
923
|
+
msg = f"Element {element_type} `{element_name}` exists"
|
|
924
|
+
logger.info(msg)
|
|
925
|
+
identifier_output = {
|
|
926
|
+
"status": INFO, "reason": msg, "value": element_name, "valid": True, "exists": True,
|
|
927
|
+
"qualified_name": q_name, 'guid': guid
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return identifier_output
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
@logger.catch
|
|
934
|
+
def proc_name_list(egeria_client: EgeriaTech, element_type: str, txt: str, element_labels: set,
|
|
935
|
+
if_missing: str = INFO) -> dict:
|
|
936
|
+
"""
|
|
937
|
+
Processes a list of names specified in the given text, retrieves details for each
|
|
938
|
+
element based on the provided type, and generates a list of valid qualified names.
|
|
939
|
+
|
|
940
|
+
The function reads a text block, extracts a list of element names according to the specified
|
|
941
|
+
element type, looks them up using the provided Egeria client, and classifies them as valid or
|
|
942
|
+
invalid. It returns the processed names, a list of qualified names, and validity and existence
|
|
943
|
+
flags.
|
|
944
|
+
|
|
945
|
+
Args:
|
|
946
|
+
|
|
947
|
+
egeria_client (EgeriaTech): The client instance to connect and query elements from an
|
|
948
|
+
external system.
|
|
949
|
+
Element_type (str): The type of element, such as schema or attribute, to process.
|
|
950
|
+
Txt (str): The raw input text containing element names to be processed.
|
|
951
|
+
element_labels: a list of equivalent label names to use in processing the element.
|
|
952
|
+
required: bool, default is False
|
|
953
|
+
- indicates whether the list of names is required to be present in the input text.
|
|
954
|
+
|
|
955
|
+
Returns:
|
|
956
|
+
Dict containing:
|
|
957
|
+
'names' - Concatenated valid input names as a single string (or None if empty).
|
|
958
|
+
'name_list' - A list of known qualified names extracted from the processed elements.
|
|
959
|
+
'valid' - A boolean indicating whether all elements are valid.
|
|
960
|
+
'exists' - A boolean indicating whether all elements exist.
|
|
961
|
+
"""
|
|
962
|
+
valid = True
|
|
963
|
+
exists = True
|
|
964
|
+
id_list_output = {}
|
|
965
|
+
elements = ""
|
|
966
|
+
new_element_list = []
|
|
967
|
+
guid_list = []
|
|
968
|
+
elements_txt = extract_attribute(txt, element_labels)
|
|
969
|
+
|
|
970
|
+
if elements_txt is None:
|
|
971
|
+
msg = f"Attribute with labels `{{element_type}}` missing"
|
|
972
|
+
logger.debug(msg)
|
|
973
|
+
return {"status": if_missing, "reason": msg, "value": None, "valid": False, "exists": False, }
|
|
974
|
+
else:
|
|
975
|
+
# element_list = re.split(r'[;,\n]+', elements_txt)
|
|
976
|
+
element_list = split_tb_string(elements_txt)
|
|
977
|
+
element_details = {}
|
|
978
|
+
for element in element_list:
|
|
979
|
+
# Get the element using the generalized function
|
|
980
|
+
known_q_name, known_guid, el_valid, el_exists = get_element_by_name(egeria_client, element_type, element)
|
|
981
|
+
details = {"known_q_name": known_q_name, "known_guid": known_guid, "el_valid": el_valid}
|
|
982
|
+
elements += element + ", " # list of the input names
|
|
983
|
+
|
|
984
|
+
if el_exists and el_valid:
|
|
985
|
+
new_element_list.append(known_q_name) # list of qualified names
|
|
986
|
+
guid_list.append(known_guid)
|
|
987
|
+
elif not el_exists:
|
|
988
|
+
msg = f"No {element_type} `{element}` found"
|
|
989
|
+
logger.debug(msg)
|
|
990
|
+
valid = False
|
|
991
|
+
valid = valid if el_valid is None else (valid and el_valid)
|
|
992
|
+
exists = exists and el_exists
|
|
993
|
+
element_details[element] = details
|
|
994
|
+
|
|
995
|
+
if elements:
|
|
996
|
+
msg = f"Found {element_type}: {elements}"
|
|
997
|
+
logger.debug(msg)
|
|
998
|
+
id_list_output = {
|
|
999
|
+
"status": INFO, "reason": msg, "value": element_details, "valid": valid, "exists": exists,
|
|
1000
|
+
"name_list": new_element_list, "guid_list": guid_list,
|
|
1001
|
+
}
|
|
1002
|
+
else:
|
|
1003
|
+
msg = f" Name list contains one or more invalid qualified names."
|
|
1004
|
+
logger.debug(msg)
|
|
1005
|
+
id_list_output = {
|
|
1006
|
+
"status": if_missing, "reason": msg, "value": elements, "valid": valid, "exists": exists,
|
|
1007
|
+
"dict_list": element_details, "guid_list": guid_list
|
|
1008
|
+
}
|
|
1009
|
+
return id_list_output
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
@logger.catch
|
|
1013
|
+
def sync_collection_memberships(egeria_client: EgeriaTech, guid: str, get_method: callable, collection_types: list,
|
|
1014
|
+
to_be_collection_guids:list, merge_update: bool = True)-> None:
|
|
1015
|
+
"""
|
|
1016
|
+
Synchronize collection memberships for an element.
|
|
1017
|
+
|
|
1018
|
+
Parameters
|
|
1019
|
+
- egeria_client: EgeriaTech composite client used to call add/remove operations.
|
|
1020
|
+
- guid: the GUID of the element (e.g., GlossaryTerm) whose memberships we sync.
|
|
1021
|
+
- get_method: callable to fetch the element details by guid; must accept (guid, output_format="JSON").
|
|
1022
|
+
- collection_types: list of collection type identifiers to consider when syncing (e.g., ["Glossary", "Folder"]).
|
|
1023
|
+
- to_be_collection_guids: list of lists of GUIDs corresponding positionally to collection_types; may contain None.
|
|
1024
|
+
- merge_update: if True, only add missing memberships; if False, remove existing memberships for the
|
|
1025
|
+
specified collection_types and then add the desired memberships.
|
|
1026
|
+
|
|
1027
|
+
Behavior
|
|
1028
|
+
- When merge_update is True: determine the element's current memberships and add the missing ones only.
|
|
1029
|
+
- When merge_update is False: remove the element from all collections of the specified types, then add the
|
|
1030
|
+
provided target memberships for those types.
|
|
1031
|
+
"""
|
|
1032
|
+
try:
|
|
1033
|
+
# Defensive defaults and shape normalization
|
|
1034
|
+
collection_types = collection_types or []
|
|
1035
|
+
to_be_collection_guids = to_be_collection_guids or []
|
|
1036
|
+
# Ensure the lists align by index; pad to length
|
|
1037
|
+
max_len = max(len(collection_types), len(to_be_collection_guids)) if (collection_types or to_be_collection_guids) else 0
|
|
1038
|
+
if len(collection_types) < max_len:
|
|
1039
|
+
collection_types = collection_types + [None] * (max_len - len(collection_types))
|
|
1040
|
+
if len(to_be_collection_guids) < max_len:
|
|
1041
|
+
to_be_collection_guids = to_be_collection_guids + [None] * (max_len - len(to_be_collection_guids))
|
|
1042
|
+
|
|
1043
|
+
# Get current element details with raw JSON to inspect relationships
|
|
1044
|
+
element = None
|
|
1045
|
+
try:
|
|
1046
|
+
element = get_method(guid, output_format="JSON")
|
|
1047
|
+
except TypeError:
|
|
1048
|
+
# Some get methods require element_type parameter; fallback best-effort
|
|
1049
|
+
element = get_method(guid, element_type=None, output_format="JSON")
|
|
1050
|
+
if isinstance(element, str):
|
|
1051
|
+
# e.g., "No elements found"; nothing to do
|
|
1052
|
+
logger.debug(f"sync_collection_memberships: element lookup returned: {element}")
|
|
1053
|
+
return
|
|
1054
|
+
if not isinstance(element, dict):
|
|
1055
|
+
logger.debug("sync_collection_memberships: element lookup did not return a dict; skipping")
|
|
1056
|
+
return
|
|
1057
|
+
|
|
1058
|
+
member_rels = element.get("memberOfCollections", []) or []
|
|
1059
|
+
|
|
1060
|
+
# Build current membership maps
|
|
1061
|
+
# - by GUID: set of current collection guids
|
|
1062
|
+
# - by type name (classification names found on related collection): map type->set(guids)
|
|
1063
|
+
current_all_guids: set[str] = set()
|
|
1064
|
+
current_by_type: dict[str, set[str]] = {}
|
|
1065
|
+
|
|
1066
|
+
for rel in member_rels:
|
|
1067
|
+
try:
|
|
1068
|
+
related = (rel or {}).get("relatedElement", {})
|
|
1069
|
+
rel_guid = ((related.get("elementHeader") or {}).get("guid"))
|
|
1070
|
+
if not rel_guid:
|
|
1071
|
+
continue
|
|
1072
|
+
current_all_guids.add(rel_guid)
|
|
1073
|
+
|
|
1074
|
+
# Collect type hints from classifications and from properties.collectionType
|
|
1075
|
+
type_names: set[str] = set()
|
|
1076
|
+
classifications = ((related.get("elementHeader") or {}).get("classifications")) or []
|
|
1077
|
+
for cls in classifications:
|
|
1078
|
+
tname = (((cls or {}).get("type") or {}).get("typeName"))
|
|
1079
|
+
if tname:
|
|
1080
|
+
type_names.add(tname)
|
|
1081
|
+
ctype = ((related.get("properties") or {}).get("collectionType"))
|
|
1082
|
+
if isinstance(ctype, str) and ctype:
|
|
1083
|
+
type_names.add(ctype)
|
|
1084
|
+
|
|
1085
|
+
if not type_names:
|
|
1086
|
+
# Fallback: try elementHeader.type.typeName
|
|
1087
|
+
tname2 = (((related.get("elementHeader") or {}).get("type") or {}).get("typeName"))
|
|
1088
|
+
if tname2:
|
|
1089
|
+
type_names.add(tname2)
|
|
1090
|
+
|
|
1091
|
+
for tn in type_names:
|
|
1092
|
+
s = current_by_type.setdefault(tn, set())
|
|
1093
|
+
s.add(rel_guid)
|
|
1094
|
+
except Exception as e:
|
|
1095
|
+
logger.debug(f"sync_collection_memberships: skipping malformed relationship: {e}")
|
|
1096
|
+
continue
|
|
1097
|
+
|
|
1098
|
+
# Helper to coerce incoming desired list entry to a set of guids
|
|
1099
|
+
def to_guid_set(maybe_list) -> set[str]:
|
|
1100
|
+
if not maybe_list:
|
|
1101
|
+
return set()
|
|
1102
|
+
if isinstance(maybe_list, list):
|
|
1103
|
+
return {g for g in maybe_list if isinstance(g, str) and g}
|
|
1104
|
+
# Sometimes a single guid may slip through
|
|
1105
|
+
if isinstance(maybe_list, str):
|
|
1106
|
+
return {maybe_list}
|
|
1107
|
+
return set()
|
|
1108
|
+
|
|
1109
|
+
# If merge_update is False: remove all existing memberships for the specified types
|
|
1110
|
+
if not merge_update:
|
|
1111
|
+
# Build a set of guids to remove across specified types
|
|
1112
|
+
to_remove: set[str] = set()
|
|
1113
|
+
for t in collection_types:
|
|
1114
|
+
if not t:
|
|
1115
|
+
continue
|
|
1116
|
+
# Match by exact type name as seen in current_by_type
|
|
1117
|
+
guids_for_type = current_by_type.get(t) or set()
|
|
1118
|
+
if not guids_for_type and t.lower() in {k.lower() for k in current_by_type.keys()}:
|
|
1119
|
+
# Case-insensitive fallback
|
|
1120
|
+
for k, v in current_by_type.items():
|
|
1121
|
+
if k.lower() == t.lower():
|
|
1122
|
+
guids_for_type = v
|
|
1123
|
+
break
|
|
1124
|
+
to_remove.update(guids_for_type)
|
|
1125
|
+
|
|
1126
|
+
for coll_guid in to_remove:
|
|
1127
|
+
try:
|
|
1128
|
+
egeria_client.remove_from_collection(coll_guid, guid)
|
|
1129
|
+
logger.info(f"Removed element {guid} from collection {coll_guid}")
|
|
1130
|
+
except Exception as e:
|
|
1131
|
+
logger.debug(f"Failed to remove element {guid} from collection {coll_guid}: {e}")
|
|
1132
|
+
|
|
1133
|
+
# Now add desired memberships (for both merge and replace flows)
|
|
1134
|
+
for idx, t in enumerate(collection_types):
|
|
1135
|
+
desired_set = to_guid_set(to_be_collection_guids[idx] if idx < len(to_be_collection_guids) else None)
|
|
1136
|
+
if not desired_set:
|
|
1137
|
+
continue
|
|
1138
|
+
for coll_guid in desired_set:
|
|
1139
|
+
# If merge_update True, skip if already a member; if False, we removed earlier so can re-add
|
|
1140
|
+
if merge_update and coll_guid in current_all_guids:
|
|
1141
|
+
continue
|
|
1142
|
+
try:
|
|
1143
|
+
egeria_client.add_to_collection(coll_guid, guid)
|
|
1144
|
+
logger.info(f"Added element {guid} to collection {coll_guid}")
|
|
1145
|
+
except Exception as e:
|
|
1146
|
+
logger.debug(f"Failed to add element {guid} to collection {coll_guid}: {e}")
|
|
1147
|
+
|
|
1148
|
+
return
|
|
1149
|
+
except Exception as e:
|
|
1150
|
+
logger.error(f"sync_collection_memberships: unexpected error: {e}")
|
|
1151
|
+
return
|
|
1152
|
+
|
|
1153
|
+
@logger.catch
|
|
1154
|
+
def process_output_command(egeria_client: EgeriaTech, txt: str, directive: str = "display") -> Optional[str]:
|
|
1155
|
+
"""
|
|
1156
|
+
Processes a generic output request by extracting attributes (including Output Format and
|
|
1157
|
+
Output Format Set) and dynamically invoking the find function specified by the
|
|
1158
|
+
report_spec, following the approach used in commands/cat/list_format_set.
|
|
1159
|
+
|
|
1160
|
+
This is modeled on process_gov_definition_list_command but uses the dynamic
|
|
1161
|
+
dispatch via the output format set rather than directly calling a specific
|
|
1162
|
+
egeria_client method.
|
|
1163
|
+
|
|
1164
|
+
:param egeria_client: EgeriaTech composite client instance
|
|
1165
|
+
:param txt: The command text (e.g., parsed from a markdown cell)
|
|
1166
|
+
:param directive: display | validate | process
|
|
1167
|
+
:return: Markdown string for processed output or None
|
|
1168
|
+
"""
|
|
1169
|
+
command, object_type, object_action = extract_command_plus(txt)
|
|
1170
|
+
print(Markdown(f"# {command}\n"))
|
|
1171
|
+
|
|
1172
|
+
parsed_output = parse_view_command(egeria_client, object_type, object_action, txt, directive)
|
|
1173
|
+
|
|
1174
|
+
valid = parsed_output['valid']
|
|
1175
|
+
print(Markdown(f"Performing {command}"))
|
|
1176
|
+
print(Markdown(parsed_output['display']))
|
|
1177
|
+
|
|
1178
|
+
attr = parsed_output.get('attributes', {})
|
|
1179
|
+
|
|
1180
|
+
search_string = attr.get('Search String', {}).get('value', '*')
|
|
1181
|
+
output_format = attr.get('Output Format', {}).get('value', 'LIST')
|
|
1182
|
+
report_spec = attr.get('Output Format Set', {}).get('value', object_type)
|
|
1183
|
+
|
|
1184
|
+
if directive == "display":
|
|
1185
|
+
return None
|
|
1186
|
+
elif directive == "validate":
|
|
1187
|
+
# Validate that the format set exists and has an action
|
|
1188
|
+
fmt = select_report_spec(report_spec, "ANY") if valid else None
|
|
1189
|
+
if valid and fmt and fmt.get("action"):
|
|
1190
|
+
print(Markdown(f"==> Validation of {command} completed successfully!\n"))
|
|
1191
|
+
return True
|
|
1192
|
+
else:
|
|
1193
|
+
msg = f"Validation failed for object_action `{command}`"
|
|
1194
|
+
logger.error(msg)
|
|
1195
|
+
return False
|
|
1196
|
+
|
|
1197
|
+
elif directive == "process":
|
|
1198
|
+
try:
|
|
1199
|
+
if not valid:
|
|
1200
|
+
msg = f"Validation failed for {object_action} `{object_type}`"
|
|
1201
|
+
logger.error(msg)
|
|
1202
|
+
return None
|
|
1203
|
+
|
|
1204
|
+
# Resolve the find function from the output format set
|
|
1205
|
+
fmt = select_report_spec(report_spec, output_format)
|
|
1206
|
+
if not fmt:
|
|
1207
|
+
logger.error(f"Output format set '{report_spec}' not found or not compatible with '{output_format}'.")
|
|
1208
|
+
return None
|
|
1209
|
+
action = fmt.get("action", {})
|
|
1210
|
+
func_spec = action.get("function")
|
|
1211
|
+
if not func_spec or "." not in func_spec:
|
|
1212
|
+
func_spec = f"EgeriaTech.find_{object_type.replace(' ', '_').lower()}"
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
# Extract method name and get it from the composite client
|
|
1216
|
+
_, method_name = func_spec.split(".", 1)
|
|
1217
|
+
if not hasattr(egeria_client, method_name):
|
|
1218
|
+
logger.error(f"Method '{method_name}' not available on EgeriaTech client.")
|
|
1219
|
+
return None
|
|
1220
|
+
method = getattr(egeria_client, method_name)
|
|
1221
|
+
|
|
1222
|
+
# Build body and params
|
|
1223
|
+
list_md = f"\n# `{object_type}` with filter: `{search_string}`\n\n"
|
|
1224
|
+
body = set_find_body(object_type, attr)
|
|
1225
|
+
|
|
1226
|
+
params = {
|
|
1227
|
+
'search_string': search_string,
|
|
1228
|
+
'body': body,
|
|
1229
|
+
'output_format': output_format,
|
|
1230
|
+
'report_spec': report_spec,
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
# Call the resolved method
|
|
1234
|
+
struct = method(**params)
|
|
1235
|
+
|
|
1236
|
+
if output_format.upper() == "DICT":
|
|
1237
|
+
list_md += f"```\n{json.dumps(struct, indent=4)}\n```\n"
|
|
1238
|
+
else:
|
|
1239
|
+
list_md += struct
|
|
1240
|
+
logger.info(f"Wrote `{object_type}` for search string: `{search_string}` using format set '{report_spec}'")
|
|
1241
|
+
|
|
1242
|
+
return list_md
|
|
1243
|
+
|
|
1244
|
+
except Exception as e:
|
|
1245
|
+
logger.error(f"Error performing {command}: {e}")
|
|
1246
|
+
console.print_exception(show_locals=True)
|
|
1247
|
+
return None
|
|
1248
|
+
else:
|
|
1249
|
+
return None
|