diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5f72385c..93ab2e6d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,24 +1,28 @@
repos:
- repo: git://github.com/pre-commit/pre-commit-hooks
- rev: v1.3.0
+ rev: v2.1.0
hooks:
+ - id: check-merge-conflict
- id: check-json
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
exclude: ^docs/.*$
- - id: trailing-whitespace
- exclude: README.md
- id: pretty-format-json
args:
- --autofix
- - id: flake8
+ - id: trailing-whitespace
+ exclude: README.md
- repo: https://github.com/asottile/pyupgrade
- rev: v1.4.0
+ rev: v1.12.0
hooks:
- id: pyupgrade
- repo: https://github.com/ambv/black
- rev: 18.6b4
+ rev: 18.9b0
hooks:
- id: black
- language_version: python3.6
+ language_version: python3
+- repo: https://github.com/PyCQA/flake8
+ rev: 3.7.7
+ hooks:
+ - id: flake8
diff --git a/.travis.yml b/.travis.yml
index 399ce134..53878e74 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,20 +1,24 @@
language: python
matrix:
include:
- - env: TOXENV=py27
- python: 2.7
- - env: TOXENV=py34
- python: 3.4
- - env: TOXENV=py35
- python: 3.5
- - env: TOXENV=py36
- python: 3.6
- - env: TOXENV=pypy
- python: pypy-5.7.1
- - env: TOXENV=pre-commit
- python: 3.6
- - env: TOXENV=mypy
- python: 3.6
+ - env: TOXENV=py27
+ python: 2.7
+ - env: TOXENV=py34
+ python: 3.4
+ - env: TOXENV=py35
+ python: 3.5
+ - env: TOXENV=py36
+ python: 3.6
+ - env: TOXENV=py37
+ python: 3.7
+ dist: xenial
+ sudo: true
+ - env: TOXENV=pypy
+ python: pypy-5.7.1
+ - env: TOXENV=pre-commit
+ python: 3.6
+ - env: TOXENV=mypy
+ python: 3.6
install:
- pip install coveralls tox
script: tox
diff --git a/BACKERS.md b/BACKERS.md
new file mode 100644
index 00000000..a1264060
--- /dev/null
+++ b/BACKERS.md
@@ -0,0 +1,97 @@
+
Sponsors & Backers
+
+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/)
+
+
+
+
+
+Platinum via Patreon
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+Gold via Patreon
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+Silver via Patreon
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+Bronze via Patreon
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+Generous Backers via Patreon ($50+)
+
+
+
+- [Lee Benson](https://github.com/leebenson)
+- [Become a Patron](https://www.patreon.com/join/syrusakbary)
+
+
+Backers via Patreon
+
+
+
+- [Become a Patron](https://www.patreon.com/join/syrusakbary)
+
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 00000000..26bb1aff
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1 @@
+/ @syrusakbary @ekampf @dan98765 @projectcheshire
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..c1a00054
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,11 @@
+.PHONY: help
+help:
+ @echo "Please use \`make ' where 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 -
diff --git a/README.md b/README.md
index 1ffb4aea..8b5a83e3 100644
--- a/README.md
+++ b/README.md
@@ -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](http://graphene-python.org) [](https://travis-ci.org/graphql-python/graphene) [](https://badge.fury.io/py/graphene) [](https://coveralls.io/github/graphql-python/graphene?branch=master)
+Supporting Graphene Python
+
+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/)
+
+
+
+Platinum via Patreon
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+Gold via Patreon
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+Silver via Patreon
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+---
+
+## Introduction
[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
through GraphQL.
-
## Integrations
Graphene has multiple integrations with different frameworks:
-| integration | Package |
-|---------------|-------------------|
-| Django | [graphene-django](https://github.com/graphql-python/graphene-django/) |
-| SQLAlchemy | [graphene-sqlalchemy](https://github.com/graphql-python/graphene-sqlalchemy/) |
-| 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)) |
+| integration | Package |
+| ----------------- | --------------------------------------------------------------------------------------- |
+| Django | [graphene-django](https://github.com/graphql-python/graphene-django/) |
+| SQLAlchemy | [graphene-sqlalchemy](https://github.com/graphql-python/graphene-sqlalchemy/) |
+| 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)) |
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.
-
## Examples
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/):
-* **Basic Schema**: [Starwars example](examples/starwars)
-* **Relay Schema**: [Starwars Relay example](examples/starwars_relay)
+- **Basic Schema**: [Starwars example](examples/starwars)
+- **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
@@ -84,13 +154,13 @@ 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:
```sh
-py.test PATH/TO/MY/DIR/test_test.py # Single file
-py.test PATH/TO/MY/DIR/ # All tests in directory
+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.
+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:
@@ -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:
+
```sh
tox
```
+
If you wish to run against a specific version defined in the `tox.ini` file:
+
```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!
-### Documentation
+### Building Documentation
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
-cd docs
-pip install -r requirements.txt
-```
-
-Then to produce a HTML version of the documentation:
-
-```sh
-make html
+make docs
```
diff --git a/README.rst b/README.rst
index bea6c4d4..5b3f3747 100644
--- a/README.rst
+++ b/README.rst
@@ -1,11 +1,190 @@
-Please read `UPGRADE-v2.0.md `__ to learn how to
-upgrade to Graphene ``2.0``.
+**We are looking for contributors**! Please check the
+`ROADMAP `__
+to see how you can help β€οΈ
--------------
|Graphene Logo| `Graphene `__ |Build Status| |PyPI version| |Coverage Status|
=========================================================================================================
+.. raw:: html
+
+
+
+Supporting Graphene Python
+
+.. raw:: html
+
+
+
+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 `__.
+If you'd like to join them, please consider:
+
+- `Become a backer or sponsor on
+ Patreon `__.
+- `One-time donation via
+ PayPal. `__
+
+
+
+.. raw:: html
+
+
+
+Platinum via Patreon
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+::
+
+
+
+
+
+
+ |
+
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+Gold via Patreon
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+::
+
+
+
+
+
+
+ |
+
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+Silver via Patreon
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+::
+
+
+
+
+
+
+ |
+
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+.. raw:: html
+
+
+
+--------------
+
+Introduction
+------------
+
`Graphene `__ is a Python library for
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
example `__
+Documentation
+-------------
+
+Documentation and links to additional resources are available at
+https://docs.graphene-python.org/en/latest/
+
Contributing
------------
-After cloning this repo, ensure dependencies are installed by running:
+After cloning this repo, create a
+`virtualenv `__ and ensure
+dependencies are installed by running:
.. code:: sh
+ virtualenv venv
+ source venv/bin/activate
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
- 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 `__ for more
+options and test running controls.
You can also run the benchmarks with:
@@ -109,24 +306,41 @@ You can also run the benchmarks with:
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
`Sphinx `__ and a custom theme.
-The documentation dependencies are installed by running:
+An HTML version of the documentation is produced by running:
.. code:: sh
- cd docs
- pip install -r requirements.txt
-
-Then to produce a HTML version of the documentation:
-
-.. code:: sh
-
- make html
+ make docs
.. |Graphene Logo| image:: http://graphene-python.org/favicon.png
.. |Build Status| image:: https://travis-ci.org/graphql-python/graphene.svg?branch=master
diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 00000000..24f542e0
--- /dev/null
+++ b/ROADMAP.md
@@ -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)
diff --git a/UPGRADE-v2.0.md b/UPGRADE-v2.0.md
index 32b28d8b..d9d48005 100644
--- a/UPGRADE-v2.0.md
+++ b/UPGRADE-v2.0.md
@@ -17,7 +17,7 @@ developer has to write to use them.
**New Features!**
* [`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
@@ -276,7 +276,7 @@ If you are using Middelwares, you need to some adjustments:
Before:
```python
-class MyGrapheneMiddleware(object):
+class MyGrapheneMiddleware(object):
def resolve(self, next_mw, root, args, context, info):
## Middleware code
@@ -287,7 +287,7 @@ class MyGrapheneMiddleware(object):
With 2.0:
```python
-class MyGrapheneMiddleware(object):
+class MyGrapheneMiddleware(object):
def resolve(self, next_mw, root, info, **args):
context = info.context
diff --git a/docs/Makefile b/docs/Makefile
index 2973acec..3eb7e638 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -17,75 +17,50 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help
help:
@echo "Please use \`make ' where is one of"
- @echo " html to make standalone HTML files"
- @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"
+ @grep -E '^\.PHONY: [a-zA-Z_-]+ .*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = "(: |##)"}; {printf "\033[36m%-30s\033[0m %s\n", $$2, $$3}'
.PHONY: clean
clean:
rm -rf $(BUILDDIR)/*
-.PHONY: html
+.PHONY: html ## to make standalone HTML files
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-.PHONY: dirhtml
+.PHONY: dirhtml ## to make HTML files named index.html in directories
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-.PHONY: singlehtml
+.PHONY: singlehtml ## to make a single large HTML file
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-.PHONY: pickle
+.PHONY: pickle ## to make pickle files
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
-.PHONY: json
+.PHONY: json ## to make JSON files
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
-.PHONY: htmlhelp
+.PHONY: htmlhelp ## to make HTML files and a HTML help project
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
-.PHONY: qthelp
+.PHONY: qthelp ## to make HTML files and a qthelp project
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@@ -95,7 +70,7 @@ qthelp:
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Graphene.qhc"
-.PHONY: applehelp
+.PHONY: applehelp ## to make an Apple Help Book
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@@ -104,7 +79,7 @@ applehelp:
"~/Library/Documentation/Help or install it in your application" \
"bundle."
-.PHONY: devhelp
+.PHONY: devhelp ## to make HTML files and a Devhelp project
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@@ -114,19 +89,19 @@ devhelp:
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Graphene"
@echo "# devhelp"
-.PHONY: epub
+.PHONY: epub ## to make an epub
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-.PHONY: epub3
+.PHONY: epub3 ## to make an epub3
epub3:
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
@echo
@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:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@@ -134,33 +109,33 @@ latex:
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
-.PHONY: latexpdf
+.PHONY: latexpdf ## to make LaTeX files and run them through pdflatex
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@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:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-.PHONY: text
+.PHONY: text ## to make text files
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
-.PHONY: man
+.PHONY: man ## to make manual pages
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-.PHONY: texinfo
+.PHONY: texinfo ## to make Texinfo files
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@@ -168,57 +143,57 @@ texinfo:
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
-.PHONY: info
+.PHONY: info ## to make Texinfo files and run them through makeinfo
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
-.PHONY: gettext
+.PHONY: gettext ## to make PO message catalogs
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@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:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
-.PHONY: linkcheck
+.PHONY: linkcheck ## to check all external links for integrity
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
-.PHONY: doctest
+.PHONY: doctest ## to run all doctests embedded in the documentation (if enabled)
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
-.PHONY: coverage
+.PHONY: coverage ## to run coverage check of the documentation (if enabled)
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
-.PHONY: xml
+.PHONY: xml ## to make Docutils-native XML files
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
-.PHONY: pseudoxml
+.PHONY: pseudoxml ## to make pseudoxml-XML files for display purposes
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
-.PHONY: dummy
+.PHONY: dummy ## to check syntax errors of document sources
dummy:
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
@echo
diff --git a/docs/execution/dataloader.rst b/docs/execution/dataloader.rst
index 522af161..3cd36fcc 100644
--- a/docs/execution/dataloader.rst
+++ b/docs/execution/dataloader.rst
@@ -25,8 +25,8 @@ Create loaders by providing a batch loading function.
return Promise.resolve([get_user(id=key) for key in keys])
-A batch loading function accepts an list of keys, and returns a ``Promise``
-which resolves to an list of ``values``.
+A batch loading function accepts a list of keys, and returns a ``Promise``
+which resolves to a list of ``values``.
Then load individual values from the loader. ``DataLoader`` will coalesce all
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.
-
.. code:: python
user_loader = UserLoader()
@@ -47,6 +46,19 @@ requested keys.
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*.
+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
sacrificing the performance of batch data-loading. While the loader presents
an API that loads individual values, all concurrent requests will be coalesced
diff --git a/docs/execution/execute.rst b/docs/execution/execute.rst
index 327ce230..21345aa3 100644
--- a/docs/execution/execute.rst
+++ b/docs/execution/execute.rst
@@ -16,7 +16,7 @@ For executing a query a schema, you can directly call the ``execute`` method on
Context
_______
-You can pass context to a query via ``context_value``.
+You can pass context to a query via ``context``.
.. code:: python
@@ -28,14 +28,14 @@ You can pass context to a query via ``context_value``.
return info.context.get('name')
schema = graphene.Schema(Query)
- result = schema.execute('{ name }', context_value={'name': 'Syrus'})
+ result = schema.execute('{ name }', context={'name': 'Syrus'})
Variables
_______
-You can pass variables to a query via ``variable_values``.
+You can pass variables to a query via ``variables``.
.. code:: python
@@ -55,5 +55,5 @@ You can pass variables to a query via ``variable_values``.
lastName
}
}''',
- variable_values={'id': 12},
+ variables={'id': 12},
)
diff --git a/docs/execution/middleware.rst b/docs/execution/middleware.rst
index 55efe730..f7dac708 100644
--- a/docs/execution/middleware.rst
+++ b/docs/execution/middleware.rst
@@ -29,7 +29,7 @@ This middleware only continues evaluation if the ``field_name`` is not ``'user'`
.. code:: python
class AuthorizationMiddleware(object):
- def resolve(self, next, root, info, **args):
+ def resolve(next, root, info, **args):
if info.field_name == 'user':
return None
return next(root, info, **args)
diff --git a/docs/index.rst b/docs/index.rst
index 3e9577ad..aff3960f 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -19,3 +19,5 @@ Integrations
* `Graphene-SQLAlchemy `_ (`source `_)
* `Graphene-GAE `_ (`source `_)
* `Graphene-Mongo `_ (`source `_)
+* `Starlette `_ (`source `_)
+* `FastAPI `_ (`source `_)
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index a2d39481..fc333fae 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -30,17 +30,18 @@ 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 {name}"``.
+one field: ``hello`` and an input name. And when we query it, it should return ``"Hello
+{argument}"``.
.. code:: python
import graphene
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):
- return 'Hello ' + name
+ def resolve_hello(self, info, argument):
+ return 'Hello ' + argument
schema = graphene.Schema(query=Query)
@@ -54,4 +55,8 @@ Then we can start querying our schema:
result = schema.execute('{ hello }')
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!
diff --git a/docs/testing/index.rst b/docs/testing/index.rst
index 0263a9aa..0103779c 100644
--- a/docs/testing/index.rst
+++ b/docs/testing/index.rst
@@ -54,7 +54,7 @@ Execute parameters
~~~~~~~~~~~~~~~~~~
You can also add extra keyword arguments to the ``execute`` method, such as
-``context_value``, ``root_value``, ``variable_values``, ...:
+``context``, ``root``, ``variables``, ...:
.. code:: python
@@ -63,7 +63,7 @@ You can also add extra keyword arguments to the ``execute`` method, such as
def test_hey():
client = Client(my_schema)
- executed = client.execute('''{ hey }''', context_value={'user': 'Peter'})
+ executed = client.execute('''{ hey }''', context={'user': 'Peter'})
assert executed == {
'data': {
'hey': 'hello Peter!'
diff --git a/docs/types/enums.rst b/docs/types/enums.rst
index 6e730628..4e019ee7 100644
--- a/docs/types/enums.rst
+++ b/docs/types/enums.rst
@@ -1,7 +1,7 @@
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.
Definition
diff --git a/docs/types/interfaces.rst b/docs/types/interfaces.rst
index 21cf2173..f2073d6a 100644
--- a/docs/types/interfaces.rst
+++ b/docs/types/interfaces.rst
@@ -91,7 +91,7 @@ For example, you can define a field ``hero`` that resolves to any
schema = graphene.Schema(query=Query, types=[Human, Droid])
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 `_.
For example, the following query:
diff --git a/docs/types/mutations.rst b/docs/types/mutations.rst
index 6949008f..8532595e 100644
--- a/docs/types/mutations.rst
+++ b/docs/types/mutations.rst
@@ -27,7 +27,7 @@ This example defines a Mutation:
**person** and **ok** are the output fields of the Mutation when it is
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
only argument for the mutation.
diff --git a/docs/types/objecttypes.rst b/docs/types/objecttypes.rst
index 69c8c08a..b6eb3087 100644
--- a/docs/types/objecttypes.rst
+++ b/docs/types/objecttypes.rst
@@ -25,8 +25,8 @@ This example model defines a Person, with a first and a last name:
last_name = graphene.String()
full_name = graphene.String()
- def resolve_full_name(self, info):
- return '{} {}'.format(self.first_name, self.last_name)
+ def resolve_full_name(root, info):
+ return '{} {}'.format(root.first_name, root.last_name)
**first\_name** and **last\_name** are fields of the ObjectType. Each
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
---------
-A resolver is a method that resolves certain fields within a
-``ObjectType``. If not specififed otherwise, the resolver of a
+A resolver is a method that resolves certain fields within an
+``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``.
-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
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
-that reverses the given ``word`` argument using the ``resolve_reverse``
-method in the class.
+ import graphene
+
+ 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
import graphene
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -83,11 +208,13 @@ A field can use a custom resolver from outside the class:
import graphene
- def reverse(root, info, word):
- return word[::-1]
+ def resolve_full_name(person, info):
+ return '{} {}'.format(person.first_name, person.last_name)
- class Query(graphene.ObjectType):
- reverse = graphene.String(word=graphene.String(), resolver=reverse)
+ class Person(graphene.ObjectType):
+ first_name = graphene.String()
+ last_name = graphene.String()
+ full_name = graphene.String(resolver=resolve_full_name)
Instances as data containers
diff --git a/docs/types/scalars.rst b/docs/types/scalars.rst
index b6e8f654..b4552cfb 100644
--- a/docs/types/scalars.rst
+++ b/docs/types/scalars.rst
@@ -39,9 +39,8 @@ Graphene defines the following base Scalar Types:
``graphene.Int``
Represents non-fractional signed whole numeric
- values. Int can represent values between `-(2^53 - 1)` and `2^53 - 1` since
- represented in JSON as double-precision floating point numbers specified
- by `IEEE 754 `_.
+ values. Int is a signed 32βbit integer per the
+ `GraphQL spec `_
``graphene.Float``
diff --git a/examples/context_example.py b/examples/context_example.py
index 5fd7647d..9b5fd1a5 100644
--- a/examples/context_example.py
+++ b/examples/context_example.py
@@ -25,11 +25,11 @@ 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 result.data == {"me": {"id": "1", "name": "Syrus"}}
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"])
diff --git a/examples/starwars/tests/test_query.py b/examples/starwars/tests/test_query.py
index b26374b6..88934b0e 100644
--- a/examples/starwars/tests/test_query.py
+++ b/examples/starwars/tests/test_query.py
@@ -72,7 +72,7 @@ def test_fetch_some_id_query(snapshot):
}
"""
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):
@@ -84,7 +84,7 @@ def test_fetch_some_id_query2(snapshot):
}
"""
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):
@@ -96,7 +96,7 @@ def test_invalid_id_query(snapshot):
}
"""
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):
diff --git a/graphene/pyutils/signature.py b/graphene/pyutils/signature.py
index c66c2563..7757d9d0 100644
--- a/graphene/pyutils/signature.py
+++ b/graphene/pyutils/signature.py
@@ -707,7 +707,10 @@ class Signature(object):
break
elif param.name in kwargs:
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)
raise TypeError(msg)
parameters_ex = (param,)
diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py
index f23849f2..927dfcc4 100644
--- a/graphene/relay/connection.py
+++ b/graphene/relay/connection.py
@@ -3,15 +3,21 @@ from collections import Iterable, OrderedDict
from functools import partial
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.field import Field
from ..types.objecttype import ObjectType, ObjectTypeOptions
+from ..utils.thenables import maybe_thenable
from .node import is_node
class PageInfo(ObjectType):
+ class Meta:
+ description = (
+ "The Relay compliant `PageInfo` type, containing data necessary to"
+ " paginate this connection."
+ )
+
has_next_page = Boolean(
required=True,
name="hasNextPage",
@@ -64,21 +70,40 @@ class Connection(ObjectType):
node = Field(_node, description="The item at the end of the edge")
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)
if edge_class:
edge_bases = (edge_class, EdgeBase, ObjectType)
else:
edge_bases = (EdgeBase, ObjectType)
- edge = type(edge_name, edge_bases, {})
+ edge = type(edge_name, edge_bases, {"Meta": EdgeMeta})
cls.Edge = edge
options["name"] = name
_meta.node = node
_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__(
@@ -103,7 +128,7 @@ class IterableConnectionField(Field):
if is_node(connection_type):
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"
)
@@ -139,10 +164,7 @@ class IterableConnectionField(Field):
connection_type = connection_type.of_type
on_resolve = partial(cls.resolve_connection, connection_type, args)
- if is_thenable(resolved):
- return Promise.resolve(resolved).then(on_resolve)
-
- return on_resolve(resolved)
+ return maybe_thenable(resolved, on_resolve)
def get_resolver(self, parent_resolver):
resolver = super(IterableConnectionField, self).get_resolver(parent_resolver)
diff --git a/graphene/relay/mutation.py b/graphene/relay/mutation.py
index 1b8b855e..ee758e78 100644
--- a/graphene/relay/mutation.py
+++ b/graphene/relay/mutation.py
@@ -1,10 +1,9 @@
import re
from collections import OrderedDict
-from promise import Promise, is_thenable
-
from ..types import Field, InputObjectType, String
from ..types.mutation import Mutation
+from ..utils.thenables import maybe_thenable
class ClientIDMutation(Mutation):
@@ -69,7 +68,4 @@ class ClientIDMutation(Mutation):
return payload
result = cls.mutate_and_get_payload(root, info, **input)
- if is_thenable(result):
- return Promise.resolve(result).then(on_resolve)
-
- return on_resolve(result)
+ return maybe_thenable(result, on_resolve)
diff --git a/graphene/relay/tests/test_connection.py b/graphene/relay/tests/test_connection.py
index 061d6ec9..ca43f397 100644
--- a/graphene/relay/tests/test_connection.py
+++ b/graphene/relay/tests/test_connection.py
@@ -138,7 +138,7 @@ def test_connectionfield_node_deprecated():
with pytest.raises(Exception) as exc_info:
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
)
diff --git a/graphene/types/argument.py b/graphene/types/argument.py
index 9c75bcee..bf304608 100644
--- a/graphene/types/argument.py
+++ b/graphene/types/argument.py
@@ -75,9 +75,7 @@ def to_arguments(args, extra_args=None):
arg_name = default_name or arg.name
assert (
arg_name not in arguments
- ), 'More than one Argument have same name "{}".'.format(
- arg_name
- )
+ ), 'More than one Argument have same name "{}".'.format(arg_name)
arguments[arg_name] = arg
return arguments
diff --git a/graphene/types/base.py b/graphene/types/base.py
index aa97ed22..75685d98 100644
--- a/graphene/types/base.py
+++ b/graphene/types/base.py
@@ -1,5 +1,9 @@
from ..utils.subclass_with_meta import SubclassWithMeta
from ..utils.trim_docstring import trim_docstring
+import six
+
+if six.PY3:
+ from typing import Type
class BaseOptions(object):
diff --git a/graphene/types/mutation.py b/graphene/types/mutation.py
index aa8f86bb..dd09e461 100644
--- a/graphene/types/mutation.py
+++ b/graphene/types/mutation.py
@@ -85,7 +85,7 @@ class Mutation(ObjectType):
args=cls._meta.arguments,
resolver=cls._meta.resolver,
name=name,
- description=description,
+ description=description or cls._meta.description,
deprecation_reason=deprecation_reason,
required=required,
)
diff --git a/graphene/types/scalars.py b/graphene/types/scalars.py
index dfb63e52..c5f43787 100644
--- a/graphene/types/scalars.py
+++ b/graphene/types/scalars.py
@@ -4,6 +4,9 @@ from graphql.language.ast import BooleanValue, FloatValue, IntValue, StringValue
from .base import BaseOptions, BaseType
from .unmountedtype import UnmountedType
+if six.PY3:
+ from typing import Any
+
class ScalarOptions(BaseOptions):
pass
diff --git a/graphene/types/tests/test_datetime.py b/graphene/types/tests/test_datetime.py
index 5165aa61..98e5e7ab 100644
--- a/graphene/types/tests/test_datetime.py
+++ b/graphene/types/tests/test_datetime.py
@@ -90,7 +90,7 @@ def test_datetime_query_variable():
result = schema.execute(
"""query Test($date: DateTime){ datetime(in: $date) }""",
- variable_values={"date": isoformat},
+ variables={"date": isoformat},
)
assert not result.errors
assert result.data == {"datetime": isoformat}
@@ -101,8 +101,7 @@ def test_date_query_variable():
isoformat = now.isoformat()
result = schema.execute(
- """query Test($date: Date){ date(in: $date) }""",
- variable_values={"date": isoformat},
+ """query Test($date: Date){ date(in: $date) }""", variables={"date": isoformat}
)
assert not result.errors
assert result.data == {"date": isoformat}
@@ -114,8 +113,7 @@ def test_time_query_variable():
isoformat = time.isoformat()
result = schema.execute(
- """query Test($time: Time){ time(at: $time) }""",
- variable_values={"time": isoformat},
+ """query Test($time: Time){ time(at: $time) }""", variables={"time": isoformat}
)
assert not result.errors
assert result.data == {"time": isoformat}
diff --git a/graphene/types/tests/test_decimal.py b/graphene/types/tests/test_decimal.py
index abc4a6c4..fd77f482 100644
--- a/graphene/types/tests/test_decimal.py
+++ b/graphene/types/tests/test_decimal.py
@@ -28,7 +28,7 @@ def test_decimal_string_query_variable():
result = schema.execute(
"""query Test($decimal: Decimal){ decimal(input: $decimal) }""",
- variable_values={"decimal": decimal_value},
+ variables={"decimal": decimal_value},
)
assert not result.errors
assert result.data == {"decimal": str(decimal_value)}
diff --git a/graphene/types/tests/test_definition.py b/graphene/types/tests/test_definition.py
index 347de9c9..549847d5 100644
--- a/graphene/types/tests/test_definition.py
+++ b/graphene/types/tests/test_definition.py
@@ -1,5 +1,3 @@
-
-
from ..argument import Argument
from ..enum import Enum
from ..field import Field
diff --git a/graphene/types/tests/test_generic.py b/graphene/types/tests/test_generic.py
index 83e9bc88..338da982 100644
--- a/graphene/types/tests/test_generic.py
+++ b/graphene/types/tests/test_generic.py
@@ -39,7 +39,7 @@ def test_generic_query_variable():
]:
result = schema.execute(
"""query Test($generic: GenericScalar){ generic(input: $generic) }""",
- variable_values={"generic": generic_value},
+ variables={"generic": generic_value},
)
assert not result.errors
assert result.data == {"generic": generic_value}
diff --git a/graphene/types/tests/test_inputobjecttype.py b/graphene/types/tests/test_inputobjecttype.py
index d565ff40..dc557b94 100644
--- a/graphene/types/tests/test_inputobjecttype.py
+++ b/graphene/types/tests/test_inputobjecttype.py
@@ -1,4 +1,3 @@
-
from ..argument import Argument
from ..field import Field
from ..inputfield import InputField
diff --git a/graphene/types/tests/test_json.py b/graphene/types/tests/test_json.py
index c6b93586..b5537180 100644
--- a/graphene/types/tests/test_json.py
+++ b/graphene/types/tests/test_json.py
@@ -1,4 +1,3 @@
-
from ..json import JSONString
from ..objecttype import ObjectType
from ..schema import Schema
@@ -28,7 +27,7 @@ def test_jsonstring_query_variable():
result = schema.execute(
"""query Test($json: JSONString){ json(input: $json) }""",
- variable_values={"json": json_value},
+ variables={"json": json_value},
)
assert not result.errors
assert result.data == {"json": json_value}
diff --git a/graphene/types/tests/test_mountedtype.py b/graphene/types/tests/test_mountedtype.py
index 787bee56..b964233e 100644
--- a/graphene/types/tests/test_mountedtype.py
+++ b/graphene/types/tests/test_mountedtype.py
@@ -1,4 +1,3 @@
-
from ..field import Field
from ..scalars import String
diff --git a/graphene/types/tests/test_mutation.py b/graphene/types/tests/test_mutation.py
index 9755ad23..8558dc7a 100644
--- a/graphene/types/tests/test_mutation.py
+++ b/graphene/types/tests/test_mutation.py
@@ -139,17 +139,41 @@ def test_mutation_allow_to_have_custom_args():
class MyMutation(ObjectType):
create_user = CreateUser.Field(
+ name="createUser",
description="Create a user",
deprecation_reason="Is deprecated",
required=True,
)
field = MyMutation._meta.fields["create_user"]
+ assert field.name == "createUser"
assert field.description == "Create a user"
assert field.deprecation_reason == "Is deprecated"
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():
class BaseCreateUser(Mutation):
class Arguments:
diff --git a/graphene/types/tests/test_query.py b/graphene/types/tests/test_query.py
index 9f693a52..8681e462 100644
--- a/graphene/types/tests/test_query.py
+++ b/graphene/types/tests/test_query.py
@@ -464,7 +464,7 @@ def test_query_annotated_resolvers():
assert not result.errors
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 result.data == {"context": "base-context"}
diff --git a/graphene/types/tests/test_resolver.py b/graphene/types/tests/test_resolver.py
index 3cc44539..a03cf187 100644
--- a/graphene/types/tests/test_resolver.py
+++ b/graphene/types/tests/test_resolver.py
@@ -1,4 +1,3 @@
-
from ..resolver import (
attr_resolver,
dict_resolver,
diff --git a/graphene/types/tests/test_scalar.py b/graphene/types/tests/test_scalar.py
index 1ec986cd..559c0ce6 100644
--- a/graphene/types/tests/test_scalar.py
+++ b/graphene/types/tests/test_scalar.py
@@ -1,4 +1,3 @@
-
from ..scalars import Scalar
diff --git a/graphene/types/tests/test_uuid.py b/graphene/types/tests/test_uuid.py
index 9b3f93a0..2280b41f 100644
--- a/graphene/types/tests/test_uuid.py
+++ b/graphene/types/tests/test_uuid.py
@@ -25,7 +25,7 @@ def test_uuidstring_query_variable():
result = schema.execute(
"""query Test($uuid: UUID){ uuid(input: $uuid) }""",
- variable_values={"uuid": uuid_value},
+ variables={"uuid": uuid_value},
)
assert not result.errors
assert result.data == {"uuid": uuid_value}
diff --git a/graphene/types/typemap.py b/graphene/types/typemap.py
index dce04445..9edb8518 100644
--- a/graphene/types/typemap.py
+++ b/graphene/types/typemap.py
@@ -255,6 +255,7 @@ class TypeMap(GraphQLTypeMap):
return GrapheneUnionType(
graphene_type=type,
name=type._meta.name,
+ description=type._meta.description,
types=types,
resolve_type=_resolve_type,
)
diff --git a/graphene/utils/str_converters.py b/graphene/utils/str_converters.py
index d8804038..216b0547 100644
--- a/graphene/utils/str_converters.py
+++ b/graphene/utils/str_converters.py
@@ -18,4 +18,4 @@ def to_snake_case(name):
def to_const(string):
- return re.sub("[\W|^]+", "_", string).upper()
+ return re.sub(r"[\W|^]+", "_", string).upper() # noqa
diff --git a/graphene/utils/thenables.py b/graphene/utils/thenables.py
new file mode 100644
index 00000000..a3089595
--- /dev/null
+++ b/graphene/utils/thenables.py
@@ -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)
diff --git a/graphene/utils/thenables_asyncio.py b/graphene/utils/thenables_asyncio.py
new file mode 100644
index 00000000..d5f93182
--- /dev/null
+++ b/graphene/utils/thenables_asyncio.py
@@ -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()
diff --git a/setup.py b/setup.py
index be2111f8..48e5be35 100644
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,5 @@
import ast
+import codecs
import re
import sys
@@ -50,6 +51,7 @@ tests_require = [
"pytest-mock",
"snapshottest",
"coveralls",
+ "promise",
"six",
"mock",
"pytz",
@@ -60,7 +62,9 @@ setup(
name="graphene",
version=version,
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",
author="Syrus Akbary",
author_email="me@syrusakbary.com",
@@ -84,7 +88,6 @@ setup(
"six>=1.10.0,<2",
"graphql-core>=2.1,<3",
"graphql-relay>=0.4.5,<1",
- "promise>=2.1,<3",
"aniso8601>=3,<4",
],
tests_require=tests_require,
diff --git a/tests_asyncio/test_relay_connection.py b/tests_asyncio/test_relay_connection.py
new file mode 100644
index 00000000..ec86fef6
--- /dev/null
+++ b/tests_asyncio/test_relay_connection.py
@@ -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},
+ }
+ }
diff --git a/tests_asyncio/test_relay_mutation.py b/tests_asyncio/test_relay_mutation.py
new file mode 100644
index 00000000..42ea5fc7
--- /dev/null
+++ b/tests_asyncio/test_relay_mutation.py
@@ -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"}},
+ }
+ }
diff --git a/tox.ini b/tox.ini
index f8e6f347..2b7ae59c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,13 +1,16 @@
[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
[testenv]
-deps = .[test]
+deps =
+ .[test]
+ py{35,36,37}: pytest-asyncio
setenv =
PYTHONPATH = .:{envdir}
-commands=
- py.test --cov=graphene graphene examples
+commands =
+ 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]
basepython=python3.6