17
17
import contextlib
18
18
import os
19
19
import tempfile
20
+ import time
20
21
import zipfile
21
22
from pathlib import Path
22
23
from typing import TYPE_CHECKING , Any , Dict , List , Optional , Type , Union , cast
23
- from uuid import UUID
24
+ from uuid import UUID , uuid4
24
25
25
26
from zenml .client import Client
26
- from zenml .constants import MODEL_METADATA_YAML_FILE_NAME
27
+ from zenml .constants import (
28
+ MAX_RETRIES_FOR_VERSIONED_ENTITY_CREATION ,
29
+ MODEL_METADATA_YAML_FILE_NAME ,
30
+ )
27
31
from zenml .enums import (
28
32
ExecutionStatus ,
29
33
MetadataResourceTypes ,
30
34
StackComponentType ,
31
35
VisualizationType ,
32
36
)
33
- from zenml .exceptions import DoesNotExistException , StepContextError
37
+ from zenml .exceptions import (
38
+ DoesNotExistException ,
39
+ EntityExistsError ,
40
+ StepContextError ,
41
+ )
34
42
from zenml .io import fileio
35
43
from zenml .logger import get_logger
36
44
from zenml .models import (
@@ -107,26 +115,40 @@ def save_artifact(
107
115
108
116
Raises:
109
117
RuntimeError: If artifact URI already exists.
118
+ EntityExistsError: If artifact version already exists.
110
119
"""
111
120
from zenml .materializers .materializer_registry import (
112
121
materializer_registry ,
113
122
)
114
123
from zenml .utils import source_utils
115
124
116
- # TODO: Can we handle this server side? If we leave it empty in the request,
117
- # it's an auto-increase?
118
- # TODO: This can probably lead to issues when multiple steps request a new
119
- # artifact version at the same time?
120
- # Get new artifact version if not specified
121
- version = version or _get_new_artifact_version (name )
125
+ client = Client ()
126
+
127
+ # Get or create the artifact
128
+ try :
129
+ artifact = client .list_artifacts (name = name )[0 ]
130
+ if artifact .has_custom_name != has_custom_name :
131
+ client .update_artifact (
132
+ name_id_or_prefix = artifact .id , has_custom_name = has_custom_name
133
+ )
134
+ except IndexError :
135
+ try :
136
+ artifact = client .zen_store .create_artifact (
137
+ ArtifactRequest (
138
+ name = name ,
139
+ has_custom_name = has_custom_name ,
140
+ tags = tags ,
141
+ )
142
+ )
143
+ except EntityExistsError :
144
+ artifact = client .list_artifacts (name = name )[0 ]
122
145
123
146
# Get the current artifact store
124
- client = Client ()
125
147
artifact_store = client .active_stack .artifact_store
126
148
127
149
# Build and check the artifact URI
128
150
if not uri :
129
- uri = os .path .join ("custom_artifacts" , name , str (version ))
151
+ uri = os .path .join ("custom_artifacts" , name , str (uuid4 () ))
130
152
if not uri .startswith (artifact_store .path ):
131
153
uri = os .path .join (artifact_store .path , uri )
132
154
@@ -136,7 +158,7 @@ def save_artifact(
136
158
other_artifacts = client .list_artifact_versions (uri = uri , size = 1 )
137
159
if other_artifacts and (other_artifact := other_artifacts [0 ]):
138
160
raise RuntimeError (
139
- f"Cannot save artifact { name } ( version { version } ) to URI "
161
+ f"Cannot save new artifact { name } version to URI "
140
162
f"{ uri } because the URI is already used by artifact "
141
163
f"{ other_artifact .name } (version { other_artifact .version } )."
142
164
)
@@ -189,42 +211,62 @@ def save_artifact(
189
211
f"Failed to extract metadata for output artifact '{ name } ': { e } "
190
212
)
191
213
192
- # Get or create the artifact
193
- try :
194
- artifact = client .list_artifacts (name = name )[0 ]
195
- if artifact .has_custom_name != has_custom_name :
196
- client .update_artifact (
197
- name_id_or_prefix = artifact .id , has_custom_name = has_custom_name
214
+ # Create the artifact version
215
+ def _create_version () -> Optional [ArtifactVersionResponse ]:
216
+ artifact_version = ArtifactVersionRequest (
217
+ artifact_id = artifact .id ,
218
+ version = version ,
219
+ tags = tags ,
220
+ type = materializer_object .ASSOCIATED_ARTIFACT_TYPE ,
221
+ uri = materializer_object .uri ,
222
+ materializer = source_utils .resolve (materializer_object .__class__ ),
223
+ data_type = source_utils .resolve (data_type ),
224
+ user = Client ().active_user .id ,
225
+ workspace = Client ().active_workspace .id ,
226
+ artifact_store_id = artifact_store .id ,
227
+ visualizations = visualizations ,
228
+ has_custom_name = has_custom_name ,
229
+ )
230
+ try :
231
+ return client .zen_store .create_artifact_version (
232
+ artifact_version = artifact_version
198
233
)
199
- except IndexError :
200
- artifact = client .zen_store .create_artifact (
201
- ArtifactRequest (
202
- name = name ,
203
- has_custom_name = has_custom_name ,
204
- tags = tags ,
234
+ except EntityExistsError :
235
+ return None
236
+
237
+ response = None
238
+ if not version :
239
+ retries_made = 0
240
+ for i in range (MAX_RETRIES_FOR_VERSIONED_ENTITY_CREATION ):
241
+ # Get new artifact version
242
+ version = _get_new_artifact_version (name )
243
+ if response := _create_version ():
244
+ break
245
+ # smoothed exponential back-off, it will go as 0.2, 0.3,
246
+ # 0.45, 0.68, 1.01, 1.52, 2.28, 3.42, 5.13, 7.69, ...
247
+ sleep = 0.2 * 1.5 ** i
248
+ logger .debug (
249
+ f"Failed to create artifact version `{ version } ` for "
250
+ f"artifact `{ name } `. Retrying in { sleep } ..."
251
+ )
252
+ time .sleep (sleep )
253
+ retries_made += 1
254
+ if not response :
255
+ raise EntityExistsError (
256
+ f"Failed to create new artifact version for artifact "
257
+ f"`{ name } `. Retried { retries_made } times. "
258
+ "This could be driven by exceptionally high concurrency of "
259
+ "pipeline runs. Please, reach out to us on ZenML Slack for support."
260
+ )
261
+ else :
262
+ response = _create_version ()
263
+ if not response :
264
+ raise EntityExistsError (
265
+ f"Failed to create artifact version `{ version } ` for artifact "
266
+ f"`{ name } `. Given version already exists."
205
267
)
206
- )
207
-
208
- # Create the artifact version
209
- artifact_version = ArtifactVersionRequest (
210
- artifact_id = artifact .id ,
211
- version = version ,
212
- tags = tags ,
213
- type = materializer_object .ASSOCIATED_ARTIFACT_TYPE ,
214
- uri = materializer_object .uri ,
215
- materializer = source_utils .resolve (materializer_object .__class__ ),
216
- data_type = source_utils .resolve (data_type ),
217
- user = Client ().active_user .id ,
218
- workspace = Client ().active_workspace .id ,
219
- artifact_store_id = artifact_store .id ,
220
- visualizations = visualizations ,
221
- has_custom_name = has_custom_name ,
222
- )
223
- response = Client ().zen_store .create_artifact_version (
224
- artifact_version = artifact_version
225
- )
226
268
if artifact_metadata :
227
- Client () .create_run_metadata (
269
+ client .create_run_metadata (
228
270
metadata = artifact_metadata ,
229
271
resource_id = response .id ,
230
272
resource_type = MetadataResourceTypes .ARTIFACT_VERSION ,
0 commit comments