The Problem

I was recently working on a project and decided to use Tortoise ORM. I appreciated the small footprint, ease of use, and the first class async support. However, I was disappointed to find that it didn’t support parameter hints when creating an instance of a model.

Tortoise ORM model instance creation

The IDE only shows **kwargs when creating a Tortoise ORM model instance

Working Towards a Solution

Libraries like PyDantic and msgspec already support this feature, so I knew it must be possible. However, finding a solution wasn’t straightforward.

Pydantic model instance creation

The IDE shows parameter hints when creating a Pydantic model instance

msgspec model instance creation

The IDE also shows parameter hints when creating a msgspec model instance

I finally checked the source code of msgspec. It uses a decorator from the typing module called dataclass_transform. Curiously, I looked up the documentation for it. As it turns out, this decorator is exactly what I needed. To quote the documentation:

The presence of dataclass_transform tells a static type checker that the decorated function, class, or metaclass performs runtime “magic” that transforms a class, endowing it with dataclass-like behaviors.

Introduced in Python 3.11, this decorator acts as a signal to type checkers that a class has dataclass-like behavior. It does not have any effect at runtime, but it allows type checkers to understand that the class should be initialized with the same parameters as it’s defined fields.

With this neat dataclass, implementing the solution was straightforward. I created a new class called TypedModel that inherits from Model and added the dataclass_transform decorator to it.

from typing import dataclass_transform

from tortoise import Model, fields
from tortoise.fields import Field


@dataclass_transform()
class TypedModel(Model):
    pass


class Group(TypedModel):
    group_id = fields.IntField(pk=True)
    name: Field[str] = fields.CharField(max_length=255)
    created_at: Field[int] = fields.IntField()

The Result

I can now use the TypedModel class as a base class for our models. The IDE will now show the parameter hints when creating an instance of a model.

As a bonus, I also have static type checking on model initialization.

Tortoise ORM model instance creation

The IDE now shows parameter hints when creating a Tortoise ORM model instance. As a bonus, it also warns about using the wrong type for a field.

I tested this approach with SQLAlchemy and Peewee as well, and it worked perfectly. I expected it to work with most ORMs.

Limitations

One limitation of this approach is that it doesn’t add parameter hints to factory methods like MyModel.create(). Parameter hints are only shown when directly instantiating the class. I’m still exploring whether there’s a workaround for method-level hints, but for now, this approach is good enough for me.