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-ormis 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
- Create a backend instance.
- Generate Strawberry types from ORM models with
@orm.type(...). - Generate filter/order types from the same models.
- Expose list fields with
orm.field(). - 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
filterandorderarguments automatically becauseUserTypecarries 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
SessionandAsyncSession - 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.sessioninfo.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=UserFilterorder=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 ifhard_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
awaitit for Tortoiseawaitit for SQLAlchemy when your request context carries anAsyncSession- 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.
_metais 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 (PATCHorREPLACE)onRemove: what to do with removed items from a list relation (DISCONNECTorDELETE)onReplace: what to do with the previous object when replacing a singular relation (DISCONNECTorDELETE)
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:
modedefaults toPATCHwhen allowedonRemovedefaults toDISCONNECTwhen allowedonReplacedefaults toDISCONNECTwhen 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_querysethooks
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
Prefetchobject with the custom queryset. - SQLAlchemy — extracts
WHEREcriteria from the modifiedselect()and applies them viarelationship.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(), andorm.order()exclude sensitive-looking fields such aspassword_hash,api_key,role, andis_adminby default- String regex filters are disabled by default
- Filter depth, branch count, and
inListsize are capped by default orm.ref(..., delete=True)unlinks by default; hard deletes requirehard_delete_refs=True
Caveats:
orm.type()does not auto-hide sensitive output fields. It warns by default, but you must still useexclude=[...]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 anauthorizecallback- 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
df33f967882e398c9c11b547d234e6ce66cfb778c2110b33c3049012d4308659
|
|
| MD5 |
ce66b401d332e98573377e3cf61b4e49
|
|
| BLAKE2b-256 |
ff9885a0d7dca05be02df0e03945ffaaca05422dfecc7a96569ff2bdac7e3443
|
Provenance
The following attestation bundles were made for strawberry_orm-0.4.0.tar.gz:
Publisher:
release.yml on strawberry-graphql/strawberry-orm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
strawberry_orm-0.4.0.tar.gz -
Subject digest:
df33f967882e398c9c11b547d234e6ce66cfb778c2110b33c3049012d4308659 - Sigstore transparency entry: 1149392776
- Sigstore integration time:
-
Permalink:
strawberry-graphql/strawberry-orm@81e1408e858998fc0829acb8d75b97c7913336c4 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/strawberry-graphql
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@81e1408e858998fc0829acb8d75b97c7913336c4 -
Trigger Event:
workflow_dispatch
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
300e4835860c5b51f1af436efa9fb2444a5f5531a38c5cbd7a3ce7109a1acec9
|
|
| MD5 |
df9cfbc4ee35f338c0cd232f41858861
|
|
| BLAKE2b-256 |
8f7681bc152eef86f03f3702dc6d88a43d1039fd9695c5fec998bf56e60b5ab5
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
strawberry_orm-0.4.0-py3-none-any.whl -
Subject digest:
300e4835860c5b51f1af436efa9fb2444a5f5531a38c5cbd7a3ce7109a1acec9 - Sigstore transparency entry: 1149392816
- Sigstore integration time:
-
Permalink:
strawberry-graphql/strawberry-orm@81e1408e858998fc0829acb8d75b97c7913336c4 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/strawberry-graphql
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@81e1408e858998fc0829acb8d75b97c7913336c4 -
Trigger Event:
workflow_dispatch
-
Statement type: