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:
- 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

View File

@ -9,6 +9,10 @@ matrix:
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

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)
<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.
@ -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)) |
| 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,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:
```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.
@ -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
```

View File

@ -1,11 +1,190 @@
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| `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
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 <examples/starwars_relay>`__
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 <https://virtualenv.pypa.io/en/stable/>`__ 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 <https://docs.pytest.org/en/latest/>`__ 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 <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
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

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!**
* [`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

View File

@ -17,75 +17,50 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help
help:
@echo "Please use \`make <target>' where <target> 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

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])
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

View File

@ -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},
)

View File

@ -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)

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-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>`_)
* `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.
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!

View File

@ -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!'

View File

@ -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

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])
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>`_.
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
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.

View File

@ -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

View File

@ -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 <http://en.wikipedia.org/wiki/IEEE_floating_point>`_.
values. Int is a signed 32bit integer per the
`GraphQL spec <https://facebook.github.io/graphql/June2018/#sec-Int>`_
``graphene.Float``

View File

@ -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"])

View File

@ -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):

View File

@ -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,)

View File

@ -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)

View File

@ -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)

View File

@ -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
)

View File

@ -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

View File

@ -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):

View File

@ -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,
)

View File

@ -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

View File

@ -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}

View File

@ -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)}

View File

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

View File

@ -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}

View File

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

View File

@ -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}

View File

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

View File

@ -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:

View File

@ -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"}

View File

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

View File

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

View File

@ -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}

View File

@ -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,
)

View File

@ -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

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 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,

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"}},
}
}

View File

@ -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
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