From 31678935aae34cd751cefe7532db5737e48cb8c8 Mon Sep 17 00:00:00 2001 From: d3vyce <44915747+d3vyce@users.noreply.github.com> Date: Fri, 20 Feb 2026 14:09:01 +0100 Subject: [PATCH] Version 1.0.0 (#80) * docs: fix typos * chore: build docs only when release * Version 1.0.0 --- .github/workflows/docs.yml | 8 ++- docs/module/cli.md | 54 +++++++++++------- docs/module/crud.md | 98 ++++++++++++++++++++++---------- docs/module/db.md | 29 +++++----- docs/module/dependencies.md | 10 ++-- docs/module/exceptions.md | 11 ++-- docs/module/fixtures.md | 30 ++++++---- docs/module/logger.md | 2 + docs/module/metrics.md | 10 +--- docs/module/pytest.md | 45 ++++++++------- docs/module/schemas.md | 15 ++--- pyproject.toml | 2 +- src/fastapi_toolsets/__init__.py | 2 +- uv.lock | 2 +- 14 files changed, 194 insertions(+), 124 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 12ed417..77b3423 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,12 +1,14 @@ name: Documentation + on: - push: - branches: - - main + release: + types: [published] + permissions: contents: read pages: write id-token: write + jobs: deploy: environment: diff --git a/docs/module/cli.md b/docs/module/cli.md index 4cb9ea0..c80a736 100644 --- a/docs/module/cli.md +++ b/docs/module/cli.md @@ -1,6 +1,6 @@ # CLI -Typer-based command-line interface for managing your FastAPI application, with built-in fixture loading. +Typer-based command-line interface for managing your FastAPI application, with built-in fixture commands integration. ## Installation @@ -16,7 +16,7 @@ Typer-based command-line interface for managing your FastAPI application, with b ## Overview -The `cli` module provides a `manager` entry point built with [Typer](https://typer.tiangolo.com/). It auto-discovers fixture commands when a [`FixtureRegistry`](../reference/fixtures.md#fastapi_toolsets.fixtures.registry.FixtureRegistry) and a database context are configured. +The `cli` module provides a `manager` entry point built with [Typer](https://typer.tiangolo.com/). It allow custom commands to be added in addition of the fixture commands when a [`FixtureRegistry`](../reference/fixtures.md#fastapi_toolsets.fixtures.registry.FixtureRegistry) and a database context are configured. ## Configuration @@ -24,24 +24,48 @@ Configure the CLI in your `pyproject.toml`: ```toml [tool.fastapi-toolsets] -cli = "myapp.cli:cli" # optional: your custom Typer app -fixtures = "myapp.fixtures:registry" # FixtureRegistry instance -db_context = "myapp.db:db_context" # async context manager for sessions +cli = "myapp.cli:cli" # Custom Typer app +fixtures = "myapp.fixtures:registry" # FixtureRegistry instance +db_context = "myapp.db:db_context" # Async context manager for sessions ``` -All fields are optional. Without configuration, the `manager` command still works but only includes the built-in commands. +All fields are optional. Without configuration, the `manager` command still works but no command are available. ## Usage ```bash -# List available commands +# Manager commands manager --help -# Load fixtures for a specific context -manager fixtures load --context testing + Usage: manager [OPTIONS] COMMAND [ARGS]... -# Load all fixtures (no context filter) -manager fixtures load + FastAPI utilities CLI. + +╭─ Options ────────────────────────────────────────────────────────────────────────╮ +│ --install-completion Install completion for the current shell. │ +│ --show-completion Show completion for the current shell, to copy it │ +│ or customize the installation. │ +│ --help Show this message and exit. │ +╰──────────────────────────────────────────────────────────────────────────────────╯ +╭─ Commands ───────────────────────────────────────────────────────────────────────╮ +│ check-db │ +│ fixtures Manage database fixtures. │ +╰──────────────────────────────────────────────────────────────────────────────────╯ + +# Fixtures commands +manager fixtures --help + + Usage: manager fixtures [OPTIONS] COMMAND [ARGS]... + + Manage database fixtures. + +╭─ Options ────────────────────────────────────────────────────────────────────────╮ +│ --help Show this message and exit. │ +╰──────────────────────────────────────────────────────────────────────────────────╯ +╭─ Commands ───────────────────────────────────────────────────────────────────────╮ +│ list List all registered fixtures. │ +│ load Load fixtures into the database. │ +╰──────────────────────────────────────────────────────────────────────────────────╯ ``` ## Custom CLI @@ -64,14 +88,6 @@ def hello(): cli = "myapp.cli:cli" ``` -## Entry point - -The `manager` script is registered automatically when the package is installed: - -```bash -manager --help -``` - --- [:material-api: API Reference](../reference/cli.md) diff --git a/docs/module/crud.md b/docs/module/crud.md index dd6d8b0..283cc9d 100644 --- a/docs/module/crud.md +++ b/docs/module/crud.md @@ -1,6 +1,9 @@ # CRUD -Generic async CRUD operations for SQLAlchemy models with search, pagination, and many-to-many support. +Generic async CRUD operations for SQLAlchemy models with search, pagination, and many-to-many support. This module has features that are only compatible with Postgres. + +!!! info + This module has been coded and tested to be compatible with PostgreSQL only. ## Overview @@ -12,10 +15,7 @@ The `crud` module provides [`AsyncCrud`](../reference/crud.md#fastapi_toolsets.c from fastapi_toolsets.crud import CrudFactory from myapp.models import User -UserCrud = CrudFactory( - User, - searchable_fields=[User.username, User.email], -) +UserCrud = CrudFactory(model=User) ``` [`CrudFactory`](../reference/crud.md#fastapi_toolsets.crud.factory.CrudFactory) dynamically creates a class named `AsyncUserCrud` with `User` as its model. @@ -24,51 +24,56 @@ UserCrud = CrudFactory( ```python # Create -user = await UserCrud.create(session, obj=UserCreateSchema(username="alice")) +user = await UserCrud.create(session=session, obj=UserCreateSchema(username="alice")) # Get one (raises NotFoundError if not found) -user = await UserCrud.get(session, filters=[User.id == user_id]) +user = await UserCrud.get(session=session, filters=[User.id == user_id]) # Get first or None -user = await UserCrud.first(session, filters=[User.email == email]) +user = await UserCrud.first(session=session, filters=[User.email == email]) # Get multiple -users = await UserCrud.get_multi(session, filters=[User.is_active == True]) +users = await UserCrud.get_multi(session=session, filters=[User.is_active == True]) # Update -user = await UserCrud.update(session, obj=UserUpdateSchema(username="bob"), filters=[User.id == user_id]) +user = await UserCrud.update(session=session, obj=UserUpdateSchema(username="bob"), filters=[User.id == user_id]) # Delete -await UserCrud.delete(session, filters=[User.id == user_id]) +await UserCrud.delete(session=session, filters=[User.id == user_id]) # Count / exists -count = await UserCrud.count(session, filters=[User.is_active == True]) -exists = await UserCrud.exists(session, filters=[User.email == email]) +count = await UserCrud.count(session=session, filters=[User.is_active == True]) +exists = await UserCrud.exists(session=session, filters=[User.email == email]) ``` ## Pagination ```python -result = await UserCrud.paginate( - session, - filters=[User.is_active == True], - order_by=[User.created_at.desc()], - page=1, - items_per_page=20, - search="alice", - search_fields=[User.username, User.email], +@router.get( + "", + response_model=PaginatedResponse[User], ) -# result.data: list of users -# result.pagination: Pagination(total_count, items_per_page, page, has_more) +async def get_users( + session: SessionDep, + items_per_page: int = 50, + page: int = 1, +): + return await crud.UserCrud.paginate( + session=session, + items_per_page=items_per_page, + page=page, + ) ``` +The [`paginate`](../reference/crud.md#fastapi_toolsets.crud.factory.AsyncCrud.paginate) function will return a [`PaginatedResponse`](../reference/schemas.md#fastapi_toolsets.schemas.PaginatedResponse). + ## Search Declare searchable fields on the CRUD class. Relationship traversal is supported via tuples: ```python PostCrud = CrudFactory( - Post, + model=Post, searchable_fields=[ Post.title, Post.content, @@ -77,18 +82,38 @@ PostCrud = CrudFactory( ) ``` +This allow to do a search with the [`paginate`](../reference/crud.md#fastapi_toolsets.crud.factory.AsyncCrud.paginate) function: + +```python +@router.get( + "", + response_model=PaginatedResponse[User], +) +async def get_users( + session: SessionDep, + items_per_page: int = 50, + page: int = 1, + search: str | None = None, +): + return await crud.UserCrud.paginate( + session=session, + items_per_page=items_per_page, + page=page, + search=search, + ) +``` + ## Many-to-many relationships Use `m2m_fields` to map schema fields containing lists of IDs to SQLAlchemy relationships. The CRUD class resolves and validates all IDs before persisting: ```python PostCrud = CrudFactory( - Post, + model=Post, m2m_fields={"tag_ids": Post.tags}, ) -# schema: PostCreateSchema(title="Hello", tag_ids=[1, 2, 3]) -post = await PostCrud.create(session, obj=PostCreateSchema(...)) +post = await PostCrud.create(session=session, obj=PostCreateSchema(title="Hello", tag_ids=[1, 2, 3])) ``` ## Upsert @@ -97,7 +122,7 @@ Atomic `INSERT ... ON CONFLICT DO UPDATE` using PostgreSQL: ```python await UserCrud.upsert( - session, + session=session, obj=UserCreateSchema(email="alice@example.com", username="alice"), index_elements=[User.email], set_={"username"}, @@ -106,11 +131,22 @@ await UserCrud.upsert( ## `as_response` -Pass `as_response=True` to any write operation to get a [`Response[ModelType]`](../reference/schemas.md#fastapi_toolsets.schemas.Response) back directly: +Pass `as_response=True` to any write operation to get a [`Response[ModelType]`](../reference/schemas.md#fastapi_toolsets.schemas.Response) back directly for API usage: ```python -response = await UserCrud.create(session, obj=schema, as_response=True) -# response: Response[User] +@router.get( + "/{uuid}", + response_model=Response[User], + responses=generate_error_responses(NotFoundError), +) +async def get_user(session: SessionDep, uuid: UUID): + return await crud.UserCrud.get( + session=session, + filters=[User.id == uuid], + as_response=True, + ) ``` +--- + [:material-api: API Reference](../reference/crud.md) diff --git a/docs/module/db.md b/docs/module/db.md index a5dc633..5116369 100644 --- a/docs/module/db.md +++ b/docs/module/db.md @@ -2,9 +2,12 @@ SQLAlchemy async session management with transactions, table locking, and row-change polling. +!!! info + This module has been coded and tested to be compatible with PostgreSQL only. + ## Overview -The `db` module provides helpers to create FastAPI dependencies and context managers for `AsyncSession`, along with utilities for nested transactions, PostgreSQL advisory locks, and polling for row changes. +The `db` module provides helpers to create FastAPI dependencies and context managers for `AsyncSession`, along with utilities for nested transactions, table lock and polling for row changes. ## Session dependency @@ -14,10 +17,10 @@ Use [`create_db_dependency`](../reference/db.md#fastapi_toolsets.db.create_db_de from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker from fastapi_toolsets.db import create_db_dependency -engine = create_async_engine("postgresql+asyncpg://...") -session_maker = async_sessionmaker(engine) +engine = create_async_engine(url="postgresql+asyncpg://...", future=True) +session_maker = async_sessionmaker(bind=engine, expire_on_commit=False) -get_db = create_db_dependency(session_maker) +get_db = create_db_dependency(session_maker=session_maker) @router.get("/users") async def list_users(session: AsyncSession = Depends(get_db)): @@ -31,11 +34,11 @@ Use [`create_db_context`](../reference/db.md#fastapi_toolsets.db.create_db_conte ```python from fastapi_toolsets.db import create_db_context -db_context = create_db_context(session_maker) +db_context = create_db_context(session_maker=session_maker) async def seed(): async with db_context() as session: - session.add(User(name="admin")) + ... ``` ## Nested transactions @@ -45,11 +48,11 @@ async def seed(): ```python from fastapi_toolsets.db import get_transaction -async def create_user_with_role(session): - async with get_transaction(session): - session.add(role) - async with get_transaction(session): # uses savepoint - session.add(user) +async def create_user_with_role(session=session): + async with get_transaction(session=session): + ... + async with get_transaction(session=session): # uses savepoint + ... ``` ## Table locking @@ -59,7 +62,7 @@ async def create_user_with_role(session): ```python from fastapi_toolsets.db import lock_tables -async with lock_tables(session, tables=[User], mode="EXCLUSIVE"): +async with lock_tables(session=session, tables=[User], mode="EXCLUSIVE"): # No other transaction can modify User until this block exits ... ``` @@ -75,7 +78,7 @@ from fastapi_toolsets.db import wait_for_row_change # Wait up to 30s for order.status to change await wait_for_row_change( - session, + session=session, model=Order, pk_value=order_id, columns=[Order.status], diff --git a/docs/module/dependencies.md b/docs/module/dependencies.md index bd83ecc..4097718 100644 --- a/docs/module/dependencies.md +++ b/docs/module/dependencies.md @@ -13,17 +13,17 @@ The `dependencies` module provides two factory functions that create FastAPI dep ```python from fastapi_toolsets.dependencies import PathDependency -UserDep = PathDependency(User, User.id, session_dep=get_db) +UserDep = PathDependency(model=User, field=User.id, session_dep=get_db) @router.get("/users/{user_id}") async def get_user(user: User = UserDep): return user ``` -The parameter name is inferred from the field (`user_id` for `User.id`). You can override it: +By default the parameter name is inferred from the field (`user_id` for `User.id`). You can override it: ```python -UserDep = PathDependency(User, User.id, session_dep=get_db, param_name="id") +UserDep = PathDependency(model=User, field=User.id, session_dep=get_db, param_name="id") @router.get("/users/{id}") async def get_user(user: User = UserDep): @@ -37,7 +37,7 @@ async def get_user(user: User = UserDep): ```python from fastapi_toolsets.dependencies import BodyDependency -RoleDep = BodyDependency(Role, Role.id, session_dep=get_db, body_field="role_id") +RoleDep = BodyDependency(model=Role, field=Role.id, session_dep=get_db, body_field="role_id") @router.post("/users") async def create_user(body: UserCreateSchema, role: Role = RoleDep): @@ -45,4 +45,6 @@ async def create_user(body: UserCreateSchema, role: Role = RoleDep): ... ``` +--- + [:material-api: API Reference](../reference/dependencies.md) diff --git a/docs/module/exceptions.md b/docs/module/exceptions.md index 55a02fe..171d5df 100644 --- a/docs/module/exceptions.md +++ b/docs/module/exceptions.md @@ -4,7 +4,7 @@ Structured API exceptions with consistent error responses and automatic OpenAPI ## Overview -The `exceptions` module provides a set of pre-built HTTP exceptions and a FastAPI exception handler that formats all errors — including validation errors — into a uniform [`ErrorResponse`](../reference/schemas.md#fastapi_toolsets.schemas.ErrorResponse) shape. +The `exceptions` module provides a set of pre-built HTTP exceptions and a FastAPI exception handler that formats all errors — including validation errors — into a uniform [`ErrorResponse`](../reference/schemas.md#fastapi_toolsets.schemas.ErrorResponse). ## Setup @@ -15,7 +15,7 @@ from fastapi import FastAPI from fastapi_toolsets.exceptions import init_exceptions_handlers app = FastAPI() -init_exceptions_handlers(app) +init_exceptions_handlers(app=app) ``` This registers handlers for: @@ -36,11 +36,11 @@ This registers handlers for: | [`NoSearchableFieldsError`](../reference/exceptions.md#fastapi_toolsets.exceptions.exceptions.NoSearchableFieldsError) | 400 | No searchable fields | ```python -from fastapi_toolsets.exceptions import NotFoundError, ForbiddenError +from fastapi_toolsets.exceptions import NotFoundError @router.get("/users/{id}") async def get_user(id: int, session: AsyncSession = Depends(get_db)): - user = await UserCrud.first(session, filters=[User.id == id]) + user = await UserCrud.first(session=session, filters=[User.id == id]) if not user: raise NotFoundError return user @@ -77,6 +77,9 @@ from fastapi_toolsets.exceptions import generate_error_responses, NotFoundError, async def get_user(...): ... ``` +!!! info + The pydantic validation error is automatically added by FastAPI. + --- [:material-api: API Reference](../reference/exceptions.md) diff --git a/docs/module/fixtures.md b/docs/module/fixtures.md index ef6e963..e3a1005 100644 --- a/docs/module/fixtures.md +++ b/docs/module/fixtures.md @@ -32,22 +32,22 @@ Dependencies declared via `depends_on` are resolved topologically — `roles` wi ## Loading fixtures -### By context +By context with [`load_fixtures_by_context`](../reference/fixtures.md#fastapi_toolsets.fixtures.utils.load_fixtures_by_context): ```python from fastapi_toolsets.fixtures import load_fixtures_by_context async with db_context() as session: - await load_fixtures_by_context(session, registry=fixtures, context=Context.TESTING) + await load_fixtures_by_context(session=session, registry=fixtures, context=Context.TESTING) ``` -### Directly +Directly with [`load_fixtures`](../reference/fixtures.md#fastapi_toolsets.fixtures.utils.load_fixtures): ```python from fastapi_toolsets.fixtures import load_fixtures async with db_context() as session: - await load_fixtures(session, registry=fixtures) + await load_fixtures(session=session, registry=fixtures) ``` ## Contexts @@ -60,7 +60,7 @@ async with db_context() as session: | `Context.TESTING` | Data only loaded during tests | | `Context.PRODUCTION` | Data only loaded in production | -A fixture with no `contexts` argument is loaded in all contexts. +A fixture with no `contexts` defined takes `Context.BASE` by default. ## Load strategies @@ -72,9 +72,21 @@ A fixture with no `contexts` argument is loaded in all contexts. | `LoadStrategy.UPSERT` | Insert or update on conflict | | `LoadStrategy.SKIP` | Skip rows that already exist | +## Merging registries + +Split fixtures definitions across modules and merge them: + +```python +from myapp.fixtures.dev import dev_fixtures +from myapp.fixtures.prod import prod_fixtures + +fixtures = fixturesRegistry() +fixtures.include_registry(registry=dev_fixtures) +fixtures.include_registry(registry=prod_fixtures) + ## Pytest integration -Use [`register_fixtures`](../reference/pytest.md#fastapi_toolsets.pytest.plugin.register_fixtures) to expose each fixture in your registry as an injectable pytest fixture named `fixture_{name}`: +Use [`register_fixtures`](../reference/pytest.md#fastapi_toolsets.pytest.plugin.register_fixtures) to expose each fixture in your registry as an injectable pytest fixture named `fixture_{name}` by default: ```python # conftest.py @@ -95,10 +107,8 @@ register_fixtures(registry=registry, namespace=globals()) ```python # test_users.py -async def test_user_can_login(fixture_users, fixture_roles, client): - # fixture_roles is loaded first (dependency), then fixture_users - response = await client.post("/auth/login", json={"username": "alice"}) - assert response.status_code == 200 +async def test_user_can_login(fixture_users: list[User], fixture_roles: list[Role]): + ... ``` diff --git a/docs/module/logger.md b/docs/module/logger.md index ba5e943..af263ce 100644 --- a/docs/module/logger.md +++ b/docs/module/logger.md @@ -34,4 +34,6 @@ When called without arguments, [`get_logger`](../reference/logger.md#fastapi_too logger = get_logger() ``` +--- + [:material-api: API Reference](../reference/logger.md) diff --git a/docs/module/metrics.md b/docs/module/metrics.md index 6827219..84bc2cf 100644 --- a/docs/module/metrics.md +++ b/docs/module/metrics.md @@ -27,7 +27,7 @@ from fastapi_toolsets.metrics import MetricsRegistry, init_metrics app = FastAPI() metrics = MetricsRegistry() -init_metrics(app, registry=metrics) +init_metrics(app=app, registry=metrics) ``` This mounts the `/metrics` endpoint that Prometheus can scrape. @@ -70,8 +70,8 @@ from myapp.metrics.http import http_metrics from myapp.metrics.db import db_metrics metrics = MetricsRegistry() -metrics.include_registry(http_metrics) -metrics.include_registry(db_metrics) +metrics.include_registry(registry=http_metrics) +metrics.include_registry(registry=db_metrics) ``` ## Multi-process mode @@ -81,10 +81,6 @@ Multi-process support is enabled automatically when the `PROMETHEUS_MULTIPROC_DI !!! warning "Environment variable name" The correct variable is `PROMETHEUS_MULTIPROC_DIR` (not `PROMETHEUS_MULTIPROCESS_DIR`). -```bash -export PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus -``` - --- [:material-api: API Reference](../reference/metrics.md) diff --git a/docs/module/pytest.md b/docs/module/pytest.md index 3e62c20..8bac694 100644 --- a/docs/module/pytest.md +++ b/docs/module/pytest.md @@ -26,8 +26,15 @@ Use [`create_async_client`](../reference/pytest.md#fastapi_toolsets.pytest.utils from fastapi_toolsets.pytest import create_async_client @pytest.fixture -async def client(app): - async with create_async_client(app=app) as c: +async def http_client(db_session): + async def _override_get_db(): + yield db_session + + async with create_async_client( + app=app, + base_url="http://127.0.0.1/api/v1", + dependency_overrides={get_db: _override_get_db}, + ) as c: yield c ``` @@ -36,36 +43,34 @@ async def client(app): Use [`create_db_session`](../reference/pytest.md#fastapi_toolsets.pytest.utils.create_db_session) to create an isolated `AsyncSession` for a test: ```python -from fastapi_toolsets.pytest import create_db_session +from fastapi_toolsets.pytest import create_db_session, create_worker_database -@pytest.fixture -async def db_session(): - async with create_db_session(database_url=DATABASE_URL, base=Base, cleanup=True) as session: - yield session -``` - -## Parallel testing with pytest-xdist - -When running tests in parallel, each worker needs its own database. Use these helpers to create and identify worker databases: - -```python -from fastapi_toolsets.pytest import create_worker_database, create_db_session - -# In conftest.py session-scoped fixture @pytest.fixture(scope="session") async def worker_db_url(): - async with create_worker_database(database_url=DATABASE_URL) as url: + async with create_worker_database( + database_url=str(settings.SQLALCHEMY_DATABASE_URI) + ) as url: yield url + @pytest.fixture async def db_session(worker_db_url): - async with create_db_session(database_url=worker_db_url, base=Base, cleanup=True) as session: + async with create_db_session( + database_url=worker_db_url, base=Base, cleanup=True + ) as session: yield session ``` +!!! info + In this example, the database is reset between each test using the argument `cleanup=True`. + +## Parallel testing with pytest-xdist + +The examples above are already compatible with parallel test execution with `pytest-xdist`. + ## Cleaning up tables -[`cleanup_tables`](../reference/pytest.md#fastapi_toolsets.pytest.utils.cleanup_tables) truncates all tables between tests for fast isolation: +If you want to manually clean up a database you can use [`cleanup_tables`](../reference/pytest.md#fastapi_toolsets.pytest.utils.cleanup_tables), this will truncates all tables between tests for fast isolation: ```python from fastapi_toolsets.pytest import cleanup_tables diff --git a/docs/module/schemas.md b/docs/module/schemas.md index 0d6924f..3e0b670 100644 --- a/docs/module/schemas.md +++ b/docs/module/schemas.md @@ -8,7 +8,7 @@ The `schemas` module provides generic response wrappers that enforce a uniform r ## Response models -### `Response[T]` +### [`Response[T]`](../reference/schemas.md#fastapi_toolsets.schemas.Response) The most common wrapper for a single resource response. @@ -20,7 +20,7 @@ async def get_user(user: User = UserDep) -> Response[UserSchema]: return Response(data=user, message="User retrieved") ``` -### `PaginatedResponse[T]` +### [`PaginatedResponse[T]`](../reference/schemas.md#fastapi_toolsets.schemas.PaginatedResponse) Wraps a list of items with pagination metadata. @@ -40,15 +40,10 @@ async def list_users() -> PaginatedResponse[UserSchema]: ) ``` -### `ErrorResponse` +### [`ErrorResponse`](../reference/schemas.md#fastapi_toolsets.schemas.ErrorResponse) -Returned automatically by the exceptions handler. Can also be used as a response model for OpenAPI docs. +Returned automatically by the exceptions handler. -```python -from fastapi_toolsets.schemas import ErrorResponse - -@router.delete("/users/{id}", responses={404: {"model": ErrorResponse}}) -async def delete_user(...): ... -``` +--- [:material-api: API Reference](../reference/schemas.md) diff --git a/pyproject.toml b/pyproject.toml index 27eda15..7618e57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "fastapi-toolsets" -version = "0.10.0" +version = "1.0.0" description = "Reusable tools for FastAPI: async CRUD, fixtures, CLI, and standardized responses for SQLAlchemy + PostgreSQL" readme = "README.md" license = "MIT" diff --git a/src/fastapi_toolsets/__init__.py b/src/fastapi_toolsets/__init__.py index a2bdb22..f5091c6 100644 --- a/src/fastapi_toolsets/__init__.py +++ b/src/fastapi_toolsets/__init__.py @@ -21,4 +21,4 @@ Example usage: return Response(data={"user": user.username}, message="Success") """ -__version__ = "0.10.0" +__version__ = "1.0.0" diff --git a/uv.lock b/uv.lock index 5fa1cc0..1f81f56 100644 --- a/uv.lock +++ b/uv.lock @@ -251,7 +251,7 @@ wheels = [ [[package]] name = "fastapi-toolsets" -version = "0.10.0" +version = "1.0.0" source = { editable = "." } dependencies = [ { name = "asyncpg" },