diff --git a/pyproject.toml b/pyproject.toml index cc6d4fd..69996b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ "ipython>=8.26.0", "libcst>=1.4.0", "jupytext>=1.16.3", + "requests>=2.32.3", ] readme = "README.md" requires-python = ">= 3.9" diff --git a/requirements-dev.lock b/requirements-dev.lock index 0fe010e..4c50ada 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -7,6 +7,7 @@ # all-features: false # with-sources: false # generate-hashes: false +# universal: false -e file:. annotated-types==0.7.0 @@ -20,6 +21,10 @@ beautifulsoup4==4.12.3 # via nbconvert bleach==6.1.0 # via nbconvert +certifi==2024.7.4 + # via requests +charset-normalizer==3.3.2 + # via requests decorator==5.1.1 # via ipython defusedxml==0.7.1 @@ -35,6 +40,8 @@ gitdb==4.0.11 # via gitpython gitpython==3.1.43 # via haipera +idna==3.7 + # via requests iniconfig==2.0.0 # via pytest ipython==8.26.0 @@ -122,6 +129,8 @@ pyzmq==26.0.3 referencing==0.35.1 # via jsonschema # via jsonschema-specifications +requests==2.32.3 + # via haipera rpds-py==0.19.0 # via jsonschema # via referencing @@ -159,6 +168,8 @@ typing-extensions==4.12.2 # via ipython # via pydantic # via pydantic-core +urllib3==2.2.2 + # via requests wcwidth==0.2.13 # via prompt-toolkit webencodings==0.5.1 diff --git a/requirements.lock b/requirements.lock index ea38983..764b822 100644 --- a/requirements.lock +++ b/requirements.lock @@ -7,6 +7,7 @@ # all-features: false # with-sources: false # generate-hashes: false +# universal: false -e file:. annotated-types==0.7.0 @@ -20,6 +21,10 @@ beautifulsoup4==4.12.3 # via nbconvert bleach==6.1.0 # via nbconvert +certifi==2024.7.4 + # via requests +charset-normalizer==3.3.2 + # via requests decorator==5.1.1 # via ipython defusedxml==0.7.1 @@ -34,6 +39,8 @@ gitdb==4.0.11 # via gitpython gitpython==3.1.43 # via haipera +idna==3.7 + # via requests ipython==8.26.0 # via haipera jedi==0.19.1 @@ -115,6 +122,8 @@ pyzmq==26.0.3 referencing==0.35.1 # via jsonschema # via jsonschema-specifications +requests==2.32.3 + # via haipera rpds-py==0.19.0 # via jsonschema # via referencing @@ -151,6 +160,8 @@ typing-extensions==4.12.2 # via ipython # via pydantic # via pydantic-core +urllib3==2.2.2 + # via requests wcwidth==0.2.13 # via prompt-toolkit webencodings==0.5.1 diff --git a/src/haipera/cli/__init__.py b/src/haipera/cli/__init__.py index 1ffa3fc..9c3111f 100644 --- a/src/haipera/cli/__init__.py +++ b/src/haipera/cli/__init__.py @@ -27,6 +27,9 @@ convert_source_code_to_ipynb, is_jupyter_kernel_installed, ) +from haipera.cloud import ( + run_code_on_cloud +) from haipera.constants import YELLOW, MAGENTA, GREEN, RED, RESET sys.stdout.reconfigure(line_buffering=True) @@ -495,11 +498,6 @@ def main(): mode = HaiperaMode(sys.argv[1]) - if mode == HaiperaMode.CLOUD: - print( - f"{MAGENTA}Cloud runs are in development. Please sign up on the waitlist for updates at https://www.haipera.com{RESET}" - ) - sys.exit(1) path = sys.argv[2] if not os.path.exists(path): @@ -509,6 +507,10 @@ def main(): cli_args = parse_args(sys.argv[3:]) hyperparameters = expand_args_dict(cli_args) + if mode == HaiperaMode.CLOUD: + run_code_on_cloud(path, sys.argv[3:]) + sys.exit(0) + if ( not path.endswith(".py") and not path.endswith(".toml") @@ -583,7 +585,8 @@ def main(): print( f"{YELLOW}Warning: Configuration file {config_path} already exists{RESET}" ) - overwrite = input("Do you want to overwrite it? (y/n): ") + print("Do you want to overwrite it? (y/n): ") + overwrite = sys.stdin.readline() if overwrite.lower() == "y": generated_config, package_file = generate_config_file(tree, path) @@ -604,7 +607,8 @@ def main(): if len(experiment_configs) > 100: print(f"{YELLOW}Warning: Running {len(experiment_configs)} experiments{RESET}") - confirm = input("Do you want to continue? (y/n): ") + print("Do you want to continue? (y/n): ") + confirm = sys.stdin.readline() if confirm.lower() != "y": sys.exit(0) elif len(experiment_configs) == 0: diff --git a/src/haipera/cloud.py b/src/haipera/cloud.py new file mode 100644 index 0000000..425a293 --- /dev/null +++ b/src/haipera/cloud.py @@ -0,0 +1,40 @@ +from collections import deque +import os +import requests +import base64 + +SERVER_URL = 'http://127.0.0.1:8000/job' + +def load_directory(directory: str): + code_dir = {} + q = deque([(code_dir, directory)]) + while len(q) > 0: + node, dir = q.popleft() + rel_dir = os.path.relpath(dir, directory) + for obj in os.listdir(dir): + full_path = os.path.join(dir, obj) + rel_path = os.path.join(rel_dir, obj) + if os.path.isdir(full_path): + node[rel_path] = {} + q.append((node[rel_path], full_path)) + else: + content_bytes = open(full_path, 'rb').read() + encoded = base64.b64encode(content_bytes).decode('utf-8') + node[rel_path] = encoded + return code_dir + +def run_code_on_cloud(script_path, cli_args): + directory = os.path.dirname(script_path) + + code_dir = load_directory(directory) + data = { + 'code_dir': code_dir, + 'script_path': script_path, + 'cli_args': cli_args + } + print(f'Running on the cloud at {SERVER_URL}') + with requests.post(SERVER_URL, json=data, stream=True) as response: + response.raise_for_status() + for chunk in response.iter_content(chunk_size=None): + if chunk: + print(chunk.decode('utf-8')) \ No newline at end of file