Skip to content

Replace sqlmodel w sqlalchemy#65

Closed
GabrielVGS wants to merge 8 commits intomainfrom
replace-sqlmodel-w-sqlalchemy
Closed

Replace sqlmodel w sqlalchemy#65
GabrielVGS wants to merge 8 commits intomainfrom
replace-sqlmodel-w-sqlalchemy

Conversation

@GabrielVGS
Copy link
Owner

Pull Request

Description

Brief description of what this PR does.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Performance improvement
  • Refactoring (no functional changes)
  • Test improvements
  • CI/CD improvements
  • Other: **___**

Related Issue

Fixes #(issue number)

Changes Made

Testing

  • Unit tests pass (make test)
  • Integration tests pass
  • Manual testing completed
  • New tests added for new functionality

Test Results:

# Paste test results here if relevant

Code Quality

  • Pre-commit hooks pass (make precommit-run)
  • Code follows project style guidelines
  • Type hints added where applicable
  • Docstrings added/updated for public APIs

Database Changes

  • No database changes
  • New migration created (make alembic-make-migrations)
  • Migration tested locally
  • Migration is backward compatible
  • Database schema documented

Breaking Changes

  • No breaking changes
  • Breaking changes documented below

Breaking Changes Details:

Describe any breaking changes and migration path

Documentation

  • No documentation needed
  • README.md updated
  • API documentation updated
  • Architecture documentation updated
  • Contributing guidelines updated

Deployment

  • No deployment changes needed
  • Environment variables added/changed (documented in PR)
  • Docker configuration updated
  • Dependencies added/updated

Screenshots (if applicable)

Checklist

  • I have read the Contributing Guidelines
  • My code follows the code style of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Additional Notes

Any additional information, concerns, or questions about this PR.

@GabrielVGS GabrielVGS requested a review from Copilot December 16, 2025 01:43
@GabrielVGS GabrielVGS linked an issue Dec 16, 2025 that may be closed by this pull request
17 tasks
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request migrates the codebase from SQLModel to pure SQLAlchemy 2.0 with separate Pydantic schemas for data validation. The migration maintains the same functionality while adopting the more explicit SQLAlchemy ORM pattern with typed mappings and separating database models from API schemas.

Key Changes:

  • Replaced SQLModel with SQLAlchemy 2.0's DeclarativeBase and Mapped types for database models
  • Separated database models from Pydantic schemas (using PydanticBaseModel for request/response validation)
  • Updated repository layer to convert between Pydantic schemas and SQLAlchemy models using model_dump()

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/models/base.py Migrated from SQLModel to SQLAlchemy DeclarativeBase with Mapped columns and proper type annotations
src/models/__init__.py Added Base export and updated example documentation to reflect new SQLAlchemy patterns
src/repositories/types.py Updated type bounds from SQLModel to Base (SQLAlchemy) and PydanticBaseModel
src/repositories/sqlalchemy.py Changed from model_validate() to model_dump() for converting Pydantic to SQLAlchemy models
src/migrations/env.py Updated metadata reference from SQLModel.metadata to Base.metadata
src/migrations/script.py.mako Removed sqlmodel import from migration template
src/schemas/__init__.py Added example Pydantic schema structure with from_attributes configuration
tests/conftest.py Updated to use Base.metadata instead of SQLModel.metadata
tests/test_repository.py Migrated test models to SQLAlchemy pattern with separate Pydantic schemas
pyproject.toml Removed sqlmodel dependency
docs/developing.md Updated documentation examples to show new SQLAlchemy 2.0 pattern
docs/architecture.md Updated references from SQLModel to SQLAlchemy
README.md Updated Built With section to reference SQLAlchemy and Pydantic separately
docker-compose.yml Modified PostgreSQL volume path (appears unrelated to migration)

retries: 5
volumes:
- db-data:/var/lib/postgresql/data
- db-data:/var/lib/postgresql/
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PostgreSQL data volume path has been changed from /var/lib/postgresql/data to /var/lib/postgresql/. This change appears unrelated to the SQLModel to SQLAlchemy migration and may cause data persistence issues. The standard PostgreSQL Docker image expects the data directory to be mounted at /var/lib/postgresql/data, not the parent directory. This could lead to the database not persisting data correctly between container restarts.

