medicafe 0.240613.0__py3-none-any.whl → 0.240716.2__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 medicafe might be problematic. Click here for more details.
- MediBot/MediBot.bat +37 -5
- MediBot/MediBot_Crosswalk_Library.py +15 -8
- MediBot/MediBot_docx_decoder.py +13 -5
- MediLink/MediLink.py +38 -73
- MediLink/MediLink_837p_encoder_library.py +4 -27
- MediLink/MediLink_API_Generator.py +246 -0
- MediLink/MediLink_API_v2.py +2 -0
- MediLink/MediLink_API_v3.py +325 -0
- MediLink/MediLink_ClaimStatus.py +144 -0
- MediLink/MediLink_ConfigLoader.py +13 -7
- MediLink/MediLink_Decoder.py +122 -20
- MediLink/MediLink_Deductible.py +203 -0
- MediLink/MediLink_Down.py +84 -52
- MediLink/MediLink_Parser.py +106 -24
- MediLink/MediLink_UI.py +4 -26
- MediLink/MediLink_Up.py +2 -1
- {medicafe-0.240613.0.dist-info → medicafe-0.240716.2.dist-info}/METADATA +2 -1
- {medicafe-0.240613.0.dist-info → medicafe-0.240716.2.dist-info}/RECORD +21 -17
- {medicafe-0.240613.0.dist-info → medicafe-0.240716.2.dist-info}/WHEEL +1 -1
- {medicafe-0.240613.0.dist-info → medicafe-0.240716.2.dist-info}/LICENSE +0 -0
- {medicafe-0.240613.0.dist-info → medicafe-0.240716.2.dist-info}/top_level.txt +0 -0
MediBot/MediBot.bat
CHANGED
|
@@ -11,6 +11,8 @@ set "medicafe_package=medicafe"
|
|
|
11
11
|
set "upgrade_medicafe=F:\Medibot\update_medicafe.py"
|
|
12
12
|
set "temp_file=F:\Medibot\last_update_timestamp.txt"
|
|
13
13
|
set "firefox_path=C:\Program Files\Mozilla Firefox\firefox.exe"
|
|
14
|
+
set "claims_status_script=..\MediLink\MediLink_ClaimStatus.py"
|
|
15
|
+
set "deductible_script=..\MediLink\MediLink_Deductible.py"
|
|
14
16
|
set "package_version="
|
|
15
17
|
set PYTHONWARNINGS=ignore
|
|
16
18
|
|
|
@@ -99,14 +101,18 @@ if "!internet_available!"=="1" (
|
|
|
99
101
|
echo 1. Check for MediCafe Package Updates
|
|
100
102
|
echo 2. Download Email de Carol
|
|
101
103
|
echo 3. MediLink Claims
|
|
104
|
+
echo 4. United Claims Status
|
|
105
|
+
echo 5. United Deductible
|
|
102
106
|
)
|
|
103
|
-
echo
|
|
104
|
-
echo
|
|
107
|
+
echo 6. Run MediBot
|
|
108
|
+
echo 7. Exit
|
|
105
109
|
echo.
|
|
106
110
|
set /p choice=Enter your choice:
|
|
107
111
|
|
|
108
|
-
if "!choice!"=="
|
|
109
|
-
if "!choice!"=="
|
|
112
|
+
if "!choice!"=="7" goto end_script
|
|
113
|
+
if "!choice!"=="6" goto medibot_flow
|
|
114
|
+
if "!choice!"=="5" goto united_deductible
|
|
115
|
+
if "!choice!"=="4" goto united_claims_status
|
|
110
116
|
if "!choice!"=="3" goto medilink_flow
|
|
111
117
|
if "!choice!"=="2" goto download_emails
|
|
112
118
|
if "!choice!"=="1" goto check_updates
|
|
@@ -168,6 +174,32 @@ if errorlevel 1 echo MediLink failed to execute.
|
|
|
168
174
|
pause
|
|
169
175
|
goto main_menu
|
|
170
176
|
|
|
177
|
+
:: United Claims Status
|
|
178
|
+
:united_claims_status
|
|
179
|
+
if "!internet_available!"=="0" (
|
|
180
|
+
echo No internet connection available.
|
|
181
|
+
goto main_menu
|
|
182
|
+
)
|
|
183
|
+
cls
|
|
184
|
+
echo Checking United Claims Status...
|
|
185
|
+
py "%claims_status_script%"
|
|
186
|
+
if errorlevel 1 echo Failed to check United Claims Status.
|
|
187
|
+
pause
|
|
188
|
+
goto main_menu
|
|
189
|
+
|
|
190
|
+
:: United Deductible
|
|
191
|
+
:united_deductible
|
|
192
|
+
if "!internet_available!"=="0" (
|
|
193
|
+
echo No internet connection available.
|
|
194
|
+
goto main_menu
|
|
195
|
+
)
|
|
196
|
+
cls
|
|
197
|
+
echo Checking United Deductible...
|
|
198
|
+
py "%deductible_script%"
|
|
199
|
+
if errorlevel 1 echo Failed to check United Deductible.
|
|
200
|
+
pause
|
|
201
|
+
goto main_menu
|
|
202
|
+
|
|
171
203
|
:: Process CSV Files
|
|
172
204
|
:process_csvs
|
|
173
205
|
for /f "tokens=1-5 delims=/: " %%a in ('echo %time%') do (
|
|
@@ -203,4 +235,4 @@ goto :eof
|
|
|
203
235
|
:end_script
|
|
204
236
|
echo Exiting MediCafe.
|
|
205
237
|
pause
|
|
206
|
-
exit /b
|
|
238
|
+
exit /b
|
|
@@ -14,10 +14,10 @@ except ImportError:
|
|
|
14
14
|
from MediLink import MediLink_ConfigLoader
|
|
15
15
|
|
|
16
16
|
try:
|
|
17
|
-
from
|
|
17
|
+
from MediLink_API_v3 import fetch_payer_name_from_api
|
|
18
18
|
except ImportError:
|
|
19
|
-
from MediLink import
|
|
20
|
-
fetch_payer_name_from_api =
|
|
19
|
+
from MediLink import MediLink_API_v3
|
|
20
|
+
fetch_payer_name_from_api = MediLink_API_v3.fetch_payer_name_from_api
|
|
21
21
|
|
|
22
22
|
try:
|
|
23
23
|
from MediBot import MediBot_Preprocessor_lib
|
|
@@ -127,7 +127,7 @@ def initialize_crosswalk_from_mapat():
|
|
|
127
127
|
validate_and_correct_payer_ids(crosswalk, config)
|
|
128
128
|
|
|
129
129
|
# Save the initial crosswalk
|
|
130
|
-
if save_crosswalk(config
|
|
130
|
+
if save_crosswalk(config, crosswalk):
|
|
131
131
|
message = "Crosswalk initialized with mappings for {} payers.".format(len(crosswalk.get('payer_id', {})))
|
|
132
132
|
print(message)
|
|
133
133
|
MediLink_ConfigLoader.log(message, config, level="INFO")
|
|
@@ -197,7 +197,7 @@ def crosswalk_update(config, crosswalk):
|
|
|
197
197
|
crosswalk['payer_id'][payer_id]['medisoft_medicare_id'] = list(crosswalk['payer_id'][payer_id]['medisoft_medicare_id'])
|
|
198
198
|
|
|
199
199
|
# Save the updated crosswalk to the specified file
|
|
200
|
-
return save_crosswalk(config
|
|
200
|
+
return save_crosswalk(config, crosswalk)
|
|
201
201
|
|
|
202
202
|
def update_crosswalk_with_corrected_payer_id(old_payer_id, corrected_payer_id, config, crosswalk):
|
|
203
203
|
"""Updates the crosswalk with the corrected payer ID."""
|
|
@@ -216,7 +216,7 @@ def update_crosswalk_with_corrected_payer_id(old_payer_id, corrected_payer_id, c
|
|
|
216
216
|
MediLink_ConfigLoader.log("Crosswalk csv_replacements updated: added {} -> {}".format(old_payer_id, corrected_payer_id), config, level="INFO")
|
|
217
217
|
|
|
218
218
|
# Save the updated crosswalk
|
|
219
|
-
return save_crosswalk(config
|
|
219
|
+
return save_crosswalk(config, crosswalk)
|
|
220
220
|
|
|
221
221
|
def update_crosswalk_with_new_payer_id(insurance_name, payer_id, config):
|
|
222
222
|
"""Updates the crosswalk with a new payer ID."""
|
|
@@ -229,14 +229,14 @@ def update_crosswalk_with_new_payer_id(insurance_name, payer_id, config):
|
|
|
229
229
|
crosswalk['payer_id'][payer_id] = {"medisoft_id": [medisoft_id_str], "medisoft_medicare_id": []}
|
|
230
230
|
else:
|
|
231
231
|
crosswalk['payer_id'][payer_id]['medisoft_id'].append(medisoft_id_str)
|
|
232
|
-
save_crosswalk(config
|
|
232
|
+
save_crosswalk(config, crosswalk)
|
|
233
233
|
MediLink_ConfigLoader.log("Updated crosswalk with new payer ID {} for insurance name {}".format(payer_id, insurance_name), config, level="INFO")
|
|
234
234
|
else:
|
|
235
235
|
message = "Failed to update crosswalk: Medisoft ID not found for insurance name {}".format(insurance_name)
|
|
236
236
|
print(message)
|
|
237
237
|
MediLink_ConfigLoader.log(message, config, level="ERROR")
|
|
238
238
|
|
|
239
|
-
def save_crosswalk(
|
|
239
|
+
def save_crosswalk(config, crosswalk):
|
|
240
240
|
"""
|
|
241
241
|
Saves the updated crosswalk to a JSON file.
|
|
242
242
|
Args:
|
|
@@ -245,6 +245,13 @@ def save_crosswalk(crosswalk_path, crosswalk):
|
|
|
245
245
|
Returns:
|
|
246
246
|
bool: True if the file was successfully saved, False otherwise.
|
|
247
247
|
"""
|
|
248
|
+
# Attempt to fetch crosswalkPath from MediLink_Config
|
|
249
|
+
try:
|
|
250
|
+
crosswalk_path = config['MediLink_Config']['crosswalkPath']
|
|
251
|
+
except KeyError:
|
|
252
|
+
# If KeyError occurs, fall back to fetching crosswalkPath directly
|
|
253
|
+
crosswalk_path = config.get('crosswalkPath', None) # Replace None with a default value if needed
|
|
254
|
+
|
|
248
255
|
try:
|
|
249
256
|
# Initialize 'payer_id' key if not present
|
|
250
257
|
if 'payer_id' not in crosswalk:
|
MediBot/MediBot_docx_decoder.py
CHANGED
|
@@ -238,10 +238,19 @@ def parse_patient_id(text):
|
|
|
238
238
|
|
|
239
239
|
def parse_diagnosis_code(text):
|
|
240
240
|
try:
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
241
|
+
# Regular expression to find all ICD-10 codes starting with 'H' and containing a period
|
|
242
|
+
pattern = re.compile(r'H\d{2}\.\d+')
|
|
243
|
+
matches = pattern.findall(text)
|
|
244
|
+
|
|
245
|
+
if matches:
|
|
246
|
+
return matches[0] # Return the first match
|
|
247
|
+
else:
|
|
248
|
+
# Fallback to original method if no match is found
|
|
249
|
+
if '(' in text and ')' in text: # Extract the diagnosis code before the '/'
|
|
250
|
+
full_code = text[text.index('(')+1:text.index(')')]
|
|
251
|
+
return full_code.split('/')[0]
|
|
252
|
+
return text.split('/')[0]
|
|
253
|
+
|
|
245
254
|
except Exception as e:
|
|
246
255
|
MediLink_ConfigLoader.log("Error parsing diagnosis code: {}. Error: {}".format(text, e))
|
|
247
256
|
return "Unknown"
|
|
@@ -268,7 +277,6 @@ def parse_femto_yes_or_no(text):
|
|
|
268
277
|
MediLink_ConfigLoader.log("Error parsing femto yes or no: {}. Error: {}".format(text, e))
|
|
269
278
|
return False
|
|
270
279
|
|
|
271
|
-
|
|
272
280
|
def rotate_docx_files(directory):
|
|
273
281
|
# List all files in the directory
|
|
274
282
|
files = os.listdir(directory)
|
MediLink/MediLink.py
CHANGED
|
@@ -2,7 +2,6 @@ import os
|
|
|
2
2
|
import MediLink_Down
|
|
3
3
|
import MediLink_Up
|
|
4
4
|
import MediLink_ConfigLoader
|
|
5
|
-
import MediLink_837p_encoder
|
|
6
5
|
import MediLink_DataMgmt
|
|
7
6
|
|
|
8
7
|
# For UI Functions
|
|
@@ -19,73 +18,31 @@ from MediBot import MediBot_Preprocessor_lib
|
|
|
19
18
|
load_insurance_data_from_mains = MediBot_Preprocessor_lib.load_insurance_data_from_mains
|
|
20
19
|
from MediBot import MediBot_Crosswalk_Library
|
|
21
20
|
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
insurance_options =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"13": "Point of Service (POS)",
|
|
28
|
-
"14": "Exclusive Provider Organization (EPO)",
|
|
29
|
-
"15": "Indemnity Insurance",
|
|
30
|
-
"16": "Health Maintenance Organization (HMO) Medicare Risk",
|
|
31
|
-
"17": "Dental Maintenance Organization",
|
|
32
|
-
"AM": "Automobile Medical",
|
|
33
|
-
"BL": "Blue Cross/Blue Shield",
|
|
34
|
-
"CH": "Champus",
|
|
35
|
-
"CI": "Commercial Insurance Co.",
|
|
36
|
-
"DS": "Disability",
|
|
37
|
-
"FI": "Federal Employees Program",
|
|
38
|
-
"HM": "Health Maintenance Organization",
|
|
39
|
-
"LM": "Liability Medical",
|
|
40
|
-
"MA": "Medicare Part A",
|
|
41
|
-
"MB": "Medicare Part B",
|
|
42
|
-
"MC": "Medicaid",
|
|
43
|
-
"OF": "Other Federal Program",
|
|
44
|
-
"TV": "Title V",
|
|
45
|
-
"VA": "Veterans Affairs Plan",
|
|
46
|
-
"WC": "Workers Compensation Health Claim",
|
|
47
|
-
"ZZ": "Mutually Defined"
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
def detect_and_display_file_summaries(directory_path, config, crosswalk):
|
|
21
|
+
# Retrieve insurance options with codes and descriptions
|
|
22
|
+
config, _ = MediLink_ConfigLoader.load_configuration()
|
|
23
|
+
insurance_options = config['MediLink_Config'].get('insurance_options')
|
|
24
|
+
|
|
25
|
+
def collect_detailed_patient_data(selected_files, config, crosswalk):
|
|
51
26
|
"""
|
|
52
|
-
|
|
53
|
-
including suggestions for endpoints based on insurance provider information found in the config.
|
|
27
|
+
Collects detailed patient data from the selected files.
|
|
54
28
|
|
|
55
|
-
:param
|
|
29
|
+
:param selected_files: List of selected file paths.
|
|
56
30
|
:param config: Configuration settings loaded from a JSON file.
|
|
57
31
|
:param crosswalk: Crosswalk data for mapping purposes.
|
|
58
|
-
:return: A
|
|
32
|
+
:return: A list of detailed patient data.
|
|
59
33
|
"""
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
MediLink_ConfigLoader.log("No new claims detected. Check Medisoft claims output.")
|
|
65
|
-
return False, []
|
|
66
|
-
|
|
67
|
-
if not file_flagged:
|
|
68
|
-
selected_files = MediLink_UI.user_select_files(new_files)
|
|
69
|
-
else:
|
|
70
|
-
# Extract the newest single latest file from the list
|
|
71
|
-
selected_files = [max(new_files, key=os.path.getctime)]
|
|
72
|
-
|
|
73
|
-
detailed_patient_data = [] # Initialize list for detailed patient data
|
|
74
|
-
for file_path in selected_files:
|
|
75
|
-
detailed_data = extract_and_suggest_endpoint(file_path, config, crosswalk)
|
|
76
|
-
detailed_patient_data.extend(detailed_data) # Accumulate detailed data for processing
|
|
77
|
-
|
|
78
|
-
# Enrich the detailed patient data with insurance type
|
|
79
|
-
detailed_patient_data = enrich_with_insurance_type(detailed_patient_data, insurance_options)
|
|
34
|
+
detailed_patient_data = []
|
|
35
|
+
for file_path in selected_files:
|
|
36
|
+
detailed_data = extract_and_suggest_endpoint(file_path, config, crosswalk)
|
|
37
|
+
detailed_patient_data.extend(detailed_data) # Accumulate detailed data for processing
|
|
80
38
|
|
|
81
|
-
|
|
82
|
-
|
|
39
|
+
# Enrich the detailed patient data with insurance type
|
|
40
|
+
detailed_patient_data = enrich_with_insurance_type(detailed_patient_data, insurance_options)
|
|
41
|
+
|
|
42
|
+
# Display summaries and provide an option for bulk edit
|
|
43
|
+
MediLink_UI.display_patient_summaries(detailed_patient_data)
|
|
83
44
|
|
|
84
|
-
|
|
85
|
-
return selected_files, detailed_patient_data
|
|
86
|
-
except Exception as e:
|
|
87
|
-
MediLink_ConfigLoader.log("Error in detect_and_display_file_summaries: {}".format(e))
|
|
88
|
-
return False, []
|
|
45
|
+
return detailed_patient_data
|
|
89
46
|
|
|
90
47
|
def enrich_with_insurance_type(detailed_patient_data, patient_insurance_type_mapping=None):
|
|
91
48
|
"""
|
|
@@ -299,17 +256,14 @@ def main_menu():
|
|
|
299
256
|
# Normalize the directory path for file operations.
|
|
300
257
|
directory_path = os.path.normpath(config['MediLink_Config']['inputFilePath'])
|
|
301
258
|
|
|
302
|
-
# Detect
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if new_files:
|
|
306
|
-
handle_submission(detailed_patient_data, config)
|
|
259
|
+
# Detect files and determine if a new file is flagged.
|
|
260
|
+
all_files, file_flagged = MediLink_DataMgmt.detect_new_files(directory_path)
|
|
307
261
|
|
|
308
262
|
while True:
|
|
309
263
|
# Define the menu options. Base options include checking remittances and exiting the program.
|
|
310
264
|
options = ["Check for new remittances", "Exit"]
|
|
311
|
-
# If
|
|
312
|
-
if
|
|
265
|
+
# If any files are detected, add the option to submit claims.
|
|
266
|
+
if all_files:
|
|
313
267
|
options.insert(1, "Submit claims")
|
|
314
268
|
|
|
315
269
|
# Display the dynamically adjusted menu options.
|
|
@@ -320,11 +274,22 @@ def main_menu():
|
|
|
320
274
|
if choice == '1':
|
|
321
275
|
# Handle remittance checking.
|
|
322
276
|
check_for_new_remittances(config)
|
|
323
|
-
elif choice == '2' and
|
|
324
|
-
# Handle the claims submission flow if
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
277
|
+
elif choice == '2' and all_files:
|
|
278
|
+
# Handle the claims submission flow if any files are present.
|
|
279
|
+
if file_flagged:
|
|
280
|
+
# Extract the newest single latest file from the list if a new file is flagged.
|
|
281
|
+
selected_files = [max(all_files, key=os.path.getctime)]
|
|
282
|
+
else:
|
|
283
|
+
# Prompt the user to select files if no new file is flagged.
|
|
284
|
+
selected_files = MediLink_UI.user_select_files(all_files)
|
|
285
|
+
|
|
286
|
+
# Collect detailed patient data for selected files.
|
|
287
|
+
detailed_patient_data = collect_detailed_patient_data(selected_files, config, crosswalk)
|
|
288
|
+
|
|
289
|
+
# Process the claims submission.
|
|
290
|
+
handle_submission(detailed_patient_data, config)
|
|
291
|
+
elif choice == '3' or (choice == '2' and not all_files):
|
|
292
|
+
# Exit the program if the user chooses to exit or if no files are present.
|
|
328
293
|
MediLink_UI.display_exit_message()
|
|
329
294
|
break
|
|
330
295
|
else:
|
|
@@ -12,7 +12,7 @@ sys.path.append(project_dir)
|
|
|
12
12
|
from MediBot import MediBot_Preprocessor_lib
|
|
13
13
|
load_insurance_data_from_mains = MediBot_Preprocessor_lib.load_insurance_data_from_mains
|
|
14
14
|
from MediBot import MediBot_Crosswalk_Library
|
|
15
|
-
from
|
|
15
|
+
from MediLink_API_v3 import fetch_payer_name_from_api
|
|
16
16
|
|
|
17
17
|
# Converts date format from one format to another.
|
|
18
18
|
def convert_date_format(date_str):
|
|
@@ -520,32 +520,9 @@ def insurance_type_selection(parsed_data):
|
|
|
520
520
|
|
|
521
521
|
print("\nInsurance Type Validation Error: Select the insurance type for patient {}: ".format(parsed_data['LAST']))
|
|
522
522
|
|
|
523
|
-
#
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
"12": "Preferred Provider Organization (PPO)",
|
|
527
|
-
"13": "Point of Service (POS)",
|
|
528
|
-
"14": "Exclusive Provider Organization (EPO)",
|
|
529
|
-
"15": "Indemnity Insurance",
|
|
530
|
-
"16": "Health Maintenance Organization (HMO) Medicare Risk",
|
|
531
|
-
"17": "Dental Maintenance Organization",
|
|
532
|
-
"AM": "Automobile Medical",
|
|
533
|
-
"BL": "Blue Cross/Blue Shield",
|
|
534
|
-
"CH": "Champus",
|
|
535
|
-
"CI": "Commercial Insurance Co.",
|
|
536
|
-
"DS": "Disability",
|
|
537
|
-
"FI": "Federal Employees Program",
|
|
538
|
-
"HM": "Health Maintenance Organization",
|
|
539
|
-
"LM": "Liability Medical",
|
|
540
|
-
"MA": "Medicare Part A",
|
|
541
|
-
"MB": "Medicare Part B",
|
|
542
|
-
"MC": "Medicaid",
|
|
543
|
-
"OF": "Other Federal Program",
|
|
544
|
-
"TV": "Title V",
|
|
545
|
-
"VA": "Veterans Affairs Plan",
|
|
546
|
-
"WC": "Workers Compensation Health Claim",
|
|
547
|
-
"ZZ": "Mutually Defined"
|
|
548
|
-
}
|
|
523
|
+
# Retrieve insurance options with codes and descriptions
|
|
524
|
+
config, _ = MediLink_ConfigLoader.load_configuration()
|
|
525
|
+
insurance_options = config['MediLink_Config'].get('insurance_options')
|
|
549
526
|
|
|
550
527
|
def prompt_display_insurance_options():
|
|
551
528
|
# Prompt to display full list
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# This script requires Python 3.11 and is to be used for intalling new API clients.
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
import subprocess
|
|
5
|
+
import shutil
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from watchdog.observers import Observer
|
|
9
|
+
from watchdog.events import FileSystemEventHandler
|
|
10
|
+
from MediLink_API_v3 import ConfigLoader
|
|
11
|
+
import shlex
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
class SwaggerHandler(FileSystemEventHandler):
|
|
15
|
+
def __init__(self, json_folder):
|
|
16
|
+
self.json_folder = json_folder
|
|
17
|
+
|
|
18
|
+
def on_created(self, event):
|
|
19
|
+
if event.is_directory:
|
|
20
|
+
return
|
|
21
|
+
if event.src_path.endswith('.yaml') or event.src_path.endswith('.json'):
|
|
22
|
+
print(f"New file detected: {event.src_path}")
|
|
23
|
+
time.sleep(2) # Add a short delay to ensure the file is ready for reading
|
|
24
|
+
self.process_swagger_file(event.src_path)
|
|
25
|
+
|
|
26
|
+
def process_swagger_file(self, file_path):
|
|
27
|
+
print(f"Processing file: {file_path}")
|
|
28
|
+
# Sanitize filename to replace spaces with underscores
|
|
29
|
+
sanitized_file_path = os.path.join(os.path.dirname(file_path), sanitize_filename(os.path.basename(file_path)))
|
|
30
|
+
if sanitized_file_path != file_path:
|
|
31
|
+
os.rename(file_path, sanitized_file_path)
|
|
32
|
+
print(f"Renamed file to: {sanitized_file_path}")
|
|
33
|
+
file_path = sanitized_file_path
|
|
34
|
+
|
|
35
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
36
|
+
temp_dir_path = Path(temp_dir)
|
|
37
|
+
temp_file_path = temp_dir_path / Path(file_path).name
|
|
38
|
+
config_path = temp_dir_path / 'openapi_config.json'
|
|
39
|
+
output_dir = temp_dir_path / "generated_client"
|
|
40
|
+
|
|
41
|
+
shutil.copy(file_path, temp_file_path)
|
|
42
|
+
config_source_path = Path(__file__).parent.parent / 'json' / 'openapi_config.json'
|
|
43
|
+
shutil.copy(config_source_path, config_path)
|
|
44
|
+
print(f"Copied files to: {temp_file_path} and {config_path}")
|
|
45
|
+
|
|
46
|
+
swagger_definitions = ConfigLoader.load_swagger_file(str(temp_file_path))
|
|
47
|
+
if swagger_definitions:
|
|
48
|
+
print(f"Swagger definitions loaded successfully from: {temp_file_path}")
|
|
49
|
+
if generate_api_client(temp_file_path, output_dir, config_path):
|
|
50
|
+
backport_code(output_dir)
|
|
51
|
+
move_generated_client(temp_dir, file_path)
|
|
52
|
+
provide_instructions(file_path)
|
|
53
|
+
else:
|
|
54
|
+
print(f"Failed to load Swagger definitions from: {temp_file_path}")
|
|
55
|
+
|
|
56
|
+
def sanitize_filename(filename):
|
|
57
|
+
return filename.replace(' ', '_')
|
|
58
|
+
|
|
59
|
+
def find_executable(name):
|
|
60
|
+
"""Find the full path to an executable."""
|
|
61
|
+
for path in os.environ["PATH"].split(os.pathsep):
|
|
62
|
+
full_path = Path(path) / name
|
|
63
|
+
if full_path.is_file():
|
|
64
|
+
return str(full_path)
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
def generate_api_client(swagger_path, output_path, config_path):
|
|
68
|
+
"""
|
|
69
|
+
Generate the API client using openapi-generator-cli.
|
|
70
|
+
"""
|
|
71
|
+
openapi_generator_path = find_executable('openapi-generator-cli.cmd')
|
|
72
|
+
if not openapi_generator_path:
|
|
73
|
+
print("openapi-generator-cli not found in PATH.")
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
command = [
|
|
77
|
+
openapi_generator_path,
|
|
78
|
+
'generate',
|
|
79
|
+
'-i', str(swagger_path),
|
|
80
|
+
'-g', 'python',
|
|
81
|
+
'-o', str(output_path),
|
|
82
|
+
'-c', str(config_path)
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
print(f"Attempting command: {' '.join(command)}")
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
result = subprocess.run(command, check=True, capture_output=True, text=True)
|
|
89
|
+
print("API client generated successfully.")
|
|
90
|
+
print(result.stdout)
|
|
91
|
+
print(result.stderr)
|
|
92
|
+
return True
|
|
93
|
+
except subprocess.CalledProcessError as e:
|
|
94
|
+
print("First attempt failed.")
|
|
95
|
+
print("Error generating API client:")
|
|
96
|
+
print(e.stdout)
|
|
97
|
+
print(e.stderr)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
print("Attempting with shell=True.")
|
|
101
|
+
result = subprocess.run(' '.join(command), check=True, shell=True, capture_output=True, text=True)
|
|
102
|
+
print("API client generated successfully with shell=True.")
|
|
103
|
+
print(result.stdout)
|
|
104
|
+
print(result.stderr)
|
|
105
|
+
return True
|
|
106
|
+
except subprocess.CalledProcessError as e:
|
|
107
|
+
print("Second attempt with shell=True failed.")
|
|
108
|
+
print("Error generating API client:")
|
|
109
|
+
print(e.stdout)
|
|
110
|
+
print(e.stderr)
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
quoted_command = [
|
|
114
|
+
openapi_generator_path,
|
|
115
|
+
'generate',
|
|
116
|
+
'-i', shlex.quote(str(swagger_path)),
|
|
117
|
+
'-g', 'python',
|
|
118
|
+
'-o', shlex.quote(str(output_path)),
|
|
119
|
+
'-c', shlex.quote(str(config_path))
|
|
120
|
+
]
|
|
121
|
+
print(f"Attempting quoted command: {' '.join(quoted_command)}")
|
|
122
|
+
result = subprocess.run(' '.join(quoted_command), check=True, shell=True, capture_output=True, text=True)
|
|
123
|
+
print("API client generated successfully with quoted command.")
|
|
124
|
+
print(result.stdout)
|
|
125
|
+
print(result.stderr)
|
|
126
|
+
return True
|
|
127
|
+
except subprocess.CalledProcessError as e:
|
|
128
|
+
print("Third attempt with quoted command failed.")
|
|
129
|
+
print("Error generating API client:")
|
|
130
|
+
print(e.stdout)
|
|
131
|
+
print(e.stderr)
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
print("Attempting with batch script.")
|
|
135
|
+
batch_script_content = f"""
|
|
136
|
+
@echo off
|
|
137
|
+
{openapi_generator_path} generate -i "{swagger_path}" -g python -o "{output_path}" -c "{config_path}"
|
|
138
|
+
"""
|
|
139
|
+
batch_script_path = Path(tempfile.gettempdir()) / "generate_client.bat"
|
|
140
|
+
with open(batch_script_path, 'w') as batch_script:
|
|
141
|
+
batch_script.write(batch_script_content)
|
|
142
|
+
|
|
143
|
+
result = subprocess.run(str(batch_script_path), check=True, shell=True, capture_output=True, text=True)
|
|
144
|
+
print("API client generated successfully with batch script.")
|
|
145
|
+
print(result.stdout)
|
|
146
|
+
print(result.stderr)
|
|
147
|
+
return True
|
|
148
|
+
except subprocess.CalledProcessError as e:
|
|
149
|
+
print("Fourth attempt with batch script failed.")
|
|
150
|
+
print("Error generating API client:")
|
|
151
|
+
print(e.stdout)
|
|
152
|
+
print(e.stderr)
|
|
153
|
+
|
|
154
|
+
print("All attempts to generate API client failed.")
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
def backport_code(output_dir):
|
|
158
|
+
for root, _, files in os.walk(output_dir):
|
|
159
|
+
for file in files:
|
|
160
|
+
if file.endswith('.py'):
|
|
161
|
+
file_path = os.path.join(root, file)
|
|
162
|
+
with open(file_path, 'r') as f:
|
|
163
|
+
code = f.read()
|
|
164
|
+
|
|
165
|
+
code = re.sub(r'f"(.*?)"', r'"\1".format', code) # Replace f-strings
|
|
166
|
+
code = re.sub(r'async def', 'def', code) # Remove async syntax
|
|
167
|
+
code = re.sub(r'await ', '', code) # Remove await syntax
|
|
168
|
+
code = re.sub(r':\s*\w+ =', '=', code) # Remove type hints in variable assignments
|
|
169
|
+
code = re.sub(r'def (\w+)\(.*?\) -> .*?:', r'def \1(', code) # Remove type hints in function definitions
|
|
170
|
+
code = re.sub(r'(from pydantic import.*)', '# \1', code) # Comment out pydantic imports
|
|
171
|
+
code = re.sub(r'(import pydantic)', '# \1', code) # Comment out pydantic imports
|
|
172
|
+
|
|
173
|
+
with open(file_path, 'w') as f:
|
|
174
|
+
f.write(code)
|
|
175
|
+
print(f"Backported {file_path}")
|
|
176
|
+
|
|
177
|
+
def move_generated_client(temp_dir, original_file_path):
|
|
178
|
+
api_name = Path(original_file_path).stem
|
|
179
|
+
destination_dir = Path('generated_clients') / api_name
|
|
180
|
+
if destination_dir.exists():
|
|
181
|
+
shutil.rmtree(destination_dir)
|
|
182
|
+
try:
|
|
183
|
+
shutil.move(str(Path(temp_dir) / 'generated_client'), str(destination_dir))
|
|
184
|
+
print(f"Moved generated client to {destination_dir}")
|
|
185
|
+
except FileNotFoundError as e:
|
|
186
|
+
print(f"Error moving generated client: {e}")
|
|
187
|
+
except Exception as e:
|
|
188
|
+
print(f"Unexpected error moving generated client: {e}")
|
|
189
|
+
|
|
190
|
+
def provide_instructions(swagger_path):
|
|
191
|
+
api_name = Path(swagger_path).stem
|
|
192
|
+
instructions = f"""
|
|
193
|
+
API Client for {api_name} has been generated successfully.
|
|
194
|
+
|
|
195
|
+
Integration Steps:
|
|
196
|
+
1. Locate the generated client code in the 'generated_clients/{api_name}' directory.
|
|
197
|
+
2. Integrate the generated client code into your project by following these steps:
|
|
198
|
+
a. Copy the generated client directory to your project's client directory.
|
|
199
|
+
b. Import the client classes in your project as needed.
|
|
200
|
+
c. Ensure that the generated client adheres to the BaseAPIClient interface if integrating with the existing system.
|
|
201
|
+
3. Update your configuration to include the new API endpoint details.
|
|
202
|
+
4. Test the integration thoroughly to ensure compatibility and functionality.
|
|
203
|
+
|
|
204
|
+
Example Integration:
|
|
205
|
+
from clients.generated.{api_name}.api_client import ApiClient as {api_name}Client
|
|
206
|
+
|
|
207
|
+
class New{api_name}APIClient(BaseAPIClient):
|
|
208
|
+
def __init__(self, config):
|
|
209
|
+
super().__init__(config)
|
|
210
|
+
self.generated_client = {api_name}Client()
|
|
211
|
+
|
|
212
|
+
def get_access_token(self, endpoint_name):
|
|
213
|
+
# Implement token retrieval logic
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None):
|
|
217
|
+
# Use the generated client to make the API call
|
|
218
|
+
if call_type == 'GET':
|
|
219
|
+
response = self.generated_client.call_api(url_extension, 'GET', params=params, header_params=headers)
|
|
220
|
+
elif call_type == 'POST':
|
|
221
|
+
response = self.generated_client.call_api(url_extension, 'POST', body=data, header_params=headers)
|
|
222
|
+
elif call_type == 'DELETE':
|
|
223
|
+
response = self.generated_client.call_api(url_extension, 'DELETE', header_params=headers)
|
|
224
|
+
else:
|
|
225
|
+
raise ValueError("Unsupported call type")
|
|
226
|
+
return response
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
print(instructions)
|
|
230
|
+
|
|
231
|
+
def main():
|
|
232
|
+
json_folder = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'json'))
|
|
233
|
+
event_handler = SwaggerHandler(json_folder)
|
|
234
|
+
observer = Observer()
|
|
235
|
+
observer.schedule(event_handler, path=json_folder, recursive=False)
|
|
236
|
+
observer.start()
|
|
237
|
+
print(f"Monitoring folder: {json_folder}")
|
|
238
|
+
try:
|
|
239
|
+
while True:
|
|
240
|
+
time.sleep(1)
|
|
241
|
+
except KeyboardInterrupt:
|
|
242
|
+
observer.stop()
|
|
243
|
+
observer.join()
|
|
244
|
+
|
|
245
|
+
if __name__ == "__main__":
|
|
246
|
+
main()
|