medicafe 0.250816.0__py3-none-any.whl → 0.250819.0__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.
@@ -1,770 +1,245 @@
1
- #update_medicafe.py
2
- # Version: 1.1.0 - Fixed upgrade sequence issues
3
- #
4
- # CRITICAL FIXES IMPLEMENTED (2025):
5
- # 1. REMOVED RACE CONDITION: Eliminated double-check logic in get_latest_version()
6
- # that caused inconsistent behavior between first and second runs
7
- # 2. IMPROVED CACHE CLEARING: Added aggressive pkg_resources cache clearing for
8
- # XP SP3 + Python 3.4.4 compatibility
9
- # 3. FIXED VERIFICATION: Improved version comparison logic to properly detect
10
- # upgrade success/failure instead of considering mismatched versions as "successful"
11
- # 4. XP TIMING: Increased delays from 2-3s to 3-5s to accommodate XP's slower
12
- # file system operations
13
- # 5. ERROR HANDLING: Standardized error messages and exit codes throughout
14
- #
15
- # DEPLOYMENT NOTES:
16
- # - This file exists in two locations for legacy compatibility:
17
- # * MediBot/update_medicafe.py (primary)
18
- # * MediBot/MediBot/update_medicafe.py (legacy fallback)
19
- # - Both files must remain identical - the batch script copies between them
20
- # - Designed for Windows XP SP3 + Python 3.4.4 + ASCII-only environments
21
- #
22
- import subprocess, sys, time, platform, os, shutil, random
23
-
24
- # Safe import for pkg_resources with fallback
25
- try:
26
- import pkg_resources
27
- except ImportError:
28
- pkg_resources = None
29
- print("Warning: pkg_resources not available. Some functionality may be limited.")
1
+ #!/usr/bin/env python
2
+ # update_medicafe.py
3
+ # Script Version: 2.0.0 (clean 3-try updater)
4
+ # Target environment: Windows XP SP3 + Python 3.4.4 (ASCII-only)
5
+
6
+ import sys, os, time, subprocess, platform
30
7
 
31
- # Safe import for requests with fallback
32
8
  try:
33
9
  import requests
34
- except ImportError:
10
+ except Exception:
35
11
  requests = None
36
- print("Warning: requests module not available. Some functionality may be limited.")
37
12
 
38
- # Safe tqdm import with fallback
39
13
  try:
40
- from tqdm import tqdm as _real_tqdm
41
- def tqdm(iterable, **kwargs):
42
- return _real_tqdm(iterable, **kwargs)
43
- TQDM_AVAILABLE = True
14
+ import pkg_resources
44
15
  except Exception:
45
- TQDM_AVAILABLE = False
46
- def tqdm(iterable, **kwargs):
47
- return iterable
48
-
49
- # Initialize console output
50
- print("="*60)
51
- print("MediCafe Update Started")
52
- print("Timestamp: {}".format(time.strftime("%Y-%m-%d %H:%M:%S")))
53
- print("Python Version: {}".format(sys.version))
54
- print("Platform: {}".format(platform.platform()))
55
- print("="*60)
56
-
57
- # Global debug mode toggle (defaults to streamlined mode)
58
- DEBUG_MODE = False
59
-
60
- def debug_step(step_number, step_title, message=""):
61
- """Print step information. Only show debug output in debug mode."""
62
- if DEBUG_MODE:
63
- print("\n" + "="*60)
64
- print("STEP {}: {}".format(step_number, step_title))
65
- print("="*60)
66
- if message:
67
- print(message)
68
-
69
- # In debug mode, optionally pause for key steps
70
- if step_number in [1, 7, 8]:
71
- print("\nPress Enter to continue...")
72
- try:
73
- input()
74
- except:
75
- pass
76
- else:
77
- print("\nContinuing...")
78
- else:
79
- # Streamlined mode: no debug output, no pauses
80
- pass
16
+ pkg_resources = None
81
17
 
82
- def print_status(message, status_type="INFO"):
83
- """Print formatted status messages with ASCII-only visual indicators."""
84
- if status_type == "SUCCESS":
85
- print("\n" + "="*60)
86
- print("[SUCCESS] {}".format(message))
87
- print("="*60)
88
- elif status_type == "ERROR":
89
- print("\n" + "="*60)
90
- print("[ERROR] {}".format(message))
91
- print("="*60)
92
- elif status_type == "WARNING":
93
- print("\n" + "-"*60)
94
- print("[WARNING] {}".format(message))
95
- print("-"*60)
96
- elif status_type == "INFO":
97
- print("\n" + "-"*60)
98
- print("[INFO] {}".format(message))
99
- print("-"*60)
100
- else:
101
- print(message)
102
18
 
