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

Commit fcb5d4a

Browse files
committed
Merge remote-tracking branch 'origin/master' into send-workspace-with-raw-post
# Conflicts: # paperspace/client.py # paperspace/commands/jobs.py # paperspace/workspace.py
2 parents 787dd1f + 96c3075 commit fcb5d4a

File tree

8 files changed

+200
-8
lines changed

8 files changed

+200
-8
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/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/commands/jobs.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ def _make_table(self, logs, table, table_data):
145145
class CreateJobCommand(JobsCommandBase):
146146
def __init__(self, workspace_handler=None, **kwargs):
147147
super(CreateJobCommand, self).__init__(**kwargs)
148-
# experiments_api = client.API(config.CONFIG_EXPERIMENTS_HOST, api_key=kwargs.get('api_key'))
149148
self._workspace_handler = workspace_handler or WorkspaceHandler(logger=self.logger)
150149

151150
def execute(self, json_):

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

paperspace/workspace.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,10 @@ def __init__(self, experiments_api, logger=None):
144144
self.experiments_api = experiments_api
145145

146146
def handle(self, input_data):
147-
archive_path = super(S3WorkspaceHandler, self).handle(input_data)
148-
if archive_path in ['none', None]:
149-
return archive_path
150-
147+
workspace = super(S3WorkspaceHandler, self).handle(input_data)
148+
if not self.archive_path:
149+
return workspace
150+
archive_path = workspace
151151
file_name = os.path.basename(archive_path)
152152
project_handle = input_data['projectHandle']
153153

tests/functional/test_run.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import sys
2+
3+
import mock
4+
from click.testing import CliRunner
5+
6+
from paperspace.cli import cli
7+
from paperspace.client import default_headers
8+
from tests import MockResponse
9+
10+
11+
class TestRunCommand(object):
12+
command_name = 'run'
13+
common_commands = ["--name", "test", "--projectId", "projectId", "--apiKey", "some_key"]
14+
url = "https://api.paperspace.io/jobs/createJob/"
15+
headers = default_headers.copy()
16+
headers["X-API-Key"] = "some_key"
17+
18+
@mock.patch("paperspace.client.requests.post")
19+
@mock.patch("paperspace.workspace.WorkspaceHandler._zip_workspace")
20+
@mock.patch("paperspace.commands.jobs.CreateJobCommand._get_files_dict")
21+
def test_run_simple_file_with_args(self, get_files_patched, workspace_zip_patched, post_patched):
22+
get_files_patched.return_value = mock.MagicMock()
23+
workspace_zip_patched.return_value = '/foo/bar'
24+
post_patched.return_value = MockResponse(status_code=200)
25+
26+
runner = CliRunner()
27+
result = runner.invoke(cli.cli, [self.command_name] + self.common_commands + ["/myscript.py", "a", "b"])
28+
29+
expected_headers = self.headers.copy()
30+
expected_headers.update({
31+
'Content-Type': "multipart/form-data"
32+
})
33+
post_patched.assert_called_with(self.url,
34+
params={'name': u'test', 'projectId': u'projectId',
35+
'workspaceFileName': 'bar',
36+
'command': 'python{} myscript.py a b'.format(str(sys.version_info[0])),
37+
'projectHandle': u'projectId',
38+
'container': u'paperspace/tensorflow-python'},
39+
data=None,
40+
files=mock.ANY,
41+
headers=expected_headers,
42+
json=None)
43+
44+
@mock.patch("paperspace.client.requests.post")
45+
def test_run_python_command_with_args_and_no_workspace(self, post_patched):
46+
post_patched.return_value = MockResponse(status_code=200)
47+
48+
runner = CliRunner()
49+
result = runner.invoke(cli.cli,
50+
[self.command_name] + self.common_commands + ["-c", "print(foo)", "--workspace", "none"])
51+
52+
expected_headers = self.headers.copy()
53+
post_patched.assert_called_with(self.url,
54+
params={'name': u'test', 'projectId': u'projectId',
55+
'workspaceFileName': 'none',
56+
'workspace': 'none',
57+
'command': 'python{} -c print(foo)'.format(str(sys.version_info[0])),
58+
'projectHandle': u'projectId',
59+
'container': u'paperspace/tensorflow-python'},
60+
data=None,
61+
files=None,
62+
headers=expected_headers,
63+
json=None)
64+
65+
@mock.patch("paperspace.client.requests.post")
66+
@mock.patch("paperspace.workspace.WorkspaceHandler._zip_workspace")
67+
def test_run_shell_command_with_args_with_s3_workspace(self, workspace_zip_patched, post_patched):
68+
workspace_zip_patched.return_value = '/foo/bar'
69+
post_patched.return_value = MockResponse(status_code=200)
70+
71+
runner = CliRunner()
72+
result = runner.invoke(cli.cli,
73+
[self.command_name] + self.common_commands + ["-s", "echo foo", "--workspaceUrl",
74+
"s3://bucket/object"])
75+
76+
expected_headers = self.headers.copy()
77+
post_patched.assert_called_with(self.url,
78+
params={'name': u'test', 'projectId': u'projectId',
79+
'workspaceFileName': 's3://bucket/object',
80+
'workspaceUrl': 's3://bucket/object',
81+
'command': 'echo foo',
82+
'projectHandle': u'projectId',
83+
'container': u'paperspace/tensorflow-python'},
84+
data=None,
85+
files=None,
86+
headers=expected_headers,
87+
json=None)

0 commit comments

Comments
 (0)