Strawberry GraphQL input validation/transformation extensions
Project description
Strawberry GraphQL Input Extensions
A simple extension system for Strawberry GraphQL that provides declarative input validation and transformation through Python type annotations. This extension allows you to add validation rules and transformations to your GraphQL inputs while maintaining clean, readable code.
Overview
This module implements a flexible extension system for Strawberry GraphQL that enables validation and transformation of input values in GraphQL operations. It supports both synchronous and asynchronous operations, and handles nested input types, lists, and optional values.
Key Components
- InputExtension: Base class for creating custom input extensions
- InputExtensionsExtension: Strawberry extension that integrates with GraphQL schema
- Built-in validators: Ready-to-use extensions for common validation scenarios
- Exception handling: Structured error reporting for validation failures
Usage
Basic Example
# Use extensions in your GraphQL types as annotations
@strawberry.type
class Mutation:
@strawberry.mutation(extensions=[InputExtensionsExtension()])
def create_user(
self,
username: MaxLength[str, 20],
age: MinValue[int, 18]
) -> str:
return f"Created user {username} ({age})"
# Or via Annotated if you prefer
@strawberry.type
class Mutation:
@strawberry.mutation(extensions=[InputExtensionsExtension()])
def create_user(
self,
username: Annotated[str, MaxLength(20)],
age: Annotated[int, MinValue(18)]
) -> str:
return f"Created user {username} ({age})"
Custom Extensions
Create your own extensions by subclassing InputExtension:
class ToUpperCase(InputExtension):
def resolve(self, value, info, next_, path):
return next_(value.upper())
Extensions can exit early if need be:
class UnsetIfNoPermission(InputExtension):
def __init__(self, permission):
self.permission = permission
def resolve(self, value, info, next_, path):
user = get_current_user(info)
if not user_has_permission(user, self.permission):
# no permission for the field, return UNSET as if it wasn't set
return UNSET
# remaining extensions are only for users with permissions
return next_(value)
@strawberry.input
class BlogInput:
title: NonNullableOptional[UnsetIfNoPermission[str, 'edit:title']] = UNSET
Input types
Input fields can be used as expected, and you can also perform object level extensions using a decorator.
class ValidatePasswordsMatch(InputExtension):
def resolve(self, value, info, next_, path):
if value.password != value.confirm_password:
# raise the error against the password field
raise InputExtensionFieldException("Passwords don't match", "password", info)
return next_(value)
# Since they're just annotated types, they don't need to be declared in-line
PasswordField = MinLength[str, 8]
@ValidatePasswordsMatch.decorator()
@strawberry.input
class MyInput:
password: PasswordField
confirm_password: PasswordField
Built-in Extensions
Value Validation
MinValue(value)- Ensures numeric value is at least the minimumMaxValue(value)- Ensures numeric value is at most the maximumBetweenValue(min, max)- Ensures numeric value is within range
Length Validation
MinLength(length)- Ensures string/sequence is at least the minimum lengthMaxLength(length)- Ensures string/sequence is at most the maximum lengthBetweenLength(min, max)- Ensures string/sequence length is within range
Optional Handling
NonNullableOptional- Makes an Optional field reject null values while still being optional
Combining Extensions
Extensions can be chained to apply multiple validations/transformations:
@strawberry.type
class Mutation:
@strawberry.mutation(extensions=[InputExtensionsExtension()])
def create_user(
self,
# called outside-in, eg. BetweenLength is called first, then ToUpperCase
username: BetweenLength[ToUpperCase[str], 3, 20]
# called in reverse order, so this is identical to the above
username: Annotated[
str,
ToUpperCase(),
BetweenLength(3, 20)
]
) -> str:
return f"Created user {username}"
Nested Validation
Extensions work with nested input types and lists:
@strawberry.input
class UserInput:
username: MaxLength[str, 20]
roles: MinLength[List[str], 1]
# either UNSET or a valid string, never null
favorite_ide: NonNullableOptional[ToUpperCase[str]] = UNSET
@strawberry.type
class Mutation:
@strawberry.mutation(extensions=[InputExtensionsExtension()])
def create_user(self, input: UserInput) -> str:
return f"Created user {input.username}"
Async Support
The extension system supports async resolvers:
class AsyncExtension(InputExtension):
async def resolve_async(self, value, info, next_, path):
# Perform async validation/transformation
return await next_(value)
By default, resolve_async calls resolve(), so this can be omitted unless you're actually doing async work in the extension
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
File details
Details for the file strawberry_input_extensions-0.0.1.tar.gz.
File metadata
- Download URL: strawberry_input_extensions-0.0.1.tar.gz
- Upload date:
- Size: 10.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
be6130b3f3a1e3a5ed670dcc25ff5eb8e704c75b2df2294ee68078bc05268d62
|
|
| MD5 |
32c0e2f2eb7d8014da4b3a90882f5963
|
|
| BLAKE2b-256 |
b378c3ff1e0a18f1667e1c9e17c93c721f7fcc3206a87e1a9c855c942e55c2ce
|