Skip to content
This repository was archived by the owner on Aug 11, 2020. It is now read-only.

Commit 96c3075

Browse files
Merge pull request #111 from Paperspace/run-restored
Command `run` restored
2 parents bdf07b6 + b3c521e commit 96c3075

17 files changed

+472
-159
lines changed

paperspace/cli/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import paperspace.cli.machines
1111
import paperspace.cli.models
1212
import paperspace.cli.projects
13+
import paperspace.cli.run
1314

1415

1516
def show(self, file=None):

paperspace/cli/common.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import functools
12
import getpass
23

34
import click
4-
from click import group
55
from click_didyoumean import DYMMixin
66
from click_help_colors import HelpColorsGroup
77

@@ -32,10 +32,10 @@ def group(self, *args, **kwargs):
3232
aliases.append(kwargs.pop('alias'))
3333

3434
def decorator(f):
35-
cmd = group(*_args, **kwargs)(f)
35+
cmd = click.group(*_args, **kwargs)(f)
3636
self.add_command(cmd)
3737
for alias in set(aliases):
38-
alias_cmd = group(alias, **kwargs)(f)
38+
alias_cmd = click.group(alias, **kwargs)(f)
3939
self.add_command(alias_cmd)
4040
alias_cmd.commands = cmd.commands
4141
return cmd
@@ -51,3 +51,20 @@ def callback_fun(ctx, param, value):
5151
return value
5252

5353
return callback_fun
54+
55+
56+
def deprecated(version="1.0.0"):
57+
deprecated_invoke_notice = """DeprecatedWarning: \nWARNING: This command will not be included in version %s .
58+
For more information, please see:
59+
60+
https://docs.paperspace.com
61+
If you depend on functionality not listed there, please file an issue.""" % version
62+
63+
def new_invoke(self, ctx):
64+
click.echo(click.style(deprecated_invoke_notice, fg='red'), err=True)
65+
super(type(self), self).invoke(ctx)
66+
67+
def decorator(f):
68+
f.invoke = functools.partial(new_invoke, f)
69+
70+
return decorator

paperspace/cli/jobs.py

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import functools
2+
13
import click
24

35
from paperspace import client, config
@@ -64,28 +66,35 @@ def list_jobs(api_key, **filters):
6466
command.execute(filters=filters)
6567

6668

69+
def common_jobs_create_options(f):
70+
options = [
71+
click.option("--name", "name", help="Job name", required=True),
72+
click.option("--machineType", "machineType", help="Virtual machine type"),
73+
click.option("--container", "container", default="paperspace/tensorflow-python", help="Docker container"),
74+
click.option("--command", "command", help="Job command/entrypoint"),
75+
click.option("--ports", "ports", help="Mapped ports"),
76+
click.option("--isPublic", "isPublic", help="Flag: is job public"),
77+
click.option("--workspace", "workspace", required=False, help="Path to workspace directory"),
78+
click.option("--workspaceArchive", "workspaceArchive", required=False, help="Path to workspace archive"),
79+
click.option("--workspaceUrl", "workspaceUrl", required=False, help="Project git repository url"),
80+
click.option("--workingDirectory", "workingDirectory", help="Working directory for the experiment", ),
81+
click.option("--ignoreFiles", "ignore_files", help="Ignore certain files from uploading"),
82+
click.option("--experimentId", "experimentId", help="Experiment Id"),
83+
click.option("--jobEnv", "envVars", type=json_string, help="Environmental variables "),
84+
click.option("--useDockerfile", "useDockerfile", help="Flag: using Dockerfile"),
85+
click.option("--isPreemptible", "isPreemptible", help="Flag: isPreemptible"),
86+
click.option("--project", "project", help="Project name"),
87+
click.option("--projectId", "projectHandle", help="Project ID"),
88+
click.option("--startedByUserId", "startedByUserId", help="User ID"),
89+
click.option("--relDockerfilePath", "relDockerfilePath", help="Relative path to Dockerfile"),
90+
click.option("--registryUsername", "registryUsername", help="Docker registry username"),
91+
click.option("--registryPassword", "registryPassword", help="Docker registry password"),
92+
]
93+
return functools.reduce(lambda x, opt: opt(x), reversed(options), f)
94+
95+
6796
@jobs_group.command("create", help="Create job")
68-
@click.option("--name", "name", help="Job name", required=True)
69-
@click.option("--machineType", "machineType", help="Virtual machine type")
70-
@click.option("--container", "container", help="Docker container")
71-
@click.option("--command", "command", help="Job command/entrypoint")
72-
@click.option("--ports", "ports", help="Mapped ports")
73-
@click.option("--isPublic", "isPublic", help="Flag: is job public")
74-
@click.option("--workspace", "workspace", required=False, help="Path to workspace directory")
75-
@click.option("--workspaceArchive", "workspaceArchive", required=False, help="Path to workspace archive")
76-
@click.option("--workspaceUrl", "workspaceUrl", required=False, help="Project git repository url")
77-
@click.option("--workingDirectory", "workingDirectory", help="Working directory for the experiment")
78-
@click.option("--ignoreFiles", "ignore_files", help="Ignore certain files from uploading")
79-
@click.option("--experimentId", "experimentId", help="Experiment Id")
80-
@click.option("--jobEnv", "envVars", type=json_string, help="Environmental variables ")
81-
@click.option("--useDockerfile", "useDockerfile", help="Flag: using Dockerfile")
82-
@click.option("--isPreemptible", "isPreemptible", help="Flag: isPreemptible")
83-
@click.option("--project", "project", help="Project name")
84-
@click.option("--projectId", "projectHandle", help="Project ID", required=True)
85-
@click.option("--startedByUserId", "startedByUserId", help="User ID")
86-
@click.option("--relDockerfilePath", "relDockerfilePath", help="Relative path to Dockerfile")
87-
@click.option("--registryUsername", "registryUsername", help="Docker registry username")
88-
@click.option("--registryPassword", "registryPassword", help="Docker registry password")
97+
@common_jobs_create_options
8998
@api_key_option
9099
@click.pass_context
91100
def create_job(ctx, api_key, **kwargs):

