Skip to main content

Unified, backend-agnostic ORM abstraction for Strawberry GraphQL

Project description

strawberry-orm

Backend-agnostic schema generation for Strawberry GraphQL on top of Django ORM, SQLAlchemy, and Tortoise ORM.

WARNING

🟧🟨 strawberry-orm is still in alpha. Expect breaking changes, incomplete APIs, and release-to-release churn while the package stabilizes. 🟨🟧

strawberry-orm helps you keep one Strawberry schema style across multiple ORMs. It focuses on:

  • model-backed Strawberry types
  • generated input, filter, and order types
  • list fields that expose filtering and ordering automatically
  • query optimization hooks to reduce N+1 queries
  • helpers for related-list mutation inputs

Installation

# Base package
uv add strawberry-orm

# With a backend
uv add "strawberry-orm[django]"
uv add "strawberry-orm[sqlalchemy]"
uv add "strawberry-orm[tortoise]"

You can do the same with pip:

pip install "strawberry-orm[sqlalchemy]"

If you are using zsh, keep the quotes around extras such as "strawberry-orm[sqlalchemy]". Unquoted square brackets are treated as shell glob syntax before uv or pip sees the package name.

Requirements:

  • Python >=3.12
  • strawberry-graphql>=0.311.0

Quick Start

  1. Create a backend instance.
  2. Generate Strawberry types from ORM models with @orm.type(...).
  3. Generate filter/order types from the same models.
  4. Expose list fields with orm.field().
  5. Add orm.optimizer_extension() to the schema.
import strawberry

from strawberry_orm import StrawberryORM, auto

orm = StrawberryORM(
    "sqlalchemy",
    dialect="postgresql",
    session_getter=lambda info: info.context["session"],
)

UserFilter = orm.filter(User)
UserOrder = orm.order(User)


@orm.type(User, filters=UserFilter, order=UserOrder)
class UserType:
    id: auto
    name: auto
    email: auto


@strawberry.type
class Query:
    users: list[UserType] = orm.field()


schema = strawberry.Schema(
    query=Query,
    extensions=[orm.optimizer_extension()],
)

That single users field will:

  • start from the backend's default queryset for User
  • accept filter and order arguments automatically because UserType carries them
  • let the optimizer eagerly load related data based on the GraphQL selection set

Backends

strawberry-orm follows Strawberry's mixed execution model:

  • sync and async schema execution are both supported
  • a resolver can return plain values, backend query objects, or awaitables
  • the optimizer extension handles both sync and async execution paths
  • direct helper APIs such as apply_ref_list(...) may be sync or awaitable depending on the backend/session in use

As a rule of thumb:

  • Django works in both sync and async Strawberry execution, but custom async resolvers still need sync_to_async(...) around direct Django ORM access
  • SQLAlchemy supports both sync Session and AsyncSession
  • Tortoise is async-first; use async Strawberry execution there
Backend Constructor Notes
Django StrawberryORM("django") Uses Django querysets directly; async execution is supported via Strawberry's mixed sync/async model.
SQLAlchemy StrawberryORM("sqlalchemy", dialect="postgresql", session_getter=...) Requires a SQLAlchemy Session or AsyncSession at resolve time.
Tortoise StrawberryORM("tortoise") Async ORM; use Strawberry's async execution path.

Django

orm = StrawberryORM("django")

When executing the schema asynchronously, custom resolvers that touch Django models directly should still wrap those ORM calls with sync_to_async(...), following the same guidance as strawberry-django.

SQLAlchemy

orm = StrawberryORM(
    "sqlalchemy",
    dialect="postgresql",
    session_getter=lambda info: info.context["session"],
)

SQLAlchemy needs a session when a query is executed. strawberry-orm can obtain it from either:

  • session_getter=...
  • info.context["session"]
  • info.context.session
  • info.context.get_session()

If your context stores a callable session factory, pass a session_getter instead of putting the callable directly on info.context.

Both sync and async sessions are supported:

# Sync session
orm = StrawberryORM(
    "sqlalchemy",
    dialect="postgresql",
    session_getter=lambda info: info.context["session"],
)

# Async session
orm = StrawberryORM(
    "sqlalchemy",
    dialect="postgresql",
    session_getter=lambda info: info.context["session"],
)

