Merge branch 'master' into next/master

This commit is contained in:
Jonathan Kim 2019-08-09 19:12:41 +01:00 committed by GitHub
commit dc812fe028
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 1279 additions and 433 deletions

17
.github/stale.yml vendored Normal file
View File

@ -0,0 +1,17 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

2
.gitignore vendored
View File

@ -11,6 +11,8 @@ __pycache__/
# Distribution / packaging # Distribution / packaging
.Python .Python
env/ env/
venv/
.venv/
build/ build/
develop-eggs/ develop-eggs/
dist/ dist/

View File

@ -1,29 +1,41 @@
language: python language: python
matrix: dist: xenial
include:
- env: TOXENV=py36 python:
python: 3.6 - "3.6"
- env: TOXENV=py37 - "3.7"
python: 3.7
dist: xenial
sudo: true
- env: TOXENV=pre-commit
python: 3.6
- env: TOXENV=mypy
python: 3.6
install: install:
- pip install coveralls tox - pip install tox tox-travis
script: tox script: tox
after_success: coveralls after_success:
- pip install coveralls
- coveralls
cache: cache:
directories: directories:
- $HOME/.cache/pip - $HOME/.cache/pip
- $HOME/.cache/pre-commit - $HOME/.cache/pre-commit
deploy:
provider: pypi stages:
user: syrusakbary - test
on: - name: deploy
tags: true if: tag IS present
password:
secure: LHOp9DvYR+70vj4YVY8+JRNCKUOfYZREEUY3+4lMUpY7Zy5QwDfgEMXG64ybREH9dFldpUqVXRj53eeU3spfudSfh8NHkgqW7qihez2AhSnRc4dK6ooNfB+kLcSoJ4nUFGxdYImABc4V1hJvflGaUkTwDNYVxJF938bPaO797IvSbuI86llwqkvuK2Vegv9q/fy9sVGaF9VZIs4JgXwR5AyDR7FBArl+S84vWww4vTFD33hoE88VR4QvFY3/71BwRtQrnCMm7AOm31P9u29yi3bpzQpiOR2rHsgrsYdm597QzFKVxYwsmf9uAx2bpbSPy2WibunLePIvOFwm8xcfwnz4/J4ONBc5PSFmUytTWpzEnxb0bfUNLuYloIS24V6OZ8BfAhiYZ1AwySeJCQDM4Vk1V8IF6trTtyx5EW/uV9jsHCZ3LFsAD7UnFRTosIgN3SAK3ZWCEk5oF2IvjecsolEfkRXB3q9EjMkkuXRUeFDH2lWJLgNE27BzY6myvZVzPmfwZUsPBlPD/6w+WLSp97Rjgr9zS3T1d4ddqFM4ZYu04f2i7a/UUQqG+itzzuX5DWLPvzuNt37JB45mB9IsvxPyXZ6SkAcLl48NGyKok1f3vQnvphkfkl4lni29woKhaau8xlsuEDrcwOoeAsVcZXiItg+l+z2SlIwM0A06EvQ= jobs:
distributions: "sdist bdist_wheel" fast_finish: true
include:
- env: TOXENV=pre-commit
python: 3.7
- env: TOXENV=mypy
python: 3.7
- stage: deploy
python: 3.7
after_success: true
deploy:
provider: pypi
user: syrusakbary
on:
tags: true
password:
secure: LHOp9DvYR+70vj4YVY8+JRNCKUOfYZREEUY3+4lMUpY7Zy5QwDfgEMXG64ybREH9dFldpUqVXRj53eeU3spfudSfh8NHkgqW7qihez2AhSnRc4dK6ooNfB+kLcSoJ4nUFGxdYImABc4V1hJvflGaUkTwDNYVxJF938bPaO797IvSbuI86llwqkvuK2Vegv9q/fy9sVGaF9VZIs4JgXwR5AyDR7FBArl+S84vWww4vTFD33hoE88VR4QvFY3/71BwRtQrnCMm7AOm31P9u29yi3bpzQpiOR2rHsgrsYdm597QzFKVxYwsmf9uAx2bpbSPy2WibunLePIvOFwm8xcfwnz4/J4ONBc5PSFmUytTWpzEnxb0bfUNLuYloIS24V6OZ8BfAhiYZ1AwySeJCQDM4Vk1V8IF6trTtyx5EW/uV9jsHCZ3LFsAD7UnFRTosIgN3SAK3ZWCEk5oF2IvjecsolEfkRXB3q9EjMkkuXRUeFDH2lWJLgNE27BzY6myvZVzPmfwZUsPBlPD/6w+WLSp97Rjgr9zS3T1d4ddqFM4ZYu04f2i7a/UUQqG+itzzuX5DWLPvzuNt37JB45mB9IsvxPyXZ6SkAcLl48NGyKok1f3vQnvphkfkl4lni29woKhaau8xlsuEDrcwOoeAsVcZXiItg+l+z2SlIwM0A06EvQ=
distributions: "sdist bdist_wheel"

View File

