Implementing Soft Deletes in Python
In many applications, directly deleting data from a database can be irreversible and can lead to data loss or complications if that data is later needed. Soft deletes provide a mechanism to "remove" data logically without permanently erasing it from the storage. This challenge will guide you to implement a soft delete system for a simple Python data structure.
Problem Description
Your task is to create a Python class that simulates a data repository capable of performing soft deletes. This means that instead of physically removing an item, we'll mark it as inactive.
Key Requirements:
- Data Storage: The repository should be able to store multiple items. Each item should have a unique identifier.
- Soft Delete: Implement a
soft_deletemethod that marks an item as deleted but keeps it in the repository. - Filtering: Implement a
get_allmethod that retrieves only the active items. - Retrieval by ID: Implement a
get_by_idmethod that retrieves a specific item by its ID, regardless of its active or inactive status. - Restoration: Implement a
restoremethod to make a soft-deleted item active again. - Data Structure: For simplicity, you can use a Python dictionary or list to store the items internally.
Expected Behavior:
- When an item is added, it should be considered active by default.
- When
soft_deleteis called, the item should be marked as inactive. It should no longer appear in results fromget_all. get_by_idshould always return the item if it exists, whether active or soft-deleted.restoreshould revert a soft-deleted item to an active state, making it visible again viaget_all.
Edge Cases to Consider:
- Attempting to delete an item that doesn't exist.
- Attempting to delete an already soft-deleted item.
- Attempting to restore an item that is not soft-deleted.
- Attempting to restore an item that doesn't exist.
Examples
Example 1:
class Item:
def __init__(self, id, name):
self.id = id
self.name = name
self.is_active = True
def __repr__(self):
return f"Item(id={self.id}, name='{self.name}', is_active={self.is_active})"
class Repository:
def __init__(self):
self._items = {}
def add_item(self, item):
self._items[item.id] = item
def soft_delete(self, item_id):
if item_id in self._items:
self._items[item_id].is_active = False
return True
return False
def get_all(self):
return [item for item in self._items.values() if item.is_active]
def get_by_id(self, item_id):
return self._items.get(item_id)
def restore(self, item_id):
if item_id in self._items:
self._items[item_id].is_active = True
return True
return False
# --- Usage ---
repo = Repository()
item1 = Item(1, "Apple")
item2 = Item(2, "Banana")
item3 = Item(3, "Cherry")
repo.add_item(item1)
repo.add_item(item2)
repo.add_item(item3)
print("All active items initially:", repo.get_all())
# Expected Output: All active items initially: [Item(id=1, name='Apple', is_active=True), Item(id=2, name='Banana', is_active=True), Item(id=3, name='Cherry', is_active=True)]
repo.soft_delete(2)
print("All active items after deleting Banana:", repo.get_all())
# Expected Output: All active items after deleting Banana: [Item(id=1, name='Apple', is_active=True), Item(id=3, name='Cherry', is_active=True)]
print("Getting Banana by ID:", repo.get_by_id(2))
# Expected Output: Getting Banana by ID: Item(id=2, name='Banana', is_active=False)
repo.restore(2)
print("All active items after restoring Banana:", repo.get_all())
# Expected Output: All active items after restoring Banana: [Item(id=1, name='Apple', is_active=True), Item(id=3, name='Cherry', is_active=True), Item(id=2, name='Banana', is_active=True)]
Example 2: Handling Edge Cases
repo = Repository()
item_x = Item(10, "Xylophone")
repo.add_item(item_x)
print("Attempting to delete non-existent item:", repo.soft_delete(99))
# Expected Output: Attempting to delete non-existent item: False
print("Attempting to restore non-existent item:", repo.restore(99))
# Expected Output: Attempting to restore non-existent item: False
repo.soft_delete(10)
print("Attempting to delete already deleted item:", repo.soft_delete(10))
# Expected Output: Attempting to delete already deleted item: True (because it marks it inactive again, which is still a valid operation even if redundant)
print("Attempting to restore active item:", repo.restore(10))
# Expected Output: Attempting to restore active item: True
Constraints
- The
Repositoryclass must be implemented in Python. - Each item in the repository is assumed to have a unique, hashable identifier.
- The internal storage mechanism for the repository is up to you, but it should efficiently support add, delete, and lookup operations by ID.
- The
is_activeattribute should be a boolean.
Notes
- Consider how you might represent the items. A simple class with an
is_activeattribute is a good starting point. - Think about the state transitions: Active -> Soft Deleted, Soft Deleted -> Active.
- The
get_by_idmethod should be robust enough to return an item even if it's soft-deleted. - This implementation is a simplified model. In a real-world scenario, you'd likely be interacting with a database, and the concept of "soft delete" would be implemented by updating a flag in a database record.