@strawberry.type
class Query:
    @strawberry.field
    def users(self) -> list[UserType]:
        return select(User)

# context["session"] can be either Session or AsyncSession

Tortoise

orm = StrawberryORM("tortoise")

Tortoise resolvers, mutations, and related-list helpers should be used from async Strawberry execution:

@strawberry.type
class Query:
    @strawberry.field
    async def users(self) -> list[UserType]:
        return await User.all()

Backend Options

Shared options:

Option Default Meaning
default_query_limit None Adds a default limit to list queries created from the backend default queryset.
exclude_sensitive_fields True Excludes sensitive-looking fields from generated input/filter/order types.
warn_sensitive True Emits warnings when sensitive-looking fields are exposed on generated output types.
hard_delete_refs False Makes apply_ref_list(..., delete=...) delete related rows instead of only unlinking them.
max_filter_depth 10 Caps recursive filter nesting.
max_filter_branches 50 Caps the total number of all / any / oneOf branches.
max_in_list_size 500 Caps inList / notInList filter size.
enable_regex_filters False Enables regex and iRegex string lookups.

SQLAlchemy-only options:

Option Default Meaning
dialect "postgresql" Chooses SQLAlchemy dialect-specific behavior.
session_getter None Returns the session for the current request.
filter_overrides {} Maps Python types to custom lookup input types.

Defining Types

@orm.type(Model)

Use @orm.type(Model) to turn an ORM model into a Strawberry object type.

from strawberry_orm import auto


@orm.type(User)
class UserType:
    id: auto
    name: auto
    email: auto

auto is an alias for strawberry.auto. The backend inspects the model and fills in the Python type for each field.

Keyword arguments:

  • include=[...]
  • exclude=[...]
  • name="CustomGraphQLTypeName"
  • filters=UserFilter
  • order=UserOrder
@orm.type(User, include=["id", "name"], name="PublicUser")
class PublicUserType:
    id: auto
    name: auto


@orm.type(User, exclude=["password_hash", "api_key"])
class SafeUserType:
    id: auto
    name: auto
    email: auto

Relations

Reference other generated types directly. The backend auto-generates resolvers for relationship fields:

@orm.type(Tag)
class TagType:
    id: auto
    name: auto


@orm.type(Post)
class PostType:
    id: auto
    title: auto
    tags: list[TagType]

If the nested type carries filters and/or order, list relations expose those arguments too.

Custom Strawberry Fields

You can mix generated fields with plain Strawberry fields:

@orm.type(User)
class UserType:
    id: auto
    name: auto
    email: auto

    @strawberry.field
    def display_name(self) -> str:
        return f"{self.name} <{self.email}>"

Type-Level Queryset Scoping

Define a get_queryset classmethod on a type to scope the model query centrally. When the optimizer extension is installed and a resolver returns a backend query object, get_queryset is applied automatically.

@orm.type(Post)
class PublishedPostType:
    id: auto
    title: auto
    is_published: auto

    @classmethod
    def get_queryset(cls, qs, info):
        return qs.filter(is_published=True)  # Django / Tortoise style
        # return qs.where(Post.is_published == True)        # SQLAlchemy style

This is useful for soft-delete filtering, multi-tenant scoping, "published only" content types, and reusable authorization-aware model filters.

orm.input(Model)

Generates a Strawberry input type from model metadata.

CreateUserInput = orm.input(User, include=["name", "email"])

Generated input fields are optional (defaulting to strawberry.UNSET), skip relations, exclude primary keys by default, and exclude sensitive-looking fields unless explicitly included.

Keyword arguments: include, exclude, exclude_pk=False, name.

orm.partial(Model)

UpdateUserInput = orm.partial(User, include=["name", "email"])

Same logic as input() with a default name like UserPartialInput. Useful for patch-style update payloads.


Filters and Ordering

Filters

Generate a filter input and attach it to a type:

UserFilter = orm.filter(User)

@orm.type(User, filters=UserFilter)
class UserType:
    id: auto
    name: auto
    email: auto

List fields returning UserType then accept a filter argument:

{
  users(filter: { field: { name: { exact: "Alice" } } }) {
    id
    name
  }
}

Filter Shape

Filters are recursive @oneOf trees supporting field, all, any, not, and oneOf:

