fastapi-paginator 0.1.4
Paginator for FastAPI
License: BSD-2-Clause
Requires Python: >=3.10,<4.0
Keywords: paginator,fastapi,sqlalchemy

![Tests](https://github.com/JGoutin/fastapi_paginator/workflows/tests/badge.svg)
[![codecov](https://codecov.io/gh/JGoutin/fastapi_paginator/branch/main/graph/badge.svg?token=QR5nYeX11F)](https://codecov.io/gh/JGoutin/fastapi_paginator)
[![PyPI](https://img.shields.io/pypi/v/fastapi_paginator.svg)](https://pypi.org/project/fastapi_paginator)

# FastAPI Paginator

Easy to use paginator for [FastAPI](https://fastapi.tiangolo.com/)

Currently, supports only [encode/databases](https://github.com/encode/databases) as 
database library and tested with SQlite and PostgreSQL.

## Features

* Simple FastAPI integration.
* Navigation with page numbers (With total page count returned on first page).
* Navigation from a specific row (since).
* Ordering result (On multiple columns).
* Filtering result using various SQL functions.

## Installation

FastAPI Paginator is available on PyPI, so it can be installed like any other Python 
package.

Example with Pip:
```bash
pip install fastapi_paginator
```

## Usage

### Routes creations in FastAPI

To use it, you only need to create a `fastapi_paginator.Paginator` instance linked to
the database and routes
using `fastapi_paginator.PageParameters` and `fastapi_paginator.Page`.

```python
import databases
import fastapi
import pydantic
import sqlalchemy
import fastapi_paginator

# Already existing database, FastAPI application, "item" table, and "item" model
database = databases.Database(f"sqlite:///local.db}")
app = fastapi.FastAPI()

table = sqlalchemy.Table(
    "table",
    sqlalchemy.MetaData(),
    Column("id", sqlalchemy.Integer, primary_key=True),
    Column("name", sqlalchemy.String, nullable=False),
)

class Item(pydantic.BaseModel):
    """Item in database."""

    class Config:
        """Config."""

        orm_mode = True  # Required

    id: int
    name: str


# Create a paginator for the database (Required only once per database)
paginator = fastapi_paginator.Paginator(database)

# Create a paginated route
@app.get("/list")
async def list_data(
    page_parameters: fastapi_paginator.PageParameters = Depends(),
) -> fastapi_paginator.Page[Item]:
    """List data with pagination."""
    return await paginator(table.select(), Item, page_parameters)
```

### Paginated routes usage from clients


#### Request
Paginator parameters are passed as query parameters, for instance:

```http request
GET /list?order_by=id&page=2
```

##### Query parameters

###### page
The page to return.

When page is not specified or equal to `1`, the request returns `total_page` that is
the maximum number of pages.

*Cannot be used with `since`.*

###### since

The item from where starting to return the result.

When navigating between successive pages, the `next_since` returned value should be used
as `since` for the subsequent requests.

*Cannot be used with `page`*.

*Cannot be used with `order_by` if not ordering on the field used by `since`*.

###### order_by
Sort the resulting items by the specified field name.

Order is descending if `-` is added before the field name, else order is ascending.

This query parameter can be specified multiple time to sort by multiple columns.

**Example:**
"Ordering descending by the `created_at` column: `order_by=-created_at`

###### filter_by

Filter the resulting items.

The query must be in the form `field_name operator argument`, with:
  * `field_name`:  the name on the field on where apply the filter.
  * `operator`:  one operator from the list bellow.
  * `argument`: is the operator argument, it can be one or more value separated by `,`
    (Depending on the operator), valid values must be a primitive JSON type like 
    numbers, double-quoted strings, `true`, `false` and `null`.

This query parameter can be specified multiple time to filter on more criteria
(Using AND logical conjunction).

Available operators:
  * `=`: Equal to a single value (Also supports `null`, `true` and `false`)
  * `<`: Lower than a single value.
  * `<=`: Lower or equal than a single value.
  * `>`: Greater than a single value.
  * `>=`: Greater or equal than a single value.
  * `between`: Between a pair of values (`value_1` <= `field_value` <= `value_2`).
  * `in`: Present in a list of one or more values.
  * `like`: Like a single value (`%` can be used as wildcard for zero to multiple
     characters, `_` as wildcard for a single character, `/` can be used as escape
     character for `%` and `_`).
  * `ilike`: Same as `like`, but case insensitive.
  * `startswith`: String representation starts with a single value.
  * `endswith`: String representation ends with a single value.
  * `contains`: String representation contains a single value.

Any operator can be negated by adding `!` in front of it.

*Warning*: Depending on your HTTP client, the query parameter value may require to be
URL encoded.

**Example:**
Returning only data with a `name` field that does not start with
`Product`: `filter_by=name%20%21like%20%22Product%25%22`
(With URL encoded value of: `name !like "Product%"`')

##### Response

The response is a JSON dictionnary with the following fields:
* `items`: The list returned items.
* `next_since`: Next value to use with `since` query parameter.
* `next_page`: Next value to use with `page` query parameter.
* `total_pages`: Total pages, only computed and returned when on page 1

### Using alternates JSON libraries

It is possible to override the `json.loads` function used in all paginator as follows 
(Example with [orjson](https://github.com/ijl/orjson)):

```python
import orjson
import fastapi_paginator


fastapi_paginator.Paginator.json_loads = orjson.loads
```