Skip to content

# 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`)Now (`v2`)

    # schema omitted — returned raw model instances
    result = await UserCrud.offset_paginate(session=session, page=1)
    result = await UserCrud.cursor_paginate(session=session, cursor=token)

    result = await UserCrud.offset_paginate(session=session, page=1, schema=UserRead)
    result = await UserCrud.cursor_paginate(session=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`)Now (`v2`)

    user = await UserCrud.create(session=session, obj=data, as_response=True)
    user = await UserCrud.get(session=session, filters=filters, as_response=True)
    user = await UserCrud.update(session=session, obj=data, filters, as_response=True)

    user = await UserCrud.create(session=session, obj=data, schema=UserRead)
    user = await UserCrud.get(session=session, filters=filters, schema=UserRead)
    user = await UserCrud.update(session=session, obj=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`)Now (`v2`)

    ok = await UserCrud.delete(session=session, filters=filters)
    if ok:  # True on success
        ...

    response = await UserCrud.delete(session=session, filters=filters, as_response=True)

    await UserCrud.delete(session=session, filters=filters)  # returns None

    response = await UserCrud.delete(session=session, filters=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`)Now (`v2`)

    result = await UserCrud.paginate(session=session, page=2, items_per_page=20, schema=UserRead)

    result = await UserCrud.offset_paginate(session=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`)Now (`v2`)

    class MyError(ApiException):
        pass  # fine until raised

    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`:

    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` or `CursorPagination`.