Skip to content

Type-safe "optional-nullable" fields #3779

Open
@nrbnlulu

Description

@nrbnlulu

Preface

it is a common practice in strawberry that when your data layer have
an optional field i.e

class Person:
    name: str
    phone: str | None = None

and you want to update it you would use UNSET in the mutation input
in order to check whether this field provided by the client or not like so:

@strawberry.input
class UpdatePersonInput:
    id: strawberry.ID
    name: str| None
    phone: str | None = UNSET

@strawberry.mutation
def update_person(input: UpdatePersonInput) -> Person:
    inst = service.get_person(input.id)
    if name := input.name:
         inst.name = name
    if input.phone is not UNSET:
        inst.phone = input.phone  # ❌ not type safe
    
    service.save(inst)

Note that this is not an optimization rather a business requirement.
if the user wants to nullify the phone it won't be possible other wise
OTOH you might nullify the phone unintentionally.

This approach can cause lots of bugs since you need to remember that you have
used UNSET and to handle this correspondingly.

Since strawberry claims to

Strawberry leverages Python type hints to provide a great developer experience while creating GraphQL Libraries.

it is only natural for us to provide a typesafe way to mitigate this.

Proposal

The Option type.which will require only this minimal implementation

import dataclasses


@dataclasses.dataclass
class Some[T]:
    value: T
    
    def some(self) -> Some[T | None] | None:
      return self
    
@dataclasses.dataclass
class Nothing[T]:
    def some(self) -> Some[T | None] | None:
      return None 

Maybe[T] = Some[T] | Nothing[T]

and this is how you'd use it

@strawberry.input
class UpdatePersonInput:
    id: strawberry.ID
    name: str| None
    phone: Maybe[str | None]

@strawberry.mutation
def update_person(input: UpdatePersonInput) -> Person:
    inst = service.get_person(input.id)
    if name := input.name:
         inst.name = name
    if phone := input.phone.some(): 
        inst.phone = phone.value  # ✅  type safe
    
    service.save(inst)

Currently if you want to know if a field was provided

Backward compat

UNSET can remain as is for existing codebases.
Option would be handled separately.

which Option library should we use?

  1. Don't use any library craft something minimal our own as suggested above.
  2. ** use something existing**

The sad truth is that there are no well-maintained libs in the ecosystem.

Never the less it is not hard to maintain something just for strawberry since the implementation
is rather straight forward and not much features are needed. we can fork either

and just forget about it.

  1. allow users to decide
# before anything

strawberry.register_option_type((MyOptionType, NOTHING))

then strawberry could use that and you could use whatever you want.

  • Core functionality
  • Alteration (enhancement/optimization) of existing feature(s)
  • New behavior

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions