Integrate GraphQL with your Pydantic models

Overview

Graphene Logo graphene-pydantic Build status PyPI version Coverage Status

A Pydantic integration for Graphene.

Installation

pip install "graphene-pydantic"

Examples

Here is a simple Pydantic model:

import uuid
import pydantic

class PersonModel(pydantic.BaseModel):
    id: uuid.UUID
    first_name: str
    last_name: str

To create a GraphQL schema for it you simply have to write the following:

import graphene
from graphene_pydantic import PydanticObjectType

class Person(PydanticObjectType):
    class Meta:
        model = PersonModel
        # exclude specified fields
        exclude_fields = ("id",)

class Query(graphene.ObjectType):
    people = graphene.List(Person)

    @staticmethod
    def resolve_people(parent, info):
        # fetch actual PersonModels here
        return [PersonModel(id=uuid.uuid4(), first_name="Beth", last_name="Smith")]

schema = graphene.Schema(query=Query)

Then you can simply query the schema:

query = """
    query {
      people {
        firstName,
        lastName
      }
    }
"""
result = schema.execute(query)
print(result.data['people'][0])

Input Object Types

You can also create input object types from Pydantic models for mutations and queries:

from graphene_pydantic import PydanticInputObjectType

class PersonInput(PydanticInputObjectType):
    class Meta:
        model = PersonModel
        # exclude specified fields
        exclude_fields = ("id",)

class CreatePerson(graphene.Mutation):
    class Arguments:
        person = PersonInput()

    Output = Person

    @staticmethod
    def mutate(parent, info, person):
        personModel = PersonModel(id=uuid.uuid4(), first_name=person.first_name, last_name=person.last_name)
        # save PersonModel here
        return person

class Mutation(graphene.ObjectType):
    createPerson = CreatePerson.Field()

schema = graphene.Schema(mutation=Mutation)

Then execute with the input:

mutation = '''
mutation {
    createPerson(person: {
        firstName: "Jerry",
        lastName: "Smith"
    }) {
        firstName
    }
}
'''
result = schema.execute(mutation)
print(result.data['createPerson']['firstName'])

Custom resolve functions

Since PydanticObjectType inherits from graphene.ObjectType you can add custom resolve functions as explained here. For instance:

class Person(PydanticObjectType):
    class Meta:
        model = PersonModel
        # exclude specified fields
        exclude_fields = ("id",)
        
    full_name = graphene.String()

    def resolve_full_name(self, info, **kwargs):
        return self.first_name + ' ' + self.last_name

Forward declarations and circular references

graphene_pydantic supports forward declarations and circular references, but you will need to call the resolve_placeholders() method to ensure the types are fully updated before you execute a GraphQL query. For instance:

class NodeModel(BaseModel):
    id: int
    name: str
    labels: 'LabelsModel'
    
class LabelsModel(BaseModel):
    node: NodeModel
    labels: typing.List[str]
    
class Node(PydanticObjectType):
    class Meta:
        model = NodeModel
        
class Labels(PydanticObjectType):
    class Meta:
        model = LabelsModel
        

Node.resolve_placeholders()  # make the `labels` field work
Labels.resolve_placeholders()  # make the `node` field work

Full Examples

Please see the examples directory for more.

License

This project is under the Apache License.

Third Party Code

This project depends on third-party code which is subject to the licenses set forth in Third Party Licenses.

Contributing

Please see the Contributing Guide. Note that you must sign the CLA.

Caveats

Mappings

Note that even though Pydantic is perfectly happy with fields that hold mappings (e.g. dictionaries), because GraphQL's type system doesn't have them those fields can't be exported to Graphene types. For instance, this will fail with an error Don't know how to handle mappings in Graphene:

import typing
from graphene_pydantic import PydanticObjectType

class Pet:
  pass

class Person:
  name: str
  pets_by_name: typing.Dict[str, Pet]
  
class GraphQLPerson(PydanticObjectType):  
  class Meta:
    model = Person

However, note that if you use exclude_fields or only_fields to exclude those values, there won't be a problem:

class GraphQLPerson(PydanticObjectType):
  class Meta:
    model = Person
    exclude_fields = ("pets_by_name",)

Union types