paperspace/cli/run.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import click
2+
3+
from paperspace import client, config
4+
from paperspace.cli import common
5+
from paperspace.cli.cli import cli
6+
from paperspace.cli.common import del_if_value_is_none, deprecated
7+
from paperspace.cli.jobs import common_jobs_create_options
8+
from paperspace.commands.run import RunCommand
9+
from paperspace.constants import RunMode
10+
11+
12+
@deprecated(version="0.6.0")
13+
@cli.command("run", help="Run script or command on remote cluster")
14+
@click.option("-c", "--python-command", "mode", flag_value=RunMode.RUN_MODE_PYTHON_COMMAND)
15+
@click.option("-m", "--module", "mode", flag_value=RunMode.RUN_MODE_PYTHON_MODULE)
16+
@click.option("-s", "--shell", "mode", flag_value=RunMode.RUN_MODE_SHELL_COMMAND)
17+
@common_jobs_create_options
18+
@click.argument("script", nargs=-1, required=True)
19+
@common.api_key_option
20+
def run(api_key, **kwargs):
21+
del_if_value_is_none(kwargs)
22+
jobs_api = client.API(config.CONFIG_HOST, api_key=api_key)
23+
command = RunCommand(api=jobs_api)
24+
command.execute(**kwargs)

paperspace/client.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ def get_path(self, url):
2828
template = "{}{}" if url.startswith("/") else "{}/{}"
2929
return template.format(api_url, url)
3030

31-
def post(self, url, json=None, params=None, files=None):
31+
def post(self, url, json=None, params=None, files=None, data=None):
3232
path = self.get_path(url)
33-
logger.debug("POST request sent to: {} \n\theaders: {}\n\tjson: {}\n\tparams: {}"
34-
.format(path, self.headers, json, params))
35-
response = requests.post(path, json=json, params=params, headers=self.headers, files=files)
33+
logger.debug("POST request sent to: {} \n\theaders: {}\n\tjson: {}\n\tparams: {}\n\tdata: {}"
34+
.format(path, self.headers, json, params, data))
35+
response = requests.post(path, json=json, params=params, headers=self.headers, files=files, data=data)
3636
logger.debug("Response status code: {}".format(response.status_code))
3737
logger.debug("Response content: {}".format(response.content))
3838
return response

paperspace/commands/experiments.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def _log_create_experiment(self, response, success_msg_template, error_msg):
3434
class CreateExperimentCommand(ExperimentCommand):
3535

3636
def execute(self, json_):
37-
workspace_url = self._workspace_handler.upload_workspace(json_)
37+
workspace_url = self._workspace_handler.handle(json_)
3838
if workspace_url:
3939
json_['workspaceUrl'] = workspace_url
4040

@@ -47,7 +47,7 @@ def execute(self, json_):
4747

4848
class CreateAndStartExperimentCommand(ExperimentCommand):
4949
def execute(self, json_):
50-
workspace_url = self._workspace_handler.upload_workspace(json_)
50+
workspace_url = self._workspace_handler.handle(json_)
5151
if workspace_url:
5252
json_['workspaceUrl'] = workspace_url
5353

paperspace/commands/jobs.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
import terminaltables
44
from click import style
55

6-
from paperspace import config, client
76
from paperspace.commands import common
87
from paperspace.exceptions import BadResponseError
98
from paperspace.utils import get_terminal_lines
10-
from paperspace.workspace import S3WorkspaceHandler
9+
from paperspace.workspace import WorkspaceHandler
1110

1211

