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

Commit f80f436

Browse files
committed
workspace uploading
some minor refactoring
1 parent b2717c7 commit f80f436

File tree

7 files changed

+178
-35
lines changed

7 files changed

+178
-35
lines changed

Pipfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ verify_ssl = true
66
[packages]
77
e1839a8 = {path = ".",editable = true}
88
requests = {extras = ["security"]}
9+
requests-toolbelt = *
10+
progressbar2 = *
911
cryptography = {extras = ["security"]}
1012
"boto3" = "*"
1113
botocore = "*"
@@ -14,6 +16,7 @@ gradient-statsd = "*"
1416
click = "*"
1517
terminaltables = "*"
1618

19+
1720
[dev-packages]
1821
twine = "*"
1922
pypandoc = "*"

paperspace/cli/cli.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,17 @@ def common_experiments_create_options(f):
112112
type=int,
113113
help="Port to use in new experiment",
114114
),
115+
click.option(
116+
"--workspace",
117+
"workspace",
118+
required=False,
119+
help="Path to workspace directory or archive",
120+
default="."
121+
),
115122
click.option(
116123
"--workspaceUrl",
117124
"workspaceUrl",
118-
required=True,
125+
required=False,
119126
help="Project git repository url",
120127
),
121128
click.option(
@@ -309,7 +316,8 @@ def common_experiments_create_single_node_options(f):
309316
def create_multi_node(api_key, **kwargs):
310317
del_if_value_is_none(kwargs)
311318
experiments_api = client.API(config.CONFIG_EXPERIMENTS_HOST, api_key=api_key)
312-
experiments_commands.create_experiment(kwargs, api=experiments_api)
319+
command = experiments_commands.CreateExperimentCommand(api=experiments_api)
320+
command.execute(kwargs)
313321

314322

315323
@create_experiment.command(name="singlenode", help="Create single node experiment")
@@ -319,7 +327,8 @@ def create_single_node(api_key, **kwargs):
319327
kwargs["experimentTypeId"] = constants.ExperimentType.SINGLE_NODE
320328
del_if_value_is_none(kwargs)
321329
experiments_api = client.API(config.CONFIG_EXPERIMENTS_HOST, api_key=api_key)
322-
experiments_commands.create_experiment(kwargs, api=experiments_api)
330+
command = experiments_commands.CreateExperimentCommand(api=experiments_api)
331+
command.execute(kwargs)
323332

324333

325334
@create_and_start_experiment.command(name="multinode", help="Create and start new multi node experiment")
@@ -328,7 +337,8 @@ def create_single_node(api_key, **kwargs):
328337
def create_and_start_multi_node(api_key, **kwargs):
329338
del_if_value_is_none(kwargs)
330339
experiments_api = client.API(config.CONFIG_EXPERIMENTS_HOST, api_key=api_key)
331-
experiments_commands.create_and_start_experiment(kwargs, api=experiments_api)
340+
command = experiments_commands.CreateAndStartExperimentCommand(api=experiments_api)
341+
command.execute(kwargs)
332342

333343

334344
@create_and_start_experiment.command(name="singlenode", help="Create and start new single node experiment")
@@ -338,7 +348,8 @@ def create_and_start_single_node(api_key, **kwargs):
338348
kwargs["experimentTypeId"] = constants.ExperimentType.SINGLE_NODE
339349
del_if_value_is_none(kwargs)
340350
experiments_api = client.API(config.CONFIG_EXPERIMENTS_HOST, api_key=api_key)
341-
experiments_commands.create_and_start_experiment(kwargs, api=experiments_api)
351+
command = experiments_commands.CreateAndStartExperimentCommand(api=experiments_api)
352+
command.execute(kwargs)
342353

343354

344355
@experiments.command("start", help="Start experiment")

paperspace/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ 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):
31+
def post(self, url, json=None, params=None, files=None):
3232
path = self.get_path(url)
33-
response = requests.post(path, json=json, params=params, headers=self.headers)
33+
response = requests.post(path, json=json, params=params, headers=self.headers, files=files)
3434
logger.debug("POST request sent to: {} \n\theaders: {}\n\tjson: {}\n\tparams: {}"
3535
.format(response.url, self.headers, json, params))
3636
logger.debug("Response status code: {}".format(response.status_code))

paperspace/commands/experiments.py

Lines changed: 138 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,157 @@
1+
import os
12
import pydoc
3+
import zipfile
24

