ob-metaflow-extensions 1.1.79__py2.py3-none-any.whl → 1.1.80__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 ob-metaflow-extensions might be problematic. Click here for more details.
- metaflow_extensions/outerbounds/config/__init__.py +28 -0
- metaflow_extensions/outerbounds/plugins/__init__.py +17 -3
- metaflow_extensions/outerbounds/plugins/fast_bakery/__init__.py +0 -0
- metaflow_extensions/outerbounds/plugins/fast_bakery/docker_environment.py +268 -0
- metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery.py +160 -0
- metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery_cli.py +54 -0
- metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery_decorator.py +50 -0
- metaflow_extensions/outerbounds/plugins/snowpark/__init__.py +0 -0
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark.py +299 -0
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_cli.py +271 -0
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_client.py +123 -0
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_decorator.py +264 -0
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_exceptions.py +13 -0
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_job.py +235 -0
- metaflow_extensions/outerbounds/plugins/snowpark/snowpark_service_spec.py +259 -0
- {ob_metaflow_extensions-1.1.79.dist-info → ob_metaflow_extensions-1.1.80.dist-info}/METADATA +2 -2
- {ob_metaflow_extensions-1.1.79.dist-info → ob_metaflow_extensions-1.1.80.dist-info}/RECORD +19 -6
- {ob_metaflow_extensions-1.1.79.dist-info → ob_metaflow_extensions-1.1.80.dist-info}/WHEEL +0 -0
- {ob_metaflow_extensions-1.1.79.dist-info → ob_metaflow_extensions-1.1.80.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
from metaflow import R, current
|
|
6
|
+
from metaflow.metadata import MetaDatum
|
|
7
|
+
from metaflow.metadata.util import sync_local_metadata_to_datastore
|
|
8
|
+
from metaflow.sidecar import Sidecar
|
|
9
|
+
from metaflow.decorators import StepDecorator
|
|
10
|
+
from metaflow.exception import MetaflowException
|
|
11
|
+
from metaflow.metaflow_config import (
|
|
12
|
+
DEFAULT_CONTAINER_IMAGE,
|
|
13
|
+
DEFAULT_CONTAINER_REGISTRY,
|
|
14
|
+
SNOWPARK_ACCOUNT,
|
|
15
|
+
SNOWPARK_USER,
|
|
16
|
+
SNOWPARK_PASSWORD,
|
|
17
|
+
SNOWPARK_ROLE,
|
|
18
|
+
SNOWPARK_DATABASE,
|
|
19
|
+
SNOWPARK_WAREHOUSE,
|
|
20
|
+
SNOWPARK_SCHEMA,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from metaflow.metaflow_config import (
|
|
24
|
+
DATASTORE_LOCAL_DIR,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from .snowpark_exceptions import SnowflakeException
|
|
28
|
+
from metaflow.plugins.aws.aws_utils import get_docker_registry
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Snowflake(object):
|
|
32
|
+
def __init__(self, connection_params):
|
|
33
|
+
self.connection_params = connection_params
|
|
34
|
+
|
|
35
|
+
def session(self):
|
|
36
|
+
# if using the pypi/conda decorator with @snowpark of any step,
|
|
37
|
+
# make sure to pass {'snowflake': '0.11.0'} as well to that step
|
|
38
|
+
try:
|
|
39
|
+
from snowflake.snowpark import Session
|
|
40
|
+
|
|
41
|
+
session = Session.builder.configs(self.connection_params).create()
|
|
42
|
+
return session
|
|
43
|
+
except (NameError, ImportError, ModuleNotFoundError):
|
|
44
|
+
raise SnowflakeException(
|
|
45
|
+
"Could not import module 'snowflake'.\n\nInstall Snowflake "
|
|
46
|
+
"Python package (https://pypi.org/project/snowflake/) first.\n"
|
|
47
|
+
"You can install the module by using the @pypi decorator - "
|
|
48
|
+
"Eg: @pypi(packages={'snowflake': '0.11.0'})\n"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SnowparkDecorator(StepDecorator):
|
|
53
|
+
name = "snowpark"
|
|
54
|
+
|
|
55
|
+
defaults = {
|
|
56
|
+
"account": None,
|
|
57
|
+
"user": None,
|
|
58
|
+
"password": None,
|
|
59
|
+
"role": None,
|
|
60
|
+
"database": None,
|
|
61
|
+
"warehouse": None,
|
|
62
|
+
"schema": None,
|
|
63
|
+
"image": None,
|
|
64
|
+
"stage": None,
|
|
65
|
+
"compute_pool": None,
|
|
66
|
+
"volume_mounts": None,
|
|
67
|
+
"external_integration": None,
|
|
68
|
+
"cpu": None,
|
|
69
|
+
"gpu": None,
|
|
70
|
+
"memory": None,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
package_url = None
|
|
74
|
+
package_sha = None
|
|
75
|
+
run_time_limit = None
|
|
76
|
+
|
|
77
|
+
def __init__(self, attributes=None, statically_defined=False):
|
|
78
|
+
super(SnowparkDecorator, self).__init__(attributes, statically_defined)
|
|
79
|
+
|
|
80
|
+
if not self.attributes["account"]:
|
|
81
|
+
self.attributes["account"] = SNOWPARK_ACCOUNT
|
|
82
|
+
if not self.attributes["user"]:
|
|
83
|
+
self.attributes["user"] = SNOWPARK_USER
|
|
84
|
+
if not self.attributes["password"]:
|
|
85
|
+
self.attributes["password"] = SNOWPARK_PASSWORD
|
|
86
|
+
if not self.attributes["role"]:
|
|
87
|
+
self.attributes["role"] = SNOWPARK_ROLE
|
|
88
|
+
if not self.attributes["database"]:
|
|
89
|
+
self.attributes["database"] = SNOWPARK_DATABASE
|
|
90
|
+
if not self.attributes["warehouse"]:
|
|
91
|
+
self.attributes["warehouse"] = SNOWPARK_WAREHOUSE
|
|
92
|
+
if not self.attributes["schema"]:
|
|
93
|
+
self.attributes["schema"] = SNOWPARK_SCHEMA
|
|
94
|
+
|
|
95
|
+
# If no docker image is explicitly specified, impute a default image.
|
|
96
|
+
if not self.attributes["image"]:
|
|
97
|
+
# If metaflow-config specifies a docker image, just use that.
|
|
98
|
+
if DEFAULT_CONTAINER_IMAGE:
|
|
99
|
+
self.attributes["image"] = DEFAULT_CONTAINER_IMAGE
|
|
100
|
+
# If metaflow-config doesn't specify a docker image, assign a
|
|
101
|
+
# default docker image.
|
|
102
|
+
else:
|
|
103
|
+
# Metaflow-R has its own default docker image (rocker family)
|
|
104
|
+
if R.use_r():
|
|
105
|
+
self.attributes["image"] = R.container_image()
|
|
106
|
+
# Default to vanilla Python image corresponding to major.minor
|
|
107
|
+
# version of the Python interpreter launching the flow.
|
|
108
|
+
self.attributes["image"] = "python:%s.%s" % (
|
|
109
|
+
platform.python_version_tuple()[0],
|
|
110
|
+
platform.python_version_tuple()[1],
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Assign docker registry URL for the image.
|
|
114
|
+
if not get_docker_registry(self.attributes["image"]):
|
|
115
|
+
if DEFAULT_CONTAINER_REGISTRY:
|
|
116
|
+
self.attributes["image"] = "%s/%s" % (
|
|
117
|
+
DEFAULT_CONTAINER_REGISTRY.rstrip("/"),
|
|
118
|
+
self.attributes["image"],
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Refer https://github.com/Netflix/metaflow/blob/master/docs/lifecycle.png
|
|
122
|
+
# to understand where these functions are invoked in the lifecycle of a
|
|
123
|
+
# Metaflow flow.
|
|
124
|
+
def step_init(self, flow, graph, step, decos, environment, flow_datastore, logger):
|
|
125
|
+
# Set internal state.
|
|
126
|
+
self.logger = logger
|
|
127
|
+
self.environment = environment
|
|
128
|
+
self.step = step
|
|
129
|
+
self.flow_datastore = flow_datastore
|
|
130
|
+
|
|
131
|
+
if any([deco.name == "parallel" for deco in decos]):
|
|
132
|
+
raise MetaflowException(
|
|
133
|
+
"Step *{step}* contains a @parallel decorator "
|
|
134
|
+
"with the @snowpark decorator. @parallel is not supported with @snowpark.".format(
|
|
135
|
+
step=step
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def package_init(self, flow, step_name, environment):
|
|
140
|
+
try:
|
|
141
|
+
# Snowflake is a soft dependency.
|
|
142
|
+
from snowflake.snowpark import Session
|
|
143
|
+
except (NameError, ImportError, ModuleNotFoundError):
|
|
144
|
+
raise SnowflakeException(
|
|
145
|
+
"Could not import module 'snowflake'.\n\nInstall Snowflake "
|
|
146
|
+
"Python package (https://pypi.org/project/snowflake/) first.\n"
|
|
147
|
+
"You can install the module by executing - "
|
|
148
|
+
"%s -m pip install snowflake\n"
|
|
149
|
+
"or equivalent through your favorite Python package manager."
|
|
150
|
+
% sys.executable
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def runtime_init(self, flow, graph, package, run_id):
|
|
154
|
+
# Set some more internal state.
|
|
155
|
+
self.flow = flow
|
|
156
|
+
self.graph = graph
|
|
157
|
+
self.package = package
|
|
158
|
+
self.run_id = run_id
|
|
159
|
+
|
|
160
|
+
def runtime_task_created(
|
|
161
|
+
self, task_datastore, task_id, split_index, input_paths, is_cloned, ubf_context
|
|
162
|
+
):
|
|
163
|
+
if not is_cloned:
|
|
164
|
+
self._save_package_once(self.flow_datastore, self.package)
|
|
165
|
+
|
|
166
|
+
def runtime_step_cli(
|
|
167
|
+
self, cli_args, retry_count, max_user_code_retries, ubf_context
|
|
168
|
+
):
|
|
169
|
+
if retry_count <= max_user_code_retries:
|
|
170
|
+
cli_args.commands = ["snowpark", "step"]
|
|
171
|
+
cli_args.command_args.append(self.package_sha)
|
|
172
|
+
cli_args.command_args.append(self.package_url)
|
|
173
|
+
cli_args.command_options.update(self.attributes)
|
|
174
|
+
cli_args.command_options["run-time-limit"] = self.run_time_limit
|
|
175
|
+
if not R.use_r():
|
|
176
|
+
cli_args.entrypoint[0] = sys.executable
|
|
177
|
+
|
|
178
|
+
def task_pre_step(
|
|
179
|
+
self,
|
|
180
|
+
step_name,
|
|
181
|
+
task_datastore,
|
|
182
|
+
metadata,
|
|
183
|
+
run_id,
|
|
184
|
+
task_id,
|
|
185
|
+
flow,
|
|
186
|
+
graph,
|
|
187
|
+
retry_count,
|
|
188
|
+
max_retries,
|
|
189
|
+
ubf_context,
|
|
190
|
+
inputs,
|
|
191
|
+
):
|
|
192
|
+
self.metadata = metadata
|
|
193
|
+
self.task_datastore = task_datastore
|
|
194
|
+
|
|
195
|
+
# this path will exist within snowpark container services
|
|
196
|
+
login_token = open("/snowflake/session/token", "r").read()
|
|
197
|
+
connection_params = {
|
|
198
|
+
"account": os.environ.get("SNOWFLAKE_ACCOUNT"),
|
|
199
|
+
"host": os.environ.get("SNOWFLAKE_HOST"),
|
|
200
|
+
"authenticator": "oauth",
|
|
201
|
+
"token": login_token,
|
|
202
|
+
"database": os.environ.get("SNOWFLAKE_DATABASE"),
|
|
203
|
+
"schema": os.environ.get("SNOWFLAKE_SCHEMA"),
|
|
204
|
+
"autocommit": True,
|
|
205
|
+
"client_session_keep_alive": True,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# SNOWFLAKE_WAREHOUSE is injected explicitly by us
|
|
209
|
+
# but is not really required. So if it exists, we use it in
|
|
210
|
+
# connection parameters
|
|
211
|
+
if os.environ.get("SNOWFLAKE_WAREHOUSE"):
|
|
212
|
+
connection_params["warehouse"] = os.environ.get("SNOWFLAKE_WAREHOUSE")
|
|
213
|
+
|
|
214
|
+
snowflake_obj = Snowflake(connection_params)
|
|
215
|
+
current._update_env({"snowflake": snowflake_obj})
|
|
216
|
+
|
|
217
|
+
meta = {}
|
|
218
|
+
if "METAFLOW_SNOWPARK_WORKLOAD" in os.environ:
|
|
219
|
+
meta["snowflake-account"] = os.environ.get("SNOWFLAKE_ACCOUNT")
|
|
220
|
+
meta["snowflake-database"] = os.environ.get("SNOWFLAKE_DATABASE")
|
|
221
|
+
meta["snowflake-schema"] = os.environ.get("SNOWFLAKE_SCHEMA")
|
|
222
|
+
meta["snowflake-host"] = os.environ.get("SNOWFLAKE_HOST")
|
|
223
|
+
meta["snowflake-service-name"] = os.environ.get("SNOWFLAKE_SERVICE_NAME")
|
|
224
|
+
|
|
225
|
+
self._save_logs_sidecar = Sidecar("save_logs_periodically")
|
|
226
|
+
self._save_logs_sidecar.start()
|
|
227
|
+
|
|
228
|
+
if len(meta) > 0:
|
|
229
|
+
entries = [
|
|
230
|
+
MetaDatum(
|
|
231
|
+
field=k,
|
|
232
|
+
value=v,
|
|
233
|
+
type=k,
|
|
234
|
+
tags=["attempt_id:{0}".format(retry_count)],
|
|
235
|
+
)
|
|
236
|
+
for k, v in meta.items()
|
|
237
|
+
if v is not None
|
|
238
|
+
]
|
|
239
|
+
# Register book-keeping metadata for debugging.
|
|
240
|
+
metadata.register_metadata(run_id, step_name, task_id, entries)
|
|
241
|
+
|
|
242
|
+
def task_finished(
|
|
243
|
+
self, step_name, flow, graph, is_task_ok, retry_count, max_retries
|
|
244
|
+
):
|
|
245
|
+
if "METAFLOW_SNOWPARK_WORKLOAD" in os.environ:
|
|
246
|
+
if hasattr(self, "metadata") and self.metadata.TYPE == "local":
|
|
247
|
+
# Note that the datastore is *always* Amazon S3 (see
|
|
248
|
+
# runtime_task_created function).
|
|
249
|
+
sync_local_metadata_to_datastore(
|
|
250
|
+
DATASTORE_LOCAL_DIR, self.task_datastore
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
self._save_logs_sidecar.terminate()
|
|
255
|
+
except:
|
|
256
|
+
# Best effort kill
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
@classmethod
|
|
260
|
+
def _save_package_once(cls, flow_datastore, package):
|
|
261
|
+
if cls.package_url is None:
|
|
262
|
+
cls.package_url, cls.package_sha = flow_datastore.save_data(
|
|
263
|
+
[package.blob], len_hint=1
|
|
264
|
+
)[0]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from metaflow.exception import MetaflowException
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SnowflakeException(MetaflowException):
|
|
5
|
+
headline = "Snowflake error"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SnowparkException(MetaflowException):
|
|
9
|
+
headline = "Snowpark error"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SnowparkKilledException(MetaflowException):
|
|
13
|
+
headline = "Snowpark job killed"
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from .snowpark_client import SnowparkClient
|
|
4
|
+
from .snowpark_service_spec import (
|
|
5
|
+
Container,
|
|
6
|
+
Resources,
|
|
7
|
+
SnowparkServiceSpec,
|
|
8
|
+
VolumeMount,
|
|
9
|
+
)
|
|
10
|
+
from .snowpark_exceptions import SnowparkException
|
|
11
|
+
|
|
12
|
+
mapping = str.maketrans("0123456789", "abcdefghij")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# keep only alpha numeric characters and underscores..
|
|
16
|
+
def sanitize_name(job_name: str):
|
|
17
|
+
return "".join(char for char in job_name if char.isalnum() or char == "_")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# this is not a decorator since the exception imports need to be inside
|
|
21
|
+
# and not at the top level..
|
|
22
|
+
def retry_operation(
|
|
23
|
+
exception_type, func, max_retries=3, retry_delay=2, *args, **kwargs
|
|
24
|
+
):
|
|
25
|
+
for attempt in range(max_retries):
|
|
26
|
+
try:
|
|
27
|
+
return func(*args, **kwargs)
|
|
28
|
+
except exception_type as e:
|
|
29
|
+
if attempt < max_retries - 1:
|
|
30
|
+
time.sleep(retry_delay)
|
|
31
|
+
else:
|
|
32
|
+
raise e
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SnowparkJob(object):
|
|
36
|
+
def __init__(self, client: SnowparkClient, name, command, **kwargs):
|
|
37
|
+
self.client = client
|
|
38
|
+
self.name = sanitize_name(name)
|
|
39
|
+
self.command = command
|
|
40
|
+
self.kwargs = kwargs
|
|
41
|
+
self.container_name = self.name.translate(mapping).lower()
|
|
42
|
+
|
|
43
|
+
def create_job_spec(self):
|
|
44
|
+
if self.kwargs.get("image") is None:
|
|
45
|
+
raise SnowparkException(
|
|
46
|
+
"Unable to launch job on Snowpark Container Services. No docker 'image' specified."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if self.kwargs.get("stage") is None:
|
|
50
|
+
raise SnowparkException(
|
|
51
|
+
"Unable to launch job on Snowpark Container Services. No 'stage' specified."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if self.kwargs.get("compute_pool") is None:
|
|
55
|
+
raise SnowparkException(
|
|
56
|
+
"Unable to launch job on Snowpark Container Services. No 'compute_pool' specified."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
resources = Resources(
|
|
60
|
+
requests={
|
|
61
|
+
k: v
|
|
62
|
+
for k, v in [
|
|
63
|
+
("cpu", self.kwargs.get("cpu")),
|
|
64
|
+
("nvidia.com/gpu", self.kwargs.get("gpu")),
|
|
65
|
+
("memory", self.kwargs.get("memory")),
|
|
66
|
+
]
|
|
67
|
+
if v
|
|
68
|
+
},
|
|
69
|
+
limits={
|
|
70
|
+
k: v
|
|
71
|
+
for k, v in [
|
|
72
|
+
("nvidia.com/gpu", self.kwargs.get("gpu")),
|
|
73
|
+
]
|
|
74
|
+
if v
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
volume_mounts = self.kwargs.get("volume_mounts")
|
|
79
|
+
vm_objs = []
|
|
80
|
+
if volume_mounts:
|
|
81
|
+
if isinstance(volume_mounts, str):
|
|
82
|
+
volume_mounts = [volume_mounts]
|
|
83
|
+
for vm in volume_mounts:
|
|
84
|
+
name, mount_path = vm.split(":", 1)
|
|
85
|
+
vm_objs.append(VolumeMount(name=name, mount_path=mount_path))
|
|
86
|
+
|
|
87
|
+
container = (
|
|
88
|
+
Container(name=self.container_name, image=self.kwargs.get("image"))
|
|
89
|
+
.env(self.kwargs.get("environment_variables"))
|
|
90
|
+
.resources(resources)
|
|
91
|
+
.volume_mounts(vm_objs)
|
|
92
|
+
.command(self.command)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self.spec = SnowparkServiceSpec().containers([container])
|
|
96
|
+
return self
|
|
97
|
+
|
|
98
|
+
def environment_variable(self, name, value):
|
|
99
|
+
# Never set to None
|
|
100
|
+
if value is None:
|
|
101
|
+
return self
|
|
102
|
+
self.kwargs["environment_variables"] = dict(
|
|
103
|
+
self.kwargs.get("environment_variables", {}), **{name: value}
|
|
104
|
+
)
|
|
105
|
+
return self
|
|
106
|
+
|
|
107
|
+
def create(self):
|
|
108
|
+
return self.create_job_spec()
|
|
109
|
+
|
|
110
|
+
def execute(self):
|
|
111
|
+
query_id, service_name = self.client.submit(
|
|
112
|
+
self.name,
|
|
113
|
+
self.spec,
|
|
114
|
+
self.kwargs.get("stage"),
|
|
115
|
+
self.kwargs.get("compute_pool"),
|
|
116
|
+
self.kwargs.get("external_integration"),
|
|
117
|
+
)
|
|
118
|
+
return RunningJob(
|
|
119
|
+
client=self.client,
|
|
120
|
+
query_id=query_id,
|
|
121
|
+
service_name=service_name,
|
|
122
|
+
**self.kwargs
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def image(self, image):
|
|
126
|
+
self.kwargs["image"] = image
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def stage(self, stage):
|
|
130
|
+
self.kwargs["stage"] = stage
|
|
131
|
+
return self
|
|
132
|
+
|
|
133
|
+
def compute_pool(self, compute_pool):
|
|
134
|
+
self.kwargs["compute_pool"] = compute_pool
|
|
135
|
+
return self
|
|
136
|
+
|
|
137
|
+
def volume_mounts(self, volume_mounts):
|
|
138
|
+
self.kwargs["volume_mounts"] = volume_mounts
|
|
139
|
+
return self
|
|
140
|
+
|
|
141
|
+
def external_integration(self, external_integration):
|
|
142
|
+
self.kwargs["external_integration"] = external_integration
|
|
143
|
+
return self
|
|
144
|
+
|
|
145
|
+
def cpu(self, cpu):
|
|
146
|
+
self.kwargs["cpu"] = cpu
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
def gpu(self, gpu):
|
|
150
|
+
self.kwargs["gpu"] = gpu
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
def memory(self, memory):
|
|
154
|
+
self.kwargs["memory"] = memory
|
|
155
|
+
return self
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class RunningJob(object):
|
|
159
|
+
def __init__(self, client, query_id, service_name, **kwargs):
|
|
160
|
+
self.client = client
|
|
161
|
+
self.query_id = query_id
|
|
162
|
+
self.service_name = service_name
|
|
163
|
+
self.kwargs = kwargs
|
|
164
|
+
|
|
165
|
+
from snowflake.core.exceptions import NotFoundError
|
|
166
|
+
|
|
167
|
+
self.service = retry_operation(NotFoundError, self.__get_service)
|
|
168
|
+
|
|
169
|
+
def __get_service(self):
|
|
170
|
+
db = self.client.session.get_current_database()
|
|
171
|
+
schema = self.client.session.get_current_schema()
|
|
172
|
+
return (
|
|
173
|
+
self.client.root.databases[db].schemas[schema].services[self.service_name]
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def __repr__(self):
|
|
177
|
+
return "{}('{}')".format(self.__class__.__name__, self.query_id)
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def id(self):
|
|
181
|
+
return self.query_id
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def job_name(self):
|
|
185
|
+
return self.service_name
|
|
186
|
+
|
|
187
|
+
def status_obj(self, timeout=0):
|
|
188
|
+
from snowflake.core.exceptions import APIError, NotFoundError
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
return retry_operation(
|
|
192
|
+
APIError, self.service.get_service_status, timeout=timeout
|
|
193
|
+
)
|
|
194
|
+
except NotFoundError:
|
|
195
|
+
raise SnowparkException(
|
|
196
|
+
"The image *%s* most probably doesn't exist on Snowpark, or too many resources (CPU, GPU, memory) were requested."
|
|
197
|
+
% self.kwargs.get("image")
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def status(self):
|
|
202
|
+
return self.status_obj()[0].get("status")
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def message(self):
|
|
206
|
+
return self.status_obj()[0].get("message")
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def is_waiting(self):
|
|
210
|
+
return self.status == "PENDING"
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def is_running(self):
|
|
214
|
+
return self.status in ["PENDING", "READY"]
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def has_failed(self):
|
|
218
|
+
return self.status == "FAILED"
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def has_succeeded(self):
|
|
222
|
+
return self.status == "DONE"
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def has_finished(self):
|
|
226
|
+
return self.has_succeeded or self.has_failed
|
|
227
|
+
|
|
228
|
+
def kill(self):
|
|
229
|
+
from snowflake.core.exceptions import NotFoundError
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
if not self.has_finished:
|
|
233
|
+
self.client.terminate_job(service=self.service)
|
|
234
|
+
except (NotFoundError, TypeError):
|
|
235
|
+
pass
|