medicafe 0.250810.5__tar.gz → 0.250810.7__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.
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/MediBot.bat +7 -1
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/MediBot_Crosswalk_Utils.py +75 -30
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediCafe/__main__.py +21 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediCafe/core_utils.py +116 -12
- {medicafe-0.250810.5/medicafe.egg-info → medicafe-0.250810.7}/PKG-INFO +1 -1
- {medicafe-0.250810.5 → medicafe-0.250810.7/medicafe.egg-info}/PKG-INFO +1 -1
- {medicafe-0.250810.5 → medicafe-0.250810.7}/setup.py +1 -1
- {medicafe-0.250810.5 → medicafe-0.250810.7}/LICENSE +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MANIFEST.in +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/MediBot.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/MediBot_Charges.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/MediBot_Crosswalk_Library.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/MediBot_Post.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/MediBot_Preprocessor.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/MediBot_Preprocessor_lib.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/MediBot_UI.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/MediBot_dataformat_library.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/MediBot_docx_decoder.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/MediBot_smart_import.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/__init__.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/get_medicafe_version.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/update_json.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediBot/update_medicafe.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediCafe/MediLink_ConfigLoader.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediCafe/__init__.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediCafe/api_core.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediCafe/api_core_backup.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediCafe/api_factory.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediCafe/api_utils.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediCafe/graphql_utils.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediCafe/logging_config.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediCafe/logging_demo.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediCafe/migration_helpers.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediCafe/smart_import.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_837p_cob_library.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_837p_encoder.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_837p_encoder_library.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_837p_utilities.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_API_Generator.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_Azure.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_ClaimStatus.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_DataMgmt.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_Decoder.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_Deductible.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_Deductible_Validator.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_Display_Utils.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_Down.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_Gmail.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_Mailer.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_Parser.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_PatientProcessor.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_Scan.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_Scheduler.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_UI.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_Up.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_insurance_utils.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_main.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/MediLink_smart_import.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/Soumit_api.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/__init__.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/insurance_type_integration_test.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/openssl.cnf +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/test.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/test_cob_library.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/test_timing.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/test_validation.py +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/MediLink/webapp.html +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/medicafe.egg-info/SOURCES.txt +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/medicafe.egg-info/dependency_links.txt +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/medicafe.egg-info/entry_points.txt +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/medicafe.egg-info/not-zip-safe +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/medicafe.egg-info/requires.txt +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/medicafe.egg-info/top_level.txt +0 -0
- {medicafe-0.250810.5 → medicafe-0.250810.7}/setup.cfg +0 -0
@@ -25,6 +25,8 @@ echo ========================================
|
|
25
25
|
echo DEBUG MODE (INTERACTIVE)
|
26
26
|
echo ========================================
|
27
27
|
echo Running full diagnostic suite...
|
28
|
+
set "MEDICAFE_IMPORT_DEBUG=1"
|
29
|
+
set "MEDICAFE_IMPORT_STRICT=0"
|
28
30
|
echo.
|
29
31
|
set "SKIP_CLS_AFTER_DEBUG=1"
|
30
32
|
call "%~dp0full_debug_suite.bat" /interactive
|
@@ -37,6 +39,8 @@ echo ========================================
|
|
37
39
|
echo DEBUG MODE (NON-INTERACTIVE)
|
38
40
|
echo ========================================
|
39
41
|
echo Running full diagnostic suite...
|
42
|
+
set "MEDICAFE_IMPORT_DEBUG=1"
|
43
|
+
set "MEDICAFE_IMPORT_STRICT=0"
|
40
44
|
echo.
|
41
45
|
set "SKIP_CLS_AFTER_DEBUG=1"
|
42
46
|
call "%~dp0full_debug_suite.bat"
|
@@ -45,6 +49,8 @@ goto normal_mode
|
|
45
49
|
|
46
50
|
:start_normal_mode
|
47
51
|
echo Starting Normal Mode...
|
52
|
+
set "MEDICAFE_IMPORT_DEBUG=0"
|
53
|
+
set "MEDICAFE_IMPORT_STRICT=1"
|
48
54
|
goto normal_mode
|
49
55
|
|
50
56
|
:normal_mode
|
@@ -329,7 +335,7 @@ if exist "%upgrade_medicafe_local%" (
|
|
329
335
|
cls
|
330
336
|
echo Version: %medicafe_version%
|
331
337
|
echo --------------------------------------------------------------
|
332
|
-
echo .//* Welcome to
|
338
|
+
echo .//* Welcome to MediCafe *\\.
|
333
339
|
echo --------------------------------------------------------------
|
334
340
|
echo.
|
335
341
|
|
@@ -83,15 +83,44 @@ def check_crosswalk_health(crosswalk):
|
|
83
83
|
|
84
84
|
def prompt_user_for_api_calls(crosswalk, config):
|
85
85
|
"""
|
86
|
-
|
87
|
-
|
88
|
-
|
86
|
+
Prompt user with a short timeout to optionally run API validation when the crosswalk looks healthy.
|
87
|
+
|
88
|
+
Implementation notes and rationale
|
89
|
+
----------------------------------
|
90
|
+
A previous implementation attempted to capture a quick ENTER press using a background thread that
|
91
|
+
called ``input()`` and then ``join(timeout=...)``. When the timeout elapsed, the thread often
|
92
|
+
remained blocked inside ``input()``. That stray, still-waiting ``input()`` subsequently consumed
|
93
|
+
the next ENTER the user typed at a later prompt (e.g., the Medicare question), producing a blank,
|
94
|
+
unlabeled input step before the real prompt. This manifested as “press Enter once to get the next
|
95
|
+
prompt,” which is undesirable and confusing.
|
96
|
+
|
97
|
+
To avoid leaving a pending console read, this implementation uses Windows' non-blocking console
|
98
|
+
polling (``msvcrt.kbhit()/getwch()``) for up to ~2 seconds to detect an ENTER press without ever
|
99
|
+
invoking ``input()``. Non-ENTER keys are discarded to avoid leaking keystrokes into subsequent
|
100
|
+
prompts. If ENTER is not pressed during the window, we skip API validation with no lingering input
|
101
|
+
state.
|
102
|
+
|
103
|
+
If threading must be used in future
|
104
|
+
-----------------------------------
|
105
|
+
Do not call ``input()`` in a background thread and leave it running after a timeout. Instead:
|
106
|
+
- Use a worker thread that performs non-blocking polling (e.g., ``msvcrt.kbhit()/getwch()``) and
|
107
|
+
sets a threading.Event when ENTER is detected.
|
108
|
+
- The main thread waits on the Event with a timeout. On timeout, signal the worker to stop.
|
109
|
+
- Before returning, drain any buffered keystrokes with a short loop while ``msvcrt.kbhit()`` to
|
110
|
+
ensure no characters (including ENTER) carry over to the next prompt.
|
111
|
+
- This avoids dangling reads and preserves a clean console state.
|
112
|
+
|
113
|
+
Compatibility
|
114
|
+
-------------
|
115
|
+
- Targets Python 3.4.4 / Windows XP constraints; avoids ``selectors``/``select`` on stdin and
|
116
|
+
avoids advanced console APIs. Falls back to a non-interactive skip on non-Windows platforms.
|
117
|
+
|
89
118
|
Args:
|
90
119
|
crosswalk (dict): The crosswalk dictionary to check.
|
91
120
|
config (dict): Configuration settings for logging.
|
92
|
-
|
121
|
+
|
93
122
|
Returns:
|
94
|
-
bool: True
|
123
|
+
bool: True to proceed with API calls; False to skip
|
95
124
|
"""
|
96
125
|
|
97
126
|
is_healthy, missing_names, missing_medisoft_ids, missing_names_list, missing_medisoft_ids_list = check_crosswalk_health(crosswalk)
|
@@ -103,31 +132,47 @@ def prompt_user_for_api_calls(crosswalk, config):
|
|
103
132
|
print(" - All payers have names")
|
104
133
|
print(" - All payers have medisoft IDs")
|
105
134
|
print("\nPress ENTER to run API validation, or wait 2 seconds to skip...")
|
106
|
-
|
107
|
-
#
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
135
|
+
|
136
|
+
# Windows-safe, non-blocking ENTER detection without leaving a stray input() pending
|
137
|
+
try:
|
138
|
+
import os, time
|
139
|
+
msvcrt = None
|
140
|
+
if os.name == 'nt':
|
141
|
+
try:
|
142
|
+
import msvcrt # Windows-only
|
143
|
+
except ImportError:
|
144
|
+
msvcrt = None
|
145
|
+
|
146
|
+
if msvcrt is not None:
|
147
|
+
start_time = time.time()
|
148
|
+
pressed_enter = False
|
149
|
+
# Consume keys for up to 2 seconds, act only if ENTER is hit
|
150
|
+
while time.time() - start_time < 2.0:
|
151
|
+
if msvcrt.kbhit():
|
152
|
+
ch = msvcrt.getwch()
|
153
|
+
if ch in ('\r', '\n'):
|
154
|
+
pressed_enter = True
|
155
|
+
break
|
156
|
+
# Discard any other keys so they don't leak into later prompts
|
157
|
+
time.sleep(0.01)
|
158
|
+
|
159
|
+
if pressed_enter:
|
160
|
+
print("Running API validation calls...")
|
161
|
+
MediLink_ConfigLoader.log("User pressed ENTER - proceeding with API calls", config, level="INFO")
|
162
|
+
return True
|
163
|
+
else:
|
164
|
+
print("Timed out - skipping API calls")
|
165
|
+
MediLink_ConfigLoader.log("Timeout - skipping API calls", config, level="INFO")
|
166
|
+
return False
|
167
|
+
else:
|
168
|
+
# Fallback for non-Windows: avoid interactive wait to prevent stdin conflicts
|
169
|
+
print("(Skipping interactive API validation prompt on this platform)")
|
170
|
+
MediLink_ConfigLoader.log("Skipped interactive API validation prompt (unsupported platform)", config, level="INFO")
|
171
|
+
return False
|
172
|
+
except Exception:
|
173
|
+
# On any error, default to skipping to avoid dangling input states
|
174
|
+
print("(Error during prompt; skipping API validation)")
|
175
|
+
MediLink_ConfigLoader.log("Error during API validation prompt; skipping", config, level="WARNING")
|
131
176
|
return False
|
132
177
|
else:
|
133
178
|
print("\nCrosswalk needs attention:")
|
@@ -25,6 +25,7 @@ import sys
|
|
25
25
|
import os
|
26
26
|
import argparse
|
27
27
|
import subprocess
|
28
|
+
from MediCafe.core_utils import import_medilink_module, require_functions, print_import_diagnostics
|
28
29
|
|
29
30
|
# Set up module paths for proper imports
|
30
31
|
def setup_entry_point_paths():
|
@@ -44,6 +45,21 @@ def setup_entry_point_paths():
|
|
44
45
|
while pkg_dir in sys.path:
|
45
46
|
sys.path.remove(pkg_dir)
|
46
47
|
|
48
|
+
def validate_critical_imports():
|
49
|
+
"""Validate key modules and capabilities before launching subprocesses.
|
50
|
+
- Ensures we resolve the in-repo MediLink_DataMgmt and it exposes required functions.
|
51
|
+
Behavior depends on env flags handled inside core_utils.require_functions.
|
52
|
+
"""
|
53
|
+
try:
|
54
|
+
datamgmt = import_medilink_module('MediLink_DataMgmt')
|
55
|
+
if datamgmt is not None:
|
56
|
+
print_import_diagnostics('MediLink_DataMgmt', datamgmt)
|
57
|
+
# These are used by both MediBot and MediLink flows
|
58
|
+
require_functions(datamgmt, ['read_general_fixed_width_data', 'read_fixed_width_data', 'parse_fixed_width_data'])
|
59
|
+
except Exception as e:
|
60
|
+
# Fail fast only if strict mode is enabled (exception originates from require_functions)
|
61
|
+
print("Import validation warning: {}".format(e))
|
62
|
+
|
47
63
|
def run_medibot(config_file=None):
|
48
64
|
"""Run MediBot application"""
|
49
65
|
try:
|
@@ -58,6 +74,9 @@ def run_medibot(config_file=None):
|
|
58
74
|
print("Error: MediBot.py not found at {}".format(medibot_path))
|
59
75
|
return 1
|
60
76
|
|
77
|
+
# Pre-flight import validation (no-op unless strict/debug enabled)
|
78
|
+
validate_critical_imports()
|
79
|
+
|
61
80
|
# Build subprocess arguments
|
62
81
|
args = [sys.executable, medibot_path]
|
63
82
|
if config_file:
|
@@ -83,6 +102,8 @@ def run_medilink():
|
|
83
102
|
"""Run MediLink application"""
|
84
103
|
try:
|
85
104
|
print("Starting MediLink...")
|
105
|
+
# Pre-flight import validation (no-op unless strict/debug enabled)
|
106
|
+
validate_critical_imports()
|
86
107
|
# Use subprocess.call for Python 3.4.4 compatibility
|
87
108
|
|
88
109
|
# Get the path to MediLink_main.py
|
@@ -29,6 +29,62 @@ for _p in list(sys.path):
|
|
29
29
|
DEFAULT_CONFIG_PATH = os.path.join(project_dir, 'json', 'config.json')
|
30
30
|
DEFAULT_CROSSWALK_PATH = os.path.join(project_dir, 'json', 'crosswalk.json')
|
31
31
|
|
32
|
+
# Environment flags controlling import behavior
|
33
|
+
_STRICT_IMPORT = os.environ.get('MEDICAFE_IMPORT_STRICT', '0').strip().lower() in ('1', 'true', 'yes', 'y')
|
34
|
+
_DEBUG_IMPORT = os.environ.get('MEDICAFE_IMPORT_DEBUG', '0').strip().lower() in ('1', 'true', 'yes', 'y')
|
35
|
+
|
36
|
+
# Simple memoization cache for resolved imports
|
37
|
+
_IMPORT_CACHE = {}
|
38
|
+
|
39
|
+
def _cache_get(key):
|
40
|
+
try:
|
41
|
+
return _IMPORT_CACHE.get(key)
|
42
|
+
except Exception:
|
43
|
+
return None
|
44
|
+
|
45
|
+
def _cache_set(key, value):
|
46
|
+
try:
|
47
|
+
_IMPORT_CACHE[key] = value
|
48
|
+
except Exception:
|
49
|
+
pass
|
50
|
+
|
51
|
+
def _module_provenance_ok(module):
|
52
|
+
"""Return True if module appears to come from this workspace."""
|
53
|
+
try:
|
54
|
+
module_file = getattr(module, '__file__', '') or ''
|
55
|
+
return os.path.abspath(project_dir) in os.path.abspath(module_file)
|
56
|
+
except Exception:
|
57
|
+
return False
|
58
|
+
|
59
|
+
def require_functions(module, names):
|
60
|
+
"""Ensure the module exposes all required attributes; raise RuntimeError on failure in strict mode; return bool otherwise."""
|
61
|
+
missing = [name for name in names if not hasattr(module, name)]
|
62
|
+
if missing:
|
63
|
+
message = "Missing required symbols {} in module {}".format(missing, getattr(module, '__name__', 'unknown'))
|
64
|
+
if _STRICT_IMPORT:
|
65
|
+
raise RuntimeError(message)
|
66
|
+
if _DEBUG_IMPORT:
|
67
|
+
try:
|
68
|
+
print("[IMPORT DIAG] {} (file: {})".format(message, getattr(module, '__file__', 'unknown')))
|
69
|
+
except Exception:
|
70
|
+
pass
|
71
|
+
return False
|
72
|
+
return True
|
73
|
+
|
74
|
+
def print_import_diagnostics(label, module):
|
75
|
+
"""Print a single-line diagnostic about a resolved module (debug only)."""
|
76
|
+
if not _DEBUG_IMPORT:
|
77
|
+
return
|
78
|
+
try:
|
79
|
+
print("[IMPORT DIAG] {} -> name={} file={} workspace_provenance={}".format(
|
80
|
+
label,
|
81
|
+
getattr(module, '__name__', 'unknown'),
|
82
|
+
getattr(module, '__file__', 'unknown'),
|
83
|
+
_module_provenance_ok(module)
|
84
|
+
))
|
85
|
+
except Exception:
|
86
|
+
pass
|
87
|
+
|
32
88
|
def setup_project_path(file_path=None):
|
33
89
|
"""
|
34
90
|
Standard project path setup function used by all entry points.
|
@@ -122,12 +178,26 @@ def smart_import(import_specs, default_value=None):
|
|
122
178
|
try:
|
123
179
|
if isinstance(spec, str):
|
124
180
|
# Simple string - direct import
|
125
|
-
|
181
|
+
# If dotted path, ensure we get the submodule, not just the top-level package
|
182
|
+
cached = _cache_get(('str', spec))
|
183
|
+
if cached is not None:
|
184
|
+
return cached
|
185
|
+
if '.' in spec:
|
186
|
+
result = __import__(spec, fromlist=['*'])
|
187
|
+
else:
|
188
|
+
result = __import__(spec)
|
189
|
+
_cache_set(('str', spec), result)
|
190
|
+
return result
|
126
191
|
elif isinstance(spec, tuple):
|
127
192
|
# Tuple - (path, function_name)
|
128
193
|
path, function_name = spec
|
194
|
+
cached = _cache_get(('tuple', path, function_name))
|
195
|
+
if cached is not None:
|
196
|
+
return cached
|
129
197
|
module = __import__(path, fromlist=[function_name])
|
130
|
-
|
198
|
+
result = getattr(module, function_name)
|
199
|
+
_cache_set(('tuple', path, function_name), result)
|
200
|
+
return result
|
131
201
|
elif isinstance(spec, dict):
|
132
202
|
# Dict with fallback
|
133
203
|
path = spec['path']
|
@@ -135,19 +205,38 @@ def smart_import(import_specs, default_value=None):
|
|
135
205
|
fallback = spec.get('fallback')
|
136
206
|
|
137
207
|
try:
|
208
|
+
cache_key = ('dict', path, function_name)
|
209
|
+
cached = _cache_get(cache_key)
|
210
|
+
if cached is not None:
|
211
|
+
return cached
|
138
212
|
if function_name:
|
139
213
|
module = __import__(path, fromlist=[function_name])
|
140
|
-
|
214
|
+
result = getattr(module, function_name)
|
141
215
|
else:
|
142
|
-
return
|
216
|
+
# If dotted, use fromlist to return the submodule
|
217
|
+
if '.' in path:
|
218
|
+
result = __import__(path, fromlist=['*'])
|
219
|
+
else:
|
220
|
+
result = __import__(path)
|
221
|
+
_cache_set(cache_key, result)
|
222
|
+
return result
|
143
223
|
except ImportError:
|
144
224
|
if fallback:
|
145
225
|
try:
|
226
|
+
cache_key = ('dict_fallback', fallback, function_name)
|
227
|
+
cached = _cache_get(cache_key)
|
228
|
+
if cached is not None:
|
229
|
+
return cached
|
146
230
|
if function_name:
|
147
231
|
module = __import__(fallback, fromlist=[function_name])
|
148
|
-
|
232
|
+
result = getattr(module, function_name)
|
149
233
|
else:
|
150
|
-
|
234
|
+
if '.' in fallback:
|
235
|
+
result = __import__(fallback, fromlist=['*'])
|
236
|
+
else:
|
237
|
+
result = __import__(fallback)
|
238
|
+
_cache_set(cache_key, result)
|
239
|
+
return result
|
151
240
|
except ImportError:
|
152
241
|
continue
|
153
242
|
continue
|
@@ -252,14 +341,16 @@ def import_medilink_module(module_name, function_name=None):
|
|
252
341
|
Returns:
|
253
342
|
The imported module/function or None
|
254
343
|
"""
|
344
|
+
# Prefer importing from the in-repo MediLink package first to avoid
|
345
|
+
# accidentally picking up a similarly named top-level/site-packages module.
|
255
346
|
import_specs = [
|
256
|
-
#
|
257
|
-
module_name,
|
258
|
-
# Then try with MediLink prefix
|
347
|
+
# Prefer package-qualified import first (local codebase)
|
259
348
|
'MediLink.{}'.format(module_name),
|
349
|
+
# Then try direct import (legacy/installed module)
|
350
|
+
module_name,
|
260
351
|
# Then try relative import
|
261
352
|
'.{}'.format(module_name),
|
262
|
-
# Finally try as a submodule
|
353
|
+
# Finally try as a submodule with fallback
|
263
354
|
{'path': 'MediLink.{}'.format(module_name), 'fallback': module_name}
|
264
355
|
]
|
265
356
|
|
@@ -275,9 +366,22 @@ def import_medilink_module(module_name, function_name=None):
|
|
275
366
|
'function': function_name,
|
276
367
|
'fallback': spec.get('fallback')
|
277
368
|
})
|
278
|
-
|
369
|
+
result = smart_import(function_specs)
|
370
|
+
if result is not None and _DEBUG_IMPORT:
|
371
|
+
try:
|
372
|
+
print_import_diagnostics('import_medilink_module:{}:function'.format(module_name), result)
|
373
|
+
except Exception:
|
374
|
+
pass
|
375
|
+
return result
|
279
376
|
else:
|
280
|
-
|
377
|
+
result = smart_import(import_specs)
|
378
|
+
if result is not None:
|
379
|
+
# In strict mode, verify provenance and capabilities if demanded by callers
|
380
|
+
if _STRICT_IMPORT and not _module_provenance_ok(result):
|
381
|
+
raise RuntimeError("Imported module '{}' from outside workspace: {}".format(
|
382
|
+
module_name, getattr(result, '__file__', 'unknown')))
|
383
|
+
print_import_diagnostics('import_medilink_module:{}'.format(module_name), result)
|
384
|
+
return result
|
281
385
|
|
282
386
|
def get_shared_config_loader():
|
283
387
|
"""
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|