I've noticed recently that there is practically no support for type-safe ORM/ODM projections in the broader Python ecosystem.
A little context: ORMs (object-relational mapper) and ODM (object-document mapper/mapping) are tools or libraries for interacting with databases. SQLAlchemy and the Django ORM are probably the most famous examples. Basically, you model a database table as a class, and rows in the table are instances of that class. ODMs are a little simpler than ORMs, they practically only map rows/documents to instances of a class, ORMs are more complex.
Writing a type-safe ODM nowadays is super simple, and there are loads out there. The idea is this:
``` @dataclass class User: id: int username: str
user = await fetch(User, id=1) ```
This can be made to work with the proper type annotations without much fuss.
Now, the issue is doing projections in a type-safe manner. A projection basically means loading only a subset of the fields, usually for performance. Let's say that we're only interested in the user username (imagine there are 30 other fields in the class that we don't care about). That would look kinda like:
``` from dataclasses import fields
user_projection: tuple[str] = await fetch_projection(User, id=1, fields(User)) ```
This can't really be very type-safe since Mypy treats `fields(User)` as `dataclasses.Field*[Any]`. Now, if Mypy treated it as `dataclasses.Field*[str]`, I assume that would be a different story, and the function could be annotated to return a 1-tuple of `str`.
As mentioned, I've looked at a bunch of ORM/ODM libraries (and written a few internally) and as far as I can tell this use-case is very much unsupported as of yet. (I would appreciate counter-examples, obviously!) This made me sad so I came to the list to see if there's anything to be done.
To be super honest I'm more interested in getting attrs support for this, but attrs and dataclasses are so similar I figured if someone did the work in the dataclass plugin, the logic could also be ported over to the attrs plugin. attrs also has a nicer API for actually getting the fields, so the equivalent attrs example would be:
``` from attrs import fields as f
user_projection = await fetch_projection(User, id=1, f(User).username) ```
Wouldn't it be super cool if Mypy (or other type checkers) could check this statically?