Prisma Client Python

repository·main·Indexed 24 days ago

https://github.com/robertcraigie/prisma-client-py

A type-safe ORM for Python that interfaces with the Prisma engine via Rust, providing full autocompletion and type safety without requiring a Node.js runtime for the client. It supports both synchronous and asynchronous usage, atomic updates, and complex cross-relational queries. Supported database providers include PostgreSQL, MySQL, SQLite, CockroachDB, and experimental support for MongoDB and SQL Server. Note: This project is no longer being maintained.

Tokens
23.5K
Snippets
88
Records
138
Agent score
83%

What's inside prisma-client-py

  1. Overview of Prisma Client Python

    main

    Prisma Client Python is a type-safe ORM for Python built on top of the Prisma engine. It provides zero-cost type safety and is designed for ease of use and correctness in any Python backend application (REST APIs, GraphQL APIs, etc.).

    Key characteristics:

    • No Node.js required: While it interfaces with Prisma using Rust, you do not need Node.js or TypeScript in your environment.
    • Type Safety: Offers full type safety for database access.
    • Async Support: Provides native support for both synchronous and asynchronous usage.
    • Autocompletion: Provides deep autocompletion for query arguments (best supported by Pylance / Pyright).

    Note: Prisma Client Python is no longer being maintained.

  2. How custom Prisma generators work

    main

    A custom Prisma generator in Python is implemented by subclassing BaseGenerator and performing two distinct steps:

    1. Metadata: Providing information about the generator (like its name and default output location) using the get_manifest() method which returns a Manifest object.
    2. Generation: Processing the Prisma Schema AST (represented as DMMF) during the generate() step. The schema data is passed to this method via a Data object (or DefaultData).

    Note that changes to the DMMF structure are not considered breaking changes by the library.

    from prisma.generator import BaseGenerator, Manifest, DefaultData
    
    class MyGenerator(BaseGenerator):
        def get_manifest(self) -> Manifest:
            return Manifest(
                name='My Generator',
                default_output='output.txt',
            )
    
        def generate(self, data: DefaultData) -> None:
            # Access schema via data.dmmf
            pass
  3. Create partial models using Partial Types

    main

    Prisma Client Python allows you to generate partial models at generation time based on your existing schema-defined models. These partial models are useful when you need to represent a subset of a model's fields, or when you need to change the nullability (optionality) of specific fields for specific use cases (e.g., API responses or input validation).

    Partial models are generated and made available under the prisma.partials namespace.

    You define these partials by calling .create_partial() on an existing model class. This configuration is typically placed in your prisma/partial_types.py file.

  4. How the Mypy plugin handles relational fields

    main

    The Mypy plugin improves type safety for included relations:

    • Non-optional relations: If a relational field is explicitly passed via include, the plugin removes the Optional type from that field on the returned model. Note that if no records are found, the field will be an empty list rather than None.
    • Optional relations: If a relation is defined as optional in your Prisma schema, the field will still be typed as Optional even when explicitly included.
    • Limitation: Dynamic include values (e.g., building an include dictionary at runtime) are not currently supported by the plugin and will trigger a parsing warning.
    # Example of non-optional relation being correctly typed after include
    user = await db.user.find_unique(
        where={
            'id': 'user_id',
        },
        include={
            'posts': True
        }
    )
    # user.posts is not treated as Optional here
    print(f'User {user.name} has {len(user.posts)} posts')
  5. Core features of Prisma Client Python

    main

    Prisma Client Python includes several advanced database interaction features:

    • Prisma Migrate: Integration with Prisma's migration system.
    • Full Type Safety: End-to-end type safety for your models and queries.
    • Async/Sync Flexibility: Choose between async or synchronous clients.
    • Atomic Updates: Support for atomic field updates.
    • Complex Queries: Support for complex cross-relational queries.
    • Batching: Ability to batch write queries.
    • Advanced Typing: Supports recursive and pseudo-recursive types, and partial type generation.

    Supported Database Providers:

    • PostgreSQL
    • MySQL
    • SQLite
    • CockroachDB
    • MongoDB (experimental)
    • SQL Server (experimental)
  6. Fetch relations automatically with Custom Models

    main

    Experimental Feature: Automatic Relation Fetching

    To avoid explicitly specifying include in every query, you can define a custom model where a one-to-many relation is defined as a non-optional type. When you run a query using this custom model, Prisma Client Python will automatically fetch that relation.

    Warning: This feature is experimental. Use it sparingly as automatic relational lookups can significantly impact performance if used unnecessarily.

    Limitations:

    • Currently only works for one-to-many relations.
    • Does not work for one-to-one relational fields.
    from typing import List
    from prisma.models import Character
    from prisma.bases import BaseUser
    
    class UserWithCharacters(BaseUser):
      id: str
      name: str
      characters: List[Character]
    
    # This query will automatically include the 'characters' relation
    user = await UserWithCharacters.prisma().find_unique(
      where={
        'id': '<user id>',
      },
    )
    print(user.name)
    print(user.characters[0].strength)
  7. How Prisma Client Python works

    main

    Prisma Client Python acts as a bridge between your Python code and the database:

    1. Query Engine: It executes queries using Prisma's Rust-based Query Engine.
    2. CLI Interface: The prisma CLI wraps the standard Prisma CLI. It requires Node.js to be installed on your machine to run the underlying Node binary.
    3. Type Safety: All methods are fully statically typed, allowing for bug detection via static type checkers (like Pyright or Pylance) without running the code.
  8. Understand Type Safety in Prisma Client Python

    main

    All Prisma Client Python methods are fully statically typed. This means the library uses Python type hints to provide metadata about the types of objects, arguments, and return values used in your database queries.

    While Python type hints are not enforced at runtime, they provide significant benefits for development:

    1. Type Checkers: Tools like mypy can find bugs in your code before you run it.
    2. Documentation: Type hints serve as built-in documentation, making it clear what data types are expected for function arguments and what will be returned.
    3. Improved IDE Experience: Modern IDEs use these hints to provide better autocompletion suggestions and to highlight type-related errors directly in your editor.
  9. Configure the Prisma schema for Prisma Client Python

    main

    Every project starts with a schema.prisma file. To use Prisma Client Python, you must include a generator block with the provider set to prisma-client-py. The schema also requires a datasource block to define your database connection and model blocks to define your application data models.

    Key configuration options for the generator:

    • provider: Must be set to "prisma-client-py".
    • recursive_type_depth: (Optional) Controls the depth of recursive types.
    • output: (Optional) Defines where the client will be generated. By default, it is generated into the same location where the package was installed.
    datasource db {
      provider = "sqlite"
      url      = "file:database.db"
    }
    
    generator client {
      provider             = "prisma-client-py"
      recursive_type_depth = 5
    }
    
    model Post {
      id        Int     @id @default(autoincrement())
      title     String
      content   String?
      views     Int     @default(0)
      published Boolean @default(false)
      author    User?   @relation(fields: [author_id], references: [id])
      author_id Int?
    }
    
    model User {
      id    Int     @id @default(autoincrement())
      email String  @unique
      name  String?
      posts Post[]
    }
  10. Use Schema Extensions with `/// @Python(...)` comments

    main

    Prisma Client Python allows you to extend the standard Prisma Schema syntax using special comments. To ensure these extensions are recognized by Prisma Client Python and not treated as standard Prisma schema comments, you must use three forward slashes (///) instead of the standard two (//).

    Currently, extensions are primarily supported for models.

    /// @Python(...)
  11. How Partial Types work in Prisma Client Python

    main

    Prisma Client Python allows you to create 'partial models' at generation time. These are specialized versions of your schema-defined models that contain only a subset of fields, with custom requirements for whether those fields are optional or required. This is useful for creating specific data shapes for different parts of your application (e.g., a UserSummary vs a UserFullProfile).

    Partial types are generated using the create_partial static method on a model class. You define the shape by specifying which fields to include or exclude, which to make required or optional, and how to handle relations.

    class Model:
      @staticmethod
      def create_partial(
          name: str,
          include: Optional[Iterable['{{ model }}Keys']] = None,
          exclude: Optional[Iterable['{{ model }}Keys']] = None,
          required: Optional[Iterable['{{ model }}Keys']] = None,
          optional: Optional[Iterable['{{ model }}Keys']] = None,
          relations: Optional[Mapping['{{ model }}RelationalKeys', str]] = None,
          exclude_relational_fields: bool = False,
      ) -> None:
          ...
  12. Filter by relational fields

    main

    You can filter records based on the properties of their related models using the is, is_not, some, every, and none operators.

    One-to-One

    Use is or is_not to match specific values on the related record.

    One-to-Many

    • some: Matches if at least one related record meets the criteria.
    • every: Matches if all related records meet the criteria.
    • none: Matches if no related records meet the criteria.