From 73fae04333565f1f3e073b2ea4e5ffdfb2026224 Mon Sep 17 00:00:00 2001 From: d3vyce <44915747+d3vyce@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:49:57 +0100 Subject: [PATCH] chore: cleanup before v1 (#73) * chore: move dependencies module to the project root * chore:update README * chore: clean conftest * chore: remove old code + comment * fix: uv.lock dependencies --- README.md | 2 +- .../factory.py => dependencies.py} | 4 +- src/fastapi_toolsets/dependencies/__init__.py | 5 -- src/fastapi_toolsets/exceptions/exceptions.py | 49 ----------------- src/fastapi_toolsets/pytest/plugin.py | 53 +------------------ tests/conftest.py | 26 ++------- uv.lock | 12 ++--- 7 files changed, 12 insertions(+), 139 deletions(-) rename src/fastapi_toolsets/{dependencies/factory.py => dependencies.py} (98%) delete mode 100644 src/fastapi_toolsets/dependencies/__init__.py diff --git a/README.md b/README.md index 480d0fd..cb7e56a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # FastAPI Toolsets -FastAPI Toolsets provides production-ready utilities for FastAPI applications built with async SQLAlchemy and PostgreSQL. It includes generic CRUD operations, a fixture system with dependency resolution, a Django-like CLI, standardized API responses, and structured exception handling with automatic OpenAPI documentation. +A modular collection of production-ready utilities for FastAPI. Install only what you need — from async CRUD and database helpers to CLI tooling, Prometheus metrics, and pytest fixtures. Each module is independently installable via optional extras, keeping your dependency footprint minimal. [![CI](https://github.com/d3vyce/fastapi-toolsets/actions/workflows/ci.yml/badge.svg)](https://github.com/d3vyce/fastapi-toolsets/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/d3vyce/fastapi-toolsets/graph/badge.svg)](https://codecov.io/gh/d3vyce/fastapi-toolsets) diff --git a/src/fastapi_toolsets/dependencies/factory.py b/src/fastapi_toolsets/dependencies.py similarity index 98% rename from src/fastapi_toolsets/dependencies/factory.py rename to src/fastapi_toolsets/dependencies.py index 10f4c4c..f2a4b15 100644 --- a/src/fastapi_toolsets/dependencies/factory.py +++ b/src/fastapi_toolsets/dependencies.py @@ -8,7 +8,9 @@ from fastapi import Depends from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import DeclarativeBase -from ..crud import CrudFactory +from .crud import CrudFactory + +__all__ = ["BodyDependency", "PathDependency"] ModelType = TypeVar("ModelType", bound=DeclarativeBase) SessionDependency = Callable[[], AsyncGenerator[AsyncSession, None]] diff --git a/src/fastapi_toolsets/dependencies/__init__.py b/src/fastapi_toolsets/dependencies/__init__.py deleted file mode 100644 index b31c5c5..0000000 --- a/src/fastapi_toolsets/dependencies/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""FastAPI dependency factories for database objects.""" - -from .factory import BodyDependency, PathDependency - -__all__ = ["BodyDependency", "PathDependency"] diff --git a/src/fastapi_toolsets/exceptions/exceptions.py b/src/fastapi_toolsets/exceptions/exceptions.py index 7b40cb9..248a6a6 100644 --- a/src/fastapi_toolsets/exceptions/exceptions.py +++ b/src/fastapi_toolsets/exceptions/exceptions.py @@ -76,55 +76,6 @@ class ConflictError(ApiException): ) -class InsufficientRolesError(ForbiddenError): - """User does not have the required roles.""" - - api_error = ApiError( - code=403, - msg="Insufficient Roles", - desc="You do not have the required roles to access this resource.", - err_code="RBAC-403", - ) - - def __init__(self, required_roles: list[str], user_roles: set[str] | None = None): - """Initialize the exception. - - Args: - required_roles: Roles needed to access the resource - user_roles: Roles the current user has, if known - """ - self.required_roles = required_roles - self.user_roles = user_roles - - desc = f"Required roles: {', '.join(required_roles)}" - if user_roles is not None: - desc += f". User has: {', '.join(user_roles) if user_roles else 'no roles'}" - - super().__init__(desc) - - -class UserNotFoundError(NotFoundError): - """User was not found.""" - - api_error = ApiError( - code=404, - msg="User Not Found", - desc="The requested user was not found.", - err_code="USER-404", - ) - - -class RoleNotFoundError(NotFoundError): - """Role was not found.""" - - api_error = ApiError( - code=404, - msg="Role Not Found", - desc="The requested role was not found.", - err_code="ROLE-404", - ) - - class NoSearchableFieldsError(ApiException): """Raised when search is requested but no searchable fields are available.""" diff --git a/src/fastapi_toolsets/pytest/plugin.py b/src/fastapi_toolsets/pytest/plugin.py index e10a770..bc1dc81 100644 --- a/src/fastapi_toolsets/pytest/plugin.py +++ b/src/fastapi_toolsets/pytest/plugin.py @@ -1,55 +1,4 @@ -"""Pytest plugin for using FixtureRegistry fixtures in tests. - -This module provides utilities to automatically generate pytest fixtures -from your FixtureRegistry, with proper dependency resolution. - -Example: - # conftest.py - import pytest - from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine - - from app.fixtures import fixtures # Your FixtureRegistry - from app.models import Base - from fastapi_toolsets.pytest_plugin import register_fixtures - - DATABASE_URL = "postgresql+asyncpg://postgres:postgres@localhost:5432/test_db" - - @pytest.fixture - async def engine(): - engine = create_async_engine(DATABASE_URL) - yield engine - await engine.dispose() - - @pytest.fixture - async def db_session(engine): - async with engine.begin() as conn: - await conn.run_sync(Base.metadata.create_all) - - session_factory = async_sessionmaker(engine, expire_on_commit=False) - session = session_factory() - - try: - yield session - finally: - await session.close() - async with engine.begin() as conn: - await conn.run_sync(Base.metadata.drop_all) - - # Automatically generate pytest fixtures from registry - # Creates: fixture_roles, fixture_users, fixture_posts, etc. - register_fixtures(fixtures, globals()) - -Usage in tests: - # test_users.py - async def test_user_count(db_session, fixture_users): - # fixture_users automatically loads fixture_roles first (if dependency) - # and returns the list of User models - assert len(fixture_users) > 0 - - async def test_user_role(db_session, fixture_users): - user = fixture_users[0] - assert user.role_id is not None -""" +"""Pytest plugin for using FixtureRegistry fixtures in tests.""" from collections.abc import Callable, Sequence from typing import Any diff --git a/tests/conftest.py b/tests/conftest.py index bd4d09d..c0a8db4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,18 +11,12 @@ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship from fastapi_toolsets.crud import CrudFactory -# PostgreSQL connection URL from environment or default for local development -DATABASE_URL = os.getenv("DATABASE_URL") or os.getenv( - "TEST_DATABASE_URL", - "postgresql+asyncpg://postgres:postgres@localhost:5432/fastapi_toolsets_test", +DATABASE_URL = os.getenv( + key="DATABASE_URL", + default="postgresql+asyncpg://postgres:postgres@localhost:5432/postgres", ) -# ============================================================================= -# Test Models -# ============================================================================= - - class Base(DeclarativeBase): """Base class for test models.""" @@ -89,11 +83,6 @@ class Post(Base): tags: Mapped[list[Tag]] = relationship(secondary=post_tags) -# ============================================================================= -# Test Schemas -# ============================================================================= - - class RoleCreate(BaseModel): """Schema for creating a role.""" @@ -171,10 +160,6 @@ class PostM2MUpdate(BaseModel): tag_ids: list[uuid.UUID] | None = None -# ============================================================================= -# CRUD Classes -# ============================================================================= - RoleCrud = CrudFactory(Role) UserCrud = CrudFactory(User) PostCrud = CrudFactory(Post) @@ -182,11 +167,6 @@ TagCrud = CrudFactory(Tag) PostM2MCrud = CrudFactory(Post, m2m_fields={"tag_ids": Post.tags}) -# ============================================================================= -# Fixtures -# ============================================================================= - - @pytest.fixture def anyio_backend(): """Use asyncio for async tests.""" diff --git a/uv.lock b/uv.lock index 5a4437f..ecb911d 100644 --- a/uv.lock +++ b/uv.lock @@ -245,6 +245,7 @@ name = "fastapi-toolsets" version = "0.10.0" source = { editable = "." } dependencies = [ + { name = "asyncpg" }, { name = "fastapi" }, { name = "pydantic" }, { name = "sqlalchemy", extra = ["asyncio"] }, @@ -252,21 +253,16 @@ dependencies = [ [package.optional-dependencies] all = [ - { name = "asyncpg" }, { name = "httpx" }, { name = "prometheus-client" }, { name = "pytest" }, { name = "pytest-xdist" }, { name = "typer" }, ] -asyncpg = [ - { name = "asyncpg" }, -] cli = [ { name = "typer" }, ] dev = [ - { name = "asyncpg" }, { name = "coverage" }, { name = "httpx" }, { name = "prometheus-client" }, @@ -297,11 +293,11 @@ test = [ [package.metadata] requires-dist = [ - { name = "asyncpg", marker = "extra == 'asyncpg'", specifier = ">=0.29.0" }, + { name = "asyncpg", specifier = ">=0.29.0" }, { name = "coverage", marker = "extra == 'test'", specifier = ">=7.0.0" }, { name = "fastapi", specifier = ">=0.100.0" }, { name = "fastapi-toolsets", extras = ["all", "test"], marker = "extra == 'dev'" }, - { name = "fastapi-toolsets", extras = ["asyncpg", "cli", "metrics", "pytest"], marker = "extra == 'all'" }, + { name = "fastapi-toolsets", extras = ["cli", "metrics", "pytest"], marker = "extra == 'all'" }, { name = "fastapi-toolsets", extras = ["pytest"], marker = "extra == 'test'" }, { name = "httpx", marker = "extra == 'pytest'", specifier = ">=0.25.0" }, { name = "prometheus-client", marker = "extra == 'metrics'", specifier = ">=0.20.0" }, @@ -315,7 +311,7 @@ requires-dist = [ { name = "ty", marker = "extra == 'dev'", specifier = ">=0.0.1a0" }, { name = "typer", marker = "extra == 'cli'", specifier = ">=0.9.0" }, ] -provides-extras = ["asyncpg", "cli", "metrics", "pytest", "all", "test", "dev"] +provides-extras = ["cli", "metrics", "pytest", "all", "test", "dev"] [[package]] name = "greenlet"