Merge branch 'master' into default-resolver

This commit is contained in:
Eran Kampf 2019-03-11 10:07:40 -07:00 committed by GitHub
commit 1af047a33e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1067 additions and 202 deletions

View File

@ -1,24 +1,28 @@
repos: repos:
- repo: git://github.com/pre-commit/pre-commit-hooks - repo: git://github.com/pre-commit/pre-commit-hooks
rev: v1.3.0 rev: v2.1.0
hooks: hooks:
- id: check-merge-conflict
- id: check-json - id: check-json
- id: check-yaml - id: check-yaml
- id: debug-statements - id: debug-statements
- id: end-of-file-fixer - id: end-of-file-fixer
exclude: ^docs/.*$ exclude: ^docs/.*$
- id: trailing-whitespace
exclude: README.md
- id: pretty-format-json - id: pretty-format-json
args: args:
- --autofix - --autofix
- id: flake8 - id: trailing-whitespace
exclude: README.md
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v1.4.0 rev: v1.12.0
hooks: hooks:
- id: pyupgrade - id: pyupgrade
- repo: https://github.com/ambv/black - repo: https://github.com/ambv/black
rev: 18.6b4 rev: 18.9b0
hooks: hooks:
- id: black - id: black
language_version: python3.6 language_version: python3
- repo: https://github.com/PyCQA/flake8
rev: 3.7.7
hooks:
- id: flake8

View File

@ -9,6 +9,10 @@ matrix:
python: 3.5 python: 3.5
- env: TOXENV=py36 - env: TOXENV=py36
python: 3.6 python: 3.6
- env: TOXENV=py37
python: 3.7
dist: xenial
sudo: true
- env: TOXENV=pypy - env: TOXENV=pypy
python: pypy-5.7.1 python: pypy-5.7.1
- env: TOXENV=pre-commit - env: TOXENV=pre-commit

97
BACKERS.md Normal file
View File

@ -0,0 +1,97 @@
<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-->

1
CODEOWNERS Normal file
View File

@ -0,0 +1 @@
/ @syrusakbary @ekampf @dan98765 @projectcheshire

11
Makefile Normal file
View File

@ -0,0 +1,11 @@
.PHONY: help
help:
@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}'
.PHONY: docs ## Generate docs
docs:
@cd docs &&\
pip install -r requirements.txt &&\
make html &&\
cd -

105
README.md
View File

