Minimalist ORM Implementation in Python
This challenge asks you to implement a simplified Object-Relational Mapper (ORM) in Python. ORMs provide a layer of abstraction between your application code and the database, allowing you to interact with data using objects instead of raw SQL queries. Building a basic ORM will help you understand the core concepts behind database interaction and object mapping.
Problem Description
You are tasked with creating a rudimentary ORM that supports basic CRUD (Create, Read, Update, Delete) operations for a single table in a SQLite database. The ORM should allow you to define a model class representing the table, map attributes of the model to columns in the table, and perform operations on the table using instances of the model.
Key Requirements:
- Model Definition: You should be able to define a model class that inherits from a base class provided by your ORM. This model class should specify the table name and the mapping between class attributes and database columns.
- CRUD Operations: The ORM should provide methods for:
create(model_instance): Inserts a new row into the database based on the model instance's attributes.get(model_class, id): Retrieves a model instance from the database based on its primary key (id).update(model_instance): Updates an existing row in the database with the model instance's attributes.delete(model_instance): Deletes a row from the database based on the model instance's primary key (id).
- Database Connection: The ORM should handle the connection to a SQLite database. The database file path should be configurable.
- Primary Key: The ORM should assume that each table has a primary key column named 'id' which is an integer.
Expected Behavior:
- The ORM should correctly map model attributes to database columns.
- CRUD operations should execute the appropriate SQL queries and interact with the database.
- The
getmethod should returnNoneif no record with the given ID is found. - The ORM should handle potential database errors gracefully (e.g., connection errors, invalid SQL). For simplicity, you can raise exceptions in case of errors.
Edge Cases to Consider:
- What happens if the database file doesn't exist?
- What happens if the table doesn't exist? (For simplicity, assume the table is created when the first record is inserted).
- What happens if you try to update a record that doesn't exist? (For simplicity, you can ignore this case).
- What happens if you try to delete a record that doesn't exist? (For simplicity, you can ignore this case).
Examples
Example 1:
Input:
Model Definition:
class User(BaseModel):
table = 'users'
columns = ['id', 'name', 'email']
ORM Usage:
user = User(name='Alice', email='alice@example.com')
orm.create(user)
Output:
A new row is inserted into the 'users' table with the values: id=1, name='Alice', email='alice@example.com' (assuming id is auto-incrementing)
Explanation:
The `create` method generates an INSERT SQL query and executes it, inserting the user's data into the 'users' table.
Example 2:
Input:
Model Definition:
class Product(BaseModel):
table = 'products'
columns = ['id', 'name', 'price']
ORM Usage:
product = orm.get(Product, 1)
Output:
product = Product(id=1, name='Laptop', price=1200.00) (assuming a product with id=1 exists in the database)
or
product = None (if no product with id=1 exists)
Explanation:
The `get` method generates a SELECT SQL query to retrieve the product with id=1. If found, it creates a Product object and returns it. Otherwise, it returns None.
Example 3:
Input:
Model Definition:
class Book(BaseModel):
table = 'books'
columns = ['id', 'title', 'author']
ORM Usage:
book = Book(id=2, title='The Hitchhiker\'s Guide to the Galaxy', author='Douglas Adams')
orm.update(book)
Output:
The row with id=2 in the 'books' table is updated with the values: title='The Hitchhiker\'s Guide to the Galaxy', author='Douglas Adams'
Explanation:
The `update` method generates an UPDATE SQL query to update the book with id=2.
Constraints
- Database: Use SQLite.
- SQL Injection: For simplicity, you do not need to implement full SQL injection prevention. However, be mindful of potential vulnerabilities and avoid directly embedding user-provided data into SQL queries without proper sanitization (e.g., using parameterized queries).
- Error Handling: Basic error handling is sufficient. Raising exceptions for database connection errors or invalid SQL is acceptable.
- Performance: Performance is not a primary concern for this challenge. Focus on correctness and clarity.
- Model Columns: Assume all columns are of string type unless the 'id' column which is an integer.
Notes
- Start by defining a base
BaseModelclass that provides common functionality for all models. - Implement a simple
ORMclass that handles database connections and CRUD operations. - Consider using the
sqlite3module for database interaction. - Focus on the core functionality of the ORM. Advanced features like relationships, indexing, and complex queries are beyond the scope of this challenge.
- The
columnsattribute in the model should define the order of columns in the table. - The
idcolumn is assumed to be auto-incrementing. You don't need to explicitly handle auto-incrementing in your code. - The
createmethod should return the created object. - The
getmethod should returnNoneif the object is not found.