# OR condition
{
  users(filter: {
    any: [
      { field: { name: { exact: "Alice" } } }
      { field: { name: { exact: "Bob" } } }
    ]
  }) { name }
}

# AND condition
{
  posts(filter: {
    all: [
      { field: { authorId: { exact: 1 } } }
      { field: { isPublished: { exact: true } } }
    ]
  }) { title }
}

# NOT condition
{
  users(filter: {
    not: { field: { email: { contains: "example.com" } } }
  }) { name }
}

Built-in Lookup Types

The package exports reusable lookup inputs:

StringLookup, BooleanLookup, IDLookup, IntComparisonLookup, FloatComparisonLookup, DateComparisonLookup, TimeComparisonLookup, DateTimeComparisonLookup

Typical string lookups: exact, neq, contains, iContains, startsWith, iStartsWith, endsWith, iEndsWith, inList, notInList, isNull.

Regex lookups (regex, iRegex) are disabled by default. Enable with enable_regex_filters=True.

Object Traversal

When filters are registered for related models, the generated filter gains an object key that lets you filter parent rows based on conditions on their related objects:

UserFilter = orm.filter(User)
PostFilter = orm.filter(Post)   # Post has an "author" relation to User
{
  posts(filter: {
    object: { author: { field: { name: { exact: "Alice" } } } }
  }) {
    title
  }
}

Object traversal composes with boolean operators:

{
  posts(filter: {
    all: [
      { field: { isPublished: { exact: true } } }
      { object: { author: { field: { name: { exact: "Alice" } } } } }
    ]
  }) { title }
}

Multi-level traversal works when the intermediate models also have registered filters:

UserFilter = orm.filter(User)
PostFilter = orm.filter(Post)
CommentFilter = orm.filter(Comment)  # Comment -> Post -> User
# Find comments on posts written by Alice
{
  comments(filter: {
    object: { post: {
      object: { author: { field: { name: { exact: "Alice" } } } }
    } }
  }) { body }
}

The object type is @oneOf, so each filter entry names exactly one relation.

Relations only appear in object if their target model already has a registered filter at the time orm.filter() is called. Register child model filters before parent model filters.

Filter Projection

By default every relation with a registered filter appears in object. Pass project={...} to orm.filter() to control exactly which relations are exposed and how deep traversal can go:

UserFilter  = orm.filter(User)
TagFilter   = orm.filter(Tag)
CommentFilter = orm.filter(Comment)

# Only expose "author" in the object type — tags and comments are excluded
PostFilter = orm.filter(Post, project={"author": {}})
# This works (author is projected)
{ posts(filter: { object: { author: { field: { name: { exact: "Alice" } } } } }) { title } }

# This would be a schema error (tags is not projected)
{ posts(filter: { object: { tags: { field: { name: { exact: "python" } } } } }) { title } }

Sub-project dicts control nested traversal. An empty dict {} means "include this relation but don't allow further object traversal from it". A non-empty dict lists which of the related model's relations are reachable:

# Allow Comment -> post, and from post -> author (but not post -> tags)
CommentFilter = orm.filter(Comment, project={
    "post": {
        "author": {},
    },
})

Summary:

project value Behavior
None (default) Auto-include all relations with registered filters
{} No object type at all (scalar lookups only)
{"rel": {}} Include rel as a leaf (no further traversal from it)
{"rel": {"nested": {}}} Include rel and allow traversal to nested from it

Projected filters are cached internally and do not overwrite the global filter registry, so you can create multiple projected variants of the same model's filter for different schema entry points.

Ordering

Generate an order input:

UserOrder = orm.order(User)

The generated type is a @oneOf input — each entry specifies exactly one column. The order argument is a list, where position determines tie-break priority:

{
  users(order: [{ name: ASC }, { email: DESC }]) {
    name
    email
  }
}

This sorts by name ascending first, then breaks ties by email descending.

Supported values from the Ordering enum: ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST, DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST.

Filters and ordering can be combined:

{
  posts(
    filter: { field: { isPublished: { exact: true } } }
    order: [{ title: DESC }]
  ) {
    title
  }
}

Queries

Automatic List Fields

If a field returns list[SomeType], orm.field() builds the resolver from the model attached to that type:

@orm.type(User, filters=UserFilter, order=UserOrder)
class UserType:
    id: auto
    name: auto
    email: auto


@strawberry.type
class Query:
    users: list[UserType] = orm.field()

You can also supply filter and order types explicitly:

@strawberry.type
class Query:
    users: list[UserType] = orm.field(filters=UserFilter, order=UserOrder)

Explicit Resolvers

For custom scoping, join logic, or backend-specific behavior, return a backend query object from a normal Strawberry resolver:

@strawberry.type
class Query:
    @strawberry.field
    def active_users(self, info: strawberry.types.Info) -> list[UserType]:
        return select(User).where(User.is_active.is_(True))  # SQLAlchemy
        # return User.objects.filter(is_active=True)         # Django
        # return User.filter(is_active=True)                 # Tortoise

This works with the optimizer extension and with type-level get_queryset hooks.

If you execute your schema asynchronously, the same pattern works with async resolvers too:

@strawberry.type
class Query:
    @strawberry.field
    async def active_users(self, info: strawberry.types.Info) -> list[UserType]:
        return await User.filter(is_active=True)  # Tortoise
        # return await sync_to_async(list)(User.objects.filter(is_active=True))  # Django

Mutations

Write plain @strawberry.mutation resolvers and use strawberry-orm for the generated input types:

CreatePostInput = orm.input(Post, include=["title", "body", "author_id"])


@strawberry.input
class UpdatePostInput:
    id: int
    title: str | None = strawberry.UNSET
    body: str | None = strawberry.UNSET


@strawberry.type
class Mutation:
    @strawberry.mutation
    def create_post(self, info: strawberry.types.Info, input: CreatePostInput) -> PostType:
        post = Post(title=input.title, body=input.body, author_id=input.author_id)
        ...
        return post

    @strawberry.mutation
    def update_post(self, info: strawberry.types.Info, input: UpdatePostInput) -> PostType | None:
        ...

Async mutations work too. Use async ORM calls in the resolver body, and await backend helpers when the active backend/session requires it:

@strawberry.type
class Mutation:
    @strawberry.mutation
    async def create_post(
        self,
        info: strawberry.types.Info,
        input: CreatePostInput,
    ) -> PostType:
        return await Post.create(
            title=input.title,
            body=input.body,
            author_id=input.author_id,
        )

Related List Inputs (orm.ref)

orm.ref(...) generates a @oneOf input for managing related lists:

CreateTagInput = orm.input(Tag, include=["name"])


@strawberry.input
class UpdateTagInput:
    id: strawberry.ID
    name: str


TagRef = orm.ref(Tag, create=CreateTagInput, update=UpdateTagInput, delete=True)

Each ref in the list can be one of:

  • { id: "1" } — link an existing row
  • { create: { ... } } — create a related row inline
  • { update: { id: "...", ... } } — update an existing related row
  • { delete: { id: "..." } } — unlink (or delete if hard_delete_refs=True)

Apply ref operations in a mutation:

@strawberry.mutation
def set_post_tags(self, info: strawberry.types.Info, post_id: int, tags: list[TagRef]) -> PostType | None:
    post = ...
    orm.apply_ref_list(post, "tags", tags, info)
    return post

Async backends can use the same helper from async mutations:

@strawberry.mutation
async def set_post_tags(
    self,
    info: strawberry.types.Info,
    post_id: int,
    tags: list[TagRef],
) -> PostType | None:
    post = await Post.get_or_none(pk=post_id)
    if post is None:
        return None

    await orm.apply_ref_list(post, "tags", tags, info)
    return post
mutation {
  setPostTags(postId: 1, tags: [
    { id: "2" }
    { update: { id: "1", name: "python3" } }
    { create: { name: "new-tag" } }
    { delete: { id: "3" } }
  ]) {
    id
    tags { id name }
  }
}

apply_ref_list supports mode="replace" (default, replaces the entire list) and mode="patch" (only touches mentioned items). An optional authorize callback receives (action, model, obj_id, info) and returns bool.

In practice:

  • use it directly in sync Django / sync SQLAlchemy mutations
  • await it for Tortoise
  • await it for SQLAlchemy when your request context carries an AsyncSession
  • in custom async Django resolvers, prefer the same async-safe pattern you already use for direct ORM calls

Recursive Node Mutations

orm.mutations.create_node(...) and orm.mutations.update_node(...) generate catch-all Relay Node mutations with recursive nested inputs.

