From 7dd8305bdf30745b0926a5afeb6c4a947bafb117 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 29 Aug 2018 12:35:44 +0300 Subject: [PATCH 01/44] Change deprecated execute() arguments to new ones Changed in https://github.com/graphql-python/graphql-core/pull/185 , the docs here were out of date, as were the tests. --- docs/execution/execute.rst | 8 ++++---- docs/testing/index.rst | 4 ++-- examples/context_example.py | 4 ++-- examples/starwars/tests/test_query.py | 6 +++--- graphene/types/tests/test_datetime.py | 6 +++--- graphene/types/tests/test_decimal.py | 2 +- graphene/types/tests/test_generic.py | 2 +- graphene/types/tests/test_json.py | 2 +- graphene/types/tests/test_query.py | 2 +- graphene/types/tests/test_uuid.py | 2 +- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/execution/execute.rst b/docs/execution/execute.rst index 327ce230..21345aa3 100644 --- a/docs/execution/execute.rst +++ b/docs/execution/execute.rst @@ -16,7 +16,7 @@ For executing a query a schema, you can directly call the ``execute`` method on Context _______ -You can pass context to a query via ``context_value``. +You can pass context to a query via ``context``. .. code:: python @@ -28,14 +28,14 @@ You can pass context to a query via ``context_value``. return info.context.get('name') schema = graphene.Schema(Query) - result = schema.execute('{ name }', context_value={'name': 'Syrus'}) + result = schema.execute('{ name }', context={'name': 'Syrus'}) Variables _______ -You can pass variables to a query via ``variable_values``. +You can pass variables to a query via ``variables``. .. code:: python @@ -55,5 +55,5 @@ You can pass variables to a query via ``variable_values``. lastName } }''', - variable_values={'id': 12}, + variables={'id': 12}, ) diff --git a/docs/testing/index.rst b/docs/testing/index.rst index 0263a9aa..0103779c 100644 --- a/docs/testing/index.rst +++ b/docs/testing/index.rst @@ -54,7 +54,7 @@ Execute parameters ~~~~~~~~~~~~~~~~~~ You can also add extra keyword arguments to the ``execute`` method, such as -``context_value``, ``root_value``, ``variable_values``, ...: +``context``, ``root``, ``variables``, ...: .. code:: python @@ -63,7 +63,7 @@ You can also add extra keyword arguments to the ``execute`` method, such as def test_hey(): client = Client(my_schema) - executed = client.execute('''{ hey }''', context_value={'user': 'Peter'}) + executed = client.execute('''{ hey }''', context={'user': 'Peter'}) assert executed == { 'data': { 'hey': 'hello Peter!' diff --git a/examples/context_example.py b/examples/context_example.py index 5fd7647d..9b5fd1a5 100644 --- a/examples/context_example.py +++ b/examples/context_example.py @@ -25,11 +25,11 @@ query = """ def test_query(): - result = schema.execute(query, context_value={"user": User(id="1", name="Syrus")}) + result = schema.execute(query, context={"user": User(id="1", name="Syrus")}) assert not result.errors assert result.data == {"me": {"id": "1", "name": "Syrus"}} if __name__ == "__main__": - result = schema.execute(query, context_value={"user": User(id="X", name="Console")}) + result = schema.execute(query, context={"user": User(id="X", name="Console")}) print(result.data["me"]) diff --git a/examples/starwars/tests/test_query.py b/examples/starwars/tests/test_query.py index b26374b6..88934b0e 100644 --- a/examples/starwars/tests/test_query.py +++ b/examples/starwars/tests/test_query.py @@ -72,7 +72,7 @@ def test_fetch_some_id_query(snapshot): } """ params = {"someId": "1000"} - snapshot.assert_match(client.execute(query, variable_values=params)) + snapshot.assert_match(client.execute(query, variables=params)) def test_fetch_some_id_query2(snapshot): @@ -84,7 +84,7 @@ def test_fetch_some_id_query2(snapshot): } """ params = {"someId": "1002"} - snapshot.assert_match(client.execute(query, variable_values=params)) + snapshot.assert_match(client.execute(query, variables=params)) def test_invalid_id_query(snapshot): @@ -96,7 +96,7 @@ def test_invalid_id_query(snapshot): } """ params = {"id": "not a valid id"} - snapshot.assert_match(client.execute(query, variable_values=params)) + snapshot.assert_match(client.execute(query, variables=params)) def test_fetch_luke_aliased(snapshot): diff --git a/graphene/types/tests/test_datetime.py b/graphene/types/tests/test_datetime.py index 5165aa61..524afd47 100644 --- a/graphene/types/tests/test_datetime.py +++ b/graphene/types/tests/test_datetime.py @@ -90,7 +90,7 @@ def test_datetime_query_variable(): result = schema.execute( """query Test($date: DateTime){ datetime(in: $date) }""", - variable_values={"date": isoformat}, + variables={"date": isoformat}, ) assert not result.errors assert result.data == {"datetime": isoformat} @@ -102,7 +102,7 @@ def test_date_query_variable(): result = schema.execute( """query Test($date: Date){ date(in: $date) }""", - variable_values={"date": isoformat}, + variables={"date": isoformat}, ) assert not result.errors assert result.data == {"date": isoformat} @@ -115,7 +115,7 @@ def test_time_query_variable(): result = schema.execute( """query Test($time: Time){ time(at: $time) }""", - variable_values={"time": isoformat}, + variables={"time": isoformat}, ) assert not result.errors assert result.data == {"time": isoformat} diff --git a/graphene/types/tests/test_decimal.py b/graphene/types/tests/test_decimal.py index abc4a6c4..fd77f482 100644 --- a/graphene/types/tests/test_decimal.py +++ b/graphene/types/tests/test_decimal.py @@ -28,7 +28,7 @@ def test_decimal_string_query_variable(): result = schema.execute( """query Test($decimal: Decimal){ decimal(input: $decimal) }""", - variable_values={"decimal": decimal_value}, + variables={"decimal": decimal_value}, ) assert not result.errors assert result.data == {"decimal": str(decimal_value)} diff --git a/graphene/types/tests/test_generic.py b/graphene/types/tests/test_generic.py index 83e9bc88..338da982 100644 --- a/graphene/types/tests/test_generic.py +++ b/graphene/types/tests/test_generic.py @@ -39,7 +39,7 @@ def test_generic_query_variable(): ]: result = schema.execute( """query Test($generic: GenericScalar){ generic(input: $generic) }""", - variable_values={"generic": generic_value}, + variables={"generic": generic_value}, ) assert not result.errors assert result.data == {"generic": generic_value} diff --git a/graphene/types/tests/test_json.py b/graphene/types/tests/test_json.py index c6b93586..6b8189c4 100644 --- a/graphene/types/tests/test_json.py +++ b/graphene/types/tests/test_json.py @@ -28,7 +28,7 @@ def test_jsonstring_query_variable(): result = schema.execute( """query Test($json: JSONString){ json(input: $json) }""", - variable_values={"json": json_value}, + variables={"json": json_value}, ) assert not result.errors assert result.data == {"json": json_value} diff --git a/graphene/types/tests/test_query.py b/graphene/types/tests/test_query.py index 9f693a52..8681e462 100644 --- a/graphene/types/tests/test_query.py +++ b/graphene/types/tests/test_query.py @@ -464,7 +464,7 @@ def test_query_annotated_resolvers(): assert not result.errors assert result.data == {"annotated": "base-self"} - result = test_schema.execute("{ context }", "base", context_value=context) + result = test_schema.execute("{ context }", "base", context=context) assert not result.errors assert result.data == {"context": "base-context"} diff --git a/graphene/types/tests/test_uuid.py b/graphene/types/tests/test_uuid.py index 9b3f93a0..2280b41f 100644 --- a/graphene/types/tests/test_uuid.py +++ b/graphene/types/tests/test_uuid.py @@ -25,7 +25,7 @@ def test_uuidstring_query_variable(): result = schema.execute( """query Test($uuid: UUID){ uuid(input: $uuid) }""", - variable_values={"uuid": uuid_value}, + variables={"uuid": uuid_value}, ) assert not result.errors assert result.data == {"uuid": uuid_value} From 4f2b278e12d74e1f4338de85e34ed8bb6bd0ef7c Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 29 Aug 2018 13:07:45 +0300 Subject: [PATCH 02/44] black reformat --- graphene/types/tests/test_datetime.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/graphene/types/tests/test_datetime.py b/graphene/types/tests/test_datetime.py index 524afd47..98e5e7ab 100644 --- a/graphene/types/tests/test_datetime.py +++ b/graphene/types/tests/test_datetime.py @@ -101,8 +101,7 @@ def test_date_query_variable(): isoformat = now.isoformat() result = schema.execute( - """query Test($date: Date){ date(in: $date) }""", - variables={"date": isoformat}, + """query Test($date: Date){ date(in: $date) }""", variables={"date": isoformat} ) assert not result.errors assert result.data == {"date": isoformat} @@ -114,8 +113,7 @@ def test_time_query_variable(): isoformat = time.isoformat() result = schema.execute( - """query Test($time: Time){ time(at: $time) }""", - variables={"time": isoformat}, + """query Test($time: Time){ time(at: $time) }""", variables={"time": isoformat} ) assert not result.errors assert result.data == {"time": isoformat} From 07ec4195781404eaf4e4ca3de034a7165bb22c9b Mon Sep 17 00:00:00 2001 From: Dan Palmer Date: Wed, 29 Aug 2018 17:31:46 +0100 Subject: [PATCH 03/44] Fix grammar --- graphene/relay/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index f23849f2..a942c01b 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -103,7 +103,7 @@ class IterableConnectionField(Field): if is_node(connection_type): raise Exception( - "ConnectionField's now need a explicit ConnectionType for Nodes.\n" + "ConnectionFields now need a explicit ConnectionType for Nodes.\n" "Read more: https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#node-connections" ) From baec6249e5bc2b1770a2c0c028a3e3a18235e02f Mon Sep 17 00:00:00 2001 From: Dan Palmer Date: Wed, 29 Aug 2018 18:10:37 +0100 Subject: [PATCH 04/44] Fix assertion --- graphene/relay/tests/test_connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene/relay/tests/test_connection.py b/graphene/relay/tests/test_connection.py index 061d6ec9..ca43f397 100644 --- a/graphene/relay/tests/test_connection.py +++ b/graphene/relay/tests/test_connection.py @@ -138,7 +138,7 @@ def test_connectionfield_node_deprecated(): with pytest.raises(Exception) as exc_info: field.type - assert "ConnectionField's now need a explicit ConnectionType for Nodes." in str( + assert "ConnectionFields now need a explicit ConnectionType for Nodes." in str( exc_info.value ) From bf3a4a88a40a738b055822c25686c2aa5b32934d Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 31 Aug 2018 17:47:05 +0200 Subject: [PATCH 05/44] Added ROADMAP to the Project --- README.md | 28 ++++++++++++++-------------- README.rst | 47 ++++++++++++++++++++++++++++++++++++++++++----- ROADMAP.md | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 ROADMAP.md diff --git a/README.md b/README.md index 1ffb4aea..a8a243a8 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ -Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade to Graphene `2.0`. +**We are looking for contributors**! Please check the [ROADMAP](https://github.com/graphql-python/graphene/blob/master/ROADMAP.md) to see how you can help ❀️ --- # ![Graphene Logo](http://graphene-python.org/favicon.png) [Graphene](http://graphene-python.org) [![Build Status](https://travis-ci.org/graphql-python/graphene.svg?branch=master)](https://travis-ci.org/graphql-python/graphene) [![PyPI version](https://badge.fury.io/py/graphene.svg)](https://badge.fury.io/py/graphene) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene?branch=master) - [Graphene](http://graphene-python.org) is a Python library for building GraphQL schemas/types fast and easily. - **Easy to use:** Graphene helps you use GraphQL in Python without effort. @@ -13,17 +12,16 @@ Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade to Graph We believe that by providing a complete API you could plug Graphene anywhere your data lives and make your data available through GraphQL. - ## Integrations Graphene has multiple integrations with different frameworks: -| integration | Package | -|---------------|-------------------| -| Django | [graphene-django](https://github.com/graphql-python/graphene-django/) | -| SQLAlchemy | [graphene-sqlalchemy](https://github.com/graphql-python/graphene-sqlalchemy/) | -| Google App Engine | [graphene-gae](https://github.com/graphql-python/graphene-gae/) | -| Peewee | *In progress* ([Tracking Issue](https://github.com/graphql-python/graphene/issues/289)) | +| integration | Package | +| ----------------- | --------------------------------------------------------------------------------------- | +| Django | [graphene-django](https://github.com/graphql-python/graphene-django/) | +| SQLAlchemy | [graphene-sqlalchemy](https://github.com/graphql-python/graphene-sqlalchemy/) | +| Google App Engine | [graphene-gae](https://github.com/graphql-python/graphene-gae/) | +| Peewee | _In progress_ ([Tracking Issue](https://github.com/graphql-python/graphene/issues/289)) | Also, Graphene is fully compatible with the GraphQL spec, working seamlessly with all GraphQL clients, such as [Relay](https://github.com/facebook/relay), [Apollo](https://github.com/apollographql/apollo-client) and [gql](https://github.com/graphql-python/gql). @@ -39,7 +37,6 @@ pip install "graphene>=2.0" Please read [UPGRADE-v2.0.md](/UPGRADE-v2.0.md) to learn how to upgrade. - ## Examples Here is one example for you to get started: @@ -67,9 +64,8 @@ result = schema.execute(query) If you want to learn even more, you can also check the following [examples](examples/): -* **Basic Schema**: [Starwars example](examples/starwars) -* **Relay Schema**: [Starwars Relay example](examples/starwars_relay) - +- **Basic Schema**: [Starwars example](examples/starwars) +- **Relay Schema**: [Starwars Relay example](examples/starwars_relay) ## Contributing @@ -90,7 +86,7 @@ py.test PATH/TO/MY/DIR/ # All tests in directory Add the `-s` flag if you have introduced breakpoints into the code for debugging. Add the `-v` ("verbose") flag to get more detailed test output. For even more detailed output, use `-vv`. -Check out the [pytest documentation](https://docs.pytest.org/en/latest/) for more options and test running controls. +Check out the [pytest documentation](https://docs.pytest.org/en/latest/) for more options and test running controls. You can also run the benchmarks with: @@ -99,13 +95,17 @@ py.test graphene --benchmark-only ``` Graphene supports several versions of Python. To make sure that changes do not break compatibility with any of those versions, we use `tox` to create virtualenvs for each python version and run tests with that version. To run against all python versions defined in the `tox.ini` config file, just run: + ```sh tox ``` + If you wish to run against a specific version defined in the `tox.ini` file: + ```sh tox -e py36 ``` + Tox can only use whatever versions of python are installed on your system. When you create a pull request, Travis will also be running the same tests and report the results, so there is no need for potential contributors to try to install every single version of python on their own system ahead of time. We appreciate opening issues and pull requests to make graphene even more stable & useful! ### Documentation diff --git a/README.rst b/README.rst index bea6c4d4..1360ec64 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,6 @@ -Please read `UPGRADE-v2.0.md `__ to learn how to -upgrade to Graphene ``2.0``. +**We are looking for contributors**! Please check the +`ROADMAP `__ +to see how you can help ❀️ -------------- @@ -91,17 +92,29 @@ If you want to learn even more, you can also check the following Contributing ------------ -After cloning this repo, ensure dependencies are installed by running: +After cloning this repo, create a +`virtualenv `__ and ensure +dependencies are installed by running: .. code:: sh + virtualenv venv + source venv/bin/activate pip install -e ".[test]" -After developing, the full test suite can be evaluated by running: +Well-written tests and maintaining good test coverage is important to +this project. While developing, run new and existing tests with: .. code:: sh - py.test graphene --cov=graphene --benchmark-skip # Use -v -s for verbose mode + py.test PATH/TO/MY/DIR/test_test.py # Single file + py.test PATH/TO/MY/DIR/ # All tests in directory + +Add the ``-s`` flag if you have introduced breakpoints into the code for +debugging. Add the ``-v`` ("verbose") flag to get more detailed test +output. For even more detailed output, use ``-vv``. Check out the +`pytest documentation `__ for more +options and test running controls. You can also run the benchmarks with: @@ -109,6 +122,30 @@ You can also run the benchmarks with: 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: + +.. 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! + Documentation ~~~~~~~~~~~~~ diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 00000000..39ce47f9 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,33 @@ +# Graphene Roadmap + +In order to move Graphene and the GraphQL Python ecosystem forward I realized is essential to be clear with the community on next steps, so we can move uniformly. + +There are few key points that need to happen in the short/mid term, divided into two main sections: + +- [Community](#community) +- [Graphene 3](#graphene-3) + +_πŸ‘‹ If you have more ideas on how to move the Graphene ecosystem forward, don't hesistate to [open a PR](https://github.com/graphql-python/graphene/edit/master/ROADMAP.md)_ + +## Community + +The goal is to improve adoption and sustainability of the project. + +- πŸ’Ž Add Commercial Support for Graphene #813 + - Create Patreon page + - Add /support page in Graphene website +- πŸ“˜ Vastly improve documentation #823 +- πŸ’° Apply for [Mozilla MOSS](https://www.mozilla.org/en-US/moss/) sponsorship + +## 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 #729 +- πŸ“„ Schema creation from SDL (API TBD) +- ✨ Improve connections structure +- πŸ“— Improve function documentation +- πŸ”€ Add support for coroutines in Connection, Mutation (abstracting out Promise requirement) From 5777d85f997c14907f7c68deb60a23dcc5fd6ed7 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 31 Aug 2018 17:58:51 +0200 Subject: [PATCH 06/44] Improved docs for testing --- README.md | 4 ++-- README.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a8a243a8..98193506 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,8 @@ pip install -e ".[test]" Well-written tests and maintaining good test coverage is important to this project. While developing, run new and existing tests with: ```sh -py.test PATH/TO/MY/DIR/test_test.py # Single file -py.test PATH/TO/MY/DIR/ # All tests in directory +py.test graphene/relay/tests/test_node.py # Single file +py.test graphene/relay # All tests in directory ``` Add the `-s` flag if you have introduced breakpoints into the code for debugging. diff --git a/README.rst b/README.rst index 1360ec64..817e1c01 100644 --- a/README.rst +++ b/README.rst @@ -107,8 +107,8 @@ this project. While developing, run new and existing tests with: .. code:: sh - py.test PATH/TO/MY/DIR/test_test.py # Single file - py.test PATH/TO/MY/DIR/ # All tests in directory + py.test graphene/relay/tests/test_node.py # Single file + py.test graphene/relay # All tests in directory Add the ``-s`` flag if you have introduced breakpoints into the code for debugging. Add the ``-v`` ("verbose") flag to get more detailed test From 3e5319cf70ee5860052388e6ce0073df433b163d Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 31 Aug 2018 19:41:20 +0200 Subject: [PATCH 07/44] Abstract thenables (promise, coroutine) out of relay Connections and Mutations --- .travis.yml | 30 +++--- graphene/relay/connection.py | 7 +- graphene/relay/mutation.py | 8 +- graphene/utils/thenables.py | 39 ++++++++ graphene/utils/thenables_asyncio.py | 6 ++ setup.py | 2 +- tests_asyncio/test_relay_connection.py | 128 +++++++++++++++++++++++++ tests_asyncio/test_relay_mutation.py | 100 +++++++++++++++++++ tox.ini | 13 ++- 9 files changed, 302 insertions(+), 31 deletions(-) create mode 100644 graphene/utils/thenables.py create mode 100644 graphene/utils/thenables_asyncio.py create mode 100644 tests_asyncio/test_relay_connection.py create mode 100644 tests_asyncio/test_relay_mutation.py diff --git a/.travis.yml b/.travis.yml index 399ce134..87aea137 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,22 @@ language: python matrix: include: - - env: TOXENV=py27 - python: 2.7 - - env: TOXENV=py34 - python: 3.4 - - env: TOXENV=py35 - python: 3.5 - - env: TOXENV=py36 - python: 3.6 - - env: TOXENV=pypy - python: pypy-5.7.1 - - env: TOXENV=pre-commit - python: 3.6 - - env: TOXENV=mypy - python: 3.6 + - env: TOXENV=py27 + python: 2.7 + - env: TOXENV=py34 + python: 3.4 + - env: TOXENV=py35 + python: 3.5 + - env: TOXENV=py36 + python: 3.6 + - env: TOXENV=py37 + python: 3.7 + - env: TOXENV=pypy + python: pypy-5.7.1 + - env: TOXENV=pre-commit + python: 3.6 + - env: TOXENV=mypy + python: 3.6 install: - pip install coveralls tox script: tox diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index a942c01b..2782865c 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -3,11 +3,11 @@ from collections import Iterable, OrderedDict from functools import partial from graphql_relay import connection_from_list -from promise import Promise, is_thenable from ..types import Boolean, Enum, Int, Interface, List, NonNull, Scalar, String, Union from ..types.field import Field from ..types.objecttype import ObjectType, ObjectTypeOptions +from ..utils.thenables import maybe_thenable from .node import is_node @@ -139,10 +139,7 @@ class IterableConnectionField(Field): connection_type = connection_type.of_type on_resolve = partial(cls.resolve_connection, connection_type, args) - if is_thenable(resolved): - return Promise.resolve(resolved).then(on_resolve) - - return on_resolve(resolved) + return maybe_thenable(resolved, on_resolve) def get_resolver(self, parent_resolver): resolver = super(IterableConnectionField, self).get_resolver(parent_resolver) diff --git a/graphene/relay/mutation.py b/graphene/relay/mutation.py index 1b8b855e..ee758e78 100644 --- a/graphene/relay/mutation.py +++ b/graphene/relay/mutation.py @@ -1,10 +1,9 @@ import re from collections import OrderedDict -from promise import Promise, is_thenable - from ..types import Field, InputObjectType, String from ..types.mutation import Mutation +from ..utils.thenables import maybe_thenable class ClientIDMutation(Mutation): @@ -69,7 +68,4 @@ class ClientIDMutation(Mutation): return payload result = cls.mutate_and_get_payload(root, info, **input) - if is_thenable(result): - return Promise.resolve(result).then(on_resolve) - - return on_resolve(result) + return maybe_thenable(result, on_resolve) diff --git a/graphene/utils/thenables.py b/graphene/utils/thenables.py new file mode 100644 index 00000000..c1fab663 --- /dev/null +++ b/graphene/utils/thenables.py @@ -0,0 +1,39 @@ +""" +This file is used mainly as a bridge for thenable abstractions. +This includes: +- Promises +- Asyncio Coroutines +""" + +try: + from promise import Promise, is_thenable +except ImportError: + + def is_thenable(obj): + return False + + +try: + from inspect import isawaitable + from .thenables_asyncio import await_and_execute +except ImportError: + + def isawaitable(obj): + 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): + return await_and_execute(obj, on_resolve) + + if is_thenable(obj): + return Promise.resolve(obj).then(on_resolve) + + # If it's not awaitable not a Promise, return + # the function executed over the object + return on_resolve(obj) diff --git a/graphene/utils/thenables_asyncio.py b/graphene/utils/thenables_asyncio.py new file mode 100644 index 00000000..3c01b1a2 --- /dev/null +++ b/graphene/utils/thenables_asyncio.py @@ -0,0 +1,6 @@ +def await_and_execute(obj, on_resolve): + async def build_resolve_async(): + return on_resolve(await obj) + + return build_resolve_async() + diff --git a/setup.py b/setup.py index be2111f8..d9f62bec 100644 --- a/setup.py +++ b/setup.py @@ -50,6 +50,7 @@ tests_require = [ "pytest-mock", "snapshottest", "coveralls", + "promise", "six", "mock", "pytz", @@ -84,7 +85,6 @@ setup( "six>=1.10.0,<2", "graphql-core>=2.1,<3", "graphql-relay>=0.4.5,<1", - "promise>=2.1,<3", "aniso8601>=3,<4", ], tests_require=tests_require, diff --git a/tests_asyncio/test_relay_connection.py b/tests_asyncio/test_relay_connection.py new file mode 100644 index 00000000..ec86fef6 --- /dev/null +++ b/tests_asyncio/test_relay_connection.py @@ -0,0 +1,128 @@ +import pytest + +from collections import OrderedDict +from graphql.execution.executors.asyncio import AsyncioExecutor + +from graphql_relay.utils import base64 + +from graphene.types import ObjectType, Schema, String +from graphene.relay.connection import Connection, ConnectionField, PageInfo +from graphene.relay.node import Node + +letter_chars = ["A", "B", "C", "D", "E"] + + +class Letter(ObjectType): + class Meta: + interfaces = (Node,) + + letter = String() + + +class LetterConnection(Connection): + class Meta: + node = Letter + + +class Query(ObjectType): + letters = ConnectionField(LetterConnection) + connection_letters = ConnectionField(LetterConnection) + promise_letters = ConnectionField(LetterConnection) + + node = Node.Field() + + def resolve_letters(self, info, **args): + return list(letters.values()) + + async def resolve_promise_letters(self, info, **args): + return list(letters.values()) + + def resolve_connection_letters(self, info, **args): + return LetterConnection( + page_info=PageInfo(has_next_page=True, has_previous_page=False), + edges=[ + LetterConnection.Edge(node=Letter(id=0, letter="A"), cursor="a-cursor") + ], + ) + + +schema = Schema(Query) + +letters = OrderedDict() +for i, letter in enumerate(letter_chars): + letters[letter] = Letter(id=i, letter=letter) + + +def edges(selected_letters): + return [ + { + "node": {"id": base64("Letter:%s" % l.id), "letter": l.letter}, + "cursor": base64("arrayconnection:%s" % l.id), + } + for l in [letters[i] for i in selected_letters] + ] + + +def cursor_for(ltr): + letter = letters[ltr] + return base64("arrayconnection:%s" % letter.id) + + +def execute(args=""): + if args: + args = "(" + args + ")" + + return schema.execute( + """ + { + letters%s { + edges { + node { + id + letter + } + cursor + } + pageInfo { + hasPreviousPage + hasNextPage + startCursor + endCursor + } + } + } + """ + % args + ) + + +@pytest.mark.asyncio +async def test_connection_promise(): + result = await schema.execute( + """ + { + promiseLetters(first:1) { + edges { + node { + id + letter + } + } + pageInfo { + hasPreviousPage + hasNextPage + } + } + } + """, + executor=AsyncioExecutor(), + return_promise=True, + ) + + assert not result.errors + assert result.data == { + "promiseLetters": { + "edges": [{"node": {"id": "TGV0dGVyOjA=", "letter": "A"}}], + "pageInfo": {"hasPreviousPage": False, "hasNextPage": True}, + } + } diff --git a/tests_asyncio/test_relay_mutation.py b/tests_asyncio/test_relay_mutation.py new file mode 100644 index 00000000..2992c8ee --- /dev/null +++ b/tests_asyncio/test_relay_mutation.py @@ -0,0 +1,100 @@ +import pytest +from graphql.execution.executors.asyncio import AsyncioExecutor + +from graphene.types import ( + ID, + Argument, + Field, + InputField, + InputObjectType, + NonNull, + ObjectType, + Schema, +) +from graphene.types.scalars import String +from graphene.relay.mutation import ClientIDMutation + + +class SharedFields(object): + shared = String() + + +class MyNode(ObjectType): + # class Meta: + # interfaces = (Node, ) + id = ID() + name = String() + + +class SaySomethingAsync(ClientIDMutation): + class Input: + what = String() + + phrase = String() + + @staticmethod + async def mutate_and_get_payload(self, info, what, client_mutation_id=None): + return SaySomethingAsync(phrase=str(what)) + + +# MyEdge = MyNode.Connection.Edge +class MyEdge(ObjectType): + node = Field(MyNode) + cursor = String() + + +class OtherMutation(ClientIDMutation): + class Input(SharedFields): + additional_field = String() + + name = String() + my_node_edge = Field(MyEdge) + + @staticmethod + def mutate_and_get_payload( + self, info, shared="", additional_field="", client_mutation_id=None + ): + edge_type = MyEdge + return OtherMutation( + name=shared + additional_field, + my_node_edge=edge_type(cursor="1", node=MyNode(name="name")), + ) + + +class RootQuery(ObjectType): + something = String() + + +class Mutation(ObjectType): + say_promise = SaySomethingAsync.Field() + other = OtherMutation.Field() + + +schema = Schema(query=RootQuery, mutation=Mutation) + + +@pytest.mark.asyncio +async def test_node_query_promise(): + executed = await schema.execute( + 'mutation a { sayPromise(input: {what:"hello", clientMutationId:"1"}) { phrase } }', + executor=AsyncioExecutor(), + return_promise=True, + ) + assert not executed.errors + assert executed.data == {"sayPromise": {"phrase": "hello"}} + + +@pytest.mark.asyncio +async def test_edge_query(): + executed = await schema.execute( + 'mutation a { other(input: {clientMutationId:"1"}) { clientMutationId, myNodeEdge { cursor node { name }} } }', + executor=AsyncioExecutor(), + return_promise=True, + ) + assert not executed.errors + assert dict(executed.data) == { + "other": { + "clientMutationId": "1", + "myNodeEdge": {"cursor": "1", "node": {"name": "name"}}, + } + } diff --git a/tox.ini b/tox.ini index f8e6f347..d52ea642 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,16 @@ [tox] -envlist = flake8,py27,py33,py34,py35,py36,pre-commit,pypy,mypy +envlist = flake8,py27,py34,py35,py36,py37,pre-commit,pypy,mypy skipsdist = true [testenv] -deps = .[test] +deps = + .[test] + py{35,36,37}: pytest-asyncio setenv = - PYTHONPATH = .:{envdir} -commands= - py.test --cov=graphene graphene examples + PYTHONPATH = .:{envdir} +commands = + py{27,34,py}: py.test --cov=graphene graphene examples {posargs} + py{35,36,37}: py.test --cov=graphene graphene examples tests_asyncio {posargs} [testenv:pre-commit] basepython=python3.6 From 3d41a500c9a4d52990d65f1c162ce6cb411a9ded Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 31 Aug 2018 20:01:03 +0200 Subject: [PATCH 08/44] Fixed lint & imports --- .travis.yml | 2 -- graphene/utils/thenables.py | 2 +- graphene/utils/thenables_asyncio.py | 1 - tests_asyncio/test_relay_mutation.py | 11 +---------- tox.ini | 4 ++-- 5 files changed, 4 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 87aea137..8cd9e0c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,6 @@ matrix: python: 3.5 - env: TOXENV=py36 python: 3.6 - - env: TOXENV=py37 - python: 3.7 - env: TOXENV=pypy python: pypy-5.7.1 - env: TOXENV=pre-commit diff --git a/graphene/utils/thenables.py b/graphene/utils/thenables.py index c1fab663..d18d7e61 100644 --- a/graphene/utils/thenables.py +++ b/graphene/utils/thenables.py @@ -18,7 +18,7 @@ try: from .thenables_asyncio import await_and_execute except ImportError: - def isawaitable(obj): + def isawaitable(obj): # type: ignore return False diff --git a/graphene/utils/thenables_asyncio.py b/graphene/utils/thenables_asyncio.py index 3c01b1a2..d5f93182 100644 --- a/graphene/utils/thenables_asyncio.py +++ b/graphene/utils/thenables_asyncio.py @@ -3,4 +3,3 @@ def await_and_execute(obj, on_resolve): return on_resolve(await obj) return build_resolve_async() - diff --git a/tests_asyncio/test_relay_mutation.py b/tests_asyncio/test_relay_mutation.py index 2992c8ee..42ea5fc7 100644 --- a/tests_asyncio/test_relay_mutation.py +++ b/tests_asyncio/test_relay_mutation.py @@ -1,16 +1,7 @@ import pytest from graphql.execution.executors.asyncio import AsyncioExecutor -from graphene.types import ( - ID, - Argument, - Field, - InputField, - InputObjectType, - NonNull, - ObjectType, - Schema, -) +from graphene.types import ID, Field, ObjectType, Schema from graphene.types.scalars import String from graphene.relay.mutation import ClientIDMutation diff --git a/tox.ini b/tox.ini index d52ea642..2b7ae59c 100644 --- a/tox.ini +++ b/tox.ini @@ -3,11 +3,11 @@ envlist = flake8,py27,py34,py35,py36,py37,pre-commit,pypy,mypy skipsdist = true [testenv] -deps = +deps = .[test] py{35,36,37}: pytest-asyncio setenv = - PYTHONPATH = .:{envdir} + PYTHONPATH = .:{envdir} commands = py{27,34,py}: py.test --cov=graphene graphene examples {posargs} py{35,36,37}: py.test --cov=graphene graphene examples tests_asyncio {posargs} From 9512528a779983967c76c67739aa6127db6a46f1 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 31 Aug 2018 20:09:29 +0200 Subject: [PATCH 09/44] Fixed async funcs --- graphene/utils/thenables.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/graphene/utils/thenables.py b/graphene/utils/thenables.py index d18d7e61..a3089595 100644 --- a/graphene/utils/thenables.py +++ b/graphene/utils/thenables.py @@ -6,10 +6,13 @@ This includes: """ try: - from promise import Promise, is_thenable + from promise import Promise, is_thenable # type: ignore except ImportError: - def is_thenable(obj): + class Promise(object): # type: ignore + pass + + def is_thenable(obj): # type: ignore return False @@ -28,7 +31,7 @@ def maybe_thenable(obj, on_resolve): returning the same type of object inputed. If the object is not thenable, it should return on_resolve(obj) """ - if isawaitable(obj): + if isawaitable(obj) and not isinstance(obj, Promise): return await_and_execute(obj, on_resolve) if is_thenable(obj): From e748c5f048e2d92b1963870e3c6ea17e8951d6f0 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 4 Sep 2018 14:36:17 +0200 Subject: [PATCH 10/44] Update ROADMAP.md --- ROADMAP.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 39ce47f9..24f542e0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -13,11 +13,11 @@ _πŸ‘‹ If you have more ideas on how to move the Graphene ecosystem forward, don' The goal is to improve adoption and sustainability of the project. -- πŸ’Ž Add Commercial Support for Graphene #813 - - Create Patreon page - - Add /support page in Graphene website -- πŸ“˜ Vastly improve documentation #823 -- πŸ’° Apply for [Mozilla MOSS](https://www.mozilla.org/en-US/moss/) sponsorship +- πŸ’Ž 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 @@ -26,8 +26,8 @@ The goal is to summarize the different improvements that Graphene will need to a 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 #729 +- πŸ”Έ 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) +- πŸ”€ Add support for coroutines in Connection, Mutation (abstracting out Promise requirement) - [See PR](https://github.com/graphql-python/graphene/pull/824) From 325ab0d002fc552b7e7c2640abc1caa41b215509 Mon Sep 17 00:00:00 2001 From: Alex Kuzmenko Date: Tue, 4 Sep 2018 16:54:29 +0300 Subject: [PATCH 11/44] Add Python3.7 to CI --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8cd9e0c2..53878e74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,10 @@ matrix: python: 3.5 - env: TOXENV=py36 python: 3.6 + - env: TOXENV=py37 + python: 3.7 + dist: xenial + sudo: true - env: TOXENV=pypy python: pypy-5.7.1 - env: TOXENV=pre-commit From 00d370b2282196af34f269cd4f63b7e365dcc221 Mon Sep 17 00:00:00 2001 From: Dan Palmer Date: Sat, 8 Sep 2018 17:20:39 +0100 Subject: [PATCH 12/44] Expose documentation for Union types --- graphene/types/typemap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/graphene/types/typemap.py b/graphene/types/typemap.py index dce04445..9edb8518 100644 --- a/graphene/types/typemap.py +++ b/graphene/types/typemap.py @@ -255,6 +255,7 @@ class TypeMap(GraphQLTypeMap): return GrapheneUnionType( graphene_type=type, name=type._meta.name, + description=type._meta.description, types=types, resolve_type=_resolve_type, ) From 8d5843dc216363b6e0fd59fa8b75a2eaa58699af Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 9 Sep 2018 18:09:06 +0200 Subject: [PATCH 13/44] Added BACKERS.md --- BACKERS.md | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 BACKERS.md diff --git a/BACKERS.md b/BACKERS.md new file mode 100644 index 00000000..4aa2214f --- /dev/null +++ b/BACKERS.md @@ -0,0 +1,97 @@ +