Suggested change
- db-data:/var/lib/postgresql/
- db-data:/var/lib/postgresql/data

Copilot uses AI. Check for mistakes.
DateTime(timezone=True),
nullable=True,
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated_at field uses a Python lambda function for onupdate, but SQLAlchemy's onupdate parameter expects a SQL expression or callable that can be compiled to SQL, not a Python runtime function. This means the updated_at field will not be automatically updated on the database side when records are modified. Consider using onupdate=text("TIMEZONE('utc', CURRENT_TIMESTAMP)") for database-level updates, or handle the timestamp update in the repository layer's update methods.

Suggested change
onupdate=lambda: datetime.now(timezone.utc),
onupdate=utcnow(),

Copilot uses AI. Check for mistakes.
Comment on lines +37 to 44
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
primary_key=True,
index=True,
sa_column_kwargs={"server_default": text("uuidv7()"), "unique": True},
unique=True,
default=uuid_ext_pkg.uuid7,
server_default=text("uuidv7()"),
)
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The id field is defined as Mapped[uuid.UUID] (non-optional) but has both a Python default and a server_default. While this works, the annotation should be Mapped[Optional[uuid.UUID]] to reflect that the value is optional at creation time (SQLAlchemy will provide it). Alternatively, if you want to enforce that the ID is always present after instantiation, the current typing is acceptable, but be aware that before database insertion, the field may be None unless explicitly set.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +17
# Example Pydantic schemas for user data
# from pydantic import BaseModel


# class UserBase(BaseModel):
# name: str
# email: str


# class UserCreate(UserBase):
# pass


# class UserRead(UserBase):
# id: int

# model_config = {"from_attributes": True}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing model_config on Pydantic schemas. For Pydantic schemas that will be used with SQLAlchemy models (e.g., in read/response schemas), you should add model_config = ConfigDict(from_attributes=True) to allow Pydantic to read data from SQLAlchemy model attributes. While the test schemas shown here are only for input (Create/Update), this comment serves as a reminder for future UserRead or similar response schemas.

Copilot uses AI. Check for mistakes.
Comment on lines +183 to 190
class UserRead(PydanticBaseModel):
id: uuid.UUID
email: str
full_name: Optional[str]
is_active: bool
created_at: Optional[datetime]
updated_at: Optional[datetime]
```
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UserRead schema is missing model_config = ConfigDict(from_attributes=True) (or the older model_config = {"from_attributes": True} format shown in the commented example in schemas/init.py). This configuration is necessary to allow Pydantic to read data from SQLAlchemy model instances. Without it, attempting to create a UserRead instance from a User database object will fail.

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +22
class BaseTest(BaseModel):
__tablename__ = "test"

email: str = Field(unique=True, index=True, max_length=255)
name: str = Field(max_length=100)
is_active: bool = Field(default=True)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False)
name: Mapped[str] = mapped_column(String(100), nullable=False)
is_active: Mapped[bool] = mapped_column(default=True, nullable=False)
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BaseTest model defined in this test file will not be automatically included in Base.metadata during the setup_database fixture in conftest.py. This is because the model is defined in the test file and not imported when conftest.py runs. To ensure the test table is created, you should either: 1) Import the BaseTest model in conftest.py before creating tables, or 2) Ensure all test models are imported in the tests' init.py, or 3) Manually create the table in a test fixture. The tests may fail with "relation does not exist" errors without this fix.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +17
# Example Pydantic schemas for user data
# from pydantic import BaseModel


# class UserBase(BaseModel):
# name: str
# email: str


# class UserCreate(UserBase):
# pass


# class UserRead(UserBase):
# id: int

# model_config = {"from_attributes": True}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment appears to contain commented-out code.

Suggested change
# Example Pydantic schemas for user data
# from pydantic import BaseModel
# class UserBase(BaseModel):
# name: str
# email: str
# class UserCreate(UserBase):
# pass
# class UserRead(UserBase):
# id: int
# model_config = {"from_attributes": True}

Copilot uses AI. Check for mistakes.
@GabrielVGS GabrielVGS closed this Jan 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

replace sqlmodel completely with sqlalchemy

2 participants