If you want to implement the resolver logic yourself, you can generate the root input types directly with orm.mutations.create_node_input(...) and orm.mutations.update_node_input(...):

import strawberry
from strawberry import relay


@orm.type(User)
class UserNode(relay.Node):
    id: relay.NodeID[int]
    name: auto
    email: auto


@orm.type(Post)
class PostNode(relay.Node):
    id: relay.NodeID[int]
    title: auto
    body: auto


CreateNodeInput = orm.mutations.create_node_input()
UpdateNodeInput = orm.mutations.update_node_input()


@strawberry.type
class Mutation:
    create_node = orm.mutations.create_node()
    update_node = orm.mutations.update_node()

    @strawberry.field
    def custom_create_node(self, input: CreateNodeInput) -> str:
        return "implement your own create logic here"

    @strawberry.field
    def custom_update_node(self, input: UpdateNodeInput) -> str:
        return "implement your own update logic here"

List relations use items, while singular relations use explicit create / update branches:

mutation {
  createNode(input: {
    post: {
      title: "Hello"
      body: "World"
      author: {
        create: {
          name: "Alice"
          email: "alice@example.com"
        }
      }
      tags: {
        items: [{ create: { name: "python" } }]
        mode: REPLACE
        onRemove: DELETE
      }
    }
  }) {
    __typename
  }
}

Projection And Policy Config

Pass project={...} to restrict recursion depth and configure relation semantics in one dict:

project = {
    "post": {
        "author": {
            "_meta": {"onReplace": ["DISCONNECT", "DELETE"]},
        },
        "comments": {
            "_meta": {
                "mode": ["PATCH", "REPLACE"],
                "onRemove": ["DISCONNECT", "DELETE"],
            },
            "author": {
                "_meta": {"onReplace": ["DISCONNECT", "DELETE"]},
            },
        },
        "tags": {
            "_meta": {
                "mode": "REPLACE",
                "onRemove": "DELETE",
            },
        },
    },
    "comment": {
        "author": {
            "_meta": {"onReplace": ["DISCONNECT", "DELETE"]},
        },
    },
}


@strawberry.type
class Mutation:
    create_node = orm.mutations.create_node(project=project)
    update_node = orm.mutations.update_node(project=project)

Rules for the config object:

  • Root keys are model names (post, comment, user, ...).
  • Nested keys are relation names available on that model.
  • _meta is optional and configures behavior for that relation subtree.
  • Omitted relations still exist as shallow nested inputs, but recursion stops after one more level.

_meta supports:

  • mode: list relation merge strategy (PATCH or REPLACE)
  • onRemove: what to do with removed items from a list relation (DISCONNECT or DELETE)
  • onReplace: what to do with the previous object when replacing a singular relation (DISCONNECT or DELETE)

The _meta values can be either:

  • an array of enum strings, which means the GraphQL input exposes that field and the caller may choose from those options
  • a single enum string, which fixes that behavior for the relation and omits the corresponding GraphQL field

Default behavior when a field is exposed but omitted in the mutation input:

  • mode defaults to PATCH when allowed
  • onRemove defaults to DISCONNECT when allowed
  • onReplace defaults to DISCONNECT when allowed

If the preferred default is not included in the allowed array, the first configured value is used.


Query Optimization

Add the optimizer extension to your schema:

schema = strawberry.Schema(
    query=Query,
    mutation=Mutation,
    extensions=[orm.optimizer_extension()],
)

The optimizer:

  • executes backend query objects returned by your resolvers
  • eager-loads relations based on the GraphQL selection set
  • applies field-level hints registered through orm.field(...)
  • honors type-level get_queryset hooks

Field Hints

Inside @orm.type(...), orm.field(...) attaches optimizer metadata:

@orm.type(Post)
class PostType:
    id: auto
    title: auto
    tags: list[TagType] = orm.field(load=["author"])
    body: auto = orm.field(only=["id", "title", "body"])
Argument Meaning
load=[...] Extra eager-load paths to apply.
load=callable A callable that customises the queryset for a related field (see below).
only=[...] Restrict loaded columns.
compute={...} Register computed-column hints for the optimizer store.
disable_optimization=True Skip optimization for that field.
description="..." Forward a field description to Strawberry.

Custom Querysets on Related Fields (load=callable)

