mirror of
https://github.com/graphql-python/graphene.git
synced 2025-09-21 19:32:33 +03:00
Merge branch 'master' into default-resolver
This commit is contained in:
commit
1af047a33e
|
@ -1,24 +1,28 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v1.3.0
|
rev: v2.1.0
|
||||||
hooks:
|
hooks:
|
||||||
|
- id: check-merge-conflict
|
||||||
- id: check-json
|
- id: check-json
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
exclude: ^docs/.*$
|
exclude: ^docs/.*$
|
||||||
- id: trailing-whitespace
|
|
||||||
exclude: README.md
|
|
||||||
- id: pretty-format-json
|
- id: pretty-format-json
|
||||||
args:
|
args:
|
||||||
- --autofix
|
- --autofix
|
||||||
- id: flake8
|
- id: trailing-whitespace
|
||||||
|
exclude: README.md
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v1.4.0
|
rev: v1.12.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
- repo: https://github.com/ambv/black
|
- repo: https://github.com/ambv/black
|
||||||
rev: 18.6b4
|
rev: 18.9b0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
language_version: python3.6
|
language_version: python3
|
||||||
|
- repo: https://github.com/PyCQA/flake8
|
||||||
|
rev: 3.7.7
|
||||||
|
hooks:
|
||||||
|
- id: flake8
|
||||||
|
|
|
@ -9,6 +9,10 @@ matrix:
|
||||||
python: 3.5
|
python: 3.5
|
||||||
- env: TOXENV=py36
|
- env: TOXENV=py36
|
||||||
python: 3.6
|
python: 3.6
|
||||||
|
- env: TOXENV=py37
|
||||||
|
python: 3.7
|
||||||
|
dist: xenial
|
||||||
|
sudo: true
|
||||||
- env: TOXENV=pypy
|
- env: TOXENV=pypy
|
||||||
python: pypy-5.7.1
|
python: pypy-5.7.1
|
||||||
- env: TOXENV=pre-commit
|
- env: TOXENV=pre-commit
|
||||||
|
|
97
BACKERS.md
Normal file
97
BACKERS.md
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
<h1 align="center">Sponsors & 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
1
CODEOWNERS
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/ @syrusakbary @ekampf @dan98765 @projectcheshire
|
11
Makefile
Normal file
11
Makefile
Normal 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
105
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)
|
#  [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)
|
||||||
|
|
||||||
|
<h1 align="center">Supporting Graphene Python</h1>
|
||||||
|
|
||||||
|
Graphene is an MIT-licensed open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/graphql-python/graphene/blob/master/BACKERS.md). If you'd like to join them, please consider:
|
||||||
|
|
||||||
|
- [Become a backer or sponsor on Patreon](https://www.patreon.com/syrusakbary).
|
||||||
|
- [One-time donation via PayPal.](https://graphene-python.org/support-graphene/)
|
||||||
|
|
||||||
|
<!--<h2 align="center">Special Sponsors</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://stdlib.com" target="_blank">
|
||||||
|
<img width="260px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!--special end-->
|
||||||
|
|
||||||
|
<h2 align="center">Platinum via Patreon</h2>
|
||||||
|
|
||||||
|
<!--platinum start-->
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="middle">
|
||||||
|
<a href="https://www.patreon.com/join/syrusakbary" target="_blank">
|
||||||
|
<img width="222px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2 align="center">Gold via Patreon</h2>
|
||||||
|
|
||||||
|
<!--gold start-->
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="middle">
|
||||||
|
<a href="https://www.patreon.com/join/syrusakbary" target="_blank">
|
||||||
|
<img width="148px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!--gold end-->
|
||||||
|
|
||||||
|
<h2 align="center">Silver via Patreon</h2>
|
||||||
|
|
||||||
|
<!--silver start-->
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="middle">
|
||||||
|
<a href="https://www.patreon.com/join/syrusakbary" target="_blank">
|
||||||
|
<img width="148px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!--silver end-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
[Graphene](http://graphene-python.org) is a Python library for building GraphQL schemas/types fast and easily.
|
[Graphene](http://graphene-python.org) is a Python library for building GraphQL schemas/types fast and easily.
|
||||||
|
|
||||||
|
@ -13,17 +81,16 @@ Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade to Graph
|
||||||
We believe that by providing a complete API you could plug Graphene anywhere your data lives and make your data available
|
We believe that by providing a complete API you could plug Graphene anywhere your data lives and make your data available
|
||||||
through GraphQL.
|
through GraphQL.
|
||||||
|
|
||||||
|
|
||||||
## Integrations
|
## Integrations
|
||||||
|
|
||||||
Graphene has multiple integrations with different frameworks:
|
Graphene has multiple integrations with different frameworks:
|
||||||
|
|
||||||
| integration | Package |
|
| integration | Package |
|
||||||
|---------------|-------------------|
|
| ----------------- | --------------------------------------------------------------------------------------- |
|
||||||
| Django | [graphene-django](https://github.com/graphql-python/graphene-django/) |
|
| Django | [graphene-django](https://github.com/graphql-python/graphene-django/) |
|
||||||
| SQLAlchemy | [graphene-sqlalchemy](https://github.com/graphql-python/graphene-sqlalchemy/) |
|
| SQLAlchemy | [graphene-sqlalchemy](https://github.com/graphql-python/graphene-sqlalchemy/) |
|
||||||
| Google App Engine | [graphene-gae](https://github.com/graphql-python/graphene-gae/) |
|
| Google App Engine | [graphene-gae](https://github.com/graphql-python/graphene-gae/) |
|
||||||
| Peewee | *In progress* ([Tracking Issue](https://github.com/graphql-python/graphene/issues/289)) |
|
| Peewee | _In progress_ ([Tracking Issue](https://github.com/graphql-python/graphene/issues/289)) |
|
||||||
|
|
||||||
Also, Graphene is fully compatible with the GraphQL spec, working seamlessly with all GraphQL clients, such as [Relay](https://github.com/facebook/relay), [Apollo](https://github.com/apollographql/apollo-client) and [gql](https://github.com/graphql-python/gql).
|
Also, Graphene is fully compatible with the GraphQL spec, working seamlessly with all GraphQL clients, such as [Relay](https://github.com/facebook/relay), [Apollo](https://github.com/apollographql/apollo-client) and [gql](https://github.com/graphql-python/gql).
|
||||||
|
|
||||||
|
@ -39,7 +106,6 @@ pip install "graphene>=2.0"
|
||||||
|
|
||||||
Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade.
|
Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade.
|
||||||
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Here is one example for you to get started:
|
Here is one example for you to get started:
|
||||||
|
@ -67,9 +133,13 @@ result = schema.execute(query)
|
||||||
|
|
||||||
If you want to learn even more, you can also check the following [examples](examples/):
|
If you want to learn even more, you can also check the following [examples](examples/):
|
||||||
|
|
||||||
* **Basic Schema**: [Starwars example](examples/starwars)
|
- **Basic Schema**: [Starwars example](examples/starwars)
|
||||||
* **Relay Schema**: [Starwars Relay example](examples/starwars_relay)
|
- **Relay Schema**: [Starwars Relay example](examples/starwars_relay)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Documentation and links to additional resources are available at
|
||||||
|
https://docs.graphene-python.org/en/latest/
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
@ -84,8 +154,8 @@ pip install -e ".[test]"
|
||||||
Well-written tests and maintaining good test coverage is important to this project. While developing, run new and existing tests with:
|
Well-written tests and maintaining good test coverage is important to this project. While developing, run new and existing tests with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
py.test PATH/TO/MY/DIR/test_test.py # Single file
|
py.test graphene/relay/tests/test_node.py # Single file
|
||||||
py.test PATH/TO/MY/DIR/ # All tests in directory
|
py.test graphene/relay # All tests in directory
|
||||||
```
|
```
|
||||||
|
|
||||||
Add the `-s` flag if you have introduced breakpoints into the code for debugging.
|
Add the `-s` flag if you have introduced breakpoints into the code for debugging.
|
||||||
|
@ -99,28 +169,25 @@ py.test graphene --benchmark-only
|
||||||
```
|
```
|
||||||
|
|
||||||
Graphene supports several versions of Python. To make sure that changes do not break compatibility with any of those versions, we use `tox` to create virtualenvs for each python version and run tests with that version. To run against all python versions defined in the `tox.ini` config file, just run:
|
Graphene supports several versions of Python. To make sure that changes do not break compatibility with any of those versions, we use `tox` to create virtualenvs for each python version and run tests with that version. To run against all python versions defined in the `tox.ini` config file, just run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
tox
|
tox
|
||||||
```
|
```
|
||||||
|
|
||||||
If you wish to run against a specific version defined in the `tox.ini` file:
|
If you wish to run against a specific version defined in the `tox.ini` file:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
tox -e py36
|
tox -e py36
|
||||||
```
|
```
|
||||||
|
|
||||||
Tox can only use whatever versions of python are installed on your system. When you create a pull request, Travis will also be running the same tests and report the results, so there is no need for potential contributors to try to install every single version of python on their own system ahead of time. We appreciate opening issues and pull requests to make graphene even more stable & useful!
|
Tox can only use whatever versions of python are installed on your system. When you create a pull request, Travis will also be running the same tests and report the results, so there is no need for potential contributors to try to install every single version of python on their own system ahead of time. We appreciate opening issues and pull requests to make graphene even more stable & useful!
|
||||||
|
|
||||||
### Documentation
|
### Building Documentation
|
||||||
|
|
||||||
The documentation is generated using the excellent [Sphinx](http://www.sphinx-doc.org/) and a custom theme.
|
The documentation is generated using the excellent [Sphinx](http://www.sphinx-doc.org/) and a custom theme.
|
||||||
|
|
||||||
The documentation dependencies are installed by running:
|
An HTML version of the documentation is produced by running:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd docs
|
make docs
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Then to produce a HTML version of the documentation:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
make html
|
|
||||||
```
|
```
|
||||||
|
|
244
README.rst
244
README.rst
|
@ -1,11 +1,190 @@
|
||||||
Please read `UPGRADE-v2.0.md </UPGRADE-v2.0.md>`__ to learn how to
|
**We are looking for contributors**! Please check the
|
||||||
upgrade to Graphene ``2.0``.
|
`ROADMAP <https://github.com/graphql-python/graphene/blob/master/ROADMAP.md>`__
|
||||||
|
to see how you can help ❤️
|
||||||
|
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|Graphene Logo| `Graphene <http://graphene-python.org>`__ |Build Status| |PyPI version| |Coverage Status|
|
|Graphene Logo| `Graphene <http://graphene-python.org>`__ |Build Status| |PyPI version| |Coverage Status|
|
||||||
=========================================================================================================
|
=========================================================================================================
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<h1 align="center">
|
||||||
|
|
||||||
|
Supporting Graphene Python
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
Graphene is an MIT-licensed open source project. It's an independent
|
||||||
|
project with its ongoing development made possible entirely thanks to
|
||||||
|
the support by these awesome
|
||||||
|
`backers <https://github.com/graphql-python/graphene/blob/master/BACKERS.md>`__.
|
||||||
|
If you'd like to join them, please consider:
|
||||||
|
|
||||||
|
- `Become a backer or sponsor on
|
||||||
|
Patreon <https://www.patreon.com/syrusakbary>`__.
|
||||||
|
- `One-time donation via
|
||||||
|
PayPal. <https://graphene-python.org/support-graphene/>`__
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<h2 align="center">
|
||||||
|
|
||||||
|
Special Sponsors
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<!--special end-->
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<h2 align="center">
|
||||||
|
|
||||||
|
Platinum via Patreon
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<!--platinum start-->
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<table>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="middle">
|
||||||
|
<a href="https://www.patreon.com/join/syrusakbary" target="_blank">
|
||||||
|
<img width="222px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<h2 align="center">
|
||||||
|
|
||||||
|
Gold via Patreon
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<!--gold start-->
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<table>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="middle">
|
||||||
|
<a href="https://www.patreon.com/join/syrusakbary" target="_blank">
|
||||||
|
<img width="148px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<!--gold end-->
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<h2 align="center">
|
||||||
|
|
||||||
|
Silver via Patreon
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<!--silver start-->
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<table>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="middle">
|
||||||
|
<a href="https://www.patreon.com/join/syrusakbary" target="_blank">
|
||||||
|
<img width="148px" src="https://raw.githubusercontent.com/graphql-python/graphene-python.org/master/src/pages/sponsors/generic-logo.png">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<!--silver end-->
|
||||||
|
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
|
||||||
`Graphene <http://graphene-python.org>`__ is a Python library for
|
`Graphene <http://graphene-python.org>`__ is a Python library for
|
||||||
building GraphQL schemas/types fast and easily.
|
building GraphQL schemas/types fast and easily.
|
||||||
|
|
||||||
|
@ -88,20 +267,38 @@ If you want to learn even more, you can also check the following
|
||||||
- **Relay Schema**: `Starwars Relay
|
- **Relay Schema**: `Starwars Relay
|
||||||
example <examples/starwars_relay>`__
|
example <examples/starwars_relay>`__
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Documentation and links to additional resources are available at
|
||||||
|
https://docs.graphene-python.org/en/latest/
|
||||||
|
|
||||||
Contributing
|
Contributing
|
||||||
------------
|
------------
|
||||||
|
|
||||||
After cloning this repo, ensure dependencies are installed by running:
|
After cloning this repo, create a
|
||||||
|
`virtualenv <https://virtualenv.pypa.io/en/stable/>`__ and ensure
|
||||||
|
dependencies are installed by running:
|
||||||
|
|
||||||
.. code:: sh
|
.. code:: sh
|
||||||
|
|
||||||
|
virtualenv venv
|
||||||
|
source venv/bin/activate
|
||||||
pip install -e ".[test]"
|
pip install -e ".[test]"
|
||||||
|
|
||||||
After developing, the full test suite can be evaluated by running:
|
Well-written tests and maintaining good test coverage is important to
|
||||||
|
this project. While developing, run new and existing tests with:
|
||||||
|
|
||||||
.. code:: sh
|
.. code:: sh
|
||||||
|
|
||||||
py.test graphene --cov=graphene --benchmark-skip # Use -v -s for verbose mode
|
py.test graphene/relay/tests/test_node.py # Single file
|
||||||
|
py.test graphene/relay # All tests in directory
|
||||||
|
|
||||||
|
Add the ``-s`` flag if you have introduced breakpoints into the code for
|
||||||
|
debugging. Add the ``-v`` ("verbose") flag to get more detailed test
|
||||||
|
output. For even more detailed output, use ``-vv``. Check out the
|
||||||
|
`pytest documentation <https://docs.pytest.org/en/latest/>`__ for more
|
||||||
|
options and test running controls.
|
||||||
|
|
||||||
You can also run the benchmarks with:
|
You can also run the benchmarks with:
|
||||||
|
|
||||||
|
@ -109,24 +306,41 @@ You can also run the benchmarks with:
|
||||||
|
|
||||||
py.test graphene --benchmark-only
|
py.test graphene --benchmark-only
|
||||||
|
|
||||||
Documentation
|
Graphene supports several versions of Python. To make sure that changes
|
||||||
|
do not break compatibility with any of those versions, we use ``tox`` to
|
||||||
|
create virtualenvs for each python version and run tests with that
|
||||||
|
version. To run against all python versions defined in the ``tox.ini``
|
||||||
|
config file, just run:
|
||||||
|
|
||||||
|
.. code:: sh
|
||||||
|
|
||||||
|
tox
|
||||||
|
|
||||||
|
If you wish to run against a specific version defined in the ``tox.ini``
|
||||||
|
file:
|
||||||
|
|
||||||
|
.. code:: sh
|
||||||
|
|
||||||
|
tox -e py36
|
||||||
|
|
||||||
|
Tox can only use whatever versions of python are installed on your
|
||||||
|
system. When you create a pull request, Travis will also be running the
|
||||||
|
same tests and report the results, so there is no need for potential
|
||||||
|
contributors to try to install every single version of python on their
|
||||||
|
own system ahead of time. We appreciate opening issues and pull requests
|
||||||
|
to make graphene even more stable & useful!
|
||||||
|
|
||||||
|
Building Documentation
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
The documentation is generated using the excellent
|
The documentation is generated using the excellent
|
||||||
`Sphinx <http://www.sphinx-doc.org/>`__ and a custom theme.
|
`Sphinx <http://www.sphinx-doc.org/>`__ and a custom theme.
|
||||||
|
|
||||||
The documentation dependencies are installed by running:
|
An HTML version of the documentation is produced by running:
|
||||||
|
|
||||||
.. code:: sh
|
.. code:: sh
|
||||||
|
|
||||||
cd docs
|
make docs
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
Then to produce a HTML version of the documentation:
|
|
||||||
|
|
||||||
.. code:: sh
|
|
||||||
|
|
||||||
make html
|
|
||||||
|
|
||||||
.. |Graphene Logo| image:: http://graphene-python.org/favicon.png
|
.. |Graphene Logo| image:: http://graphene-python.org/favicon.png
|
||||||
.. |Build Status| image:: https://travis-ci.org/graphql-python/graphene.svg?branch=master
|
.. |Build Status| image:: https://travis-ci.org/graphql-python/graphene.svg?branch=master
|
||||||
|
|
33
ROADMAP.md
Normal file
33
ROADMAP.md
Normal 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)
|
|
@ -17,7 +17,7 @@ developer has to write to use them.
|
||||||
|
|
||||||
**New Features!**
|
**New Features!**
|
||||||
* [`InputObjectType`](#inputobjecttype)
|
* [`InputObjectType`](#inputobjecttype)
|
||||||
* [`Meta as Class arguments`](#meta-ass-class-arguments) (_only available for Python 3_)
|
* [`Meta as Class arguments`](#meta-as-class-arguments) (_only available for Python 3_)
|
||||||
|
|
||||||
|
|
||||||
> The type metaclasses are now deleted as they are no longer necessary. If your code was depending
|
> The type metaclasses are now deleted as they are no longer necessary. If your code was depending
|
||||||
|
|
|
@ -17,75 +17,50 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help:
|
help:
|
||||||
@echo "Please use \`make <target>' where <target> is one of"
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
@echo " html to make standalone HTML files"
|
@grep -E '^\.PHONY: [a-zA-Z_-]+ .*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = "(: |##)"}; {printf "\033[36m%-30s\033[0m %s\n", $$2, $$3}'
|
||||||
@echo " dirhtml to make HTML files named index.html in directories"
|
|
||||||
@echo " singlehtml to make a single large HTML file"
|
|
||||||
@echo " pickle to make pickle files"
|
|
||||||
@echo " json to make JSON files"
|
|
||||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
|
||||||
@echo " qthelp to make HTML files and a qthelp project"
|
|
||||||
@echo " applehelp to make an Apple Help Book"
|
|
||||||
@echo " devhelp to make HTML files and a Devhelp project"
|
|
||||||
@echo " epub to make an epub"
|
|
||||||
@echo " epub3 to make an epub3"
|
|
||||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
|
||||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
|
||||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
|
||||||
@echo " text to make text files"
|
|
||||||
@echo " man to make manual pages"
|
|
||||||
@echo " texinfo to make Texinfo files"
|
|
||||||
@echo " info to make Texinfo files and run them through makeinfo"
|
|
||||||
@echo " gettext to make PO message catalogs"
|
|
||||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
|
||||||
@echo " xml to make Docutils-native XML files"
|
|
||||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
|
||||||
@echo " linkcheck to check all external links for integrity"
|
|
||||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
|
||||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
|
||||||
@echo " dummy to check syntax errors of document sources"
|
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BUILDDIR)/*
|
rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
.PHONY: html
|
.PHONY: html ## to make standalone HTML files
|
||||||
html:
|
html:
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
.PHONY: dirhtml
|
.PHONY: dirhtml ## to make HTML files named index.html in directories
|
||||||
dirhtml:
|
dirhtml:
|
||||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||||
|
|
||||||
.PHONY: singlehtml
|
.PHONY: singlehtml ## to make a single large HTML file
|
||||||
singlehtml:
|
singlehtml:
|
||||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||||
|
|
||||||
.PHONY: pickle
|
.PHONY: pickle ## to make pickle files
|
||||||
pickle:
|
pickle:
|
||||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished; now you can process the pickle files."
|
@echo "Build finished; now you can process the pickle files."
|
||||||
|
|
||||||
.PHONY: json
|
.PHONY: json ## to make JSON files
|
||||||
json:
|
json:
|
||||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished; now you can process the JSON files."
|
@echo "Build finished; now you can process the JSON files."
|
||||||
|
|
||||||
.PHONY: htmlhelp
|
.PHONY: htmlhelp ## to make HTML files and a HTML help project
|
||||||
htmlhelp:
|
htmlhelp:
|
||||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||||
|
|
||||||
.PHONY: qthelp
|
.PHONY: qthelp ## to make HTML files and a qthelp project
|
||||||
qthelp:
|
qthelp:
|
||||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||||
@echo
|
@echo
|
||||||
|
@ -95,7 +70,7 @@ qthelp:
|
||||||
@echo "To view the help file:"
|
@echo "To view the help file:"
|
||||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Graphene.qhc"
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Graphene.qhc"
|
||||||
|
|
||||||
.PHONY: applehelp
|
.PHONY: applehelp ## to make an Apple Help Book
|
||||||
applehelp:
|
applehelp:
|
||||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||||
@echo
|
@echo
|
||||||
|
@ -104,7 +79,7 @@ applehelp:
|
||||||
"~/Library/Documentation/Help or install it in your application" \
|
"~/Library/Documentation/Help or install it in your application" \
|
||||||
"bundle."
|
"bundle."
|
||||||
|
|
||||||
.PHONY: devhelp
|
.PHONY: devhelp ## to make HTML files and a Devhelp project
|
||||||
devhelp:
|
devhelp:
|
||||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||||
@echo
|
@echo
|
||||||
|
@ -114,19 +89,19 @@ devhelp:
|
||||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Graphene"
|
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Graphene"
|
||||||
@echo "# devhelp"
|
@echo "# devhelp"
|
||||||
|
|
||||||
.PHONY: epub
|
.PHONY: epub ## to make an epub
|
||||||
epub:
|
epub:
|
||||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||||
|
|
||||||
.PHONY: epub3
|
.PHONY: epub3 ## to make an epub3
|
||||||
epub3:
|
epub3:
|
||||||
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
|
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
|
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
|
||||||
|
|
||||||
.PHONY: latex
|
.PHONY: latex ## to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||||
latex:
|
latex:
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
@echo
|
@echo
|
||||||
|
@ -134,33 +109,33 @@ latex:
|
||||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||||
"(use \`make latexpdf' here to do that automatically)."
|
"(use \`make latexpdf' here to do that automatically)."
|
||||||
|
|
||||||
.PHONY: latexpdf
|
.PHONY: latexpdf ## to make LaTeX files and run them through pdflatex
|
||||||
latexpdf:
|
latexpdf:
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
@echo "Running LaTeX files through pdflatex..."
|
@echo "Running LaTeX files through pdflatex..."
|
||||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
.PHONY: latexpdfja
|
.PHONY: latexpdfja ## to make LaTeX files and run them through platex/dvipdfmx
|
||||||
latexpdfja:
|
latexpdfja:
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
.PHONY: text
|
.PHONY: text ## to make text files
|
||||||
text:
|
text:
|
||||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||||
|
|
||||||
.PHONY: man
|
.PHONY: man ## to make manual pages
|
||||||
man:
|
man:
|
||||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||||
|
|
||||||
.PHONY: texinfo
|
.PHONY: texinfo ## to make Texinfo files
|
||||||
texinfo:
|
texinfo:
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
@echo
|
@echo
|
||||||
|
@ -168,57 +143,57 @@ texinfo:
|
||||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||||
"(use \`make info' here to do that automatically)."
|
"(use \`make info' here to do that automatically)."
|
||||||
|
|
||||||
.PHONY: info
|
.PHONY: info ## to make Texinfo files and run them through makeinfo
|
||||||
info:
|
info:
|
||||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
@echo "Running Texinfo files through makeinfo..."
|
@echo "Running Texinfo files through makeinfo..."
|
||||||
make -C $(BUILDDIR)/texinfo info
|
make -C $(BUILDDIR)/texinfo info
|
||||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||||
|
|
||||||
.PHONY: gettext
|
.PHONY: gettext ## to make PO message catalogs
|
||||||
gettext:
|
gettext:
|
||||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||||
|
|
||||||
.PHONY: changes
|
.PHONY: changes ## to make an overview of all changed/added/deprecated items
|
||||||
changes:
|
changes:
|
||||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||||
@echo
|
@echo
|
||||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||||
|
|
||||||
.PHONY: linkcheck
|
.PHONY: linkcheck ## to check all external links for integrity
|
||||||
linkcheck:
|
linkcheck:
|
||||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||||
@echo
|
@echo
|
||||||
@echo "Link check complete; look for any errors in the above output " \
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||||
|
|
||||||
.PHONY: doctest
|
.PHONY: doctest ## to run all doctests embedded in the documentation (if enabled)
|
||||||
doctest:
|
doctest:
|
||||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||||
@echo "Testing of doctests in the sources finished, look at the " \
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||||
"results in $(BUILDDIR)/doctest/output.txt."
|
"results in $(BUILDDIR)/doctest/output.txt."
|
||||||
|
|
||||||
.PHONY: coverage
|
.PHONY: coverage ## to run coverage check of the documentation (if enabled)
|
||||||
coverage:
|
coverage:
|
||||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||||
@echo "Testing of coverage in the sources finished, look at the " \
|
@echo "Testing of coverage in the sources finished, look at the " \
|
||||||
"results in $(BUILDDIR)/coverage/python.txt."
|
"results in $(BUILDDIR)/coverage/python.txt."
|
||||||
|
|
||||||
.PHONY: xml
|
.PHONY: xml ## to make Docutils-native XML files
|
||||||
xml:
|
xml:
|
||||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||||
|
|
||||||
.PHONY: pseudoxml
|
.PHONY: pseudoxml ## to make pseudoxml-XML files for display purposes
|
||||||
pseudoxml:
|
pseudoxml:
|
||||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||||
@echo
|
@echo
|
||||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||||
|
|
||||||
.PHONY: dummy
|
.PHONY: dummy ## to check syntax errors of document sources
|
||||||
dummy:
|
dummy:
|
||||||
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
|
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
|
||||||
@echo
|
@echo
|
||||||
|
|
|
@ -25,8 +25,8 @@ Create loaders by providing a batch loading function.
|
||||||
return Promise.resolve([get_user(id=key) for key in keys])
|
return Promise.resolve([get_user(id=key) for key in keys])
|
||||||
|
|
||||||
|
|
||||||
A batch loading function accepts an list of keys, and returns a ``Promise``
|
A batch loading function accepts a list of keys, and returns a ``Promise``
|
||||||
which resolves to an list of ``values``.
|
which resolves to a list of ``values``.
|
||||||
|
|
||||||
Then load individual values from the loader. ``DataLoader`` will coalesce all
|
Then load individual values from the loader. ``DataLoader`` will coalesce all
|
||||||
individual loads which occur within a single frame of execution (executed once
|
individual loads which occur within a single frame of execution (executed once
|
||||||
|
@ -34,7 +34,6 @@ the wrapping promise is resolved) and then call your batch function with all
|
||||||
requested keys.
|
requested keys.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
user_loader = UserLoader()
|
user_loader = UserLoader()
|
||||||
|
@ -47,6 +46,19 @@ requested keys.
|
||||||
A naive application may have issued *four* round-trips to a backend for the
|
A naive application may have issued *four* round-trips to a backend for the
|
||||||
required information, but with ``DataLoader`` this application will make at most *two*.
|
required information, but with ``DataLoader`` this application will make at most *two*.
|
||||||
|
|
||||||
|
Note that loaded values are one-to-one with the keys and must have the same
|
||||||
|
order. This means that if you load all values from a single query, you must
|
||||||
|
make sure that you then order the query result for the results to match the keys:
|
||||||
|
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class UserLoader(DataLoader):
|
||||||
|
def batch_load_fn(self, keys):
|
||||||
|
users = {user.id: user for user in User.objects.filter(id__in=keys)}
|
||||||
|
return Promise.resolve([users.get(user_id) for user_id in keys])
|
||||||
|
|
||||||
|
|
||||||
``DataLoader`` allows you to decouple unrelated parts of your application without
|
``DataLoader`` allows you to decouple unrelated parts of your application without
|
||||||
sacrificing the performance of batch data-loading. While the loader presents
|
sacrificing the performance of batch data-loading. While the loader presents
|
||||||
an API that loads individual values, all concurrent requests will be coalesced
|
an API that loads individual values, all concurrent requests will be coalesced
|
||||||
|
|
|
@ -16,7 +16,7 @@ For executing a query a schema, you can directly call the ``execute`` method on
|
||||||
Context
|
Context
|
||||||
_______
|
_______
|
||||||
|
|
||||||
You can pass context to a query via ``context_value``.
|
You can pass context to a query via ``context``.
|
||||||
|
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
@ -28,14 +28,14 @@ You can pass context to a query via ``context_value``.
|
||||||
return info.context.get('name')
|
return info.context.get('name')
|
||||||
|
|
||||||
schema = graphene.Schema(Query)
|
schema = graphene.Schema(Query)
|
||||||
result = schema.execute('{ name }', context_value={'name': 'Syrus'})
|
result = schema.execute('{ name }', context={'name': 'Syrus'})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Variables
|
Variables
|
||||||
_______
|
_______
|
||||||
|
|
||||||
You can pass variables to a query via ``variable_values``.
|
You can pass variables to a query via ``variables``.
|
||||||
|
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
@ -55,5 +55,5 @@ You can pass variables to a query via ``variable_values``.
|
||||||
lastName
|
lastName
|
||||||
}
|
}
|
||||||
}''',
|
}''',
|
||||||
variable_values={'id': 12},
|
variables={'id': 12},
|
||||||
)
|
)
|
||||||
|
|
|
@ -29,7 +29,7 @@ This middleware only continues evaluation if the ``field_name`` is not ``'user'`
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
class AuthorizationMiddleware(object):
|
class AuthorizationMiddleware(object):
|
||||||
def resolve(self, next, root, info, **args):
|
def resolve(next, root, info, **args):
|
||||||
if info.field_name == 'user':
|
if info.field_name == 'user':
|
||||||
return None
|
return None
|
||||||
return next(root, info, **args)
|
return next(root, info, **args)
|
||||||
|
|
|
@ -19,3 +19,5 @@ Integrations
|
||||||
* `Graphene-SQLAlchemy <http://docs.graphene-python.org/projects/sqlalchemy/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-sqlalchemy/>`_)
|
* `Graphene-SQLAlchemy <http://docs.graphene-python.org/projects/sqlalchemy/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-sqlalchemy/>`_)
|
||||||
* `Graphene-GAE <http://docs.graphene-python.org/projects/gae/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-gae/>`_)
|
* `Graphene-GAE <http://docs.graphene-python.org/projects/gae/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-gae/>`_)
|
||||||
* `Graphene-Mongo <http://graphene-mongo.readthedocs.io/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-mongo>`_)
|
* `Graphene-Mongo <http://graphene-mongo.readthedocs.io/en/latest/>`_ (`source <https://github.com/graphql-python/graphene-mongo>`_)
|
||||||
|
* `Starlette <https://www.starlette.io/graphql/>`_ (`source <https://github.com/encode/starlette>`_)
|
||||||
|
* `FastAPI <https://fastapi.tiangolo.com/tutorial/graphql/>`_ (`source <https://github.com/tiangolo/fastapi>`_)
|
||||||
|
|
|
@ -30,17 +30,18 @@ server with an associated set of resolve methods that know how to fetch
|
||||||
data.
|
data.
|
||||||
|
|
||||||
We are going to create a very simple schema, with a ``Query`` with only
|
We are going to create a very simple schema, with a ``Query`` with only
|
||||||
one field: ``hello`` and an input name. And when we query it, it should return ``"Hello {name}"``.
|
one field: ``hello`` and an input name. And when we query it, it should return ``"Hello
|
||||||
|
{argument}"``.
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
hello = graphene.String(name=graphene.String(default_value="stranger"))
|
hello = graphene.String(argument=graphene.String(default_value="stranger"))
|
||||||
|
|
||||||
def resolve_hello(self, info, name):
|
def resolve_hello(self, info, argument):
|
||||||
return 'Hello ' + name
|
return 'Hello ' + argument
|
||||||
|
|
||||||
schema = graphene.Schema(query=Query)
|
schema = graphene.Schema(query=Query)
|
||||||
|
|
||||||
|
@ -54,4 +55,8 @@ Then we can start querying our schema:
|
||||||
result = schema.execute('{ hello }')
|
result = schema.execute('{ hello }')
|
||||||
print(result.data['hello']) # "Hello stranger"
|
print(result.data['hello']) # "Hello stranger"
|
||||||
|
|
||||||
|
# or passing the argument in the query
|
||||||
|
result = schema.execute('{ hello (argument: "graph") }')
|
||||||
|
print(result.data['hello']) # "Hello graph"
|
||||||
|
|
||||||
Congrats! You got your first graphene schema working!
|
Congrats! You got your first graphene schema working!
|
||||||
|
|
|
@ -54,7 +54,7 @@ Execute parameters
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
You can also add extra keyword arguments to the ``execute`` method, such as
|
You can also add extra keyword arguments to the ``execute`` method, such as
|
||||||
``context_value``, ``root_value``, ``variable_values``, ...:
|
``context``, ``root``, ``variables``, ...:
|
||||||
|
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
@ -63,7 +63,7 @@ You can also add extra keyword arguments to the ``execute`` method, such as
|
||||||
|
|
||||||
def test_hey():
|
def test_hey():
|
||||||
client = Client(my_schema)
|
client = Client(my_schema)
|
||||||
executed = client.execute('''{ hey }''', context_value={'user': 'Peter'})
|
executed = client.execute('''{ hey }''', context={'user': 'Peter'})
|
||||||
assert executed == {
|
assert executed == {
|
||||||
'data': {
|
'data': {
|
||||||
'hey': 'hello Peter!'
|
'hey': 'hello Peter!'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Enums
|
Enums
|
||||||
=====
|
=====
|
||||||
|
|
||||||
A ``Enum`` is a special ``GraphQL`` type that represents a set of
|
An ``Enum`` is a special ``GraphQL`` type that represents a set of
|
||||||
symbolic names (members) bound to unique, constant values.
|
symbolic names (members) bound to unique, constant values.
|
||||||
|
|
||||||
Definition
|
Definition
|
||||||
|
|
|
@ -91,7 +91,7 @@ For example, you can define a field ``hero`` that resolves to any
|
||||||
schema = graphene.Schema(query=Query, types=[Human, Droid])
|
schema = graphene.Schema(query=Query, types=[Human, Droid])
|
||||||
|
|
||||||
This allows you to directly query for fields that exist on the Character interface
|
This allows you to directly query for fields that exist on the Character interface
|
||||||
as well as selecting specific fields on any type that implments the interface
|
as well as selecting specific fields on any type that implements the interface
|
||||||
using `inline fragments <https://graphql.org/learn/queries/#inline-fragments>`_.
|
using `inline fragments <https://graphql.org/learn/queries/#inline-fragments>`_.
|
||||||
|
|
||||||
For example, the following query:
|
For example, the following query:
|
||||||
|
|
|
@ -27,7 +27,7 @@ This example defines a Mutation:
|
||||||
**person** and **ok** are the output fields of the Mutation when it is
|
**person** and **ok** are the output fields of the Mutation when it is
|
||||||
resolved.
|
resolved.
|
||||||
|
|
||||||
**Input** attributes are the arguments that the Mutation
|
**Arguments** attributes are the arguments that the Mutation
|
||||||
``CreatePerson`` needs for resolving, in this case **name** will be the
|
``CreatePerson`` needs for resolving, in this case **name** will be the
|
||||||
only argument for the mutation.
|
only argument for the mutation.
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,8 @@ This example model defines a Person, with a first and a last name:
|
||||||
last_name = graphene.String()
|
last_name = graphene.String()
|
||||||
full_name = graphene.String()
|
full_name = graphene.String()
|
||||||
|
|
||||||
def resolve_full_name(self, info):
|
def resolve_full_name(root, info):
|
||||||
return '{} {}'.format(self.first_name, self.last_name)
|
return '{} {}'.format(root.first_name, root.last_name)
|
||||||
|
|
||||||
**first\_name** and **last\_name** are fields of the ObjectType. Each
|
**first\_name** and **last\_name** are fields of the ObjectType. Each
|
||||||
field is specified as a class attribute, and each attribute maps to a
|
field is specified as a class attribute, and each attribute maps to a
|
||||||
|
@ -46,33 +46,158 @@ The above ``Person`` ObjectType has the following schema representation:
|
||||||
Resolvers
|
Resolvers
|
||||||
---------
|
---------
|
||||||
|
|
||||||
A resolver is a method that resolves certain fields within a
|
A resolver is a method that resolves certain fields within an
|
||||||
``ObjectType``. If not specififed otherwise, the resolver of a
|
``ObjectType``. If not specified otherwise, the resolver of a
|
||||||
field is the ``resolve_{field_name}`` method on the ``ObjectType``.
|
field is the ``resolve_{field_name}`` method on the ``ObjectType``.
|
||||||
|
|
||||||
By default resolvers take the arguments ``info`` and ``*args``.
|
By default resolvers take the arguments ``info`` and ``*args``.
|
||||||
|
|
||||||
NOTE: The resolvers on a ``ObjectType`` are always treated as ``staticmethod``\ s,
|
NOTE: The resolvers on an ``ObjectType`` are always treated as ``staticmethod``\ s,
|
||||||
so the first argument to the resolver method ``self`` (or ``root``) need
|
so the first argument to the resolver method ``self`` (or ``root``) need
|
||||||
not be an actual instance of the ``ObjectType``.
|
not be an actual instance of the ``ObjectType``.
|
||||||
|
|
||||||
|
If an explicit resolver is not defined on the ``ObjectType`` then Graphene will
|
||||||
|
attempt to use a property with the same name on the object that is passed to the
|
||||||
|
``ObjectType``.
|
||||||
|
|
||||||
Quick example
|
.. code:: python
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This example model defines a ``Query`` type, which has a reverse field
|
import graphene
|
||||||
that reverses the given ``word`` argument using the ``resolve_reverse``
|
|
||||||
method in the class.
|
class Person(graphene.ObjectType):
|
||||||
|
first_name = graphene.String()
|
||||||
|
last_name = graphene.String()
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
me = graphene.Field(Person)
|
||||||
|
|
||||||
|
def resolve_me(_, info):
|
||||||
|
# returns an object that represents a Person
|
||||||
|
return get_human(name='Luke Skywalker')
|
||||||
|
|
||||||
|
If you are passing a dict instead of an object to your ``ObjectType`` you can
|
||||||
|
change the default resolver in the ``Meta`` class like this:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import graphene
|
||||||
|
from graphene.types.resolver import dict_resolver
|
||||||
|
|
||||||
|
class Person(graphene.ObjectType):
|
||||||
|
class Meta:
|
||||||
|
default_resolver = dict_resolver
|
||||||
|
|
||||||
|
first_name = graphene.String()
|
||||||
|
last_name = graphene.String()
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
me = graphene.Field(Person)
|
||||||
|
|
||||||
|
def resolve_me(_, info):
|
||||||
|
return {
|
||||||
|
"first_name": "Luke",
|
||||||
|
"last_name": "Skywalker",
|
||||||
|
}
|
||||||
|
|
||||||
|
Or you can change the default resolver globally by calling ``set_default_resolver``
|
||||||
|
before executing a query.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import graphene
|
||||||
|
from graphene.types.resolver import dict_resolver, set_default_resolver
|
||||||
|
|
||||||
|
set_default_resolver(dict_resolver)
|
||||||
|
|
||||||
|
schema = graphene.Schema(query=Query)
|
||||||
|
result = schema.execute('''
|
||||||
|
query {
|
||||||
|
me {
|
||||||
|
firstName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
Resolvers with arguments
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Any arguments that a field defines gets passed to the resolver function as
|
||||||
|
kwargs. For example:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Query(graphene.ObjectType):
|
||||||
reverse = graphene.String(word=graphene.String())
|
human_by_name = graphene.Field(Human, name=graphene.String(required=True))
|
||||||
|
|
||||||
|
def resolve_human_by_name(_, info, name):
|
||||||
|
return get_human(name=name)
|
||||||
|
|
||||||
|
You can then execute the following query:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
query {
|
||||||
|
humanByName(name: "Luke Skywalker") {
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NOTE: if you define an argument for a field that is not required (and in a query
|
||||||
|
execution it is not provided as an argument) it will not be passed to the
|
||||||
|
resolver function at all. This is so that the developer can differenciate
|
||||||
|
between a ``undefined`` value for an argument and an explicit ``null`` value.
|
||||||
|
|
||||||
|
For example, given this schema:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import graphene
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
hello = graphene.String(required=True, name=graphene.String())
|
||||||
|
|
||||||
|
def resolve_hello(_, info, name):
|
||||||
|
return name if name else 'World'
|
||||||
|
|
||||||
|
And this query:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
query {
|
||||||
|
hello
|
||||||
|
}
|
||||||
|
|
||||||
|
An error will be thrown:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
TypeError: resolve_hello() missing 1 required positional argument: 'name'
|
||||||
|
|
||||||
|
You can fix this error in 2 ways. Either by combining all keyword arguments
|
||||||
|
into a dict:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
hello = graphene.String(required=True, name=graphene.String())
|
||||||
|
|
||||||
|
def resolve_hello(_, info, **args):
|
||||||
|
return args.get('name', 'World')
|
||||||
|
|
||||||
|
Or by setting a default value for the keyword argument:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
class Query(graphene.ObjectType):
|
||||||
|
hello = graphene.String(required=True, name=graphene.String())
|
||||||
|
|
||||||
|
def resolve_hello(_, info, name='World'):
|
||||||
|
return name
|
||||||
|
|
||||||
def resolve_reverse(self, info, word):
|
|
||||||
return word[::-1]
|
|
||||||
|
|
||||||
Resolvers outside the class
|
Resolvers outside the class
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -83,11 +208,13 @@ A field can use a custom resolver from outside the class:
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
def reverse(root, info, word):
|
def resolve_full_name(person, info):
|
||||||
return word[::-1]
|
return '{} {}'.format(person.first_name, person.last_name)
|
||||||
|
|
||||||
class Query(graphene.ObjectType):
|
class Person(graphene.ObjectType):
|
||||||
reverse = graphene.String(word=graphene.String(), resolver=reverse)
|
first_name = graphene.String()
|
||||||
|
last_name = graphene.String()
|
||||||
|
full_name = graphene.String(resolver=resolve_full_name)
|
||||||
|
|
||||||
|
|
||||||
Instances as data containers
|
Instances as data containers
|
||||||
|
|
|
@ -39,9 +39,8 @@ Graphene defines the following base Scalar Types:
|
||||||
``graphene.Int``
|
``graphene.Int``
|
||||||
|
|
||||||
Represents non-fractional signed whole numeric
|
Represents non-fractional signed whole numeric
|
||||||
values. Int can represent values between `-(2^53 - 1)` and `2^53 - 1` since
|
values. Int is a signed 32‐bit integer per the
|
||||||
represented in JSON as double-precision floating point numbers specified
|
`GraphQL spec <https://facebook.github.io/graphql/June2018/#sec-Int>`_
|
||||||
by `IEEE 754 <http://en.wikipedia.org/wiki/IEEE_floating_point>`_.
|
|
||||||
|
|
||||||
``graphene.Float``
|
``graphene.Float``
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,11 @@ query = """
|
||||||
|
|
||||||
|
|
||||||
def test_query():
|
def test_query():
|
||||||
result = schema.execute(query, context_value={"user": User(id="1", name="Syrus")})
|
result = schema.execute(query, context={"user": User(id="1", name="Syrus")})
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {"me": {"id": "1", "name": "Syrus"}}
|
assert result.data == {"me": {"id": "1", "name": "Syrus"}}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
result = schema.execute(query, context_value={"user": User(id="X", name="Console")})
|
result = schema.execute(query, context={"user": User(id="X", name="Console")})
|
||||||
print(result.data["me"])
|
print(result.data["me"])
|
||||||
|
|
|
@ -72,7 +72,7 @@ def test_fetch_some_id_query(snapshot):
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
params = {"someId": "1000"}
|
params = {"someId": "1000"}
|
||||||
snapshot.assert_match(client.execute(query, variable_values=params))
|
snapshot.assert_match(client.execute(query, variables=params))
|
||||||
|
|
||||||
|
|
||||||
def test_fetch_some_id_query2(snapshot):
|
def test_fetch_some_id_query2(snapshot):
|
||||||
|
@ -84,7 +84,7 @@ def test_fetch_some_id_query2(snapshot):
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
params = {"someId": "1002"}
|
params = {"someId": "1002"}
|
||||||
snapshot.assert_match(client.execute(query, variable_values=params))
|
snapshot.assert_match(client.execute(query, variables=params))
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_id_query(snapshot):
|
def test_invalid_id_query(snapshot):
|
||||||
|
@ -96,7 +96,7 @@ def test_invalid_id_query(snapshot):
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
params = {"id": "not a valid id"}
|
params = {"id": "not a valid id"}
|
||||||
snapshot.assert_match(client.execute(query, variable_values=params))
|
snapshot.assert_match(client.execute(query, variables=params))
|
||||||
|
|
||||||
|
|
||||||
def test_fetch_luke_aliased(snapshot):
|
def test_fetch_luke_aliased(snapshot):
|
||||||
|
|
|
@ -707,7 +707,10 @@ class Signature(object):
|
||||||
break
|
break
|
||||||
elif param.name in kwargs:
|
elif param.name in kwargs:
|
||||||
if param.kind == _POSITIONAL_ONLY:
|
if param.kind == _POSITIONAL_ONLY:
|
||||||
msg = "{arg!r} parameter is positional only, " "but was passed as a keyword"
|
msg = (
|
||||||
|
"{arg!r} parameter is positional only, "
|
||||||
|
"but was passed as a keyword"
|
||||||
|
)
|
||||||
msg = msg.format(arg=param.name)
|
msg = msg.format(arg=param.name)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
parameters_ex = (param,)
|
parameters_ex = (param,)
|
||||||
|
|
|
@ -3,15 +3,21 @@ from collections import Iterable, OrderedDict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from graphql_relay import connection_from_list
|
from graphql_relay import connection_from_list
|
||||||
from promise import Promise, is_thenable
|
|
||||||
|
|
||||||
from ..types import Boolean, Enum, Int, Interface, List, NonNull, Scalar, String, Union
|
from ..types import Boolean, Enum, Int, Interface, List, NonNull, Scalar, String, Union
|
||||||
from ..types.field import Field
|
from ..types.field import Field
|
||||||
from ..types.objecttype import ObjectType, ObjectTypeOptions
|
from ..types.objecttype import ObjectType, ObjectTypeOptions
|
||||||
|
from ..utils.thenables import maybe_thenable
|
||||||
from .node import is_node
|
from .node import is_node
|
||||||
|
|
||||||
|
|
||||||
class PageInfo(ObjectType):
|
class PageInfo(ObjectType):
|
||||||
|
class Meta:
|
||||||
|
description = (
|
||||||
|
"The Relay compliant `PageInfo` type, containing data necessary to"
|
||||||
|
" paginate this connection."
|
||||||
|
)
|
||||||
|
|
||||||
has_next_page = Boolean(
|
has_next_page = Boolean(
|
||||||
required=True,
|
required=True,
|
||||||
name="hasNextPage",
|
name="hasNextPage",
|
||||||
|
@ -64,21 +70,40 @@ class Connection(ObjectType):
|
||||||
node = Field(_node, description="The item at the end of the edge")
|
node = Field(_node, description="The item at the end of the edge")
|
||||||
cursor = String(required=True, description="A cursor for use in pagination")
|
cursor = String(required=True, description="A cursor for use in pagination")
|
||||||
|
|
||||||
|
class EdgeMeta:
|
||||||
|
description = "A Relay edge containing a `{}` and its cursor.".format(
|
||||||
|
base_name
|
||||||
|
)
|
||||||
|
|
||||||
edge_name = "{}Edge".format(base_name)
|
edge_name = "{}Edge".format(base_name)
|
||||||
if edge_class:
|
if edge_class:
|
||||||
edge_bases = (edge_class, EdgeBase, ObjectType)
|
edge_bases = (edge_class, EdgeBase, ObjectType)
|
||||||
else:
|
else:
|
||||||
edge_bases = (EdgeBase, ObjectType)
|
edge_bases = (EdgeBase, ObjectType)
|
||||||
|
|
||||||
edge = type(edge_name, edge_bases, {})
|
edge = type(edge_name, edge_bases, {"Meta": EdgeMeta})
|
||||||
cls.Edge = edge
|
cls.Edge = edge
|
||||||
|
|
||||||
options["name"] = name
|
options["name"] = name
|
||||||
_meta.node = node
|
_meta.node = node
|
||||||
_meta.fields = OrderedDict(
|
_meta.fields = OrderedDict(
|
||||||
[
|
[
|
||||||
("page_info", Field(PageInfo, name="pageInfo", required=True)),
|
(
|
||||||
("edges", Field(NonNull(List(edge)))),
|
"page_info",
|
||||||
|
Field(
|
||||||
|
PageInfo,
|
||||||
|
name="pageInfo",
|
||||||
|
required=True,
|
||||||
|
description="Pagination data for this connection.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"edges",
|
||||||
|
Field(
|
||||||
|
NonNull(List(edge)),
|
||||||
|
description="Contains the nodes in this connection.",
|
||||||
|
),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
return super(Connection, cls).__init_subclass_with_meta__(
|
return super(Connection, cls).__init_subclass_with_meta__(
|
||||||
|
@ -103,7 +128,7 @@ class IterableConnectionField(Field):
|
||||||
|
|
||||||
if is_node(connection_type):
|
if is_node(connection_type):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"ConnectionField's now need a explicit ConnectionType for Nodes.\n"
|
"ConnectionFields now need a explicit ConnectionType for Nodes.\n"
|
||||||
"Read more: https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#node-connections"
|
"Read more: https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#node-connections"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -139,10 +164,7 @@ class IterableConnectionField(Field):
|
||||||
connection_type = connection_type.of_type
|
connection_type = connection_type.of_type
|
||||||
|
|
||||||
on_resolve = partial(cls.resolve_connection, connection_type, args)
|
on_resolve = partial(cls.resolve_connection, connection_type, args)
|
||||||
if is_thenable(resolved):
|
return maybe_thenable(resolved, on_resolve)
|
||||||
return Promise.resolve(resolved).then(on_resolve)
|
|
||||||
|
|
||||||
return on_resolve(resolved)
|
|
||||||
|
|
||||||
def get_resolver(self, parent_resolver):
|
def get_resolver(self, parent_resolver):
|
||||||
resolver = super(IterableConnectionField, self).get_resolver(parent_resolver)
|
resolver = super(IterableConnectionField, self).get_resolver(parent_resolver)
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from promise import Promise, is_thenable
|
|
||||||
|
|
||||||
from ..types import Field, InputObjectType, String
|
from ..types import Field, InputObjectType, String
|
||||||
from ..types.mutation import Mutation
|
from ..types.mutation import Mutation
|
||||||
|
from ..utils.thenables import maybe_thenable
|
||||||
|
|
||||||
|
|
||||||
class ClientIDMutation(Mutation):
|
class ClientIDMutation(Mutation):
|
||||||
|
@ -69,7 +68,4 @@ class ClientIDMutation(Mutation):
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
result = cls.mutate_and_get_payload(root, info, **input)
|
result = cls.mutate_and_get_payload(root, info, **input)
|
||||||
if is_thenable(result):
|
return maybe_thenable(result, on_resolve)
|
||||||
return Promise.resolve(result).then(on_resolve)
|
|
||||||
|
|
||||||
return on_resolve(result)
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ def test_connectionfield_node_deprecated():
|
||||||
with pytest.raises(Exception) as exc_info:
|
with pytest.raises(Exception) as exc_info:
|
||||||
field.type
|
field.type
|
||||||
|
|
||||||
assert "ConnectionField's now need a explicit ConnectionType for Nodes." in str(
|
assert "ConnectionFields now need a explicit ConnectionType for Nodes." in str(
|
||||||
exc_info.value
|
exc_info.value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -75,9 +75,7 @@ def to_arguments(args, extra_args=None):
|
||||||
arg_name = default_name or arg.name
|
arg_name = default_name or arg.name
|
||||||
assert (
|
assert (
|
||||||
arg_name not in arguments
|
arg_name not in arguments
|
||||||
), 'More than one Argument have same name "{}".'.format(
|
), 'More than one Argument have same name "{}".'.format(arg_name)
|
||||||
arg_name
|
|
||||||
)
|
|
||||||
arguments[arg_name] = arg
|
arguments[arg_name] = arg
|
||||||
|
|
||||||
return arguments
|
return arguments
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
from ..utils.subclass_with_meta import SubclassWithMeta
|
from ..utils.subclass_with_meta import SubclassWithMeta
|
||||||
from ..utils.trim_docstring import trim_docstring
|
from ..utils.trim_docstring import trim_docstring
|
||||||
|
import six
|
||||||
|
|
||||||
|
if six.PY3:
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
|
||||||
class BaseOptions(object):
|
class BaseOptions(object):
|
||||||
|
|
|
@ -85,7 +85,7 @@ class Mutation(ObjectType):
|
||||||
args=cls._meta.arguments,
|
args=cls._meta.arguments,
|
||||||
resolver=cls._meta.resolver,
|
resolver=cls._meta.resolver,
|
||||||
name=name,
|
name=name,
|
||||||
description=description,
|
description=description or cls._meta.description,
|
||||||
deprecation_reason=deprecation_reason,
|
deprecation_reason=deprecation_reason,
|
||||||
required=required,
|
required=required,
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,9 @@ from graphql.language.ast import BooleanValue, FloatValue, IntValue, StringValue
|
||||||
from .base import BaseOptions, BaseType
|
from .base import BaseOptions, BaseType
|
||||||
from .unmountedtype import UnmountedType
|
from .unmountedtype import UnmountedType
|
||||||
|
|
||||||
|
if six.PY3:
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class ScalarOptions(BaseOptions):
|
class ScalarOptions(BaseOptions):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -90,7 +90,7 @@ def test_datetime_query_variable():
|
||||||
|
|
||||||
result = schema.execute(
|
result = schema.execute(
|
||||||
"""query Test($date: DateTime){ datetime(in: $date) }""",
|
"""query Test($date: DateTime){ datetime(in: $date) }""",
|
||||||
variable_values={"date": isoformat},
|
variables={"date": isoformat},
|
||||||
)
|
)
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {"datetime": isoformat}
|
assert result.data == {"datetime": isoformat}
|
||||||
|
@ -101,8 +101,7 @@ def test_date_query_variable():
|
||||||
isoformat = now.isoformat()
|
isoformat = now.isoformat()
|
||||||
|
|
||||||
result = schema.execute(
|
result = schema.execute(
|
||||||
"""query Test($date: Date){ date(in: $date) }""",
|
"""query Test($date: Date){ date(in: $date) }""", variables={"date": isoformat}
|
||||||
variable_values={"date": isoformat},
|
|
||||||
)
|
)
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {"date": isoformat}
|
assert result.data == {"date": isoformat}
|
||||||
|
@ -114,8 +113,7 @@ def test_time_query_variable():
|
||||||
isoformat = time.isoformat()
|
isoformat = time.isoformat()
|
||||||
|
|
||||||
result = schema.execute(
|
result = schema.execute(
|
||||||
"""query Test($time: Time){ time(at: $time) }""",
|
"""query Test($time: Time){ time(at: $time) }""", variables={"time": isoformat}
|
||||||
variable_values={"time": isoformat},
|
|
||||||
)
|
)
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {"time": isoformat}
|
assert result.data == {"time": isoformat}
|
||||||
|
|
|
@ -28,7 +28,7 @@ def test_decimal_string_query_variable():
|
||||||
|
|
||||||
result = schema.execute(
|
result = schema.execute(
|
||||||
"""query Test($decimal: Decimal){ decimal(input: $decimal) }""",
|
"""query Test($decimal: Decimal){ decimal(input: $decimal) }""",
|
||||||
variable_values={"decimal": decimal_value},
|
variables={"decimal": decimal_value},
|
||||||
)
|
)
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {"decimal": str(decimal_value)}
|
assert result.data == {"decimal": str(decimal_value)}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
from ..argument import Argument
|
from ..argument import Argument
|
||||||
from ..enum import Enum
|
from ..enum import Enum
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
|
|
|
@ -39,7 +39,7 @@ def test_generic_query_variable():
|
||||||
]:
|
]:
|
||||||
result = schema.execute(
|
result = schema.execute(
|
||||||
"""query Test($generic: GenericScalar){ generic(input: $generic) }""",
|
"""query Test($generic: GenericScalar){ generic(input: $generic) }""",
|
||||||
variable_values={"generic": generic_value},
|
variables={"generic": generic_value},
|
||||||
)
|
)
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {"generic": generic_value}
|
assert result.data == {"generic": generic_value}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
from ..argument import Argument
|
from ..argument import Argument
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
from ..inputfield import InputField
|
from ..inputfield import InputField
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
from ..json import JSONString
|
from ..json import JSONString
|
||||||
from ..objecttype import ObjectType
|
from ..objecttype import ObjectType
|
||||||
from ..schema import Schema
|
from ..schema import Schema
|
||||||
|
@ -28,7 +27,7 @@ def test_jsonstring_query_variable():
|
||||||
|
|
||||||
result = schema.execute(
|
result = schema.execute(
|
||||||
"""query Test($json: JSONString){ json(input: $json) }""",
|
"""query Test($json: JSONString){ json(input: $json) }""",
|
||||||
variable_values={"json": json_value},
|
variables={"json": json_value},
|
||||||
)
|
)
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {"json": json_value}
|
assert result.data == {"json": json_value}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
from ..field import Field
|
from ..field import Field
|
||||||
from ..scalars import String
|
from ..scalars import String
|
||||||
|
|
||||||
|
|
|
@ -139,17 +139,41 @@ def test_mutation_allow_to_have_custom_args():
|
||||||
|
|
||||||
class MyMutation(ObjectType):
|
class MyMutation(ObjectType):
|
||||||
create_user = CreateUser.Field(
|
create_user = CreateUser.Field(
|
||||||
|
name="createUser",
|
||||||
description="Create a user",
|
description="Create a user",
|
||||||
deprecation_reason="Is deprecated",
|
deprecation_reason="Is deprecated",
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
field = MyMutation._meta.fields["create_user"]
|
field = MyMutation._meta.fields["create_user"]
|
||||||
|
assert field.name == "createUser"
|
||||||
assert field.description == "Create a user"
|
assert field.description == "Create a user"
|
||||||
assert field.deprecation_reason == "Is deprecated"
|
assert field.deprecation_reason == "Is deprecated"
|
||||||
assert field.type == NonNull(CreateUser)
|
assert field.type == NonNull(CreateUser)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mutation_default_args_output():
|
||||||
|
class CreateUser(Mutation):
|
||||||
|
"""Description."""
|
||||||
|
|
||||||
|
class Arguments:
|
||||||
|
name = String()
|
||||||
|
|
||||||
|
name = String()
|
||||||
|
|
||||||
|
def mutate(self, info, name):
|
||||||
|
return CreateUser(name=name)
|
||||||
|
|
||||||
|
class MyMutation(ObjectType):
|
||||||
|
create_user = CreateUser.Field()
|
||||||
|
|
||||||
|
field = MyMutation._meta.fields["create_user"]
|
||||||
|
assert field.name is None
|
||||||
|
assert field.description == "Description."
|
||||||
|
assert field.deprecation_reason is None
|
||||||
|
assert field.type == CreateUser
|
||||||
|
|
||||||
|
|
||||||
def test_mutation_as_subclass():
|
def test_mutation_as_subclass():
|
||||||
class BaseCreateUser(Mutation):
|
class BaseCreateUser(Mutation):
|
||||||
class Arguments:
|
class Arguments:
|
||||||
|
|
|
@ -464,7 +464,7 @@ def test_query_annotated_resolvers():
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {"annotated": "base-self"}
|
assert result.data == {"annotated": "base-self"}
|
||||||
|
|
||||||
result = test_schema.execute("{ context }", "base", context_value=context)
|
result = test_schema.execute("{ context }", "base", context=context)
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {"context": "base-context"}
|
assert result.data == {"context": "base-context"}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
from ..resolver import (
|
from ..resolver import (
|
||||||
attr_resolver,
|
attr_resolver,
|
||||||
dict_resolver,
|
dict_resolver,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
from ..scalars import Scalar
|
from ..scalars import Scalar
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ def test_uuidstring_query_variable():
|
||||||
|
|
||||||
result = schema.execute(
|
result = schema.execute(
|
||||||
"""query Test($uuid: UUID){ uuid(input: $uuid) }""",
|
"""query Test($uuid: UUID){ uuid(input: $uuid) }""",
|
||||||
variable_values={"uuid": uuid_value},
|
variables={"uuid": uuid_value},
|
||||||
)
|
)
|
||||||
assert not result.errors
|
assert not result.errors
|
||||||
assert result.data == {"uuid": uuid_value}
|
assert result.data == {"uuid": uuid_value}
|
||||||
|
|
|
@ -255,6 +255,7 @@ class TypeMap(GraphQLTypeMap):
|
||||||
return GrapheneUnionType(
|
return GrapheneUnionType(
|
||||||
graphene_type=type,
|
graphene_type=type,
|
||||||
name=type._meta.name,
|
name=type._meta.name,
|
||||||
|
description=type._meta.description,
|
||||||
types=types,
|
types=types,
|
||||||
resolve_type=_resolve_type,
|
resolve_type=_resolve_type,
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,4 +18,4 @@ def to_snake_case(name):
|
||||||
|
|
||||||
|
|
||||||
def to_const(string):
|
def to_const(string):
|
||||||
return re.sub("[\W|^]+", "_", string).upper()
|
return re.sub(r"[\W|^]+", "_", string).upper() # noqa
|
||||||
|
|
42
graphene/utils/thenables.py
Normal file
42
graphene/utils/thenables.py
Normal 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)
|
5
graphene/utils/thenables_asyncio.py
Normal file
5
graphene/utils/thenables_asyncio.py
Normal 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()
|
7
setup.py
7
setup.py
|
@ -1,4 +1,5 @@
|
||||||
import ast
|
import ast
|
||||||
|
import codecs
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -50,6 +51,7 @@ tests_require = [
|
||||||
"pytest-mock",
|
"pytest-mock",
|
||||||
"snapshottest",
|
"snapshottest",
|
||||||
"coveralls",
|
"coveralls",
|
||||||
|
"promise",
|
||||||
"six",
|
"six",
|
||||||
"mock",
|
"mock",
|
||||||
"pytz",
|
"pytz",
|
||||||
|
@ -60,7 +62,9 @@ setup(
|
||||||
name="graphene",
|
name="graphene",
|
||||||
version=version,
|
version=version,
|
||||||
description="GraphQL Framework for Python",
|
description="GraphQL Framework for Python",
|
||||||
long_description=open("README.rst").read(),
|
long_description=codecs.open(
|
||||||
|
"README.rst", "r", encoding="ascii", errors="replace"
|
||||||
|
).read(),
|
||||||
url="https://github.com/graphql-python/graphene",
|
url="https://github.com/graphql-python/graphene",
|
||||||
author="Syrus Akbary",
|
author="Syrus Akbary",
|
||||||
author_email="me@syrusakbary.com",
|
author_email="me@syrusakbary.com",
|
||||||
|
@ -84,7 +88,6 @@ setup(
|
||||||
"six>=1.10.0,<2",
|
"six>=1.10.0,<2",
|
||||||
"graphql-core>=2.1,<3",
|
"graphql-core>=2.1,<3",
|
||||||
"graphql-relay>=0.4.5,<1",
|
"graphql-relay>=0.4.5,<1",
|
||||||
"promise>=2.1,<3",
|
|
||||||
"aniso8601>=3,<4",
|
"aniso8601>=3,<4",
|
||||||
],
|
],
|
||||||
tests_require=tests_require,
|
tests_require=tests_require,
|
||||||
|
|
128
tests_asyncio/test_relay_connection.py
Normal file
128
tests_asyncio/test_relay_connection.py
Normal 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},
|
||||||
|
}
|
||||||
|
}
|
91
tests_asyncio/test_relay_mutation.py
Normal file
91
tests_asyncio/test_relay_mutation.py
Normal 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"}},
|
||||||
|
}
|
||||||
|
}
|
9
tox.ini
9
tox.ini
|
@ -1,13 +1,16 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = flake8,py27,py33,py34,py35,py36,pre-commit,pypy,mypy
|
envlist = flake8,py27,py34,py35,py36,py37,pre-commit,pypy,mypy
|
||||||
skipsdist = true
|
skipsdist = true
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps = .[test]
|
deps =
|
||||||
|
.[test]
|
||||||
|
py{35,36,37}: pytest-asyncio
|
||||||
setenv =
|
setenv =
|
||||||
PYTHONPATH = .:{envdir}
|
PYTHONPATH = .:{envdir}
|
||||||
commands =
|
commands =
|
||||||
py.test --cov=graphene graphene examples
|
py{27,34,py}: py.test --cov=graphene graphene examples {posargs}
|
||||||
|
py{35,36,37}: py.test --cov=graphene graphene examples tests_asyncio {posargs}
|
||||||
|
|
||||||
[testenv:pre-commit]
|
[testenv:pre-commit]
|
||||||
basepython=python3.6
|
basepython=python3.6
|
||||||
|
|
Loading…
Reference in New Issue
Block a user