pyegeria 5.3.9.9.3__py3-none-any.whl → 5.3.9.9.4__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.
- md_processing/__init__.py +49 -0
- md_processing/commands.json +3252 -0
- md_processing/dr_egeria_inbox/archive/dr_egeria_intro.md +254 -0
- md_processing/dr_egeria_inbox/archive/dr_egeria_intro_more_terms.md +696 -0
- md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part1.md +254 -0
- md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part2.md +298 -0
- md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part3.md +608 -0
- md_processing/dr_egeria_inbox/archive/dr_egeria_intro_part4.md +94 -0
- md_processing/dr_egeria_inbox/archive/freddie_intro.md +284 -0
- md_processing/dr_egeria_inbox/archive/freddie_intro_orig.md +275 -0
- md_processing/dr_egeria_inbox/archive/test-term.md +110 -0
- md_processing/dr_egeria_inbox/cat_test.md +100 -0
- md_processing/dr_egeria_inbox/data_field.md +54 -0
- md_processing/dr_egeria_inbox/data_spec.md +77 -0
- md_processing/dr_egeria_inbox/data_spec_test.md +2406 -0
- md_processing/dr_egeria_inbox/data_test.md +86 -0
- md_processing/dr_egeria_inbox/dr_egeria_intro_categories.md +168 -0
- md_processing/dr_egeria_inbox/dr_egeria_intro_part1.md +280 -0
- md_processing/dr_egeria_inbox/dr_egeria_intro_part2.md +313 -0
- md_processing/dr_egeria_inbox/dr_egeria_intro_part3.md +1073 -0
- md_processing/dr_egeria_inbox/dr_egeria_isc1.md +44 -0
- md_processing/dr_egeria_inbox/glossary_creation_experiment.ipynb +341 -0
- md_processing/dr_egeria_inbox/glossary_test1.md +324 -0
- md_processing/dr_egeria_inbox/rel.md +8 -0
- md_processing/dr_egeria_inbox/sb.md +119 -0
- md_processing/dr_egeria_inbox/search_test.md +39 -0
- md_processing/dr_egeria_inbox/solution-components.md +154 -0
- md_processing/dr_egeria_inbox/solution_blueprints.md +118 -0
- md_processing/dr_egeria_inbox/synonym_test.md +42 -0
- md_processing/dr_egeria_inbox/t1.md +0 -0
- md_processing/dr_egeria_inbox/t2.md +268 -0
- md_processing/dr_egeria_outbox/processed-2025-05-15 19:52-data_test.md +94 -0
- md_processing/dr_egeria_outbox/processed-2025-05-16 07:39-data_test.md +88 -0
- md_processing/dr_egeria_outbox/processed-2025-05-17 16:01-data_field.md +56 -0
- md_processing/dr_egeria_outbox/processed-2025-05-18 15:51-data_test.md +103 -0
- md_processing/dr_egeria_outbox/processed-2025-05-18 16:47-data_test.md +94 -0
- md_processing/dr_egeria_outbox/processed-2025-05-19 07:14-data_test.md +96 -0
- md_processing/dr_egeria_outbox/processed-2025-05-19 07:20-data_test.md +100 -0
- md_processing/dr_egeria_outbox/processed-2025-05-19 07:22-data_test.md +88 -0
- md_processing/md_commands/__init__.py +3 -0
- md_processing/md_commands/blueprint_commands.py +303 -0
- md_processing/md_commands/data_designer_commands.py +1182 -0
- md_processing/md_commands/glossary_commands.py +1144 -0
- md_processing/md_commands/project_commands.py +163 -0
- md_processing/md_processing_utils/__init__.py +4 -0
- md_processing/md_processing_utils/common_md_proc_utils.py +724 -0
- md_processing/md_processing_utils/common_md_utils.py +172 -0
- md_processing/md_processing_utils/extraction_utils.py +486 -0
- md_processing/md_processing_utils/md_processing_constants.py +112 -0
- md_processing/md_processing_utils/message_constants.py +19 -0
- {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.3.9.9.4.dist-info}/METADATA +1 -2
- {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.3.9.9.4.dist-info}/RECORD +55 -5
- {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.3.9.9.4.dist-info}/LICENSE +0 -0
- {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.3.9.9.4.dist-info}/WHEEL +0 -0
- {pyegeria-5.3.9.9.3.dist-info → pyegeria-5.3.9.9.4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,172 @@
|
|
1
|
+
"""
|
2
|
+
This file contains general utility functions for processing Egeria Markdown
|
3
|
+
"""
|
4
|
+
import os
|
5
|
+
import re
|
6
|
+
from datetime import datetime
|
7
|
+
from typing import Any
|
8
|
+
|
9
|
+
from rich import print
|
10
|
+
from rich.console import Console
|
11
|
+
from rich.markdown import Markdown
|
12
|
+
|
13
|
+
from pyegeria._globals import DEBUG_LEVEL
|
14
|
+
from md_processing.md_processing_utils.message_constants import message_types
|
15
|
+
|
16
|
+
# Constants
|
17
|
+
EGERIA_WIDTH = int(os.environ.get("EGERIA_WIDTH", "170"))
|
18
|
+
console = Console(width=EGERIA_WIDTH)
|
19
|
+
|
20
|
+
debug_level = DEBUG_LEVEL
|
21
|
+
global COMMAND_DEFINITIONS
|
22
|
+
|
23
|
+
def split_tb_string(input: str)-> [Any]:
|
24
|
+
"""Split the string and trim the items"""
|
25
|
+
l = [item.strip() for item in re.split(r'[;,\n]+',input)] if input is not None else None
|
26
|
+
return l
|
27
|
+
|
28
|
+
def str_to_bool(value: str) -> bool:
|
29
|
+
"""Converts a string to a boolean value."""
|
30
|
+
return value.lower() in ("yes", "true", "t", "1")
|
31
|
+
|
32
|
+
def render_markdown(markdown_text: str) -> None:
|
33
|
+
"""Renders the given markdown text in the console."""
|
34
|
+
console.print(Markdown(markdown_text))
|
35
|
+
|
36
|
+
|
37
|
+
def is_valid_iso_date(date_text) -> bool:
|
38
|
+
"""Checks if the given string is a valid ISO date."""
|
39
|
+
try:
|
40
|
+
datetime.strptime(date_text, '%Y-%m-%d')
|
41
|
+
return True
|
42
|
+
except ValueError:
|
43
|
+
return False
|
44
|
+
|
45
|
+
|
46
|
+
def set_debug_level(directive: str) -> None:
|
47
|
+
"""Sets the debug level for the script."""
|
48
|
+
global debug_level
|
49
|
+
if directive == "display":
|
50
|
+
debug_level = "display-only"
|
51
|
+
|
52
|
+
|
53
|
+
def get_current_datetime_string():
|
54
|
+
"""Returns the current date and time as a human-readable string."""
|
55
|
+
now = datetime.now().strftime('%Y-%m-%d %H:%M')
|
56
|
+
return now
|
57
|
+
|
58
|
+
|
59
|
+
def print_msg(msg_level: str, msg: str, verbosity: str):
|
60
|
+
"""
|
61
|
+
Prints a message based on its type and verbosity level.
|
62
|
+
|
63
|
+
This function handles the output of messages depending on the specified
|
64
|
+
verbosity level and message type. It uses predefined message types and
|
65
|
+
formats the output accordingly.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
msg_level: The type of the message, such as 'WARNING', 'ERROR', 'INFO', or
|
69
|
+
'ALWAYS'.
|
70
|
+
msg: The content of the message to display.
|
71
|
+
verbosity: The verbosity level, which determines how the message is
|
72
|
+
displayed ('verbose', 'quiet', or 'debug').
|
73
|
+
"""
|
74
|
+
if msg_level == "ALWAYS":
|
75
|
+
print(f"{message_types.get(msg_level, '')}{msg}")
|
76
|
+
else:
|
77
|
+
print(f"{message_types.get(msg_level, '')}{msg}")
|
78
|
+
# elif verbosity == "verbose" and msg_level in ["INFO", "WARNING", "ERROR"]:
|
79
|
+
# print(f"{message_types.get(msg_level, '')}{msg}")
|
80
|
+
# elif verbosity == "quiet" and msg_level in ["WARNING", "ERROR"]:
|
81
|
+
# print(f"{message_types.get(msg_level, '')}{msg}")
|
82
|
+
# elif verbosity == "debug" and msg_level in ["INFO", "WARNING", "ERROR", "DEBUG-INFO", "DEBUG-WARNING",
|
83
|
+
# "DEBUG-ERROR"]:
|
84
|
+
# print(f"{message_types.get(msg_level, '')}{msg}")
|
85
|
+
# elif verbosity == "display-only" and msg_level in ["ALWAYS", "ERROR"]:
|
86
|
+
# print(f"{message_types.get(msg_level, '')}{msg}")
|
87
|
+
|
88
|
+
|
89
|
+
def process_provenance_command(file_path: str, txt: [str]) -> str:
|
90
|
+
"""
|
91
|
+
Processes a provenance object_action by extracting the file path and current datetime.
|
92
|
+
|
93
|
+
Args:
|
94
|
+
file_path: The path to the file being processed.
|
95
|
+
txt: The text containing the provenance object_action.
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
A string containing the provenance information.
|
99
|
+
"""
|
100
|
+
now = get_current_datetime_string()
|
101
|
+
file_name = os.path.basename(file_path)
|
102
|
+
provenance = f"\n\n\n# Provenance:\n \n* Derived from processing file {file_name} on {now}\n"
|
103
|
+
return provenance
|
104
|
+
|
105
|
+
|
106
|
+
# Dictionary to store element information to avoid redundant API calls
|
107
|
+
element_dictionary = {}
|
108
|
+
|
109
|
+
|
110
|
+
def get_element_dictionary():
|
111
|
+
"""
|
112
|
+
Get the shared element dictionary.
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
dict: The shared element dictionary
|
116
|
+
"""
|
117
|
+
global element_dictionary
|
118
|
+
return element_dictionary
|
119
|
+
|
120
|
+
|
121
|
+
def update_element_dictionary(key, value):
|
122
|
+
"""
|
123
|
+
Update the shared element dictionary with a new key-value pair.
|
124
|
+
|
125
|
+
Args:
|
126
|
+
key (str): The key to update
|
127
|
+
value (dict): The value to associate with the key
|
128
|
+
"""
|
129
|
+
global element_dictionary
|
130
|
+
if (key is None or value is None):
|
131
|
+
print(f"===>ERROR Key is {key} and value is {value}")
|
132
|
+
return
|
133
|
+
element_dictionary[key] = value
|
134
|
+
|
135
|
+
|
136
|
+
def clear_element_dictionary():
|
137
|
+
"""
|
138
|
+
Clear the shared element dictionary.
|
139
|
+
"""
|
140
|
+
global element_dictionary
|
141
|
+
element_dictionary.clear()
|
142
|
+
|
143
|
+
|
144
|
+
def is_present(value: str) -> bool:
|
145
|
+
global element_dictionary
|
146
|
+
present = value in element_dictionary.keys() or any(
|
147
|
+
value in inner_dict.values() for inner_dict in element_dictionary.values())
|
148
|
+
return present
|
149
|
+
|
150
|
+
|
151
|
+
def find_key_with_value(value: str) -> str | None:
|
152
|
+
"""
|
153
|
+
Finds the top-level key whose nested dictionary contains the given value.
|
154
|
+
|
155
|
+
Args:
|
156
|
+
data (dict): A dictionary where keys map to nested dictionaries.
|
157
|
+
value (str): The value to search for.
|
158
|
+
|
159
|
+
Returns:
|
160
|
+
str | None: The top-level key that contains the value, or None if not found.
|
161
|
+
"""
|
162
|
+
global element_dictionary
|
163
|
+
# Check if the value matches a top-level key
|
164
|
+
if value in element_dictionary.keys():
|
165
|
+
return value
|
166
|
+
|
167
|
+
# Check if the value exists in any of the nested dictionaries
|
168
|
+
for key, inner_dict in element_dictionary.items():
|
169
|
+
if value in inner_dict.values():
|
170
|
+
return key
|
171
|
+
|
172
|
+
return None # If value not found
|
@@ -0,0 +1,486 @@
|
|
1
|
+
"""
|
2
|
+
This file contains functions for extracting data from text for Egeria Markdown processing
|
3
|
+
"""
|
4
|
+
import re
|
5
|
+
from typing import Any
|
6
|
+
|
7
|
+
from md_processing.md_processing_utils.common_md_utils import (print_msg, find_key_with_value, get_element_dictionary,
|
8
|
+
update_element_dictionary)
|
9
|
+
from md_processing.md_processing_utils.message_constants import INFO, EXISTS_REQUIRED
|
10
|
+
from md_processing.md_processing_utils.md_processing_constants import debug_level
|
11
|
+
from pyegeria._globals import NO_ELEMENTS_FOUND
|
12
|
+
from pyegeria.egeria_tech_client import EgeriaTech
|
13
|
+
|
14
|
+
|
15
|
+
def extract_command_plus(block: str) -> tuple[str, str, str] | None:
|
16
|
+
"""
|
17
|
+
Extracts a multi-word object and its associated action from the given block of text.
|
18
|
+
|
19
|
+
This function searches for a pattern in the format of `#...##` or `#...\n`
|
20
|
+
inside the provided string `block`. The matched pattern is split into
|
21
|
+
two parts: the action and the object type. The action is expected to
|
22
|
+
be the first part, while the rest is treated as the object type. If
|
23
|
+
no match is found, the function returns None.
|
24
|
+
|
25
|
+
Lines beginning with '>' are ignored.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
block: A string containing the block of text to search for the
|
29
|
+
object_action and action.
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
A tuple containing the object_action, the object type, and the object action if a
|
33
|
+
match is found. Otherwise, returns None.
|
34
|
+
"""
|
35
|
+
# Filter out lines beginning with '>'
|
36
|
+
filtered_lines = [line for line in block.split('\n') if not line.strip().startswith('>')]
|
37
|
+
filtered_block = '\n'.join(filtered_lines)
|
38
|
+
|
39
|
+
match = re.search(r"#(.*?)(?:##|\n|$)", filtered_block) # Using a non capturing group
|
40
|
+
if match:
|
41
|
+
clean_match = match.group(1).strip()
|
42
|
+
if ' ' in clean_match:
|
43
|
+
parts = clean_match.split(' ')
|
44
|
+
object_action = parts[0].strip()
|
45
|
+
# Join the rest of the parts to allow object_type to be one or two words
|
46
|
+
object_type = ' '.join(parts[1:]).strip()
|
47
|
+
else:
|
48
|
+
object_type = clean_match.split(' ')[1].strip()
|
49
|
+
object_action = clean_match.split(' ')[0].strip()
|
50
|
+
|
51
|
+
return clean_match, object_type, object_action
|
52
|
+
return None
|
53
|
+
|
54
|
+
|
55
|
+
def extract_command(block: str) -> str | None:
|
56
|
+
"""
|
57
|
+
Extracts a object_action from a block of text that is contained between a single hash ('#') and
|
58
|
+
either a double hash ('##'), a newline character, or the end of the string.
|
59
|
+
|
60
|
+
The function searches for a specific pattern within the block of text and extracts the
|
61
|
+
content that appears immediately after a single hash ('#'). Ensures that the extracted
|
62
|
+
content is appropriately trimmed of leading or trailing whitespace, if present.
|
63
|
+
|
64
|
+
Args:
|
65
|
+
block: A string representing the block of text to process. Contains the content
|
66
|
+
in which the object_action and delimiters are expected to be present.
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
The extracted object_action as a string if a match is found, otherwise None.
|
70
|
+
"""
|
71
|
+
match = re.search(r"#(.*?)(?:##|\n|$)", block) # Using a non-capturing group
|
72
|
+
if match:
|
73
|
+
return match.group(1).strip()
|
74
|
+
return None
|
75
|
+
|
76
|
+
|
77
|
+
def extract_attribute(text: str, labels: set) -> str | None:
|
78
|
+
"""
|
79
|
+
Extracts the attribute value from a string.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
text: The input string.
|
83
|
+
labels: List of equivalent labels to search for
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
The value of the attribute, or None if not found.
|
87
|
+
|
88
|
+
Note:
|
89
|
+
Lines beginning with '>' are ignored.
|
90
|
+
"""
|
91
|
+
# Iterate over the list of labels
|
92
|
+
for label in labels:
|
93
|
+
# Construct pattern for the current label
|
94
|
+
# text = re.sub(r'\s+', ' ', text).strip()
|
95
|
+
text = re.sub(r'\n\n+', '\n\n', text).strip()
|
96
|
+
|
97
|
+
label = label.strip()
|
98
|
+
pattern = rf"##\s*{re.escape(label)}\s*\n(?:\s*\n)*?(.*?)(?:#|___|$)"
|
99
|
+
|
100
|
+
# pattern = rf"##\s+{re.escape(label)}\n(.*?)(?:#|___|$)" # modified from --- to enable embedded tables
|
101
|
+
match = re.search(pattern, text, re.DOTALL)
|
102
|
+
if match:
|
103
|
+
# Extract matched text
|
104
|
+
matched_text = match.group(1)
|
105
|
+
|
106
|
+
# Filter out lines beginning with '>'
|
107
|
+
filtered_lines = [line for line in matched_text.split('\n') if not line.strip().startswith('>')]
|
108
|
+
filtered_text = '\n'.join(filtered_lines)
|
109
|
+
|
110
|
+
# Replace consecutive \n with a single \n
|
111
|
+
extracted_text = re.sub(r'\n+', '\n', filtered_text)
|
112
|
+
if not extracted_text.isspace() and extracted_text:
|
113
|
+
return extracted_text.strip() # Return the cleaned text - I removed the title casing
|
114
|
+
|
115
|
+
return None
|
116
|
+
|
117
|
+
|
118
|
+
def process_simple_attribute(txt: str, labels: set, if_missing: str = INFO) -> str | None:
|
119
|
+
"""Process a simple attribute based on the provided labels and if_missing value.
|
120
|
+
Extract the attribute value from the text and return it if it exists.
|
121
|
+
If it doesn`t exist, return None and print an error message with severity of if_missing.
|
122
|
+
|
123
|
+
Parameters:
|
124
|
+
----------
|
125
|
+
txt: str
|
126
|
+
The block of object_action text to extract attributes from.
|
127
|
+
labels: list
|
128
|
+
The possible attribute labels to search for. The first label will be used in messages.
|
129
|
+
if_missing: str, default is INFO
|
130
|
+
Can be one of "WARNING", "ERROR", "INFO". The severity of the missing attribute.
|
131
|
+
"""
|
132
|
+
if if_missing not in ["WARNING", "ERROR", "INFO"]:
|
133
|
+
print_msg("ERROR", "Invalid severity for missing attribute", debug_level)
|
134
|
+
return None
|
135
|
+
|
136
|
+
attribute = extract_attribute(txt, labels)
|
137
|
+
|
138
|
+
if attribute is None:
|
139
|
+
if if_missing == INFO:
|
140
|
+
msg = f"Optional attribute with labels `{labels}` missing"
|
141
|
+
else:
|
142
|
+
msg = f"Missing attribute with labels `{labels}` "
|
143
|
+
print_msg(if_missing, msg, debug_level)
|
144
|
+
return None
|
145
|
+
return attribute
|
146
|
+
|
147
|
+
|
148
|
+
# def process_simple_attribute(txt: str, labels: list[str], if_missing: str = INFO) -> str | None:
|
149
|
+
# """
|
150
|
+
# Processes a simple attribute from a string.
|
151
|
+
#
|
152
|
+
# Args:
|
153
|
+
# txt: The input string.
|
154
|
+
# labels: List of equivalent labels to search for
|
155
|
+
# if_missing: The message level to use if the attribute is missing.
|
156
|
+
#
|
157
|
+
# Returns:
|
158
|
+
# The value of the attribute, or None if not found.
|
159
|
+
# """
|
160
|
+
# from md_processing.md_processing_utils.common_utils import debug_level, print_msg
|
161
|
+
#
|
162
|
+
# attribute = extract_attribute(txt, labels)
|
163
|
+
# if attribute is None and if_missing:
|
164
|
+
# msg = f"No {labels[0]} found"
|
165
|
+
# print_msg(if_missing, msg, debug_level)
|
166
|
+
# return attribute
|
167
|
+
|
168
|
+
|
169
|
+
def process_name_list(egeria_client: EgeriaTech, element_type: str, txt: str, element_labels: set) -> tuple[str,
|
170
|
+
list[Any], bool | Any, bool | None | Any] | None:
|
171
|
+
"""
|
172
|
+
Processes a list of names specified in the given text, retrieves details for each
|
173
|
+
element based on the provided type, and generates a list of valid qualified names.
|
174
|
+
|
175
|
+
The function reads a text block, extracts a list of element names according to the specified
|
176
|
+
element type, looks them up using the provided Egeria client, and classifies them as valid or
|
177
|
+
invalid. It returns the processed names, a list of qualified names, and validity and existence
|
178
|
+
flags.
|
179
|
+
|
180
|
+
Args:
|
181
|
+
|
182
|
+
egeria_client (EgeriaTech): The client instance to connect and query elements from an
|
183
|
+
external system.
|
184
|
+
Element_type (str): The type of element, such as schema or attribute, to process.
|
185
|
+
Txt (str): The raw input text containing element names to be processed.
|
186
|
+
element_labels: a list of equivalent label names to use in processing the element.
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
tuple[str | None, list | None, bool, bool]: A tuple containing:
|
190
|
+
- Concatenated valid input names as a single string (or None if empty).
|
191
|
+
- A list of known qualified names extracted from the processed elements.
|
192
|
+
- A boolean indicating whether all elements are valid.
|
193
|
+
- A boolean indicating whether all elements exist.
|
194
|
+
"""
|
195
|
+
valid = True
|
196
|
+
exists = True
|
197
|
+
elements = ""
|
198
|
+
new_element_list = []
|
199
|
+
|
200
|
+
elements_txt = extract_attribute(txt, element_labels)
|
201
|
+
|
202
|
+
if elements_txt is None:
|
203
|
+
msg = f"No {element_type} found"
|
204
|
+
print_msg("DEBUG-INFO", msg, debug_level)
|
205
|
+
|
206
|
+
else:
|
207
|
+
element_list = re.split(r'[,\n]+', elements_txt)
|
208
|
+
|
209
|
+
for element in element_list:
|
210
|
+
element_el = element.strip()
|
211
|
+
|
212
|
+
# Get the element using the generalized function
|
213
|
+
known_q_name, known_guid, el_valid, el_exists = get_element_by_name(egeria_client, element_type, element_el)
|
214
|
+
# print_msg("DEBUG-INFO", status_msg, debug_level)
|
215
|
+
|
216
|
+
if el_exists and el_valid:
|
217
|
+
elements = f"{element_el} {elements}" # list of the input names
|
218
|
+
new_element_list.append(known_q_name) # list of qualified names
|
219
|
+
elif not el_exists:
|
220
|
+
msg = f"No {element_type} `{element_el}` found"
|
221
|
+
print_msg("DEBUG-INFO", msg, debug_level)
|
222
|
+
valid = False
|
223
|
+
valid = valid if el_valid is None else (valid and el_valid)
|
224
|
+
exists = exists and el_exists
|
225
|
+
|
226
|
+
if elements:
|
227
|
+
# elements += "\n"
|
228
|
+
msg = f"Found {element_type}: {elements}"
|
229
|
+
print_msg("DEBUG-INFO", msg, debug_level)
|
230
|
+
else:
|
231
|
+
msg = " Name list contains one or more invalid qualified names."
|
232
|
+
print_msg("DEBUG-INFO", msg, debug_level)
|
233
|
+
return elements, new_element_list, valid, exists
|
234
|
+
|
235
|
+
|
236
|
+
# def process_name_list(egeria_client, element_type: str, txt: str, element_labels: list[str]) -> tuple[list, list,
|
237
|
+
# bool, bool]:
|
238
|
+
# """
|
239
|
+
# Processes a list of names from a string.
|
240
|
+
#
|
241
|
+
# Args:
|
242
|
+
# egeria_client: The Egeria client to use for validation.
|
243
|
+
# element_type: The type of element to process.
|
244
|
+
# txt: The input string.
|
245
|
+
# element_labels: List of equivalent labels to search for
|
246
|
+
#
|
247
|
+
# Returns:
|
248
|
+
# A tuple containing:
|
249
|
+
# - A list of element names
|
250
|
+
# - A list of element qualified names
|
251
|
+
# - A boolean indicating if all elements are valid
|
252
|
+
# - A boolean indicating if any elements exist
|
253
|
+
# """
|
254
|
+
# from md_processing.md_processing_utils.common_utils import debug_level, print_msg
|
255
|
+
#
|
256
|
+
# element_names = []
|
257
|
+
# element_q_names = []
|
258
|
+
# all_valid = True
|
259
|
+
# any_exist = False
|
260
|
+
#
|
261
|
+
# # Get the list of element names
|
262
|
+
# element_list = process_simple_attribute(txt, element_labels)
|
263
|
+
# if element_list:
|
264
|
+
# # Split the list by commas or newlines
|
265
|
+
# element_names = list(filter(None, re.split(r'[,\n]+', element_list.strip())))
|
266
|
+
#
|
267
|
+
# # Validate each element
|
268
|
+
# for element_name in element_names:
|
269
|
+
# element_name = element_name.strip()
|
270
|
+
# if element_name:
|
271
|
+
# element = get_element_by_name(egeria_client, element_type, element_name)
|
272
|
+
# if element:
|
273
|
+
# any_exist = True
|
274
|
+
# element_q_name = element.get('qualifiedName', None)
|
275
|
+
# if element_q_name:
|
276
|
+
# element_q_names.append(element_q_name)
|
277
|
+
# else:
|
278
|
+
# all_valid = False
|
279
|
+
# msg = f"Element {element_name} has no qualified name"
|
280
|
+
# print_msg("ERROR", msg, debug_level)
|
281
|
+
# else:
|
282
|
+
# all_valid = False
|
283
|
+
# msg = f"Element {element_name} not found"
|
284
|
+
# print_msg("ERROR", msg, debug_level)
|
285
|
+
#
|
286
|
+
# return element_names, element_q_names, all_valid, any_exist
|
287
|
+
|
288
|
+
def process_element_identifiers(egeria_client: EgeriaTech, element_type: str, element_labels: set, txt: str,
|
289
|
+
action: str, version: str = None) -> tuple[str, str, bool, bool]:
|
290
|
+
"""
|
291
|
+
Processes element identifiers by extracting display name and qualified name from the input text,
|
292
|
+
checking if the element exists in Egeria and validating the information.
|
293
|
+
|
294
|
+
Parameters
|
295
|
+
----------
|
296
|
+
egeria_client: EgeriaTech
|
297
|
+
Client object for interacting with Egeria.
|
298
|
+
element_type: str
|
299
|
+
type of element to process (e.g., 'blueprint', 'category', 'term')
|
300
|
+
element_labels: a list of equivalent label names to use in processing the element.
|
301
|
+
txt: str
|
302
|
+
A string representing the input text to be processed for extracting element identifiers.
|
303
|
+
action: str
|
304
|
+
The action object_action to be executed (e.g., 'Create', 'Update', 'Display', ...)
|
305
|
+
version: str, optional = None
|
306
|
+
An optional version identifier used if we need to construct the qualified name
|
307
|
+
|
308
|
+
Returns: tuple[str, str, str, bool, bool]
|
309
|
+
A tuple containing:
|
310
|
+
- qualified_name: Empty string or element identifier
|
311
|
+
- guid: Empty string or additional element information
|
312
|
+
- Valid: Boolean indicating if the element information is valid
|
313
|
+
- Exists: Boolean indicating if the element exists in Egeria
|
314
|
+
"""
|
315
|
+
valid = True
|
316
|
+
|
317
|
+
element_name = extract_attribute(txt, element_labels)
|
318
|
+
qualified_name = extract_attribute(txt, ["Qualified Name"])
|
319
|
+
|
320
|
+
if qualified_name:
|
321
|
+
q_name, guid, unique, exists = get_element_by_name(egeria_client, element_type,
|
322
|
+
qualified_name) # Qualified name could be different if it
|
323
|
+
# is being updated
|
324
|
+
else:
|
325
|
+
q_name, guid, unique, exists = get_element_by_name(egeria_client, element_type, element_name)
|
326
|
+
if unique is False:
|
327
|
+
msg = f"Multiple elements named {element_name} found"
|
328
|
+
print_msg("DEBUG-ERROR", msg, debug_level)
|
329
|
+
valid = False
|
330
|
+
|
331
|
+
if action == "Update" and not exists:
|
332
|
+
msg = f"Element {element_name} does not exist"
|
333
|
+
print_msg("DEBUG-ERROR", msg, debug_level)
|
334
|
+
valid = False
|
335
|
+
|
336
|
+
elif action == "Update" and exists:
|
337
|
+
msg = f"Element {element_name} exists"
|
338
|
+
print_msg("DEBUG-INFO", msg, debug_level)
|
339
|
+
|
340
|
+
elif action == "Create" and exists:
|
341
|
+
msg = f"Element {element_name} already exists"
|
342
|
+
print_msg("DEBUG-ERROR", msg, debug_level)
|
343
|
+
valid = False
|
344
|
+
|
345
|
+
elif action == "Create" and not exists:
|
346
|
+
msg = f"{element_type} `{element_name}` does not exist"
|
347
|
+
print_msg("DEBUG-INFO", msg, debug_level)
|
348
|
+
|
349
|
+
if q_name is None and qualified_name is None:
|
350
|
+
q_name = egeria_client.__create_qualified_name__(element_type, element_name, version_identifier=version)
|
351
|
+
update_element_dictionary(q_name, {'display_name': element_name})
|
352
|
+
elif qualified_name:
|
353
|
+
update_element_dictionary(qualified_name, {'display_name': element_name})
|
354
|
+
elif action == EXISTS_REQUIRED:
|
355
|
+
if not exists:
|
356
|
+
msg = f"Required {element_type} `{element_name}` does not exist"
|
357
|
+
print_msg("DEBUG-ERROR", msg, debug_level)
|
358
|
+
valid = False
|
359
|
+
else:
|
360
|
+
msg = f"Required {element_type} `{element_name}` exists"
|
361
|
+
print_msg("DEBUG-INFO", msg, debug_level)
|
362
|
+
valid = True
|
363
|
+
|
364
|
+
return q_name, guid, valid, exists
|
365
|
+
|
366
|
+
|
367
|
+
def get_element_by_name(egeria_client, element_type: str, element_name: str) -> tuple[
|
368
|
+
str | None, str | None, bool | None, bool | None]:
|
369
|
+
"""
|
370
|
+
Generalized function to retrieve an element by name based on its type.
|
371
|
+
|
372
|
+
Parameters:
|
373
|
+
egeria_client: Client
|
374
|
+
Client object for interacting with Egeria.
|
375
|
+
element_type: str
|
376
|
+
The type of element to retrieve (e.g., 'blueprint', 'category', 'term').
|
377
|
+
element_name: str
|
378
|
+
The name of the element to retrieve.
|
379
|
+
|
380
|
+
Returns:
|
381
|
+
tuple of qualified_name, guid, uniqye, exists
|
382
|
+
"""
|
383
|
+
unique = None
|
384
|
+
|
385
|
+
element_dict = get_element_dictionary()
|
386
|
+
q_name = find_key_with_value(element_name)
|
387
|
+
if q_name: # use information from element_dictionary
|
388
|
+
guid = element_dict[q_name].get('guid', None)
|
389
|
+
unique = True
|
390
|
+
|
391
|
+
if guid is not None: # Found complete entry in element_dictionary
|
392
|
+
msg = f'Found {element_type} qualified name and guid in element_dictionary for `{element_name}`'
|
393
|
+
print_msg("DEBUG-INFO", msg, debug_level)
|
394
|
+
exists = True
|
395
|
+
return q_name, guid, unique, exists
|
396
|
+
|
397
|
+
else: # Missing guid from element_dictionary
|
398
|
+
guid = egeria_client.get_element_guid_by_unique_name(element_name)
|
399
|
+
if guid == NO_ELEMENTS_FOUND:
|
400
|
+
guid = None
|
401
|
+
exists = False
|
402
|
+
msg = f"No {element_type} guid found with name {element_name} in Egeria"
|
403
|
+
print_msg("DEBUG-INFO", msg, debug_level)
|
404
|
+
|
405
|
+
return q_name, guid, unique, exists
|
406
|
+
else:
|
407
|
+
exists = True
|
408
|
+
update_element_dictionary(q_name, {'guid': guid})
|
409
|
+
msg = f"Found guid value of {guid} for {element_name} in Egeria"
|
410
|
+
print_msg("DEBUG-INFO", msg, debug_level)
|
411
|
+
|
412
|
+
return q_name, guid, unique, exists
|
413
|
+
|
414
|
+
# Haven't seen this element before
|
415
|
+
property_names = ['qualifiedName', 'name', 'displayName']
|
416
|
+
open_metadata_type_name = None
|
417
|
+
details = egeria_client.get_elements_by_property_value(element_name, property_names, open_metadata_type_name)
|
418
|
+
if isinstance(details, str):
|
419
|
+
msg = f"{element_type} `{element_name}` not found in Egeria"
|
420
|
+
print_msg("DEBUG-INFO", msg, debug_level)
|
421
|
+
exists = False
|
422
|
+
return None, None, unique, exists
|
423
|
+
if len(details) > 1:
|
424
|
+
msg = (f"More than one element with name {element_name} found, please specify a "
|
425
|
+
f"**Qualified Name**")
|
426
|
+
print_msg("DEBUG-ERROR", msg, debug_level)
|
427
|
+
unique = False
|
428
|
+
exists = None
|
429
|
+
return element_name, None, unique, exists
|
430
|
+
|
431
|
+
el_qname = details[0]["properties"].get('qualifiedName', None)
|
432
|
+
el_guid = details[0]['elementHeader']['guid']
|
433
|
+
el_display_name = details[0]["properties"].get('displayName', None)
|
434
|
+
if el_display_name is None:
|
435
|
+
el_display_name = details[0]["properties"].get('name', None)
|
436
|
+
update_element_dictionary(el_qname, {
|
437
|
+
'guid': el_guid, 'displayName': el_display_name
|
438
|
+
})
|
439
|
+
msg = f"Found {element_type} `{el_display_name}` with qualified name `{el_qname}`"
|
440
|
+
print_msg("DEBUG-INFO", msg, debug_level)
|
441
|
+
exists = True
|
442
|
+
unique = True
|
443
|
+
return el_qname, el_guid, unique, exists
|
444
|
+
|
445
|
+
|
446
|
+
def update_a_command(txt: str, object_action: str, obj_type: str, q_name: str, u_guid: str) -> str:
|
447
|
+
"""
|
448
|
+
Updates a command in a string.
|
449
|
+
|
450
|
+
Args:
|
451
|
+
txt: The input string.
|
452
|
+
object_action: The command to update.
|
453
|
+
obj_type: The type of object to update.
|
454
|
+
q_name: The qualified name of the object.
|
455
|
+
u_guid: The GUID of the object.
|
456
|
+
|
457
|
+
Returns:
|
458
|
+
The updated string.
|
459
|
+
"""
|
460
|
+
|
461
|
+
# Determine the new action
|
462
|
+
new_action = "Update" if object_action == "Create" else "Create"
|
463
|
+
|
464
|
+
# Replace the object_action
|
465
|
+
new_command = f"{new_action} {obj_type}"
|
466
|
+
pattern = rf"#\s*{object_action}\s+{obj_type}"
|
467
|
+
replacement = f"# {new_command}"
|
468
|
+
updated_txt = re.sub(pattern, replacement, txt)
|
469
|
+
|
470
|
+
# Add qualified name and GUID if updating
|
471
|
+
if new_action == "Update" and q_name and u_guid:
|
472
|
+
# Check if Qualified Name section exists
|
473
|
+
if "## Qualified Name" not in updated_txt:
|
474
|
+
# Add Qualified Name section before the first ## that's not part of the object_action
|
475
|
+
pattern = r"(##\s+[^#\n]+)"
|
476
|
+
replacement = f"## Qualified Name\n{q_name}\n\n\\1"
|
477
|
+
updated_txt = re.sub(pattern, replacement, updated_txt, count=1)
|
478
|
+
|
479
|
+
# Check if GUID section exists
|
480
|
+
if "## GUID" not in updated_txt and "## guid" not in updated_txt:
|
481
|
+
# Add GUID section before the first ## that's not part of the object_action or Qualified Name
|
482
|
+
pattern = r"(##\s+(?!Qualified Name)[^#\n]+)"
|
483
|
+
replacement = f"## GUID\n{u_guid}\n\n\\1"
|
484
|
+
updated_txt = re.sub(pattern, replacement, updated_txt, count=1)
|
485
|
+
|
486
|
+
return updated_txt
|