tortoise-serializer


Nametortoise-serializer JSON
Version 1.6.0 PyPI version JSON
download
home_pageNone
SummaryPydantic serialization for tortoise-orm
upload_time2025-07-25 10:13:03
maintainerNone
docs_urlNone
authorSebastien Nicolet
requires_python<4.0,>=3.12
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Tortoise Serializer
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/b801e7563fb34f76a27aeae4d38f2853)](https://app.codacy.com/gh/Chr0nos/tortoise_serializer/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/b801e7563fb34f76a27aeae4d38f2853)](https://app.codacy.com/gh/Chr0nos/tortoise_serializer/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)

## Motivation
This project was created to address some of the limitations of `pydantic_model_creator`, including:
- The ability to use a `context` in serialization at the field level.
- Access to the actual Tortoise `Model` instance during serialization.
- Improved readability.
- Support for adding extra logic to specific serializers.
- The ability to document fields in a way that is visible in Swagger.

## Usefull readings
- https://docs.pydantic.dev/latest/
- https://tortoise.github.io/


## Installation
```shell
pip add tortoise-serializer
```

## Core concept
A `Serializer` does not need to know which model it will serialize. For example:
```python
from tortoise_serializer import Serializer


class ItemByNameSerializer(Serializer):
    id: int
    name: str


products = await ItemByNameSerializer.from_queryset(Product.all())
users = await ItemByNameSerializer.from_queryset(User.all())

```
This is entirely valid.

`Serializers` are `pydantic.BaseModel` objects, which means you can directly return them from FastAPI endpoints or use any functionality provided by BaseModel.


## Usage
### Reading
```python
from tortoise_serializer import Serializer
from tortoise import Model, fields
from pydantic import Field
from fastapi.routing import APIRouter


class MyUser(Model):
    id = fields.IntegerField(primary_key=True)
    name = fields.CharField(max_length=100, unique=True)


class MyUserSerializer(Serializer):
    id: int
    name: str = Field(max_length=100, description="User unique name")


router = APIRouter(prefix="/users")


@router.get("")
async def get_users() -> list[MyUserSerializer]:
    return await MyUserSerializer.from_queryset(MyUser.all(), context={"user": ...})
```

(Note: You can specify a `context` to pass additional information to serializers, but it is not mandatory.)

### Writing
```python
from fastapi import Body
from pydantic import Field


class MyUserCreationSerializer(Serializer):
    name: str = Field(max_length=200)


@router.post("")
async def create_user(user_serializer: MyUserCreationSerializer = Body(...)) -> MyUserSerializer:
    user = await user_serializer.create_tortoise_instance(MyUser)
    # Here you can also pass a `context=` to this function.
    return await MyUserSerializer.from_tortoise_orm(user)
```

> Note: It is currently not possible to handle ForeignKeys directly using serializers. You need to manage such logic in your views.


### Context
The context in serializers is immutable.


### Resolvers
Sometimes, you need to compute values or restrict access to sensitive data. This can be achieved with `resolvers` and `context`. Here's an example:

```python
from tortoise_serializer import ContextType, Serializer, require_permission_or_unset
from tortoise import Model, fields


class UserModel(Model):
    id = fields.IntegerField(primary_key=True)
    address = fields.CharField(max_length=1000)


def is_self(instance: UserModel, context: ContextType) -> bool:
    current_user = context.get("user")
    if not current_user:
        return False
    return current_user.id == instance.id


class UserSerializer(Serializer):
    id: int
    # Default is set to None, but the field will be omitted.
    address: str | None = None

    @classmethod
    @require_permission_or_unset(is_self)
    async def resolve_address(cls, instance: UserModel, context: ContextType) -> str:
        return instance.address


@app.get("/users", response_model_exclude_unset=True)
async def list_users(user: UserModel = Depends(...)) -> list[UserSerializer]:
    return await UserSerializer.from_queryset(UserModel.all(), context={"user": user})
```

This ensures that the `address` field is not exposed to unauthorized users.

Async resolvers are called concurrently during serializer instantiation.

## Relations
### ForeignKeys & OneToOne
To serialize relations, declare a field in the serializer as another serializer:

```python
from tortoise import Model, fields
from tortoise_serializer import Serializer


class BookShelf(Model):
    id = fields.IntField(primary_key=True)
    name = fields.CharField(unique=True)


class Book(Model):
    id = fields.IntField(primary_key=True)
    title = fields.CharField(db_index=True)
    shelf = fields.ForeignKeyField(
        "models.BookShelf",
        on_delete=fields.SET_NULL,
        null=True,
        related_name="books",
    )


class BookSerializer(Serializer):
    id: int
    title: str


class ShelfSerializer(Serializer):
    id: int
    name: str
    books: list[BookSerializer] = []


# Prefetching related fields is optional but improves performance.
serializer = ShelfSerializer.from_queryset(
    BookShelf.all().prefetch_related("books").order_by("name")
)
```

For a normal ForeignKey relationship:

```python
class ShelfSerializer(Serializer):
    id: int
    name: str


class BookSerializer(Serializer):
    id: int
    title: str
    shelf: ShelfSerializer | None
```


Reverse relations are `list[Serializer]`

Limitations:
Limitations: You cannot declare a field like this:
```python
class SerializerA(Serializer):
    ...


class SerializerB(Serializer):
    ...


class MyWrongSerializer(Serializer):
    my_field = SerializerA | SerializerB
```

but you can still use `None` like:
```python
class MySerializer(Serializer):
    some_relation: SerializerA | None = None
```

### Many2Many
There are two ways to handle Many-to-Many relationships:

- Use an intermediate Serializer with two ForeignKeys.
- Use a resolver in the serializer.

### Computed fields
Serialization involves resolving fields in the following order:

- Resolvers (computed fields)
- ForeignKeys
- Model fields
This order allows hiding fields based on the request.

Example of a computed field:
```python
from pydantic import Field
from tortoise_serializer import Serializer, ContextType
from tortoise.queryset import QuerySet


class Book(Model):
    id = fields.IntField(primary_key=True)
    title = fields.CharField(db_index=True)
    shelf = fields.ForeignKeyField(
        "models.BookShelf",
        on_delete=fields.SET_NULL,
        null=True,
        related_name="books",
    )


class BookSerializer(Serializer):
    id: int
    title: str
    path: str
    # This description will appear in Swagger's schema.
    answer_to_the_question: int = Field(description="The answer to the big question of life")

    @classmethod
    async def resolve_path(cls, instance: Book, context: ContextType) -> str:
        if not instance.shelf:
            return instance.title
        if isinstance(instance.shelf, QuerySet):
            await instance.fetch_related("shelf")
        return f'{instance.shelf.name}/{instance.title}'

    @classmethod
    def resolve_answer_to_the_question(cls, instance: Book, context: ContextType) -> int:
        return 42

main_shelf = await Shelf.create(title="main")
my_book = await Book.create(title="Serializers 101", shelf=main_shelf)
serializer = await BookSerializer.from_tortoise_orm(my_book)

assert serializer.path == "main/Serializers 101"
assert serializer.answer_to_the_question == 42

```

All async resolvers will be resolved in concurency in a `asyncio.gather`, non-async ones will be resolved one after the other

## Model Serializers
Sometime it may be usefull or necessary to be able to create a row and it's related foreignkeys at once in one endpoint, to achieve that the `ModelSerializer` class exists

Models serializer can manage:
- [x] Foreign keys
- [x] Backward foreign key
- [x] Many2Many relations
- [x] One to one relationship

### Basic Usage
```python
from tortoise import Model, fields
from tortoise_serializer import ModelSerializer


class Book(Model):
    id = fields.IntField(primary_key=True)
    title = fields.CharField(db_index=True, max_length=200)
    shelf = fields.ForeignKeyField(
        "models.BookShelf",
        on_delete=fields.SET_NULL,
        null=True,
        related_name="books",
    )


class BookShelf(Model):
    id = fields.IntField(primary_key=True)
    name = fields.CharField(unique=True, max_length=200)
    books: BackwardFKRelation[Book]


class ShelfCreationSerializer(ModelSerializer[BookShelf]):
    name: str


class BookCreationSerializer(ModelSerializer[Book]):
    title: str
    # here of course it's a bit weird to create the shelves with the books but
    # it's only for the example
    shelf: ShelfCreationSerializer


serializer = BookCreationSerializer(title="Some Title", shelv={"name": "where examples lie"})
example = await serializer.create_tortoise_instance()

# example will be an instance of `Book` here with it's related `shelf` realtion

assert await Book.filter(name="Some Title", shelv__name="where examples lie").exists()
```

### FastAPI
Since Serializers inherit from `pydantic.BaseModel` it means you can safely use them with FastAPI without any extra effort

Fastapi Documentation: https://fastapi.tiangolo.com/

#### Example
```python
from fastapi import status, Body, HTTPException
from fastapi.routing import APIRouter
from pydantic import Field
from tortoise import Model, fields
from tortoise.transaction import in_transaction
from tortoise_serializer import ModelSerializer

# Tortoise Models

class Author(Model):
    id = models.IntegerField(primary_key=True)
    name = models.CharField(max_length=200, unique=True)


class Book(Model):
    id = fields.IntegerField(primary_key=True)
    title = fields.CharField(max_length=200)
    pages_count = fields.IntegerField()
    author = fields.ForeignKeyField("models.Author", related_name="books")


# Serializer for creation

class AuthorCreationSerializer(ModelSerializer[Author]):
    name: str


class BookCreationSerializer(ModelSerializer[Book]):
    title: str = Field(max_length=200)
    author: AuthorCreationSerializer

    async def _get_or_create_author(self) -> Author:
        # here's an example of get or create flow using the serializers
        author = await Author.filter(name=self.author.name).get_or_none()
        if not author:
            author = await self.author.create_tortoise_instance()
        return author

    async def create_tortoise_instance(self, *args,  **kwargs) -> Book:
        kwargs["author"] = await self._get_or_create_author()
        return await super().create_tortoise_instance(*args, **kwargs)


# Serializer for reading

class AuthorSerializer(ModelSerializer[Author]):
    id: int
    name: str


class BookSerializer(ModelSerializer[Book]):
    id: int
    title: str
    author: AuthorSerializer

# Views to manage the books

router = APIRouter(prefix="/test")


@router.post("", status_code=status.HTTP_201_CREATED)
async def create_book(serializer: BookCreationSerializer = Body(...)) -> BookSerializer:
    async with in_transaction():
        book = await serializer.create_tortoise_instance()
    return await BookSerializer.from_tortoise_orm(book)


@router.get("")
async def list_books() -> list[BookSerializer]:
    queryset = Book.all().prefetch_related(*BookSerializer.get_prefetch_fields())
    return await BookSerializer.from_queryset(queryset)


@router.get("/{book_id}")
async def get_book(book_id: int) -> BookSerializer:
    book = await (
        Book.filter(id=book_id)
        .prefetch_related(*BookSerializer.get_prefetch_fields())
        .get_or_none()
    )
    if not book:
        raise HTTPException(status.HTTP_404_NOT_FOUND, "No such book")
    return await BookSerializer.from_tortoise_orm(book)


@router.delete("/{book_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_book(book_id: int) -> None:
    await Book.filter(id=book_id).delete()


@router.patch("{book_id}")
async def update_book(book_id: int, update: BookCreationSerializer) -> BookSerializer:
    book = await Book.get_or_none(id=book_id)
    if not book:
        raise HTTPException(status.HTTP_404_NOT_FOUND, "No such book")
    book.author = await update._get_or_create_author()
    update.partial_update_tortoise_instance(book)
    await book.save().
    return await BookSerializer.from_tortoise_orm(book)
```

### Optimizing Database Queries with Field Selection

Starting from `tortoise-orm` version 0.25.0, you can optimize your database queries by only fetching the fields that will be serialized. This feature helps reduce database load and improve performance by avoiding unnecessary field fetches.

Here's how to use it:

```python
class LocationSerializer[ModelSerializer[Location]]:
    id: str
    name: str


class PersonSerializer(ModelSerializer[Person]):
    id: int
    name: str
    location: LocationSerializer


persons = await PersonSerializer.from_queryset(
    Person.all().only(*PersonSerializer.get_only_fetch_fields())
)

```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "tortoise-serializer",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.12",
    "maintainer_email": null,
    "keywords": null,
    "author": "Sebastien Nicolet",
    "author_email": "snicolet95@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/0b/95/4535421a6b7f205e4e888a75565b781908bcaab8052d804281eb1170f093/tortoise_serializer-1.6.0.tar.gz",
    "platform": null,
    "description": "# Tortoise Serializer\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/b801e7563fb34f76a27aeae4d38f2853)](https://app.codacy.com/gh/Chr0nos/tortoise_serializer/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)\n[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/b801e7563fb34f76a27aeae4d38f2853)](https://app.codacy.com/gh/Chr0nos/tortoise_serializer/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)\n\n## Motivation\nThis project was created to address some of the limitations of `pydantic_model_creator`, including:\n- The ability to use a `context` in serialization at the field level.\n- Access to the actual Tortoise `Model` instance during serialization.\n- Improved readability.\n- Support for adding extra logic to specific serializers.\n- The ability to document fields in a way that is visible in Swagger.\n\n## Usefull readings\n- https://docs.pydantic.dev/latest/\n- https://tortoise.github.io/\n\n\n## Installation\n```shell\npip add tortoise-serializer\n```\n\n## Core concept\nA `Serializer` does not need to know which model it will serialize. For example:\n```python\nfrom tortoise_serializer import Serializer\n\n\nclass ItemByNameSerializer(Serializer):\n    id: int\n    name: str\n\n\nproducts = await ItemByNameSerializer.from_queryset(Product.all())\nusers = await ItemByNameSerializer.from_queryset(User.all())\n\n```\nThis is entirely valid.\n\n`Serializers` are `pydantic.BaseModel` objects, which means you can directly return them from FastAPI endpoints or use any functionality provided by BaseModel.\n\n\n## Usage\n### Reading\n```python\nfrom tortoise_serializer import Serializer\nfrom tortoise import Model, fields\nfrom pydantic import Field\nfrom fastapi.routing import APIRouter\n\n\nclass MyUser(Model):\n    id = fields.IntegerField(primary_key=True)\n    name = fields.CharField(max_length=100, unique=True)\n\n\nclass MyUserSerializer(Serializer):\n    id: int\n    name: str = Field(max_length=100, description=\"User unique name\")\n\n\nrouter = APIRouter(prefix=\"/users\")\n\n\n@router.get(\"\")\nasync def get_users() -> list[MyUserSerializer]:\n    return await MyUserSerializer.from_queryset(MyUser.all(), context={\"user\": ...})\n```\n\n(Note: You can specify a `context` to pass additional information to serializers, but it is not mandatory.)\n\n### Writing\n```python\nfrom fastapi import Body\nfrom pydantic import Field\n\n\nclass MyUserCreationSerializer(Serializer):\n    name: str = Field(max_length=200)\n\n\n@router.post(\"\")\nasync def create_user(user_serializer: MyUserCreationSerializer = Body(...)) -> MyUserSerializer:\n    user = await user_serializer.create_tortoise_instance(MyUser)\n    # Here you can also pass a `context=` to this function.\n    return await MyUserSerializer.from_tortoise_orm(user)\n```\n\n> Note: It is currently not possible to handle ForeignKeys directly using serializers. You need to manage such logic in your views.\n\n\n### Context\nThe context in serializers is immutable.\n\n\n### Resolvers\nSometimes, you need to compute values or restrict access to sensitive data. This can be achieved with `resolvers` and `context`. Here's an example:\n\n```python\nfrom tortoise_serializer import ContextType, Serializer, require_permission_or_unset\nfrom tortoise import Model, fields\n\n\nclass UserModel(Model):\n    id = fields.IntegerField(primary_key=True)\n    address = fields.CharField(max_length=1000)\n\n\ndef is_self(instance: UserModel, context: ContextType) -> bool:\n    current_user = context.get(\"user\")\n    if not current_user:\n        return False\n    return current_user.id == instance.id\n\n\nclass UserSerializer(Serializer):\n    id: int\n    # Default is set to None, but the field will be omitted.\n    address: str | None = None\n\n    @classmethod\n    @require_permission_or_unset(is_self)\n    async def resolve_address(cls, instance: UserModel, context: ContextType) -> str:\n        return instance.address\n\n\n@app.get(\"/users\", response_model_exclude_unset=True)\nasync def list_users(user: UserModel = Depends(...)) -> list[UserSerializer]:\n    return await UserSerializer.from_queryset(UserModel.all(), context={\"user\": user})\n```\n\nThis ensures that the `address` field is not exposed to unauthorized users.\n\nAsync resolvers are called concurrently during serializer instantiation.\n\n## Relations\n### ForeignKeys & OneToOne\nTo serialize relations, declare a field in the serializer as another serializer:\n\n```python\nfrom tortoise import Model, fields\nfrom tortoise_serializer import Serializer\n\n\nclass BookShelf(Model):\n    id = fields.IntField(primary_key=True)\n    name = fields.CharField(unique=True)\n\n\nclass Book(Model):\n    id = fields.IntField(primary_key=True)\n    title = fields.CharField(db_index=True)\n    shelf = fields.ForeignKeyField(\n        \"models.BookShelf\",\n        on_delete=fields.SET_NULL,\n        null=True,\n        related_name=\"books\",\n    )\n\n\nclass BookSerializer(Serializer):\n    id: int\n    title: str\n\n\nclass ShelfSerializer(Serializer):\n    id: int\n    name: str\n    books: list[BookSerializer] = []\n\n\n# Prefetching related fields is optional but improves performance.\nserializer = ShelfSerializer.from_queryset(\n    BookShelf.all().prefetch_related(\"books\").order_by(\"name\")\n)\n```\n\nFor a normal ForeignKey relationship:\n\n```python\nclass ShelfSerializer(Serializer):\n    id: int\n    name: str\n\n\nclass BookSerializer(Serializer):\n    id: int\n    title: str\n    shelf: ShelfSerializer | None\n```\n\n\nReverse relations are `list[Serializer]`\n\nLimitations:\nLimitations: You cannot declare a field like this:\n```python\nclass SerializerA(Serializer):\n    ...\n\n\nclass SerializerB(Serializer):\n    ...\n\n\nclass MyWrongSerializer(Serializer):\n    my_field = SerializerA | SerializerB\n```\n\nbut you can still use `None` like:\n```python\nclass MySerializer(Serializer):\n    some_relation: SerializerA | None = None\n```\n\n### Many2Many\nThere are two ways to handle Many-to-Many relationships:\n\n- Use an intermediate Serializer with two ForeignKeys.\n- Use a resolver in the serializer.\n\n### Computed fields\nSerialization involves resolving fields in the following order:\n\n- Resolvers (computed fields)\n- ForeignKeys\n- Model fields\nThis order allows hiding fields based on the request.\n\nExample of a computed field:\n```python\nfrom pydantic import Field\nfrom tortoise_serializer import Serializer, ContextType\nfrom tortoise.queryset import QuerySet\n\n\nclass Book(Model):\n    id = fields.IntField(primary_key=True)\n    title = fields.CharField(db_index=True)\n    shelf = fields.ForeignKeyField(\n        \"models.BookShelf\",\n        on_delete=fields.SET_NULL,\n        null=True,\n        related_name=\"books\",\n    )\n\n\nclass BookSerializer(Serializer):\n    id: int\n    title: str\n    path: str\n    # This description will appear in Swagger's schema.\n    answer_to_the_question: int = Field(description=\"The answer to the big question of life\")\n\n    @classmethod\n    async def resolve_path(cls, instance: Book, context: ContextType) -> str:\n        if not instance.shelf:\n            return instance.title\n        if isinstance(instance.shelf, QuerySet):\n            await instance.fetch_related(\"shelf\")\n        return f'{instance.shelf.name}/{instance.title}'\n\n    @classmethod\n    def resolve_answer_to_the_question(cls, instance: Book, context: ContextType) -> int:\n        return 42\n\nmain_shelf = await Shelf.create(title=\"main\")\nmy_book = await Book.create(title=\"Serializers 101\", shelf=main_shelf)\nserializer = await BookSerializer.from_tortoise_orm(my_book)\n\nassert serializer.path == \"main/Serializers 101\"\nassert serializer.answer_to_the_question == 42\n\n```\n\nAll async resolvers will be resolved in concurency in a `asyncio.gather`, non-async ones will be resolved one after the other\n\n## Model Serializers\nSometime it may be usefull or necessary to be able to create a row and it's related foreignkeys at once in one endpoint, to achieve that the `ModelSerializer` class exists\n\nModels serializer can manage:\n- [x] Foreign keys\n- [x] Backward foreign key\n- [x] Many2Many relations\n- [x] One to one relationship\n\n### Basic Usage\n```python\nfrom tortoise import Model, fields\nfrom tortoise_serializer import ModelSerializer\n\n\nclass Book(Model):\n    id = fields.IntField(primary_key=True)\n    title = fields.CharField(db_index=True, max_length=200)\n    shelf = fields.ForeignKeyField(\n        \"models.BookShelf\",\n        on_delete=fields.SET_NULL,\n        null=True,\n        related_name=\"books\",\n    )\n\n\nclass BookShelf(Model):\n    id = fields.IntField(primary_key=True)\n    name = fields.CharField(unique=True, max_length=200)\n    books: BackwardFKRelation[Book]\n\n\nclass ShelfCreationSerializer(ModelSerializer[BookShelf]):\n    name: str\n\n\nclass BookCreationSerializer(ModelSerializer[Book]):\n    title: str\n    # here of course it's a bit weird to create the shelves with the books but\n    # it's only for the example\n    shelf: ShelfCreationSerializer\n\n\nserializer = BookCreationSerializer(title=\"Some Title\", shelv={\"name\": \"where examples lie\"})\nexample = await serializer.create_tortoise_instance()\n\n# example will be an instance of `Book` here with it's related `shelf` realtion\n\nassert await Book.filter(name=\"Some Title\", shelv__name=\"where examples lie\").exists()\n```\n\n### FastAPI\nSince Serializers inherit from `pydantic.BaseModel` it means you can safely use them with FastAPI without any extra effort\n\nFastapi Documentation: https://fastapi.tiangolo.com/\n\n#### Example\n```python\nfrom fastapi import status, Body, HTTPException\nfrom fastapi.routing import APIRouter\nfrom pydantic import Field\nfrom tortoise import Model, fields\nfrom tortoise.transaction import in_transaction\nfrom tortoise_serializer import ModelSerializer\n\n# Tortoise Models\n\nclass Author(Model):\n    id = models.IntegerField(primary_key=True)\n    name = models.CharField(max_length=200, unique=True)\n\n\nclass Book(Model):\n    id = fields.IntegerField(primary_key=True)\n    title = fields.CharField(max_length=200)\n    pages_count = fields.IntegerField()\n    author = fields.ForeignKeyField(\"models.Author\", related_name=\"books\")\n\n\n# Serializer for creation\n\nclass AuthorCreationSerializer(ModelSerializer[Author]):\n    name: str\n\n\nclass BookCreationSerializer(ModelSerializer[Book]):\n    title: str = Field(max_length=200)\n    author: AuthorCreationSerializer\n\n    async def _get_or_create_author(self) -> Author:\n        # here's an example of get or create flow using the serializers\n        author = await Author.filter(name=self.author.name).get_or_none()\n        if not author:\n            author = await self.author.create_tortoise_instance()\n        return author\n\n    async def create_tortoise_instance(self, *args,  **kwargs) -> Book:\n        kwargs[\"author\"] = await self._get_or_create_author()\n        return await super().create_tortoise_instance(*args, **kwargs)\n\n\n# Serializer for reading\n\nclass AuthorSerializer(ModelSerializer[Author]):\n    id: int\n    name: str\n\n\nclass BookSerializer(ModelSerializer[Book]):\n    id: int\n    title: str\n    author: AuthorSerializer\n\n# Views to manage the books\n\nrouter = APIRouter(prefix=\"/test\")\n\n\n@router.post(\"\", status_code=status.HTTP_201_CREATED)\nasync def create_book(serializer: BookCreationSerializer = Body(...)) -> BookSerializer:\n    async with in_transaction():\n        book = await serializer.create_tortoise_instance()\n    return await BookSerializer.from_tortoise_orm(book)\n\n\n@router.get(\"\")\nasync def list_books() -> list[BookSerializer]:\n    queryset = Book.all().prefetch_related(*BookSerializer.get_prefetch_fields())\n    return await BookSerializer.from_queryset(queryset)\n\n\n@router.get(\"/{book_id}\")\nasync def get_book(book_id: int) -> BookSerializer:\n    book = await (\n        Book.filter(id=book_id)\n        .prefetch_related(*BookSerializer.get_prefetch_fields())\n        .get_or_none()\n    )\n    if not book:\n        raise HTTPException(status.HTTP_404_NOT_FOUND, \"No such book\")\n    return await BookSerializer.from_tortoise_orm(book)\n\n\n@router.delete(\"/{book_id}\", status_code=status.HTTP_204_NO_CONTENT)\nasync def delete_book(book_id: int) -> None:\n    await Book.filter(id=book_id).delete()\n\n\n@router.patch(\"{book_id}\")\nasync def update_book(book_id: int, update: BookCreationSerializer) -> BookSerializer:\n    book = await Book.get_or_none(id=book_id)\n    if not book:\n        raise HTTPException(status.HTTP_404_NOT_FOUND, \"No such book\")\n    book.author = await update._get_or_create_author()\n    update.partial_update_tortoise_instance(book)\n    await book.save().\n    return await BookSerializer.from_tortoise_orm(book)\n```\n\n### Optimizing Database Queries with Field Selection\n\nStarting from `tortoise-orm` version 0.25.0, you can optimize your database queries by only fetching the fields that will be serialized. This feature helps reduce database load and improve performance by avoiding unnecessary field fetches.\n\nHere's how to use it:\n\n```python\nclass LocationSerializer[ModelSerializer[Location]]:\n    id: str\n    name: str\n\n\nclass PersonSerializer(ModelSerializer[Person]):\n    id: int\n    name: str\n    location: LocationSerializer\n\n\npersons = await PersonSerializer.from_queryset(\n    Person.all().only(*PersonSerializer.get_only_fetch_fields())\n)\n\n```\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Pydantic serialization for tortoise-orm",
    "version": "1.6.0",
    "project_urls": null,
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "950a3d3c24242ad3700dd3751c6730a3b39114686de62fd648c107c1e66978c3",
                "md5": "55b1e0cd43f7719ae0d8d74f04699b2a",
                "sha256": "8b67398866bb26ef59d6ed9e9d6dcacd4e511cc284ca0b3e8b9d71838adf0b65"
            },
            "downloads": -1,
            "filename": "tortoise_serializer-1.6.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "55b1e0cd43f7719ae0d8d74f04699b2a",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.12",
            "size": 17149,
            "upload_time": "2025-07-25T10:13:02",
            "upload_time_iso_8601": "2025-07-25T10:13:02.032047Z",
            "url": "https://files.pythonhosted.org/packages/95/0a/3d3c24242ad3700dd3751c6730a3b39114686de62fd648c107c1e66978c3/tortoise_serializer-1.6.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0b954535421a6b7f205e4e888a75565b781908bcaab8052d804281eb1170f093",
                "md5": "b6ad1c608b60d1dd574af25797a767c5",
                "sha256": "4b15339cf8fca71cafde6e4bf3ed87ece2e39e29392d0f55a2a29d627a630b3e"
            },
            "downloads": -1,
            "filename": "tortoise_serializer-1.6.0.tar.gz",
            "has_sig": false,
            "md5_digest": "b6ad1c608b60d1dd574af25797a767c5",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.12",
            "size": 17385,
            "upload_time": "2025-07-25T10:13:03",
            "upload_time_iso_8601": "2025-07-25T10:13:03.353179Z",
            "url": "https://files.pythonhosted.org/packages/0b/95/4535421a6b7f205e4e888a75565b781908bcaab8052d804281eb1170f093/tortoise_serializer-1.6.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-25 10:13:03",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "tortoise-serializer"
}
        
Elapsed time: 2.28316s