easy-sm 0.1.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of easy-sm might be problematic. Click here for more details.
- easy_sm/__init__.py +0 -0
- easy_sm/__main__.py +27 -0
- easy_sm/commands/__init__.py +0 -0
- easy_sm/commands/build.py +74 -0
- easy_sm/commands/cloud.py +266 -0
- easy_sm/commands/initialize.py +169 -0
- easy_sm/commands/local.py +80 -0
- easy_sm/commands/push.py +79 -0
- easy_sm/config/__init__.py +0 -0
- easy_sm/config/config.py +52 -0
- easy_sm/sagemaker/__init__.py +0 -0
- easy_sm/sagemaker/sagemaker.py +265 -0
- easy_sm/template/__init__.py +0 -0
- easy_sm/template/easy_sm_base/Dockerfile +27 -0
- easy_sm/template/easy_sm_base/__init__.py +0 -0
- easy_sm/template/easy_sm_base/build.sh +20 -0
- easy_sm/template/easy_sm_base/executor.sh +7 -0
- easy_sm/template/easy_sm_base/local_test/deploy_local.sh +7 -0
- easy_sm/template/easy_sm_base/local_test/test_dir/input/data/training/.gitkeep +0 -0
- easy_sm/template/easy_sm_base/local_test/test_dir/model/.gitkeep +0 -0
- easy_sm/template/easy_sm_base/local_test/test_dir/output/.gitkeep +0 -0
- easy_sm/template/easy_sm_base/local_test/train_local.sh +7 -0
- easy_sm/template/easy_sm_base/prediction/__init__.py +0 -0
- easy_sm/template/easy_sm_base/prediction/serve +57 -0
- easy_sm/template/easy_sm_base/push.sh +74 -0
- easy_sm/template/easy_sm_base/training/__init__.py +0 -0
- easy_sm/template/easy_sm_base/training/train +83 -0
- easy_sm/template/easy_sm_base/training/training.py +10 -0
- easy_sm-0.1.0.dist-info/LICENSE +21 -0
- easy_sm-0.1.0.dist-info/METADATA +9 -0
- easy_sm-0.1.0.dist-info/RECORD +39 -0
- easy_sm-0.1.0.dist-info/WHEEL +6 -0
- easy_sm-0.1.0.dist-info/entry_points.txt +2 -0
- easy_sm-0.1.0.dist-info/top_level.txt +2 -0
- src/__init__.py +0 -0
- src/easy_sm_base/__init__.py +0 -0
- src/easy_sm_base/prediction/__init__.py +0 -0
- src/easy_sm_base/training/__init__.py +0 -0
- src/easy_sm_base/training/training.py +29 -0
easy_sm/__init__.py
ADDED
|
File without changes
|
easy_sm/__main__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from easy_sm.commands.local import local
|
|
3
|
+
from easy_sm.commands.initialize import init
|
|
4
|
+
from easy_sm.commands.build import build
|
|
5
|
+
from easy_sm.commands.push import push
|
|
6
|
+
from easy_sm.commands.cloud import cloud
|
|
7
|
+
|
|
8
|
+
@click.group()
|
|
9
|
+
@click.option(u"-t", u"--docker-tag", default=u"latest", help=u"Specify tag for Docker image")
|
|
10
|
+
@click.pass_context
|
|
11
|
+
def cli(ctx, docker_tag):
|
|
12
|
+
"""
|
|
13
|
+
easy_sm enables training and deploying machine learning models on AWS SageMaker in a few minutes!
|
|
14
|
+
"""
|
|
15
|
+
ctx.obj = {'docker_tag': docker_tag}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def add_commands(cli):
|
|
19
|
+
cli.add_command(init)
|
|
20
|
+
cli.add_command(build)
|
|
21
|
+
cli.add_command(local)
|
|
22
|
+
cli.add_command(push)
|
|
23
|
+
cli.add_command(cloud)
|
|
24
|
+
# cli.add_command(configure)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
add_commands(cli)
|
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import click
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
from easy_sm.config.config import ConfigManager
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _build(source_dir, requirements_dir, image_name, docker_tag, python_version):
|
|
9
|
+
"""
|
|
10
|
+
Builds a Docker image that contains code under the given source root directory.
|
|
11
|
+
|
|
12
|
+
Assumes that Docker is installed and running locally.
|
|
13
|
+
|
|
14
|
+
:param source_dir: [str], source root directory
|
|
15
|
+
:param requirements_dir: [str], path to requirements.txt
|
|
16
|
+
:param image_name: [str], The name of the Docker image
|
|
17
|
+
:param docker_tag: [str], the Docker tag for the image
|
|
18
|
+
"""
|
|
19
|
+
easy_sm_module_path = os.path.relpath(os.path.join(source_dir, 'easy_sm_base/'))
|
|
20
|
+
|
|
21
|
+
build_script_path = os.path.join(easy_sm_module_path, 'build.sh')
|
|
22
|
+
dockerfile_path = os.path.join(easy_sm_module_path, 'Dockerfile')
|
|
23
|
+
|
|
24
|
+
train_file_path = os.path.join(easy_sm_module_path, 'training', 'train')
|
|
25
|
+
serve_file_path = os.path.join(easy_sm_module_path, 'prediction', 'serve')
|
|
26
|
+
executor_file_path = os.path.join(easy_sm_module_path, 'executor.sh')
|
|
27
|
+
|
|
28
|
+
if not os.path.isfile(build_script_path) or not os.path.isfile(train_file_path) or not \
|
|
29
|
+
os.path.isfile(serve_file_path):
|
|
30
|
+
raise ValueError("This is not a easy_sm directory: {}".format(source_dir))
|
|
31
|
+
|
|
32
|
+
os.chmod(train_file_path, 0o777)
|
|
33
|
+
os.chmod(serve_file_path, 0o777)
|
|
34
|
+
os.chmod(executor_file_path, 0o777)
|
|
35
|
+
|
|
36
|
+
target_dir_name = os.path.basename(os.path.normpath(source_dir))
|
|
37
|
+
print("------------ Using docker buildx to build from Mac for Sagemaker ------------")
|
|
38
|
+
print("------------ this behaviour is set in src/easy_sm_base/build.sh ------------")
|
|
39
|
+
output = subprocess.check_output(
|
|
40
|
+
[
|
|
41
|
+
"{}".format(build_script_path),
|
|
42
|
+
"{}".format(os.path.relpath(source_dir)),
|
|
43
|
+
"{}".format(os.path.relpath(target_dir_name)),
|
|
44
|
+
"{}".format(dockerfile_path),
|
|
45
|
+
"{}".format(os.path.relpath(requirements_dir)),
|
|
46
|
+
docker_tag,
|
|
47
|
+
image_name,
|
|
48
|
+
python_version
|
|
49
|
+
]
|
|
50
|
+
)
|
|
51
|
+
print(output)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@click.command()
|
|
55
|
+
@click.pass_obj
|
|
56
|
+
def build(obj):
|
|
57
|
+
"""
|
|
58
|
+
Command to build SageMaker app
|
|
59
|
+
"""
|
|
60
|
+
print("Started building SageMaker Docker image. It will take some minutes...\n")
|
|
61
|
+
|
|
62
|
+
config_file_path = os.path.join('.easy_sm.json')
|
|
63
|
+
if not os.path.isfile(config_file_path):
|
|
64
|
+
raise ValueError("This is not a easy_sm directory: {}".format(os.getcwd()))
|
|
65
|
+
|
|
66
|
+
config = ConfigManager(config_file_path).get_config()
|
|
67
|
+
_build(
|
|
68
|
+
source_dir=config.easy_sm_module_dir,
|
|
69
|
+
requirements_dir=config.requirements_dir,
|
|
70
|
+
docker_tag=obj['docker_tag'],
|
|
71
|
+
image_name=config.image_name,
|
|
72
|
+
python_version=config.python_version)
|
|
73
|
+
|
|
74
|
+
print("Docker image built successfully!")
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import click
|
|
4
|
+
from easy_sm.config.config import ConfigManager
|
|
5
|
+
from easy_sm.sagemaker import sagemaker
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _config():
|
|
9
|
+
if not os.path.isfile('.easy_sm.json'):
|
|
10
|
+
raise ValueError("This is not a easy_sm directory: {}".format(os.getcwd()))
|
|
11
|
+
else:
|
|
12
|
+
return ConfigManager('.easy_sm.json').get_config()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group()
|
|
16
|
+
def cloud():
|
|
17
|
+
"""
|
|
18
|
+
Commands for AWS operations: upload data, train and deploy
|
|
19
|
+
"""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.command(name='upload-data')
|
|
24
|
+
@click.option(u"-i", u"--input-dir", required=True, help="Path to data input directory")
|
|
25
|
+
@click.option(
|
|
26
|
+
u"-s", u"--s3-dir",
|
|
27
|
+
required=True,
|
|
28
|
+
help="s3 location to upload data",
|
|
29
|
+
type=click.Path()
|
|
30
|
+
)
|
|
31
|
+
@click.option(
|
|
32
|
+
u"-r",
|
|
33
|
+
u"--iam-role-arn",
|
|
34
|
+
required=False,
|
|
35
|
+
help="The AWS role to use for the upload command"
|
|
36
|
+
)
|
|
37
|
+
def upload_data(input_dir, s3_dir, iam_role_arn):
|
|
38
|
+
"""
|
|
39
|
+
Command to upload data to S3
|
|
40
|
+
"""
|
|
41
|
+
print("Started uploading data to S3...\n")
|
|
42
|
+
config = _config()
|
|
43
|
+
sage_maker_client = sagemaker.SageMakerClient(config.aws_profile, config.aws_region, iam_role_arn)
|
|
44
|
+
s3_path = sage_maker_client.upload_data(input_dir, s3_dir)
|
|
45
|
+
print("Data uploaded to {} successfully".format(s3_path))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@click.command(name='train')
|
|
49
|
+
@click.option(
|
|
50
|
+
u"-i", u"--input-s3-dir",
|
|
51
|
+
required=True,
|
|
52
|
+
help="s3 location to input data",
|
|
53
|
+
type=click.Path()
|
|
54
|
+
)
|
|
55
|
+
@click.option(
|
|
56
|
+
u"-o", u"--output-s3-dir",
|
|
57
|
+
required=True,
|
|
58
|
+
help="s3 location to save output (models, etc)",
|
|
59
|
+
type=click.Path()
|
|
60
|
+
)
|
|
61
|
+
@click.option(u"-e", u"--ec2-type", required=True, help="ec2 instance type")
|
|
62
|
+
@click.option(
|
|
63
|
+
u"-r",
|
|
64
|
+
u"--iam-role-arn",
|
|
65
|
+
required=True,
|
|
66
|
+
help="The AWS role to use for the train command"
|
|
67
|
+
)
|
|
68
|
+
@click.option(
|
|
69
|
+
u"-n",
|
|
70
|
+
u"--base-job-name",
|
|
71
|
+
required=False,
|
|
72
|
+
help="Optional prefix for the SageMaker training job."
|
|
73
|
+
"If not specified, the estimator generates a default job name, based on the training image name and current timestamp."
|
|
74
|
+
)
|
|
75
|
+
@click.pass_obj
|
|
76
|
+
def train(
|
|
77
|
+
obj,
|
|
78
|
+
input_s3_dir,
|
|
79
|
+
output_s3_dir,
|
|
80
|
+
ec2_type,
|
|
81
|
+
iam_role_arn,
|
|
82
|
+
base_job_name
|
|
83
|
+
):
|
|
84
|
+
"""
|
|
85
|
+
Command to train ML model(s) on SageMaker
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
print("Started training on SageMaker...\n")
|
|
89
|
+
config = _config()
|
|
90
|
+
sage_maker_client = sagemaker.SageMakerClient(config.aws_profile, config.aws_region, iam_role_arn)
|
|
91
|
+
|
|
92
|
+
image_name = config.image_name+':'+obj['docker_tag']
|
|
93
|
+
|
|
94
|
+
s3_model_location = sage_maker_client.train(
|
|
95
|
+
image_name=image_name,
|
|
96
|
+
input_s3_data_location=input_s3_dir,
|
|
97
|
+
train_instance_type=ec2_type,
|
|
98
|
+
output_path=output_s3_dir,
|
|
99
|
+
base_job_name=base_job_name
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
print("Training on SageMaker succeeded")
|
|
103
|
+
print("Model S3 location: {}".format(s3_model_location))
|
|
104
|
+
return s3_model_location # To pipe into other commands
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@click.command(name='deploy_serverless')
|
|
108
|
+
@click.option(
|
|
109
|
+
u"-m", u"--s3-model-location",
|
|
110
|
+
required=True,
|
|
111
|
+
help="s3 location to model tar.gz",
|
|
112
|
+
type=click.Path()
|
|
113
|
+
)
|
|
114
|
+
@click.option(u"-s",
|
|
115
|
+
u"--memory-size-in-mb",
|
|
116
|
+
required=True,
|
|
117
|
+
type=click.INT,
|
|
118
|
+
help="memory size in MB for serverless endpoint")
|
|
119
|
+
@click.option(
|
|
120
|
+
u"-r",
|
|
121
|
+
u"--iam-role-arn",
|
|
122
|
+
required=False,
|
|
123
|
+
help="The AWS role to use for the deploy command"
|
|
124
|
+
)
|
|
125
|
+
@click.option(
|
|
126
|
+
u"-n",
|
|
127
|
+
u"--endpoint-name",
|
|
128
|
+
required=True,
|
|
129
|
+
default=None,
|
|
130
|
+
help="Name for the SageMaker endpoint"
|
|
131
|
+
)
|
|
132
|
+
@click.pass_obj
|
|
133
|
+
def deploy_serverless(
|
|
134
|
+
obj,
|
|
135
|
+
s3_model_location,
|
|
136
|
+
memory_size_in_mb,
|
|
137
|
+
iam_role_arn,
|
|
138
|
+
endpoint_name
|
|
139
|
+
):
|
|
140
|
+
"""
|
|
141
|
+
Command to deploy ML model(s) on SageMaker
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
print("Started deployment on SageMaker ...\n")
|
|
145
|
+
config = _config()
|
|
146
|
+
image_name = config.image_name+':'+obj['docker_tag']
|
|
147
|
+
|
|
148
|
+
sage_maker_client = sagemaker.SageMakerClient(config.aws_profile, config.aws_region, iam_role_arn)
|
|
149
|
+
endpoint_name = sage_maker_client.deploy_serverless(
|
|
150
|
+
image_name=image_name,
|
|
151
|
+
s3_model_location=s3_model_location,
|
|
152
|
+
memory_size_in_mb=memory_size_in_mb,
|
|
153
|
+
endpoint_name=endpoint_name
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
print("Endpoint name: {}".format(endpoint_name))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@click.command(name='batch-transform')
|
|
160
|
+
@click.option(
|
|
161
|
+
u"-m", u"--s3-model-location",
|
|
162
|
+
required=True,
|
|
163
|
+
help="s3 location to model tar.gz",
|
|
164
|
+
type=click.Path()
|
|
165
|
+
)
|
|
166
|
+
@click.option(
|
|
167
|
+
u"-i", u"--s3-input-location",
|
|
168
|
+
required=True,
|
|
169
|
+
help="s3 input data location",
|
|
170
|
+
type=click.Path()
|
|
171
|
+
)
|
|
172
|
+
@click.option(
|
|
173
|
+
u"-o", u"--s3-output-location",
|
|
174
|
+
required=True,
|
|
175
|
+
help="s3 location to save predictions",
|
|
176
|
+
type=click.Path()
|
|
177
|
+
)
|
|
178
|
+
@click.option(u"-n", u"--num-instances", required=True, type=int, help="Number of ec2 instances")
|
|
179
|
+
@click.option(u"-e", u"--ec2-type", required=True, help="ec2 instance type")
|
|
180
|
+
@click.option(
|
|
181
|
+
u"-r",
|
|
182
|
+
u"--iam-role-arn",
|
|
183
|
+
required=False,
|
|
184
|
+
help="The AWS role to use for batch transform command"
|
|
185
|
+
)
|
|
186
|
+
@click.option(
|
|
187
|
+
u"-w",
|
|
188
|
+
u"--wait",
|
|
189
|
+
default=False,
|
|
190
|
+
is_flag=True,
|
|
191
|
+
help="Wait until Batch Transform is finished. "
|
|
192
|
+
"Default: don't wait"
|
|
193
|
+
)
|
|
194
|
+
@click.option(
|
|
195
|
+
u"--job-name",
|
|
196
|
+
required=True,
|
|
197
|
+
default=None,
|
|
198
|
+
help="Name for the SageMaker batch transform job."
|
|
199
|
+
)
|
|
200
|
+
@click.pass_obj
|
|
201
|
+
def batch_transform(
|
|
202
|
+
obj,
|
|
203
|
+
s3_model_location,
|
|
204
|
+
s3_input_location,
|
|
205
|
+
s3_output_location,
|
|
206
|
+
num_instances,
|
|
207
|
+
ec2_type,
|
|
208
|
+
iam_role_arn,
|
|
209
|
+
wait,
|
|
210
|
+
job_name
|
|
211
|
+
):
|
|
212
|
+
"""
|
|
213
|
+
Command to execute a batch transform job given a trained ML model on SageMaker
|
|
214
|
+
"""
|
|
215
|
+
print("Started configuration of batch transform on SageMaker ...\n")
|
|
216
|
+
|
|
217
|
+
config = _config()
|
|
218
|
+
image_name = config.image_name+':'+obj['docker_tag']
|
|
219
|
+
|
|
220
|
+
sage_maker_client = sagemaker.SageMakerClient(config.aws_profile, config.aws_region, iam_role_arn)
|
|
221
|
+
status = sage_maker_client.batch_transform(
|
|
222
|
+
image_name=image_name,
|
|
223
|
+
s3_model_location=s3_model_location,
|
|
224
|
+
s3_input_location=s3_input_location,
|
|
225
|
+
s3_output_location=s3_output_location,
|
|
226
|
+
transform_instance_count=num_instances,
|
|
227
|
+
transform_instance_type=ec2_type,
|
|
228
|
+
wait=wait,
|
|
229
|
+
job_name=job_name
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if wait:
|
|
233
|
+
print("Batch transform on SageMaker finished with status: {}".format(status))
|
|
234
|
+
if status == "Failed":
|
|
235
|
+
sys.exit(1)
|
|
236
|
+
else:
|
|
237
|
+
print("Started batch transform on SageMaker successfully")
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@click.command(name='delete_endpoint')
|
|
241
|
+
@click.option(
|
|
242
|
+
u"-n",
|
|
243
|
+
u"--endpoint-name",
|
|
244
|
+
required=True,
|
|
245
|
+
default=None,
|
|
246
|
+
help="Name of the SageMaker endpoint"
|
|
247
|
+
)
|
|
248
|
+
@click.option(
|
|
249
|
+
u"-r",
|
|
250
|
+
u"--iam-role-arn",
|
|
251
|
+
required=False,
|
|
252
|
+
help="The AWS role to use for delete command"
|
|
253
|
+
)
|
|
254
|
+
@click.pass_obj
|
|
255
|
+
def delete_endpoint(obj, endpoint_name, iam_role_arn):
|
|
256
|
+
config = _config()
|
|
257
|
+
sage_maker_client = sagemaker.SageMakerClient(config.aws_profile, config.aws_region, iam_role_arn)
|
|
258
|
+
sage_maker_client.shutdown_endpoint(endpoint_name)
|
|
259
|
+
print(f"Endpoint {endpoint_name} has been deleted")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
cloud.add_command(upload_data)
|
|
263
|
+
cloud.add_command(train)
|
|
264
|
+
cloud.add_command(deploy_serverless)
|
|
265
|
+
cloud.add_command(batch_transform)
|
|
266
|
+
cloud.add_command(delete_endpoint)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import boto3
|
|
5
|
+
import click
|
|
6
|
+
from click import BadParameter
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from easy_sm.config.config import ConfigManager
|
|
10
|
+
from distutils.dir_util import copy_tree
|
|
11
|
+
|
|
12
|
+
_FILE_DIR_PATH = os.path.dirname(os.path.realpath(__file__))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _template_creation(app_name, aws_profile, aws_region, python_version, output_dir, requirements_dir):
|
|
16
|
+
easy_sm_module_name = 'easy_sm_base'
|
|
17
|
+
|
|
18
|
+
easy_sm_exists = os.path.exists(os.path.join(output_dir, easy_sm_module_name))
|
|
19
|
+
if easy_sm_exists:
|
|
20
|
+
raise ValueError(
|
|
21
|
+
"There is a easy_sm directory/module already. "
|
|
22
|
+
"Please, rename it in order to use easy_sm."
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
Path(output_dir).mkdir(exist_ok=True)
|
|
26
|
+
Path(os.path.join(output_dir, '__init__.py')).touch()
|
|
27
|
+
|
|
28
|
+
# Set 'easy_sm module' directory up
|
|
29
|
+
copy_tree(os.path.join(_FILE_DIR_PATH, '../template'), output_dir)
|
|
30
|
+
|
|
31
|
+
# Set configuration file up
|
|
32
|
+
config_manager = ConfigManager(os.path.join('.easy_sm.json'))
|
|
33
|
+
config = config_manager.get_config()
|
|
34
|
+
|
|
35
|
+
config.image_name = app_name
|
|
36
|
+
config.aws_region = aws_region
|
|
37
|
+
config.aws_profile = aws_profile
|
|
38
|
+
config.easy_sm_module_dir = output_dir
|
|
39
|
+
config.python_version = python_version
|
|
40
|
+
config.requirements_dir = requirements_dir
|
|
41
|
+
config_manager.set_config(config)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _get_local_aws_profiles():
|
|
45
|
+
return boto3.Session().available_profiles
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def ask_for_app_name():
|
|
49
|
+
return click.prompt(
|
|
50
|
+
text="Type in a name for your SageMaker app (Only alphanumeric characters and - are allowed))",
|
|
51
|
+
type=str
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def ask_if_existing_project_exists():
|
|
56
|
+
return click.confirm(text="Are you starting a new project?")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def ask_for_root_dir():
|
|
60
|
+
return click.prompt(text="Type in the directory where your code lives. Example: src", type=str).strip('/')
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def ask_for_python_version():
|
|
64
|
+
print("Select Python interpreter:")
|
|
65
|
+
print('{}'.format('\n'.join(['1 - Python39', '2 - Python310', '3 - Python311'])))
|
|
66
|
+
|
|
67
|
+
def _validate_python_option(input_value):
|
|
68
|
+
if int(input_value) not in {1, 2, 3}:
|
|
69
|
+
raise BadParameter(
|
|
70
|
+
message="invalid choice: {}. (choose from 1, 2, 3)".format(str(input_value))
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return int(input_value)
|
|
74
|
+
|
|
75
|
+
chosen_python_index = click.prompt(
|
|
76
|
+
text="Choose from 1, 2, 3",
|
|
77
|
+
default=1,
|
|
78
|
+
value_proc=lambda x: _validate_python_option(x)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
_index_to_version = {
|
|
82
|
+
1: '3.9',
|
|
83
|
+
2: '3.10',
|
|
84
|
+
3: '3.11'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return _index_to_version[chosen_python_index]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def ask_for_aws_details():
|
|
91
|
+
available_profiles = _get_local_aws_profiles()
|
|
92
|
+
|
|
93
|
+
if len(available_profiles) == 0:
|
|
94
|
+
print("aws cli is not configured!")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
valid_positions = list(range(1, len(available_profiles) + 1))
|
|
98
|
+
print("Select AWS profile:")
|
|
99
|
+
print('{}'.format(
|
|
100
|
+
'\n'.join(
|
|
101
|
+
[
|
|
102
|
+
'{} - {}'.format(pos, profile)
|
|
103
|
+
for pos, profile in zip(valid_positions, available_profiles)
|
|
104
|
+
]
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def _validate_profile_position(input_pos):
|
|
110
|
+
if int(input_pos) not in valid_positions:
|
|
111
|
+
raise BadParameter(
|
|
112
|
+
message="invalid choice: {}. (choose from {})".format(
|
|
113
|
+
input_pos,
|
|
114
|
+
', '.join([str(pos) for pos in valid_positions])
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
return int(input_pos)
|
|
118
|
+
|
|
119
|
+
chosen_profile_index = click.prompt(
|
|
120
|
+
text="Choose from {}".format(', '.join([str(pos) for pos in valid_positions])),
|
|
121
|
+
default=1,
|
|
122
|
+
value_proc=lambda x: _validate_profile_position(x)
|
|
123
|
+
)
|
|
124
|
+
chosen_profile_index -= 1
|
|
125
|
+
|
|
126
|
+
chosen_profile = available_profiles[chosen_profile_index]
|
|
127
|
+
|
|
128
|
+
chosen_region = click.prompt(
|
|
129
|
+
text="Type in your preferred AWS region name",
|
|
130
|
+
default='eu-west-1',
|
|
131
|
+
type=str
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return chosen_profile, chosen_region
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def ask_for_requirements_dir():
|
|
138
|
+
return click.prompt(text="Type in the path to requirements.txt. Example: requirements.txt", type=str).strip('/')
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@click.command()
|
|
142
|
+
def init():
|
|
143
|
+
"""
|
|
144
|
+
Command to initialize SageMaker template
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
easy_sm_app_name = ask_for_app_name()
|
|
148
|
+
|
|
149
|
+
is_new_project = ask_if_existing_project_exists()
|
|
150
|
+
|
|
151
|
+
root_dir = None
|
|
152
|
+
if not is_new_project:
|
|
153
|
+
root_dir = ask_for_root_dir()
|
|
154
|
+
|
|
155
|
+
python_version = ask_for_python_version()
|
|
156
|
+
|
|
157
|
+
aws_profile, aws_region = ask_for_aws_details()
|
|
158
|
+
|
|
159
|
+
requirements_dir = ask_for_requirements_dir()
|
|
160
|
+
_template_creation(
|
|
161
|
+
app_name=easy_sm_app_name,
|
|
162
|
+
aws_profile=aws_profile,
|
|
163
|
+
aws_region=aws_region,
|
|
164
|
+
python_version=python_version,
|
|
165
|
+
output_dir=root_dir if root_dir else 'src',
|
|
166
|
+
requirements_dir=requirements_dir
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
print("\neasy_sm module is created! ヽ(´▽`)/")
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import click
|
|
4
|
+
import subprocess
|
|
5
|
+
|
|
6
|
+
from easy_sm.config.config import ConfigManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
def local():
|
|
11
|
+
"""
|
|
12
|
+
Commands for local operations: train and deploy
|
|
13
|
+
"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.command()
|
|
18
|
+
@click.pass_obj
|
|
19
|
+
def train(obj):
|
|
20
|
+
"""
|
|
21
|
+
Command to train ML model(s) locally
|
|
22
|
+
"""
|
|
23
|
+
print("Started local training...\n")
|
|
24
|
+
config = ConfigManager(os.path.join('.easy_sm.json')).get_config()
|
|
25
|
+
dir = config.easy_sm_module_dir
|
|
26
|
+
docker_tag = obj['docker_tag']
|
|
27
|
+
image_name = config.image_name
|
|
28
|
+
easy_sm_module_path = os.path.join(dir, 'easy_sm_base')
|
|
29
|
+
local_train_script_path = os.path.join(easy_sm_module_path, 'local_test', 'train_local.sh')
|
|
30
|
+
test_path = os.path.join(easy_sm_module_path, 'local_test', 'test_dir')
|
|
31
|
+
|
|
32
|
+
if not os.path.isdir(test_path):
|
|
33
|
+
raise ValueError("This is not a easy_sm directory: {}".format(dir))
|
|
34
|
+
|
|
35
|
+
output = subprocess.check_output(
|
|
36
|
+
[
|
|
37
|
+
"{}".format(local_train_script_path),
|
|
38
|
+
"{}".format(os.path.abspath(test_path)),
|
|
39
|
+
docker_tag,
|
|
40
|
+
image_name
|
|
41
|
+
]
|
|
42
|
+
)
|
|
43
|
+
print(output)
|
|
44
|
+
|
|
45
|
+
print("Local training completed successfully!")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@click.command()
|
|
49
|
+
@click.pass_obj
|
|
50
|
+
def deploy(obj):
|
|
51
|
+
"""
|
|
52
|
+
Command to deploy ML model(s) locally
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
config = ConfigManager(os.path.join('.easy_sm.json')).get_config()
|
|
56
|
+
dir = config.easy_sm_module_dir
|
|
57
|
+
docker_tag = obj['docker_tag']
|
|
58
|
+
image_name = config.image_name
|
|
59
|
+
|
|
60
|
+
easy_sm_module_path = os.path.join(dir, 'easy_sm_base')
|
|
61
|
+
local_deploy_script_path = os.path.join(easy_sm_module_path, 'local_test', 'deploy_local.sh')
|
|
62
|
+
test_path = os.path.join(easy_sm_module_path, 'local_test', 'test_dir')
|
|
63
|
+
|
|
64
|
+
if not os.path.isdir(test_path):
|
|
65
|
+
raise ValueError("This is not a easy_sm directory: {}".format(dir))
|
|
66
|
+
|
|
67
|
+
print("Started local deployment at localhost:8080 ...\n")
|
|
68
|
+
output = subprocess.check_output(
|
|
69
|
+
[
|
|
70
|
+
"{}".format(local_deploy_script_path),
|
|
71
|
+
"{}".format(os.path.abspath(test_path)),
|
|
72
|
+
docker_tag,
|
|
73
|
+
image_name
|
|
74
|
+
]
|
|
75
|
+
)
|
|
76
|
+
print(output)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
local.add_command(train)
|
|
80
|
+
local.add_command(deploy)
|
easy_sm/commands/push.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import click
|
|
3
|
+
import subprocess
|
|
4
|
+
import os
|
|
5
|
+
from easy_sm.config.config import ConfigManager
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _push(dir, docker_tag, aws_region, iam_role_arn, aws_profile, external_id, image_name):
|
|
9
|
+
"""
|
|
10
|
+
Push Docker image to AWS ECR
|
|
11
|
+
|
|
12
|
+
:param dir: [str], source root directory
|
|
13
|
+
:param docker_tag: [str], the Docker tag for the image
|
|
14
|
+
:param aws_region: [str], the AWS region to push the image to
|
|
15
|
+
:param iam_role_arn: [str], the AWS role used to push the image to ECR
|
|
16
|
+
:param aws_profile: [str], the AWS profile used to push the image to ECR
|
|
17
|
+
:param external_id: [str], Optional external id used when using an IAM role
|
|
18
|
+
:param image_name: [str], The name of the Docker image
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
easy_sm_module_path = os.path.relpath(os.path.join(dir, 'easy_sm_base/'))
|
|
22
|
+
push_script_path = os.path.join(easy_sm_module_path, 'push.sh')
|
|
23
|
+
|
|
24
|
+
if not os.path.isfile(push_script_path):
|
|
25
|
+
raise ValueError("This is not a easy_sm directory: {}".format(dir))
|
|
26
|
+
|
|
27
|
+
output = subprocess.check_output([
|
|
28
|
+
"{}".format(push_script_path),
|
|
29
|
+
docker_tag,
|
|
30
|
+
aws_region,
|
|
31
|
+
iam_role_arn,
|
|
32
|
+
aws_profile,
|
|
33
|
+
external_id,
|
|
34
|
+
image_name])
|
|
35
|
+
print(output)
|
|
36
|
+
|
|
37
|
+
@click.command()
|
|
38
|
+
@click.option(u"-r", u"--aws-region", required=False, help="The AWS region to push the image to")
|
|
39
|
+
@click.option(u"-i", u"--iam-role-arn", required=False, help="The AWS role to use for the push command")
|
|
40
|
+
@click.option(u"-p", u"--aws-profile", required=False, help="The AWS profile to use for the push command")
|
|
41
|
+
@click.option(u"-e", u"--external-id", required=False, help="Optional external id used when using an IAM role")
|
|
42
|
+
@click.pass_obj
|
|
43
|
+
def push(obj, aws_region, iam_role_arn, aws_profile, external_id):
|
|
44
|
+
"""
|
|
45
|
+
Command to push Docker image to AWS ECR
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
if iam_role_arn is not None and aws_profile is not None:
|
|
49
|
+
print('Only one of iam-role-arn and aws-profile can be used.')
|
|
50
|
+
sys.exit(2)
|
|
51
|
+
|
|
52
|
+
if iam_role_arn is not None:
|
|
53
|
+
aws_profile = ''
|
|
54
|
+
|
|
55
|
+
config_file_path = os.path.join('.easy_sm.json')
|
|
56
|
+
if not os.path.isfile(config_file_path):
|
|
57
|
+
raise ValueError("This is not a easy_sm directory: {}".format(os.getcwd()))
|
|
58
|
+
|
|
59
|
+
config = ConfigManager(config_file_path).get_config()
|
|
60
|
+
image_name = config.image_name
|
|
61
|
+
aws_region = config.aws_region if aws_region is None else aws_region
|
|
62
|
+
aws_profile = config.aws_profile if (aws_profile is None and iam_role_arn is None) else aws_profile
|
|
63
|
+
external_id = "" if external_id is None else external_id
|
|
64
|
+
iam_role_arn = "" if iam_role_arn is None else iam_role_arn
|
|
65
|
+
|
|
66
|
+
print("Started pushing Docker image to AWS ECR. It will take some time. Please, be patient...\n")
|
|
67
|
+
|
|
68
|
+
_push(
|
|
69
|
+
dir=config.easy_sm_module_dir,
|
|
70
|
+
docker_tag=obj['docker_tag'],
|
|
71
|
+
aws_region=aws_region,
|
|
72
|
+
iam_role_arn=iam_role_arn,
|
|
73
|
+
aws_profile=aws_profile,
|
|
74
|
+
external_id=external_id,
|
|
75
|
+
image_name=image_name)
|
|
76
|
+
|
|
77
|
+
print("Docker image pushed to ECR successfully!")
|
|
78
|
+
|
|
79
|
+
|
|
File without changes
|