1312
class JobsCommandBase(common.CommandBase):
@@ -146,22 +145,41 @@ def _make_table(self, logs, table, table_data):
146145
class CreateJobCommand(JobsCommandBase):
147146
def __init__(self, workspace_handler=None, **kwargs):
148147
super(CreateJobCommand, self).__init__(**kwargs)
149-
experiments_api = client.API(config.CONFIG_EXPERIMENTS_HOST, api_key=kwargs.get('api_key'))
150-
self._workspace_handler = workspace_handler or S3WorkspaceHandler(experiments_api=experiments_api,
151-
logger=self.logger)
148+
self._workspace_handler = workspace_handler or WorkspaceHandler(logger=self.logger)
152149

153150
def execute(self, json_):
154151
url = "/jobs/createJob/"
155-
156-
workspace_url = self._workspace_handler.upload_workspace(json_)
152+
files = None
153+
workspace_url = self._workspace_handler.handle(json_)
157154
if workspace_url:
158-
json_['workspaceFileName'] = workspace_url
159-
json_['projectId'] = json_.get('projectId', json_.get('projectHandle'))
160-
response = self.api.post(url, json_)
155+
if self._workspace_handler.archive_path:
156+
archive_basename = self._workspace_handler.archive_basename
157+
json_["workspaceFileName"] = archive_basename
158+
self.api.headers["Content-Type"] = "multipart/form-data"
159+
files = self._get_files_dict(workspace_url)
160+
else:
161+
json_["workspaceFileName"] = workspace_url
162+
163+
self.set_project(json_)
164+
165+
response = self.api.post(url, params=json_, files=files)
161166
self._log_message(response,
162167
"Job created",
163168
"Unknown error while creating job")
164169

170+
@staticmethod
171+
def _get_files_dict(workspace_url):
172+
files = {"file": open(workspace_url, "rb")}
173+
return files
174+
175+
@staticmethod
176+
def set_project(json_):
177+
project_id = json_.get("projectId", json_.get("projectHandle"))
178+
if not project_id:
179+
json_["project"] = "paperspace-python"
180+
else:
181+
json_["projectId"] = project_id
182+
165183

166184
class ArtifactsDestroyCommand(JobsCommandBase):
167185
def execute(self, job_id, files=None):

paperspace/commands/run.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import os
2+
import sys
3+
4+
from paperspace import client, config, logger
5+
from paperspace.commands.jobs import CreateJobCommand
6+
from paperspace.constants import RunMode
7+
from paperspace.workspace import WorkspaceHandler
8+
9+
10+
class RunCommand(object):
11+
12+
def __init__(self, api=None, logger_=logger):
13+
self.api = api
14+
self.logger = logger_
15+
16+
@staticmethod
17+
def _get_executor(mode, python_version=None):
18+
python_version = python_version or str(sys.version_info[0]) # defaults locally running version
19+
python_bin = 'python{v}'.format(v=python_version)
20+
executors = {
21+
RunMode.RUN_MODE_DEFAULT: python_bin,
22+
RunMode.RUN_MODE_PYTHON_COMMAND: '{python} -c'.format(python=python_bin),
23+
RunMode.RUN_MODE_SHELL_COMMAND: '',
24+
RunMode.RUN_MODE_PYTHON_MODULE: '{python} -m'.format(python=python_bin),
25+
}
26+
return executors[mode]
27+
28+
@staticmethod
29+
def _clear_script_name(script_name, mode):
30+
if mode == RunMode.RUN_MODE_DEFAULT:
31+
return os.path.basename(script_name)
32+
return script_name
33+
34+
def _create_command(self, mode, script, python_version=None):
35+
command_parts = []
36+
executor = self._get_executor(mode, python_version)
37+
if executor:
38+
command_parts.append(executor)
39+
40+
script_name = self._clear_script_name(script[0], mode)
41+
if script_name:
42+
command_parts.append(script_name)
43+
44+
script_params = ' '.join(script[1:])
45+
if script_params:
46+
command_parts.append(script_params)
47+
48+
command = ' '.join(command_parts)
49+
return command
50+
51+
def execute(self, mode=None, script=None, **json_):
52+
mode = mode or RunMode.RUN_MODE_DEFAULT
53+
command = self._create_command(mode, script)
54+
json_['command'] = command
55+
56+
command = CreateJobCommand(api=self.api, workspace_handler=WorkspaceHandler())
57+
command.execute(json_)

paperspace/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,10 @@ class Region(object):
5959
"C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10")
6060

6161
BILLING_TYPES = ["hourly", "monthly"]
62+
63+
64+
class RunMode:
65+
RUN_MODE_DEFAULT = 1
66+
RUN_MODE_PYTHON_COMMAND = 2
67+
RUN_MODE_SHELL_COMMAND = 3
68+
RUN_MODE_PYTHON_MODULE = 4

0 commit comments

Comments
 (0)