When load is a callable instead of a list, it receives the default queryset for the related model and returns a modified queryset. This lets you filter, reorder, or limit related objects from the parent level:

@orm.type(User)
class UserType:
    id: auto
    name: auto
    posts: list[PostType] = orm.field(
        load=lambda qs: qs.filter(is_published=True)
    )

How each backend applies the callable:

  • Django — wraps the relation in a Prefetch object with the custom queryset.
  • SQLAlchemy — extracts WHERE criteria from the modified select() and applies them via relationship.and_(...).
  • Tortoise — performs a separate batch query filtered by parent IDs and assigns results back to each parent instance.

This composes with type-level get_queryset. If the related type defines get_queryset and the field has a load callable, both are applied (type-level first, then the field-level callable):

@orm.type(Post)
class PublishedPostType:
    id: auto
    title: auto

    @classmethod
    def get_queryset(cls, qs, info):
        return qs.filter(is_published=True)


@orm.type(User)
class UserType:
    id: auto
    name: auto
    posts: list[PublishedPostType] = orm.field(
        load=lambda qs: qs.order_by("-created_at")
    )

The optimizer handles batching, so this avoids N+1 queries even with custom filtering.

Field Permissions

Use make_field(...) to attach Strawberry permission classes to a generated field:

from strawberry_orm import make_field


@orm.type(User)
class UserType:
    id: auto
    name: auto
    email: auto = make_field(permission_classes=[IsAuthenticated])

Security

strawberry-orm has safety-focused defaults, but you still need to make deliberate schema choices.

Defaults:

  • orm.input(), orm.filter(), and orm.order() exclude sensitive-looking fields such as password_hash, api_key, role, and is_admin by default
  • String regex filters are disabled by default
  • Filter depth, branch count, and inList size are capped by default
  • orm.ref(..., delete=True) unlinks by default; hard deletes require hard_delete_refs=True

Caveats:

  • orm.type() does not auto-hide sensitive output fields. It warns by default, but you must still use exclude=[...] or permission classes to protect them.
  • List queries are unbounded unless you set default_query_limit=...
  • apply_ref_list() only enforces authorization if you provide an authorize callback
  • GraphQL introspection, auth, and query-complexity limits are still your application's responsibility

A production-oriented configuration:

orm = StrawberryORM(
    "sqlalchemy",
    dialect="postgresql",
    session_getter=lambda info: info.context["session"],
    default_query_limit=100,
    max_filter_depth=8,
    max_filter_branches=25,
    max_in_list_size=200,
)

Public Exports

Top-level exports from strawberry_orm:

StrawberryORM, auto, make_field, make_ref_type, Ordering, FieldDefinition, FieldHints, OptimizerExtension, OptimizerStore, UNSET, and the built-in lookup input classes from strawberry_orm.filters.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

strawberry_orm-0.4.0.tar.gz (48.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

strawberry_orm-0.4.0-py3-none-any.whl (56.0 kB view details)

Uploaded Python 3

File details

Details for the file strawberry_orm-0.4.0.tar.gz.

File metadata

  • Download URL: strawberry_orm-0.4.0.tar.gz
  • Upload date:
  • Size: 48.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for strawberry_orm-0.4.0.tar.gz
Algorithm Hash digest
SHA256 df33f967882e398c9c11b547d234e6ce66cfb778c2110b33c3049012d4308659
MD5 ce66b401d332e98573377e3cf61b4e49
BLAKE2b-256 ff9885a0d7dca05be02df0e03945ffaaca05422dfecc7a96569ff2bdac7e3443

See more details on using hashes here.

Provenance

The following attestation bundles were made for strawberry_orm-0.4.0.tar.gz:

Publisher: release.yml on strawberry-graphql/strawberry-orm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file strawberry_orm-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: strawberry_orm-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 56.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for strawberry_orm-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 300e4835860c5b51f1af436efa9fb2444a5f5531a38c5cbd7a3ce7109a1acec9
MD5 df9cfbc4ee35f338c0cd232f41858861
BLAKE2b-256 8f7681bc152eef86f03f3702dc6d88a43d1039fd9695c5fec998bf56e60b5ab5

See more details on using hashes here.

Provenance

The following attestation bundles were made for strawberry_orm-0.4.0-py3-none-any.whl:

Publisher: release.yml on strawberry-graphql/strawberry-orm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page