Skip to content

Commit

Permalink
Fix: Resolved issue #53 by removing alias if make_persistent fails.
Browse files Browse the repository at this point in the history
  • Loading branch information
marcmonfort committed Feb 6, 2025
1 parent fbf578e commit 87ddbb5
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 46 deletions.
96 changes: 50 additions & 46 deletions src/dataclay/runtime.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
""" Class description goes here. """

from __future__ import annotations

import asyncio
Expand Down Expand Up @@ -123,52 +121,58 @@ async def make_persistent(
alias, instance._dc_meta.dataset_name, instance._dc_meta.id
)

# If called inside backend runtime, default is to register in the current backend
# unles another backend is explicitly specified
if self.is_backend and (backend_id is None or backend_id == self.backend.id):
logger.debug("(%s) Registering the object in this backend", instance._dc_meta.id)
instance._dc_meta.master_backend_id = self.backend_id
await self.metadata_service.upsert_object(instance._dc_meta)
instance._dc_is_registered = True
self.inmemory_objects[instance._dc_meta.id] = instance
self.data_manager.add_hard_reference(instance)
return self.backend_id

# Called from client runtime, default is to choose a random backend
elif backend_id is None:
logger.debug(
"(%s) Choosing a random backend to register the object", instance._dc_meta.id
)
# If there is no backend client, update the list of backend clients
if not self.backend_clients:
await self.backend_clients.update()
try:
# If called inside backend runtime, default is to register in the current backend
# unles another backend is explicitly specified
if self.is_backend and (backend_id is None or backend_id == self.backend.id):
logger.debug("(%s) Registering the object in this backend", instance._dc_meta.id)
instance._dc_meta.master_backend_id = self.backend_id
await self.metadata_service.upsert_object(instance._dc_meta)
instance._dc_is_registered = True
self.inmemory_objects[instance._dc_meta.id] = instance
self.data_manager.add_hard_reference(instance)
return self.backend_id

# Called from client runtime, default is to choose a random backend
elif backend_id is None:
logger.debug(
"(%s) Choosing a random backend to register the object", instance._dc_meta.id
)
# If there is no backend client, update the list of backend clients
if not self.backend_clients:
raise RuntimeError(
f"({instance._dc_meta.id}) No backends available to register the object"
)
# Choose a random backend
backend_id, backend_client = random.choice(tuple(self.backend_clients.items()))
else:
backend_client = await self.backend_clients.get(backend_id)
await self.backend_clients.update()
if not self.backend_clients:
raise RuntimeError(
f"({instance._dc_meta.id}) No backends available to register the object"
)
# Choose a random backend
backend_id, backend_client = random.choice(tuple(self.backend_clients.items()))
else:
backend_client = await self.backend_clients.get(backend_id)

# Serialize instance with a recursive Pickle
visited_objects: dict[UUID, DataClayObject] = {}
serialized_objects = await recursive_dcdumps(
instance, local_objects=visited_objects, make_persistent=True
)
# Register the object in the backend
await backend_client.make_persistent(serialized_objects)

# Update the object metadata
for dc_object in visited_objects.values():
dc_object._clean_dc_properties()
dc_object._dc_is_registered = True
dc_object._dc_is_local = False
dc_object._dc_is_loaded = False
dc_object._dc_meta.master_backend_id = backend_id
self.inmemory_objects[dc_object._dc_meta.id] = dc_object

return instance._dc_meta.master_backend_id
# Serialize instance with a recursive Pickle
visited_objects: dict[UUID, DataClayObject] = {}
serialized_objects = await recursive_dcdumps(
instance, local_objects=visited_objects, make_persistent=True
)
# Register the object in the backend
await backend_client.make_persistent(serialized_objects)

# Update the object metadata
for dc_object in visited_objects.values():
dc_object._clean_dc_properties()
dc_object._dc_is_registered = True
dc_object._dc_is_local = False
dc_object._dc_is_loaded = False
dc_object._dc_meta.master_backend_id = backend_id
self.inmemory_objects[dc_object._dc_meta.id] = dc_object

return instance._dc_meta.master_backend_id
except Exception as e:
# If there is an error, delete the alias
if alias:
await self.metadata_service.delete_alias(alias, instance._dc_meta.dataset_name)
raise e

##################
# Object methods #
Expand Down
16 changes: 16 additions & 0 deletions tests/functional/test_alias.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import uuid

import pytest

from dataclay.contrib.modeltest.family import Person
Expand Down Expand Up @@ -105,3 +107,17 @@ async def test_get_aliases_async(client):
await Person.a_delete_alias("test_get_aliases_async")
await Person.a_delete_alias("test_get_aliases_1_async")
await Person.a_delete_alias("test_get_aliases_2_async")


def test_error_alias_creation(client):
"""
If the make_persistent method fails, the alias should not be created
"""
person = Person("Marc", 24)
with pytest.raises(KeyError):
# Force an error by using an invalid backend_id
invalid_backend_id = uuid.uuid4()
person.make_persistent(alias="test_error_alias_creation", backend_id=invalid_backend_id)
with pytest.raises(DataClayException) as excinfo:
Person.get_by_alias("test_error_alias_creation")
assert "does not exist" in str(excinfo.value)

0 comments on commit 87ddbb5

Please sign in to comment.