There are some caveats when using Unions. Let's take the following pydantic models as an example for this section:

class EmployeeModel(pydantic.BaseModel):
    name: str


class ManagerModel(EmployeeModel):
    title: str


class DepartmentModel(pydantic.BaseModel):
    employees: T.List[T.Union[ManagerModel, EmployeeModel]]
You have to implement the class method is_type_of in the graphene models

To get the Union between ManagerModel and EmployeeModel to successfully resolve in graphene, you need to implement is_type_of like this:

class Employee(PydanticObjectType):
    class Meta:
        model = EmployeeModel

    @classmethod
    def is_type_of(cls, root, info):
        return isinstance(root, (cls, EmployeeModel))


class Manager(PydanticObjectType):
    class Meta:
        model = ManagerModel

    @classmethod
    def is_type_of(cls, root, info):
        return isinstance(root, (cls, ManagerModel))


class Department(PydanticObjectType):
    class Meta:
        model = DepartmentModel

Otherwise GraphQL will throw an error similar to "[GraphQLError('Abstract type UnionOfManagerModelEmployeeModel must resolve to an Object type at runtime for field Department.employees ..."

For unions between subclasses, you need to put the subclass first in the type annotation

Looking at the employees field above, if you write the type annotation with Employee first, employees: T.List[T.Union[EmployeeModel, ManagerModel]], you will not be able to query manager-related fields (in this case title). In a query containing a spread like this:

...on Employee {
  name
}
...on Manager {
  name
  title
}

... the objects will always resolve to being an Employee. This can be avoided if you put the subclass first in the list of annotations: employees: T.List[T.Union[ManagerModel, EmployeeModel]].

Unions between subclasses don't work in Python 3.6

If a field on a model is a Union between a class and a subclass (as in our example), Python 3.6's typing will not preserve the Union and throws away the annotation for the subclass. See this issue for more details. The solution at present is to use Python 3.7.

Input Object Types don't support unions as fields

This is a GraphQL limitation. See this RFC for the progress on supporting input unions. If you see an error like '{union-type} may only contain Object types', you are most likely encountering this limitation.

