aerapi 0.1.1__tar.gz
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.
- aerapi-0.1.1/PKG-INFO +14 -0
- aerapi-0.1.1/aerAPI.egg-info/PKG-INFO +14 -0
- aerapi-0.1.1/aerAPI.egg-info/SOURCES.txt +24 -0
- aerapi-0.1.1/aerAPI.egg-info/dependency_links.txt +1 -0
- aerapi-0.1.1/aerAPI.egg-info/requires.txt +2 -0
- aerapi-0.1.1/aerAPI.egg-info/top_level.txt +4 -0
- aerapi-0.1.1/common/__init__.py +4 -0
- aerapi-0.1.1/common/auth.py +110 -0
- aerapi-0.1.1/common/config_utils.py +238 -0
- aerapi-0.1.1/common/utils.py +436 -0
- aerapi-0.1.1/config/__init__.py +3 -0
- aerapi-0.1.1/external/__init__.py +3 -0
- aerapi-0.1.1/external/api_client.py +2103 -0
- aerapi-0.1.1/external/external_utils.py +658 -0
- aerapi-0.1.1/internal/__init__.py +3 -0
- aerapi-0.1.1/internal/api_client.py +1886 -0
- aerapi-0.1.1/internal/internal_utils.py +607 -0
- aerapi-0.1.1/setup.cfg +4 -0
- aerapi-0.1.1/setup.py +23 -0
- aerapi-0.1.1/tests/test_external.py +1 -0
- aerapi-0.1.1/tests/test_internal.py +1 -0
aerapi-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: aerapi
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A package for accessing APIs used by the data team
|
|
5
|
+
Home-page: https://github.com/stelltec/aerapi.git
|
|
6
|
+
Author: S. Nicholson
|
|
7
|
+
Author-email: Sean.Nicholson@aerlytix.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.6
|
|
13
|
+
Requires-Dist: requests
|
|
14
|
+
Requires-Dist: PyYAML
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: aerapi
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A package for accessing APIs used by the data team
|
|
5
|
+
Home-page: https://github.com/stelltec/aerapi.git
|
|
6
|
+
Author: S. Nicholson
|
|
7
|
+
Author-email: Sean.Nicholson@aerlytix.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.6
|
|
13
|
+
Requires-Dist: requests
|
|
14
|
+
Requires-Dist: PyYAML
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
setup.py
|
|
2
|
+
aerAPI.egg-info/PKG-INFO
|
|
3
|
+
aerAPI.egg-info/SOURCES.txt
|
|
4
|
+
aerAPI.egg-info/dependency_links.txt
|
|
5
|
+
aerAPI.egg-info/requires.txt
|
|
6
|
+
aerAPI.egg-info/top_level.txt
|
|
7
|
+
aerapi.egg-info/PKG-INFO
|
|
8
|
+
aerapi.egg-info/SOURCES.txt
|
|
9
|
+
aerapi.egg-info/dependency_links.txt
|
|
10
|
+
aerapi.egg-info/requires.txt
|
|
11
|
+
aerapi.egg-info/top_level.txt
|
|
12
|
+
common/__init__.py
|
|
13
|
+
common/auth.py
|
|
14
|
+
common/config_utils.py
|
|
15
|
+
common/utils.py
|
|
16
|
+
config/__init__.py
|
|
17
|
+
external/__init__.py
|
|
18
|
+
external/api_client.py
|
|
19
|
+
external/external_utils.py
|
|
20
|
+
internal/__init__.py
|
|
21
|
+
internal/api_client.py
|
|
22
|
+
internal/internal_utils.py
|
|
23
|
+
tests/test_external.py
|
|
24
|
+
tests/test_internal.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import logging
|
|
3
|
+
import common.utils as utils
|
|
4
|
+
|
|
5
|
+
logging.basicConfig(level=logging.INFO)
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_auth_headers(api_key_id, api_secret_key):
|
|
10
|
+
"""
|
|
11
|
+
Create the headers needed for authentication with API keys.
|
|
12
|
+
|
|
13
|
+
Parameters:
|
|
14
|
+
- api_key_id (str): The API key ID from the config file
|
|
15
|
+
- api_secret_key (str): The API secret key from the config file
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
- dict: Headers containing the API key ID and secret key
|
|
19
|
+
"""
|
|
20
|
+
headers = {
|
|
21
|
+
'Api-Key-Id': api_key_id,
|
|
22
|
+
'Api-Secret-Key': api_secret_key,
|
|
23
|
+
'Content-Type': 'application/json'
|
|
24
|
+
}
|
|
25
|
+
return headers
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def check_authentication_status(response):
|
|
29
|
+
"""
|
|
30
|
+
Check if the API response indicates authentication failure.
|
|
31
|
+
|
|
32
|
+
Parameters:
|
|
33
|
+
- response (requests.Response): The response from an API request
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
- bool: True if authentication failed, False otherwise
|
|
37
|
+
"""
|
|
38
|
+
if response.status_code == 401:
|
|
39
|
+
# Authentication failed
|
|
40
|
+
print("Authentication failed. Please check your API keys.")
|
|
41
|
+
return False
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def make_authenticated_request(config, url, method='GET', params=None, data=None, debug=False, multiSend=False, sendSize=100, timeout=30):
|
|
46
|
+
"""
|
|
47
|
+
Make an authenticated request to a given API, with optional support for sending requests in chunks.
|
|
48
|
+
"""
|
|
49
|
+
base_url = config['base_url']
|
|
50
|
+
api_key_id = config['api_key_id']
|
|
51
|
+
api_secret_key = config['api_secret_key']
|
|
52
|
+
|
|
53
|
+
# print(base_url)
|
|
54
|
+
# print(api_key_id)
|
|
55
|
+
# print(api_secret_key)
|
|
56
|
+
|
|
57
|
+
full_url = f"{base_url.rstrip('/')}{url}"
|
|
58
|
+
if debug:
|
|
59
|
+
logger.info(f"Request URL: {full_url}")
|
|
60
|
+
|
|
61
|
+
headers = get_auth_headers(api_key_id, api_secret_key)
|
|
62
|
+
responses = []
|
|
63
|
+
|
|
64
|
+
# Multi-send logic
|
|
65
|
+
if multiSend and data and isinstance(data, dict) and 'items' in data:
|
|
66
|
+
items = data['items']
|
|
67
|
+
chunks = utils.chunkIt(items, max(1, len(items) // sendSize))
|
|
68
|
+
|
|
69
|
+
for chunk in chunks:
|
|
70
|
+
chunked_data = {"items": chunk}
|
|
71
|
+
try:
|
|
72
|
+
response = _send_request(method, full_url, headers, chunked_data, params, timeout, debug=debug)
|
|
73
|
+
if response:
|
|
74
|
+
responses.append(response)
|
|
75
|
+
except requests.exceptions.RequestException as e:
|
|
76
|
+
logger.error(f"Error during chunked request: {e}")
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
return responses
|
|
80
|
+
|
|
81
|
+
# Single request logic
|
|
82
|
+
try:
|
|
83
|
+
return _send_request(method, full_url, headers, data, params, timeout, debug=debug)
|
|
84
|
+
except requests.exceptions.RequestException as e:
|
|
85
|
+
logger.error(f"Error during request: {e}")
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _send_request(method, url, headers, data, params, timeout, debug):
|
|
90
|
+
if method == 'GET':
|
|
91
|
+
response = requests.get(url, headers=headers, params=params, timeout=timeout)
|
|
92
|
+
elif method == 'POST':
|
|
93
|
+
response = requests.post(url, headers=headers, json=data, timeout=timeout)
|
|
94
|
+
elif method == 'PUT':
|
|
95
|
+
response = requests.put(url, headers=headers, json=data, timeout=timeout)
|
|
96
|
+
elif method == 'DELETE':
|
|
97
|
+
response = requests.delete(url, headers=headers, params=params, timeout=timeout)
|
|
98
|
+
else:
|
|
99
|
+
raise ValueError(f"Unsupported HTTP method: {method}")
|
|
100
|
+
|
|
101
|
+
# Check if authentication or other errors occurred
|
|
102
|
+
if not check_authentication_status(response):
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
# Log the status code for each request
|
|
106
|
+
if debug:
|
|
107
|
+
logger.info(f"Response Status Code: {response.status_code}")
|
|
108
|
+
|
|
109
|
+
# Return JSON response if content exists, otherwise return an empty dict
|
|
110
|
+
return response.json() if response.content else {}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_api_config(env, name):
|
|
8
|
+
"""
|
|
9
|
+
Get the API configuration for a specific environment and API name.
|
|
10
|
+
|
|
11
|
+
Parameters:
|
|
12
|
+
- env (str): The environment (e.g., 'preprod', 'demo').
|
|
13
|
+
- name (str): The specific name of the API within the environment (e.g., 'api-api-preprod-amergin').
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
- dict: A dictionary containing 'api_key_id', 'api_secret_key', and 'base_url'.
|
|
17
|
+
"""
|
|
18
|
+
try:
|
|
19
|
+
# Open and load the YAML configuration file
|
|
20
|
+
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
21
|
+
yaml_path = os.path.join(base_dir, 'config', 'config.yaml')
|
|
22
|
+
|
|
23
|
+
# Open and load the YAML configuration file
|
|
24
|
+
with open(yaml_path, 'r') as file:
|
|
25
|
+
config = yaml.safe_load(file)
|
|
26
|
+
|
|
27
|
+
# Loop through the configurations for the given environment
|
|
28
|
+
for entry in config['environments'].get(env, []):
|
|
29
|
+
if entry['name'] == name:
|
|
30
|
+
# Return the API key, secret, and name as base_url
|
|
31
|
+
return {
|
|
32
|
+
'api_key_id': entry['api_key_id'],
|
|
33
|
+
'api_secret_key': entry['api_secret_key'],
|
|
34
|
+
'base_url': 'https://' + entry['url'] + '/v1'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# If no matching configuration is found, return None
|
|
38
|
+
print(f"No configuration found for {env} environment with API name {name}.")
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
except FileNotFoundError:
|
|
42
|
+
print("Error: config.yaml file not found.")
|
|
43
|
+
return None
|
|
44
|
+
except yaml.YAMLError as e:
|
|
45
|
+
print(f"Error parsing config.yaml: {e}")
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def extract_paths(file_content):
|
|
50
|
+
"""
|
|
51
|
+
Extracts API paths from a TypeScript paths file, including paths with parameters,
|
|
52
|
+
and derives the HTTP method from the key, with special handling for 'list'.
|
|
53
|
+
|
|
54
|
+
Parameters:
|
|
55
|
+
- file_content (str): The content of the TypeScript file containing the paths.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
- dict: A dictionary mapping path names to a tuple containing the HTTP method, API path, and additional parameters.
|
|
59
|
+
"""
|
|
60
|
+
paths = {}
|
|
61
|
+
# Match the pattern for API paths (e.g., assemblyList: "/external-api/assemblies" or "/external-api/assemblies/:assemblyId")
|
|
62
|
+
matches = re.findall(r'(\w+):\s*"(/[\w/-]+(:\w+)?)"', file_content)
|
|
63
|
+
|
|
64
|
+
for match in matches:
|
|
65
|
+
path_name, api_path, _ = match
|
|
66
|
+
|
|
67
|
+
# Derive the method from the path name
|
|
68
|
+
method, requires_pagination = derive_http_method(path_name)
|
|
69
|
+
|
|
70
|
+
# Store the derived method, the API path, and pagination info
|
|
71
|
+
paths[path_name] = {
|
|
72
|
+
'method': method,
|
|
73
|
+
'path': api_path,
|
|
74
|
+
'pagination': requires_pagination
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return paths
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def derive_http_method(path_name):
|
|
81
|
+
"""
|
|
82
|
+
Derives the HTTP method from the path name based on common action keywords,
|
|
83
|
+
and determines if pagination is needed.
|
|
84
|
+
|
|
85
|
+
Parameters:
|
|
86
|
+
- path_name (str): The name of the path (e.g., 'create', 'update', 'list', 'info').
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
- tuple: (HTTP method as a string, requires_pagination as a bool)
|
|
90
|
+
"""
|
|
91
|
+
# Common action keywords mapped to HTTP methods
|
|
92
|
+
action_method_map = {
|
|
93
|
+
'create': 'POST',
|
|
94
|
+
'update': 'PUT',
|
|
95
|
+
'delete': 'DELETE',
|
|
96
|
+
'remove': 'DELETE',
|
|
97
|
+
'get': 'GET',
|
|
98
|
+
'info': 'GET'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Default to GET method
|
|
102
|
+
method = 'GET'
|
|
103
|
+
requires_pagination = False
|
|
104
|
+
|
|
105
|
+
# Special case for 'list' indicating a paginated GET request
|
|
106
|
+
if 'list' in path_name.lower():
|
|
107
|
+
method = 'GET'
|
|
108
|
+
requires_pagination = True
|
|
109
|
+
|
|
110
|
+
# Check for other keywords to determine the method
|
|
111
|
+
for keyword, mapped_method in action_method_map.items():
|
|
112
|
+
if keyword.lower() in path_name.lower():
|
|
113
|
+
method = mapped_method
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
return method, requires_pagination
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_ts_files_info(base_dir):
|
|
120
|
+
"""
|
|
121
|
+
Searches through the specified directory for paths files, extracts relevant information from them.
|
|
122
|
+
|
|
123
|
+
Parameters:
|
|
124
|
+
- base_dir (str): The base directory where the search will start.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
- list: A list of dictionaries containing extracted data from paths files.
|
|
128
|
+
"""
|
|
129
|
+
extracted_data = []
|
|
130
|
+
|
|
131
|
+
# Traverse the directory to find TypeScript files
|
|
132
|
+
for root, dirs, files in os.walk(base_dir):
|
|
133
|
+
for file in files:
|
|
134
|
+
if file.endswith("paths.ts"):
|
|
135
|
+
file_path = os.path.join(root, file)
|
|
136
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
137
|
+
content = f.read()
|
|
138
|
+
paths_info = extract_paths(content)
|
|
139
|
+
extracted_data.append({
|
|
140
|
+
'file': file,
|
|
141
|
+
'type': 'paths',
|
|
142
|
+
'data': paths_info
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
return extracted_data
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def extract_urls_from_file(file_path):
|
|
150
|
+
"""
|
|
151
|
+
Extract all URLs from the specified api_client.py file.
|
|
152
|
+
|
|
153
|
+
Parameters:
|
|
154
|
+
- file_path (str): The path to the api_client.py file.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
- list: A list of all extracted URLs in the form they appear in the file.
|
|
158
|
+
"""
|
|
159
|
+
urls = []
|
|
160
|
+
# Define a regex pattern to match URL strings in the format "/external-api/...".
|
|
161
|
+
url_pattern = re.compile(r'url\s*=\s*f?"(/external-api/[^\s"]+)"')
|
|
162
|
+
|
|
163
|
+
with open(file_path, 'r', encoding='utf-8') as file:
|
|
164
|
+
# Read the entire content of the file.
|
|
165
|
+
content = file.read()
|
|
166
|
+
|
|
167
|
+
# Find all occurrences of the URL pattern.
|
|
168
|
+
matches = url_pattern.findall(content)
|
|
169
|
+
|
|
170
|
+
# Collect and clean the URLs.
|
|
171
|
+
for match in matches:
|
|
172
|
+
urls.append(match)
|
|
173
|
+
|
|
174
|
+
return urls
|
|
175
|
+
|
|
176
|
+
def find_new_apis(extracted_info, existing_urls):
|
|
177
|
+
"""
|
|
178
|
+
Identifies APIs from extracted TypeScript paths that are not yet implemented in the Python client.
|
|
179
|
+
|
|
180
|
+
Parameters:
|
|
181
|
+
- extracted_info (list): List of dictionaries containing data from paths files.
|
|
182
|
+
- existing_urls (list): List of URLs extracted from the existing Python API client.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
- list: A list of new APIs that need to be created.
|
|
186
|
+
"""
|
|
187
|
+
new_apis = []
|
|
188
|
+
|
|
189
|
+
# Normalize the existing URLs to match the TypeScript format
|
|
190
|
+
normalized_existing_urls = {url.replace('{',':').replace('}','').rstrip('/') for url in existing_urls}
|
|
191
|
+
|
|
192
|
+
# Loop through each extracted TypeScript path
|
|
193
|
+
for file in extracted_info:
|
|
194
|
+
for path_name, path_data in file['data'].items():
|
|
195
|
+
# Normalize the path to ensure consistent comparison
|
|
196
|
+
normalized_path = path_data['path'].rstrip('/')
|
|
197
|
+
|
|
198
|
+
# Check if the path exists in the Python client
|
|
199
|
+
if normalized_path not in normalized_existing_urls:
|
|
200
|
+
new_apis.append({
|
|
201
|
+
'name': path_name,
|
|
202
|
+
'path': normalized_path,
|
|
203
|
+
'method': path_data.get('method', 'GET'),
|
|
204
|
+
'pagination': path_data.get('pagination', False)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
return new_apis
|
|
208
|
+
|
|
209
|
+
def identify_missing_external_apis(base_directory, python_client_path):
|
|
210
|
+
"""
|
|
211
|
+
Wrapper function to extract, compare, and identify missing API implementations.
|
|
212
|
+
|
|
213
|
+
Parameters:
|
|
214
|
+
- base_directory (str): The base directory where the TypeScript paths files are located.
|
|
215
|
+
- python_client_path (str): The path to the Python client file containing the implemented URLs.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
- list: A list of new APIs that need to be implemented.
|
|
219
|
+
|
|
220
|
+
Eaxmple:
|
|
221
|
+
base_directory = C:/Users/Sean.Nicholson/GitHub/core2/backend/src/modules/external_api
|
|
222
|
+
python_client_path = ./external/api_client.py
|
|
223
|
+
|
|
224
|
+
"""
|
|
225
|
+
# Extract TypeScript path info
|
|
226
|
+
extracted_info = get_ts_files_info(base_directory)
|
|
227
|
+
|
|
228
|
+
# Extract URLs from the existing Python client
|
|
229
|
+
existing_urls = extract_urls_from_file(python_client_path)
|
|
230
|
+
|
|
231
|
+
# Find new APIs that are not yet implemented
|
|
232
|
+
new_apis = find_new_apis(extracted_info, existing_urls)
|
|
233
|
+
|
|
234
|
+
# Print the new APIs for clarity
|
|
235
|
+
for api in new_apis:
|
|
236
|
+
print(f"API Name: {api['name']}, Method: {api['method']}, Path: {api['path']}, Pagination: {api['pagination']}")
|
|
237
|
+
|
|
238
|
+
return new_apis
|