5+
import click
6+
import progressbar
7+
import requests
38
import terminaltables
9+
from requests_toolbelt.multipart import encoder
410

511
from paperspace import logger, constants, client, config
12+
from paperspace.commands import CommandBase
13+
from paperspace.exceptions import PresignedUrlUnreachableException, S3UploadFailedException
614
from paperspace.logger import log_response
715
from paperspace.utils import get_terminal_lines
816

17+
# from clint.textui.progress import Bar as ProgressBar
18+
919
experiments_api = client.API(config.CONFIG_EXPERIMENTS_HOST, headers=client.default_headers)
1020

1121

12-
def _log_create_experiment(response, success_msg_template, error_msg, logger_=logger):
13-
if response.ok:
14-
j = response.json()
15-
handle = j["handle"]
16-
msg = success_msg_template.format(handle)
17-
logger_.log(msg)
18-
else:
19-
try:
20-
data = response.json()
21-
logger_.log_error_response(data)
22-
except ValueError:
23-
logger_.log(error_msg)
22+
class ExperimentCommand(CommandBase):
23+
def _log_create_experiment(self, response, success_msg_template, error_msg):
24+
if response.ok:
25+
j = response.json()
26+
handle = j["handle"]
27+
msg = success_msg_template.format(handle)
28+
self.logger.log(msg)
29+
else:
30+
try:
31+
data = response.json()
32+
self.logger.log_error_response(data)
33+
except ValueError:
34+
self.logger.log(error_msg)
35+
36+
37+
class CreateExperimentCommand(ExperimentCommand):
38+
def retrieve_file_paths(self, dirName):
39+
40+
# setup file paths variable
41+
filePaths = []
42+
exclude = ['.git']
43+
# Read all directory, subdirectories and file lists
44+
for root, dirs, files in os.walk(dirName, topdown=True):
45+
dirs[:] = [d for d in dirs if d not in exclude]
46+
for filename in files:
47+
# Create the full filepath by using os module.
48+
filePath = os.path.join(root, filename)
49+
filePaths.append(filePath)
50+
51+
# return all paths
52+
return filePaths
53+
54+
def _zip_workspace(self, workspace_path):
55+
if not workspace_path:
56+
workspace_path = '.'
57+
zip_file_name = os.path.basename(os.getcwd()) + '.zip'
58+
else:
59+
zip_file_name = os.path.basename(workspace_path) + '.zip'
60+
61+
zip_file_path = os.path.join(workspace_path, zip_file_name)
62+
63+
if os.path.exists(zip_file_path):
64+
self.logger.log('Removing existing archive')
65+
os.remove(zip_file_path)
66+
67+
file_paths = self.retrieve_file_paths(workspace_path)
68+
69+
self.logger.log('Creating zip archive: %s' % zip_file_name)
70+
zip_file = zipfile.ZipFile(zip_file_path, 'w')
71+
72+
bar = progressbar.ProgressBar(max_value=len(file_paths))
73+
74+
with zip_file:
75+
i = 0
76+
for file in file_paths:
77+
i+=1
78+
self.logger.debug('Adding %s to archive' % file)
79+
zip_file.write(file)
80+
bar.update(i)
81+
bar.finish()
82+
self.logger.log('\nFinished creating archive: %s' % zip_file_name)
83+
return zip_file_path
84+
85+
def _create_callback(self, encoder_obj):
86+
bar = progressbar.ProgressBar(max_value=encoder_obj.len)
87+
88+
def callback(monitor):
89+
bar.update(monitor.bytes_read)
90+
if monitor.bytes_read == monitor.len:
91+
bar.finish()
92+
return callback
93+
94+
def _upload_workspace(self, input_data):
95+
workspace_url = input_data.get('workspaceUrl')
96+
workspace_path = input_data.get('workspacePath')
97+
workspace_archive = input_data.get('workspaceArchive')
98+
if (workspace_archive and workspace_path) or (workspace_archive and workspace_url) or (
99+
workspace_path and workspace_url):
100+
raise click.UsageError("Use either:\n\t--workspaceUrl to point repository URL"
101+
"\n\t--workspacePath to point on project directory"
102+
"\n\t--workspaceArchive to point on project ZIP archive"
103+
"\n or neither to use current directory")
104+
105+
if workspace_url:
106+
return # nothing to do
107+
108+
if workspace_archive:
109+
archive_path = os.path.abspath(workspace_archive)
110+
else:
111+
archive_path = self._zip_workspace(workspace_path)
112+
113+
s3_upload_data = self._get_upload_data(os.path.basename(archive_path))
114+
115+
self.logger.log('Uploading zipped workspace to S3')
116+
117+
files = {'file': (archive_path, open(archive_path, 'rb'))}
118+
fields = s3_upload_data['fields']
119+
fields.update(files)
120+
121+
s3_encoder = encoder.MultipartEncoder(fields=fields)
122+
monitor = encoder.MultipartEncoderMonitor(s3_encoder, callback=self._create_callback(s3_encoder))
123+
s3_response = requests.post(s3_upload_data['url'], data=monitor, headers={'Content-Type': monitor.content_type})
124+
if not s3_response.ok:
125+
raise S3UploadFailedException(s3_response)
126+
127+
self.logger.log('\nUploading completed')
128+
s3_workspace_url = s3_response.headers.get('Location')
129+
return s3_workspace_url
130+
131+
def execute(self, json_):
132+
workspace_url = self._upload_workspace(json_)
133+
if workspace_url:
134+
json_['workspaceUrl'] = workspace_url
24135