Comments
  • Pydantic compatibility features

    Pydantic compatibility features

    issue - https://github.com/graphql-python/graphene-pydantic/issues/73

    Python 3.9

    https://github.com/graphql-python/graphene-pydantic/pull/75/files#diff-7a099366beb78ce7e417d3c73fef1dcb56772e9c163a7b0e9a4680cb29d7b1c1R193

    Perhaps here you can somehow check that the field in the model is Optional and then use this class?

    override_input_fields - Perhaps there is a better solution.

    opened by dima-dmytruk23 10
  • Great work on the package

    Great work on the package

    Hi team!

    I think this package could be a great addition to the GraphQL-Python organization. Let me know if you would be interested in adding this repo to the org! ❤️

    opened by syrusakbary 9
  • Pydantic Version Error

    Pydantic Version Error

    When installing graphene-pydantic with pip, I get this error message

    ERROR: After October 2020 you may experience errors when installing or updating packages. This is because pip will change the way that it resolves dependency conflicts.
    
    We recommend you use --use-feature=2020-resolver to test your packages with the new resolver before it becomes the default.
    
    graphene-pydantic 0.1.0 requires pydantic<=1.6,>=1.0, but you'll have pydantic 1.6.1 which is incompatible.
    

    This does not affect installation in any way but it would help updating the library to use the latest pydantic version

    opened by moluwole 7
  • Added support for InputObjectTypes.

    Added support for InputObjectTypes.

    Title

    Please describe the feature(s) added, bug(s) fixed, etc here at a high level, and go into detail in further paragraphs if necessary.

    Upside CLA

    opened by excessdenied 7
  • AssertionError: Found different types with the same name in the schema: Foo, Foo.

    AssertionError: Found different types with the same name in the schema: Foo, Foo.

    @necaris I get AssertionError: Found different types with the same name in the schema: Foo, Foo. if I run test_query from test_forward_refs.py twice!

    opened by plopd 6
  • graphql.error.located_error.GraphQLLocatedError: name 'get_hackers' is not defined

    graphql.error.located_error.GraphQLLocatedError: name 'get_hackers' is not defined

    I'm sorry for the title being vague but I didn't know how to present to you the problem.

    Basically I was trying to hack up an API using FastAPI, Pydantic and GraphQL. I found this repo from an Issue there. Following the readme, I couldn't query the data using GraphiQl(I hacked starlette and currently running on graphql playground). for this query

    query {
      hackers {
        id
      }
    }
    

    I'm getting an error like this

    {
      "error": {
        "data": {
          "hackers": null
        },
        "errors": [
          {
            "message": "name 'get_hackers' is not defined",
            "locations": [
              {
                "line": 2,
                "column": 3
              }
            ],
            "path": [
              "hackers"
            ]
          }
        ]
      }
    }
    

    My Pydantic Basemodel and ObjectType as

    class HackerModel(BaseModel):
        id: int
        name: str
        college: str
        email: str
        dob: date
    
    class Hacker(PydanticObjectType):
        class Meta:
            model = HackerModel
            only_fields=("name","college","id","email","dob")
        class Config:
            arbitrary_types_allowed = True
    

    And my Query Class Looks like this

    class Query(graphene.ObjectType):
        hackers = graphene.List(Hacker)
        def resolve_hackers(self, info):
            return get_hackers()
    

    I only followed your Readme Example . Any idea on How to resolve this?

    opened by athul 6
  • Make it graphene 2 compatible

    Make it graphene 2 compatible

    Title

    From graphene 2.* to graphene 3.0, the name of the first argument for graphene.types.field.Field.__init__ changed from type to type_, which caused some incompatibility issue. This PR is to fix the issue to make sure that it can also work well with graphene.2.*.

    opened by conglei 5
  • Support for pydantic 1.8

    Support for pydantic 1.8

    Currently, library is built for pydantic:

    pydantic = ">=1.0,<1.7"
    

    But newer version 1.8.2 of pydantic is currently available making it impossible to use graphene-pydantic with this version.

    opened by deepaerial 4
  • Support for Dict types

    Support for Dict types

    In the following example, add the foo: Dict[str, str] member to Person model causes an error. Is there another way that I can use a Dict, which works great in pydantic?

    import uuid
    from typing import Dict
    
    import pydantic
    import graphene
    from graphene_pydantic import PydanticObjectType
    
    
    class PersonModel(pydantic.BaseModel):
        id: uuid.UUID
        first_name: str
        last_name: str
        foo: Dict[str, str]
    
    
    
    class Person(PydanticObjectType):
        class Meta:
            model = PersonModel
    
    class Query(graphene.ObjectType):
        people = graphene.List(Person)
    
        @staticmethod
        def resolve_people(parent, info):
            # fetch actual PersonModels here
            return [PersonModel(id=uuid.uuid4(), first_name="Beth", last_name="Smith")]
    
    
    Produces:
    
    Traceback (most recent call last):
      File "/usr/lib/python3.8/runpy.py", line 194, in _run_module_as_main
        return _run_code(code, main_globals, None,
      File "/usr/lib/python3.8/runpy.py", line 87, in _run_code
        exec(code, run_globals)
      File "/home/dylan/.vscode/extensions/ms-python.python-2021.2.582707922/pythonFiles/lib/python/debugpy/__main__.py", line 45, in <module>
        cli.main()
      File "/home/dylan/.vscode/extensions/ms-python.python-2021.2.582707922/pythonFiles/lib/python/debugpy/../debugpy/server/cli.py", line 444, in main
        run()
      File "/home/dylan/.vscode/extensions/ms-python.python-2021.2.582707922/pythonFiles/lib/python/debugpy/../debugpy/server/cli.py", line 285, in run_file
        runpy.run_path(target_as_str, run_name=compat.force_str("__main__"))
      File "/usr/lib/python3.8/runpy.py", line 265, in run_path
        return _run_module_code(code, init_globals, run_name,
      File "/usr/lib/python3.8/runpy.py", line 97, in _run_module_code
        _run_code(code, mod_globals, init_globals,
      File "/usr/lib/python3.8/runpy.py", line 87, in _run_code
        exec(code, run_globals)
      File "/home/dylan/work/als-computing/splash-ml/examples/test_graphql.py", line 17, in <module>
        class Person(PydanticObjectType):
      File "/home/dylan/work/als-computing/splash-ml/env/lib/python3.8/site-packages/graphene/types/objecttype.py", line 30, in __new__
        base_cls = super().__new__(
      File "/home/dylan/work/als-computing/splash-ml/env/lib/python3.8/site-packages/graphene/utils/subclass_with_meta.py", line 46, in __init_subclass__
        super_class.__init_subclass_with_meta__(**options)
      File "/home/dylan/work/als-computing/splash-ml/env/lib/python3.8/site-packages/graphene_pydantic/objecttype.py", line 90, in __init_subclass_with_meta__
        construct_fields(
      File "/home/dylan/work/als-computing/splash-ml/env/lib/python3.8/site-packages/graphene_pydantic/objecttype.py", line 46, in construct_fields
        converted = convert_pydantic_field(
      File "/home/dylan/work/als-computing/splash-ml/env/lib/python3.8/site-packages/graphene_pydantic/converters.py", line 130, in convert_pydantic_field
        convert_pydantic_type(
      File "/home/dylan/work/als-computing/splash-ml/env/lib/python3.8/site-packages/graphene_pydantic/converters.py", line 166, in convert_pydantic_type
        raise ConversionError("Don't know how to handle mappings in Graphene.")
    graphene_pydantic.converters.ConversionError: Don't know how to handle mappings in Graphene.  
    
    opened by dylanmcreynolds 4
  • Upgrade to graphene v3

    Upgrade to graphene v3

    Hey, I just spend some time upgrading to graphene v3, it was surprisingly uncomplicated. Tests are green on my end, so lets see what CI has to say ;) Unfortunately I dont have a project using graphene-pydantic, so I had no chance to test my changes in a realistic environment. This PR will solve #36.

    I might messed up the tox.ini file in an attempt to add graphene v3 there. (lemme know).

    Just for reference: the graphene v3 update tracking issue. It was decided in this comment that all graphene integrations shall be updated to graphene v3 before graphene itself will exit beta. (thats why I updated this package, even though I dont use it).

    opened by DoctorJohn 4
  • json string support?

    json string support?

    pydantic has support for Json field, i think that maps directly to https://docs.graphene-python.org/en/latest/_modules/graphene/types/json/ of graphene?

    opened by hyusetiawan 4
  • Pydantic - Graphene throws error for discriminator input objects.

    Pydantic - Graphene throws error for discriminator input objects.

    I am using graphene-pydantic to generate a GraphQL schema for my mutation. I have gone through the documentation and it's working fine for all the types but the problem is when I use discriminators in the modules. Below is the sample code with discriminators and that's throwing an error.

    from graphene_pydantic import PydanticInputObjectType, PydanticObjectType
    import graphene
    from typing import Literal, Union
    from pydantic import BaseModel, Field
    
    
    class Cat(BaseModel):
        pet_type: Literal['cat']
        meows: int
    
    
    class Dog(BaseModel):
        pet_type: Literal['dog']
        barks: float
    
    
    class Lizard(BaseModel):
        pet_type: Literal['reptile', 'lizard']
        scales: bool
    
    
    class Model(BaseModel):
        pet: Union[Cat, Dog, Lizard] = Field(..., discriminator='pet_type')
        n: int
    
    
    # print(Model(pet={'pet_type': 'dog', 'barks': 3.14, 'eats': 'biscuit'}, n=1))
    
    
    class Input(PydanticInputObjectType):
        class Meta:
            model = Model
            # exclude specified fields
            exclude_fields = ("id",)
    
    
    class Output(PydanticObjectType):
        class Meta:
            model = Model
            # exclude specified fields
            exclude_fields = ("id",)
    
    
    class CreateAnimal(graphene.Mutation):
        class Arguments:
            input = Input()
    
        output = Output
    
        @staticmethod
        def mutate(parent, info, input):
            print(input)
            # save model here
            return input
    
    
    class Mutation(graphene.ObjectType):
        createPerson = CreateAnimal.Field()
    
    
    schema = graphene.Schema(mutation=Mutation)
    print(schema)
    

    The error getting from graphene is like below and it is like a generalized error.

    File "\AppData\Local\Programs\Python\Python310\lib\site-packages\graphql\type\definition.py", line 1338, in fields    raise TypeError(f"{self.name} fields cannot be resolved. {error}")
    TypeError: Input fields cannot be resolved. The input field type must be a GraphQL input type.
    

    Can someone help on this?

    opened by maheshchowdam523 3
  • Support Constrained types

    Support Constrained types

    Currently attempting to convert a constrained type will result in:

    graphene_pydantic.converters.ConversionError: Don't know how to convert the Pydantic field ModelField(name='id', type=ConstrainedIntValue, required=True) (<class 'pydantic.types.ConstrainedIntValue'>)
    
    opened by lovetoburnswhen 3
  • Model inheritance does not convert correctly

    Model inheritance does not convert correctly

    Use Case Scenario

    I've two pydantic models. Each in his own module (file). One Model, the TUser depends on the TAddress.

    address_model.py

    class TAddress(BaseModel):
        id: Optional[int]
        address: Optional[constr(max_length=100, strip_whitespace=True)]
        city: Optional[constr(max_length=80, strip_whitespace=True)]
        postal_code: Optional[constr(max_length=15, strip_whitespace=True)]
        country: Optional[constr(max_length=3)]
    

    user_model.py

    class TUser(BaseModel):
        id: UUID = None
        email: Optional[EmailStr]
    
        address: Optional[TAddress]
    
        is_active: Optional[bool]
        is_email_verified: Optional[bool]
        created_at: Optional[datetime.datetime]
    

    If I use the TUser model know for my PydanticObjectType

    class UserType(PydanticObjectType):
        class Meta:
            model = TUser
    

    I get the following error message:

    graphene_pydantic.converters.ConversionError: Don't know how to convert the Pydantic field ModelField(name='address', type=Optional[TAddress], required=False, default=None) (<class 'app.address.serializers.TAddress'>)
    

    It seems like that there is a problem when using pydantic models from different modules that depends on each other. What is the solution for this use case?

    Any idea?

    opened by VerzCar 1
  • Pydantic -> Graphene type conversion breaks when using freezegun

    Pydantic -> Graphene type conversion breaks when using freezegun

    this schema

    class TestSchema(BaseModel):
        date_field: datetime.date
    

    breaks in tests that use freezegun to freeze time:

    E   graphene_pydantic.converters.ConversionError: Don't know how to convert the Pydantic field ModelField(name='date_field', type=date, required=True) (<class 'datetime.date'>)
    

    I believe the issue is because freezegun overwrites the type of datetime.date and datetime.datetime, so these lines in the graphene_pydantic converter (find_graphene_type()) don't evaluate to true:

    elif type_ == datetime.date:
        return Date
    

    pydantic code: https://github.com/graphql-python/graphene-pydantic/blob/master/graphene_pydantic/converters.py#L186 freezegun code: https://github.com/spulec/freezegun/blob/master/freezegun/api.py#L637 related freezegun issue: https://github.com/spulec/freezegun/issues/170

    I'm not sure if this is a weird fix or not, but changing the if condition to:

    elif type_.__name__ == "date"
    

    or

    elif issubclass(type_, datetime.date):
    

    fixes this use case.

    A better suggestion (though I don't know the best way to implement) is to allow a custom type mappings so we don't have to rely on this switch statement.

    opened by emasatsugu 0
Releases(0.3.0)
Owner
GraphQL Python
GraphQL Python
The Foundation for All Legate Libraries

Legate The Legate project endeavors to democratize computing by making it possible for all programmers to leverage the power of large clusters of CPUs

Legate 144 Dec 26, 2022
tartiflette-aiohttp is a wrapper of aiohttp which includes the Tartiflette GraphQL Engine, do not hesitate to take a look of the Tartiflette project.

tartiflette-aiohttp is a wrapper of aiohttp which includes the Tartiflette GraphQL Engine. You can take a look at the Tartiflette API documentation. U

tartiflette 60 Nov 08, 2022
Django GraphQL User Management

Django GraphQL User Management An app that explores User management with GraphQL using Graphene in Django. Topics covered: Login. Log Out. Authenticat

0101 Solutions 4 Feb 22, 2022
A library to help construct a graphql-py server supporting react-relay

Relay Library for GraphQL Python GraphQL-relay-py is the Relay library for GraphQL-core. It allows the easy creation of Relay-compliant servers using

GraphQL Python 143 Nov 15, 2022
This is a minimal project using graphene with django and user authentication to expose a graphql endpoint.

Welcome This is a minimal project using graphene with django and user authentication to expose a graphql endpoint. Definitely checkout how I have mana

yosef salmalian 1 Nov 18, 2021
A Python dependency resolver

python-resolver A Python dependency resolver. Issues Only supports wheels (no sdists!) Usage Python library import packaging.requirements import resol

Filipe Laíns 19 Jun 29, 2022
Modular, cohesive, transparent and fast web server template

kingdom-python-server 🐍 Modular, transparent, batteries (half) included, lightning fast web server. Features a functional, isolated business layer wi

T10 20 Feb 08, 2022
A real time webchat made in graphql

Graphql Chat. This is a real time webchat made in graphql. Description Welcome to my webchat api, here i put my knowledge in graphql to work. Requirem

Nathan André 1 Jan 03, 2022
Graphene MongoEngine integration

Graphene-Mongo A Mongoengine integration for Graphene. Installation For installing graphene-mongo, just run this command in your shell pip install gra

GraphQL Python 261 Dec 31, 2022
ASGI support for the Tartiflette GraphQL engine

tartiflette-asgi is a wrapper that provides ASGI support for the Tartiflette Python GraphQL engine. It is ideal for serving a GraphQL API over HTTP, o

tartiflette 99 Dec 27, 2022
Translate APIs described by OpenAPI Specifications (OAS) into GraphQL

OpenAPI-to-GraphQL Translate APIs described by OpenAPI Specifications (OAS) or Swagger into GraphQL. Getting started OpenAPI-to-GraphQL can be used in

International Business Machines 1.4k Dec 29, 2022
Lightning fast and portable programming language!

Photon Documentation in English Lightning fast and portable programming language! What is Photon? Photon is a programming language aimed at filling th

William 58 Dec 27, 2022
Integrate GraphQL with your Pydantic models

graphene-pydantic A Pydantic integration for Graphene. Installation pip install "graphene-pydantic" Examples Here is a simple Pydantic model: import u

GraphQL Python 179 Jan 02, 2023
Tyk Open Source API Gateway written in Go, supporting REST, GraphQL, TCP and gRPC protocols

Tyk API Gateway Tyk is an open source Enterprise API Gateway, supporting REST, GraphQL, TCP and gRPC protocols. Tyk Gateway is provided ‘Batteries-inc

Tyk Technologies 8k Jan 09, 2023
Integrate GraphQL into your Django project.

Graphene-Django A Django integration for Graphene. 💬 Join the community on Slack Documentation Visit the documentation to get started! Quickstart For

GraphQL Python 4k Dec 31, 2022
Fastapi strawberry graphql

fastapi-strawberry-graphql Quick and dirty 🍓 python python --version Python 3.10 pip pip install sqlalchemy pip install sqlmodel pip install fastapi

Rodrigo Ney 7 Oct 19, 2022
Blazing fast GraphQL endpoints finder using subdomain enumeration, scripts analysis and bruteforce.

Graphinder Graphinder is a tool that extracts all GraphQL endpoints from a given domain. Run with docker docker run -it -v $(pwd):/usr/bin/graphinder

Escape 76 Dec 28, 2022
Simple GraphQL client for Python 2.7+

python-graphql-client Simple GraphQL client for Python 2.7+ Install pip install graphqlclient Usage from graphqlclient import GraphQLClient client =

Prisma Labs 150 Nov 29, 2022
Gerenciar a velocidade da internet banda larga

Monitoramento da Velocidade da internet 📶 Status do Projeto: ✔️ (pronto) Tópicos ✍️ Descrição do projeto Funcionalidades Deploy da Aplicação Pré-requ

Bárbara Guerbas de Figueiredo 147 Nov 02, 2022
Enable idempotent operations in POST and PATCH endpoints

Idempotency Header ASGI Middleware A middleware for making POST and PATCH endpoints idempotent. The purpose of the middleware is to guarantee that exe

Sondre Lillebø Gundersen 12 Dec 28, 2022