103
- def print_final_result(success, message):
104
- """Print final result with clear visual indication."""
105
- if success:
106
- print_status("UPDATE COMPLETED SUCCESSFULLY", "SUCCESS")
107
- print("Final Status: {}".format(message))
108
- else:
109
- print_status("UPDATE FAILED", "ERROR")
110
- print("Final Status: {}".format(message))
111
-
112
- print("\nExiting in 5 seconds...")
113
- time.sleep(5)
114
- sys.exit(0 if success else 1)
115
-
116
- def clear_pkg_resources_cache():
117
- """
118
- Aggressively clear pkg_resources cache for XP compatibility.
119
-
120
- CRITICAL FIX (2025): On XP SP3 + Python 3.4.4, pkg_resources caching is particularly
121
- problematic and can cause version detection to fail after package updates. This function
122
- aggressively clears all known cache locations to ensure reliable version detection.
123
-
124
- Called before every version check to prevent stale cache issues.
125
- """
126
- if not pkg_resources:
127
- return False
128
-
19
+ SCRIPT_NAME = "update_medicafe.py"
20
+ SCRIPT_VERSION = "2.0.0"
21
+ PACKAGE_NAME = "medicafe"
22
+
23
+
24
+ # ---------- UI helpers (ASCII-only) ----------
25
+ def _line(char, width):
129
26
  try:
130
- # Clear working set cache
131
- pkg_resources.working_set = pkg_resources.WorkingSet()
132
-
133
- # Clear distribution cache
134
- if hasattr(pkg_resources, '_dist_cache'):
135
- pkg_resources._dist_cache.clear()
136
-
137
- # Clear working set cache
138
- if hasattr(pkg_resources, '_ws_cache'):
139
- pkg_resources._ws_cache.clear()
140
-
141
- # Force reload of distributions
142
- pkg_resources.require = pkg_resources.Requirement.parse
143
-
144
- return True
145
- except Exception as e:
146
- print("Warning: Could not clear pkg_resources cache: {}".format(e))
147
- return False
27
+ return char * width
28
+ except Exception:
29
+ return char * 60
30
+
31
+
32
+ def print_banner(title):
33
+ width = 60
34
+ print(_line("=", width))
35
+ print(title)
36
+ print(_line("=", width))
37
+
38
+
39
+ def print_section(title):
40
+ width = 60
41
+ print("\n" + _line("-", width))
42
+ print(title)
43
+ print(_line("-", width))
44
+
45
+
46
+ def print_status(kind, message):
47
+ label = "[{}]".format(kind)
48
+ print("{} {}".format(label, message))
49
+
50
+
51
+ # ---------- Version utilities ----------
52
+ def compare_versions(version1, version2):
53
+ try:
54
+ v1_parts = list(map(int, version1.split(".")))
55
+ v2_parts = list(map(int, version2.split(".")))
56
+ return (v1_parts > v2_parts) - (v1_parts < v2_parts)
57
+ except Exception:
58
+ # Fall back to string compare if unexpected formats
59
+ return (version1 > version2) - (version1 < version2)
60
+
148
61
 
149
62
  def get_installed_version(package):
150
- """Get installed version with improved cache clearing for XP."""
151
63
  try:
152
- # Clear cache before checking version
153
- clear_pkg_resources_cache()
154
-
155
- # First try using pkg_resources directly (more reliable for Python 3.4)
156
- if pkg_resources:
157
- try:
158
- version = pkg_resources.get_distribution(package).version
159
- return version
160
- except pkg_resources.DistributionNotFound:
161
- return None
162
- except Exception as e:
163
- print("Warning: pkg_resources failed: {}".format(e))
164
- # Fall through to pip method
165
-
166
- # Fallback to pip show (may fail on Python 3.4 due to packaging issues)
167
- process = subprocess.Popen(
168
- [sys.executable, '-m', 'pip', 'show', package],
169
- stdout=subprocess.PIPE,
170
- stderr=subprocess.PIPE
171
- )
172
- stdout, stderr = process.communicate()
173
- if process.returncode == 0:
174
- for line in stdout.decode().splitlines():
64
+ proc = subprocess.Popen([sys.executable, '-m', 'pip', 'show', package],
65
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
66
+ out, err = proc.communicate()
67
+ if proc.returncode == 0:
68
+ for line in out.decode().splitlines():
175
69
  if line.startswith("Version:"):
176
70
  return line.split(":", 1)[1].strip()
177
- return None
178
- except Exception as e:
179
- print("Error retrieving installed version: {}".format(e))
180
- return None
71
+ except Exception:
72
+ pass
181
73
 
182
- def get_latest_version(package, retries=3, delay=1):
183
- """
184
- Fetch the latest version of the specified package from PyPI with retries.
185
-
186
- CRITICAL FIX (2025): Removed problematic double-check logic that caused race conditions.
187
- The original code would check if current_version == latest_version and then make a
188
- second API call, which created inconsistent behavior:
189
- - First run: Script thinks it needs update, attempts upgrade, version detection fails
190
- - Second run: Script detects same version, incorrectly assumes already up-to-date
191
-
192
- Now simply returns the latest version immediately without double-checking.
193
- """
74
+ if pkg_resources:
75
+ try:
76
+ return pkg_resources.get_distribution(package).version
77
+ except Exception:
78
+ return None
79
+ return None
80
+
81
+
82
+ def get_latest_version(package, retries):
194
83
  if not requests:
195
- print("Error: requests module not available. Cannot fetch latest version.")
196
84
  return None
197
-
85
+
86
+ headers = {
87
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
88
+ 'Pragma': 'no-cache',
89
+ 'Expires': '0',
90
+ 'User-Agent': 'MediCafe-Updater/2.0.0'
91
+ }
92
+
93
+ last = None
198
94
  for attempt in range(1, retries + 1):
199
95
  try:
200
- response = requests.get("https://pypi.org/pypi/{}/json".format(package), timeout=10)
201
- response.raise_for_status() # Raise an error for bad responses
202
- data = response.json()
203
- latest_version = data['info']['version']
204
-
205
- # Print the version with attempt information
206
- if attempt == 1:
207
- print("Latest available version: {}".format(latest_version))
208
- else:
209
- print("Latest available version: {} ({} attempt)".format(latest_version, attempt))
210
-
211
- return latest_version # Return the version immediately - no double-checking
212
-
213
- except requests.RequestException as e:
214
- print("Attempt {}: Error fetching latest version: {}".format(attempt, e))
215
- if attempt < retries:
216
- print("Retrying in {} seconds...".format(delay))
217
- time.sleep(delay)
218
- return None
96
+ url = "https://pypi.org/pypi/{}/json?t={}".format(package, int(time.time()))
97
+ resp = requests.get(url, headers=headers, timeout=10)
98
+ resp.raise_for_status()
99
+ data = resp.json()
100
+ latest = data.get('info', {}).get('version')
101
+ if not latest:
102
+ raise Exception("Malformed PyPI response")
103
+
104
+ # Pragmatic double-fetch-if-equal to mitigate CDN staleness
105
+ if last and latest == last:
106
+ return latest
107
+ last = latest
108
+ if attempt == retries:
109
+ return latest
110
+ # If we just fetched same as before and it's equal to current installed, refetch once more quickly
111
+ time.sleep(1)
112
+ except Exception:
113
+ if attempt == retries:
114
+ return None
115
+ time.sleep(1)
116
+
117
+ return last
118
+
219
119
 
220
120
  def check_internet_connection():
121
+ if not requests:
122
+ return False
221
123
  try:
222
124
  requests.get("http://www.google.com", timeout=5)
223
125
  return True
224
- except requests.ConnectionError:
126
+ except Exception:
225
127
  return False
226
128
 
227
- def clear_python_cache(workspace_path=None):
228
- """
229
- Clear Python bytecode cache files to prevent import issues after updates.
230
-
231
- Args:
232
- workspace_path (str, optional): Path to the workspace root. If None,
233
- will attempt to detect automatically.
234
-
235
- Returns:
236
- bool: True if cache was cleared successfully, False otherwise
237
- """
238
- try:
239
- print_status("Clearing Python bytecode cache...", "INFO")
240
-
241
- # If no workspace path provided, try to detect it
242
- if not workspace_path:
243
- # Try to find the MediCafe workspace by looking for common directories
244
- current_dir = os.getcwd()
245
- potential_paths = [
246
- current_dir,
247
- os.path.dirname(current_dir),
248
- os.path.join(current_dir, '..'),
249
- os.path.join(current_dir, '..', '..')
250
- ]
251
-
252
- for path in potential_paths:
253
- if os.path.exists(os.path.join(path, 'MediCafe')) and \
254
- os.path.exists(os.path.join(path, 'MediBot')) and \
255
- os.path.exists(os.path.join(path, 'MediLink')):
256
- workspace_path = path
257
- break
258
-
259
- if not workspace_path:
260
- print_status("Could not detect workspace path. Cache clearing skipped.", "WARNING")
261
- return False
262
-
263
- print("Workspace path: {}".format(workspace_path))
264
-
265
- # Directories to clear cache from
266
- cache_dirs = [
267
- os.path.join(workspace_path, 'MediCafe'),
268
- os.path.join(workspace_path, 'MediBot'),
269
- os.path.join(workspace_path, 'MediLink'),
270
- workspace_path # Root workspace
271
- ]
272
-
273
- cleared_count = 0
274
- # First, remove __pycache__ directories (these are few, so prints are acceptable)
275
- for cache_dir in cache_dirs:
276
- if os.path.exists(cache_dir):
277
- pycache_path = os.path.join(cache_dir, '__pycache__')
278
- if os.path.exists(pycache_path):
279
- try:
280
- shutil.rmtree(pycache_path)
281
- print("Cleared cache: {}".format(pycache_path))
282
- cleared_count += 1
283
- except Exception as e:
284
- print("Warning: Could not clear cache at {}: {}".format(pycache_path, e))
285
-
286
- # Next, collect all .pyc files to provide a clean progress indicator
287
- pyc_files = []
288
- for cache_dir in cache_dirs:
289
- if os.path.exists(cache_dir):
290
- for root, dirs, files in os.walk(cache_dir):
291
- for file in files:
292
- if file.endswith('.pyc'):
293
- pyc_files.append(os.path.join(root, file))
294
-
295
- # Remove .pyc files with a progress bar (tqdm if available, otherwise a single-line counter)
296
- if pyc_files:
297
- total_files = len(pyc_files)
298
- removed_files = 0
299
-
300
- if TQDM_AVAILABLE:
301
- for file_path in tqdm(pyc_files, desc="Removing .pyc files", unit="file"):
302
- try:
303
- os.remove(file_path)
304
- cleared_count += 1
305
- removed_files += 1
306
- except Exception as e:
307
- print("Warning: Could not remove .pyc file {}: {}".format(os.path.basename(file_path), e))
308
- else:
309
- # Minimal, XP-safe single-line progress indicator
310
- for file_path in pyc_files:
311
- try:
312
- os.remove(file_path)
313
- cleared_count += 1
314
- removed_files += 1
315
- except Exception as e:
316
- print("Warning: Could not remove .pyc file {}: {}".format(os.path.basename(file_path), e))
317
- # Update progress on one line
318
- try:
319
- sys.stdout.write("\rRemoving .pyc files: {}/{}".format(removed_files, total_files))
320
- sys.stdout.flush()
321
- except Exception:
322
- pass
323
- # Finish the line after completion
324
- try:
325
- sys.stdout.write("\n")
326
- sys.stdout.flush()
327
- except Exception:
328
- pass
329
-
330
- if cleared_count > 0:
331
- print_status("Successfully cleared {} cache items".format(cleared_count), "SUCCESS")
332
- return True
333
- else:
334
- print_status("No cache files found to clear", "INFO")
335
- return True
336
-
337
- except Exception as e:
338
- print_status("Error clearing cache: {}".format(e), "ERROR")
339
- return False
340
129
 
341
- def compare_versions(version1, version2):
342
- v1_parts = list(map(int, version1.split(".")))
343
- v2_parts = list(map(int, version2.split(".")))
344
- return (v1_parts > v2_parts) - (v1_parts < v2_parts)
345
-
346
- def upgrade_package(package, retries=4, delay=2, target_version=None):
347
- """
348
- Attempts to upgrade the package multiple times with escalating techniques.
349
-
350
- CRITICAL FIX (2025): Improved version verification logic to properly detect upgrade
351
- success/failure. The original code would consider an upgrade "successful" even when
352
- there was a version mismatch, leading to inconsistent behavior.
353
-
354
- Now properly compares expected vs actual versions and only returns True if the
355
- upgrade actually succeeded with the correct version.
356
- """
357
- if not check_internet_connection():
358
- print_status("No internet connection detected. Please check your internet connection and try again.", "ERROR")
359
- print_final_result(False, "No internet connection available")
360
-
361
- # Light verbosity: show pinned target once
362
- if target_version:
363
- print("Pinned target version: {}".format(target_version))
364
-
365
- def get_installed_version_fresh(package):
366
- """Get installed version using a fresh subprocess to avoid pkg_resources cache issues."""
367
- try:
368
- # Clear cache first
369
- clear_pkg_resources_cache()
370
-
371
- # First try pip show
372
- process = subprocess.Popen(
373
- [sys.executable, '-m', 'pip', 'show', package],
374
- stdout=subprocess.PIPE,
375
- stderr=subprocess.PIPE
376
- )
377
- stdout, stderr = process.communicate()
378
- if process.returncode == 0:
379
- for line in stdout.decode().splitlines():
380
- if line.startswith("Version:"):
381
- return line.split(":", 1)[1].strip()
382
-
383
- # If pip show fails, try pkg_resources in a fresh subprocess
384
- try:
385
- import subprocess
386
- process = subprocess.Popen(
387
- [sys.executable, '-c', 'import pkg_resources; print(pkg_resources.get_distribution("{}").version)'.format(package)],
388
- stdout=subprocess.PIPE,
389
- stderr=subprocess.PIPE
390
- )
391
- stdout, stderr = process.communicate()
392
- if process.returncode == 0:
393
- return stdout.decode().strip()
394
- except Exception:
395
- pass
396
-
397
- return None
398
- except Exception as e:
399
- print("Warning: Could not get fresh version: {}".format(e))
400
- return None
130
+ # ---------- Upgrade logic (3 attempts, minimal delays) ----------
131
+ def run_pip_install(args):
132
+ proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
133
+ out, err = proc.communicate()
134
+ return proc.returncode, out.decode(), err.decode()
401
135
 
402
- def try_upgrade_with_strategy(attempt, strategy_name, cmd_args):
403
- """Try upgrade with specific strategy and return success status."""
404
- print("Attempt {}/{}: Using {} strategy...".format(attempt, retries, strategy_name))
405
-
406
- pkg_spec = package
407
- if target_version:
408
- pkg_spec = "{}=={}".format(package, target_version)
409
-
410
- cmd = [sys.executable, '-m', 'pip', 'install'] + cmd_args + [pkg_spec]
411
-
412
- process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
413
- stdout, stderr = process.communicate()
414
-
415
- if process.returncode == 0:
416
- print(stdout.decode().strip())
417
- # Add longer delay to allow file system and package metadata to settle
418
- # FIXED: Increased delay for XP compatibility
419
- print("Waiting for package metadata to update...")
420
- time.sleep(5) # Increased from 3 to 5 seconds
421
-
422
- # Try multiple times to get the new version with increasing delays
423
- new_version = None
424
- for retry in range(3):
425
- # Clear pkg_resources cache before each attempt
426
- clear_pkg_resources_cache()
427
-
428
- new_version = get_installed_version_fresh(package)
429
- if new_version:
430
- print("Detected new version: {}".format(new_version))
431
- break
432
- print("Version detection attempt {} failed, retrying...".format(retry + 1))
433
- time.sleep(3) # Increased from 2 to 3 seconds
434
-
435
- expected_version = target_version or get_latest_version(package)
436
-
437
- # FIXED: Improved version verification logic
438
- if expected_version and new_version:
439
- version_comparison = compare_versions(new_version, expected_version)
440
- if version_comparison >= 0:
441
- print_status("Attempt {}: Upgrade succeeded with {}!".format(attempt, strategy_name), "SUCCESS")
442
- return True
443
- else:
444
- print_status("Upgrade failed: Version mismatch. Current: {} Expected: {}".format(
445
- new_version, expected_version), "ERROR")
446
- return False
447
- elif new_version:
448
- # If we got a new version but can't verify expected, still consider success
449
- print_status("Upgrade succeeded but version verification unclear. New version: {}".format(new_version), "WARNING")
450
- return True
451
- else:
452
- print_status("Upgrade incomplete. Could not detect new version.", "ERROR")
453
- return False
454
- else:
455
- print(stderr.decode().strip())
456
- print_status("Attempt {}: Upgrade failed with {}.".format(attempt, strategy_name), "WARNING")
457
- return False
458
-
459
- # Define escalation strategies for each attempt
460
- strategies = {
461
- 1: [
462
- ("Gentle Upgrade", ['--upgrade', '--no-deps', '--no-cache-dir', '--disable-pip-version-check', '-q']),
463
- ("Force Reinstall", ['--upgrade', '--force-reinstall', '--no-cache-dir', '--disable-pip-version-check', '-q'])
464
- ],
465
- 2: [
466
- ("Clean Install", ['--upgrade', '--force-reinstall', '--no-cache-dir', '--disable-pip-version-check', '--ignore-installed', '-q']),
467
- ("User Install", ['--upgrade', '--user', '--no-cache-dir', '--disable-pip-version-check', '-q'])
468
- ],
469
- 3: [
470
- ("Aggressive Clean", ['--upgrade', '--force-reinstall', '--no-cache-dir', '--disable-pip-version-check', '--ignore-installed', '--no-deps', '-q']),
471
- ("Pre-download", ['--upgrade', '--force-reinstall', '--no-cache-dir', '--disable-pip-version-check', '--pre', '-q'])
472
- ],
473
- 4: [
474
- ("Nuclear Option", ['--upgrade', '--force-reinstall', '--no-cache-dir', '--disable-pip-version-check', '--ignore-installed', '--no-deps', '--pre', '-q']),
475
- ("Last Resort", ['--upgrade', '--force-reinstall', '--no-cache-dir', '--disable-pip-version-check', '--ignore-installed', '--no-deps', '--pre', '--user', '-q'])
476
- ]
477
- }
478
136
 
479
- for attempt in range(1, retries + 1):
480
- print("Attempt {}/{} to upgrade {}...".format(attempt, retries, package))
481
-
482
- # Try each strategy for this attempt
483
- for strategy_name, cmd_args in strategies[attempt]:
484
- if try_upgrade_with_strategy(attempt, strategy_name, cmd_args):
485
- time.sleep(delay)
486
- return True
487
-
488
- # If we get here, all strategies for this attempt failed
489
- if attempt < retries:
490
- # Escalating delays: 2s, 3s, 5s
491
- current_delay = delay + (attempt - 1)
492
- print("All strategies failed for attempt {}. Retrying in {} seconds...".format(attempt, current_delay))
493
- try:
494
- time.sleep(current_delay + (random.random() * 1.0))
495
- except Exception:
496
- time.sleep(current_delay)
497
-
498
- print_status("All upgrade attempts failed.", "ERROR")
499
- return False
137
+ def verify_post_install(package, expected_version):
138
+ # Try quick reads with minimal backoff to avoid unnecessary slowness
139
+ for _ in range(3):
140
+ installed = get_installed_version(package)
141
+ if installed:
142
+ # Re-fetch latest once to avoid stale latest
143
+ latest_again = get_latest_version(package, retries=1) or expected_version
144
+ if compare_versions(installed, latest_again) >= 0:
145
+ return True, installed
146
+ time.sleep(1)
147
+ return False, get_installed_version(package)
148
+
500
149
 
501
- def ensure_dependencies():
502
- """Ensure all dependencies listed in setup.py are installed and up-to-date."""
503
- # Don't try to read requirements.txt as it won't be available after installation
504
- # Instead, hardcode the same dependencies that are in setup.py
505
- required_packages = [
506
- 'requests==2.21.0',
507
- 'argparse==1.4.0',
508
- 'tqdm==4.14.0',
509
- 'python-docx==0.8.11',
510
- 'PyYAML==5.2',
511
- 'chardet==3.0.4',
512
- 'msal==1.26.0'
150
+ def upgrade_package(package):
151
+ strategies = [
152
+ ['install', '--upgrade', package, '--no-cache-dir', '--disable-pip-version-check'],
153
+ ['install', '--upgrade', '--force-reinstall', package, '--no-cache-dir', '--disable-pip-version-check'],
154
+ ['install', '--upgrade', '--force-reinstall', '--ignore-installed', '--user', package, '--no-cache-dir', '--disable-pip-version-check']
513
155
  ]
514
156
 
515
- # Define problematic packages for Windows XP with Python 3.4
516
- problematic_packages = ['numpy==1.11.3', 'pandas==0.20.0', 'lxml==4.2.0']
517
- is_windows_py34 = sys.version_info[:2] == (3, 4) and platform.system() == 'Windows'
518
-
519
- if is_windows_py34:
520
- print_status("Detected Windows with Python 3.4", "INFO")
521
- print("Please ensure the following packages are installed manually:")
522
- for pkg in problematic_packages:
523
- package_name, version = pkg.split('==')
524
- try:
525
- installed_version = pkg_resources.get_distribution(package_name).version
526
- print("{} {} is already installed".format(package_name, installed_version))
527
- if installed_version != version:
528
- print("Note: Installed version ({}) differs from required ({})".format(installed_version, version))
529
- print("If you experience issues, consider installing version {} manually".format(version))
530
- except pkg_resources.DistributionNotFound:
531
- print("{} is not installed".format(package_name))
532
- print("Please install {}=={} manually using a pre-compiled wheel".format(package_name, version))
533
- print("Download from: https://www.lfd.uci.edu/~gohlke/pythonlibs/")
534
- print("Then run: pip install path\\to\\{}-{}-cp34-cp34m-win32.whl".format(package_name, version))
535
- print("\nContinuing with other dependencies...")
536
- else:
537
- # Add problematic packages to the list for non-Windows XP environments
538
- required_packages.extend(problematic_packages)
157
+ latest_before = get_latest_version(package, retries=2)
158
+ if not latest_before:
159
+ print_status('ERROR', 'Unable to determine latest version from PyPI')
160
+ return False
539
161
 
540
- for pkg in required_packages:
541
- if '==' in pkg:
542
- package_name, version = pkg.split('==') # Extract package name and version
162
+ for idx, parts in enumerate(strategies):
163
+ attempt = idx + 1
164
+ print_section("Attempt {}/3".format(attempt))
165
+ cmd = [sys.executable, '-m', 'pip'] + parts
166
+ print_status('INFO', 'Running: {} -m pip {}'.format(sys.executable, ' '.join(parts)))
167
+ code, out, err = run_pip_install(cmd)
168
+ if code == 0:
169
+ ok, installed = verify_post_install(package, latest_before)
170
+ if ok:
171
+ print_status('SUCCESS', 'Installed version: {}'.format(installed))
172
+ return True
173
+ else:
174
+ print_status('WARNING', 'Install returned success but version not updated yet{}'.format(
175
+ '' if not installed else ' (detected {})'.format(installed)))
543
176
  else:
544
- package_name = pkg
545
- version = None # No specific version required
177
+ # Show error output concisely
178
+ if err:
179
+ print(err.strip())
180
+ print_status('WARNING', 'pip returned non-zero exit code ({})'.format(code))
181
+
182
+ return False
546
183
 
547
- # Skip problematic packages on Windows XP Python 3.4
548
- if is_windows_py34 and any(package_name in p for p in problematic_packages):
549
- continue
550
184
 
551
- try:
552
- installed_version = pkg_resources.get_distribution(package_name).version
553
- if version and installed_version != version: # Check if installed version matches required version
554
- print("Current version of {}: {}".format(package_name, installed_version))
555
- print("Required version of {}: {}".format(package_name, version))
556
- time.sleep(2) # Pause for 2 seconds to allow user to read the output
557
- if not upgrade_package(package_name): # Attempt to upgrade/downgrade to the required version
558
- print_status("Failed to upgrade/downgrade {} to version {}.".format(package_name, version), "WARNING")
559
- time.sleep(2) # Pause for 2 seconds after failure message
560
- elif version and installed_version == version: # Check if installed version matches required version
561
- print("All versions match for {}. No changes needed.".format(package_name))
562
- time.sleep(1) # Pause for 2 seconds to allow user to read the output
563
- elif not version: # If no specific version is required, check for the latest version
564
- latest_version = get_latest_version(package_name)
565
- if latest_version and installed_version != latest_version:
566
- print("Current version of {}: {}".format(package_name, installed_version))
567
- print("Latest version of {}: {}".format(package_name, latest_version))
568
- time.sleep(2) # Pause for 2 seconds to allow user to read the output
569
- if not upgrade_package(package_name):
570
- print_status("Failed to upgrade {}.".format(package_name), "WARNING")
571
- time.sleep(2) # Pause for 2 seconds after failure message
572
- except pkg_resources.DistributionNotFound:
573
- print("Package {} is not installed. Attempting to install...".format(package_name))
574
- time.sleep(2) # Pause for 2 seconds before attempting installation
575
- if not upgrade_package(package_name):
576
- print_status("Failed to install {}.".format(package_name), "WARNING")
577
- time.sleep(2) # Pause for 2 seconds after failure message
578
-
579
- def check_for_updates_only():
580
- """
581
- Check if a new version is available without performing the upgrade.
582
- Returns a simple status message for batch script consumption.
583
- """
185
+ # ---------- Main ----------
186
+ def main():
187
+ print_banner("MediCafe Updater ({} v{})".format(SCRIPT_NAME, SCRIPT_VERSION))
188
+ print_status('INFO', 'Python: {}'.format(sys.version.split(" ")[0]))
189
+ print_status('INFO', 'Platform: {}'.format(platform.platform()))
190
+
584
191
  if not check_internet_connection():
585
- print("ERROR")
586
- return
587
-
588
- package = "medicafe"
589
- current_version = get_installed_version(package)
590
- if not current_version:
591
- print("ERROR")
592
- return
593
-
594
- latest_version = get_latest_version(package)
595
- if not latest_version:
596
- print("ERROR")
597
- return
598
-
599
- if compare_versions(latest_version, current_version) > 0:
600
- print("UPDATE_AVAILABLE:" + latest_version)
192
+ print_section('Network check')
193
+ print_status('ERROR', 'No internet connection detected')
194
+ sys.exit(1)
195
+
196
+ print_section('Environment')
197
+ current = get_installed_version(PACKAGE_NAME)
198
+ if current:
199
+ print_status('INFO', 'Installed {}: {}'.format(PACKAGE_NAME, current))
601
200
  else:
602
- print("UP_TO_DATE")
201
+ print_status('WARNING', '{} is not currently installed'.format(PACKAGE_NAME))
603
202
 
604
- def main():
605
- global DEBUG_MODE
606
- # Enable debug mode if requested via CLI or environment
607
- DEBUG_MODE = ('--debug' in sys.argv) or (os.environ.get('MEDICAFE_DEBUG', '0') in ['1', 'true', 'TRUE'])
608
-
609
- print_status("MediCafe Update Utility v1.1.0", "INFO")
610
- print("Starting update process...")
611
-
612
- # STEP 1: Environment Information
613
- debug_step(1, "Environment Information",
614
- "Python version: {}\n"
615
- "Platform: {}\n"
616
- "Current working directory: {}\n"
617
- "Script location: {}\n"
618
- "sys.executable: {}".format(
619
- sys.version, platform.platform(), os.getcwd(),
620
- __file__, sys.executable))
621
-
622
- # STEP 2: Check Python and pip
623
- debug_step(2, "Python and pip Verification")
624
- print("Checking Python installation...")
625
- try:
626
- process = subprocess.Popen([sys.executable, '--version'],
627
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
628
- stdout, stderr = process.communicate()
629
- if process.returncode == 0:
630
- print("Python version: {}".format(stdout.decode().strip()))
631
- else:
632
- print("Error checking Python: {}".format(stderr.decode().strip()))
633
- except Exception as e:
634
- print("Error checking Python: {}".format(e))
203
+ latest = get_latest_version(PACKAGE_NAME, retries=3)
204
+ if not latest:
205
+ print_status('ERROR', 'Could not fetch latest version information from PyPI')
206
+ sys.exit(1)
207
+ print_status('INFO', 'Latest {} on PyPI: {}'.format(PACKAGE_NAME, latest))
635
208
 
636
- print("\nChecking pip installation...")
637
- try:
638
- process = subprocess.Popen([sys.executable, '-m', 'pip', '--version'],
639
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
640
- stdout, stderr = process.communicate()
641
- if process.returncode == 0:
642
- print("pip version: {}".format(stdout.decode().strip()))
643
- else:
644
- print("Error checking pip: {}".format(stderr.decode().strip()))
645
- except Exception as e:
646
- print("Error checking pip: {}".format(e))
647
-
648
- # STEP 3: Check MediCafe package
649
- debug_step(3, "MediCafe Package Check")
650
- package = "medicafe"
651
- current_version = get_installed_version(package)
652
- if current_version:
653
- print("Current MediCafe version: {}".format(current_version))
654
- else:
655
- print("MediCafe package not found or not accessible")
209
+ if current and compare_versions(latest, current) <= 0:
210
+ print_section('Status')
211
+ print_status('SUCCESS', 'Already up to date')
212
+ sys.exit(0)
656
213
 
657
- # STEP 4: Internet connectivity
658
- debug_step(4, "Internet Connectivity Test")
659
- if check_internet_connection():
660
- print("Internet connection: OK")
661
- print("Testing PyPI connectivity...")
662
- try:
663
- response = requests.get("https://pypi.org/pypi/medicafe/json", timeout=10)
664
- print("PyPI connectivity: OK (Status: {})".format(response.status_code))
665
- except Exception as e:
666
- print("PyPI connectivity: FAILED - {}".format(e))
667
- else:
668
- print("Internet connection: FAILED")
669
- print_final_result(False, "No internet connection available")
670
-
671
- # STEP 5: Check for updates
672
- debug_step(5, "Version Comparison")
673
- latest_version = get_latest_version(package)
674
- if latest_version:
675
- print("Latest available version: {}".format(latest_version))
676
- if current_version:
677
- comparison = compare_versions(latest_version, current_version)
678
- if comparison > 0:
679
- print("Update needed: Current ({}) < Latest ({})".format(current_version, latest_version))
680
- elif comparison == 0:
681
- print("Already up to date: Current ({}) = Latest ({})".format(current_version, latest_version))
682
- else:
683
- print("Version mismatch: Current ({}) > Latest ({})".format(current_version, latest_version))
684
- else:
685
- print("Cannot compare versions - current version not available")
686
- else:
687
- print("Could not retrieve latest version information")
688
- print_final_result(False, "Unable to fetch latest version")
689
-
690
- # STEP 6: Dependencies check (skipped by default in streamlined mode)
691
- debug_step(6, "Dependencies Check")
692
- if DEBUG_MODE:
693
- response = input("Do you want to check dependencies? (yes/no, default/enter is no): ").strip().lower()
694
- if response in ['yes', 'y']:
695
- ensure_dependencies()
696
- else:
697
- print_status("Skipping dependency check.", "INFO")
698
- else:
699
- print_status("Skipping dependency check (streamlined mode).", "INFO")
700
-
701
- # STEP 7: Perform update
702
- debug_step(7, "Update Execution")
703
- if current_version and latest_version and compare_versions(latest_version, current_version) > 0:
704
- print_status("A newer version is available. Proceeding with upgrade.", "INFO")
705
- print("Current version: {}".format(current_version))
706
- print("Target version: {}".format(latest_version))
707
-
708
- if upgrade_package(package, target_version=latest_version):
709
- # STEP 8: Verify upgrade
710
- debug_step(8, "Upgrade Verification")
711
-
712
- # Clear cache and wait for package metadata to settle
713
- clear_pkg_resources_cache()
714
-
715
- # FIXED: Increased wait time for XP compatibility
716
- time.sleep(3) # Increased from 2 to 3 seconds
717
- new_version = get_installed_version(package)
718
- print("New installed version: {}".format(new_version))
719
-
720
- if new_version and compare_versions(new_version, latest_version) >= 0:
721
- print_status("Upgrade successful. New version: {}".format(new_version), "SUCCESS")
722
-
723
- # DEBUG STEP 9: Clear cache
724
- debug_step(9, "Cache Clearing")
725
- print_status("Clearing Python cache to prevent import issues...", "INFO")
726
- if clear_python_cache():
727
- print_status("Cache cleared successfully. Update complete.", "SUCCESS")
728
- else:
729
- print_status("Cache clearing failed, but update was successful.", "WARNING")
730
-
731
- print_final_result(True, "Successfully upgraded to version {}".format(new_version))
732
- elif new_version:
733
- print_status("Upgrade completed but version verification unclear. New version: {}".format(new_version), "WARNING")
734
- print_status("The package was updated, but version comparison failed. This may be due to caching issues.", "WARNING")
735
-
736
- # Still clear cache and exit successfully
737
- debug_step(9, "Cache Clearing")
738
- print_status("Clearing Python cache to prevent import issues...", "INFO")
739
- if clear_python_cache():
740
- print_status("Cache cleared successfully. Update complete.", "SUCCESS")
741
- else:
742
- print_status("Cache clearing failed, but update was successful.", "WARNING")
743
-
744
- print_final_result(True, "Package updated (version verification unclear)")
745
- else:
746
- print_status("Upgrade verification failed. Could not detect new version.", "ERROR")
747
- print_final_result(False, "Upgrade verification failed")
748
- else:
749
- print_final_result(False, "Upgrade process failed")
750
- else:
751
- print_status("You already have the latest version installed.", "SUCCESS")
752
- print_final_result(True, "Already running latest version")
753
-
754
- if __name__ == "__main__":
755
- if len(sys.argv) > 1:
756
- if sys.argv[1] == "--check-only":
757
- check_for_updates_only()
758
- sys.exit(0)
759
- elif sys.argv[1] == "--clear-cache":
760
- # Standalone cache clearing mode
761
- print_status("MediCafe Cache Clearing Utility", "INFO")
762
- workspace_path = sys.argv[2] if len(sys.argv) > 2 else None
763
- if clear_python_cache(workspace_path):
764
- print_status("Cache clearing completed successfully", "SUCCESS")
765
- sys.exit(0)
766
- else:
767
- print_status("Cache clearing failed", "ERROR")
768
- sys.exit(1)
214
+ print_section('Upgrade')
215
+ print_status('INFO', 'Upgrading {} to {} (up to 3 attempts)'.format(PACKAGE_NAME, latest))
216
+ success = upgrade_package(PACKAGE_NAME)
217
+
218
+ print_section('Result')
219
+ final_version = get_installed_version(PACKAGE_NAME)
220
+ if success:
221
+ print_status('SUCCESS', 'Update completed. {} is now at {}'.format(PACKAGE_NAME, final_version or '(unknown)'))
222
+ print_status('INFO', 'This updater script: v{}'.format(SCRIPT_VERSION))
223
+ sys.exit(0)
769
224
  else:
770
- main()
225
+ print_status('ERROR', 'Update failed.')
226
+ if final_version and current and compare_versions(final_version, current) > 0:
227
+ print_status('WARNING', 'Partial success: detected {} after failures'.format(final_version))
228
+ print_status('INFO', 'This updater script: v{}'.format(SCRIPT_VERSION))
229
+ sys.exit(1)
230
+
231
+
232
+ if __name__ == '__main__':
233
+ # Optional quick mode: --check-only prints machine-friendly status
234
+ if len(sys.argv) > 1 and sys.argv[1] == '--check-only':
235
+ if not check_internet_connection():
236
+ print('ERROR')
237
+ sys.exit(1)
238
+ cur = get_installed_version(PACKAGE_NAME)
239
+ lat = get_latest_version(PACKAGE_NAME, retries=2)
240
+ if not cur or not lat:
241
+ print('ERROR')
242
+ sys.exit(1)
243
+ print('UPDATE_AVAILABLE:' + lat if compare_versions(lat, cur) > 0 else 'UP_TO_DATE')
244
+ sys.exit(0)
245
+ main()