prisma-client-py icon indicating copy to clipboard operation
prisma-client-py copied to clipboard

Support defining the Prisma Schema using python models

Open RobertCraigie opened this issue 3 years ago • 0 comments

Problem

One potential source of conflict when transitioning to use Prisma Client Python from other ORMs is that models must be defined in a separate Prisma Schema file.

We should support defining models using a python module.

It should then be possible to query directly from these models.

Suggested solution

Notes:

  • prisma.schema.BaseModel is a re-export of pydantic.BaseModel

Example:

from typing import List
from prisma.schema import BaseModel, Field, Default, Relation, render


class User(BaseModel):
    id: str = Field(is_id=True, default=Default.cuid)
    name: str
    posts: List['Post']


class Post(BaseModel):
    id: str = Field(is_id=True, default=Default.cuid)
    title: str
    author: Optional[User] = Relation('author_id', references=['id'])
    author_id: str

User.update_forward_refs()

if __name__ == '__main__':
    render('schema.prisma', models=[User, Post])

Equivalent Prisma Schema file:

model User {
  id    String @id @default(cuid())
  name  String
  posts Post[]
}

model Post {
  id        String     @id @default(cuid())
  title     String
  author    User       @relation(fields: [author_id], references: [id])
  author_id String
}

Features still to be implemented:

  • Datasources (we could make these configurable on the client level?)
  • Generators

Implementation notes:

  • Error early if a relation is not typed as Optional (or automatically include them once #19 is merged)

Potential difficulties:

  • Making these models query-able will be difficult. Solution is still a work in progress.
  • Integrating this schema with the standard Prisma commands

Querying

The key for making these models queryable is replacing code during generation time.

Looks like the solution for this is to dynamically resolve base class to inherit from and use literal overloads to type it post-generation. However, mypy does not support this :/

class User(models.BaseUser):
    pass

Where:

# pre-generation
class _Models:
    def __getattr__(self, attr: str) -> BaseModel:
        ...

models = _Models()

# post-generation
class _Models:
    BaseUser: QueryableUser

models = _Models()

The only disadvantage to this is that it is very magic and could be potentially confusing, maybe a different naming schema could help?

Additional questions

  • Is it even worth making these models queryable?

RobertCraigie avatar Dec 08 '21 18:12 RobertCraigie