Skip to content

Commit 55a0853

Browse files
authored
[HWORKS-2064] Auto-increment model export only looks at the model version folder, should also account for model version db metadata (#503)
1 parent 1fab3ee commit 55a0853

File tree

2 files changed

+52
-18
lines changed

2 files changed

+52
-18
lines changed

python/hopsworks_common/core/dataset_api.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,7 @@ def list_files(self, path, offset, limit):
645645
return inode_lst["count"], inode.Inode.from_response_json(inode_lst)
646646

647647
@usage.method_logger
648-
def list(self, remote_path, sort_by=None, limit=1000):
648+
def list(self, remote_path, sort_by=None, offset=0, limit=1000):
649649
"""List all files in a directory in datasets.
650650
651651
:param remote_path: path to list
@@ -659,7 +659,7 @@ def list(self, remote_path, sort_by=None, limit=1000):
659659
# they seem to handle paths differently and return different results, which prevents the merge at the moment (2024-09-03), due to the requirement of backwards-compatibility
660660
_client = client.get_instance()
661661
path_params = ["project", _client._project_id, "dataset", remote_path]
662-
query_params = {"action": "listing", "sort_by": sort_by, "limit": limit}
662+
query_params = {"action": "listing", "sort_by": sort_by, "limit": limit, "offset": offset}
663663
headers = {"content-type": "application/json"}
664664
return _client._send_request(
665665
"GET", path_params, headers=headers, query_params=query_params

python/hsml/engine/model_engine.py

+50-16
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,17 @@ def _set_model_version(
270270
# Set model version if not defined
271271
if model_instance._version is None:
272272
current_highest_version = 0
273-
for item in self._dataset_api.list(dataset_model_path, sort_by="NAME:desc")[
274-
"items"
275-
]:
273+
files = []
274+
offset = 0
275+
limit = 1000
276+
items = self._dataset_api.list(dataset_model_path, sort_by="NAME:desc", offset=offset, limit=limit)["items"]
277+
while items:
278+
files = files + items
279+
offset += limit
280+
items = self._dataset_api.list(dataset_model_path, sort_by="NAME:desc", offset=offset, limit=limit)["items"]
281+
for item in files:
276282
_, file_name = os.path.split(item["attributes"]["path"])
283+
# Get highest version folder
277284
try:
278285
try:
279286
current_version = int(file_name)
@@ -283,20 +290,47 @@ def _set_model_version(
283290
current_highest_version = current_version
284291
except RestAPIError:
285292
pass
286-
model_instance._version = current_highest_version + 1
287-
288-
elif self._dataset_api.path_exists(
289-
dataset_models_root_path
290-
+ "/"
291-
+ model_instance._name
292-
+ "/"
293-
+ str(model_instance._version)
294-
):
295-
raise ModelRegistryException(
296-
"Model with name {} and version {} already exists".format(
297-
model_instance._name, model_instance._version
293+
294+
current_highest_version += 1
295+
296+
# Get highest available model metadata version
297+
# This makes sure we skip corrupt versions where the model folder is deleted manually but the backend metadata is still there
298+
model = self._model_api.get(model_instance._name, current_highest_version, model_instance.model_registry_id)
299+
while model:
300+
current_highest_version += 1
301+
model = self._model_api.get(model_instance._name, current_highest_version, model_instance.model_registry_id)
302+
303+
model_instance._version = current_highest_version
304+
else:
305+
model_backend_object_exists = self._model_api.get(model_instance._name, model_instance._version, model_instance.model_registry_id) is not None
306+
model_version_folder_exists = self._dataset_api.path_exists(
307+
dataset_models_root_path
308+
+ "/"
309+
+ model_instance._name
310+
+ "/"
311+
+ str(model_instance._version)
312+
)
313+
314+
# Perform validations to handle possible inconsistency between db and filesystem
315+
if model_backend_object_exists and not model_version_folder_exists:
316+
raise ModelRegistryException(
317+
"Model with name {0} and version {1} looks to be corrupt as the version is registered but there is no Models/{0}/{1} folder in the filesystem. Delete this version using Model.delete() or the UI and try to export this version again.".format(
318+
model_instance._name, model_instance._version
319+
)
298320
)
299-
)
321+
elif not model_backend_object_exists and model_version_folder_exists:
322+
raise ModelRegistryException(
323+
"Model with name {0} and version {1} looks to be corrupt as the version exists in the filesystem but it is not registered. To proceed, please delete the Models/{0}/{1} folder manually and try to save this model again.".format(
324+
model_instance._name, model_instance._version
325+
)
326+
)
327+
elif model_backend_object_exists and model_version_folder_exists:
328+
raise ModelRegistryException(
329+
"Model with name {} and version {} already exists, please select another version.".format(
330+
model_instance._name, model_instance._version
331+
)
332+
)
333+
300334
return model_instance
301335

302336
def _build_resource_path(self, model_instance, artifact):

0 commit comments

Comments
 (0)