diff --git a/docs/migration/v2.md b/docs/migration/v2.md new file mode 100644 index 0000000..ccf69cf --- /dev/null +++ b/docs/migration/v2.md @@ -0,0 +1,137 @@ +# Migrating to v2.0 + +This page covers every breaking change introduced in **v2.0** and the steps required to update your code. + +--- + +## CRUD + +### `schema` is now required in `offset_paginate()` and `cursor_paginate()` + +Calls that omit `schema` will now raise a `TypeError` at runtime. + +Previously `schema` was optional; omitting it returned raw SQLAlchemy model instances inside the response. It is now a required keyword argument and the response always contains serialized schema instances. + +=== "Before (`v1`)" + + ```python + # schema omitted — returned raw model instances + result = await UserCrud.offset_paginate(session, page=1) + result = await UserCrud.cursor_paginate(session, cursor=token) + ``` + +=== "Now (`v2`)" + + ```python + result = await UserCrud.offset_paginate(session, page=1, schema=UserRead) + result = await UserCrud.cursor_paginate(session, cursor=token, schema=UserRead) + ``` + +### `as_response` removed from `create()`, `get()`, and `update()` + +Passing `as_response` to these methods will raise a `TypeError` at runtime. + +The `as_response=True` shorthand is replaced by passing a `schema` directly. The return value is a `Response[schema]` when `schema` is provided, or the raw model instance when it is not. + +=== "Before (`v1`)" + + ```python + user = await UserCrud.create(session, data, as_response=True) + user = await UserCrud.get(session, filters, as_response=True) + user = await UserCrud.update(session, data, filters, as_response=True) + ``` + +=== "Now (`v2`)" + + ```python + user = await UserCrud.create(session, data, schema=UserRead) + user = await UserCrud.get(session, filters, schema=UserRead) + user = await UserCrud.update(session, data, filters, schema=UserRead) + ``` + +### `delete()`: `as_response` renamed and return type changed + +`as_response` is gone, and the plain (non-response) call no longer returns `True`. + +Two changes were made to `delete()`: + +1. The `as_response` parameter is renamed to `return_response`. +2. When called without `return_response=True`, the method now returns `None` on success instead of `True`. + +=== "Before (`v1`)" + + ```python + ok = await UserCrud.delete(session, filters) + if ok: # True on success + ... + + response = await UserCrud.delete(session, filters, as_response=True) + ``` + +=== "Now (`v2`)" + + ```python + await UserCrud.delete(session, filters) # returns None + + response = await UserCrud.delete(session, filters, return_response=True) + ``` + +### `paginate()` alias removed + +Any call to `crud.paginate(...)` will raise `AttributeError` at runtime. + +The `paginate` shorthand was an alias for `offset_paginate`. It has been removed; call `offset_paginate` directly. + +=== "Before (`v1`)" + + ```python + result = await UserCrud.paginate(session, page=2, items_per_page=20, schema=UserRead) + ``` + +=== "Now (`v2`)" + + ```python + result = await UserCrud.offset_paginate(session, page=2, items_per_page=20, schema=UserRead) + ``` + +--- + +## Exceptions + +### Missing `api_error` raises `TypeError` at class definition time + +Unfinished or stub exception subclasses that previously compiled fine will now fail on import. + +In `v1`, a subclass without `api_error` would only fail when the exception was raised. In `v2`, `__init_subclass__` validates this at class definition time. + +=== "Before (`v1`)" + + ```python + class MyError(ApiException): + pass # fine until raised + ``` + +=== "Now (`v2`)" + + ```python + class MyError(ApiException): + pass # TypeError: MyError must define an 'api_error' class attribute. + ``` + +For shared base classes that are not meant to be raised directly, use `abstract=True`: + +```python +class BillingError(ApiException, abstract=True): + """Base for all billing-related errors — not raised directly.""" + +class PaymentRequiredError(BillingError): + api_error = ApiError(code=402, msg="Payment Required", desc="...", err_code="BILLING-402") +``` + +--- + +## Schemas + +### `Pagination` alias removed + +`Pagination` was already deprecated in `v1` and is fully removed in `v2`, you now need to use [`OffsetPagination`](../reference/schemas.md#fastapi_toolsets.schemas.OffsetPagination) or [`CursorPagination`](../reference/schemas.md#fastapi_toolsets.schemas.CursorPagination). diff --git a/docs/reference/schemas.md b/docs/reference/schemas.md index 40dcb57..be27c87 100644 --- a/docs/reference/schemas.md +++ b/docs/reference/schemas.md @@ -1,4 +1,4 @@ -# `schemas` module +# `schemas` Here's the reference for all response models and types provided by the `schemas` module. diff --git a/src/fastapi_toolsets/crud/__init__.py b/src/fastapi_toolsets/crud/__init__.py index 59058ad..cb22110 100644 --- a/src/fastapi_toolsets/crud/__init__.py +++ b/src/fastapi_toolsets/crud/__init__.py @@ -1,7 +1,13 @@ """Generic async CRUD operations for SQLAlchemy models.""" from ..exceptions import InvalidFacetFilterError, NoSearchableFieldsError -from ..types import FacetFieldType, JoinType, M2MFieldType, OrderByClause +from ..types import ( + FacetFieldType, + JoinType, + M2MFieldType, + OrderByClause, + SearchFieldType, +) from .factory import CrudFactory from .search import SearchConfig, get_searchable_fields @@ -15,4 +21,5 @@ __all__ = [ "NoSearchableFieldsError", "OrderByClause", "SearchConfig", + "SearchFieldType", ] diff --git a/zensical.toml b/zensical.toml index 46861b6..79fbb03 100644 --- a/zensical.toml +++ b/zensical.toml @@ -77,6 +77,10 @@ md_in_html = {} "pymdownx.tasklist" = {custom_checkbox = true} "pymdownx.tilde" = {} +[project.markdown_extensions.pymdownx.emoji] +emoji_index = "zensical.extensions.emoji.twemoji" +emoji_generator = "zensical.extensions.emoji.to_svg" + [project.markdown_extensions."pymdownx.highlight"] anchor_linenums = true line_spans = "__span" @@ -95,3 +99,47 @@ permalink = true [project.markdown_extensions."pymdownx.snippets"] base_path = ["."] check_paths = true + +[[project.nav]] +Home = "index.md" + +[[project.nav]] +Modules = [ + {CLI = "module/cli.md"}, + {CRUD = "module/crud.md"}, + {Database = "module/db.md"}, + {Dependencies = "module/dependencies.md"}, + {Exceptions = "module/exceptions.md"}, + {Fixtures = "module/fixtures.md"}, + {Logger = "module/logger.md"}, + {Metrics = "module/metrics.md"}, + {Pytest = "module/pytest.md"}, + {Schemas = "module/schemas.md"}, +] + +[[project.nav]] +Reference = [ + {CLI = "reference/cli.md"}, + {CRUD = "reference/crud.md"}, + {Database = "reference/db.md"}, + {Dependencies = "reference/dependencies.md"}, + {Exceptions = "reference/exceptions.md"}, + {Fixtures = "reference/fixtures.md"}, + {Logger = "reference/logger.md"}, + {Metrics = "reference/metrics.md"}, + {Pytest = "reference/pytest.md"}, + {Schemas = "reference/schemas.md"}, +] + +[[project.nav]] +Examples = [ + {"Pagination & Search" = "examples/pagination-search.md"}, +] + +[[project.nav]] +Migration = [ + {"v2.0" = "migration/v2.md"}, +] + +[[project.nav]] +"Changelog ↗" = "https://github.com/d3vyce/fastapi-toolsets/releases"