@ -1,9 +1,77 @@
Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade to Graphene `2.0`. **We are looking for contributors**! Please check the [ROADMAP](https://github.com/graphql-python/graphene/blob/master/ROADMAP.md) to see how you can help ❤️
--- ---
# ![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
[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.
@ -13,17 +81,16 @@ Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade to Graph
We believe that by providing a complete API you could plug Graphene anywhere your data lives and make your data available We believe that by providing a complete API you could plug Graphene anywhere your data lives and make your data available
through GraphQL. through GraphQL.
## Integrations ## Integrations
Graphene has multiple integrations with different frameworks: Graphene has multiple integrations with different frameworks:
| integration | Package | | integration | Package |
|---------------|-------------------| | ----------------- | --------------------------------------------------------------------------------------- |
| Django | [graphene-django](https://github.com/graphql-python/graphene-django/) | | Django | [graphene-django](https://github.com/graphql-python/graphene-django/) |
| SQLAlchemy | [graphene-sqlalchemy](https://github.com/graphql-python/graphene-sqlalchemy/) | | SQLAlchemy | [graphene-sqlalchemy](https://github.com/graphql-python/graphene-sqlalchemy/) |
| Google App Engine | [graphene-gae](https://github.com/graphql-python/graphene-gae/) | | Google App Engine | [graphene-gae](https://github.com/graphql-python/graphene-gae/) |
| Peewee | *In progress* ([Tracking Issue](https://github.com/graphql-python/graphene/issues/289)) | | Peewee | _In progress_ ([Tracking Issue](https://github.com/graphql-python/graphene/issues/289)) |
Also, Graphene is fully compatible with the GraphQL spec, working seamlessly with all GraphQL clients, such as [Relay](https://github.com/facebook/relay), [Apollo](https://github.com/apollographql/apollo-client) and [gql](https://github.com/graphql-python/gql). Also, Graphene is fully compatible with the GraphQL spec, working seamlessly with all GraphQL clients, such as [Relay](https://github.com/facebook/relay), [Apollo](https://github.com/apollographql/apollo-client) and [gql](https://github.com/graphql-python/gql).
@ -39,7 +106,6 @@ pip install "graphene>=2.0"
Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade. Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade.
## Examples ## Examples
Here is one example for you to get started: Here is one example for you to get started:
@ -67,9 +133,13 @@ result = schema.execute(query)
If you want to learn even more, you can also check the following [examples](examples/): If you want to learn even more, you can also check the following [examples](examples/):
* **Basic Schema**: [Starwars example](examples/starwars) - **Basic Schema**: [Starwars example](examples/starwars)
* **Relay Schema**: [Starwars Relay example](examples/starwars_relay) - **Relay Schema**: [Starwars Relay example](examples/starwars_relay)
## Documentation
Documentation and links to additional resources are available at
https://docs.graphene-python.org/en/latest/
## Contributing ## Contributing
@ -84,8 +154,8 @@ pip install -e ".[test]"
Well-written tests and maintaining good test coverage is important to this project. While developing, run new and existing tests with: Well-written tests and maintaining good test coverage is important to this project. While developing, run new and existing tests with:
```sh ```sh
py.test PATH/TO/MY/DIR/test_test.py # Single file py.test graphene/relay/tests/test_node.py # Single file
py.test PATH/TO/MY/DIR/ # All tests in directory py.test graphene/relay # All tests in directory
``` ```
Add the `-s` flag if you have introduced breakpoints into the code for debugging. Add the `-s` flag if you have introduced breakpoints into the code for debugging.
@ -99,28 +169,25 @@ py.test graphene --benchmark-only
``` ```
Graphene supports several versions of Python. To make sure that changes do not break compatibility with any of those versions, we use `tox` to create virtualenvs for each python version and run tests with that version. To run against all python versions defined in the `tox.ini` config file, just run: Graphene supports several versions of Python. To make sure that changes do not break compatibility with any of those versions, we use `tox` to create virtualenvs for each python version and run tests with that version. To run against all python versions defined in the `tox.ini` config file, just run:
```sh ```sh
tox tox
``` ```
If you wish to run against a specific version defined in the `tox.ini` file: If you wish to run against a specific version defined in the `tox.ini` file:
```sh ```sh
tox -e py36 tox -e py36
``` ```
Tox can only use whatever versions of python are installed on your system. When you create a pull request, Travis will also be running the same tests and report the results, so there is no need for potential contributors to try to install every single version of python on their own system ahead of time. We appreciate opening issues and pull requests to make graphene even more stable & useful! Tox can only use whatever versions of python are installed on your system. When you create a pull request, Travis will also be running the same tests and report the results, so there is no need for potential contributors to try to install every single version of python on their own system ahead of time. We appreciate opening issues and pull requests to make graphene even more stable & useful!
### Documentation ### Building Documentation
The documentation is generated using the excellent [Sphinx](http://www.sphinx-doc.org/) and a custom theme. The documentation is generated using the excellent [Sphinx](http://www.sphinx-doc.org/) and a custom theme.
The documentation dependencies are installed by running: An HTML version of the documentation is produced by running:
```sh ```sh
cd docs make docs
pip install -r requirements.txt
```
Then to produce a HTML version of the documentation:
```sh
make html
``` ```

View File

@ -1,11 +1,190 @@
Please read `UPGRADE-v2.0.md </UPGRADE-v2.0.md>`__ to learn how to **We are looking for contributors**! Please check the
upgrade to Graphene ``2.0``. `ROADMAP <https://github.com/graphql-python/graphene/blob/master/ROADMAP.md>`__
to see how you can help ❤️
-------------- --------------
|Graphene Logo| `Graphene <http://graphene-python.org>`__ |Build Status| |PyPI version| |Coverage Status| |Graphene Logo| `Graphene <http://graphene-python.org>`__ |Build Status| |PyPI version| |Coverage Status|
========================================================================================================= =========================================================================================================
.. raw:: html
<h1 align="center">
Supporting Graphene Python
.. raw:: html
</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/>`__
<!--
.. raw:: html
<h2 align="center">
Special Sponsors
.. raw:: html
</h2>
.. raw:: html
<p align="center">
.. raw:: html
</p>
.. raw:: html
<!--special end-->
.. raw:: html
<h2 align="center">
Platinum via Patreon
.. raw:: html
</h2>
.. raw:: html
<!--platinum start-->
.. raw:: html
<table>
.. raw:: html
<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>
.. raw:: html
</tbody>
.. raw:: html
</table>
.. raw:: html
<h2 align="center">
Gold via Patreon
.. raw:: html
</h2>
.. raw:: html
<!--gold start-->
.. raw:: html
<table>
.. raw:: html
<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>
.. raw:: html
</tbody>
.. raw:: html
</table>
.. raw:: html
<!--gold end-->
.. raw:: html
<h2 align="center">
Silver via Patreon
.. raw:: html
</h2>
.. raw:: html
<!--silver start-->
.. raw:: html
<table>
.. raw:: html
<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>
.. raw:: html
</tbody>
.. raw:: html
</table>
.. raw:: html
<!--silver end-->
--------------
Introduction
------------
`Graphene <http://graphene-python.org>`__ is a Python library for `Graphene <http://graphene-python.org>`__ is a Python library for
building GraphQL schemas/types fast and easily. building GraphQL schemas/types fast and easily.
@ -88,20 +267,38 @@ If you want to learn even more, you can also check the following
- **Relay Schema**: `Starwars Relay - **Relay Schema**: `Starwars Relay
example <examples/starwars_relay>`__ example <examples/starwars_relay>`__
Documentation
-------------
Documentation and links to additional resources are available at
https://docs.graphene-python.org/en/latest/
Contributing Contributing
------------ ------------
After cloning this repo, ensure dependencies are installed by running: After cloning this repo, create a
`virtualenv <https://virtualenv.pypa.io/en/stable/>`__ and ensure
dependencies are installed by running:
.. code:: sh .. code:: sh
virtualenv venv
source venv/bin/activate
pip install -e ".[test]" pip install -e ".[test]"
After developing, the full test suite can be evaluated by running: Well-written tests and maintaining good test coverage is important to
this project. While developing, run new and existing tests with:
.. code:: sh .. code:: sh
py.test graphene --cov=graphene --benchmark-skip # Use -v -s for verbose mode py.test graphene/relay/tests/test_node.py # Single file
py.test graphene/relay # All tests in directory
Add the ``-s`` flag if you have introduced breakpoints into the code for
debugging. Add the ``-v`` ("verbose") flag to get more detailed test
output. For even more detailed output, use ``-vv``. Check out the
`pytest documentation <https://docs.pytest.org/en/latest/>`__ for more
options and test running controls.
You can also run the benchmarks with: You can also run the benchmarks with:
@ -109,24 +306,41 @@ You can also run the benchmarks with:
py.test graphene --benchmark-only py.test graphene --benchmark-only
Documentation Graphene supports several versions of Python. To make sure that changes
do not break compatibility with any of those versions, we use ``tox`` to
create virtualenvs for each python version and run tests with that
version. To run against all python versions defined in the ``tox.ini``
config file, just run:
.. code:: sh
tox
If you wish to run against a specific version defined in the ``tox.ini``
file:
.. code:: sh
tox -e py36
Tox can only use whatever versions of python are installed on your
system. When you create a pull request, Travis will also be running the
same tests and report the results, so there is no need for potential
contributors to try to install every single version of python on their
own system ahead of time. We appreciate opening issues and pull requests
to make graphene even more stable & useful!
Building Documentation
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
The documentation is generated using the excellent The documentation is generated using the excellent
`Sphinx <http://www.sphinx-doc.org/>`__ and a custom theme. `Sphinx <http://www.sphinx-doc.org/>`__ and a custom theme.
The documentation dependencies are installed by running: An HTML version of the documentation is produced by running:
.. code:: sh .. code:: sh
cd docs make docs
pip install -r requirements.txt
Then to produce a HTML version of the documentation:
.. code:: sh
make html
.. |Graphene Logo| image:: http://graphene-python.org/favicon.png .. |Graphene Logo| image:: http://graphene-python.org/favicon.png
.. |Build Status| image:: https://travis-ci.org/graphql-python/graphene.svg?branch=master .. |Build Status| image:: https://travis-ci.org/graphql-python/graphene.svg?branch=master

33
ROADMAP.md Normal file
View File

@ -0,0 +1,33 @@
# Graphene 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.
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)_
## Community
The goal is to improve adoption and sustainability of the project.
- 💎 Add Commercial Support for Graphene - [See issue](https://github.com/graphql-python/graphene/issues/813)
- Create [Patreon page](https://www.patreon.com/syrusakbary)
- 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
The goal is to summarize the different improvements that Graphene will need to accomplish for version 3.
In a nushell, Graphene 3 should take the Python 3 integration one step forward while still maintaining compatibility with Python 2.
- 🚀 [graphql-core-next](https://github.com/graphql-python/graphql-core-next) GraphQL engine support (almost same API as graphql-core)
- 🔸 GraphQL types from type annotations - [See issue](https://github.com/graphql-python/graphene/issues/729)
- 📄 Schema creation from SDL (API TBD)
- ✨ Improve connections structure
- 📗 Improve function documentation
- 🔀 Add support for coroutines in Connection, Mutation (abstracting out Promise requirement) - [See PR](https://github.com/graphql-python/graphene/pull/824)

View File

@ -17,7 +17,7 @@ developer has to write to use them.
**New Features!** **New Features!**
* [`InputObjectType`](#inputobjecttype) * [`InputObjectType`](#inputobjecttype)
* [`Meta as Class arguments`](#meta-ass-class-arguments) (_only available for Python 3_) * [`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

View File

@ -17,75 +17,50 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help .PHONY: help
help: help:
@echo "Please use \`make <target>' where <target> is one of" @echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files" @grep -E '^\.PHONY: [a-zA-Z_-]+ .*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = "(: |##)"}; {printf "\033[36m%-30s\033[0m %s\n", $$2, $$3}'
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " epub3 to make an epub3"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
@echo " dummy to check syntax errors of document sources"
.PHONY: clean .PHONY: clean
clean: clean:
rm -rf $(BUILDDIR)/* rm -rf $(BUILDDIR)/*
.PHONY: html .PHONY: html ## to make standalone HTML files
html: html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo @echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
.PHONY: dirhtml .PHONY: dirhtml ## to make HTML files named index.html in directories
dirhtml: dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo @echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
.PHONY: singlehtml .PHONY: singlehtml ## to make a single large HTML file
singlehtml: singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo @echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
.PHONY: pickle .PHONY: pickle ## to make pickle files
pickle: pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo @echo
@echo "Build finished; now you can process the pickle files." @echo "Build finished; now you can process the pickle files."
.PHONY: json .PHONY: json ## to make JSON files
json: json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo @echo
@echo "Build finished; now you can process the JSON files." @echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp .PHONY: htmlhelp ## to make HTML files and a HTML help project
htmlhelp: htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo @echo
@echo "Build finished; now you can run HTML Help Workshop with the" \ @echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp." ".hhp project file in $(BUILDDIR)/htmlhelp."
.PHONY: qthelp .PHONY: qthelp ## to make HTML files and a qthelp project
qthelp: qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo @echo
@ -95,7 +70,7 @@ qthelp:
@echo "To view the help file:" @echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Graphene.qhc" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Graphene.qhc"
.PHONY: applehelp .PHONY: applehelp ## to make an Apple Help Book
applehelp: applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo @echo
@ -104,7 +79,7 @@ applehelp:
"~/Library/Documentation/Help or install it in your application" \ "~/Library/Documentation/Help or install it in your application" \
"bundle." "bundle."
.PHONY: devhelp .PHONY: devhelp ## to make HTML files and a Devhelp project
devhelp: devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo @echo
@ -114,19 +89,19 @@ devhelp:
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Graphene" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Graphene"
@echo "# devhelp" @echo "# devhelp"
.PHONY: epub .PHONY: epub ## to make an epub
epub: epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo @echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub." @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: epub3 .PHONY: epub3 ## to make an epub3
epub3: epub3:
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
@echo @echo
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
.PHONY: latex .PHONY: latex ## to make LaTeX files, you can set PAPER=a4 or PAPER=letter
latex: latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo @echo
@ -134,33 +109,33 @@ latex:
@echo "Run \`make' in that directory to run these through (pdf)latex" \ @echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)." "(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf .PHONY: latexpdf ## to make LaTeX files and run them through pdflatex
latexpdf: latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..." @echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf $(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: latexpdfja .PHONY: latexpdfja ## to make LaTeX files and run them through platex/dvipdfmx
latexpdfja: latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..." @echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: text .PHONY: text ## to make text files
text: text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo @echo
@echo "Build finished. The text files are in $(BUILDDIR)/text." @echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man .PHONY: man ## to make manual pages
man: man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo @echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man." @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo .PHONY: texinfo ## to make Texinfo files
texinfo: texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo @echo
@ -168,57 +143,57 @@ texinfo:
@echo "Run \`make' in that directory to run these through makeinfo" \ @echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)." "(use \`make info' here to do that automatically)."
.PHONY: info .PHONY: info ## to make Texinfo files and run them through makeinfo
info: info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..." @echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: gettext .PHONY: gettext ## to make PO message catalogs
gettext: gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo @echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes .PHONY: changes ## to make an overview of all changed/added/deprecated items
changes: changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo @echo
@echo "The overview file is in $(BUILDDIR)/changes." @echo "The overview file is in $(BUILDDIR)/changes."
.PHONY: linkcheck .PHONY: linkcheck ## to check all external links for integrity
linkcheck: linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo @echo
@echo "Link check complete; look for any errors in the above output " \ @echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt." "or in $(BUILDDIR)/linkcheck/output.txt."
.PHONY: doctest .PHONY: doctest ## to run all doctests embedded in the documentation (if enabled)
doctest: doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \ @echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt." "results in $(BUILDDIR)/doctest/output.txt."
.PHONY: coverage .PHONY: coverage ## to run coverage check of the documentation (if enabled)
coverage: coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \ @echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt." "results in $(BUILDDIR)/coverage/python.txt."
.PHONY: xml .PHONY: xml ## to make Docutils-native XML files
xml: xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo @echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml." @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
.PHONY: pseudoxml .PHONY: pseudoxml ## to make pseudoxml-XML files for display purposes
pseudoxml: pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo @echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
.PHONY: dummy .PHONY: dummy ## to check syntax errors of document sources
dummy: dummy:
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
@echo @echo

View File

@ -25,8 +25,8 @@ Create loaders by providing a batch loading function.
return Promise.resolve([get_user(id=key) for key in keys]) return Promise.resolve([get_user(id=key) for key in keys])
A batch loading function accepts an list of keys, and returns a ``Promise`` A batch loading function accepts a list of keys, and returns a ``Promise``
which resolves to an list of ``values``. which resolves to a list of ``values``.
Then load individual values from the loader. ``DataLoader`` will coalesce all Then load individual values from the loader. ``DataLoader`` will coalesce all
individual loads which occur within a single frame of execution (executed once individual loads which occur within a single frame of execution (executed once
@ -34,7 +34,6 @@ the wrapping promise is resolved) and then call your batch function with all
requested keys. requested keys.
.. code:: python .. code:: python
user_loader = UserLoader() user_loader = UserLoader()
@ -47,6 +46,19 @@ requested keys.
A naive application may have issued *four* round-trips to a backend for the A naive application may have issued *four* round-trips to a backend for the
required information, but with ``DataLoader`` this application will make at most *two*. required information, but with ``DataLoader`` this application will make at most *two*.
Note that loaded values are one-to-one with the keys and must have the same
order. This means that if you load all values from a single query, you must
make sure that you then order the query result for the results to match the keys:
.. code:: python
class UserLoader(DataLoader):
def batch_load_fn(self, keys):
users = {user.id: user for user in User.objects.filter(id__in=keys)}
return Promise.resolve([users.get(user_id) for user_id in keys])
``DataLoader`` allows you to decouple unrelated parts of your application without ``DataLoader`` allows you to decouple unrelated parts of your application without
sacrificing the performance of batch data-loading. While the loader presents sacrificing the performance of batch data-loading. While the loader presents
an API that loads individual values, all concurrent requests will be coalesced an API that loads individual values, all concurrent requests will be coalesced

View File

@ -16,7 +16,7 @@ For executing a query a schema, you can directly call the ``execute`` method on
Context Context
_______ _______
You can pass context to a query via ``context_value``. You can pass context to a query via ``context``.
.. code:: python .. code:: python
@ -28,14 +28,14 @@ You can pass context to a query via ``context_value``.
return info.context.get('name') return info.context.get('name')
schema = graphene.Schema(Query) schema = graphene.Schema(Query)
result = schema.execute('{ name }', context_value={'name': 'Syrus'}) result = schema.execute('{ name }', context={'name': 'Syrus'})
Variables Variables
_______ _______
You can pass variables to a query via ``variable_values``. You can pass variables to a query via ``variables``.
.. code:: python .. code:: python
@ -55,5 +55,5 @@ You can pass variables to a query via ``variable_values``.
lastName lastName
} }
}''', }''',
variable_values={'id': 12}, variables={'id': 12},
) )

View File

@ -29,7 +29,7 @@ This middleware only continues evaluation if the ``field_name`` is not ``'user'`
.. code:: python .. code:: python
class AuthorizationMiddleware(object): class AuthorizationMiddleware(object):
def resolve(self, next, root, info, **args): def resolve(next, root, info, **args):
if info.field_name == 'user': if info.field_name == 'user':
return None return None
return next(root, info, **args) return next(root, info, **args)

View File

@ -19,3 +19,5 @@ Integrations
* `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>`_)
* `Starlette <https://www.starlette.io/graphql/>`_ (`source <https://github.com/encode/starlette>`_)
* `FastAPI <https://fastapi.tiangolo.com/tutorial/graphql/>`_ (`source <https://github.com/tiangolo/fastapi>`_)

View File

@ -30,17 +30,18 @@ server with an associated set of resolve methods that know how to fetch
data. data.
We are going to create a very simple schema, with a ``Query`` with only 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 {name}"``. one field: ``hello`` and an input name. And when we query it, it should return ``"Hello
{argument}"``.
.. code:: python .. code:: python
import graphene import graphene
class Query(graphene.ObjectType): class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="stranger")) hello = graphene.String(argument=graphene.String(default_value="stranger"))
def resolve_hello(self, info, name): def resolve_hello(self, info, argument):
return 'Hello ' + name return 'Hello ' + argument
schema = graphene.Schema(query=Query) schema = graphene.Schema(query=Query)
@ -54,4 +55,8 @@ Then we can start querying our schema:
result = schema.execute('{ hello }') result = schema.execute('{ hello }')
print(result.data['hello']) # "Hello stranger" print(result.data['hello']) # "Hello stranger"
# or passing the argument in the query
result = schema.execute('{ hello (argument: "graph") }')
print(result.data['hello']) # "Hello graph"
Congrats! You got your first graphene schema working! Congrats! You got your first graphene schema working!

View File

@ -54,7 +54,7 @@ Execute parameters
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
You can also add extra keyword arguments to the ``execute`` method, such as You can also add extra keyword arguments to the ``execute`` method, such as
``context_value``, ``root_value``, ``variable_values``, ...: ``context``, ``root``, ``variables``, ...:
.. code:: python .. code:: python
@ -63,7 +63,7 @@ You can also add extra keyword arguments to the ``execute`` method, such as
def test_hey(): def test_hey():
client = Client(my_schema) client = Client(my_schema)
executed = client.execute('''{ hey }''', context_value={'user': 'Peter'}) executed = client.execute('''{ hey }''', context={'user': 'Peter'})
assert executed == { assert executed == {
'data': { 'data': {
'hey': 'hello Peter!' 'hey': 'hello Peter!'

View File

@ -1,7 +1,7 @@
Enums Enums
===== =====
A ``Enum`` is a special ``GraphQL`` type that represents a set of An ``Enum`` is a special ``GraphQL`` type that represents a set of
symbolic names (members) bound to unique, constant values. symbolic names (members) bound to unique, constant values.
Definition Definition

View File

@ -91,7 +91,7 @@ For example, you can define a field ``hero`` that resolves to any
schema = graphene.Schema(query=Query, types=[Human, Droid]) schema = graphene.Schema(query=Query, types=[Human, Droid])
This allows you to directly query for fields that exist on the Character interface This allows you to directly query for fields that exist on the Character interface
as well as selecting specific fields on any type that implments the interface as well as selecting specific fields on any type that implements the interface
using `inline fragments <https://graphql.org/learn/queries/#inline-fragments>`_. using `inline fragments <https://graphql.org/learn/queries/#inline-fragments>`_.
For example, the following query: For example, the following query:

View File

@ -27,7 +27,7 @@ This example defines a Mutation:
**person** and **ok** are the output fields of the Mutation when it is **person** and **ok** are the output fields of the Mutation when it is
resolved. resolved.
**Input** attributes are the arguments that the Mutation **Arguments** attributes are the arguments that the Mutation
``CreatePerson`` needs for resolving, in this case **name** will be the ``CreatePerson`` needs for resolving, in this case **name** will be the
only argument for the mutation. only argument for the mutation.

View File

@ -25,8 +25,8 @@ This example model defines a Person, with a first and a last name:
last_name = graphene.String() last_name = graphene.String()
full_name = graphene.String() full_name = graphene.String()
def resolve_full_name(self, info): def resolve_full_name(root, info):
return '{} {}'.format(self.first_name, self.last_name) return '{} {}'.format(root.first_name, root.last_name)
**first\_name** and **last\_name** are fields of the ObjectType. Each **first\_name** and **last\_name** are fields of the ObjectType. Each
field is specified as a class attribute, and each attribute maps to a field is specified as a class attribute, and each attribute maps to a
@ -46,33 +46,158 @@ The above ``Person`` ObjectType has the following schema representation:
Resolvers Resolvers
--------- ---------
A resolver is a method that resolves certain fields within a A resolver is a method that resolves certain fields within an
``ObjectType``. If not specififed otherwise, the resolver of a ``ObjectType``. If not specified otherwise, the resolver of a
field is the ``resolve_{field_name}`` method on the ``ObjectType``. field is the ``resolve_{field_name}`` method on the ``ObjectType``.
By default resolvers take the arguments ``info`` and ``*args``. By default resolvers take the arguments ``info`` and ``*args``.
NOTE: The resolvers on a ``ObjectType`` are always treated as ``staticmethod``\ s, NOTE: The resolvers on an ``ObjectType`` are always treated as ``staticmethod``\ s,
so the first argument to the resolver method ``self`` (or ``root``) need so the first argument to the resolver method ``self`` (or ``root``) need
not be an actual instance of the ``ObjectType``. not be an actual instance of the ``ObjectType``.
If an explicit resolver is not defined on the ``ObjectType`` then Graphene will
attempt to use a property with the same name on the object that is passed to the
``ObjectType``.
Quick example .. code:: python
~~~~~~~~~~~~~
This example model defines a ``Query`` type, which has a reverse field import graphene
that reverses the given ``word`` argument using the ``resolve_reverse``
method in the class. class Person(graphene.ObjectType):
first_name = graphene.String()
last_name = graphene.String()
class Query(graphene.ObjectType):
me = graphene.Field(Person)
def resolve_me(_, info):
# returns an object that represents a Person
return get_human(name='Luke Skywalker')
If you are passing a dict instead of an object to your ``ObjectType`` you can
change the default resolver in the ``Meta`` class like this:
.. code:: python
import graphene
from graphene.types.resolver import dict_resolver
class Person(graphene.ObjectType):
class Meta:
default_resolver = dict_resolver
first_name = graphene.String()
last_name = graphene.String()
class Query(graphene.ObjectType):
me = graphene.Field(Person)
def resolve_me(_, info):
return {
"first_name": "Luke",
"last_name": "Skywalker",
}
Or you can change the default resolver globally by calling ``set_default_resolver``
before executing a query.
.. code:: python
import graphene
from graphene.types.resolver import dict_resolver, set_default_resolver
set_default_resolver(dict_resolver)
schema = graphene.Schema(query=Query)
result = schema.execute('''
query {
me {
firstName
}
}
''')
Resolvers with arguments
~~~~~~~~~~~~~~~~~~~~~~~~
Any arguments that a field defines gets passed to the resolver function as
kwargs. For example:
.. code:: python .. code:: python
import graphene import graphene
class Query(graphene.ObjectType): class Query(graphene.ObjectType):
reverse = graphene.String(word=graphene.String()) human_by_name = graphene.Field(Human, name=graphene.String(required=True))
def resolve_human_by_name(_, info, name):
return get_human(name=name)
You can then execute the following query:
.. code::
query {
humanByName(name: "Luke Skywalker") {
firstName
lastName
}
}
NOTE: 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
resolver function at all. This is so that the developer can differenciate
between a ``undefined`` value for an argument and an explicit ``null`` value.
For example, given this schema:
.. code:: python
import graphene
class Query(graphene.ObjectType):
hello = graphene.String(required=True, name=graphene.String())
def resolve_hello(_, info, name):
return name if name else 'World'
And this query:
.. code::
query {
hello
}
An error will be thrown:
.. code::
TypeError: resolve_hello() missing 1 required positional argument: 'name'
You can fix this error in 2 ways. Either by combining all keyword arguments
into a dict:
.. code:: python
class Query(graphene.ObjectType):
hello = graphene.String(required=True, name=graphene.String())
def resolve_hello(_, info, **args):
return args.get('name', 'World')
Or by setting a default value for the keyword argument:
.. code:: python
class Query(graphene.ObjectType):
hello = graphene.String(required=True, name=graphene.String())
def resolve_hello(_, info, name='World'):
return name
def resolve_reverse(self, info, word):
return word[::-1]
Resolvers outside the class Resolvers outside the class
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -83,11 +208,13 @@ A field can use a custom resolver from outside the class:
import graphene import graphene
def reverse(root, info, word): def resolve_full_name(person, info):
return word[::-1] return '{} {}'.format(person.first_name, person.last_name)
class Query(graphene.ObjectType): class Person(graphene.ObjectType):
reverse = graphene.String(word=graphene.String(), resolver=reverse) first_name = graphene.String()
last_name = graphene.String()
full_name = graphene.String(resolver=resolve_full_name)
Instances as data containers Instances as data containers

View File

@ -39,9 +39,8 @@ Graphene defines the following base Scalar Types:
``graphene.Int`` ``graphene.Int``
Represents non-fractional signed whole numeric Represents non-fractional signed whole numeric
values. Int can represent values between `-(2^53 - 1)` and `2^53 - 1` since values. Int is a signed 32bit integer per the
represented in JSON as double-precision floating point numbers specified `GraphQL spec <https://facebook.github.io/graphql/June2018/#sec-Int>`_
by `IEEE 754 <http://en.wikipedia.org/wiki/IEEE_floating_point>`_.
``graphene.Float`` ``graphene.Float``

View File

@ -25,11 +25,11 @@ query = """
def test_query(): def test_query():
result = schema.execute(query, context_value={"user": User(id="1", name="Syrus")}) result = schema.execute(query, context={"user": User(id="1", name="Syrus")})
assert not result.errors assert not result.errors
assert result.data == {"me": {"id": "1", "name": "Syrus"}} assert result.data == {"me": {"id": "1", "name": "Syrus"}}
if __name__ == "__main__": if __name__ == "__main__":
result = schema.execute(query, context_value={"user": User(id="X", name="Console")}) result = schema.execute(query, context={"user": User(id="X", name="Console")})
print(result.data["me"]) print(result.data["me"])

View File

@ -72,7 +72,7 @@ def test_fetch_some_id_query(snapshot):
} }
""" """
params = {"someId": "1000"} params = {"someId": "1000"}
snapshot.assert_match(client.execute(query, variable_values=params)) snapshot.assert_match(client.execute(query, variables=params))
def test_fetch_some_id_query2(snapshot): def test_fetch_some_id_query2(snapshot):
@ -84,7 +84,7 @@ def test_fetch_some_id_query2(snapshot):
} }
""" """
params = {"someId": "1002"} params = {"someId": "1002"}
snapshot.assert_match(client.execute(query, variable_values=params)) snapshot.assert_match(client.execute(query, variables=params))
def test_invalid_id_query(snapshot): def test_invalid_id_query(snapshot):
@ -96,7 +96,7 @@ def test_invalid_id_query(snapshot):
} }
""" """
params = {"id": "not a valid id"} params = {"id": "not a valid id"}
snapshot.assert_match(client.execute(query, variable_values=params)) snapshot.assert_match(client.execute(query, variables=params))
def test_fetch_luke_aliased(snapshot): def test_fetch_luke_aliased(snapshot):

View File

@ -707,7 +707,10 @@ class Signature(object):
break break
elif param.name in kwargs: elif param.name in kwargs:
if param.kind == _POSITIONAL_ONLY: if param.kind == _POSITIONAL_ONLY:
msg = "{arg!r} parameter is positional only, " "but was passed as a keyword" msg = (
"{arg!r} parameter is positional only, "
"but was passed as a keyword"
)
msg = msg.format(arg=param.name) msg = msg.format(arg=param.name)
raise TypeError(msg) raise TypeError(msg)
parameters_ex = (param,) parameters_ex = (param,)

View File

@ -3,15 +3,21 @@ from collections import Iterable, OrderedDict
from functools import partial from functools import partial
from graphql_relay import connection_from_list from graphql_relay import connection_from_list
from promise import Promise, is_thenable
from ..types import Boolean, Enum, Int, Interface, List, NonNull, Scalar, String, Union from ..types import Boolean, Enum, Int, Interface, List, NonNull, Scalar, String, Union
from ..types.field import Field from ..types.field import Field
from ..types.objecttype import ObjectType, ObjectTypeOptions from ..types.objecttype import ObjectType, ObjectTypeOptions
from ..utils.thenables import maybe_thenable
from .node import is_node from .node import is_node
class PageInfo(ObjectType): class PageInfo(ObjectType):
class Meta:
description = (
"The Relay compliant `PageInfo` type, containing data necessary to"
" paginate this connection."
)
has_next_page = Boolean( has_next_page = Boolean(
required=True, required=True,
name="hasNextPage", name="hasNextPage",
@ -64,21 +70,40 @@ class Connection(ObjectType):
node = Field(_node, description="The item at the end of the edge") node = Field(_node, description="The item at the end of the edge")
cursor = String(required=True, description="A cursor for use in pagination") cursor = String(required=True, description="A cursor for use in pagination")
class EdgeMeta:
description = "A Relay edge containing a `{}` and its cursor.".format(
base_name
)
edge_name = "{}Edge".format(base_name) edge_name = "{}Edge".format(base_name)
if edge_class: if edge_class:
edge_bases = (edge_class, EdgeBase, ObjectType) edge_bases = (edge_class, EdgeBase, ObjectType)
else: else:
edge_bases = (EdgeBase, ObjectType) edge_bases = (EdgeBase, ObjectType)
edge = type(edge_name, edge_bases, {}) edge = type(edge_name, edge_bases, {"Meta": EdgeMeta})
cls.Edge = edge cls.Edge = edge
options["name"] = name options["name"] = name
_meta.node = node _meta.node = node
_meta.fields = OrderedDict( _meta.fields = OrderedDict(
[ [
("page_info", Field(PageInfo, name="pageInfo", required=True)), (
("edges", Field(NonNull(List(edge)))), "page_info",
Field(
PageInfo,
name="pageInfo",
required=True,
description="Pagination data for this connection.",
),
),
(
"edges",
Field(
NonNull(List(edge)),
description="Contains the nodes in this connection.",
),
),
] ]
) )
return super(Connection, cls).__init_subclass_with_meta__( return super(Connection, cls).__init_subclass_with_meta__(
@ -103,7 +128,7 @@ class IterableConnectionField(Field):
if is_node(connection_type): if is_node(connection_type):
raise Exception( raise Exception(
"ConnectionField's now need a explicit ConnectionType for Nodes.\n" "ConnectionFields now need a explicit ConnectionType for Nodes.\n"
"Read more: https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#node-connections" "Read more: https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#node-connections"
) )
@ -139,10 +164,7 @@ class IterableConnectionField(Field):
connection_type = connection_type.of_type connection_type = connection_type.of_type
on_resolve = partial(cls.resolve_connection, connection_type, args) on_resolve = partial(cls.resolve_connection, connection_type, args)
if is_thenable(resolved): return maybe_thenable(resolved, on_resolve)
return Promise.resolve(resolved).then(on_resolve)
return on_resolve(resolved)
def get_resolver(self, parent_resolver): def get_resolver(self, parent_resolver):
resolver = super(IterableConnectionField, self).get_resolver(parent_resolver) resolver = super(IterableConnectionField, self).get_resolver(parent_resolver)

View File

@ -1,10 +1,9 @@
import re import re
from collections import OrderedDict from collections import OrderedDict
from promise import Promise, is_thenable
from ..types import Field, InputObjectType, String from ..types import Field, InputObjectType, String
from ..types.mutation import Mutation from ..types.mutation import Mutation
from ..utils.thenables import maybe_thenable
class ClientIDMutation(Mutation): class ClientIDMutation(Mutation):
@ -69,7 +68,4 @@ class ClientIDMutation(Mutation):
return payload return payload
result = cls.mutate_and_get_payload(root, info, **input) result = cls.mutate_and_get_payload(root, info, **input)
if is_thenable(result): return maybe_thenable(result, on_resolve)
return Promise.resolve(result).then(on_resolve)
return on_resolve(result)

View File

@ -138,7 +138,7 @@ def test_connectionfield_node_deprecated():
with pytest.raises(Exception) as exc_info: with pytest.raises(Exception) as exc_info:
field.type field.type
assert "ConnectionField's now need a explicit ConnectionType for Nodes." in str( assert "ConnectionFields now need a explicit ConnectionType for Nodes." in str(
exc_info.value exc_info.value
) )

View File

@ -75,9 +75,7 @@ def to_arguments(args, extra_args=None):
arg_name = default_name or arg.name arg_name = default_name or arg.name
assert ( assert (
arg_name not in arguments arg_name not in arguments
), 'More than one Argument have same name "{}".'.format( ), 'More than one Argument have same name "{}".'.format(arg_name)
arg_name
)
arguments[arg_name] = arg arguments[arg_name] = arg
return arguments return arguments

View File

@ -1,5 +1,9 @@
from ..utils.subclass_with_meta import SubclassWithMeta from ..utils.subclass_with_meta import SubclassWithMeta
from ..utils.trim_docstring import trim_docstring from ..utils.trim_docstring import trim_docstring
import six
if six.PY3:
from typing import Type
class BaseOptions(object): class BaseOptions(object):

View File

@ -85,7 +85,7 @@ class Mutation(ObjectType):
args=cls._meta.arguments, args=cls._meta.arguments,
resolver=cls._meta.resolver, resolver=cls._meta.resolver,
name=name, name=name,
description=description, description=description or cls._meta.description,
deprecation_reason=deprecation_reason, deprecation_reason=deprecation_reason,
required=required, required=required,
) )

View File

@ -4,6 +4,9 @@ from graphql.language.ast import BooleanValue, FloatValue, IntValue, StringValue
from .base import BaseOptions, BaseType from .base import BaseOptions, BaseType
from .unmountedtype import UnmountedType from .unmountedtype import UnmountedType
if six.PY3:
from typing import Any
class ScalarOptions(BaseOptions): class ScalarOptions(BaseOptions):
pass pass

View File

@ -90,7 +90,7 @@ def test_datetime_query_variable():
result = schema.execute( result = schema.execute(
"""query Test($date: DateTime){ datetime(in: $date) }""", """query Test($date: DateTime){ datetime(in: $date) }""",
variable_values={"date": isoformat}, variables={"date": isoformat},
) )
assert not result.errors assert not result.errors
assert result.data == {"datetime": isoformat} assert result.data == {"datetime": isoformat}
@ -101,8 +101,7 @@ def test_date_query_variable():
isoformat = now.isoformat() isoformat = now.isoformat()
result = schema.execute( result = schema.execute(
"""query Test($date: Date){ date(in: $date) }""", """query Test($date: Date){ date(in: $date) }""", variables={"date": isoformat}
variable_values={"date": isoformat},
) )
assert not result.errors assert not result.errors
assert result.data == {"date": isoformat} assert result.data == {"date": isoformat}
@ -114,8 +113,7 @@ def test_time_query_variable():
isoformat = time.isoformat() isoformat = time.isoformat()
result = schema.execute( result = schema.execute(
"""query Test($time: Time){ time(at: $time) }""", """query Test($time: Time){ time(at: $time) }""", variables={"time": isoformat}
variable_values={"time": isoformat},
) )
assert not result.errors assert not result.errors
assert result.data == {"time": isoformat} assert result.data == {"time": isoformat}

View File

@ -28,7 +28,7 @@ def test_decimal_string_query_variable():
result = schema.execute( result = schema.execute(
"""query Test($decimal: Decimal){ decimal(input: $decimal) }""", """query Test($decimal: Decimal){ decimal(input: $decimal) }""",
variable_values={"decimal": decimal_value}, variables={"decimal": decimal_value},
) )
assert not result.errors assert not result.errors
assert result.data == {"decimal": str(decimal_value)} assert result.data == {"decimal": str(decimal_value)}

View File

@ -1,5 +1,3 @@
from ..argument import Argument from ..argument import Argument
from ..enum import Enum from ..enum import Enum
from ..field import Field from ..field import Field

View File

@ -39,7 +39,7 @@ def test_generic_query_variable():
]: ]:
result = schema.execute( result = schema.execute(
"""query Test($generic: GenericScalar){ generic(input: $generic) }""", """query Test($generic: GenericScalar){ generic(input: $generic) }""",
variable_values={"generic": generic_value}, variables={"generic": generic_value},
) )
assert not result.errors assert not result.errors
assert result.data == {"generic": generic_value} assert result.data == {"generic": generic_value}

View File

@ -1,4 +1,3 @@
from ..argument import Argument from ..argument import Argument
from ..field import Field from ..field import Field
from ..inputfield import InputField from ..inputfield import InputField

View File

@ -1,4 +1,3 @@
from ..json import JSONString from ..json import JSONString
from ..objecttype import ObjectType from ..objecttype import ObjectType
from ..schema import Schema from ..schema import Schema
@ -28,7 +27,7 @@ def test_jsonstring_query_variable():
result = schema.execute( result = schema.execute(
"""query Test($json: JSONString){ json(input: $json) }""", """query Test($json: JSONString){ json(input: $json) }""",
variable_values={"json": json_value}, variables={"json": json_value},
) )
assert not result.errors assert not result.errors
assert result.data == {"json": json_value} assert result.data == {"json": json_value}

View File

@ -1,4 +1,3 @@
from ..field import Field from ..field import Field
from ..scalars import String from ..scalars import String

View File

@ -139,17 +139,41 @@ def test_mutation_allow_to_have_custom_args():
class MyMutation(ObjectType): class MyMutation(ObjectType):
create_user = CreateUser.Field( create_user = CreateUser.Field(
name="createUser",
description="Create a user", description="Create a user",
deprecation_reason="Is deprecated", deprecation_reason="Is deprecated",
required=True, required=True,
) )
field = MyMutation._meta.fields["create_user"] field = MyMutation._meta.fields["create_user"]
assert field.name == "createUser"
assert field.description == "Create a user" assert field.description == "Create a user"
assert field.deprecation_reason == "Is deprecated" assert field.deprecation_reason == "Is deprecated"
assert field.type == NonNull(CreateUser) assert field.type == NonNull(CreateUser)
def test_mutation_default_args_output():
class CreateUser(Mutation):
"""Description."""
class Arguments:
name = String()
name = String()
def mutate(self, info, name):
return CreateUser(name=name)
class MyMutation(ObjectType):
create_user = CreateUser.Field()
field = MyMutation._meta.fields["create_user"]
assert field.name is None
assert field.description == "Description."
assert field.deprecation_reason is None
assert field.type == CreateUser
def test_mutation_as_subclass(): def test_mutation_as_subclass():
class BaseCreateUser(Mutation): class BaseCreateUser(Mutation):
class Arguments: class Arguments:

View File

@ -464,7 +464,7 @@ def test_query_annotated_resolvers():
assert not result.errors assert not result.errors
assert result.data == {"annotated": "base-self"} assert result.data == {"annotated": "base-self"}
result = test_schema.execute("{ context }", "base", context_value=context) result = test_schema.execute("{ context }", "base", context=context)
assert not result.errors assert not result.errors
assert result.data == {"context": "base-context"} assert result.data == {"context": "base-context"}

View File

@ -1,4 +1,3 @@
from ..resolver import ( from ..resolver import (
attr_resolver, attr_resolver,
dict_resolver, dict_resolver,

View File

@ -1,4 +1,3 @@
from ..scalars import Scalar from ..scalars import Scalar

View File

@ -25,7 +25,7 @@ def test_uuidstring_query_variable():
result = schema.execute( result = schema.execute(
"""query Test($uuid: UUID){ uuid(input: $uuid) }""", """query Test($uuid: UUID){ uuid(input: $uuid) }""",
variable_values={"uuid": uuid_value}, variables={"uuid": uuid_value},
) )
assert not result.errors assert not result.errors
assert result.data == {"uuid": uuid_value} assert result.data == {"uuid": uuid_value}

View File

@ -255,6 +255,7 @@ class TypeMap(GraphQLTypeMap):
return GrapheneUnionType( return GrapheneUnionType(
graphene_type=type, graphene_type=type,
name=type._meta.name, name=type._meta.name,
description=type._meta.description,
types=types, types=types,
resolve_type=_resolve_type, resolve_type=_resolve_type,
) )

View File

@ -18,4 +18,4 @@ def to_snake_case(name):
def to_const(string): def to_const(string):
return re.sub("[\W|^]+", "_", string).upper() return re.sub(r"[\W|^]+", "_", string).upper() # noqa

View File

@ -0,0 +1,42 @@
"""
This file is used mainly as a bridge for thenable abstractions.
This includes:
- Promises
- Asyncio Coroutines
"""
try:
from promise import Promise, is_thenable # type: ignore
except ImportError:
class Promise(object): # type: ignore
pass
def is_thenable(obj): # type: ignore
return False
try:
from inspect import isawaitable
from .thenables_asyncio import await_and_execute
except ImportError:
def isawaitable(obj): # type: ignore
return False
def maybe_thenable(obj, on_resolve):
"""
Execute a on_resolve function once the thenable is resolved,
returning the same type of object inputed.
If the object is not thenable, it should return on_resolve(obj)
"""
if isawaitable(obj) and not isinstance(obj, Promise):
return await_and_execute(obj, on_resolve)
if is_thenable(obj):
return Promise.resolve(obj).then(on_resolve)
# If it's not awaitable not a Promise, return
# the function executed over the object
return on_resolve(obj)

View File

@ -0,0 +1,5 @@
def await_and_execute(obj, on_resolve):
async def build_resolve_async():
return on_resolve(await obj)
return build_resolve_async()

View File

@ -1,4 +1,5 @@
import ast import ast
import codecs
import re import re
import sys import sys
@ -50,6 +51,7 @@ tests_require = [
"pytest-mock", "pytest-mock",
"snapshottest", "snapshottest",
"coveralls", "coveralls",
"promise",
"six", "six",
"mock", "mock",
"pytz", "pytz",
@ -60,7 +62,9 @@ setup(
name="graphene", name="graphene",
version=version, version=version,
description="GraphQL Framework for Python", description="GraphQL Framework for Python",
long_description=open("README.rst").read(), long_description=codecs.open(
"README.rst", "r", encoding="ascii", errors="replace"
).read(),
url="https://github.com/graphql-python/graphene", url="https://github.com/graphql-python/graphene",
author="Syrus Akbary", author="Syrus Akbary",
author_email="me@syrusakbary.com", author_email="me@syrusakbary.com",
@ -84,7 +88,6 @@ setup(
"six>=1.10.0,<2", "six>=1.10.0,<2",
"graphql-core>=2.1,<3", "graphql-core>=2.1,<3",
"graphql-relay>=0.4.5,<1", "graphql-relay>=0.4.5,<1",
"promise>=2.1,<3",
"aniso8601>=3,<4", "aniso8601>=3,<4",
], ],
tests_require=tests_require, tests_require=tests_require,

View File

@ -0,0 +1,128 @@
import pytest
from collections import OrderedDict
from graphql.execution.executors.asyncio import AsyncioExecutor
from graphql_relay.utils import base64
from graphene.types import ObjectType, Schema, String
from graphene.relay.connection import Connection, ConnectionField, PageInfo
from graphene.relay.node import Node
letter_chars = ["A", "B", "C", "D", "E"]
class Letter(ObjectType):
class Meta:
interfaces = (Node,)
letter = String()
class LetterConnection(Connection):
class Meta:
node = Letter
class Query(ObjectType):
letters = ConnectionField(LetterConnection)
connection_letters = ConnectionField(LetterConnection)
promise_letters = ConnectionField(LetterConnection)
node = Node.Field()
def resolve_letters(self, info, **args):
return list(letters.values())
async def resolve_promise_letters(self, info, **args):
return list(letters.values())
def resolve_connection_letters(self, info, **args):
return LetterConnection(
page_info=PageInfo(has_next_page=True, has_previous_page=False),
edges=[
LetterConnection.Edge(node=Letter(id=0, letter="A"), cursor="a-cursor")
],
)
schema = Schema(Query)
letters = OrderedDict()
for i, letter in enumerate(letter_chars):
letters[letter] = Letter(id=i, letter=letter)
def edges(selected_letters):
return [
{
"node": {"id": base64("Letter:%s" % l.id), "letter": l.letter},
"cursor": base64("arrayconnection:%s" % l.id),
}
for l in [letters[i] for i in selected_letters]
]
def cursor_for(ltr):
letter = letters[ltr]
return base64("arrayconnection:%s" % letter.id)
def execute(args=""):
if args:
args = "(" + args + ")"
return schema.execute(
"""
{
letters%s {
edges {
node {
id
letter
}
cursor
}
pageInfo {
hasPreviousPage
hasNextPage
startCursor
endCursor
}
}
}
"""
% args
)
@pytest.mark.asyncio
async def test_connection_promise():
result = await schema.execute(
"""
{
promiseLetters(first:1) {
edges {
node {
id
letter
}
}
pageInfo {
hasPreviousPage
hasNextPage
}
}
}
""",
executor=AsyncioExecutor(),
return_promise=True,
)
assert not result.errors
assert result.data == {
"promiseLetters": {
"edges": [{"node": {"id": "TGV0dGVyOjA=", "letter": "A"}}],
"pageInfo": {"hasPreviousPage": False, "hasNextPage": True},
}
}

View File

@ -0,0 +1,91 @@
import pytest
from graphql.execution.executors.asyncio import AsyncioExecutor
from graphene.types import ID, Field, ObjectType, Schema
from graphene.types.scalars import String
from graphene.relay.mutation import ClientIDMutation
class SharedFields(object):
shared = String()
class MyNode(ObjectType):
# class Meta:
# interfaces = (Node, )
id = ID()
name = String()
class SaySomethingAsync(ClientIDMutation):
class Input:
what = String()
phrase = String()
@staticmethod
async def mutate_and_get_payload(self, info, what, client_mutation_id=None):
return SaySomethingAsync(phrase=str(what))
# MyEdge = MyNode.Connection.Edge
class MyEdge(ObjectType):
node = Field(MyNode)
cursor = String()
class OtherMutation(ClientIDMutation):
class Input(SharedFields):
additional_field = String()
name = String()
my_node_edge = Field(MyEdge)
@staticmethod
def mutate_and_get_payload(
self, info, shared="", additional_field="", client_mutation_id=None
):
edge_type = MyEdge
return OtherMutation(
name=shared + additional_field,
my_node_edge=edge_type(cursor="1", node=MyNode(name="name")),
)
class RootQuery(ObjectType):
something = String()
class Mutation(ObjectType):
say_promise = SaySomethingAsync.Field()
other = OtherMutation.Field()
schema = Schema(query=RootQuery, mutation=Mutation)
@pytest.mark.asyncio
async def test_node_query_promise():
executed = await schema.execute(
'mutation a { sayPromise(input: {what:"hello", clientMutationId:"1"}) { phrase } }',
executor=AsyncioExecutor(),
return_promise=True,
)
assert not executed.errors
assert executed.data == {"sayPromise": {"phrase": "hello"}}
@pytest.mark.asyncio
async def test_edge_query():
executed = await schema.execute(
'mutation a { other(input: {clientMutationId:"1"}) { clientMutationId, myNodeEdge { cursor node { name }} } }',
executor=AsyncioExecutor(),
return_promise=True,
)
assert not executed.errors
assert dict(executed.data) == {
"other": {
"clientMutationId": "1",
"myNodeEdge": {"cursor": "1", "node": {"name": "name"}},
}
}

11
tox.ini
View File

@ -1,13 +1,16 @@
[tox] [tox]
envlist = flake8,py27,py33,py34,py35,py36,pre-commit,pypy,mypy envlist = flake8,py27,py34,py35,py36,py37,pre-commit,pypy,mypy
skipsdist = true skipsdist = true
[testenv] [testenv]
deps = .[test] deps =
.[test]
py{35,36,37}: pytest-asyncio
setenv = setenv =
PYTHONPATH = .:{envdir} PYTHONPATH = .:{envdir}
commands= commands =
py.test --cov=graphene graphene examples py{27,34,py}: py.test --cov=graphene graphene examples {posargs}
py{35,36,37}: py.test --cov=graphene graphene examples tests_asyncio {posargs}
[testenv:pre-commit] [testenv:pre-commit]
basepython=python3.6 basepython=python3.6