strawberry-input-extensions


Namestrawberry-input-extensions JSON
Version 0.0.2 PyPI version JSON
download
home_pageNone
SummaryStrawberry GraphQL input validation/transformation extensions
upload_time2025-08-11 03:09:01
maintainerNone
docs_urlNone
authorNone
requires_python<4.0,>=3.9
licenseNone
keywords graphql strawberry-graphql
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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.


### This package should currently be considered unstable, and not used in production. Don't count on SemVer versioning being representative of non-breaking changes until a stable 1.0 release.  


## 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

```python
# 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`:

```python
class ToUpperCase(InputExtension):
    def resolve(self, value, info, next_, path):
        return next_(value.upper())
```

Extensions can exit early if need be:
```python
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.

```python
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 minimum
- `MaxValue(value)` - Ensures numeric value is at most the maximum
- `BetweenValue(min, max)` - Ensures numeric value is within range

### Length Validation
- `MinLength(length)` - Ensures string/sequence is at least the minimum length
- `MaxLength(length)` - Ensures string/sequence is at most the maximum length
- `BetweenLength(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:

```python
@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:

```python
@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:

```python
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

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "strawberry-input-extensions",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.9",
    "maintainer_email": null,
    "keywords": "graphql, strawberry-graphql",
    "author": null,
    "author_email": "Michael Thomas <michael@metatick.net>",
    "download_url": "https://files.pythonhosted.org/packages/38/b6/a4689b3b31bda496781866f55b8171cb6635fc8c679935fd95f683951f01/strawberry_input_extensions-0.0.2.tar.gz",
    "platform": null,
    "description": "# Strawberry GraphQL Input Extensions\r\n\r\nA 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.\r\n\r\n\r\n### This package should currently be considered unstable, and not used in production. Don't count on SemVer versioning being representative of non-breaking changes until a stable 1.0 release.  \r\n\r\n\r\n## Overview\r\n\r\nThis 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.\r\n\r\n## Key Components\r\n\r\n- **InputExtension**: Base class for creating custom input extensions\r\n- **InputExtensionsExtension**: Strawberry extension that integrates with GraphQL schema\r\n- **Built-in validators**: Ready-to-use extensions for common validation scenarios\r\n- **Exception handling**: Structured error reporting for validation failures\r\n\r\n## Usage\r\n\r\n### Basic Example\r\n\r\n```python\r\n# Use extensions in your GraphQL types as annotations\r\n@strawberry.type\r\nclass Mutation:\r\n    @strawberry.mutation(extensions=[InputExtensionsExtension()])\r\n    def create_user(\r\n        self, \r\n        username: MaxLength[str, 20],\r\n        age: MinValue[int, 18]\r\n    ) -> str:\r\n        return f\"Created user {username} ({age})\"\r\n\r\n\r\n# Or via Annotated if you prefer\r\n@strawberry.type\r\nclass Mutation:\r\n    @strawberry.mutation(extensions=[InputExtensionsExtension()])\r\n    def create_user(\r\n        self, \r\n        username: Annotated[str, MaxLength(20)],\r\n        age: Annotated[int, MinValue(18)]\r\n    ) -> str:\r\n        return f\"Created user {username} ({age})\"\r\n```\r\n\r\n### Custom Extensions\r\n\r\nCreate your own extensions by subclassing `InputExtension`:\r\n\r\n```python\r\nclass ToUpperCase(InputExtension):\r\n    def resolve(self, value, info, next_, path):\r\n        return next_(value.upper())\r\n```\r\n\r\nExtensions can exit early if need be:\r\n```python\r\nclass UnsetIfNoPermission(InputExtension):\r\n    def __init__(self, permission):\r\n        self.permission = permission\r\n        \r\n    def resolve(self, value, info, next_, path):\r\n        user = get_current_user(info)\r\n        if not user_has_permission(user, self.permission):\r\n            # no permission for the field, return UNSET as if it wasn't set\r\n            return UNSET\r\n        # remaining extensions are only for users with permissions\r\n        return next_(value)\r\n\r\n@strawberry.input\r\nclass BlogInput:\r\n    title: NonNullableOptional[UnsetIfNoPermission[str, 'edit:title']] = UNSET\r\n```\r\n\r\n\r\n### Input types\r\n\r\nInput fields can be used as expected, and you can also perform object level extensions using a decorator.\r\n\r\n```python\r\nclass ValidatePasswordsMatch(InputExtension):\r\n    def resolve(self, value, info, next_, path):\r\n        if value.password != value.confirm_password:\r\n            # raise the error against the password field\r\n            raise InputExtensionFieldException(\"Passwords don't match\", \"password\", info)\r\n        return next_(value)\r\n    \r\n# Since they're just annotated types, they don't need to be declared in-line\r\nPasswordField = MinLength[str, 8]\r\n\r\n@ValidatePasswordsMatch.decorator()\r\n@strawberry.input\r\nclass MyInput:\r\n    password: PasswordField\r\n    confirm_password: PasswordField\r\n```\r\n\r\n\r\n## Built-in Extensions\r\n\r\n### Value Validation\r\n- `MinValue(value)` - Ensures numeric value is at least the minimum\r\n- `MaxValue(value)` - Ensures numeric value is at most the maximum\r\n- `BetweenValue(min, max)` - Ensures numeric value is within range\r\n\r\n### Length Validation\r\n- `MinLength(length)` - Ensures string/sequence is at least the minimum length\r\n- `MaxLength(length)` - Ensures string/sequence is at most the maximum length\r\n- `BetweenLength(min, max)` - Ensures string/sequence length is within range\r\n\r\n### Optional Handling\r\n- `NonNullableOptional` - Makes an Optional field reject null values while still being optional\r\n\r\n## Combining Extensions\r\n\r\nExtensions can be chained to apply multiple validations/transformations:\r\n\r\n```python\r\n@strawberry.type\r\nclass Mutation:\r\n    @strawberry.mutation(extensions=[InputExtensionsExtension()])\r\n    def create_user(\r\n        self, \r\n        # called outside-in, eg. BetweenLength is called first, then ToUpperCase    \r\n        username: BetweenLength[ToUpperCase[str], 3, 20]\r\n        \r\n        # called in reverse order, so this is identical to the above\r\n        username: Annotated[\r\n            str, \r\n            ToUpperCase(),\r\n            BetweenLength(3, 20)\r\n        ]\r\n    ) -> str:\r\n        return f\"Created user {username}\"\r\n```\r\n\r\n## Nested Validation\r\n\r\nExtensions work with nested input types and lists:\r\n\r\n```python\r\n@strawberry.input\r\nclass UserInput:\r\n    username: MaxLength[str, 20]\r\n    roles: MinLength[List[str], 1]\r\n     # either UNSET or a valid string, never null\r\n    favorite_ide: NonNullableOptional[ToUpperCase[str]] = UNSET\r\n    \r\n\r\n@strawberry.type\r\nclass Mutation:\r\n    @strawberry.mutation(extensions=[InputExtensionsExtension()])\r\n    def create_user(self, input: UserInput) -> str:\r\n        return f\"Created user {input.username}\"\r\n```\r\n\r\n## Async Support\r\n\r\nThe extension system supports async resolvers:\r\n\r\n```python\r\nclass AsyncExtension(InputExtension):\r\n    async def resolve_async(self, value, info, next_, path):\r\n        # Perform async validation/transformation\r\n        return await next_(value)\r\n```\r\nBy default, resolve_async calls resolve(), so this can be omitted unless you're actually doing async work in the extension\r\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Strawberry GraphQL input validation/transformation extensions",
    "version": "0.0.2",
    "project_urls": {
        "Documentation": "https://github.com/metatick/strawberry-input-extensions",
        "Repository": "https://github.com/metatick/strawberry-input-extensions"
    },
    "split_keywords": [
        "graphql",
        " strawberry-graphql"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "4f4bfcd279ccafed57e80a1cb12bcf831640576b7dace5be74dc7ae518e15bb8",
                "md5": "d48783a2ae4a591eb09e6d33365d872c",
                "sha256": "faee95b7d74eafb9f54d5d5d5c7981a757aae730606d252f9f5a7e2def2c904a"
            },
            "downloads": -1,
            "filename": "strawberry_input_extensions-0.0.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "d48783a2ae4a591eb09e6d33365d872c",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.9",
            "size": 10849,
            "upload_time": "2025-08-11T03:09:00",
            "upload_time_iso_8601": "2025-08-11T03:09:00.462761Z",
            "url": "https://files.pythonhosted.org/packages/4f/4b/fcd279ccafed57e80a1cb12bcf831640576b7dace5be74dc7ae518e15bb8/strawberry_input_extensions-0.0.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "38b6a4689b3b31bda496781866f55b8171cb6635fc8c679935fd95f683951f01",
                "md5": "87cbd1d62540e26333038468d1a2614b",
                "sha256": "660035cc1a2b0dbab5cd4b3848c0987ea79e0ad9bec7102700a3a21cb8389576"
            },
            "downloads": -1,
            "filename": "strawberry_input_extensions-0.0.2.tar.gz",
            "has_sig": false,
            "md5_digest": "87cbd1d62540e26333038468d1a2614b",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.9",
            "size": 11015,
            "upload_time": "2025-08-11T03:09:01",
            "upload_time_iso_8601": "2025-08-11T03:09:01.818752Z",
            "url": "https://files.pythonhosted.org/packages/38/b6/a4689b3b31bda496781866f55b8171cb6635fc8c679935fd95f683951f01/strawberry_input_extensions-0.0.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-11 03:09:01",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "metatick",
    "github_project": "strawberry-input-extensions",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "strawberry-input-extensions"
}
        
Elapsed time: 1.11049s