Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| Deploy script for DR MURPHY ODE Dashboard to HuggingFace Spaces. | |
| Run this from any directory: | |
| python3 /path/to/hf_ode_space/deploy.py | |
| Steps: | |
| 1. Copy source files from the project tree into this flat directory | |
| 2. Patch sys.path imports for flat structure | |
| 3. Fix run_sweep_unified argument mismatch | |
| 4. Test import chain | |
| 5. Deploy to HuggingFace | |
| """ | |
| import shutil | |
| import os | |
| import sys | |
| import re | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| SRC_BASE = os.path.dirname(os.path.dirname(HERE)) # cycle2mdot_acceleration/ | |
| def step1_copy_files(): | |
| """Copy all source files into the flat deployment directory.""" | |
| print("Step 1: Copying source files...") | |
| # Dashboard files | |
| dash = os.path.join(SRC_BASE, 'ode_reformulation/dashboard') | |
| for f in ['scenario_gui.py', 'scenario_analyzer.py', 'sweep_cache.py', 'pump_animation.js']: | |
| src = os.path.join(dash, f) | |
| dst = os.path.join(HERE, f) | |
| shutil.copy2(src, dst) | |
| print(f" Copied dashboard/{f}") | |
| # ODE engine files | |
| ode = os.path.join(SRC_BASE, 'ode_reformulation') | |
| for f in ['cycle2mdot_ode_v2.py', 'cycle2mdot_10var_njit.py']: | |
| shutil.copy2(os.path.join(ode, f), os.path.join(HERE, f)) | |
| print(f" Copied ode_reformulation/{f}") | |
| # Project root files | |
| for f in ['cycle2mdot_fast.py', 'cycle2mdot_cached.py', 'h2_ps_lut.npz']: | |
| shutil.copy2(os.path.join(SRC_BASE, f), os.path.join(HERE, f)) | |
| print(f" Copied {f}") | |
| # break_coolprop directory | |
| bc_src = os.path.join(SRC_BASE, 'break_coolprop') | |
| bc_dst = os.path.join(HERE, 'break_coolprop') | |
| os.makedirs(bc_dst, exist_ok=True) | |
| for f in ['__init__.py', 'helmholtz_h2.py', 'lut_ps.py', 'h2_props.py', 'cycle2mdot_break.py']: | |
| shutil.copy2(os.path.join(bc_src, f), os.path.join(bc_dst, f)) | |
| print(f" Copied break_coolprop/{f}") | |
| # sweep_analysis.py is already created by the Write tool | |
| # engine_bridge.py, app.py, README.md, requirements.txt are already present | |
| print(" Done.") | |
| def step2_patch_imports(): | |
| """Patch sys.path hacks in copied engine files for flat structure.""" | |
| print("\nStep 2: Patching imports for flat structure...") | |
| # cycle2mdot_ode_v2.py: change sys.path.insert to use current dir | |
| _patch_sys_path(os.path.join(HERE, 'cycle2mdot_ode_v2.py')) | |
| # cycle2mdot_10var_njit.py: same | |
| _patch_sys_path(os.path.join(HERE, 'cycle2mdot_10var_njit.py')) | |
| print(" Done.") | |
| def _patch_sys_path(filepath): | |
| """Replace sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) | |
| with sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))""" | |
| with open(filepath, 'r') as f: | |
| content = f.read() | |
| # Replace grandparent path with current directory path | |
| old_pattern = "sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))" | |
| new_pattern = "sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))" | |
| if old_pattern in content: | |
| content = content.replace(old_pattern, new_pattern) | |
| with open(filepath, 'w') as f: | |
| f.write(content) | |
| print(f" Patched {os.path.basename(filepath)}: sys.path -> current dir") | |
| else: | |
| print(f" {os.path.basename(filepath)}: no sys.path patch needed") | |
| def step3_fix_sweep_args(): | |
| """Fix run_sweep_unified to accept the engine argument from sr_all_inputs.""" | |
| print("\nStep 3: Fixing run_sweep_unified argument mismatch...") | |
| gui_path = os.path.join(HERE, 'scenario_gui.py') | |
| with open(gui_path, 'r') as f: | |
| content = f.read() | |
| # Find the function signature and add engine parameter | |
| old_sig = " fill_type, vsnubber, vchss, aov140f, rodia,\n num_cycles=1):" | |
| new_sig = " fill_type, vsnubber, vchss, aov140f, rodia,\n num_cycles=1, engine='njit_10var'):" | |
| if old_sig in content and "def run_sweep_unified" in content: | |
| content = content.replace(old_sig, new_sig, 1) | |
| with open(gui_path, 'w') as f: | |
| f.write(content) | |
| print(" Added 'engine' parameter to run_sweep_unified()") | |
| else: | |
| print(" run_sweep_unified already has engine param or signature changed") | |
| print(" Done.") | |
| def step4_test_imports(): | |
| """Test that the import chain works in the flat structure.""" | |
| print("\nStep 4: Testing import chain...") | |
| # Add this directory to path | |
| if HERE not in sys.path: | |
| sys.path.insert(0, HERE) | |
| try: | |
| import engine_bridge | |
| print(f" engine_bridge: OK (engines: {engine_bridge.ENGINES})") | |
| except Exception as e: | |
| print(f" engine_bridge: FAILED - {e}") | |
| return False | |
| try: | |
| import scenario_analyzer | |
| print(" scenario_analyzer: OK") | |
| except Exception as e: | |
| print(f" scenario_analyzer: FAILED - {e}") | |
| return False | |
| try: | |
| import sweep_analysis | |
| print(" sweep_analysis: OK") | |
| except Exception as e: | |
| print(f" sweep_analysis: FAILED - {e}") | |
| return False | |
| try: | |
| import sweep_cache | |
| print(" sweep_cache: OK") | |
| except Exception as e: | |
| print(f" sweep_cache: FAILED - {e}") | |
| return False | |
| print(" All imports OK!") | |
| return True | |
| def step5_deploy(): | |
| """Deploy to HuggingFace Spaces.""" | |
| print("\nStep 5: Deploying to HuggingFace...") | |
| try: | |
| from huggingface_hub import HfApi | |
| except ImportError: | |
| print(" huggingface_hub not installed. Installing...") | |
| os.system(f"{sys.executable} -m pip install huggingface_hub") | |
| from huggingface_hub import HfApi | |
| api = HfApi() | |
| # Create the repo (or verify it exists) | |
| try: | |
| api.create_repo("csh2/ode-pump-dashboard", repo_type="space", | |
| space_sdk="gradio", exist_ok=True) | |
| print(" Repository csh2/ode-pump-dashboard created/verified.") | |
| except Exception as e: | |
| print(f" Error creating repo: {e}") | |
| print(" You may need to run: huggingface-cli login") | |
| return False | |
| # Upload the folder (exclude deploy.py, __pycache__, .db files) | |
| try: | |
| api.upload_folder( | |
| folder_path=HERE, | |
| repo_id="csh2/ode-pump-dashboard", | |
| repo_type="space", | |
| ignore_patterns=["deploy.py", "__pycache__", "*.db", "*.db-shm", "*.db-wal", | |
| ".DS_Store", "*.pyc"], | |
| ) | |
| print(" Upload complete!") | |
| print(" Space URL: https://huggingface.co/spaces/csh2/ode-pump-dashboard") | |
| except Exception as e: | |
| print(f" Upload failed: {e}") | |
| print(" You may need to run: huggingface-cli login") | |
| return False | |
| return True | |
| def main(): | |
| print("=" * 60) | |
| print("DR MURPHY ODE -- HuggingFace Space Deployment") | |
| print("=" * 60) | |
| step1_copy_files() | |
| step2_patch_imports() | |
| step3_fix_sweep_args() | |
| ok = step4_test_imports() | |
| if not ok: | |
| print("\nImport test failed. Fix errors before deploying.") | |
| sys.exit(1) | |
| step5_deploy() | |
| print("\n" + "=" * 60) | |
| print("Deployment complete!") | |
| print("=" * 60) | |
| if __name__ == "__main__": | |
| main() | |