@ -1,97 +0,0 @@
<h1 align="center">Sponsors &amp; Backers</h1>
Graphene is an MIT-licensed open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/graphql-python/graphene/blob/master/BACKERS.md). If you'd like to join them, please consider:
- [Become a backer or sponsor on Patreon](https://www.patreon.com/syrusakbary).
- [One-time donation via PayPal.](https://graphene-python.org/support-graphene/)
<br><br>
<!--<h2 align="center">Special Sponsors</h2>
<p align="center">
<a href="https://stdlib.com" target="_blank">
<img width="260px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
</a>
</p>
<!--special end-->
<h2 align="center">Platinum via Patreon</h2>
<!--platinum start-->
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.patreon.com/join/syrusakbary" target="_blank">
<img width="222px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
</a>
</td>
</tr>
</tbody>
</table>
<h2 align="center">Gold via Patreon</h2>
<!--gold start-->
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.patreon.com/join/syrusakbary" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
</a>
</td>
</tr>
</tbody>
</table>
<!--gold end-->
<h2 align="center">Silver via Patreon</h2>
<!--silver start-->
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.patreon.com/join/syrusakbary" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
</a>
</td>
</tr>
</tbody>
</table>
<!--silver end-->
<h2 align="center">Bronze via Patreon</h2>
<!--bronze start-->
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.patreon.com/join/syrusakbary" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
</a>
</td>
</tr>
</tbody>
</table>
<!--bronze end-->
<h2 align="center">Generous Backers via Patreon ($50+)</h2>
<!--50 start-->
- [Lee Benson](https://github.com/leebenson)
- [Become a Patron](https://www.patreon.com/join/syrusakbary)
<!--50 end-->
<h2 align="center">Backers via Patreon</h2>
<!--10 start-->
- [Become a Patron](https://www.patreon.com/join/syrusakbary)
<!--10 end-->

View File

@ -1,3 +1,3 @@
/ @syrusakbary @ekampf @dan98765 @projectcheshire * @ekampf @dan98765 @projectcheshire @jkimbo
/docs/ @dvndrsn @phalt @changeling /docs/ @dvndrsn @phalt @changeling
/examples/ @dvndrsn @phalt @changeling /examples/ @dvndrsn @phalt @changeling

View File

@ -3,9 +3,17 @@ help:
@echo "Please use \`make <target>' where <target> is one of" @echo "Please use \`make <target>' where <target> is one of"
@grep -E '^\.PHONY: [a-zA-Z_-]+ .*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = "(: |##)"}; {printf "\033[36m%-30s\033[0m %s\n", $$2, $$3}' @grep -E '^\.PHONY: [a-zA-Z_-]+ .*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = "(: |##)"}; {printf "\033[36m%-30s\033[0m %s\n", $$2, $$3}'
.PHONY: install-dev ## Install development dependencies
install-dev:
pip install -e ".[test]"
test:
py.test graphene
.PHONY: docs ## Generate docs .PHONY: docs ## Generate docs
docs: docs: install-dev
@cd docs &&\ cd docs && make install && make html
pip install -r requirements.txt &&\
make html &&\ .PHONY: docs-live ## Generate docs with live reloading
cd - docs-live: install-dev
cd docs && make install && make livehtml

View File

@ -4,73 +4,6 @@
# ![Graphene Logo](http://graphene-python.org/favicon.png) [Graphene](http://graphene-python.org) [![Build Status](https://travis-ci.org/graphql-python/graphene.svg?branch=master)](https://travis-ci.org/graphql-python/graphene) [![PyPI version](https://badge.fury.io/py/graphene.svg)](https://badge.fury.io/py/graphene) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene?branch=master) # ![Graphene Logo](http://graphene-python.org/favicon.png) [Graphene](http://graphene-python.org) [![Build Status](https://travis-ci.org/graphql-python/graphene.svg?branch=master)](https://travis-ci.org/graphql-python/graphene) [![PyPI version](https://badge.fury.io/py/graphene.svg)](https://badge.fury.io/py/graphene) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene?branch=master)
<h1 align="center">Supporting Graphene Python</h1>
Graphene is an MIT-licensed open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/graphql-python/graphene/blob/master/BACKERS.md). If you'd like to join them, please consider:
- [Become a backer or sponsor on Patreon](https://www.patreon.com/syrusakbary).
- [One-time donation via PayPal.](https://graphene-python.org/support-graphene/)
<!--<h2 align="center">Special Sponsors</h2>
<p align="center">
<a href="https://stdlib.com" target="_blank">
<img width="260px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
</a>
</p>
<!--special end-->
<h2 align="center">Platinum via Patreon</h2>
<!--platinum start-->
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.patreon.com/join/syrusakbary" target="_blank">
<img width="222px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
</a>
</td>
</tr>
</tbody>
</table>
<h2 align="center">Gold via Patreon</h2>
<!--gold start-->
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.patreon.com/join/syrusakbary" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
</a>
</td>
</tr>
</tbody>
</table>
<!--gold end-->
<h2 align="center">Silver via Patreon</h2>
<!--silver start-->
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.patreon.com/join/syrusakbary" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
</a>
</td>
</tr>
</tbody>
</table>
<!--silver end-->
---
## Introduction ## Introduction
[Graphene](http://graphene-python.org) is a Python library for building GraphQL schemas/types fast and easily. [Graphene](http://graphene-python.org) is a Python library for building GraphQL schemas/types fast and easily.

View File

@ -1,33 +1,54 @@
# Graphene Roadmap # GraphQL Python Roadmap
In order to move Graphene and the GraphQL Python ecosystem forward I realized is essential to be clear with the community on next steps, so we can move uniformly. In order to move Graphene and the GraphQL Python ecosystem forward it's essential to be clear with the community on next steps, so we can move uniformly.
There are few key points that need to happen in the short/mid term, divided into two main sections:
- [Community](#community)
- [Graphene 3](#graphene-3)
_👋 If you have more ideas on how to move the Graphene ecosystem forward, don't hesistate to [open a PR](https://github.com/graphql-python/graphene/edit/master/ROADMAP.md)_ _👋 If you have more ideas on how to move the Graphene ecosystem forward, don't hesistate to [open a PR](https://github.com/graphql-python/graphene/edit/master/ROADMAP.md)_
## Community
The goal is to improve adoption and sustainability of the project. ## Now
- [ ] Continue to support v2.x with security releases
- [ ] Last major/feature release is cut and graphene-* libraries should pin to that version number
- 💎 Add Commercial Support for Graphene - [See issue](https://github.com/graphql-python/graphene/issues/813) ## Next
- Create [Patreon page](https://www.patreon.com/syrusakbary) New features will only be developed on version 3 of ecosystem libraries.
- Add [/support-graphene page](https://graphene-python.org/support-graphene/) in Graphene website
- 📘 Vastly improve documentation - [See issue](https://github.com/graphql-python/graphene/issues/823)
- ~~💰 Apply for [Mozilla MOSS](https://www.mozilla.org/en-US/moss/) sponsorship~~ (not for now)
## Graphene 3 ### [Core-Next](https://github.com/graphql-python/graphql-core-next)
Targeted as v3 of [graphql-core](https://pypi.org/project/graphql-core/), Python 3 only
The goal is to summarize the different improvements that Graphene will need to accomplish for version 3. ### Graphene
- [ ] Integrate with the core-next API and resolve all breaking changes
- [ ] GraphQL types from type annotations - [See issue](https://github.com/graphql-python/graphene/issues/729)
- [ ] Add support for coroutines in Connection, Mutation (abstracting out Promise requirement) - [See PR](https://github.com/graphql-python/graphene/pull/824)
In a nushell, Graphene 3 should take the Python 3 integration one step forward while still maintaining compatibility with Python 2. ### Graphene-*
- [ ] Integrate with the graphene core-next API and resolve all breaking changes
- 🚀 [graphql-core-next](https://github.com/graphql-python/graphql-core-next) GraphQL engine support (almost same API as graphql-core) ### *-graphql
- 🔸 GraphQL types from type annotations - [See issue](https://github.com/graphql-python/graphene/issues/729) - [ ] Integrate with the graphql core-next API and resolve all breaking changes
- 📄 Schema creation from SDL (API TBD)
- ✨ Improve connections structure ## Ongoing Initiatives
- 📗 Improve function documentation - [ ] Improve documentation, especially for new users to the library
- 🔀 Add support for coroutines in Connection, Mutation (abstracting out Promise requirement) - [See PR](https://github.com/graphql-python/graphene/pull/824) - [ ] Recipes for “quick start” that people can ideally use/run
## Dependent Libraries
| Repo | Release Manager | CODEOWNERS | Pinned | next/master created | Labels Standardized |
| ---------------------------------------------------------------------------- | --------------- | ---------- | ---------- | ------------------- | ------------------- |
| [graphene](https://github.com/graphql-python/graphene) | ekampf | ✅ | | ✅ | |
| [graphql-core](https://github.com/graphql-python/graphql-core) | Cito | ✅ | N/A | N/A | |
| [graphql-core-next](https://github.com/graphql-python/graphql-core-next) | Cito | ✅ | N/A | N/A | |
| [graphql-server-core](https://github.com/graphql-python/graphql-server-core) | Cito | | ✅ | ✅ | |
| [gql](https://github.com/graphql-python/gql) | ekampf | | | | |
| [gql-next](https://github.com/graphql-python/gql-next) | ekampf | | N/A | N/A | |
| ...[aiohttp](https://github.com/graphql-python/aiohttp-graphql) | | | | | |
| ...[django](https://github.com/graphql-python/graphene-django) | mvanlonden | | ✅ | ✅ | |
| ...[sanic](https://github.com/graphql-python/sanic-graphql) | ekampf | | | | |
| ...[flask](https://github.com/graphql-python/flask-graphql) | | | | | |
| ...[webob](https://github.com/graphql-python/webob-graphql) | | | | | |
| ...[tornado](https://github.com/graphql-python/graphene-tornado) | ewhauser | | PR created | ✅ | |
| ...[ws](https://github.com/graphql-python/graphql-ws) | Cito/dfee | | ✅ | ✅ | |
| ...[gae](https://github.com/graphql-python/graphene-gae) | ekampf | | PR created | ✅ | |
| ...[sqlalchemy](https://github.com/graphql-python/graphene-sqlalchemy) | jnak/Nabell | ✅ | ✅ | ✅ | |
| ...[mongo](https://github.com/graphql-python/graphene-mongo) | | | ✅ | ✅ | |
| ...[relay-py](https://github.com/graphql-python/graphql-relay-py) | Cito | | | | |
| ...[wsgi](https://github.com/moritzmhmk/wsgi-graphql) | | | | | |

View File

@ -2,30 +2,29 @@
Big changes from v0.10.x to 1.0. While on the surface a lot of this just looks like shuffling around API, the entire codebase has been rewritten to handle some really great use cases and improved performance. Big changes from v0.10.x to 1.0. While on the surface a lot of this just looks like shuffling around API, the entire codebase has been rewritten to handle some really great use cases and improved performance.
## Backwards Compatibility and Deprecation Warnings ## Backwards Compatibility and Deprecation Warnings
This has been a community project from the start, we need your help making the upgrade as smooth as possible for everybody! This has been a community project from the start, we need your help making the upgrade as smooth as possible for everybody!
We have done our best to provide backwards compatibility with deprecated APIs. We have done our best to provide backwards compatibility with deprecated APIs.
## Deprecations ## Deprecations
* `with_context` is no longer needed. Resolvers now always take the context argument. - `with_context` is no longer needed. Resolvers now always take the context argument.
Before: Before:
```python ```python
def resolve_xxx(self, args, info): def resolve_xxx(root, args, info):
# ... # ...
``` ```
With 1.0: With 1.0:
```python ```python
def resolve_xxx(self, args, context, info): def resolve_xxx(root, args, context, info):
# ... # ...
``` ```
* `ObjectType` and `Interface` no longer accept the `abstract` option in the `Meta`. - `ObjectType` and `Interface` no longer accept the `abstract` option in the `Meta`.
Inheriting fields should be now achieved using `AbstractType` inheritance. Inheriting fields should be now achieved using `AbstractType` inheritance.
Before: Before:
@ -42,6 +41,7 @@ We have done our best to provide backwards compatibility with deprecated APIs.
``` ```
With 1.0: With 1.0:
```python ```python
class MyBaseQuery(graphene.AbstractType): class MyBaseQuery(graphene.AbstractType):
my_field = String() my_field = String()
@ -50,9 +50,9 @@ We have done our best to provide backwards compatibility with deprecated APIs.
pass pass
``` ```
* The `type_name` option in the Meta in types is now `name` - The `type_name` option in the Meta in types is now `name`
* Type references no longer work with strings, but with functions. - Type references no longer work with strings, but with functions.
Before: Before:
@ -70,7 +70,6 @@ We have done our best to provide backwards compatibility with deprecated APIs.
users = graphene.List(lambda: User) users = graphene.List(lambda: User)
``` ```
## Schema ## Schema
Schemas in graphene `1.0` are `Immutable`, that means that once you create a `graphene.Schema` any Schemas in graphene `1.0` are `Immutable`, that means that once you create a `graphene.Schema` any
@ -80,7 +79,6 @@ The `name` argument is removed from the Schema.
The arguments `executor` and `middlewares` are also removed from the `Schema` definition. The arguments `executor` and `middlewares` are also removed from the `Schema` definition.
You can still use them, but by calling explicitly in the `execute` method in `graphql`. You can still use them, but by calling explicitly in the `execute` method in `graphql`.
```python ```python
# Old way # Old way
schema = graphene.Schema(name='My Schema') schema = graphene.Schema(name='My Schema')
@ -94,7 +92,6 @@ schema = graphene.Schema(
) )
``` ```
## Interfaces ## Interfaces
For implementing an Interface in an ObjectType, you have to add it onto `Meta.interfaces`. For implementing an Interface in an ObjectType, you have to add it onto `Meta.interfaces`.
@ -131,7 +128,7 @@ class ReverseString(Mutation):
reversed = String() reversed = String()
def mutate(self, args, context, info): def mutate(root, args, context, info):
reversed = args.get('input')[::-1] reversed = args.get('input')[::-1]
return ReverseString(reversed=reversed) return ReverseString(reversed=reversed)
@ -158,14 +155,13 @@ class Query(ObjectType):
Also, if you wanted to create an `ObjectType` that implements `Node`, you have to do it Also, if you wanted to create an `ObjectType` that implements `Node`, you have to do it
explicity. explicity.
## Django ## Django
The Django integration with Graphene now has an independent package: `graphene-django`. The Django integration with Graphene now has an independent package: `graphene-django`.
For installing, you have to replace the old `graphene[django]` with `graphene-django`. For installing, you have to replace the old `graphene[django]` with `graphene-django`.
* As the package is now independent, you now have to import from `graphene_django`. - As the package is now independent, you now have to import from `graphene_django`.
* **DjangoNode no longer exists**, please use `relay.Node` instead: - **DjangoNode no longer exists**, please use `relay.Node` instead:
```python ```python
from graphene.relay import Node from graphene.relay import Node
@ -181,8 +177,8 @@ For installing, you have to replace the old `graphene[django]` with `graphene-dj
The SQLAlchemy integration with Graphene now has an independent package: `graphene-sqlalchemy`. The SQLAlchemy integration with Graphene now has an independent package: `graphene-sqlalchemy`.
For installing, you have to replace the old `graphene[sqlalchemy]` with `graphene-sqlalchemy`. For installing, you have to replace the old `graphene[sqlalchemy]` with `graphene-sqlalchemy`.
* As the package is now independent, you have to import now from `graphene_sqlalchemy`. - As the package is now independent, you have to import now from `graphene_sqlalchemy`.
* **SQLAlchemyNode no longer exists**, please use `relay.Node` instead: - **SQLAlchemyNode no longer exists**, please use `relay.Node` instead:
```python ```python
from graphene.relay import Node from graphene.relay import Node

View File

@ -7,20 +7,22 @@ It also improves the field resolvers, [simplifying the code](#simpler-resolvers)
developer has to write to use them. developer has to write to use them.
**Deprecations:** **Deprecations:**
* [`AbstractType`](#abstracttype-deprecated)
* [`resolve_only_args`](#resolve_only_args) - [`AbstractType`](#abstracttype-deprecated)
* [`Mutation.Input`](#mutationinput) - [`resolve_only_args`](#resolve_only_args)
- [`Mutation.Input`](#mutationinput)
**Breaking changes:** **Breaking changes:**
* [`Simpler Resolvers`](#simpler-resolvers)
* [`Node Connections`](#node-connections) - [`Simpler Resolvers`](#simpler-resolvers)
- [`Node Connections`](#node-connections)
**New Features!** **New Features!**
* [`InputObjectType`](#inputobjecttype)
* [`Meta as Class arguments`](#meta-as-class-arguments) (_only available for Python 3_)
- [`InputObjectType`](#inputobjecttype)
- [`Meta as Class arguments`](#meta-as-class-arguments) (_only available for Python 3_)
> The type metaclasses are now deleted as they are no longer necessary. If your code was depending > The type metaclasses are now deleted as they are no longer necessary. If your code was depending
> on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/v2.0.0/graphene/tests/issues/test_425.py). > on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/v2.0.0/graphene/tests/issues/test_425.py).
## Deprecations ## Deprecations
@ -49,7 +51,7 @@ class Pet(CommonFields, Interface):
pass pass
``` ```
### resolve\_only\_args ### resolve_only_args
`resolve_only_args` is now deprecated as the resolver API has been simplified. `resolve_only_args` is now deprecated as the resolver API has been simplified.
@ -60,8 +62,8 @@ class User(ObjectType):
name = String() name = String()
@resolve_only_args @resolve_only_args
def resolve_name(self): def resolve_name(root):
return self.name return root.name
``` ```
With 2.0: With 2.0:
@ -70,8 +72,8 @@ With 2.0:
class User(ObjectType): class User(ObjectType):
name = String() name = String()
def resolve_name(self, info): def resolve_name(root, info):
return self.name return root.name
``` ```
### Mutation.Input ### Mutation.Input
@ -94,7 +96,6 @@ class User(Mutation):
name = String() name = String()
``` ```
## Breaking Changes ## Breaking Changes
### Simpler resolvers ### Simpler resolvers
@ -108,7 +109,7 @@ Before:
```python ```python
my_field = graphene.String(my_arg=graphene.String()) my_field = graphene.String(my_arg=graphene.String())
def resolve_my_field(self, args, context, info): def resolve_my_field(root, args, context, info):
my_arg = args.get('my_arg') my_arg = args.get('my_arg')
return ... return ...
``` ```
@ -118,7 +119,7 @@ With 2.0:
```python ```python
my_field = graphene.String(my_arg=graphene.String()) my_field = graphene.String(my_arg=graphene.String())
def resolve_my_field(self, info, my_arg): def resolve_my_field(root, info, my_arg):
return ... return ...
``` ```
@ -126,7 +127,7 @@ def resolve_my_field(self, info, my_arg):
You may need something like this: You may need something like this:
```python ```python
def resolve_my_field(self, info, known_field1, known_field2, **args): ## get other args with: args.get('arg_key') def resolve_my_field(root, info, known_field1, known_field2, **args): ## get other args with: args.get('arg_key')
``` ```
And, if you need the context in the resolver, you can use `info.context`: And, if you need the context in the resolver, you can use `info.context`:
@ -134,7 +135,7 @@ And, if you need the context in the resolver, you can use `info.context`:
```python ```python
my_field = graphene.String(my_arg=graphene.String()) my_field = graphene.String(my_arg=graphene.String())
def resolve_my_field(self, info, my_arg): def resolve_my_field(root, info, my_arg):
context = info.context context = info.context
return ... return ...
``` ```
@ -188,6 +189,7 @@ class MyObject(ObjectType):
``` ```
To: To:
```python ```python
class MyObject(ObjectType): class MyObject(ObjectType):
class Meta: class Meta:
@ -203,30 +205,32 @@ class MyObject(ObjectType):
The parameters' order of `get_node_from_global_id` method has changed. You may need to adjust your [Node Root Field](http://docs.graphene-python.org/en/latest/relay/nodes/#node-root-field) and maybe other places that uses this method to obtain an object. The parameters' order of `get_node_from_global_id` method has changed. You may need to adjust your [Node Root Field](http://docs.graphene-python.org/en/latest/relay/nodes/#node-root-field) and maybe other places that uses this method to obtain an object.
Before: Before:
```python ```python
class RootQuery(object): class RootQuery(object):
... ...
node = Field(relay.Node, id=ID(required=True)) node = Field(relay.Node, id=ID(required=True))
def resolve_node(self, args, context, info): def resolve_node(root, args, context, info):
node = relay.Node.get_node_from_global_id(args['id'], context, info) node = relay.Node.get_node_from_global_id(args['id'], context, info)
return node return node
``` ```
Now: Now:
```python ```python
class RootQuery(object): class RootQuery(object):
... ...
node = Field(relay.Node, id=ID(required=True)) node = Field(relay.Node, id=ID(required=True))
def resolve_node(self, info, id): def resolve_node(root, info, id):
node = relay.Node.get_node_from_global_id(info, id) node = relay.Node.get_node_from_global_id(info, id)
return node return node
``` ```
## Mutation.mutate ## Mutation.mutate
Now only receives (`self`, `info`, `**args`) and is not a @classmethod Now only receives (`root`, `info`, `**kwargs`) and is not a @classmethod
Before: Before:
@ -245,7 +249,7 @@ With 2.0:
class SomeMutation(Mutation): class SomeMutation(Mutation):
... ...
def mutate(self, info, **args): def mutate(root, info, **args):
... ...
``` ```
@ -258,17 +262,14 @@ class SomeMutation(Mutation):
last_name = String(required=True) last_name = String(required=True)
... ...
def mutate(self, info, first_name, last_name): def mutate(root, info, first_name, last_name):
... ...
``` ```
## ClientIDMutation.mutate_and_get_payload ## ClientIDMutation.mutate_and_get_payload
Now only receives (`root`, `info`, `**input`) Now only receives (`root`, `info`, `**input`)
### Middlewares ### Middlewares
If you are using Middelwares, you need to some adjustments: If you are using Middelwares, you need to some adjustments:
@ -294,10 +295,9 @@ class MyGrapheneMiddleware(object):
## Middleware code ## Middleware code
info.context = context info.context = context
       return next_mw(root, info, **args)```        return next_mw(root, info, **args)
``` ```
## New Features ## New Features
### InputObjectType ### InputObjectType
@ -321,7 +321,7 @@ class Query(ObjectType):
user = graphene.Field(User, input=UserInput()) user = graphene.Field(User, input=UserInput())
@resolve_only_args @resolve_only_args
def resolve_user(self, input): def resolve_user(root, input):
user_id = input.get('id') user_id = input.get('id')
if is_valid_input(user_id): if is_valid_input(user_id):
return get_user(user_id) return get_user(user_id)
@ -334,18 +334,17 @@ class UserInput(InputObjectType):
id = ID(required=True) id = ID(required=True)
@property @property
def is_valid(self): def is_valid(root):
return self.id.startswith('userid_') return root.id.startswith('userid_')
class Query(ObjectType): class Query(ObjectType):
user = graphene.Field(User, input=UserInput()) user = graphene.Field(User, input=UserInput())
def resolve_user(self, info, input): def resolve_user(root, info, input):
if input.is_valid: if input.is_valid:
return get_user(input.id) return get_user(input.id)
``` ```
### Meta as Class arguments ### Meta as Class arguments
Now you can use the meta options as class arguments (**ONLY PYTHON 3**). Now you can use the meta options as class arguments (**ONLY PYTHON 3**).
@ -366,7 +365,6 @@ class Dog(ObjectType, interfaces=[Pet]):
name = String() name = String()
``` ```
### Abstract types ### Abstract types
Now you can create abstact types super easily, without the need of subclassing the meta. Now you can create abstact types super easily, without the need of subclassing the meta.
@ -378,10 +376,10 @@ class Base(ObjectType):
id = ID() id = ID()
def resolve_id(self, info): def resolve_id(root, info):
return "{type}_{id}".format( return "{type}_{id}".format(
type=self.__class__.__name__, type=root.__class__.__name__,
id=self.id id=root.id
) )
``` ```

View File

@ -19,7 +19,11 @@ help:
@echo "Please use \`make <target>' where <target> is one of" @echo "Please use \`make <target>' where <target> is one of"
@grep -E '^\.PHONY: [a-zA-Z_-]+ .*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = "(: |##)"}; {printf "\033[36m%-30s\033[0m %s\n", $$2, $$3}' @grep -E '^\.PHONY: [a-zA-Z_-]+ .*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = "(: |##)"}; {printf "\033[36m%-30s\033[0m %s\n", $$2, $$3}'
.PHONY: clean .PHONY: install ## to install all documentation related requirements
install:
pip install -r requirements.txt
.PHONY: clean ## to remove all built documentation
clean: clean:
rm -rf $(BUILDDIR)/* rm -rf $(BUILDDIR)/*
@ -199,6 +203,6 @@ dummy:
@echo @echo
@echo "Build finished. Dummy builder generates no files." @echo "Build finished. Dummy builder generates no files."
.PHONY: livehtml .PHONY: livehtml ## to build and serve live-reloading documentation
livehtml: livehtml:
sphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html sphinx-autobuild -b html --watch ../graphene $(ALLSPHINXOPTS) $(BUILDDIR)/html

0
docs/_static/.gitkeep vendored Normal file
View File

106
docs/api/index.rst Normal file
View File

@ -0,0 +1,106 @@
API Reference
=============
Schema
------
.. autoclass:: graphene.types.schema.Schema
:members:
.. Uncomment sections / types as API documentation is fleshed out
.. in each class
Object types
------------
.. autoclass:: graphene.ObjectType
.. autoclass:: graphene.InputObjectType
.. autoclass:: graphene.Mutation
:members:
Fields (Mounted Types)
----------------------
.. autoclass:: graphene.Field
.. autoclass:: graphene.Argument
.. autoclass:: graphene.InputField
Fields (Unmounted Types)
------------------------
.. autoclass:: graphene.types.unmountedtype.UnmountedType
GraphQL Scalars
---------------
.. autoclass:: graphene.Int()
.. autoclass:: graphene.Float()
.. autoclass:: graphene.String()
.. autoclass:: graphene.Boolean()
.. autoclass:: graphene.ID()
Graphene Scalars
----------------
.. autoclass:: graphene.Date()
.. autoclass:: graphene.DateTime()
.. autoclass:: graphene.Time()
.. autoclass:: graphene.Decimal()
.. autoclass:: graphene.UUID()
.. autoclass:: graphene.JSONString()
Enum
----
.. autoclass:: graphene.Enum()
Structures
----------
.. autoclass:: graphene.List
.. autoclass:: graphene.NonNull
Type Extension
--------------
.. autoclass:: graphene.Interface()
.. autoclass:: graphene.Union()
Execution Metadata
------------------
.. autoclass:: graphene.ResolveInfo
.. autoclass:: graphene.Context
.. autoclass:: graphql.execution.base.ExecutionResult
.. Relay
.. -----
.. .. autoclass:: graphene.Node
.. .. autoclass:: graphene.GlobalID
.. .. autoclass:: graphene.ClientIDMutation
.. .. autoclass:: graphene.Connection
.. .. autoclass:: graphene.ConnectionField
.. .. autoclass:: graphene.PageInfo

View File

@ -22,9 +22,10 @@ on_rtd = os.environ.get("READTHEDOCS", None) == "True"
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
# #
# import os import os
# import sys import sys
# sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, os.path.abspath(".."))
# -- General configuration ------------------------------------------------ # -- General configuration ------------------------------------------------
@ -41,6 +42,7 @@ extensions = [
"sphinx.ext.todo", "sphinx.ext.todo",
"sphinx.ext.coverage", "sphinx.ext.coverage",
"sphinx.ext.viewcode", "sphinx.ext.viewcode",
"sphinx.ext.napoleon",
] ]
if not on_rtd: if not on_rtd:
extensions += ["sphinx.ext.githubpages"] extensions += ["sphinx.ext.githubpages"]

View File

@ -111,8 +111,8 @@ leaner code and at most 4 database requests, and possibly fewer if there are cac
best_friend = graphene.Field(lambda: User) best_friend = graphene.Field(lambda: User)
friends = graphene.List(lambda: User) friends = graphene.List(lambda: User)
def resolve_best_friend(self, info): def resolve_best_friend(root, info):
return user_loader.load(self.best_friend_id) return user_loader.load(root.best_friend_id)
def resolve_friends(self, info): def resolve_friends(root, info):
return user_loader.load_many(self.friend_ids) return user_loader.load_many(root.friend_ids)

View File

@ -1,3 +1,5 @@
.. _SchemaExecute:
Executing a query Executing a query
================= =================
@ -7,12 +9,16 @@ For executing a query a schema, you can directly call the ``execute`` method on
.. code:: python .. code:: python
schema = graphene.Schema(...) from graphene import Schema
schema = Schema(...)
result = schema.execute('{ name }') result = schema.execute('{ name }')
``result`` represents the result of execution. ``result.data`` is the result of executing the query, ``result.errors`` is ``None`` if no errors occurred, and is a non-empty list if an error occurred. ``result`` represents the result of execution. ``result.data`` is the result of executing the query, ``result.errors`` is ``None`` if no errors occurred, and is a non-empty list if an error occurred.
.. _SchemaExecuteContext:
Context Context
_______ _______
@ -21,15 +27,17 @@ You can pass context to a query via ``context``.
.. code:: python .. code:: python
class Query(graphene.ObjectType): from graphene import ObjectType, String, Schema
name = graphene.String()
class Query(ObjectType):
name = String()
def resolve_name(root, info): def resolve_name(root, info):
return info.context.get('name') return info.context.get('name')
schema = graphene.Schema(Query) schema = Schema(Query)
result = schema.execute('{ name }', context={'name': 'Syrus'}) result = schema.execute('{ name }', context={'name': 'Syrus'})
assert result.data['name'] == 'Syrus'
Variables Variables
@ -40,13 +48,15 @@ You can pass variables to a query via ``variables``.
.. code:: python .. code:: python
class Query(graphene.ObjectType): from graphene import ObjectType, Field, ID, Schema
user = graphene.Field(User, id=graphene.ID(required=True))
class Query(ObjectType):
user = Field(User, id=ID(required=True))
def resolve_user(root, info, id): def resolve_user(root, info, id):
return get_user_by_id(id) return get_user_by_id(id)
schema = graphene.Schema(Query) schema = Schema(Query)
result = schema.execute( result = schema.execute(
''' '''
query getUser($id: ID) { query getUser($id: ID) {
@ -59,3 +69,71 @@ You can pass variables to a query via ``variables``.
''', ''',
variables={'id': 12}, variables={'id': 12},
) )
Root Value
__________
Value used for :ref:`ResolverParamParent` in root queries and mutations can be overridden using ``root`` parameter.
.. code:: python
from graphene import ObjectType, Field, Schema
class Query(ObjectType):
me = Field(User)
def resolve_user(root, info):
return {'id': root.id, 'firstName': root.name}
schema = Schema(Query)
user_root = User(id=12, name='bob'}
result = schema.execute(
'''
query getUser {
user {
id
firstName
lastName
}
}
''',
root=user_root
)
assert result.data['user']['id'] == user_root.id
Operation Name
______________
If there are multiple operations defined in a query string, ``operation_name`` should be used to indicate which should be executed.
.. code:: python
from graphene import ObjectType, Field, Schema
class Query(ObjectType):
me = Field(User)
def resolve_user(root, info):
return get_user_by_id(12)
schema = Schema(Query)
query_string = '''
query getUserWithFirstName {
user {
id
firstName
lastName
}
}
query getUserWithFullName {
user {
id
fullName
}
}
'''
result = schema.execute(
query_string,
operation_name='getUserWithFullName'
)
assert result.data['user']['fullName']

View File

@ -11,11 +11,15 @@ Contents:
execution/index execution/index
relay/index relay/index
testing/index testing/index
api/index
.. _Integrations:
Integrations Integrations
----- ------------
* `Graphene-Django <http://docs.graphene-python.org/projects/django/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-django/>`_) * `Graphene-Django <http://docs.graphene-python.org/projects/django/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-django/>`_)
* Flask-Graphql (`source <https://github.com/graphql-python/flask-graphql>`_)
* `Graphene-SQLAlchemy <http://docs.graphene-python.org/projects/sqlalchemy/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-sqlalchemy/>`_) * `Graphene-SQLAlchemy <http://docs.graphene-python.org/projects/sqlalchemy/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-sqlalchemy/>`_)
* `Graphene-GAE <http://docs.graphene-python.org/projects/gae/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-gae/>`_) * `Graphene-GAE <http://docs.graphene-python.org/projects/gae/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-gae/>`_)
* `Graphene-Mongo <http://graphene-mongo.readthedocs.io/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-mongo>`_) * `Graphene-Mongo <http://graphene-mongo.readthedocs.io/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-mongo>`_)

View File

@ -1,62 +1,143 @@
Getting started Getting started
=============== ===============
Introduction
------------
What is GraphQL? What is GraphQL?
---------------- ~~~~~~~~~~~~~~~~
For an introduction to GraphQL and an overview of its concepts, please refer GraphQL is a query language for your API.
to `the official introduction <http://graphql.org/learn/>`_.
It provides a standard way to:
* *describe data provided by a server* in a statically typed **Schema**
* *request data* in a **Query** which exactly describes your data requirements and
* *receive data* in a **Response** containing only the data you requested.
For an introduction to GraphQL and an overview of its concepts, please refer to `the official GraphQL documentation`_.
.. _the official GraphQL documentation: http://graphql.org/learn/
What is Graphene?
~~~~~~~~~~~~~~~~~
Graphene is a library that provides tools to implement a GraphQL API in Python using a *code-first* approach.
Compare Graphene's *code-first* approach to building a GraphQL API with *schema-first* approaches like `Apollo Server`_ (JavaScript) or Ariadne_ (Python). Instead of writing GraphQL **Schema Definition Langauge (SDL)**, we write Python code to describe the data provided by your server.
.. _Apollo Server: https://www.apollographql.com/docs/apollo-server/
.. _Ariadne: https://ariadne.readthedocs.io
Graphene is fully featured with integrations for the most popular web frameworks and ORMs. Graphene produces schemas tha are fully compliant with the GraphQL spec and provides tools and patterns for building a Relay-Compliant API as well.
An example in Graphene
----------------------
Lets build a basic GraphQL schema to say "hello" and "goodbye" in Graphene.
When we send a **Query** requesting only one **Field**, ``hello``, and specify a value for the ``name`` **Argument**...
.. code::
{
hello(name: "friend")
}
...we would expect the following Response containing only the data requested (the ``goodbye`` field is not resolved).
.. code::
{
"data": {
"hello": "Hello friend!"
}
}
Lets build a basic GraphQL schema from scratch.
Requirements Requirements
------------ ~~~~~~~~~~~~
- Python (2.7, 3.4, 3.5, 3.6, pypy) - Python (2.7, 3.4, 3.5, 3.6, pypy)
- Graphene (2.0) - Graphene (2.0)
Project setup Project setup
------------- ~~~~~~~~~~~~~
.. code:: bash .. code:: bash
pip install "graphene>=2.0" pip install "graphene>=2.0"
Creating a basic Schema Creating a basic Schema
----------------------- ~~~~~~~~~~~~~~~~~~~~~~~
A GraphQL schema describes your data model, and provides a GraphQL In Graphene, we can define a simple schema using the following code:
server with an associated set of resolve methods that know how to fetch
data.
We are going to create a very simple schema, with a ``Query`` with only
one field: ``hello`` and an input name. And when we query it, it should return ``"Hello
{argument}"``.
.. code:: python .. code:: python
import graphene from graphene import ObjectType, String, Schema
class Query(graphene.ObjectType): class Query(ObjectType):
hello = graphene.String(argument=graphene.String(default_value="stranger")) # this defines a Field `hello` in our Schema with a single Argument `name`
hello = String(name=String(default_value="stranger"))
goodbye = String()
def resolve_hello(self, info, argument): # our Resolver method takes the GraphQL context (root, info) as well as
return 'Hello ' + argument # Argument (name) for the Field and returns data for the query Response
def resolve_hello(root, info, name):
return f'Hello {name}!'
schema = graphene.Schema(query=Query) def resolve_goodbye(root, info):
return 'See ya!'
schema = Schema(query=Query)
A GraphQL **Schema** describes each **Field** in the data model provided by the server using scalar types like *String*, *Int* and *Enum* and compound types like *List* and *Object*. For more details refer to the Graphene :ref:`TypesReference`.
Our schema can also define any number of **Arguments** for our **Fields**. This is a powerful way for a **Query** to describe the exact data requirements for each **Field**.
For each **Field** in our **Schema**, we write a **Resolver** method to fetch data requested by a client's **Query** using the current context and **Arguments**. For more details, refer to this section on :ref:`Resolvers`.
Schema Definition Language (SDL)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In the `GraphQL Schema Definition Language`_, we could describe the fields defined by our example code as show below.
.. _GraphQL Schema Definition Language: https://graphql.org/learn/schema/
.. code::
type Query {
hello(name: String = "stranger"): String
goodbye: String
}
Further examples in this documentation will use SDL to describe schema created by ObjectTypes and other fields.
Querying Querying
-------- ~~~~~~~~
Then we can start querying our schema: Then we can start querying our **Schema** by passing a GraphQL query string to ``execute``:
.. code:: python .. code:: python
result = schema.execute('{ hello }') # we can query for our field (with the default argument)
print(result.data['hello']) # "Hello stranger" query_string = '{ hello }'
result = schema.execute(query_string)
print(result.data['hello'])
# "Hello stranger"
# or passing the argument in the query # or passing the argument in the query
result = schema.execute('{ hello (argument: "graph") }') query_with_argument = '{ hello(name: "GraphQL") }'
print(result.data['hello']) # "Hello graph" result = schema.execute(query_with_argument)
print(result.data['hello'])
# "Hello GraphQL!"
Congrats! You got your first graphene schema working! Next steps
~~~~~~~~~~
Congrats! You got your first Graphene schema working!
Normally, we don't need to directly execute a query string against our schema as Graphene provides many useful Integrations with popular web frameworks like Flask and Django. Check out :ref:`Integrations` for more information on how to get started serving your GraphQL API.

View File

@ -41,5 +41,5 @@ that implements ``Node`` will have a default Connection.
name = graphene.String() name = graphene.String()
ships = relay.ConnectionField(ShipConnection) ships = relay.ConnectionField(ShipConnection)
def resolve_ships(self, info): def resolve_ships(root, info):
return [] return []

View File

@ -1,4 +1,5 @@
# Required library # Required library
Sphinx==1.5.3 Sphinx==1.5.3
sphinx-autobuild==0.7.1
# Docs template # Docs template
http://graphene-python.org/sphinx_graphene_theme.zip http://graphene-python.org/sphinx_graphene_theme.zip

View File

@ -1,3 +1,5 @@
.. _TypesReference:
=============== ===============
Types Reference Types Reference
=============== ===============
@ -5,12 +7,12 @@ Types Reference
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
enums schema
scalars scalars
list-and-nonnull list-and-nonnull
objecttypes objecttypes
enums
interfaces interfaces
unions unions
schema
mutations mutations
abstracttypes abstracttypes

View File

@ -1,3 +1,5 @@
.. _Interfaces:
Interfaces Interfaces
========== ==========
@ -82,7 +84,7 @@ For example, you can define a field ``hero`` that resolves to any
episode=graphene.Int(required=True) episode=graphene.Int(required=True)
) )
def resolve_hero(_, info, episode): def resolve_hero(root, info, episode):
# Luke is the hero of Episode V # Luke is the hero of Episode V
if episode == 5: if episode == 5:
return get_human(name='Luke Skywalker') return get_human(name='Luke Skywalker')

View File

@ -19,7 +19,7 @@ This example defines a Mutation:
ok = graphene.Boolean() ok = graphene.Boolean()
person = graphene.Field(lambda: Person) person = graphene.Field(lambda: Person)
def mutate(self, info, name): def mutate(root, info, name):
person = Person(name=name) person = Person(name=name)
ok = True ok = True
return CreatePerson(person=person, ok=ok) return CreatePerson(person=person, ok=ok)
@ -32,7 +32,8 @@ resolved.
only argument for the mutation. only argument for the mutation.
**mutate** is the function that will be applied once the mutation is **mutate** is the function that will be applied once the mutation is
called. called. This method is just a special resolver that we can change
data within. It takes the same arguments as the standard query :ref:`ResolverArguments`.
So, we can finish our schema like this: So, we can finish our schema like this:
@ -157,7 +158,7 @@ To return an existing ObjectType instead of a mutation-specific type, set the **
Output = Person Output = Person
def mutate(self, info, name): def mutate(root, info, name):
return Person(name=name) return Person(name=name)
Then, if we query (``schema.execute(query_str)``) the following: Then, if we query (``schema.execute(query_str)``) the following:

View File

@ -1,15 +1,15 @@
ObjectTypes .. _ObjectType:
===========
An ObjectType is the single, definitive source of information about your ObjectType
data. It contains the essential fields and behaviors of the data youre ==========
querying.
A Graphene *ObjectType* is the building block used to define the relationship between **Fields** in your **Schema** and how their data is retrieved.
The basics: The basics:
- Each ObjectType is a Python class that inherits from - Each ObjectType is a Python class that inherits from ``graphene.ObjectType``.
``graphene.ObjectType``.
- Each attribute of the ObjectType represents a ``Field``. - Each attribute of the ObjectType represents a ``Field``.
- Each ``Field`` has a :ref:`resolver method<Resolvers>` to fetch data (or :ref:`DefaultResolver`).
Quick example Quick example
------------- -------------
@ -18,19 +18,17 @@ This example model defines a Person, with a first and a last name:
.. code:: python .. code:: python
import graphene from graphene import ObjectType, String
class Person(graphene.ObjectType): class Person(ObjectType):
first_name = graphene.String() first_name = String()
last_name = graphene.String() last_name = String()
full_name = graphene.String() full_name = String()
def resolve_full_name(root, info): def resolve_full_name(parent, info):
return '{} {}'.format(root.first_name, root.last_name) return f"{parent.first_name} {parent.last_name}"
**first\_name** and **last\_name** are fields of the ObjectType. Each This *ObjectType* defines the field **first\_name**, **last\_name**, and **full\_name**. Each field is specified as a class attribute, and each attribute maps to a Field. Data is fetched by our ``resolve_full_name`` :ref:`resolver method<Resolvers>` for ``full_name`` field and the :ref:`DefaultResolver` for other fields.
field is specified as a class attribute, and each attribute maps to a
Field.
The above ``Person`` ObjectType has the following schema representation: The above ``Person`` ObjectType has the following schema representation:
@ -42,61 +40,111 @@ The above ``Person`` ObjectType has the following schema representation:
fullName: String fullName: String
} }
.. _Resolvers:
Resolvers Resolvers
--------- ---------
A resolver is a method that resolves certain fields within an A **Resolver** is a method that helps us answer **Queries** by fetching data for a **Field** in our **Schema**.
``ObjectType``. If not specified otherwise, the resolver of a
field is the ``resolve_{field_name}`` method on the ``ObjectType``.
By default resolvers take the arguments ``info`` and ``*args``. Resolvers are lazily executed, so if a field is not included in a query, its resolver will not be executed.
NOTE: The resolvers on an ``ObjectType`` are always treated as ``staticmethod``\ s, Each field on an *ObjectType* in Graphene should have a corresponding resolver method to fetch data. This resolver method should match the field name. For example, in the ``Person`` type above, the ``full_name`` field is resolved by the method ``resolve_full_name``.
so the first argument to the resolver method ``self`` (or ``root``) need
not be an actual instance of the ``ObjectType``.
If an explicit resolver is not defined on the ``ObjectType`` then Graphene will Each resolver method takes the parameters:
attempt to use a property with the same name on the object or dict that is * :ref:`ResolverParamParent` for the value object use to resolve most fields
passed to the ``ObjectType``. * :ref:`ResolverParamInfo` for query and schema meta information and per-request context
* :ref:`ResolverParamGraphQLArguments` as defined on the **Field**.
.. _ResolverArguments:
Resolver Parameters
~~~~~~~~~~~~~~~~~~~
.. _ResolverParamParent:
Parent Value Object (*parent*)
******************************
This parameter is typically used to derive the values for most fields on an *ObjectType*.
The first parameter of a resolver method (*parent*) is the value object returned from the resolver of the parent field. If there is no parent field, such as a root Query field, then the value for *parent* is set to the ``root_value`` configured while executing the query (default ``None``). See :ref:`SchemaExecute` for more details on executing queries.
Resolver example
^^^^^^^^^^^^^^^^
If we have a schema with Person type and one field on the root query.
.. code:: python .. code:: python
import graphene from graphene import ObjectType, String, Field
class Person(graphene.ObjectType): class Person(ObjectType):
first_name = graphene.String() full_name = String()
last_name = graphene.String()
class Query(graphene.ObjectType): def resolve_full_name(parent, info):
me = graphene.Field(Person) return f"{parent.first_name} {parent.last_name}"
best_friend = graphene.Field(Person)
def resolve_me(_, info): class Query(ObjectType):
me = Field(Person)
def resolve_me(parent, info):
# returns an object that represents a Person # returns an object that represents a Person
return get_human(name='Luke Skywalker') return get_human(name="Luke Skywalker")
def resolve_best_friend(_, info): When we execute a query against that schema.
return {
"first_name": "R2",
"last_name": "D2",
}
.. code:: python
Resolvers with arguments schema = Schema(query=Query)
~~~~~~~~~~~~~~~~~~~~~~~~
query_string = "{ me { fullName } }"
result = schema.execute(query_string)
assert result["data"]["me"] == {"fullName": "Luke Skywalker")
Then we go through the following steps to resolve this query:
* ``parent`` is set with the root_value from query execution (None).
* ``Query.resolve_me`` called with ``parent`` None which returns a value object ``Person("Luke", "Skywalker")``.
* This value object is then used as ``parent`` while calling ``Person.resolve_full_name`` to resolve the scalar String value "Luke Skywalker".
* The scalar value is serialized and sent back in the query response.
Each resolver returns the next :ref:`ResolverParamParent` to be used in executing the following resolver in the chain. If the Field is a Scalar type, that value will be serialized and sent in the **Response**. Otherwise, while resolving Compound types like *ObjectType*, the value be passed forward as the next :ref:`ResolverParamParent`.
Naming convention
^^^^^^^^^^^^^^^^^
This :ref:`ResolverParamParent` is sometimes named ``obj``, ``parent``, or ``source`` in other GraphQL documentation. It can also be named after the value object being resolved (ex. ``root`` for a root Query or Mutation, and ``person`` for a Person value object). Sometimes this argument will be named ``self`` in Graphene code, but this can be misleading due to :ref:`ResolverImplicitStaticMethod` while executing queries in Graphene.
.. _ResolverParamInfo:
GraphQL Execution Info (*info*)
*******************************
The second parameter provides two things:
* reference to meta information about the execution of the current GraphQL Query (fields, schema, parsed query, etc.)
* access to per-request ``context`` which can be used to store user authentication, data loader instances or anything else useful for resolving the query.
Only context will be required for most applications. See :ref:`SchemaExecuteContext` for more information about setting context.
.. _ResolverParamGraphQLArguments:
GraphQL Arguments (*\*\*kwargs*)
********************************
Any arguments that a field defines gets passed to the resolver function as Any arguments that a field defines gets passed to the resolver function as
kwargs. For example: keyword arguments. For example:
.. code:: python .. code:: python
import graphene from graphene import ObjectType, Field, String
class Query(graphene.ObjectType): class Query(ObjectType):
human_by_name = graphene.Field(Human, name=graphene.String(required=True)) human_by_name = Field(Human, name=String(required=True))
def resolve_human_by_name(_, info, name): def resolve_human_by_name(parent, info, name):
return get_human(name=name) return get_human(name=name)
You can then execute the following query: You can then execute the following query:
@ -110,7 +158,98 @@ You can then execute the following query:
} }
} }
NOTE: if you define an argument for a field that is not required (and in a query Convenience Features of Graphene Resolvers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. _ResolverImplicitStaticMethod:
Implicit staticmethod
*********************
One surprising feature of Graphene is that all resolver methods are treated implicitly as staticmethods. This means that, unlike other methods in Python, the first argument of a resolver is *never* ``self`` while it is being executed by Graphene. Instead, the first argument is always :ref:`ResolverParamParent`. In practice, this is very convenient as, in GraphQL, we are almost always more concerned with the using the parent value object to resolve queries than attributes on the Python object itself.
The two resolvers in this example are effectively the same.
.. code:: python
from graphene import ObjectType, String
class Person(ObjectType):
first_name = String()
last_name = String()
@staticmethod
def resolve_first_name(parent, info):
'''
Decorating a Python method with `staticmethod` ensures that `self` will not be provided as an
argument. However, Graphene does not need this decorator for this behavior.
'''
return parent.first_name
def resolve_last_name(parent, info):
'''
Normally the first argument for this method would be `self`, but Graphene executes this as
a staticmethod implicitly.
'''
return parent.last_name
# ...
If you prefer your code to be more explict, feel free to use ``@staticmethod`` decorators. Otherwise, your code may be cleaner without them!
.. _DefaultResolver:
Default Resolver
****************
If a resolver method is not defined for a **Field** attribute on our *ObjectType*, Graphene supplies a default resolver.
If the :ref:`ResolverParamParent` is a dictionary, the resolver will look for a dictionary key matching the field name. Otherwise, the resolver will get the attribute from the parent value object matching the field name.
.. code:: python
from collections import namedtuple
from graphene import ObjectType, String, Field, Schema
PersonValueObject = namedtuple('Person', 'first_name', 'last_name')
class Person(ObjectType):
first_name = String()
last_name = String()
class Query(ObjectType):
me = Field(Person)
my_best_friend = Field(Person)
def resolve_me(parent, info):
# always pass an object for `me` field
return PersonValueObject(first_name='Luke', last_name='Skywalker')
def resolve_my_best_friend(parent, info):
# always pass a dictionary for `my_best_fiend_field`
return {"first_name": "R2", "last_name": "D2"}
schema = Schema(query=Query)
result = schema.execute('''
{
me { firstName lastName }
myBestFriend { firstName lastName }
}
''')
# With default resolvers we can resolve attributes from an object..
assert result['data']['me'] == {"firstName": "Luke", "lastName": "Skywalker"}
# With default resolvers, we can also resolve keys from a dictionary..
assert result['data']['my_best_friend'] == {"firstName": "R2", "lastName": "D2"}
Advanced
~~~~~~~~
GraphQL Argument defaults
*************************
If you define an argument for a field that is not required (and in a query
execution it is not provided as an argument) it will not be passed to the execution it is not provided as an argument) it will not be passed to the
resolver function at all. This is so that the developer can differenciate resolver function at all. This is so that the developer can differenciate
between a ``undefined`` value for an argument and an explicit ``null`` value. between a ``undefined`` value for an argument and an explicit ``null`` value.
@ -119,12 +258,12 @@ For example, given this schema:
.. code:: python .. code:: python
import graphene from graphene import ObjectType, String
class Query(graphene.ObjectType): class Query(ObjectType):
hello = graphene.String(required=True, name=graphene.String()) hello = String(required=True, name=String())
def resolve_hello(_, info, name): def resolve_hello(parent, info, name):
return name if name else 'World' return name if name else 'World'
And this query: And this query:
@ -141,61 +280,90 @@ An error will be thrown:
TypeError: resolve_hello() missing 1 required positional argument: 'name' TypeError: resolve_hello() missing 1 required positional argument: 'name'
You can fix this error in 2 ways. Either by combining all keyword arguments You can fix this error in serveral ways. Either by combining all keyword arguments
into a dict: into a dict:
.. code:: python .. code:: python
class Query(graphene.ObjectType): from graphene import ObjectType, String
hello = graphene.String(required=True, name=graphene.String())
def resolve_hello(_, info, **args): class Query(ObjectType):
return args.get('name', 'World') hello = String(required=True, name=String())
def resolve_hello(parent, info, **kwargs):
name = kwargs.get('name', 'World')
return f'Hello, {name}!'
Or by setting a default value for the keyword argument: Or by setting a default value for the keyword argument:
.. code:: python .. code:: python
class Query(graphene.ObjectType): from graphene import ObjectType, String
hello = graphene.String(required=True, name=graphene.String())
def resolve_hello(_, info, name='World'): class Query(ObjectType):
return name hello = String(required=True, name=String())
def resolve_hello(parent, info, name='World'):
return f'Hello, {name}!'
One can also set a default value for an Argument in the GraphQL schema itself using Graphene!
.. code:: python
from graphene import ObjectType, String
class Query(ObjectType):
hello = String(
required=True,
name=String(default_value='World')
)
def resolve_hello(parent, info, name):
return f'Hello, {name}!'
Resolvers outside the class Resolvers outside the class
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ***************************
A field can use a custom resolver from outside the class: A field can use a custom resolver from outside the class:
.. code:: python .. code:: python
import graphene from graphene import ObjectType, String
def resolve_full_name(person, info): def resolve_full_name(person, info):
return '{} {}'.format(person.first_name, person.last_name) return '{} {}'.format(person.first_name, person.last_name)
class Person(graphene.ObjectType): class Person(ObjectType):
first_name = graphene.String() first_name = String()
last_name = graphene.String() last_name = String()
full_name = graphene.String(resolver=resolve_full_name) full_name = String(resolver=resolve_full_name)
Instances as data containers Instances as value objects
---------------------------- **************************
Graphene ``ObjectType``\ s can act as containers too. So with the Graphene ``ObjectType``\ s can act as value objects too. So with the
previous example you could do: previous example you could use ``Person`` to capture data for each of the *ObjectType*'s fields.
.. code:: python .. code:: python
peter = Person(first_name='Peter', last_name='Griffin') peter = Person(first_name='Peter', last_name='Griffin')
peter.first_name # prints "Peter" peter.first_name # prints "Peter"
peter.last_name # prints "Griffin" peter.last_name # prints "Griffin"
Changing the name Field camelcasing
----------------- *****************
Graphene automatically camelcases fields on *ObjectType* from ``field_name`` to ``fieldName`` to conform with GraphQL standards. See :ref:`SchemaAutoCamelCase` for more information.
*ObjectType* Configuration - Meta class
---------------------------------------
Graphene uses a Meta inner class on *ObjectType* to set different options.
GraphQL type name
~~~~~~~~~~~~~~~~~
By default the type name in the GraphQL schema will be the same as the class name By default the type name in the GraphQL schema will be the same as the class name
that defines the ``ObjectType``. This can be changed by setting the ``name`` that defines the ``ObjectType``. This can be changed by setting the ``name``
@ -203,8 +371,44 @@ property on the ``Meta`` class:
.. code:: python .. code:: python
class MyGraphQlSong(graphene.ObjectType): from graphene import ObjectType
class MyGraphQlSong(ObjectType):
class Meta: class Meta:
name = 'Song' name = 'Song'
GraphQL Description
~~~~~~~~~~~~~~~~~~~
The schema description of an *ObjectType* can be set as a docstring on the Python object or on the Meta inner class.
.. code:: python
from graphene import ObjectType
class MyGraphQlSong(ObjectType):
''' We can set the schema description for an Object Type here on a docstring '''
class Meta:
description = 'But if we set the description in Meta, this value is used instead'
Interfaces & Possible Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Setting ``interfaces`` in Meta inner class specifies the GraphQL Interfaces that this Object implements.
Providing ``possible_types`` helps Graphene resolve ambiguous types such as interfaces or Unions.
See :ref:`Interfaces` for more information.
.. code:: python
from graphene import ObjectType, Node
Song = namedtuple('Song', ('title', 'artist'))
class MyGraphQlSong(ObjectType):
class Meta:
interfaces = (Node, )
possible_types = (Song, )
.. _Interface: /docs/interfaces/ .. _Interface: /docs/interfaces/

View File

@ -1,3 +1,5 @@
.. _Scalars:
Scalars Scalars
======= =======
@ -13,7 +15,7 @@ All Scalar types accept the following arguments. All are optional:
``required``: *boolean* ``required``: *boolean*
If ``True``, the server will enforce a value for this field. See `NonNull <./list-and-nonnull.html#nonnull>`_. Default is ``False``. If ``True``, the server will enforce a value for this field. See `NonNull <../list-and-nonnull.html#nonnull>`_. Default is ``False``.
``deprecation_reason``: *string* ``deprecation_reason``: *string*

View File

@ -1,16 +1,42 @@
Schema Schema
====== ======
A Schema is created by supplying the root types of each type of operation, query and mutation (optional). A GraphQL **Schema** defines the types and relationship between **Fields** in your API.
A schema definition is then supplied to the validator and executor.
A Schema is created by supplying the root :ref:`ObjectType` of each operation, query (mandatory), mutation and subscription.
Schema will collect all type definitions related to the root operations and then supplied to the validator and executor.
.. code:: python .. code:: python
my_schema = Schema( my_schema = Schema(
query=MyRootQuery, query=MyRootQuery,
mutation=MyRootMutation, mutation=MyRootMutation,
subscription=MyRootSubscription
) )
A Root Query is just a special :ref:`ObjectType` that :ref:`defines the fields <Scalars>` that are the entrypoint for your API. Root Mutation and Root Subscription are similar to Root Query, but for different operation types:
* Query fetches data
* Mutation to changes data and retrieve the changes
* Subscription to sends changes to clients in real time
Review the `GraphQL documentation on Schema`_ for a brief overview of fields, schema and operations.
.. _GraphQL documentation on Schema: https://graphql.org/learn/schema/
Querying
--------
To query a schema, call the ``execute`` method on it. See :ref:`SchemaExecute` for more details.
.. code:: python
query_string = 'query whoIsMyBestFriend { myBestFriend { lastName } }'
my_schema.execute(query_string)
Types Types
----- -----
@ -28,17 +54,7 @@ In this case, we need to use the ``types`` argument when creating the Schema.
types=[SomeExtraObjectType, ] types=[SomeExtraObjectType, ]
) )
.. _SchemaAutoCamelCase:
Querying
--------
To query a schema, call the ``execute`` method on it.
.. code:: python
my_schema.execute('{ lastName }')
Auto CamelCase field names Auto CamelCase field names
-------------------------- --------------------------

View File

@ -8,6 +8,37 @@ from .utils import get_type
class Argument(MountedType): class Argument(MountedType):
"""
Makes an Argument available on a Field in the GraphQL schema.
Arguments will be parsed and provided to resolver methods for fields as keyword arguments.
All ``arg`` and ``**extra_args`` for a ``graphene.Field`` are implicitly mounted as Argument
using the below parameters.
.. code:: python
from graphene import String, Boolean, Argument
age = String(
# Boolean implicitly mounted as Argument
dog_years=Boolean(description="convert to dog years"),
# Boolean explicitly mounted as Argument
decades=Argument(Boolean, default_value=False),
)
args:
type (class for a graphene.UnmountedType): must be a class (not an instance) of an
unmounted graphene type (ex. scalar or object) which is used for the type of this
argument in the GraphQL schema.
required (bool): indicates this argument as not null in the graphql scehma. Same behavior
as graphene.NonNull. Default False.
name (str): the name of the GraphQL argument. Defaults to parameter name.
description (str): the description of the GraphQL argument in the schema.
default_value (Any): The value to be provided if the user does not set this argument in
the operation.
"""
def __init__( def __init__(
self, self,
type, type,

View File

@ -1,4 +1,25 @@
class Context(object): class Context(object):
"""
Context can be used to make a convenient container for attributes to provide
for execution for resolvers of a GraphQL operation like a query.
.. code:: python
from graphene import Context
context = Context(loaders=build_dataloaders(), request=my_web_request)
schema.execute('{ hello(name: "world") }', context=context)
def resolve_hello(parent, info, name):
info.context.request # value set in Context
info.context.loaders # value set in Context
# ...
args:
**params (Dict[str, Any]): values to make available on Context instance as attributes.
"""
def __init__(self, **params): def __init__(self, **params):
for key, value in params.items(): for key, value in params.items():
setattr(self, key, value) setattr(self, key, value)

View File

@ -64,6 +64,30 @@ class EnumMeta(SubclassWithMeta_Meta):
class Enum(UnmountedType, BaseType, metaclass=EnumMeta): class Enum(UnmountedType, BaseType, metaclass=EnumMeta):
"""
Enum type definition
Defines a static set of values that can be provided as a Field, Argument or InputField.
.. code:: python
from graphene import Enum
class NameFormat(Enum):
FIRST_LAST = "first_last"
LAST_FIRST = "last_first"
Meta:
enum (optional, Enum): Python enum to use as a base for GraphQL Enum.
name (optional, str): Name of the GraphQL type (must be unique in schema). Defaults to class
name.
description (optional, str): Description of the GraphQL type in the schema. Defaults to class
docstring.
deprecation_reason (optional, str): Setting this value indicates that the enum is
depreciated and may provide instruction or reason on how for clients to proceed.
"""
@classmethod @classmethod
def __init_subclass_with_meta__(cls, enum=None, _meta=None, **options): def __init_subclass_with_meta__(cls, enum=None, _meta=None, **options):
if not _meta: if not _meta:

View File

@ -19,6 +19,47 @@ def source_resolver(source, root, info, **args):
class Field(MountedType): class Field(MountedType):
"""
Makes a field available on an ObjectType in the GraphQL schema. Any type can be mounted as a
Field:
- Object Type
- Scalar Type
- Enum
- Interface
- Union
All class attributes of ``graphene.ObjectType`` are implicitly mounted as Field using the below
arguments.
.. code:: python
class Person(ObjectType):
first_name = graphene.String(required=True) # implicitly mounted as Field
last_name = graphene.Field(String, description='Surname') # explicitly mounted as Field
args:
type (class for a graphene.UnmountedType): must be a class (not an instance) of an
unmounted graphene type (ex. scalar or object) which is used for the type of this
field in the GraphQL schema.
args (optional, Dict[str, graphene.Argument]): arguments that can be input to the field.
Prefer to use **extra_args.
resolver (optional, Callable): A function to get the value for a Field from the parent
value object. If not set, the default resolver method for the schema is used.
source (optional, str): attribute name to resolve for this field from the parent value
object. Alternative to resolver (cannot set both source and resolver).
deprecation_reason (optional, str): Setting this value indicates that the field is
depreciated and may provide instruction or reason on how for clients to proceed.
required (optional, bool): indicates this field as not null in the graphql scehma. Same behavior as
graphene.NonNull. Default False.
name (optional, str): the name of the GraphQL field (must be unique in a type). Defaults to attribute
name.
description (optional, str): the description of the GraphQL field in the schema.
default_value (optional, Any): Default value to resolve if none set from schema.
**extra_args (optional, Dict[str, Union[graphene.Argument, graphene.UnmountedType]): any
additional arguments to mount on the field.
"""
def __init__( def __init__(
self, self,
type, type,

View File

@ -4,6 +4,46 @@ from .utils import get_type
class InputField(MountedType): class InputField(MountedType):
"""
Makes a field available on an ObjectType in the GraphQL schema. Any type can be mounted as a
Input Field except Interface and Union:
- Object Type
- Scalar Type
- Enum
Input object types also can't have arguments on their input fields, unlike regular ``graphene.Field``.
All class attributes of ``graphene.InputObjectType`` are implicitly mounted as InputField
using the below arguments.
.. code:: python
from graphene import InputObjectType, String, InputField
class Person(InputObjectType):
# implicitly mounted as Input Field
first_name = String(required=True)
# explicitly mounted as Input Field
last_name = InputField(String, description="Surname")
args:
type (class for a graphene.UnmountedType): Must be a class (not an instance) of an
unmounted graphene type (ex. scalar or object) which is used for the type of this
field in the GraphQL schema.
name (optional, str): Name of the GraphQL input field (must be unique in a type).
Defaults to attribute name.
default_value (optional, Any): Default value to use as input if none set in user operation (
query, mutation, etc.).
deprecation_reason (optional, str): Setting this value indicates that the field is
depreciated and may provide instruction or reason on how for clients to proceed.
description (optional, str): Description of the GraphQL field in the schema.
required (optional, bool): Indicates this input field as not null in the graphql scehma.
Raises a validation error if argument not provided. Same behavior as graphene.NonNull.
Default False.
**extra_args (optional, Dict): Not used.
"""
def __init__( def __init__(
self, self,
type, type,

View File

@ -36,7 +36,33 @@ class InputObjectType(UnmountedType, BaseType):
An input object defines a structured collection of fields which may be An input object defines a structured collection of fields which may be
supplied to a field argument. supplied to a field argument.
Using `NonNull` will ensure that a value must be provided by the query Using ``graphene.NonNull`` will ensure that a input value must be provided by the query.
All class attributes of ``graphene.InputObjectType`` are implicitly mounted as InputField
using the below Meta class options.
.. code:: python
from graphene import InputObjectType, String, InputField
class Person(InputObjectType):
# implicitly mounted as Input Field
first_name = String(required=True)
# explicitly mounted as Input Field
last_name = InputField(String, description="Surname")
The fields on an input object type can themselves refer to input object types, but you can't
mix input and output types in your schema.
Meta class options (optional):
name (str): the name of the GraphQL type (must be unique in schema). Defaults to class
name.
description (str): the description of the GraphQL type in the schema. Defaults to class
docstring.
container (class): A class reference for a value object that allows for
attribute initialization and access. Default InputObjectTypeContainer.
fields (Dict[str, graphene.InputField]): Dictionary of field name to InputField. Not
recommended to use (prefer class attributes).
""" """
@classmethod @classmethod

View File

@ -22,6 +22,28 @@ class Interface(BaseType):
is used to describe what types are possible, what fields are in common across is used to describe what types are possible, what fields are in common across
all types, as well as a function to determine which type is actually used all types, as well as a function to determine which type is actually used
when the field is resolved. when the field is resolved.
.. code:: python
from graphene import Interface, String
class HasAddress(Interface):
class Meta:
description = "Address fields"
address1 = String()
address2 = String()
If a field returns an Interface Type, the ambiguous type of the object can be determined using
``resolve_type`` on Interface and an ObjectType with ``Meta.possible_types`` or ``is_type_of``.
Meta:
name (str): Name of the GraphQL type (must be unique in schema). Defaults to class
name.
description (str): Description of the GraphQL type in the schema. Defaults to class
docstring.
fields (Dict[str, graphene.Field]): Dictionary of field name to Field. Not recommended to
use (prefer class attributes).
""" """
@classmethod @classmethod

View File

@ -8,7 +8,12 @@ from .scalars import Scalar
class JSONString(Scalar): class JSONString(Scalar):
"""JSON String""" """
Allows use of a JSON String for input / output from the GraphQL schema.
Use of this type is *not recommended* as you lose the benefits of having a defined, static
schema (one of the key benefits of GraphQL).
"""
@staticmethod @staticmethod
def serialize(dt): def serialize(dt):

View File

@ -6,34 +6,88 @@ from ..utils.props import props
from .field import Field from .field import Field
from .objecttype import ObjectType, ObjectTypeOptions from .objecttype import ObjectType, ObjectTypeOptions
from .utils import yank_fields_from_attrs from .utils import yank_fields_from_attrs
from .interface import Interface
# For static type checking with Mypy # For static type checking with Mypy
MYPY = False MYPY = False
if MYPY: if MYPY:
from .argument import Argument # NOQA from .argument import Argument # NOQA
from typing import Dict, Type, Callable # NOQA from typing import Dict, Type, Callable, Iterable # NOQA
class MutationOptions(ObjectTypeOptions): class MutationOptions(ObjectTypeOptions):
arguments = None # type: Dict[str, Argument] arguments = None # type: Dict[str, Argument]
output = None # type: Type[ObjectType] output = None # type: Type[ObjectType]
resolver = None # type: Callable resolver = None # type: Callable
interfaces = () # type: Iterable[Type[Interface]]
class Mutation(ObjectType): class Mutation(ObjectType):
""" """
Mutation Type Definition Object Type Definition (mutation field)
Mutation is a convenience type that helps us build a Field which takes Arguments and returns a
mutation Output ObjectType.
.. code:: python
from graphene import Mutation, ObjectType, String, Boolean, Field
class CreatePerson(Mutation):
class Arguments:
name = String()
ok = Boolean()
person = Field(Person)
def mutate(parent, info, name):
person = Person(name=name)
ok = True
return CreatePerson(person=person, ok=ok)
class Mutation(ObjectType):
create_person = CreatePerson.Field()
Meta class options (optional):
output (graphene.ObjectType): Or ``Output`` inner class with attributes on Mutation class.
Or attributes from Mutation class. Fields which can be returned from this mutation
field.
resolver (Callable resolver method): Or ``mutate`` method on Mutation class. Perform data
change and return output.
arguments (Dict[str, graphene.Argument]): Or ``Arguments`` inner class with attributes on
Mutation class. Arguments to use for the mutation Field.
name (str): Name of the GraphQL type (must be unique in schema). Defaults to class
name.
description (str): Description of the GraphQL type in the schema. Defaults to class
docstring.
interfaces (Iterable[graphene.Interface]): GraphQL interfaces to extend with the payload
object. All fields from interface will be included in this object's schema.
fields (Dict[str, graphene.Field]): Dictionary of field name to Field. Not recommended to
use (prefer class attributes or ``Meta.output``).
""" """
@classmethod @classmethod
def __init_subclass_with_meta__( def __init_subclass_with_meta__(
cls, resolver=None, output=None, arguments=None, _meta=None, **options cls,
interfaces=(),
resolver=None,
output=None,
arguments=None,
_meta=None,
**options
): ):
if not _meta: if not _meta:
_meta = MutationOptions(cls) _meta = MutationOptions(cls)
output = output or getattr(cls, "Output", None) output = output or getattr(cls, "Output", None)
fields = {} fields = {}
for interface in interfaces:
assert issubclass(interface, Interface), (
'All interfaces of {} must be a subclass of Interface. Received "{}".'
).format(cls.__name__, interface)
fields.update(interface._meta.fields)
if not output: if not output:
# If output is defined, we don't need to get the fields # If output is defined, we don't need to get the fields
fields = OrderedDict() fields = OrderedDict()
@ -70,6 +124,7 @@ class Mutation(ObjectType):
else: else:
_meta.fields = fields _meta.fields = fields
_meta.interfaces = interfaces
_meta.output = output _meta.output = output
_meta.resolver = resolver _meta.resolver = resolver
_meta.arguments = arguments _meta.arguments = arguments
@ -80,6 +135,7 @@ class Mutation(ObjectType):
def Field( def Field(
cls, name=None, description=None, deprecation_reason=None, required=False cls, name=None, description=None, deprecation_reason=None, required=False
): ):
""" Mount instance of mutation Field. """
return Field( return Field(
cls._meta.output, cls._meta.output,
args=cls._meta.arguments, args=cls._meta.arguments,

View File

@ -22,6 +22,70 @@ class ObjectType(BaseType):
Almost all of the GraphQL types you define will be object types. Object types Almost all of the GraphQL types you define will be object types. Object types
have a name, but most importantly describe their fields. have a name, but most importantly describe their fields.
The name of the type defined by an _ObjectType_ defaults to the class name. The type
description defaults to the class docstring. This can be overriden by adding attributes
to a Meta inner class.
The class attributes of an _ObjectType_ are mounted as instances of ``graphene.Field``.
Methods starting with ``resolve_<field_name>`` are bound as resolvers of the matching Field
name. If no resolver is provided, the default resolver is used.
Ambiguous types with Interface and Union can be determined through``is_type_of`` method and
``Meta.possible_types`` attribute.
.. code:: python
from graphene import ObjectType, String, Field
class Person(ObjectType):
class Meta:
description = 'A human'
# implicitly mounted as Field
first_name = String()
# explicitly mounted as Field
last_name = Field(String)
def resolve_last_name(parent, info):
return last_name
ObjectType must be mounted using ``graphene.Field``.
.. code:: python
from graphene import ObjectType, Field
class Query(ObjectType):
person = Field(Person, description="My favorite person")
Meta class options (optional):
name (str): Name of the GraphQL type (must be unique in schema). Defaults to class
name.
description (str): Description of the GraphQL type in the schema. Defaults to class
docstring.
interfaces (Iterable[graphene.Interface]): GraphQL interfaces to extend with this object.
all fields from interface will be included in this object's schema.
possible_types (Iterable[class]): Used to test parent value object via isintance to see if
this type can be used to resolve an ambigous type (interface, union).
default_resolver (any Callable resolver): Override the default resolver for this
type. Defaults to graphene default resolver which returns an attribute or dictionary
key with the same name as the field.
fields (Dict[str, graphene.Field]): Dictionary of field name to Field. Not recommended to
use (prefer class attributes).
An _ObjectType_ can be used as a simple value object by creating an instance of the class.
.. code:: python
p = Person(first_name='Bob', last_name='Roberts')
assert p.first_name == 'Bob'
Args:
*args (List[Any]): Positional values to use for Field values of value object
**kwargs (Dict[str: Any]): Keyword arguments to use for Field values of value object
""" """
@classmethod @classmethod
@ -57,7 +121,8 @@ class ObjectType(BaseType):
else: else:
_meta.fields = fields _meta.fields = fields
_meta.interfaces = interfaces if not _meta.interfaces:
_meta.interfaces = interfaces
_meta.possible_types = possible_types _meta.possible_types = possible_types
_meta.default_resolver = default_resolver _meta.default_resolver = default_resolver

View File

@ -27,10 +27,26 @@ def assert_valid_root_type(_type):
class Schema(GraphQLSchema): class Schema(GraphQLSchema):
""" """
Schema Definition Graphene Schema can execute operations (query, mutation, subscription) against the defined
types.
A Schema is created by supplying the root types of each type of operation, For advanced purposes, the schema can be used to lookup type definitions and answer questions
query and mutation (optional). about the types through introspection.
Args:
query (ObjectType): Root query *ObjectType*. Describes entry point for fields to *read*
data in your Schema.
mutation (ObjectType, optional): Root mutation *ObjectType*. Describes entry point for
fields to *create, update or delete* data in your API.
subscription (ObjectType, optional): Root subscription *ObjectType*. Describes entry point
for fields to receive continuous updates.
directives (List[GraphQLDirective], optional): List of custom directives to include in
GraphQL schema. Defaults to only include directives definved by GraphQL spec (@include
and @skip) [GraphQLIncludeDirective, GraphQLSkipDirective].
types (List[GraphQLType], optional): List of any types to include in schema that
may not be introspected through root types.
auto_camelcase (bool): Fieldnames will be transformed in Schema's TypeMap from snake_case
to camelCase (preferred by GraphQL standard). Default True.
""" """
def __init__( def __init__(
@ -99,6 +115,32 @@ class Schema(GraphQLSchema):
raise Exception("{} is not a valid GraphQL type.".format(_type)) raise Exception("{} is not a valid GraphQL type.".format(_type))
def execute(self, *args, **kwargs): def execute(self, *args, **kwargs):
"""
Use the `graphql` function from `graphql-core` to provide the result for a query string.
Most of the time this method will be called by one of the Graphene :ref:`Integrations`
via a web request.
Args:
request_string (str or Document): GraphQL request (query, mutation or subscription) in
string or parsed AST form from `graphql-core`.
root (Any, optional): Value to use as the parent value object when resolving root
types.
context (Any, optional): Value to be made avaiable to all resolvers via
`info.context`. Can be used to share authorization, dataloaders or other
information needed to resolve an operation.
variables (dict, optional): If variables are used in the request string, they can be
provided in dictionary form mapping the variable name to the variable value.
operation_name (str, optional): If mutiple operations are provided in the
request_string, an operation name must be provided for the result to be provided.
middleware (List[SupportsGraphQLMiddleware]): Supply request level middleware as
defined in `graphql-core`.
backend (GraphQLCoreBackend, optional): Override the default GraphQLCoreBackend.
**execute_options (Any): Depends on backend selected. Default backend has several
options such as: validate, allow_subscriptions, return_promise, executor.
Returns:
:obj:`ExecutionResult` containing any data and errors for the operation.
"""
return graphql(self, *args, **kwargs) return graphql(self, *args, **kwargs)
def introspect(self): def introspect(self):

View File

@ -39,6 +39,14 @@ class List(Structure):
A list is a kind of type marker, a wrapping type which points to another A list is a kind of type marker, a wrapping type which points to another
type. Lists are often created within the context of defining the fields of type. Lists are often created within the context of defining the fields of
an object type. an object type.
List indicates that many values will be returned (or input) for this field.
.. code:: python
from graphene import List, String
field_name = List(String, description="There will be many values")
""" """
def __str__(self): def __str__(self):
@ -63,6 +71,16 @@ class NonNull(Structure):
usually the id field of a database row will never be null. usually the id field of a database row will never be null.
Note: the enforcement of non-nullability occurs within the executor. Note: the enforcement of non-nullability occurs within the executor.
NonNull can also be indicated on all Mounted types with the keyword argument ``required``.
.. code:: python
from graphene import NonNull, String
field_name = NonNull(String, description='This field will not be null')
another_field = String(required=True, description='This is equivalent to the above')
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -7,6 +7,11 @@ from ..objecttype import ObjectType
from ..scalars import String from ..scalars import String
from ..schema import Schema from ..schema import Schema
from ..structures import NonNull from ..structures import NonNull
from ..interface import Interface
class MyType(Interface):
pass
def test_generate_mutation_no_args(): def test_generate_mutation_no_args():
@ -28,12 +33,14 @@ def test_generate_mutation_with_meta():
class Meta: class Meta:
name = "MyOtherMutation" name = "MyOtherMutation"
description = "Documentation" description = "Documentation"
interfaces = (MyType,)
def mutate(self, info, **args): def mutate(self, info, **args):
return args return args
assert MyMutation._meta.name == "MyOtherMutation" assert MyMutation._meta.name == "MyOtherMutation"
assert MyMutation._meta.description == "Documentation" assert MyMutation._meta.description == "Documentation"
assert MyMutation._meta.interfaces == (MyType,)
resolved = MyMutation.Field().resolver(None, None, name="Peter") resolved = MyMutation.Field().resolver(None, None, name="Peter")
assert resolved == {"name": "Peter"} assert resolved == {"name": "Peter"}

View File

@ -19,6 +19,34 @@ class Union(UnmountedType, BaseType):
When a field can return one of a heterogeneous set of types, a Union type When a field can return one of a heterogeneous set of types, a Union type
is used to describe what types are possible as well as providing a function is used to describe what types are possible as well as providing a function
to determine which type is actually used when the field is resolved. to determine which type is actually used when the field is resolved.
The schema in this example can take a search text and return any of the GraphQL object types
indicated: Human, Droid or Startship.
Ambigous return types can be resolved on each ObjectType through ``Meta.possible_types``
attribute or ``is_type_of`` method. Or by implementing ``resolve_type`` class method on the
Union.
.. code:: python
from graphene import Union, ObjectType, List
class SearchResult(Union):
class Meta:
types = (Human, Droid, Starship)
class Query(ObjectType):
search = List(SearchResult.Field(
search_text=String(description='Value to search for'))
)
Meta:
types (Iterable[graphene.ObjectType]): Required. Collection of types that may be returned
by this Union for the graphQL schema.
name (optional, str): the name of the GraphQL type (must be unique in schema). Defaults to class
name.
description (optional, str): the description of the GraphQL type in the schema. Defaults to class
docstring.
""" """
@classmethod @classmethod

View File

@ -6,13 +6,37 @@ class UnmountedType(OrderedType):
This class acts a proxy for a Graphene Type, so it can be mounted This class acts a proxy for a Graphene Type, so it can be mounted
dynamically as Field, InputField or Argument. dynamically as Field, InputField or Argument.
Instead of writing Instead of writing:
>>> class MyObjectType(ObjectType):
>>> my_field = Field(String, description='Description here')
It let you write .. code:: python
>>> class MyObjectType(ObjectType):
>>> my_field = String(description='Description here') from graphene import ObjectType, Field, String
class MyObjectType(ObjectType):
my_field = Field(String, description='Description here')
It lets you write:
.. code:: python
from graphene import ObjectType, String
class MyObjectType(ObjectType):
my_field = String(description='Description here')
It is not used directly, but is inherited by other types and streamlines their use in
different context:
- Object Type
- Scalar Type
- Enum
- Interface
- Union
An unmounted type will accept arguments based upon its context (ObjectType, Field or
InputObjectType) and pass it on to the appropriate MountedType (Field, Argument or InputField).
See each Mounted type reference for more information about valid parameters.
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -7,7 +7,10 @@ from .scalars import Scalar
class UUID(Scalar): class UUID(Scalar):
"""UUID""" """
Leverages the internal Python implmeentation of UUID (uuid.UUID) to provide native UUID objects
in fields, resolvers and input.
"""
@staticmethod @staticmethod
def serialize(uuid): def serialize(uuid):

View File

@ -81,8 +81,8 @@ setup(
packages=find_packages(exclude=["tests", "tests.*", "examples"]), packages=find_packages(exclude=["tests", "tests.*", "examples"]),
install_requires=[ install_requires=[
"graphql-core>=2.1,<3", "graphql-core>=2.1,<3",
"graphql-relay>=0.4.5,<1", "graphql-relay>=2,<3",
"aniso8601>=3,<=6.0.*", "aniso8601>=3,<=7",
], ],
tests_require=tests_require, tests_require=tests_require,
extras_require={ extras_require={

View File

@ -11,7 +11,7 @@ commands =
py{36,37}: py.test --cov=graphene graphene examples tests_asyncio {posargs} py{36,37}: py.test --cov=graphene graphene examples tests_asyncio {posargs}
[testenv:pre-commit] [testenv:pre-commit]
basepython=python3.6 basepython=python3.7
deps = deps =
pre-commit>0.12.0 pre-commit>0.12.0
setenv = setenv =
@ -20,7 +20,7 @@ commands =
pre-commit {posargs:run --all-files} pre-commit {posargs:run --all-files}
[testenv:mypy] [testenv:mypy]
basepython=python3.6 basepython=python3.7
deps = deps =
mypy mypy
commands = commands =