Hone logo
Hone
Problems

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:

  1. Data Storage: The repository should be able to store multiple items. Each item should have a unique identifier.
  2. Soft Delete: Implement a soft_delete method that marks an item as deleted but keeps it in the repository.
  3. Filtering: Implement a get_all method that retrieves only the active items.
  4. Retrieval by ID: Implement a get_by_id method that retrieves a specific item by its ID, regardless of its active or inactive status.
  5. Restoration: Implement a restore method to make a soft-deleted item active again.
  6. 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_delete is called, the item should be marked as inactive. It should no longer appear in results from get_all.
  • get_by_id should always return the item if it exists, whether active or soft-deleted.
  • restore should revert a soft-deleted item to an active state, making it visible again via get_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 Repository class 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_active attribute should be a boolean.

Notes

  • Consider how you might represent the items. A simple class with an is_active attribute is a good starting point.
  • Think about the state transitions: Active -> Soft Deleted, Soft Deleted -> Active.
  • The get_by_id method 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.
Loading editor...
python