From b6bd31ca975f9f1f8b046a56d4914b16921874c9 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 14 Jul 2019 15:20:44 +0200 Subject: [PATCH] Create v2 that is compatible with Core v2 (#21) * Create v2 that is compatible with Core v2 * Bump version to 2.0 * Support GraphQL-core >= 2.0, < 3 (use the newer signature for resolvers) * Drop support for Python 3.3 * Drop redundant README.rst * Clean-up and PEP-8 * Use .flake8 instead of setup.cfg * Use "setup.py test" for testing, as explained in README * Make this run with pytest 4. This will be changed in v3, where we use pytest 5 and run it directly. * Only check relevant files with flake8 * Minor fixes in the README --- .flake8 | 4 + .gitignore | 39 +-- .travis.yml | 16 +- README.md | 54 ++-- README.rst | 272 ------------------ graphql_relay/__init__.py | 3 +- graphql_relay/connection/arrayconnection.py | 38 ++- graphql_relay/connection/connection.py | 4 +- .../connection/tests/test_arrayconnection.py | 60 ++-- .../connection/tests/test_connection.py | 19 +- graphql_relay/mutation/mutation.py | 20 +- graphql_relay/mutation/tests/__init__.py | 0 graphql_relay/mutation/tests/test_mutation.py | 181 ++++++------ graphql_relay/node/node.py | 27 +- graphql_relay/node/plural.py | 10 +- graphql_relay/node/tests/test_global.py | 8 +- graphql_relay/node/tests/test_node.py | 80 +++--- graphql_relay/node/tests/test_plural.py | 16 +- graphql_relay/tests/test_utils.py | 7 +- setup.cfg | 3 - setup.py | 34 +-- tests/starwars/data.py | 14 +- tests/starwars/schema.py | 40 ++- tests/starwars/test_connections.py | 1 - tests/starwars/test_mutations.py | 5 +- tests/starwars/test_objectidentification.py | 1 - tox.ini | 23 +- 27 files changed, 349 insertions(+), 630 deletions(-) create mode 100644 .flake8 delete mode 100644 README.rst create mode 100644 graphql_relay/mutation/tests/__init__.py delete mode 100644 setup.cfg diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..12b6f7d --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +ignore = E203,W503,E704 +exclude = .git,.mypy_cache,.pytest_cache,.tox,.venv,__pycache__,build,dist,docs +max-line-length = 88 diff --git a/.gitignore b/.gitignore index 37744de..f55a50f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,7 @@ -# Created by https://www.gitignore.io - -### Python ### -# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] -# C extensions -*.so - -# Distribution / packaging .Python -env/ build/ develop-eggs/ dist/ @@ -22,40 +13,50 @@ lib64/ parts/ sdist/ var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ *.egg-info/ .installed.cfg *.egg +MANIFEST -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec -# Installer logs pip-log.txt pip-delete-this-directory.txt -# Unit test / coverage reports htmlcov/ .tox/ +.nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml -*,cover +*.cover +.pytest_cache/ -# Translations *.mo *.pot -# Django stuff: *.log -# Sphinx documentation docs/_build/ -# PyBuilder target/ +.python-version + +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +.mypy_cache/ + +.idea/ diff --git a/.travis.yml b/.travis.yml index 6bd80c7..26f0df3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,17 +2,17 @@ language: python dist: xenial python: - - 2.7 - - 3.5 - - 3.6 - - 3.7 - - pypy3 + - "2.7" + - "3.5" + - "3.6" + - "3.7" + - "pypy3" install: - - pip install pytest pytest-cov flake8 - pip install . + - pip install "flake8>=3.7,<4" script: - - py.test --cov=graphql_relay -# - flake8 + - python setup.py test -a "--cov=graphql_relay" + - flake8 setup.py src tests after_success: - pip install coveralls - coveralls diff --git a/README.md b/README.md index 9a2aea8..b12dcf3 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ that documentation and the corresponding tests in this library together. Install Relay Library for GraphQL Python ```sh -pip install graphql-core --pre # Last version of graphql-core +pip install "graphql-core>=2,<3" # use version 2.x of graphql-core pip install graphql-relay ``` @@ -54,13 +54,13 @@ returning those types. they return a connection type. - `connection_definitions` returns a `connection_type` and its associated `edgeType`, given a name and a node type. - - `connection_from_list` is a helper method that takes an array and the + - `connection_from_list` is a helper method that takes a list and the arguments from `connection_args`, does pagination and filtering, and returns an object in the shape expected by a `connection_type`'s `resolver` function. - `connection_from_promised_list` is similar to `connection_from_list`, but it takes a promise that resolves to an array, and returns a promise that resolves to the expected shape by `connection_type`. - - `cursor_for_object_in_connection` is a helper method that takes an array and a + - `cursor_for_object_in_connection` is a helper method that takes a list and a member object, and returns a cursor for use in the mutation payload. An example usage of these methods from the [test schema](tests/starwars/schema.py): @@ -69,8 +69,8 @@ An example usage of these methods from the [test schema](tests/starwars/schema.p ship_edge, ship_connection = connection_definitions('Ship', shipType) factionType = GraphQLObjectType( - name= 'Faction', - description= 'A faction in the Star Wars saga', + name='Faction', + description='A faction in the Star Wars saga', fields= lambda: { 'id': global_id_field('Faction'), 'name': GraphQLField( @@ -78,23 +78,22 @@ factionType = GraphQLObjectType( description='The name of the faction.', ), 'ships': GraphQLField( - shipConnection, - description= 'The ships used by the faction.', - args= connection_args, - resolver= lambda faction, args, *_: connection_from_list( - map(getShip, faction.ships), - args + ship_connection, + description='The ships used by the faction.', + args=connection_args, + resolver=lambda faction, _info, **args: connection_from_list( + [getShip(ship) for ship in faction.ships], args ), ) }, - interfaces= [node_interface] + interfaces=[node_interface] ) ``` This shows adding a `ships` field to the `Faction` object that is a connection. It uses `connection_definitions({name: 'Ship', nodeType: shipType})` to create the connection type, adds `connection_args` as arguments on this function, and -then implements the resolver function by passing the array of ships and the +then implements the resolver function by passing the list of ships and the arguments to `connection_from_list`. ### Object Identification @@ -108,7 +107,7 @@ this, it takes a function to resolve an ID to an object, and to determine the type of a given object. - `to_global_id` takes a type name and an ID specific to that type name, and returns a "global ID" that is unique among all types. - - `from_global_id` takes the "global ID" created by `toGlobalID`, and retuns + - `from_global_id` takes the "global ID" created by `to_global_id`, and returns the type name and ID used to create it. - `global_id_field` creates the configuration for an `id` field on a node. - `plural_identifying_root_field` creates a field that accepts a list of @@ -118,17 +117,16 @@ objects. An example usage of these methods from the [test schema](tests/starwars/schema.py): ```python -def get_node(global_id, context, info): - resolvedGlobalId = from_global_id(global_id) - _type, _id = resolvedGlobalId.type, resolvedGlobalId.id - if _type == 'Faction': - return getFaction(_id) - elif _type == 'Ship': - return getShip(_id) +def get_node(global_id, _info): + type_, id_ = from_global_id(global_id) + if type_ == 'Faction': + return getFaction(id_) + elif type_ == 'Ship': + return getShip(id_) else: return None -def get_node_type(obj, context, info): +def get_node_type(obj, _info): if isinstance(obj, Faction): return factionType else: @@ -177,11 +175,9 @@ class IntroduceShipMutation(object): def __init__(self, shipId, factionId, clientMutationId=None): self.shipId = shipId self.factionId = factionId - self.clientMutationId = None + self.clientMutationId = clientMutationId -def mutate_and_get_payload(data, *_): - shipName = data.get('shipName') - factionId = data.get('factionId') +def mutate_and_get_payload(_info, shipName, factionId, **_input): newShip = createShip(shipName, factionId) return IntroduceShipMutation( shipId=newShip.id, @@ -201,11 +197,11 @@ shipMutation = mutation_with_client_mutation_id( output_fields= { 'ship': GraphQLField( shipType, - resolver= lambda payload, *_: getShip(payload.shipId) + resolver=lambda payload, _info: getShip(payload.shipId) ), 'faction': GraphQLField( factionType, - resolver= lambda payload, *_: getFaction(payload.factionId) + resolver=lambda payload, _info: getFaction(payload.factionId) ) }, mutate_and_get_payload=mutate_and_get_payload @@ -213,7 +209,7 @@ shipMutation = mutation_with_client_mutation_id( mutationType = GraphQLObjectType( 'Mutation', - fields= lambda: { + fields=lambda: { 'introduceShip': shipMutation } ) diff --git a/README.rst b/README.rst deleted file mode 100644 index ff59233..0000000 --- a/README.rst +++ /dev/null @@ -1,272 +0,0 @@ -Relay Library for GraphQL Python -================================ - -This is a library to allow the easy creation of Relay-compliant servers -using the `GraphQL -Python `__ reference -implementation of a GraphQL server. - -Note: The code is a **exact** port of the original `graphql-relay js -implementation `__ from -Facebook - -|PyPI version| |Build Status| |Coverage Status| - -Getting Started ---------------- - -A basic understanding of GraphQL and of the GraphQL Python -implementation is needed to provide context for this library. - -An overview of GraphQL in general is available in the -`README `__ -for the `Specification for -GraphQL `__. - -This library is designed to work with the the `GraphQL -Python `__ reference -implementation of a GraphQL server. - -An overview of the functionality that a Relay-compliant GraphQL server -should provide is in the `GraphQL Relay -Specification `__ -on the `Relay website `__. That -overview describes a simple set of examples that exist as -`tests `__ in this repository. A good way to get started with -this repository is to walk through that documentation and the -corresponding tests in this library together. - -Using Relay Library for GraphQL Python (graphql-core) ------------------------------------------------------ - -Install Relay Library for GraphQL Python - -.. code:: sh - - pip install graphql-core --pre # Last version of graphql-core - pip install graphql-relay - -When building a schema for -`GraphQL `__, the -provided library functions can be used to simplify the creation of Relay -patterns. - -Connections -~~~~~~~~~~~ - -Helper functions are provided for both building the GraphQL types for -connections and for implementing the ``resolver`` method for fields -returning those types. - -- ``connection_args`` returns the arguments that fields should provide - when they return a connection type. -- ``connection_definitions`` returns a ``connection_type`` and its - associated ``edgeType``, given a name and a node type. -- ``connection_from_list`` is a helper method that takes an array and - the arguments from ``connection_args``, does pagination and - filtering, and returns an object in the shape expected by a - ``connection_type``'s ``resolver`` function. -- ``connection_from_promised_list`` is similar to - ``connection_from_list``, but it takes a promise that resolves to an - array, and returns a promise that resolves to the expected shape by - ``connection_type``. -- ``cursor_for_object_in_connection`` is a helper method that takes an - array and a member object, and returns a cursor for use in the - mutation payload. - -An example usage of these methods from the `test -schema `__: - -.. code:: python - - ship_edge, ship_connection = connection_definitions('Ship', shipType) - - factionType = GraphQLObjectType( - name= 'Faction', - description= 'A faction in the Star Wars saga', - fields= lambda: { - 'id': global_id_field('Faction'), - 'name': GraphQLField( - GraphQLString, - description='The name of the faction.', - ), - 'ships': GraphQLField( - shipConnection, - description= 'The ships used by the faction.', - args= connection_args, - resolver= lambda faction, args, *_: connection_from_list( - map(getShip, faction.ships), - args - ), - ) - }, - interfaces= [node_interface] - ) - -This shows adding a ``ships`` field to the ``Faction`` object that is a -connection. It uses -``connection_definitions({name: 'Ship', nodeType: shipType})`` to create -the connection type, adds ``connection_args`` as arguments on this -function, and then implements the resolver function by passing the array -of ships and the arguments to ``connection_from_list``. - -Object Identification -~~~~~~~~~~~~~~~~~~~~~ - -Helper functions are provided for both building the GraphQL types for -nodes and for implementing global IDs around local IDs. - -- ``node_definitions`` returns the ``Node`` interface that objects can - implement, and returns the ``node`` root field to include on the - query type. To implement this, it takes a function to resolve an ID - to an object, and to determine the type of a given object. -- ``to_global_id`` takes a type name and an ID specific to that type - name, and returns a "global ID" that is unique among all types. -- ``from_global_id`` takes the "global ID" created by ``toGlobalID``, - and retuns the type name and ID used to create it. -- ``global_id_field`` creates the configuration for an ``id`` field on - a node. -- ``plural_identifying_root_field`` creates a field that accepts a list - of non-ID identifiers (like a username) and maps then to their - corresponding objects. - -An example usage of these methods from the `test -schema `__: - -.. code:: python - - def get_node(global_id, context, info): - resolvedGlobalId = from_global_id(global_id) - _type, _id = resolvedGlobalId.type, resolvedGlobalId.id - if _type == 'Faction': - return getFaction(_id) - elif _type == 'Ship': - return getShip(_id) - else: - return None - - def get_node_type(obj, context, info): - if isinstance(obj, Faction): - return factionType - else: - return shipType - - node_interface, node_field = node_definitions(get_node, get_node_type) - - factionType = GraphQLObjectType( - name= 'Faction', - description= 'A faction in the Star Wars saga', - fields= lambda: { - 'id': global_id_field('Faction'), - }, - interfaces= [node_interface] - ) - - queryType = GraphQLObjectType( - name= 'Query', - fields= lambda: { - 'node': node_field - } - ) - -This uses ``node_definitions`` to construct the ``Node`` interface and -the ``node`` field; it uses ``from_global_id`` to resolve the IDs passed -in in the implementation of the function mapping ID to object. It then -uses the ``global_id_field`` method to create the ``id`` field on -``Faction``, which also ensures implements the ``node_interface``. -Finally, it adds the ``node`` field to the query type, using the -``node_field`` returned by ``node_definitions``. - -Mutations -~~~~~~~~~ - -A helper function is provided for building mutations with single inputs -and client mutation IDs. - -- ``mutation_with_client_mutation_id`` takes a name, input fields, - output fields, and a mutation method to map from the input fields to - the output fields, performing the mutation along the way. It then - creates and returns a field configuration that can be used as a - top-level field on the mutation type. - -An example usage of these methods from the `test -schema `__: - -.. code:: python - - class IntroduceShipMutation(object): - def __init__(self, shipId, factionId, clientMutationId=None): - self.shipId = shipId - self.factionId = factionId - self.clientMutationId = None - - def mutate_and_get_payload(data, *_): - shipName = data.get('shipName') - factionId = data.get('factionId') - newShip = createShip(shipName, factionId) - return IntroduceShipMutation( - shipId=newShip.id, - factionId=factionId, - ) - - shipMutation = mutation_with_client_mutation_id( - 'IntroduceShip', - input_fields={ - 'shipName': GraphQLField( - GraphQLNonNull(GraphQLString) - ), - 'factionId': GraphQLField( - GraphQLNonNull(GraphQLID) - ) - }, - output_fields= { - 'ship': GraphQLField( - shipType, - resolver= lambda payload, *_: getShip(payload.shipId) - ), - 'faction': GraphQLField( - factionType, - resolver= lambda payload, *_: getFaction(payload.factionId) - ) - }, - mutate_and_get_payload=mutate_and_get_payload - ) - - mutationType = GraphQLObjectType( - 'Mutation', - fields= lambda: { - 'introduceShip': shipMutation - } - ) - -This code creates a mutation named ``IntroduceShip``, which takes a -faction ID and a ship name as input. It outputs the ``Faction`` and the -``Ship`` in question. ``mutate_and_get_payload`` then gets an object -with a property for each input field, performs the mutation by -constructing the new ship, then returns an object that will be resolved -by the output fields. - -Our mutation type then creates the ``introduceShip`` field using the -return value of ``mutation_with_client_mutation_id``. - -Contributing ------------- - -After cloning this repo, ensure dependencies are installed by running: - -.. code:: sh - - python setup.py install - -After developing, the full test suite can be evaluated by running: - -.. code:: sh - - python setup.py test # Use --pytest-args="-v -s" for verbose mode - -.. |PyPI version| image:: https://badge.fury.io/py/graphql-relay.svg - :target: https://badge.fury.io/py/graphql-relay -.. |Build Status| image:: https://travis-ci.org/graphql-python/graphql-relay-py.svg?branch=master - :target: https://travis-ci.org/graphql-python/graphql-relay-py -.. |Coverage Status| image:: https://coveralls.io/repos/graphql-python/graphql-relay-py/badge.svg?branch=master&service=github - :target: https://coveralls.io/github/graphql-python/graphql-relay-py?branch=master diff --git a/graphql_relay/__init__.py b/graphql_relay/__init__.py index 7fc6189..39ee980 100644 --- a/graphql_relay/__init__.py +++ b/graphql_relay/__init__.py @@ -21,7 +21,8 @@ # Helpers for creating connection types in the schema 'connection_args', 'connection_definitions', # Helpers for creating connections from arrays - 'connection_from_list', 'connection_from_promised_list', 'cursor_for_object_in_connection', + 'connection_from_list', 'connection_from_promised_list', + 'cursor_for_object_in_connection', # Helper for creating node definitions 'node_definitions', # Utilities for creating global IDs in systems that don't have them diff --git a/graphql_relay/connection/arrayconnection.py b/graphql_relay/connection/arrayconnection.py index a426db0..ea951f5 100644 --- a/graphql_relay/connection/arrayconnection.py +++ b/graphql_relay/connection/arrayconnection.py @@ -1,15 +1,13 @@ -from promise import Promise - from ..utils import base64, unbase64, is_str from .connectiontypes import Connection, PageInfo, Edge def connection_from_list(data, args=None, **kwargs): - ''' + """ A simple function that accepts an array and connection arguments, and returns a connection object for use in GraphQL. It uses array offsets as pagination, so pagination will only work if the array is static. - ''' + """ _len = len(data) return connection_from_list_slice( data, @@ -22,24 +20,24 @@ def connection_from_list(data, args=None, **kwargs): def connection_from_promised_list(data_promise, args=None, **kwargs): - ''' + """ A version of `connectionFromArray` that takes a promised array, and returns a promised connection. - ''' + """ return data_promise.then(lambda data: connection_from_list(data, args, **kwargs)) def connection_from_list_slice(list_slice, args=None, connection_type=None, edge_type=None, pageinfo_type=None, slice_start=0, list_length=0, list_slice_length=None): - ''' + """ Given a slice (subset) of an array, returns a connection object for use in GraphQL. This function is similar to `connectionFromArray`, but is intended for use cases where you know the cardinality of the connection, consider it too large to materialize the entire array, and instead wish pass in a slice of the total result large enough to cover the range specified in `args`. - ''' + """ connection_type = connection_type or Connection edge_type = edge_type or Edge pageinfo_type = pageinfo_type or PageInfo @@ -90,7 +88,6 @@ def connection_from_list_slice(list_slice, args=None, connection_type=None, for i, node in enumerate(_slice) ] - first_edge_cursor = edges[0].cursor if edges else None last_edge_cursor = edges[-1].cursor if edges else None lower_bound = after_offset + 1 if after else 0 @@ -111,30 +108,31 @@ def connection_from_list_slice(list_slice, args=None, connection_type=None, def connection_from_promised_list_slice(data_promise, args=None, **kwargs): - return data_promise.then(lambda data: connection_from_list_slice(data, args, **kwargs)) + return data_promise.then( + lambda data: connection_from_list_slice(data, args, **kwargs)) def offset_to_cursor(offset): - ''' + """ Creates the cursor string from an offset. - ''' + """ return base64(PREFIX + str(offset)) def cursor_to_offset(cursor): - ''' + """ Rederives the offset from the cursor string. - ''' + """ try: return int(unbase64(cursor)[len(PREFIX):]) - except: + except Exception: return None def cursor_for_object_in_connection(data, _object): - ''' + """ Return the cursor associated with an object in an array. - ''' + """ if _object not in data: return None @@ -143,16 +141,16 @@ def cursor_for_object_in_connection(data, _object): def get_offset_with_default(cursor=None, default_offset=0): - ''' + """ Given an optional cursor and a default offset, returns the offset to use; if the cursor contains a valid offset, that will be used, otherwise it will be the default. - ''' + """ if not is_str(cursor): return default_offset offset = cursor_to_offset(cursor) try: return int(offset) - except: + except Exception: return default_offset diff --git a/graphql_relay/connection/connection.py b/graphql_relay/connection/connection.py index 4fd5c49..3ae6678 100644 --- a/graphql_relay/connection/connection.py +++ b/graphql_relay/connection/connection.py @@ -21,7 +21,9 @@ )) -def connection_definitions(name, node_type, resolve_node=None, resolve_cursor=None, edge_fields=None, connection_fields=None): +def connection_definitions( + name, node_type, resolve_node=None, resolve_cursor=None, + edge_fields=None, connection_fields=None): edge_fields = edge_fields or OrderedDict() connection_fields = connection_fields or OrderedDict() edge_type = GraphQLObjectType( diff --git a/graphql_relay/connection/tests/test_arrayconnection.py b/graphql_relay/connection/tests/test_arrayconnection.py index 102ba63..810558d 100644 --- a/graphql_relay/connection/tests/test_arrayconnection.py +++ b/graphql_relay/connection/tests/test_arrayconnection.py @@ -1,5 +1,5 @@ from promise import Promise -from pytest import raises + from ..arrayconnection import ( connection_from_list, connection_from_list_slice, @@ -266,10 +266,9 @@ def test_pagination_respects_longlast_before(): def test_first_after_before_few(): - c = connection_from_list(letters, dict(first=2, - after='YXJyYXljb25uZWN0aW9uOjA=', - before='YXJyYXljb25uZWN0aW9uOjQ=', - )) + c = connection_from_list(letters, dict( + first=2, after='YXJyYXljb25uZWN0aW9uOjA=', before='YXJyYXljb25uZWN0aW9uOjQ=', + )) expected = { 'edges': [ { @@ -292,10 +291,9 @@ def test_first_after_before_few(): def test_first_after_before_many(): - c = connection_from_list(letters, dict(first=4, - after='YXJyYXljb25uZWN0aW9uOjA=', - before='YXJyYXljb25uZWN0aW9uOjQ=', - )) + c = connection_from_list(letters, dict( + first=4, after='YXJyYXljb25uZWN0aW9uOjA=', before='YXJyYXljb25uZWN0aW9uOjQ=', + )) expected = { 'edges': [ { @@ -322,10 +320,9 @@ def test_first_after_before_many(): def test_first_after_before_exact(): - c = connection_from_list(letters, dict(first=3, - after='YXJyYXljb25uZWN0aW9uOjA=', - before='YXJyYXljb25uZWN0aW9uOjQ=', - )) + c = connection_from_list(letters, dict( + first=3, after='YXJyYXljb25uZWN0aW9uOjA=', before='YXJyYXljb25uZWN0aW9uOjQ=', + )) expected = { 'edges': [ { @@ -352,10 +349,9 @@ def test_first_after_before_exact(): def test_last_after_before_few(): - c = connection_from_list(letters, dict(last=2, - after='YXJyYXljb25uZWN0aW9uOjA=', - before='YXJyYXljb25uZWN0aW9uOjQ=', - )) + c = connection_from_list(letters, dict( + last=2, after='YXJyYXljb25uZWN0aW9uOjA=', before='YXJyYXljb25uZWN0aW9uOjQ=', + )) expected = { 'edges': [ { @@ -378,10 +374,9 @@ def test_last_after_before_few(): def test_last_after_before_many(): - c = connection_from_list(letters, dict(last=4, - after='YXJyYXljb25uZWN0aW9uOjA=', - before='YXJyYXljb25uZWN0aW9uOjQ=', - )) + c = connection_from_list(letters, dict( + last=4, after='YXJyYXljb25uZWN0aW9uOjA=', before='YXJyYXljb25uZWN0aW9uOjQ=', + )) expected = { 'edges': [ { @@ -408,10 +403,9 @@ def test_last_after_before_many(): def test_last_after_before_exact(): - c = connection_from_list(letters, dict(last=3, - after='YXJyYXljb25uZWN0aW9uOjA=', - before='YXJyYXljb25uZWN0aW9uOjQ=', - )) + c = connection_from_list(letters, dict( + last=3, after='YXJyYXljb25uZWN0aW9uOjA=', before='YXJyYXljb25uZWN0aW9uOjQ=', + )) expected = { 'edges': [ { @@ -489,8 +483,8 @@ def test_all_elements_invalid_cursors(): def test_all_elements_cursor_outside(): c = connection_from_list(letters, dict( - before='YXJyYXljb25uZWN0aW9uOjYK', - after='YXJyYXljb25uZWN0aW9uOi0xCg==')) + before='YXJyYXljb25uZWN0aW9uOjYK', after='YXJyYXljb25uZWN0aW9uOi0xCg==' + )) expected = { 'edges': [ { @@ -526,8 +520,8 @@ def test_all_elements_cursor_outside(): def test_no_elements_cursors_cross(): c = connection_from_list(letters, dict( - before='YXJyYXljb25uZWN0aW9uOjI=', - after='YXJyYXljb25uZWN0aW9uOjQ=')) + before='YXJyYXljb25uZWN0aW9uOjI=', after='YXJyYXljb25uZWN0aW9uOjQ=' + )) expected = { 'edges': [ ], @@ -542,13 +536,13 @@ def test_no_elements_cursors_cross(): def test_cursor_for_object_in_connection_member_object(): - letterBCursor = cursor_for_object_in_connection(letters, 'B') - assert letterBCursor == 'YXJyYXljb25uZWN0aW9uOjE=' + letter_b_cursor = cursor_for_object_in_connection(letters, 'B') + assert letter_b_cursor == 'YXJyYXljb25uZWN0aW9uOjE=' def test_cursor_for_object_in_connection_non_member_object(): - letterBCursor = cursor_for_object_in_connection(letters, 'F') - assert letterBCursor is None + letter_b_cursor = cursor_for_object_in_connection(letters, 'F') + assert letter_b_cursor is None def test_promised_list_returns_all_elements_without_filters(): diff --git a/graphql_relay/connection/tests/test_connection.py b/graphql_relay/connection/tests/test_connection.py index b0cc871..f571cfe 100644 --- a/graphql_relay/connection/tests/test_connection.py +++ b/graphql_relay/connection/tests/test_connection.py @@ -1,17 +1,12 @@ from collections import namedtuple -from pytest import raises + from graphql import graphql from graphql.type import ( GraphQLSchema, GraphQLObjectType, GraphQLField, - GraphQLArgument, - GraphQLList, - GraphQLNonNull, GraphQLInt, GraphQLString, - GraphQLBoolean, - GraphQLID, ) from ..arrayconnection import connection_from_list @@ -37,8 +32,8 @@ 'friends': GraphQLField( friendConnection, args=connection_args, - resolver=lambda user, args, * - _: connection_from_list(user.friends, args), + resolver=lambda user, _info, **args: + connection_from_list(user.friends, args), ), }, ) @@ -46,17 +41,17 @@ friendEdge, friendConnection = connection_definitions( 'Friend', userType, - resolve_node=lambda edge, *_: allUsers[edge.node], + resolve_node=lambda edge, _info: allUsers[edge.node], edge_fields=lambda: { 'friendshipTime': GraphQLField( GraphQLString, - resolver=lambda *_: 'Yesterday' + resolver=lambda _user, _info: 'Yesterday' ), }, connection_fields=lambda: { 'totalCount': GraphQLField( GraphQLInt, - resolver=lambda *_: len(allUsers) - 1 + resolver=lambda _user, _info: len(allUsers) - 1 ), } ) @@ -66,7 +61,7 @@ fields=lambda: { 'user': GraphQLField( userType, - resolver=lambda *_: allUsers[0] + resolver=lambda _root, _info: allUsers[0] ), } ) diff --git a/graphql_relay/mutation/mutation.py b/graphql_relay/mutation/mutation.py index 8aae02f..f9fcc5c 100644 --- a/graphql_relay/mutation/mutation.py +++ b/graphql_relay/mutation/mutation.py @@ -1,5 +1,7 @@ from collections import OrderedDict + from promise import Promise + from graphql.type import ( GraphQLArgument, GraphQLInputObjectField, @@ -10,10 +12,12 @@ GraphQLField, ) from graphql.error import GraphQLError + from ..utils import resolve_maybe_thunk -def mutation_with_client_mutation_id(name, input_fields, output_fields, mutate_and_get_payload): +def mutation_with_client_mutation_id( + name, input_fields, output_fields, mutate_and_get_payload): augmented_input_fields = OrderedDict( resolve_maybe_thunk(input_fields), clientMutationId=GraphQLInputObjectField( @@ -36,17 +40,19 @@ def mutation_with_client_mutation_id(name, input_fields, output_fields, mutate_a fields=augmented_output_fields, ) - def resolver(__, args, *_): - input = args.get('input') + def resolver(_root, info, **args): + input_ = args.get('input') def on_resolve(payload): try: - payload.clientMutationId = input['clientMutationId'] - except: - raise GraphQLError('Cannot set clientMutationId in the payload object {}'.format(repr(payload))) + payload.clientMutationId = input_['clientMutationId'] + except Exception: + raise GraphQLError( + 'Cannot set clientMutationId in the payload object {}'.format( + repr(payload))) return payload - return Promise.resolve(mutate_and_get_payload(input, *_)).then(on_resolve) + return Promise.resolve(mutate_and_get_payload(info, **input_)).then(on_resolve) return GraphQLField( output_type, diff --git a/graphql_relay/mutation/tests/__init__.py b/graphql_relay/mutation/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graphql_relay/mutation/tests/test_mutation.py b/graphql_relay/mutation/tests/test_mutation.py index f2517be..f0d5aec 100644 --- a/graphql_relay/mutation/tests/test_mutation.py +++ b/graphql_relay/mutation/tests/test_mutation.py @@ -1,6 +1,5 @@ -from collections import OrderedDict - from promise import Promise + from graphql import graphql from graphql.type import ( GraphQLSchema, @@ -10,9 +9,7 @@ GraphQLInputObjectField ) -from graphql_relay.mutation.mutation import ( - mutation_with_client_mutation_id, -) +from ..mutation import mutation_with_client_mutation_id class Result(object): @@ -21,13 +18,14 @@ def __init__(self, result, clientMutationId=None): self.clientMutationId = clientMutationId self.result = result + simpleMutation = mutation_with_client_mutation_id( 'SimpleMutation', input_fields={}, output_fields={ 'result': GraphQLField(GraphQLInt) }, - mutate_and_get_payload=lambda *_: Result(result=1) + mutate_and_get_payload=lambda _info, **_input: Result(result=1) ) simpleMutationWithThunkFields = mutation_with_client_mutation_id( @@ -38,7 +36,7 @@ def __init__(self, result, clientMutationId=None): output_fields=lambda: { 'result': GraphQLField(GraphQLInt) }, - mutate_and_get_payload=lambda args, *_: Result(result=args.get('inputData')) + mutate_and_get_payload=lambda _info, **input_: Result(result=input_['inputData']) ) simplePromiseMutation = mutation_with_client_mutation_id( @@ -47,7 +45,7 @@ def __init__(self, result, clientMutationId=None): output_fields={ 'result': GraphQLField(GraphQLInt) }, - mutate_and_get_payload=lambda *_: Promise.resolve(Result(result=1)) + mutate_and_get_payload=lambda _info, **_input: Promise.resolve(Result(result=1)) ) simpleRootValueMutation = mutation_with_client_mutation_id( @@ -56,7 +54,7 @@ def __init__(self, result, clientMutationId=None): output_fields={ 'result': GraphQLField(GraphQLInt) }, - mutate_and_get_payload=lambda params, context, info: info.root_value + mutate_and_get_payload=lambda info, **_input: info.root_value ) mutation = GraphQLObjectType( @@ -110,7 +108,8 @@ def test_returns_the_same_client_mutation_id(): def test_supports_thunks_as_input_and_output_fields(): query = ''' mutation M { - simpleMutationWithThunkFields(input: {inputData: 1234, clientMutationId: "abc"}) { + simpleMutationWithThunkFields( + input: {inputData: 1234, clientMutationId: "abc"}) { result clientMutationId } @@ -162,7 +161,7 @@ def test_can_access_root_value(): 'clientMutationId': 'abc' } } - result = graphql(schema, query, root_value=Result(result=1)) + result = graphql(schema, query, root=Result(result=1)) assert not result.errors assert result.data == expected @@ -319,90 +318,90 @@ def test_contains_correct_field(): ''' expected = { '__schema': { - 'mutationType': { - 'fields': [ - { - 'name': 'simplePromiseMutation', - 'args': [ - { - 'name': 'input', - 'type': { - 'name': None, - 'kind': 'NON_NULL', - 'ofType': { - 'name': 'SimplePromiseMutationInput', - 'kind': 'INPUT_OBJECT' - } + 'mutationType': { + 'fields': [ + { + 'name': 'simplePromiseMutation', + 'args': [ + { + 'name': 'input', + 'type': { + 'name': None, + 'kind': 'NON_NULL', + 'ofType': { + 'name': 'SimplePromiseMutationInput', + 'kind': 'INPUT_OBJECT' + } + }, + } + ], + 'type': { + 'name': 'SimplePromiseMutationPayload', + 'kind': 'OBJECT', + } }, - } - ], - 'type': { - 'name': 'SimplePromiseMutationPayload', - 'kind': 'OBJECT', - } - }, - { - 'name': 'simpleRootValueMutation', - 'args': [ - { - 'name': 'input', - 'type': { - 'name': None, - 'kind': 'NON_NULL', - 'ofType': { - 'name': 'SimpleRootValueMutationInput', - 'kind': 'INPUT_OBJECT' - } + { + 'name': 'simpleRootValueMutation', + 'args': [ + { + 'name': 'input', + 'type': { + 'name': None, + 'kind': 'NON_NULL', + 'ofType': { + 'name': 'SimpleRootValueMutationInput', + 'kind': 'INPUT_OBJECT' + } + }, + } + ], + 'type': { + 'name': 'SimpleRootValueMutationPayload', + 'kind': 'OBJECT', + } }, - } - ], - 'type': { - 'name': 'SimpleRootValueMutationPayload', - 'kind': 'OBJECT', - } - }, - { - 'name': 'simpleMutation', - 'args': [ - { - 'name': 'input', - 'type': { - 'name': None, - 'kind': 'NON_NULL', - 'ofType': { - 'name': 'SimpleMutationInput', - 'kind': 'INPUT_OBJECT' - } + { + 'name': 'simpleMutation', + 'args': [ + { + 'name': 'input', + 'type': { + 'name': None, + 'kind': 'NON_NULL', + 'ofType': { + 'name': 'SimpleMutationInput', + 'kind': 'INPUT_OBJECT' + } + }, + } + ], + 'type': { + 'name': 'SimpleMutationPayload', + 'kind': 'OBJECT', + } }, - } - ], - 'type': { - 'name': 'SimpleMutationPayload', - 'kind': 'OBJECT', - } - }, - { - 'name': 'simpleMutationWithThunkFields', - 'args': [ - { - 'name': 'input', - 'type': { - 'name': None, - 'kind': 'NON_NULL', - 'ofType': { - 'name': 'SimpleMutationWithThunkFieldsInput', - 'kind': 'INPUT_OBJECT' - } + { + 'name': 'simpleMutationWithThunkFields', + 'args': [ + { + 'name': 'input', + 'type': { + 'name': None, + 'kind': 'NON_NULL', + 'ofType': { + 'name': 'SimpleMutationWithThunkFieldsInput', + 'kind': 'INPUT_OBJECT' + } + }, + } + ], + 'type': { + 'name': 'SimpleMutationWithThunkFieldsPayload', + 'kind': 'OBJECT', + } }, - } - ], - 'type': { - 'name': 'SimpleMutationWithThunkFieldsPayload', - 'kind': 'OBJECT', - } - }, - ] - } + ] + } } } result = graphql(schema, query) diff --git a/graphql_relay/node/node.py b/graphql_relay/node/node.py index 01ed462..410ad8a 100644 --- a/graphql_relay/node/node.py +++ b/graphql_relay/node/node.py @@ -1,5 +1,4 @@ from collections import OrderedDict -from graphql_relay.utils import base64, unbase64 from six import text_type @@ -11,9 +10,11 @@ GraphQLInterfaceType, ) +from ..utils import base64, unbase64 + def node_definitions(id_fetcher, type_resolver=None, id_resolver=None): - ''' + """ Given a function to map from an ID to an underlying object, and a function to map from an underlying object to the concrete GraphQLObjectType it corresponds to, constructs a `Node` interface that objects can implement, @@ -22,7 +23,7 @@ def node_definitions(id_fetcher, type_resolver=None, id_resolver=None): If the type_resolver is omitted, object resolution on the interface will be handled with the `isTypeOf` method on object types, as with any GraphQL interface without a provided `resolveType` method. - ''' + """ node_interface = GraphQLInterfaceType( 'Node', description='An object with an ID', @@ -44,41 +45,41 @@ def node_definitions(id_fetcher, type_resolver=None, id_resolver=None): description='The ID of an object' )), )), - resolver=lambda obj, args, *_: id_fetcher(args.get('id'), *_) + resolver=lambda _obj, info, id: id_fetcher(id, info) ) return node_interface, node_field def to_global_id(type, id): - ''' + """ Takes a type name and an ID specific to that type name, and returns a "global ID" that is unique among all types. - ''' + """ return base64(':'.join([type, text_type(id)])) def from_global_id(global_id): - ''' - Takes the "global ID" created by toGlobalID, and retuns the type name and ID + """ + Takes the "global ID" created by toGlobalID, and returns the type name and ID used to create it. - ''' + """ unbased_global_id = unbase64(global_id) _type, _id = unbased_global_id.split(':', 1) return _type, _id def global_id_field(type_name, id_fetcher=None): - ''' + """ Creates the configuration for an id field on a node, using `to_global_id` to construct the ID from the provided typename. The type-specific ID is fetcher by calling id_fetcher on the object, or if not provided, by accessing the `id` property on the object. - ''' + """ return GraphQLField( GraphQLNonNull(GraphQLID), description='The ID of an object', - resolver=lambda obj, args, context, info: to_global_id( + resolver=lambda obj, info, **args: to_global_id( type_name or info.parent_type.name, - id_fetcher(obj, context, info) if id_fetcher else obj.id + id_fetcher(obj, info) if id_fetcher else obj.id ) ) diff --git a/graphql_relay/node/plural.py b/graphql_relay/node/plural.py index 965bb47..716a1eb 100644 --- a/graphql_relay/node/plural.py +++ b/graphql_relay/node/plural.py @@ -1,5 +1,7 @@ from collections import OrderedDict + from promise import Promise + from graphql.type import ( GraphQLArgument, GraphQLList, @@ -8,7 +10,8 @@ ) -def plural_identifying_root_field(arg_name, input_type, output_type, resolve_single_input, description=None): +def plural_identifying_root_field( + arg_name, input_type, output_type, resolve_single_input, description=None): input_args = OrderedDict() input_args[arg_name] = GraphQLArgument( GraphQLNonNull( @@ -18,11 +21,10 @@ def plural_identifying_root_field(arg_name, input_type, output_type, resolve_sin ) ) - def resolver(obj, args, context, info): + def resolver(_obj, info, **args): inputs = args[arg_name] return Promise.all([ - resolve_single_input(input, context, info) - for input in inputs + resolve_single_input(info, input_) for input_ in inputs ]) return GraphQLField( diff --git a/graphql_relay/node/tests/test_global.py b/graphql_relay/node/tests/test_global.py index da99e5a..0fbc658 100644 --- a/graphql_relay/node/tests/test_global.py +++ b/graphql_relay/node/tests/test_global.py @@ -29,7 +29,7 @@ } -def get_node(global_id, *args): +def get_node(global_id, _info): _type, _id = from_global_id(global_id) if _type == 'User': return userData[_id] @@ -37,12 +37,13 @@ def get_node(global_id, *args): return photoData[_id] -def get_node_type(obj, context, info): +def get_node_type(obj, _info): if isinstance(obj, User): return userType else: return photoType + node_interface, node_field = node_definitions(get_node, get_node_type) userType = GraphQLObjectType( @@ -69,7 +70,8 @@ def get_node_type(obj, context, info): 'node': node_field, 'allObjects': GraphQLField( GraphQLList(node_interface), - resolver=lambda *_: [userData['1'], userData['2'], photoData['1'], photoData['2']] + resolver=lambda _root, _info: + [userData['1'], userData['2'], photoData['1'], photoData['2']] ) } ) diff --git a/graphql_relay/node/tests/test_node.py b/graphql_relay/node/tests/test_node.py index 2c6db8e..3f47eca 100644 --- a/graphql_relay/node/tests/test_node.py +++ b/graphql_relay/node/tests/test_node.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - from collections import namedtuple from graphql import graphql from graphql.type import ( @@ -29,7 +26,7 @@ } -def get_node(id, context, info): +def get_node(id, info): assert info.schema == schema if id in userData: return userData.get(id) @@ -37,12 +34,13 @@ def get_node(id, context, info): return photoData.get(id) -def get_node_type(obj, context, info): +def get_node_type(obj, _info): if obj.id in userData: return userType else: return photoType + node_interface, node_field = node_definitions(get_node, get_node_type) userType = GraphQLObjectType( @@ -254,20 +252,20 @@ def test_have_correct_node_interface(): ''' expected = { '__type': { - 'name': 'Node', - 'kind': 'INTERFACE', - 'fields': [ - { - 'name': 'id', - 'type': { - 'kind': 'NON_NULL', - 'ofType': { - 'name': 'ID', - 'kind': 'SCALAR' + 'name': 'Node', + 'kind': 'INTERFACE', + 'fields': [ + { + 'name': 'id', + 'type': { + 'kind': 'NON_NULL', + 'ofType': { + 'name': 'ID', + 'kind': 'SCALAR' + } + } } - } - } - ] + ] } } result = graphql(schema, query) @@ -303,29 +301,29 @@ def test_has_correct_node_root_field(): ''' expected = { '__schema': { - 'queryType': { - 'fields': [ - { - 'name': 'node', - 'type': { - 'name': 'Node', - 'kind': 'INTERFACE' - }, - 'args': [ - { - 'name': 'id', - 'type': { - 'kind': 'NON_NULL', - 'ofType': { - 'name': 'ID', - 'kind': 'SCALAR' - } + 'queryType': { + 'fields': [ + { + 'name': 'node', + 'type': { + 'name': 'Node', + 'kind': 'INTERFACE' + }, + 'args': [ + { + 'name': 'id', + 'type': { + 'kind': 'NON_NULL', + 'ofType': { + 'name': 'ID', + 'kind': 'SCALAR' + } + } + } + ] } - } ] - } - ] - } + } } } result = graphql(schema, query) @@ -334,7 +332,7 @@ def test_has_correct_node_root_field(): def test_to_global_id_converts_unicode_strings_correctly(): - my_unicode_id = u'ûñö' + my_unicode_id = u'\xfb\xf1\xf6' g_id = to_global_id('MyType', my_unicode_id) assert g_id == 'TXlUeXBlOsO7w7HDtg==' @@ -344,7 +342,7 @@ def test_to_global_id_converts_unicode_strings_correctly(): def test_from_global_id_converts_unicode_strings_correctly(): - my_unicode_id = u'ûñö' + my_unicode_id = u'\xfb\xf1\xf6' my_type, my_id = from_global_id('TXlUeXBlOsO7w7HDtg==') assert my_type == 'MyType' assert my_id == my_unicode_id diff --git a/graphql_relay/node/tests/test_plural.py b/graphql_relay/node/tests/test_plural.py index ad7489b..a26d1b6 100644 --- a/graphql_relay/node/tests/test_plural.py +++ b/graphql_relay/node/tests/test_plural.py @@ -1,17 +1,11 @@ from collections import namedtuple -from pytest import raises + from graphql import graphql from graphql.type import ( GraphQLSchema, GraphQLObjectType, GraphQLField, - GraphQLArgument, - GraphQLList, - GraphQLNonNull, - GraphQLInt, GraphQLString, - GraphQLBoolean, - GraphQLID, ) from graphql_relay.node.plural import plural_identifying_root_field @@ -34,7 +28,7 @@ description='Map from a username to the user', input_type=GraphQLString, output_type=userType, - resolve_single_input=lambda username, context, info: User( + resolve_single_input=lambda info, username: User( username=username, url='www.facebook.com/' + username + '?lang=' + info.root_value.lang ) @@ -42,9 +36,11 @@ } ) -class root_value: + +class RootValue: lang = 'en' + schema = GraphQLSchema(query=queryType) @@ -73,7 +69,7 @@ def test_allows_fetching(): }, ] } - result = graphql(schema, query, root_value=root_value) + result = graphql(schema, query, root=RootValue()) assert not result.errors assert result.data == expected diff --git a/graphql_relay/tests/test_utils.py b/graphql_relay/tests/test_utils.py index 67e365b..8897239 100644 --- a/graphql_relay/tests/test_utils.py +++ b/graphql_relay/tests/test_utils.py @@ -1,13 +1,10 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - import base64 from .. import utils def test_base64_encode_unicode_strings_correctly(): - my_unicode = u'ûñö' + my_unicode = u'\xfb\xf1\xf6' my_base64 = utils.base64(my_unicode) assert my_base64 == base64.b64encode(my_unicode.encode('utf-8')).decode('utf-8') @@ -23,7 +20,7 @@ def test_base64_encode_strings_correctly(): def test_unbase64_decodes_unicode_strings_correctly(): - my_unicode = u'ûñö' + my_unicode = u'\xfb\xf1\xf6' my_converted_unicode = utils.unbase64(utils.base64(my_unicode)) assert my_unicode == my_converted_unicode diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 15bcaf1..0000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -exclude = tests/*,setup.py -max-line-length = 160 diff --git a/setup.py b/setup.py index 079fc21..fd12f90 100644 --- a/setup.py +++ b/setup.py @@ -5,29 +5,29 @@ class PyTest(TestCommand): - user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] + user_options = [('pytest-args=', 'a', "Arguments to pass to pytest")] def initialize_options(self): TestCommand.initialize_options(self) - self.pytest_args = [] - - def finalize_options(self): - TestCommand.finalize_options(self) - self.test_args = [] - self.test_suite = True + self.pytest_args = "" def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded import pytest - errno = pytest.main(self.pytest_args) + + errno = pytest.main(shlex.split(self.pytest_args)) sys.exit(errno) + setup( name='graphql-relay', - version='0.4.5', + version='2.0.0', description='Relay implementation for Python', - long_description=open('README.rst').read(), + long_description=open('README.md').read(), + long_description_content_type="text/markdown", url='https://github.com/graphql-python/graphql-relay-py', @@ -37,15 +37,17 @@ def run_tests(self): license='MIT', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', + "License :: OSI Approved :: MIT License", 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: PyPy', ], @@ -54,11 +56,11 @@ def run_tests(self): packages=find_packages(exclude=['tests']), install_requires=[ - 'six>=1.10.0', - 'graphql-core>=0.5.0,<2', - 'promise>=0.4.0' + 'six>=1.12', + 'graphql-core>=2.2,<3', + 'promise>=2.2,<3' ], - tests_require=['pytest>=2.7.2'], + tests_require=['pytest>=4.6,<5', 'pytest-cov>=2.7,<3'], extras_require={ }, diff --git a/tests/starwars/data.py b/tests/starwars/data.py index 4377ada..a979007 100644 --- a/tests/starwars/data.py +++ b/tests/starwars/data.py @@ -1,8 +1,10 @@ -# This defines a basic set of data for our Star Wars Schema. -# -# This data is hard coded for the sake of the demo, but you could imagine -# fetching this data from a backend service rather than from hardcoded -# JSON objects in a more complex demo. +"""This defines a basic set of data for our Star Wars Schema. + +This data is hard coded for the sake of the demo, but you could imagine +fetching this data from a backend service rather than from hardcoded +JSON objects in a more complex demo. +""" + from collections import namedtuple Ship = namedtuple('Ship', ['id', 'name']) @@ -81,7 +83,7 @@ def createShip(shipName, factionId): - nextShip = len(data['Ship'].keys())+1 + nextShip = len(data['Ship']) + 1 newShip = Ship( id=str(nextShip), name=shipName diff --git a/tests/starwars/schema.py b/tests/starwars/schema.py index eca662f..b1f2910 100644 --- a/tests/starwars/schema.py +++ b/tests/starwars/schema.py @@ -1,5 +1,3 @@ -from collections import namedtuple - from graphql.type import ( GraphQLID, GraphQLNonNull, @@ -31,7 +29,6 @@ from .data import ( Faction, - Ship, getFaction, getShip, getRebels, @@ -54,7 +51,7 @@ # Wars trilogy. # Using our shorthand to describe type systems, the type system for our -# example will be the followng: +# example will be the following: # # interface Node { # id: ID! @@ -116,22 +113,23 @@ # way we resolve an object that implements node to its type. -def get_node(global_id, *args): - _type, _id = from_global_id(global_id) - if _type == 'Faction': - return getFaction(_id) - elif _type == 'Ship': - return getShip(_id) +def get_node(global_id, _info): + type_, id_ = from_global_id(global_id) + if type_ == 'Faction': + return getFaction(id_) + elif type_ == 'Ship': + return getShip(id_) else: return None -def get_node_type(obj, context, info): +def get_node_type(obj, _info): if isinstance(obj, Faction): return factionType else: return shipType + node_interface, node_field = node_definitions(get_node, get_node_type) @@ -192,9 +190,8 @@ def get_node_type(obj, context, info): shipConnection, description='The ships used by the faction.', args=connection_args, - resolver=lambda faction, args, *_: connection_from_list( - [getShip(ship) for ship in faction.ships], - args + resolver=lambda faction, _info, **args: connection_from_list( + [getShip(ship) for ship in faction.ships], args ), ) }, @@ -215,11 +212,11 @@ def get_node_type(obj, context, info): fields=lambda: { 'rebels': GraphQLField( factionType, - resolver=lambda *_: getRebels(), + resolver=lambda _obj, _info: getRebels(), ), 'empire': GraphQLField( factionType, - resolver=lambda *_: getEmpire(), + resolver=lambda _obj, _info: getEmpire(), ), 'node': node_field } @@ -247,18 +244,17 @@ class IntroduceShipMutation(object): def __init__(self, shipId, factionId, clientMutationId=None): self.shipId = shipId self.factionId = factionId - self.clientMutationId = None + self.clientMutationId = clientMutationId -def mutate_and_get_payload(data, *_): - shipName = data.get('shipName') - factionId = data.get('factionId') +def mutate_and_get_payload(_info, shipName, factionId, **_input): newShip = createShip(shipName, factionId) return IntroduceShipMutation( shipId=newShip.id, factionId=factionId, ) + shipMutation = mutation_with_client_mutation_id( 'IntroduceShip', input_fields={ @@ -272,11 +268,11 @@ def mutate_and_get_payload(data, *_): output_fields={ 'ship': GraphQLField( shipType, - resolver=lambda payload, *_: getShip(payload.shipId) + resolver=lambda payload, _info: getShip(payload.shipId) ), 'faction': GraphQLField( factionType, - resolver=lambda payload, *_: getFaction(payload.factionId) + resolver=lambda payload, _info: getFaction(payload.factionId) ) }, mutate_and_get_payload=mutate_and_get_payload diff --git a/tests/starwars/test_connections.py b/tests/starwars/test_connections.py index e973e41..99e5916 100644 --- a/tests/starwars/test_connections.py +++ b/tests/starwars/test_connections.py @@ -1,4 +1,3 @@ -from pytest import raises from graphql import graphql from .schema import StarWarsSchema diff --git a/tests/starwars/test_mutations.py b/tests/starwars/test_mutations.py index 3bb4377..a4194f9 100644 --- a/tests/starwars/test_mutations.py +++ b/tests/starwars/test_mutations.py @@ -1,10 +1,9 @@ -from pytest import raises from graphql import graphql from .schema import StarWarsSchema -def test_correctely_mutates_dataset(): +def test_correctly_mutates_dataset(): query = ''' mutation AddBWingQuery($input: IntroduceShipInput!) { introduceShip(input: $input) { @@ -38,6 +37,6 @@ def test_correctely_mutates_dataset(): 'clientMutationId': 'abcde', } } - result = graphql(StarWarsSchema, query, variable_values=params) + result = graphql(StarWarsSchema, query, variables=params) assert not result.errors assert result.data == expected diff --git a/tests/starwars/test_objectidentification.py b/tests/starwars/test_objectidentification.py index 07b8ef0..b579877 100644 --- a/tests/starwars/test_objectidentification.py +++ b/tests/starwars/test_objectidentification.py @@ -1,4 +1,3 @@ -from pytest import raises from graphql import graphql from .schema import StarWarsSchema diff --git a/tox.ini b/tox.ini index babbf4f..144cf4a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,18 @@ [tox] -envlist = py27,py33,py34,py35,pypy +envlist = py{27,34,35,36,37,py,py3}, flake8 + +[testenv:flake8] +basepython = python3.7 +deps = flake8>=3.7,<4 +commands = + flake8 setup.py graphql_relay tests + +[testenv:pypy] +whitelist_externals=* + +[testenv:pypy3] +whitelist_externals=* [testenv] -deps= - pytest>=2.7.2 - django>=1.8.0,<1.9 - six - flake8 - singledispatch commands= - py.test - flake8 + python setup.py test -a "{posargs}"