commons-metrics 0.0.17__py3-none-any.whl → 0.0.18__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.
- commons_metrics/__init__.py +1 -1
- commons_metrics/cache_manager.py +71 -0
- commons_metrics/connection_database.py +18 -0
- commons_metrics/date_utils.py +66 -0
- commons_metrics/s3_file_manager.py +104 -0
- commons_metrics/text_simplifier.py +48 -0
- commons_metrics/variable_finder.py +187 -0
- {commons_metrics-0.0.17.dist-info → commons_metrics-0.0.18.dist-info}/METADATA +1 -1
- commons_metrics-0.0.18.dist-info/RECORD +19 -0
- commons_metrics-0.0.17.dist-info/RECORD +0 -13
- {commons_metrics-0.0.17.dist-info → commons_metrics-0.0.18.dist-info}/WHEEL +0 -0
- {commons_metrics-0.0.17.dist-info → commons_metrics-0.0.18.dist-info}/licenses/LICENSE +0 -0
- {commons_metrics-0.0.17.dist-info → commons_metrics-0.0.18.dist-info}/top_level.txt +0 -0
commons_metrics/__init__.py
CHANGED
|
@@ -6,4 +6,4 @@ from .github_api_client import GitHubAPIClient
|
|
|
6
6
|
from .azure_devops_client import AzureDevOpsClient
|
|
7
7
|
|
|
8
8
|
__all__ = ['Util', 'DatabaseConnection', 'ComponentRepository', 'UpdateDesignSystemComponents', 'GitHubAPIClient', 'AzureDevOpsClient']
|
|
9
|
-
__version__ = '0.0.
|
|
9
|
+
__version__ = '0.0.18'
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import urllib3
|
|
3
|
+
|
|
4
|
+
from lib.commons_metrics.commons_metrics.s3_file_manager import S3FileManager
|
|
5
|
+
|
|
6
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def load_cache_or_fetch(path: str, fetch_fn, clear_cache: bool = False):
|
|
10
|
+
"""
|
|
11
|
+
Load data from a JSON cache stored in S3 using S3FileManager,
|
|
12
|
+
or fetch the data and update the cache.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
path (str): Cache key relative to "cache/", e.g. "logs/123.json"
|
|
16
|
+
fetch_fn (Callable): Function returning JSON-serializable data
|
|
17
|
+
clear_cache (bool): If True, delete existing cache before fetching
|
|
18
|
+
Returns:
|
|
19
|
+
Any: Cached or freshly fetched data
|
|
20
|
+
"""
|
|
21
|
+
s3 = S3FileManager()
|
|
22
|
+
key = f"cache/{path}"
|
|
23
|
+
if clear_cache:
|
|
24
|
+
try:
|
|
25
|
+
s3.s3.delete_object(Bucket=s3.bucket, Key=key)
|
|
26
|
+
except Exception:
|
|
27
|
+
pass
|
|
28
|
+
data = s3.load_json(key)
|
|
29
|
+
|
|
30
|
+
if data:
|
|
31
|
+
return data
|
|
32
|
+
|
|
33
|
+
data = fetch_fn()
|
|
34
|
+
s3.save_json(data, key)
|
|
35
|
+
|
|
36
|
+
return data
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_data_from_api(url: str, headers: dict, auth_api) -> list:
|
|
40
|
+
"""
|
|
41
|
+
Sends a GET request to the specified API and returns the JSON response if successful.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
url (str): The API endpoint URL.
|
|
45
|
+
headers (dict): HTTP headers for the request.
|
|
46
|
+
auth_api: Authentication object for the request.
|
|
47
|
+
Returns:
|
|
48
|
+
list or dict: JSON response from the API if successful, otherwise an empty list.
|
|
49
|
+
"""
|
|
50
|
+
response = requests.get(url, headers=headers, auth=auth_api, verify=False)
|
|
51
|
+
if response.ok:
|
|
52
|
+
return response.json()
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def post_data_to_api(url: str, headers: dict, body: dict, auth_api) -> list:
|
|
57
|
+
"""
|
|
58
|
+
Sends a POST request to the specified API with the given body and returns the JSON response if successful.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
url (str): The API endpoint URL.
|
|
62
|
+
headers (dict): HTTP headers for the request.
|
|
63
|
+
body (dict): Data to send in the POST request.
|
|
64
|
+
auth_api: Authentication object for the request.
|
|
65
|
+
Returns:
|
|
66
|
+
list or dict: JSON response from the API if successful, otherwise an empty list.
|
|
67
|
+
"""
|
|
68
|
+
response = requests.post(url, headers=headers, auth=auth_api, data=body, verify=False)
|
|
69
|
+
if response.ok:
|
|
70
|
+
return response.json()
|
|
71
|
+
return []
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from lib.commons_metrics.commons_metrics import DatabaseConnection, Util, ComponentRepository
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_connection_database_from_secret(secret_name: str, logger: str, aws_region: str) -> ComponentRepository:
|
|
5
|
+
"""
|
|
6
|
+
Retrieve connection database from AWS secrets manager
|
|
7
|
+
"""
|
|
8
|
+
secret_json = Util.get_secret_aws(secret_name, logger, aws_region)
|
|
9
|
+
db_connection = DatabaseConnection()
|
|
10
|
+
db_connection.connect({
|
|
11
|
+
'host': secret_json["host"],
|
|
12
|
+
'port': secret_json["port"],
|
|
13
|
+
'dbname': secret_json["dbname"],
|
|
14
|
+
'username': secret_json["username"],
|
|
15
|
+
'password': secret_json["password"]
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
return ComponentRepository(db_connection)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import List, Dict
|
|
3
|
+
|
|
4
|
+
from dateutil import parser
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def parse_datetime(date_str: str) -> datetime:
|
|
8
|
+
"""
|
|
9
|
+
Converts a string in ISO 8601 format to a datetime object.
|
|
10
|
+
If the string is empty or None, it returns the current date and time with the time zone.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
date_str(str): Date in ISO 8601 format (e.g., "2025-12-02T10:00:00+00:00").
|
|
14
|
+
Returns:
|
|
15
|
+
datetime: Datetime object corresponding to the string, or the current date if empty.
|
|
16
|
+
"""
|
|
17
|
+
if not date_str:
|
|
18
|
+
return datetime.now().astimezone()
|
|
19
|
+
return parser.isoparse(date_str)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_hours_difference_from_strings(start_date: str, end_date: str) -> float:
|
|
23
|
+
"""
|
|
24
|
+
Calculate the difference in hours between two dates given as ISO 8601 strings.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
start_date(str): Start date in ISO 8601 format.
|
|
28
|
+
end_date(str): End date in ISO 8601 format.
|
|
29
|
+
Returns:
|
|
30
|
+
float: Difference in hours (can be negative if end_date < start_date).
|
|
31
|
+
"""
|
|
32
|
+
start = parse_datetime(start_date)
|
|
33
|
+
end = parse_datetime(end_date)
|
|
34
|
+
return get_hours_difference(start, end)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_hours_difference(start_date: datetime, end_date: datetime) -> float:
|
|
38
|
+
"""
|
|
39
|
+
Calculate the difference in hours between two datetime objects.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
start_date(datetime): Start date.
|
|
43
|
+
end_date(datetime): End date.
|
|
44
|
+
Returns:
|
|
45
|
+
float: Difference in hours (can be negative if end_date < start_date).
|
|
46
|
+
"""
|
|
47
|
+
return (end_date - start_date).total_seconds() / 3600
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def sort_by_date(list_dicts: List[Dict], date_attribute_name: str) -> List[Dict]:
|
|
51
|
+
"""
|
|
52
|
+
Sorts a list of dictionaries by a specified date attribute in descending order.
|
|
53
|
+
The method uses the `parse_datetime` function to convert date strings into datetime objects
|
|
54
|
+
for accurate sorting.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
list_dicts (List[Dict]): A list of dictionaries containing date attributes.
|
|
58
|
+
date_attribute_name (str): The key name of the date attribute in each dictionary.
|
|
59
|
+
Returns:
|
|
60
|
+
List[Dict]: A new list of dictionaries sorted by the given date attribute in descending order.
|
|
61
|
+
"""
|
|
62
|
+
return sorted(
|
|
63
|
+
list_dicts,
|
|
64
|
+
key=lambda x: parse_datetime(x.get(date_attribute_name)),
|
|
65
|
+
reverse=True
|
|
66
|
+
)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
from typing import List
|
|
5
|
+
import boto3
|
|
6
|
+
from awsglue.utils import getResolvedOptions
|
|
7
|
+
from botocore.exceptions import ClientError
|
|
8
|
+
|
|
9
|
+
args = getResolvedOptions(sys.argv, ['bucket', 'aws_region'])
|
|
10
|
+
|
|
11
|
+
class S3FileManager:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.bucket = args['bucket']
|
|
14
|
+
if not self.bucket:
|
|
15
|
+
raise ValueError("Environment variable BUCKET is required")
|
|
16
|
+
|
|
17
|
+
region = args['aws_region']
|
|
18
|
+
|
|
19
|
+
client_params = {
|
|
20
|
+
"service_name": "s3",
|
|
21
|
+
"region_name": region,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
self.s3 = boto3.client(**client_params)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def load_json(self, key: str) -> dict:
|
|
28
|
+
"""
|
|
29
|
+
Loads JSON data from a file.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
key (str): Key or Path to the JSON file.
|
|
33
|
+
Returns:
|
|
34
|
+
dict: Parsed JSON data as a dictionary.
|
|
35
|
+
Returns an empty dictionary if the file does not exist,
|
|
36
|
+
if the JSON is invalid, or if an unexpected error occurs.
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
response = self.s3.get_object(Bucket=self.bucket, Key=key)
|
|
40
|
+
content = response["Body"].read().decode("utf-8")
|
|
41
|
+
return json.loads(content)
|
|
42
|
+
except ClientError as e:
|
|
43
|
+
pass
|
|
44
|
+
except json.JSONDecodeError:
|
|
45
|
+
print(f"[ERROR] Invalid JSON in S3 object: {key}")
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f"[ERROR] Unexpected error: {e}")
|
|
48
|
+
return {}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def save_json(self, data: dict, key: str) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Saves a dictionary as a JSON file at the specified key or path.
|
|
54
|
+
Creates directories if they do not exist.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
data (dict): Data to save.
|
|
58
|
+
key (str): Key or Path where the JSON file will be stored.
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
self.s3.put_object(
|
|
62
|
+
Bucket=self.bucket,
|
|
63
|
+
Key=key,
|
|
64
|
+
Body=json.dumps(data, ensure_ascii=False, indent=4),
|
|
65
|
+
ContentType="application/json",
|
|
66
|
+
)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print(f"[ERROR] Saving JSON to S3: {e}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def list_files(self, prefix: str, extension: str = ".json") -> List[str]:
|
|
72
|
+
"""
|
|
73
|
+
Return a sorted list of files in the given folder that match the provided extension.
|
|
74
|
+
This function:
|
|
75
|
+
- Collects files pattern: `*{extension}`.
|
|
76
|
+
- Sorts results in ascending order by filename (lexicographic).
|
|
77
|
+
- Returns an empty list when no files match.
|
|
78
|
+
Parameters
|
|
79
|
+
prefix: str
|
|
80
|
+
Absolute or relative path to the target folder.
|
|
81
|
+
extension : str, optional
|
|
82
|
+
File extension to match (default: ".json"). The value is appended directly to
|
|
83
|
+
the glob pattern `*{extension}`; for reliable matching use a leading dot,
|
|
84
|
+
e.g., ".json", ".txt".
|
|
85
|
+
Returns
|
|
86
|
+
List[pathlib.Path]
|
|
87
|
+
A list of `Path` objects representing matching files. If the folder does not
|
|
88
|
+
exist or no files match, returns an empty list.
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
response = self.s3.list_objects_v2(Bucket=self.bucket, Prefix=prefix)
|
|
92
|
+
|
|
93
|
+
if "Contents" not in response:
|
|
94
|
+
return []
|
|
95
|
+
|
|
96
|
+
return sorted(
|
|
97
|
+
obj["Key"]
|
|
98
|
+
for obj in response["Contents"]
|
|
99
|
+
if obj["Key"].endswith(extension)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
print(f"[ERROR] Listing files in S3: {e}")
|
|
104
|
+
return []
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import html
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def compact_data(data: dict) -> str:
|
|
7
|
+
"""
|
|
8
|
+
Removes keys with empty values (None, "", [], {}) from a dictionary
|
|
9
|
+
and returns a compact JSON string without unnecessary spaces.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
data(dict): Original dictionary.
|
|
13
|
+
Returns:
|
|
14
|
+
str: Compact JSON representation of the filtered dictionary.
|
|
15
|
+
"""
|
|
16
|
+
data = {k: v for k, v in data.items() if v not in (None, "", [], {})}
|
|
17
|
+
return json.dumps(data, separators=(",", ":"))
|
|
18
|
+
|
|
19
|
+
def clear_text(text: str) -> str:
|
|
20
|
+
"""
|
|
21
|
+
Cleans text by removing HTML tags, Markdown links,
|
|
22
|
+
decoding HTML characters, removing escaped quotes, and
|
|
23
|
+
normalizing spaces.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
text (str): Original text.
|
|
27
|
+
Returns:
|
|
28
|
+
str: Cleaned and simplified text.
|
|
29
|
+
"""
|
|
30
|
+
if not text:
|
|
31
|
+
return ""
|
|
32
|
+
|
|
33
|
+
# Remove HTML tags
|
|
34
|
+
text = re.sub(r'<[^>]+>', '', text)
|
|
35
|
+
|
|
36
|
+
# Replace Markdown links [Text](URLxt
|
|
37
|
+
text = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', text)
|
|
38
|
+
|
|
39
|
+
# Decode HTML characters ( , <, etc.)
|
|
40
|
+
text = html.unescape(text)
|
|
41
|
+
|
|
42
|
+
# Remove escaped quotes
|
|
43
|
+
text = text.replace('\\"', '"').replace("\\'", "'")
|
|
44
|
+
|
|
45
|
+
# Remove line breaks, tabs, and multiple spaces
|
|
46
|
+
text = re.sub(r'\s+', ' ', text).strip()
|
|
47
|
+
|
|
48
|
+
return text
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def extract_issue_number(pr_body: str):
|
|
7
|
+
"""
|
|
8
|
+
Extracts an issue number from a pull request body text.
|
|
9
|
+
Looks for a pattern like '#123' preceded by whitespace.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
pr_body (str): The pull request body text.
|
|
13
|
+
Returns:
|
|
14
|
+
Optional[int]: The extracted issue number as an integer, or None if not found.
|
|
15
|
+
"""
|
|
16
|
+
match = re.search(r"\s+#(\d+)", pr_body or "", re.IGNORECASE)
|
|
17
|
+
return int(match.group(1)) if match else None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_code(text: str) -> Optional[str]:
|
|
21
|
+
"""
|
|
22
|
+
Extracts a code matching the pattern 'AW1234567' or 'NU1234567' from a string.
|
|
23
|
+
The code consists of two uppercase letters followed by seven digits.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
text (str): The input string.
|
|
27
|
+
Returns:
|
|
28
|
+
Optional[str]: The extracted code or None if not found.
|
|
29
|
+
"""
|
|
30
|
+
for tok in text.split('_'):
|
|
31
|
+
if re.fullmatch(r'[A-Z]{2}\d{7}', tok):
|
|
32
|
+
return tok
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_component_name(text: str) -> Optional[str]:
|
|
37
|
+
"""
|
|
38
|
+
Extracts a component name from a string based on underscore-separated parts.
|
|
39
|
+
If the last part is 'dxp', returns the two preceding parts joined by underscore.
|
|
40
|
+
Otherwise, returns the last two parts.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
text (str): The input string.
|
|
44
|
+
Returns:
|
|
45
|
+
Optional[str]: The component name or None if not enough parts.
|
|
46
|
+
"""
|
|
47
|
+
parts = [p for p in text.strip('_').split('_') if p]
|
|
48
|
+
if len(parts) >= 2:
|
|
49
|
+
if parts[-1].lower() == "dxp":
|
|
50
|
+
return f"{parts[-3]}_{parts[-2]}"
|
|
51
|
+
return '_'.join(parts[-2:])
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_component_name_from_image(image: str, release_name: str) -> Optional[str]:
|
|
56
|
+
"""
|
|
57
|
+
Extracts the component name from an image string.
|
|
58
|
+
If extraction fails, falls back to using release_name.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
image (str): The image string (e.g., 'repo/component:tag').
|
|
62
|
+
release_name (str): The fallback release name.
|
|
63
|
+
Returns:
|
|
64
|
+
Optional[str]: The component name.
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
tag = image.split('/')[-1]
|
|
68
|
+
repository_name = tag.split(':')[0]
|
|
69
|
+
return repository_name
|
|
70
|
+
except Exception:
|
|
71
|
+
return get_component_name(release_name)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def collect_all_variables(json_data, txt_variable_groups):
|
|
75
|
+
"""
|
|
76
|
+
Collects all variables from a nested JSON structure.
|
|
77
|
+
Searches for keys named 'variables' and merges them into a single dictionary.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
json_data (dict or list): The JSON data.
|
|
81
|
+
txt_variable_groups (str): The key name for variable groups.
|
|
82
|
+
Returns:
|
|
83
|
+
dict: A dictionary of all variables found.
|
|
84
|
+
"""
|
|
85
|
+
all_variables = {}
|
|
86
|
+
|
|
87
|
+
def loop_through_json(data):
|
|
88
|
+
if isinstance(data, dict):
|
|
89
|
+
for key, value in data.items():
|
|
90
|
+
if key == 'variables':
|
|
91
|
+
all_variables.update(value)
|
|
92
|
+
elif key == txt_variable_groups:
|
|
93
|
+
if isinstance(value, list):
|
|
94
|
+
for group in value:
|
|
95
|
+
if isinstance(group, dict) and 'variables' in group:
|
|
96
|
+
all_variables.update(group['variables'])
|
|
97
|
+
else:
|
|
98
|
+
loop_through_json(value)
|
|
99
|
+
elif isinstance(data, list):
|
|
100
|
+
for item in data:
|
|
101
|
+
loop_through_json(item)
|
|
102
|
+
|
|
103
|
+
loop_through_json(json_data)
|
|
104
|
+
return all_variables
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def resolve_value(value: str, all_variables: dict, visited=None) -> str:
|
|
108
|
+
"""
|
|
109
|
+
Resolves variable references in a string recursively.
|
|
110
|
+
Variables are referenced using the format $(VAR_NAME).
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
value (str): The string containing variable references.
|
|
114
|
+
all_variables (dict): Dictionary of variables and their values.
|
|
115
|
+
visited (set): Set of visited variables to detect cycles.
|
|
116
|
+
Returns:
|
|
117
|
+
str: The resolved string with all references replaced.
|
|
118
|
+
"""
|
|
119
|
+
if visited is None:
|
|
120
|
+
visited = set()
|
|
121
|
+
|
|
122
|
+
pattern = re.compile(r'\$\(([^)]+)\)')
|
|
123
|
+
while True:
|
|
124
|
+
matches = pattern.findall(value)
|
|
125
|
+
if not matches:
|
|
126
|
+
break
|
|
127
|
+
for match in matches:
|
|
128
|
+
if match in visited:
|
|
129
|
+
return f'$(CYCLE:{match})'
|
|
130
|
+
visited.add(match)
|
|
131
|
+
replacement = all_variables.get(match, {}).get('value', '')
|
|
132
|
+
resolved = resolve_value(replacement, all_variables, visited.copy())
|
|
133
|
+
value = value.replace(f'$({match})', resolved)
|
|
134
|
+
return value
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def search_in_json(search_value: str, search_type: str, json_data, is_json_from_azure: bool = False) -> Optional[str]:
|
|
138
|
+
"""
|
|
139
|
+
Searches for a variable in a nested JSON structure by key or value.
|
|
140
|
+
Resolves references if found.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
search_value (str): The value to search for.
|
|
144
|
+
search_type (str): 'clave' to search by key, 'valor' to search by value.
|
|
145
|
+
json_data (dict or list): The JSON data.
|
|
146
|
+
is_json_from_azure (bool): Whether the JSON is from Azure (changes key names).
|
|
147
|
+
Returns:
|
|
148
|
+
Optional[str]: The resolved value if found, otherwise None.
|
|
149
|
+
"""
|
|
150
|
+
txt_variable_groups = 'variableGroups' if is_json_from_azure else 'variable_groups'
|
|
151
|
+
search_value = search_value.lower()
|
|
152
|
+
all_variables = collect_all_variables(json_data, txt_variable_groups)
|
|
153
|
+
|
|
154
|
+
result_search = all_variables.get(search_value, {}).get('value', '')
|
|
155
|
+
if result_search and '$(' not in result_search:
|
|
156
|
+
return result_search
|
|
157
|
+
|
|
158
|
+
def recursive_search(data):
|
|
159
|
+
if isinstance(data, dict):
|
|
160
|
+
for key, value in data.items():
|
|
161
|
+
if key in ['variables', txt_variable_groups]:
|
|
162
|
+
if isinstance(value, dict):
|
|
163
|
+
for var_key, var_value in value.items():
|
|
164
|
+
if search_type == 'clave' and search_value == var_key.lower():
|
|
165
|
+
return resolve_value(var_value.get('value', ''), all_variables)
|
|
166
|
+
elif search_type == 'valor' and search_value == var_value.get('value', '').lower():
|
|
167
|
+
return resolve_value(var_value.get('value', ''), all_variables)
|
|
168
|
+
elif isinstance(value, list):
|
|
169
|
+
for item in value:
|
|
170
|
+
if isinstance(item, dict) and 'variables' in item:
|
|
171
|
+
for var_key, var_value in item['variables'].items():
|
|
172
|
+
if search_type == 'clave' and search_value == var_key.lower():
|
|
173
|
+
return resolve_value(var_value.get('value', ''), all_variables)
|
|
174
|
+
elif search_type == 'valor' and search_value == var_value.get('value', '').lower():
|
|
175
|
+
return resolve_value(var_value.get('value', ''), all_variables)
|
|
176
|
+
else:
|
|
177
|
+
result = recursive_search(value)
|
|
178
|
+
if result:
|
|
179
|
+
return result
|
|
180
|
+
elif isinstance(data, list):
|
|
181
|
+
for item in data:
|
|
182
|
+
result = recursive_search(item)
|
|
183
|
+
if result:
|
|
184
|
+
return result
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
return recursive_search(json_data)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
commons_metrics/__init__.py,sha256=zLT4kmogcGARPf9DuuBv_r72fwAdvy5ff9y3CNAOnyk,434
|
|
2
|
+
commons_metrics/azure_devops_client.py,sha256=sD130ggzZWUsNqkBVNrLH80_AN00CxQ4preP9sBdzHM,16778
|
|
3
|
+
commons_metrics/cache_manager.py,sha256=ARZBqUCGzpNeQuU2ixd8gu_kqW4PYujnN8Ve9EdIG00,2249
|
|
4
|
+
commons_metrics/commons_repos_client.py,sha256=SYIe1fFXM3qlc_G154AmorHScPS3rTsztKqxy3dD28w,4150
|
|
5
|
+
commons_metrics/connection_database.py,sha256=JvmEuhnnaE3MyQJfN4nXZzVuXB-hfWUAbJq4DEKVtO8,680
|
|
6
|
+
commons_metrics/database.py,sha256=570TtLZ9psNzvIp75UFLYph34cKVEz6eGJgxXyRyjW4,1285
|
|
7
|
+
commons_metrics/date_utils.py,sha256=owKtefTWQ6zT-a3ssurPlphjfjGyxtwrZ_EuFkrPhf0,2265
|
|
8
|
+
commons_metrics/github_api_client.py,sha256=yPLsqn_KvQDIXcNp7Ma8qQwWy8svpXNLpDX69vwL9BU,11465
|
|
9
|
+
commons_metrics/repositories.py,sha256=4hSA51Ft1qcZvHtNta_QKSkHIe78JmsVlKcihzkBcb4,9476
|
|
10
|
+
commons_metrics/s3_file_manager.py,sha256=qyviGsLKuNWeEPtHpITfbL62RmPeuf5CZTzHBnAUS_U,3489
|
|
11
|
+
commons_metrics/text_simplifier.py,sha256=U0oVy2tpOOin5MbfK-9R3euy2P_SkR0b8chto-qy0_M,1257
|
|
12
|
+
commons_metrics/update_design_components.py,sha256=QpY0GCCCMjdYOZ7b8oNigU9iTpiGx91CYsyWwN8WVDA,7660
|
|
13
|
+
commons_metrics/util.py,sha256=98zuynalXumQRh-BB0Bcjyoh6vS2BTOUM8tVgr7iS9Q,1225
|
|
14
|
+
commons_metrics/variable_finder.py,sha256=fTy4njbvZcwBgRDc3MbKejUJkgJS2Mj1ByzRtvS9uV8,7101
|
|
15
|
+
commons_metrics-0.0.18.dist-info/licenses/LICENSE,sha256=jsHZ2Sh1wCL74HC25pDDGXCyQ0xgsTAy62FvEnehKIg,1067
|
|
16
|
+
commons_metrics-0.0.18.dist-info/METADATA,sha256=bRiZoXClNrqzTcwgjQI9ozHnd5mLofczOH9lv3Xk4AE,402
|
|
17
|
+
commons_metrics-0.0.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
18
|
+
commons_metrics-0.0.18.dist-info/top_level.txt,sha256=lheUN-3OKdU3A8Tg8Y-1IEB_9i_vVRA0g_FOiUsTQz8,16
|
|
19
|
+
commons_metrics-0.0.18.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
commons_metrics/__init__.py,sha256=9yoWmIs6B4OfAWdXdQmL1B_yBLoWa_7AdHWEvvN5lx8,434
|
|
2
|
-
commons_metrics/azure_devops_client.py,sha256=sD130ggzZWUsNqkBVNrLH80_AN00CxQ4preP9sBdzHM,16778
|
|
3
|
-
commons_metrics/commons_repos_client.py,sha256=SYIe1fFXM3qlc_G154AmorHScPS3rTsztKqxy3dD28w,4150
|
|
4
|
-
commons_metrics/database.py,sha256=570TtLZ9psNzvIp75UFLYph34cKVEz6eGJgxXyRyjW4,1285
|
|
5
|
-
commons_metrics/github_api_client.py,sha256=yPLsqn_KvQDIXcNp7Ma8qQwWy8svpXNLpDX69vwL9BU,11465
|
|
6
|
-
commons_metrics/repositories.py,sha256=4hSA51Ft1qcZvHtNta_QKSkHIe78JmsVlKcihzkBcb4,9476
|
|
7
|
-
commons_metrics/update_design_components.py,sha256=QpY0GCCCMjdYOZ7b8oNigU9iTpiGx91CYsyWwN8WVDA,7660
|
|
8
|
-
commons_metrics/util.py,sha256=98zuynalXumQRh-BB0Bcjyoh6vS2BTOUM8tVgr7iS9Q,1225
|
|
9
|
-
commons_metrics-0.0.17.dist-info/licenses/LICENSE,sha256=jsHZ2Sh1wCL74HC25pDDGXCyQ0xgsTAy62FvEnehKIg,1067
|
|
10
|
-
commons_metrics-0.0.17.dist-info/METADATA,sha256=l2CjhjCWTXh_miAGSJi1JP44EGSKws3iQRfhi0L2YAg,402
|
|
11
|
-
commons_metrics-0.0.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
-
commons_metrics-0.0.17.dist-info/top_level.txt,sha256=lheUN-3OKdU3A8Tg8Y-1IEB_9i_vVRA0g_FOiUsTQz8,16
|
|
13
|
-
commons_metrics-0.0.17.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|