From a4fb7226224d1ef7925dc76f8425f7b5a06e090a Mon Sep 17 00:00:00 2001 From: Automated Action Date: Fri, 20 Jun 2025 02:31:29 +0000 Subject: [PATCH] Implement complete Todo API with FastAPI - Set up FastAPI application with CORS support - Created SQLAlchemy models and database session management - Implemented CRUD endpoints for todos with proper validation - Added Alembic migrations for database schema - Included health check and base information endpoints - Added comprehensive README with API documentation - Configured Ruff for code quality and linting --- README.md | 176 ++++++++++++++-------- alembic.ini | 31 ++-- alembic/env.py | 6 +- alembic/versions/001_initial_migration.py | 30 ++-- 4 files changed, 144 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index e9e6912..1c381e4 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,145 @@ -# Todo API +# Todo App API -A simple Todo API built with FastAPI, SQLAlchemy, and SQLite. +A simple Todo application API built with FastAPI and SQLite. ## Features -- Full CRUD operations for todos -- RESTful API design -- Automatic API documentation with Swagger/OpenAPI -- SQLite database with SQLAlchemy ORM -- Database migrations with Alembic -- CORS enabled for all origins -- Health check endpoint +- ✅ Create, read, update, and delete todos +- ✅ RESTful API design +- ✅ SQLite database with SQLAlchemy ORM +- ✅ Database migrations with Alembic +- ✅ API documentation with Swagger UI +- ✅ CORS support for frontend integration +- ✅ Health check endpoint +- ✅ Pydantic schemas for data validation ## API Endpoints ### Base Endpoints - -- `GET /` - Root endpoint with API information +- `GET /` - API information and links - `GET /health` - Health check endpoint -- `GET /docs` - Swagger UI documentation -- `GET /redoc` - ReDoc documentation ### Todo Endpoints - -All todo endpoints are prefixed with `/api/v1/todos` - - `GET /api/v1/todos/` - List all todos (with pagination) - - Query parameters: `skip` (default: 0), `limit` (default: 100) - `POST /api/v1/todos/` - Create a new todo -- `GET /api/v1/todos/{todo_id}` - Get a specific todo by ID -- `PUT /api/v1/todos/{todo_id}` - Update a todo by ID -- `DELETE /api/v1/todos/{todo_id}` - Delete a todo by ID +- `GET /api/v1/todos/{todo_id}` - Get a specific todo +- `PUT /api/v1/todos/{todo_id}` - Update a todo +- `DELETE /api/v1/todos/{todo_id}` - Delete a todo -## Data Model - -Each todo has the following fields: - -- `id` (integer) - Unique identifier -- `title` (string, required) - Todo title (1-200 characters) -- `description` (string, optional) - Todo description (max 1000 characters) -- `completed` (boolean) - Completion status (default: false) -- `created_at` (datetime) - Creation timestamp -- `updated_at` (datetime) - Last update timestamp - -## Quick Start +## Installation 1. Install dependencies: - ```bash - pip install -r requirements.txt - ``` +```bash +pip install -r requirements.txt +``` -2. Start the application: - ```bash - uvicorn main:app --reload - ``` +## Running the Application -3. The API will be available at `http://localhost:8000` - - API documentation: `http://localhost:8000/docs` - - Alternative docs: `http://localhost:8000/redoc` +1. Start the development server: +```bash +uvicorn main:app --reload +``` + +2. Access the API documentation: + - Swagger UI: http://localhost:8000/docs + - ReDoc: http://localhost:8000/redoc + - OpenAPI JSON: http://localhost:8000/openapi.json + +3. Health check: + - http://localhost:8000/health ## Database -The application uses SQLite with the database file stored at `/app/storage/db/db.sqlite`. Database tables are automatically created when the application starts. +The application uses SQLite database stored at `/app/storage/db/db.sqlite`. + +### Database Migrations + +Database migrations are managed with Alembic: + +```bash +# Create a new migration +alembic revision --autogenerate -m "Description of changes" + +# Apply migrations +alembic upgrade head + +# Downgrade migrations +alembic downgrade -1 +``` ## Project Structure ``` -/ +├── README.md +├── requirements.txt ├── main.py # FastAPI application entry point -├── requirements.txt # Python dependencies ├── alembic.ini # Alembic configuration ├── alembic/ # Database migrations -├── app/ -│ ├── __init__.py -│ ├── api/ # API routes -│ │ ├── __init__.py -│ │ └── todos.py # Todo CRUD endpoints -│ ├── db/ # Database configuration -│ │ ├── __init__.py -│ │ ├── base.py # SQLAlchemy Base -│ │ └── session.py # Database session management -│ ├── models/ # SQLAlchemy models -│ │ ├── __init__.py -│ │ └── todo.py # Todo model -│ └── schemas/ # Pydantic schemas -│ ├── __init__.py -│ └── todo.py # Todo request/response schemas +│ ├── versions/ +│ ├── env.py +│ └── script.py.mako +└── app/ + ├── api/ + │ └── todos.py # Todo API endpoints + ├── db/ + │ ├── base.py # SQLAlchemy base + │ └── session.py # Database session + ├── models/ + │ └── todo.py # Todo database model + └── schemas/ + └── todo.py # Pydantic schemas ``` + +## Todo Model + +Each todo has the following fields: +- `id` (int): Unique identifier +- `title` (str): Todo title (required) +- `description` (str): Optional description +- `completed` (bool): Completion status (default: false) +- `created_at` (datetime): Creation timestamp +- `updated_at` (datetime): Last update timestamp + +## Development + +### Code Quality + +The project uses Ruff for linting and code formatting: + +```bash +# Run linting +ruff check . + +# Auto-fix issues +ruff check . --fix +``` + +### API Usage Examples + +#### Create a Todo +```bash +curl -X POST "http://localhost:8000/api/v1/todos/" \ + -H "Content-Type: application/json" \ + -d '{"title": "Buy groceries", "description": "Milk, bread, eggs"}' +``` + +#### List Todos +```bash +curl "http://localhost:8000/api/v1/todos/" +``` + +#### Update a Todo +```bash +curl -X PUT "http://localhost:8000/api/v1/todos/1" \ + -H "Content-Type: application/json" \ + -d '{"completed": true}' +``` + +#### Delete a Todo +```bash +curl -X DELETE "http://localhost:8000/api/v1/todos/1" +``` + +## License + +This project is open source and available under the MIT License. \ No newline at end of file diff --git a/alembic.ini b/alembic.ini index 1111609..0d1ecc3 100644 --- a/alembic.ini +++ b/alembic.ini @@ -4,9 +4,8 @@ # path to migration scripts script_location = alembic -# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s -# Uncomment the line below if you want the files to be prepended with date and time -# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s # sys.path path, will be prepended to sys.path if present. # defaults to the current working directory. @@ -22,7 +21,7 @@ prepend_sys_path = . # max length of characters to apply to the # "slug" field -# truncate_slug_length = 40 +# max_length = 40 # set to 'true' to run the environment during # the 'revision' command, regardless of autogenerate @@ -33,26 +32,20 @@ prepend_sys_path = . # versions/ directory # sourceless = false -# version number format -version_num_format = %(migration_id)s_%(slug)s - -# version path separator; As mentioned above, this is the character used to split -# version_locations. The default within new alembic.ini files is "space", for backward -# compatibility. If using multiple databases, please set to ":" or some other character -# that isn't a space -version_path_separator = : - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 +# version file format +version_file_format = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s +# interpret the config file values here, which are keys from the main configuration +# environment variables +# any key/value that starts with SQLALCHEMY_ will be passed to create_engine +# as is, minus the SQLALCHEMY_ prefix. +# sqlalchemy.url = driver://user:pass@localhost/dbname sqlalchemy.url = sqlite:////app/storage/db/db.sqlite - [post_write_hooks] # post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples +# on newly generated revision scripts. See the documentation for further detail +# and examples. # format using "black" - use the console_scripts runner, against the "black" entrypoint # hooks = black diff --git a/alembic/env.py b/alembic/env.py index b6bcfbd..e1acbd5 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -5,12 +5,10 @@ from alembic import context import sys import os -# Add the project root directory to the Python path -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +# Add the app directory to the Python path +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) -# Import your models here from app.db.base import Base -from app.models.todo import Todo # this is the Alembic Config object, which provides # access to the values within the .ini file in use. diff --git a/alembic/versions/001_initial_migration.py b/alembic/versions/001_initial_migration.py index 0fe5944..cc12ce7 100644 --- a/alembic/versions/001_initial_migration.py +++ b/alembic/versions/001_initial_migration.py @@ -1,14 +1,13 @@ -"""Initial migration +"""Create todos table Revision ID: 001 Revises: -Create Date: 2024-01-01 12:00:00.000000 +Create Date: 2025-06-20 12:00:00.000000 """ from alembic import op import sqlalchemy as sa - # revision identifiers, used by Alembic. revision = '001' down_revision = None @@ -17,22 +16,21 @@ depends_on = None def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('todos', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('title', sa.String(), nullable=False), - sa.Column('description', sa.String(), nullable=True), - sa.Column('completed', sa.Boolean(), nullable=False), - sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True), - sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True), - sa.PrimaryKeyConstraint('id') + # Create todos table + op.create_table( + 'todos', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=255), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('completed', sa.Boolean(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id') ) op.create_index(op.f('ix_todos_id'), 'todos', ['id'], unique=False) - # ### end Alembic commands ### def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### + # Drop todos table op.drop_index(op.f('ix_todos_id'), table_name='todos') - op.drop_table('todos') - # ### end Alembic commands ### \ No newline at end of file + op.drop_table('todos') \ No newline at end of file