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
GraphQL security auditing script with a focus on performing batch GraphQL queries and mutations

BatchQL BatchQL is a GraphQL security auditing script with a focus on performing batch GraphQL queries and mutations. This script is not complex, and

Assetnote 267 Dec 24, 2022
This is a graphql api build using ariadne python that serves a graphql-endpoint at port 3002 to perform language translation and identification using deep learning in python pytorch.

Language Translation and Identification this machine/deep learning api that will be served as a graphql-api using ariadne, to perform the following ta

crispengari 2 Dec 30, 2021
A Django GraphQL Starter that uses graphene and graphene_django to interface GraphQL.

Django GraphQL Starter GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data... According to the doc

0101 Solutions 1 Jan 10, 2022
MGE-GraphQL is a Python library for building GraphQL mutations fast and easily

MGE-GraphQL Introduction MGE-GraphQL is a Python library for building GraphQL mutations fast and easily. Data Validations: A similar data validation w

MGE Software 4 Apr 23, 2022
Adds GraphQL support to your Flask application.

Flask-GraphQL Adds GraphQL support to your Flask application. Usage Just use the GraphQLView view from flask_graphql from flask import Flask from flas

GraphQL Python 1.3k Dec 31, 2022
Pygitstats - a package that allows you to use the GitHub GraphQL API with ease in your Python programs

Pygitstats - a package that allows you to use the GitHub GraphQL API with ease in your Python programs

Dillon Barnes 4 Mar 29, 2022
GraphQL framework for Python

Graphene ?? Join the community on Slack We are looking for contributors! Please check the ROADMAP to see how you can help ❤️ The below readme is the d

GraphQL Python 7.5k Jan 1, 2023
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 8, 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
GraphQL is a query language and execution engine tied to any backend service.

GraphQL The GraphQL specification is edited in the markdown files found in /spec the latest release of which is published at https://graphql.github.io

GraphQL 14k Jan 1, 2023
GraphQL framework for Python

Graphene ?? Join the community on Slack We are looking for contributors! Please check the ROADMAP to see how you can help ❤️ The below readme is the d

GraphQL Python 7.5k Jan 1, 2023
Ariadne is a Python library for implementing GraphQL servers using schema-first approach.

Ariadne Ariadne is a Python library for implementing GraphQL servers. Schema-first: Ariadne enables Python developers to use schema-first approach to

Mirumee Labs 1.9k Jan 1, 2023
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
GraphQL Engine built with Python 3.6+ / asyncio

Tartiflette is a GraphQL Server implementation built with Python 3.6+. Summary Motivation Status Usage Installation Installation dependencies Tartifle

tartiflette 839 Dec 31, 2022
Django registration and authentication with GraphQL.

Django GraphQL Auth Django registration and authentication with GraphQL. Demo About Abstract all the basic logic of handling user accounts out of your

pedrobern 301 Dec 9, 2022
A new GraphQL library for Python 🍓

Strawberry GraphQL Python GraphQL library based on dataclasses Installation ( Quick Start ) The quick start method provides a server and CLI to get go

Strawberry GraphQL 2.8k Jan 1, 2023
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 8, 2022
(Now finding maintainer) 🐍A Pythonic way to provide JWT authentication for Flask-GraphQL

Flask-GraphQL-Auth What is Flask-GraphQL-Auth? Flask-GraphQL-Auth is JWT decorator for flask-graphql inspired from Flask-JWT-Extended. all you have to

Seonghyeon Kim 64 Feb 19, 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