A Python implementation of an in-memory key-value database with transaction support, designed to handle atomic updates and rollback capabilities.
This project implements a simple in-memory key-value database in Python, supporting basic database operations (put, get) and transaction features like begin_transaction, commit, and rollback. The database allows atomic updates, ensuring changes are either fully applied or fully reverted.
Transactions are an important part of relational databases. Transactions allow “all or nothing” updates to databases to prevent dirty writes. This is especially important while dealing with operations involving consistency, such as financial transactions.
For example:
If you are building a money transfer app like Venmo, when User A initiates a transfer of $X to User B:
- $X should be deducted from A’s account.
- $X should be added to B’s account.
If either of these steps fails, the overall accounting would be in an inconsistent state. Transactions ensure that these updates are part of a single operation that either fully succeeds or fully fails.
In this project, we will build an in-memory key-value database with transaction support using Python. The project also includes automated testing and optional GUI implementation.
begin_transaction()
: Starts a new transaction.put(key, value)
: Inserts or updates a key-value pair within a transaction.get(key)
: Retrieves the value associated with the key orNone
if the key doesn't exist.commit()
: Applies changes made within the transaction to the main state.rollback()
: Aborts all changes made within the transaction.
-
Keys as Strings and Values as Integers:
Theput
method accepts a string key and an integer value. -
put(key, val)
Behavior:
Creates a new key with the provided value if it doesn’t exist or updates the value if it does. -
get(key)
Behavior:
Returns the value associated with the key orNone
if the key doesn’t exist. -
Exception When
put
Called Without Active Transaction:
Raises aNoActiveTransactionError
ifput
is called without an active transaction. -
get(key)
Can Be Called Anytime:
get
can be called whether or not a transaction is in progress. -
begin_transaction()
Starts a New Transaction:
Initializes a new transaction. -
Only One Transaction at a Time:
Raises aTransactionAlreadyActiveError
ifbegin_transaction
is called while a transaction is active. -
Uncommitted Changes Not Visible to
get()
:
get
retrieves values only from the committed state (main_db
). Uncommitted changes intransaction_log
are not visible untilcommit()
is called. -
Transaction Ends with
commit()
orrollback()
:
Both methods reset the transaction state. -
commit()
Applies Changes to Main State:
Mergestransaction_log
intomain_db
, making changes visible to futureget()
calls. -
rollback()
Aborts Changes:
Discardstransaction_log
without alteringmain_db
.
class InMemoryDB:
def get(self, key: str) -> int:
"""
Retrieves the value associated with the key from the main database.
Returns None if the key does not exist.
"""
pass
def put(self, key: str, val: int):
"""
Inserts or updates a key-value pair within a transaction.
Raises an exception if no transaction is active.
"""
pass
def begin_transaction(self):
"""
Starts a new transaction.
Raises an exception if a transaction is already in progress.
"""
pass
def commit(self):
"""
Commits the current transaction, applying all changes to the main database.
Raises an exception if no transaction is active.
"""
pass
def rollback(self):
"""
Aborts the current transaction, discarding all uncommitted changes.
Raises an exception if no transaction is active.
"""
pass
-
assets
: Images -
tests/
Folder:test_in_memory_db.py
: Sample test cases for various operations, including transaction workflows.test_unit.py
: Unit tests using Python’sunittest
framework to validate functionality.
-
in_memory_db.ipynb
: Interactive Jupyter Notebook for testing and experimentation. -
in_memory_db.py
: Main implementation of the in-memory key-value database. -
main.py
: CLI menu-driven interface for interacting with the database. -
tkinter_gui.py
: A simple Tkinter-based graphical interface for visualizing and testing database operations.in-memory-db/ │ ├── assets/ # (Optional) For GUI screenshots ├── tests/ # Test folder │ ├── test_in_memory_db.py # Example script for manual testing │ ├── test_unit.py # Unit tests using Python's unittest module ├── in_memory_db.ipynb # Jupyter Notebook for interactive testing ├── in_memory_db.py # Core implementation of the in-memory database ├── main.py # CLI menu-driven interface ├── README.md # Project documentation └── tkinter_gui.py # Tkinter GUI interface
-
Python Version:
- Ensure Python 3.11 or later is installed.
- To check your Python version:
python --version
-
Tkinter:
- Included in most Python installations. To confirm or install:
pip install tk
- Included in most Python installations. To confirm or install:
-
Jupyter Notebook:
- Install Jupyter Notebook for running
.ipynb
files:pip install notebook
- For Visual Studio Code, install the Jupyter Notebook Extension.
- Install Jupyter Notebook for running
- Clone the Repository:
git clone https://github.com/OzPol/in-memory-db
- Navigate to the project directory:
cd in-memory-db
- Run the
main.py
file:python main.py
- Follow the prompts in the terminal to interact with the database.
- Navigate to the project directory:
cd in-memory-db
- Run the
tkinter_gui.py
file:python tkinter_gui.py
GUI Features:
- Begin Transaction: Start a transaction before performing operations.
- Put Key-Value Pairs: Enter a key and value, then click "Put" to add the pair within the transaction.
- Get: Retrieve the value of a committed key. Displays an error if the key does not exist.
- Commit: Apply all changes in the transaction to the database.
- Rollback: Revert all uncommitted changes.
Make sure you begin a transaction before using Put, Commit, or Rollback to avoid errors.
The Get button can be used anytime to retrieve committed values.
-
Open the
in_memory_db.ipynb
file in Jupyter Notebook or Visual Studio Code. -
Run all cells or interact step-by-step with the database.
- Open the repository folder in Visual Studio Code.
- Open the desired Python file, such as
main.py
,tkinter_gui.py
, or any test file. - Run the file:
- Click the green "Run" button in the top-right corner of VSC.
- Or open the terminal in VSC and type:
Replace
python <file_name>.py
<file_name>
with the file you want to execute (e.g.,main.py
).
Running Tests:
- Open
tests/test_unit.py
ortests/test_in_memory_db.py
. - Run the files like any other program in VS Code to execute the tests.
Optionally:
-
Run the
tests/test_unit.py
file to verify the database's functionality:python tests/test_unit.py
or
-
Run
tests/test_in_memory_db.py
to see example scenarios in action:python tests/test_in_memory_db.py
This runs automated tests using Python's unittest
module.
-
Testing
put
without an active transaction:- Calling
put
outside a transaction raises an exception.
- Calling
-
Transactional Operations:
- Covers end-to-end testing of operations like
begin_transaction
,put
,commit
, androllback
. - Verifies scenarios such as:
- Committing changes makes them visible to
get
. - Rolling back a transaction discards uncommitted changes.
- Only committed changes persist between transactions.
- Errors are thrown when invalid operations are attempted (e.g., committing without an active transaction).
- Committing changes makes them visible to
- Covers end-to-end testing of operations like
# Create an instance of InMemoryDB
db = InMemoryDB()
# Start a transaction
print(db.begin_transaction()) # Output: "Transaction started."
# Add key-value pairs
print(db.put("A", 5)) # Output: "Set A = 5."
print(db.put("B", 10)) # Output: "Set B = 10."
# Commit the transaction
print(db.commit()) # Output: "Transaction committed: Changes applied."
# Retrieve values
print(db.get("A")) # Output: "A = 5."
print(db.get("B")) # Output: "B = 10."
# Start another transaction and rollback
print(db.begin_transaction()) # Output: "Transaction started."
print(db.put("A", 15)) # Output: "Set A = 15."
print(db.rollback()) # Output: "Transaction rolled back."
# Value remains unchanged after rollback
print(db.get("A")) # Output: "A = 5."
- Use the CLI for command-line interaction or the GUI for a user-friendly experience.
- The Jupyter Notebook allows for step-by-step exploration of database operations.
The current assignment offers a solid foundation for understanding transaction management and implementing key database operations. However, certain areas of the instructions and structure could be clarified and enhanced for a better student experience and learning outcome. Below are the suggestions:
- The instructions state: "
get(key)
will return the value associated with the key or null if the key doesn’t exist." - Issue: It's unclear whether
get(key)
should explicitly returnNone
or an explanatory message like"Key does not exist."
- Suggestion: Add an explicit expectation for the return value (e.g.,
None
or a message) for consistency across implementations.
- Current Issue: The instructions do not specify what happens when
commit
is called without any priorput
operations. - Suggestion: Clarify that committing an empty transaction should result in a no-op (e.g.,
"No changes made"
).
- While the assignment provides examples, it does not include test cases for:
- Rolling back multiple updates within a transaction.
- Committing a transaction after a rollback attempt.
- Handling invalid inputs (e.g., non-integer values, empty keys).
- Suggestion: Provide a set of sample test cases covering edge scenarios and include them in the assignment instructions.
- Optionally, suggest implementing a GUI (e.g., using Python's Tkinter or another tool) to help students visualize transactions.
- Add bonus tasks, such as allowing nested transactions or implementing database persistence.