136+
response = self.api.post("/experiments/", json=json_)
25137

26-
def create_experiment(json_, api=experiments_api):
27-
response = api.post("/experiments/", json=json_)
138+
self._log_create_experiment(response,
139+
"New experiment created with handle: {}",
140+
"Unknown error while creating the experiment")
28141

29-
_log_create_experiment(response,
30-
"New experiment created with handle: {}",
31-
"Unknown error while creating the experiment")
142+
def _get_upload_data(self, file_name):
143+
response = self.api.get("/workspace/get_presigned_url", params={'workspaceName': file_name})
144+
if response.status_code == 404:
145+
raise PresignedUrlUnreachableException
146+
return response.json()
32147

33148

34-
def create_and_start_experiment(json_, api=experiments_api):
35-
response = api.post("/experiments/create_and_start/", json=json_)
36-
_log_create_experiment(response,
37-
"New experiment created and started with handle: {}",
38-
"Unknown error while creating/starting the experiment")
149+
class CreateAndStartExperimentCommand(ExperimentCommand):
150+
def execute(self, json_):
151+
response = self.api.post("/experiments/create_and_start/", json=json_)
152+
self._log_create_experiment(response,
153+
"New experiment created and started with handle: {}",
154+
"Unknown error while creating/starting the experiment")
39155

40156

41157
def start_experiment(experiment_handle, api=experiments_api):

paperspace/commands/machines.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import terminaltables
55

66
from paperspace.commands import CommandBase
7-
from paperspace.exceptions import BadResponse
7+
from paperspace.exceptions import BadResponseException
88
from paperspace.utils import get_terminal_lines
99

1010

@@ -228,7 +228,7 @@ def execute(self, machine_id, state, interval=5):
228228
while True:
229229
try:
230230
current_state = self._get_machine_state(machine_id)
231-
except BadResponse as e:
231+
except BadResponseException as e:
232232
self.logger.log(e)
233233
return
234234
else:
@@ -246,8 +246,8 @@ def _get_machine_state(self, machine_id):
246246
json_ = response.json()
247247
if not response.ok:
248248
self.logger.log_error_response(json_)
249-
raise BadResponse("Error while reading machine state")
249+
raise BadResponseException("Error while reading machine state")
250250
state = json_.get("state")
251251
except (ValueError, AttributeError):
252-
raise BadResponse("Unknown error while reading machine state")
252+
raise BadResponseException("Unknown error while reading machine state")
253253
return state

paperspace/exceptions.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
1-
class BadResponse(Exception):
1+
class ApplicationException(Exception):
2+
pass
3+
4+
5+
class BadResponseException(ApplicationException):
6+
pass
7+
8+
9+
class PresignedUrlUnreachableException(ApplicationException):
10+
pass
11+
12+
13+
class S3UploadFailedException(ApplicationException):
214
pass

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
],
4141
keywords='paperspace api development library',
4242
packages=find_packages(exclude=['contrib', 'docs', 'tests', 'old_tests']),
43-
install_requires=['requests[security]', 'boto3', 'botocore', 'six', 'gradient-statsd', 'click', 'terminaltables'],
43+
install_requires=['requests[security]', 'boto3', 'botocore', 'six', 'gradient-statsd', 'click', 'terminaltables',
44+
'requests-toolbelt', 'progressbar2'],
4445
entry_points={'console_scripts': [
4546
'paperspace-python = paperspace.main:main',
4647
]},

0 commit comments

Comments
 (0)