Sponsors & Backers

+ +Graphene is an MIT-licensed open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/graphql-python/graphene/blob/master/BACKERS.md). If you'd like to join them, please consider: + +- [Become a backer or sponsor on Patreon](https://www.patreon.com/syrusakbary). +- [One-time donation via PayPal.](https://graphene-python.org/support-graphene/) + +

+ + + +

Platinum via Patreon

+ + + + + + + + +
+ + + +
+ +

Gold via Patreon

+ + + + + + + + +
+ + + +
+ + +

Silver via Patreon

+ + + + + + + + +
+ + + +
+ + +

Bronze via Patreon

+ + + + + + + + +
+ + + +
+ + +

Generous Backers via Patreon ($50+)

+ + + +- [Lee Benson](https://github.com/leebenson) +- [Become a Patron](https://www.patreon.com/bePatron?c=2046477) + + +

Backers via Patreon

+ + + +- [Become a Patron](https://www.patreon.com/bePatron?c=2046477) + From b8ecc3929d55e69de9abb1d8c4cf75ffd45ac656 Mon Sep 17 00:00:00 2001 From: Dan Palmer Date: Sun, 9 Sep 2018 18:19:58 +0100 Subject: [PATCH 14/44] Add basic type documentation for Relay fields This adds documentation in the API for `PageInfo`s and `Edges`. This is useful to include in Graphene because `PageInfo` is always the same, and Edges always have the same format, so documentation for both can be created automatically. --- graphene/relay/connection.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index 2782865c..a0f5a14c 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -12,6 +12,12 @@ from .node import is_node class PageInfo(ObjectType): + class Meta: + description = ( + "The Relay compliant `PageInfo` type, containing data necessary to" + " paginate this connection." + ) + has_next_page = Boolean( required=True, name="hasNextPage", @@ -64,13 +70,18 @@ class Connection(ObjectType): node = Field(_node, description="The item at the end of the edge") cursor = String(required=True, description="A cursor for use in pagination") + class EdgeMeta: + description = "A Relay edge containing a `{}` and its cursor.".format( + base_name + ) + edge_name = "{}Edge".format(base_name) if edge_class: edge_bases = (edge_class, EdgeBase, ObjectType) else: edge_bases = (EdgeBase, ObjectType) - edge = type(edge_name, edge_bases, {}) + edge = type(edge_name, edge_bases, {"Meta": EdgeMeta}) cls.Edge = edge options["name"] = name From 2a3d92682a76080b4e80d9adea6f9fafbc1962cd Mon Sep 17 00:00:00 2001 From: Dan Palmer Date: Sun, 9 Sep 2018 18:44:01 +0100 Subject: [PATCH 15/44] Add descriptions to the fields as well --- graphene/relay/connection.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/graphene/relay/connection.py b/graphene/relay/connection.py index a0f5a14c..927dfcc4 100644 --- a/graphene/relay/connection.py +++ b/graphene/relay/connection.py @@ -88,8 +88,22 @@ class Connection(ObjectType): _meta.node = node _meta.fields = OrderedDict( [ - ("page_info", Field(PageInfo, name="pageInfo", required=True)), - ("edges", Field(NonNull(List(edge)))), + ( + "page_info", + Field( + PageInfo, + name="pageInfo", + required=True, + description="Pagination data for this connection.", + ), + ), + ( + "edges", + Field( + NonNull(List(edge)), + description="Contains the nodes in this connection.", + ), + ), ] ) return super(Connection, cls).__init_subclass_with_meta__( From 3df3754ae6b79cc35c0cd6ac0a0e5e2b62d5d670 Mon Sep 17 00:00:00 2001 From: Derek Schaller Date: Sat, 13 Oct 2018 11:21:07 -0700 Subject: [PATCH 16/44] inline doctstring of make targets --- docs/Makefile | 79 ++++++++++++++++++--------------------------------- 1 file changed, 27 insertions(+), 52 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 2973acec..3eb7e638 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -17,75 +17,50 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " epub3 to make an epub3" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - @echo " dummy to check syntax errors of document sources" + @grep -E '^\.PHONY: [a-zA-Z_-]+ .*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = "(: |##)"}; {printf "\033[36m%-30s\033[0m %s\n", $$2, $$3}' .PHONY: clean clean: rm -rf $(BUILDDIR)/* -.PHONY: html +.PHONY: html ## to make standalone HTML files html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." -.PHONY: dirhtml +.PHONY: dirhtml ## to make HTML files named index.html in directories dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." -.PHONY: singlehtml +.PHONY: singlehtml ## to make a single large HTML file singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." -.PHONY: pickle +.PHONY: pickle ## to make pickle files pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." -.PHONY: json +.PHONY: json ## to make JSON files json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." -.PHONY: htmlhelp +.PHONY: htmlhelp ## to make HTML files and a HTML help project htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." -.PHONY: qthelp +.PHONY: qthelp ## to make HTML files and a qthelp project qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @@ -95,7 +70,7 @@ qthelp: @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Graphene.qhc" -.PHONY: applehelp +.PHONY: applehelp ## to make an Apple Help Book applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @@ -104,7 +79,7 @@ applehelp: "~/Library/Documentation/Help or install it in your application" \ "bundle." -.PHONY: devhelp +.PHONY: devhelp ## to make HTML files and a Devhelp project devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @@ -114,19 +89,19 @@ devhelp: @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Graphene" @echo "# devhelp" -.PHONY: epub +.PHONY: epub ## to make an epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." -.PHONY: epub3 +.PHONY: epub3 ## to make an epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." -.PHONY: latex +.PHONY: latex ## to make LaTeX files, you can set PAPER=a4 or PAPER=letter latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @@ -134,33 +109,33 @@ latex: @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." -.PHONY: latexpdf +.PHONY: latexpdf ## to make LaTeX files and run them through pdflatex latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." -.PHONY: latexpdfja +.PHONY: latexpdfja ## to make LaTeX files and run them through platex/dvipdfmx latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." -.PHONY: text +.PHONY: text ## to make text files text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." -.PHONY: man +.PHONY: man ## to make manual pages man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." -.PHONY: texinfo +.PHONY: texinfo ## to make Texinfo files texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @@ -168,57 +143,57 @@ texinfo: @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." -.PHONY: info +.PHONY: info ## to make Texinfo files and run them through makeinfo info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." -.PHONY: gettext +.PHONY: gettext ## to make PO message catalogs gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." -.PHONY: changes +.PHONY: changes ## to make an overview of all changed/added/deprecated items changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." -.PHONY: linkcheck +.PHONY: linkcheck ## to check all external links for integrity linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." -.PHONY: doctest +.PHONY: doctest ## to run all doctests embedded in the documentation (if enabled) doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." -.PHONY: coverage +.PHONY: coverage ## to run coverage check of the documentation (if enabled) coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." -.PHONY: xml +.PHONY: xml ## to make Docutils-native XML files xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." -.PHONY: pseudoxml +.PHONY: pseudoxml ## to make pseudoxml-XML files for display purposes pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." -.PHONY: dummy +.PHONY: dummy ## to check syntax errors of document sources dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo From a4c812f8865f48dbd4a3816179cb4c52d1937a9a Mon Sep 17 00:00:00 2001 From: Derek Schaller Date: Sat, 13 Oct 2018 11:27:57 -0700 Subject: [PATCH 17/44] add root make target for creating html version of docs --- Makefile | 11 +++++++++++ README.md | 11 ++--------- 2 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..c1a00054 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @grep -E '^\.PHONY: [a-zA-Z_-]+ .*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = "(: |##)"}; {printf "\033[36m%-30s\033[0m %s\n", $$2, $$3}' + +.PHONY: docs ## Generate docs +docs: + @cd docs &&\ + pip install -r requirements.txt &&\ + make html &&\ + cd - diff --git a/README.md b/README.md index 98193506..a5a64f09 100644 --- a/README.md +++ b/README.md @@ -112,15 +112,8 @@ Tox can only use whatever versions of python are installed on your system. When The documentation is generated using the excellent [Sphinx](http://www.sphinx-doc.org/) and a custom theme. -The documentation dependencies are installed by running: +An HTML version of the documentation is produced by running: ```sh -cd docs -pip install -r requirements.txt -``` - -Then to produce a HTML version of the documentation: - -```sh -make html +make docs ``` From 85e6c3d3fbd3683f2cc139f025107f909fe02127 Mon Sep 17 00:00:00 2001 From: adroullier Date: Tue, 16 Oct 2018 13:59:25 +0200 Subject: [PATCH 18/44] quickstart example improvement, rename name parameter of String field to argument --- docs/quickstart.rst | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index a2d39481..8c0055c6 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -30,17 +30,18 @@ server with an associated set of resolve methods that know how to fetch data. We are going to create a very simple schema, with a ``Query`` with only -one field: ``hello`` and an input name. And when we query it, it should return ``"Hello {name}"``. +one field: ``hello`` and an input name. And when we query it, it should return ``"Hello +{argument}"``. .. code:: python import graphene class Query(graphene.ObjectType): - hello = graphene.String(name=graphene.String(default_value="stranger")) + hello = graphene.String(argument=graphene.String(default_value="stranger")) - def resolve_hello(self, info, name): - return 'Hello ' + name + def resolve_hello(self, info, argument): + return 'Hello ' + argument schema = graphene.Schema(query=Query) @@ -54,4 +55,8 @@ Then we can start querying our schema: result = schema.execute('{ hello }') print(result.data['hello']) # "Hello stranger" + # or passing the argument in the query + result = schema.execute('{ hello (argument: "graph") }') + print(result.data['hello']) # "Hello graph" + Congrats! You got your first graphene schema working! From 7e17b92a18ddb445b4e93c0e2e909a18dc6a8fc4 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Thu, 18 Oct 2018 12:28:12 -0700 Subject: [PATCH 19/44] Don't enforce python 3.6 on precommit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5f72385c..a71f6fe0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,4 +21,4 @@ repos: rev: 18.6b4 hooks: - id: black - language_version: python3.6 + language_version: python3 From 4752ec08ab95f29168f4d4fe331f6ef05d656e76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Gonz=C3=A1lez?= Date: Sat, 20 Oct 2018 11:16:29 +0200 Subject: [PATCH 20/44] fix deprecated "Input" class --- docs/types/mutations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/types/mutations.rst b/docs/types/mutations.rst index 6949008f..8532595e 100644 --- a/docs/types/mutations.rst +++ b/docs/types/mutations.rst @@ -27,7 +27,7 @@ This example defines a Mutation: **person** and **ok** are the output fields of the Mutation when it is resolved. -**Input** attributes are the arguments that the Mutation +**Arguments** attributes are the arguments that the Mutation ``CreatePerson`` needs for resolving, in this case **name** will be the only argument for the mutation. From ca02095806927cd37bbcbe54d98d01c8a67bffb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D0=BC=D1=91=D0=BD=20=D0=9C=D0=B0=D1=80=D1=8C?= =?UTF-8?q?=D1=8F=D1=81=D0=B8=D0=BD?= Date: Mon, 22 Oct 2018 16:19:55 +0300 Subject: [PATCH 21/44] Fix typo in docs --- docs/types/objecttypes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/types/objecttypes.rst b/docs/types/objecttypes.rst index 69c8c08a..fb23dcfc 100644 --- a/docs/types/objecttypes.rst +++ b/docs/types/objecttypes.rst @@ -47,7 +47,7 @@ Resolvers --------- A resolver is a method that resolves certain fields within a -``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``. By default resolvers take the arguments ``info`` and ``*args``. From 21dbaa93c42ee9fe91595d562cc39cf7abe6d9f3 Mon Sep 17 00:00:00 2001 From: Elias Tandel Barrionovo Date: Mon, 22 Oct 2018 11:28:26 -0300 Subject: [PATCH 22/44] Improve dataloader doc Explicitly mention that loaded values must have the same order as the given keys --- docs/execution/dataloader.rst | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/execution/dataloader.rst b/docs/execution/dataloader.rst index 522af161..3cd36fcc 100644 --- a/docs/execution/dataloader.rst +++ b/docs/execution/dataloader.rst @@ -25,8 +25,8 @@ Create loaders by providing a batch loading function. return Promise.resolve([get_user(id=key) for key in keys]) -A batch loading function accepts an list of keys, and returns a ``Promise`` -which resolves to an list of ``values``. +A batch loading function accepts a list of keys, and returns a ``Promise`` +which resolves to a list of ``values``. Then load individual values from the loader. ``DataLoader`` will coalesce all individual loads which occur within a single frame of execution (executed once @@ -34,7 +34,6 @@ the wrapping promise is resolved) and then call your batch function with all requested keys. - .. code:: python user_loader = UserLoader() @@ -47,6 +46,19 @@ requested keys. A naive application may have issued *four* round-trips to a backend for the required information, but with ``DataLoader`` this application will make at most *two*. +Note that loaded values are one-to-one with the keys and must have the same +order. This means that if you load all values from a single query, you must +make sure that you then order the query result for the results to match the keys: + + +.. code:: python + + class UserLoader(DataLoader): + def batch_load_fn(self, keys): + users = {user.id: user for user in User.objects.filter(id__in=keys)} + return Promise.resolve([users.get(user_id) for user_id in keys]) + + ``DataLoader`` allows you to decouple unrelated parts of your application without sacrificing the performance of batch data-loading. While the loader presents an API that loads individual values, all concurrent requests will be coalesced From 49a41060a6c7cc7634ec7e8071649c76371eabd7 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 26 Oct 2018 19:30:37 +0200 Subject: [PATCH 23/44] Improved documentation showcasing sponsors --- BACKERS.md | 12 +-- README.md | 87 ++++++++++++++++++++ README.rst | 233 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 317 insertions(+), 15 deletions(-) diff --git a/BACKERS.md b/BACKERS.md index 4aa2214f..a1264060 100644 --- a/BACKERS.md +++ b/BACKERS.md @@ -25,7 +25,7 @@ Graphene is an MIT-licensed open source project. It's an independent project wit - + @@ -40,7 +40,7 @@ Graphene is an MIT-licensed open source project. It's an independent project wit - + @@ -56,7 +56,7 @@ Graphene is an MIT-licensed open source project. It's an independent project wit - + @@ -72,7 +72,7 @@ Graphene is an MIT-licensed open source project. It's an independent project wit - + @@ -86,12 +86,12 @@ Graphene is an MIT-licensed open source project. It's an independent project wit - [Lee Benson](https://github.com/leebenson) -- [Become a Patron](https://www.patreon.com/bePatron?c=2046477) +- [Become a Patron](https://www.patreon.com/join/syrusakbary)

Backers via Patreon

-- [Become a Patron](https://www.patreon.com/bePatron?c=2046477) +- [Become a Patron](https://www.patreon.com/join/syrusakbary) diff --git a/README.md b/README.md index a5a64f09..db0c068c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,93 @@ # ![Graphene Logo](http://graphene-python.org/favicon.png) [Graphene](http://graphene-python.org) [![Build Status](https://travis-ci.org/graphql-python/graphene.svg?branch=master)](https://travis-ci.org/graphql-python/graphene) [![PyPI version](https://badge.fury.io/py/graphene.svg)](https://badge.fury.io/py/graphene) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene?branch=master) +

Supporting Graphene Python

+ +Graphene is an MIT-licensed open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/graphql-python/graphene/blob/master/BACKERS.md). If you'd like to join them, please consider: + +- [Become a backer or sponsor on Patreon](https://www.patreon.com/syrusakbary). +- [One-time donation via PayPal.](https://graphene-python.org/support-graphene/) + +

+ + + +

Platinum via Patreon

+ + + + + + + + +
+ + + +
+ +

Gold via Patreon

+ + + + + + + + +
+ + + +
+ + +

Silver via Patreon

+ + + + + + + + +
+ + + +
+ + +

Bronze via Patreon

+ + + + + + + + +
+ + + +
+ + +--- + +## Introduction + [Graphene](http://graphene-python.org) is a Python library for building GraphQL schemas/types fast and easily. - **Easy to use:** Graphene helps you use GraphQL in Python without effort. diff --git a/README.rst b/README.rst index 817e1c01..cb8580e8 100644 --- a/README.rst +++ b/README.rst @@ -7,6 +7,228 @@ to see how you can help ❀️ |Graphene Logo| `Graphene `__ |Build Status| |PyPI version| |Coverage Status| ========================================================================================================= +.. raw:: html + +

+ +Supporting Graphene Python + +.. raw:: html + +

+ +Graphene is an MIT-licensed open source project. It's an independent +project with its ongoing development made possible entirely thanks to +the support by these awesome +`backers `__. +If you'd like to join them, please consider: + +- `Become a backer or sponsor on + Patreon `__. +- `One-time donation via + PayPal. `__ + + + +.. raw:: html + +

+ +Platinum via Patreon + +.. raw:: html + +

+ +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +:: + + + + + +.. raw:: html + + + +.. raw:: html + +
+ + + +
+ +.. raw:: html + +

+ +Gold via Patreon + +.. raw:: html + +

+ +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +:: + + + + + +.. raw:: html + + + +.. raw:: html + +
+ + + +
+ +.. raw:: html + + + +.. raw:: html + +

+ +Silver via Patreon + +.. raw:: html + +

+ +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +:: + + + + + +.. raw:: html + + + +.. raw:: html + +
+ + + +
+ +.. raw:: html + + + +.. raw:: html + +

+ +Bronze via Patreon + +.. raw:: html + +

+ +.. raw:: html + + + +.. raw:: html + + + +.. raw:: html + + + +:: + + + + + +.. raw:: html + + + +.. raw:: html + +
+ + + +
+ +.. raw:: html + + + +-------------- + +Introduction +------------ + `Graphene `__ is a Python library for building GraphQL schemas/types fast and easily. @@ -152,18 +374,11 @@ Documentation The documentation is generated using the excellent `Sphinx `__ and a custom theme. -The documentation dependencies are installed by running: +An HTML version of the documentation is produced by running: .. code:: sh - cd docs - pip install -r requirements.txt - -Then to produce a HTML version of the documentation: - -.. code:: sh - - make html + make docs .. |Graphene Logo| image:: http://graphene-python.org/favicon.png .. |Build Status| image:: https://travis-ci.org/graphql-python/graphene.svg?branch=master From a0be081fc0c20746b166a5c10a7668c71723ff24 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 26 Oct 2018 19:34:58 +0200 Subject: [PATCH 24/44] Improved README --- README.md | 16 ---------------- README.rst | 44 -------------------------------------------- 2 files changed, 60 deletions(-) diff --git a/README.md b/README.md index db0c068c..cfea3135 100644 --- a/README.md +++ b/README.md @@ -71,22 +71,6 @@ Graphene is an MIT-licensed open source project. It's an independent project wit -

Bronze via Patreon

- - - - - - - - -
- - - -
- - --- ## Introduction diff --git a/README.rst b/README.rst index cb8580e8..a32b9321 100644 --- a/README.rst +++ b/README.rst @@ -180,50 +180,6 @@ Silver via Patreon -.. raw:: html - -

- -Bronze via Patreon - -.. raw:: html - -

- -.. raw:: html - - - -.. raw:: html - - - -.. raw:: html - - - -:: - - - - - -.. raw:: html - - - -.. raw:: html - -
- - - -
- -.. raw:: html - - - -------------- Introduction From e07e89d2e27c6e284c720cc545f21f5e8dd0312e Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Fri, 26 Oct 2018 19:34:58 +0200 Subject: [PATCH 25/44] Improved README --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index cfea3135..6f085d01 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,6 @@ Graphene is an MIT-licensed open source project. It's an independent project wit - [Become a backer or sponsor on Patreon](https://www.patreon.com/syrusakbary). - [One-time donation via PayPal.](https://graphene-python.org/support-graphene/) -

-