diff --git a/.gitignore b/.gitignore index 8f0e0ae..642f015 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,204 @@ -*.pyc -.idea -.cache -.tox + +# Created by https://www.gitignore.io/api/python,intellij+all,visualstudiocode +# Edit at https://www.gitignore.io/?templates=python,intellij+all,visualstudiocode + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg *.egg -*.egg-info +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/ +.venv/ .coverage -/build/ +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +### VisualStudioCode ### +.vscode + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history -/dist/ -/.pytest_cache +# End of https://www.gitignore.io/api/python,intellij+all,visualstudiocode diff --git a/.travis.yml b/.travis.yml index 4a129cb..52554b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,21 @@ language: python sudo: false -matrix: - include: - - python: pypy - env: TOX_ENV=pypy - - python: '2.7' - env: TOX_ENV=py27 - - python: '3.5' - env: TOX_ENV=py35 - - python: '3.6' - env: TOX_ENV=py36 - - python: '3.6' - env: TOX_ENV=import-order,flake8 -cache: - directories: - - $HOME/.cache/pip - - $TRAVIS_BUILD_DIR/.tox +python: + - 3.6 + - 3.7 + - 3.8 +cache: pip + install: -- pip install tox coveralls + - pip install tox-travis + script: -- tox -e $TOX_ENV -- --cov=flask_graphql + - tox + after_success: -- coveralls + - pip install coveralls + - coveralls + deploy: provider: pypi user: syrusakbary diff --git a/MANIFEST.in b/MANIFEST.in index 497302a..0fa13a1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,10 @@ +include LICENSE include README.md -recursive-include flask_graphql/static * -recursive-include flask_graphql/templates * + +include tox.ini +include Makefile + +recursive-include flask_graphql *.py +recursive-include tests *.py + +global-exclude *.py[co] __pycache__ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a6f33e3 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +dev-setup: + python pip install -e ".[test]" + +tests: + py.test tests --cov=flask_graphql -vv \ No newline at end of file diff --git a/README.md b/README.md index 8c918ae..3546b1d 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,78 @@ # Flask-GraphQL -[![Build Status](https://travis-ci.org/graphql-python/flask-graphql.svg?branch=master)](https://travis-ci.org/graphql-python/flask-graphql) [![Coverage Status](https://coveralls.io/repos/graphql-python/flask-graphql/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/flask-graphql?branch=master) [![PyPI version](https://badge.fury.io/py/flask-graphql.svg)](https://badge.fury.io/py/flask-graphql) - Adds GraphQL support to your Flask application. +[![travis][travis-image]][travis-url] +[![pypi][pypi-image]][pypi-url] +[![Anaconda-Server Badge][conda-image]][conda-url] +[![coveralls][coveralls-image]][coveralls-url] + +[travis-image]: https://travis-ci.org/graphql-python/flask-graphql.svg?branch=master +[travis-url]: https://travis-ci.org/graphql-python/flask-graphql +[pypi-image]: https://img.shields.io/pypi/v/flask-graphql.svg?style=flat +[pypi-url]: https://pypi.org/project/flask-graphql/ +[coveralls-image]: https://coveralls.io/repos/graphql-python/flask-graphql/badge.svg?branch=master&service=github +[coveralls-url]: https://coveralls.io/github/graphql-python/flask-graphql?branch=master +[conda-image]: https://img.shields.io/conda/vn/conda-forge/flask-graphql.svg +[conda-url]: https://anaconda.org/conda-forge/flask-graphql + ## Usage Just use the `GraphQLView` view from `flask_graphql` ```python +from flask import Flask from flask_graphql import GraphQLView -app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True)) +from schema import schema + +app = Flask(__name__) + +app.add_url_rule('/graphql', view_func=GraphQLView.as_view( + 'graphql', + schema=schema, + graphiql=True, +)) # Optional, for adding batch query support (used in Apollo-Client) -app.add_url_rule('/graphql/batch', view_func=GraphQLView.as_view('graphql', schema=schema, batch=True)) +app.add_url_rule('/graphql/batch', view_func=GraphQLView.as_view( + 'graphql', + schema=schema, + batch=True +)) + +if __name__ == '__main__': + app.run() ``` -This will add `/graphql` and `/graphiql` endpoints to your app. +This will add `/graphql` endpoint to your app and enable the GraphiQL IDE. + +### Special Note for Graphene v3 + +If you are using the `Schema` type of [Graphene](https://github.com/graphql-python/graphene) library, be sure to use the `graphql_schema` attribute to pass as schema on the `GraphQLView` view. Otherwise, the `GraphQLSchema` from `graphql-core` is the way to go. + +More info at [Graphene v3 release notes](https://github.com/graphql-python/graphene/wiki/v3-release-notes#graphene-schema-no-longer-subclasses-graphqlschema-type) and [GraphQL-core 3 usage](https://github.com/graphql-python/graphql-core#usage). + + +### Supported options for GraphQLView -### Supported options * `schema`: The `GraphQLSchema` object that you want the view to execute when it gets a valid request. - * `context`: A value to pass as the `context` to the `graphql()` function. - * `root_value`: The `root_value` you want to provide to `executor.execute`. + * `context`: A value to pass as the `context_value` to graphql `execute` function. By default is set to `dict` with request object at key `request`. + * `root_value`: The `root_value` you want to provide to graphql `execute`. * `pretty`: Whether or not you want the response to be pretty printed JSON. - * `executor`: The `Executor` that you want to use to execute queries. * `graphiql`: If `True`, may present [GraphiQL](https://github.com/graphql/graphiql) when loaded directly from a browser (a useful tool for debugging and exploration). + * `graphiql_version`: The graphiql version to load. Defaults to **"1.0.3"**. * `graphiql_template`: Inject a Jinja template string to customize GraphiQL. + * `graphiql_html_title`: The graphiql title to display. Defaults to **"GraphiQL"**. * `batch`: Set the GraphQL view as batch (for using in [Apollo-Client](http://dev.apollodata.com/core/network.html#query-batching) or [ReactRelayNetworkLayer](https://github.com/nodkz/react-relay-network-layer)) * `middleware`: A list of graphql [middlewares](http://docs.graphene-python.org/en/latest/execution/middleware/). + * `encode`: the encoder to use for responses (sensibly defaults to `graphql_server.json_encode`). + * `format_error`: the error formatter to use for responses (sensibly defaults to `graphql_server.default_format_error`. + * `subscriptions`: The GraphiQL socket endpoint for using subscriptions in graphql-ws. + * `headers`: An optional GraphQL string to use as the initial displayed request headers, if not provided, the stored headers will be used. + * `default_query`: An optional GraphQL string to use when no query is provided and no stored query exists from a previous session. If not provided, GraphiQL will use its own default query. +* `header_editor_enabled`: An optional boolean which enables the header editor when true. Defaults to **false**. +* `should_persist_headers`: An optional boolean which enables to persist headers to storage when true. Defaults to **false**. You can also subclass `GraphQLView` and overwrite `get_root_value(self, request)` to have a dynamic root value per request. @@ -39,3 +83,6 @@ class UserRootValue(GraphQLView): return request.user ``` + +## Contributing +Since v3, `flask-graphql` code lives at [graphql-server](https://github.com/graphql-python/graphql-server) repository to keep any breaking change on the base package on sync with all other integrations. In order to contribute, please take a look at [CONTRIBUTING.md](https://github.com/graphql-python/graphql-server/blob/master/CONTRIBUTING.md). diff --git a/README.rst b/README.rst deleted file mode 100644 index a2c5ef7..0000000 --- a/README.rst +++ /dev/null @@ -1,63 +0,0 @@ -Flask-GraphQL -============= - -|Build Status| |Coverage Status| |PyPI version| - -Adds GraphQL support to your Flask application. - -Usage ------ - -Just use the ``GraphQLView`` view from ``flask_graphql`` - -.. code:: python - - from flask_graphql import GraphQLView - - app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema, graphiql=True)) - - # Optional, for adding batch query support (used in Apollo-Client) - app.add_url_rule('/graphql/batch', view_func=GraphQLView.as_view('graphql', schema=schema, batch=True)) - -This will add ``/graphql`` and ``/graphiql`` endpoints to your app. - -Supported options -~~~~~~~~~~~~~~~~~ - -- ``schema``: The ``GraphQLSchema`` object that you want the view to - execute when it gets a valid request. -- ``context``: A value to pass as the ``context`` to the ``graphql()`` - function. -- ``root_value``: The ``root_value`` you want to provide to - ``executor.execute``. -- ``pretty``: Whether or not you want the response to be pretty printed - JSON. -- ``executor``: The ``Executor`` that you want to use to execute - queries. -- ``graphiql``: If ``True``, may present - `GraphiQL `__ when loaded - directly from a browser (a useful tool for debugging and - exploration). -- ``graphiql_template``: Inject a Jinja template string to customize - GraphiQL. -- ``batch``: Set the GraphQL view as batch (for using in - `Apollo-Client `__ - or - `ReactRelayNetworkLayer `__) - -You can also subclass ``GraphQLView`` and overwrite -``get_root_value(self, request)`` to have a dynamic root value per -request. - -.. code:: python - - class UserRootValue(GraphQLView): - def get_root_value(self, request): - return request.user - -.. |Build Status| image:: https://travis-ci.org/graphql-python/flask-graphql.svg?branch=master - :target: https://travis-ci.org/graphql-python/flask-graphql -.. |Coverage Status| image:: https://coveralls.io/repos/graphql-python/flask-graphql/badge.svg?branch=master&service=github - :target: https://coveralls.io/github/graphql-python/flask-graphql?branch=master -.. |PyPI version| image:: https://badge.fury.io/py/flask-graphql.svg - :target: https://badge.fury.io/py/flask-graphql diff --git a/flask_graphql/__init__.py b/flask_graphql/__init__.py index 4c30423..5b3ee3c 100644 --- a/flask_graphql/__init__.py +++ b/flask_graphql/__init__.py @@ -1,4 +1,3 @@ -from .blueprint import GraphQL -from .graphqlview import GraphQLView +from graphql_server.flask.graphqlview import GraphQLView -__all__ = ['GraphQL', 'GraphQLView'] +__all__ = ['GraphQLView'] diff --git a/flask_graphql/blueprint.py b/flask_graphql/blueprint.py deleted file mode 100644 index b02266a..0000000 --- a/flask_graphql/blueprint.py +++ /dev/null @@ -1,17 +0,0 @@ -import warnings - -from flask import Blueprint - -from .graphqlview import GraphQLView - - -class GraphQL(object): - def __init__(self, app, schema, **options): - self.app = app - warnings.warn('GraphQL Blueprint is now deprecated, please use GraphQLView directly') - self.blueprint = Blueprint('graphql', __name__, - template_folder='templates') - - app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema, **options)) - - self.app.register_blueprint(self.blueprint) diff --git a/flask_graphql/graphqlview.py b/flask_graphql/graphqlview.py deleted file mode 100644 index ff257b3..0000000 --- a/flask_graphql/graphqlview.py +++ /dev/null @@ -1,154 +0,0 @@ -from functools import partial - -from flask import Response, request -from flask.views import View - -from graphql.type.schema import GraphQLSchema -from graphql_server import (HttpQueryError, default_format_error, - encode_execution_results, json_encode, - load_json_body, run_http_query) - -from .render_graphiql import render_graphiql - - -class GraphQLView(View): - schema = None - executor = None - root_value = None - pretty = False - graphiql = False - backend = None - graphiql_version = None - graphiql_template = None - graphiql_html_title = None - middleware = None - batch = False - - methods = ['GET', 'POST', 'PUT', 'DELETE'] - - def __init__(self, **kwargs): - super(GraphQLView, self).__init__() - for key, value in kwargs.items(): - if hasattr(self, key): - setattr(self, key, value) - - assert isinstance(self.schema, GraphQLSchema), 'A Schema is required to be provided to GraphQLView.' - - # noinspection PyUnusedLocal - def get_root_value(self): - return self.root_value - - def get_context(self): - return request - - def get_middleware(self): - return self.middleware - - def get_backend(self): - return self.backend - - def get_executor(self): - return self.executor - - def render_graphiql(self, params, result): - return render_graphiql( - params=params, - result=result, - graphiql_version=self.graphiql_version, - graphiql_template=self.graphiql_template, - graphiql_html_title=self.graphiql_html_title, - ) - - format_error = staticmethod(default_format_error) - encode = staticmethod(json_encode) - - def dispatch_request(self): - try: - request_method = request.method.lower() - data = self.parse_body() - - show_graphiql = request_method == 'get' and self.should_display_graphiql() - catch = show_graphiql - - pretty = self.pretty or show_graphiql or request.args.get('pretty') - - extra_options = {} - executor = self.get_executor() - if executor: - # We only include it optionally since - # executor is not a valid argument in all backends - extra_options['executor'] = executor - - execution_results, all_params = run_http_query( - self.schema, - request_method, - data, - query_data=request.args, - batch_enabled=self.batch, - catch=catch, - backend=self.get_backend(), - - # Execute options - root=self.get_root_value(), - context=self.get_context(), - middleware=self.get_middleware(), - **extra_options - ) - result, status_code = encode_execution_results( - execution_results, - is_batch=isinstance(data, list), - format_error=self.format_error, - encode=partial(self.encode, pretty=pretty) - ) - - if show_graphiql: - return self.render_graphiql( - params=all_params[0], - result=result - ) - - return Response( - result, - status=status_code, - content_type='application/json' - ) - - except HttpQueryError as e: - return Response( - self.encode({ - 'errors': [self.format_error(e)] - }), - status=e.status_code, - headers=e.headers, - content_type='application/json' - ) - - # Flask - # noinspection PyBroadException - def parse_body(self): - # We use mimetype here since we don't need the other - # information provided by content_type - content_type = request.mimetype - if content_type == 'application/graphql': - return {'query': request.data.decode('utf8')} - - elif content_type == 'application/json': - return load_json_body(request.data.decode('utf8')) - - elif content_type in ('application/x-www-form-urlencoded', 'multipart/form-data'): - return request.form - - return {} - - def should_display_graphiql(self): - if not self.graphiql or 'raw' in request.args: - return False - - return self.request_wants_html() - - def request_wants_html(self): - best = request.accept_mimetypes \ - .best_match(['application/json', 'text/html']) - return best == 'text/html' and \ - request.accept_mimetypes[best] > \ - request.accept_mimetypes['application/json'] diff --git a/flask_graphql/render_graphiql.py b/flask_graphql/render_graphiql.py deleted file mode 100644 index 6bfb2da..0000000 --- a/flask_graphql/render_graphiql.py +++ /dev/null @@ -1,138 +0,0 @@ -from flask import render_template_string - -GRAPHIQL_VERSION = '0.11.11' - -TEMPLATE = ''' - - - - {{graphiql_html_title|default("GraphiQL", true)}} - - - - - - - - - - - -''' - - -def render_graphiql(params, result, graphiql_version=None, - graphiql_template=None, graphiql_html_title=None): - graphiql_version = graphiql_version or GRAPHIQL_VERSION - template = graphiql_template or TEMPLATE - - return render_template_string( - template, - graphiql_version=graphiql_version, - graphiql_html_title=graphiql_html_title, - result=result, - params=params - ) diff --git a/setup.cfg b/setup.cfg index 34e51b5..b6ff204 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,9 +1,9 @@ [flake8] exclude = tests,scripts,setup.py,docs -max-line-length = 160 +max-line-length = 88 [isort] known_first_party=graphql [tool:pytest] -norecursedirs = venv .tox .cache +norecursedirs = venv .venv .tox .git .cache .mypy_cache .pytest_cache diff --git a/setup.py b/setup.py index 0429c9e..d8a626e 100644 --- a/setup.py +++ b/setup.py @@ -1,12 +1,29 @@ from setuptools import setup, find_packages -required_packages = ["graphql-core>=2.1,<3", "flask>=0.7.0", "graphql-server-core>=1.1,<2"] +install_requires = [ + "graphql-server[flask]>=3.0.0b1", +] + +tests_requires = [ + "pytest>=5.4,<5.5", + "pytest-cov>=2.8,<3", +] + +dev_requires = [ + "flake8>=3.7,<4", + "isort>=4,<5", + "check-manifest>=0.40,<1", +] + tests_requires + +with open("README.md", encoding="utf-8") as readme_file: + readme = readme_file.read() setup( name="Flask-GraphQL", version="2.0.1", description="Adds GraphQL support to your Flask application", - long_description=open("README.rst").read(), + long_description=readme, + long_description_content_type="text/markdown", url="https://github.com/graphql-python/flask-graphql", download_url="https://github.com/graphql-python/flask-graphql/releases", author="Syrus Akbary", @@ -16,21 +33,19 @@ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", - "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", + "Programming Language :: Python :: 3.8", "License :: OSI Approved :: MIT License", ], keywords="api graphql protocol rest flask", packages=find_packages(exclude=["tests"]), - install_requires=required_packages, - tests_require=["pytest>=2.7.3"], + install_requires=install_requires, + tests_require=tests_requires, + extras_require={ + 'test': tests_requires, + 'dev': dev_requires, + }, include_package_data=True, zip_safe=False, platforms="any", diff --git a/tests/app.py b/tests/app.py index 0d38502..84f1d23 100644 --- a/tests/app.py +++ b/tests/app.py @@ -1,19 +1,18 @@ from flask import Flask + from flask_graphql import GraphQLView -from .schema import Schema -from graphql import GraphQLCachedBackend -# from quiver.backend import GraphQLQuiverBackend +from tests.schema import Schema -def create_app(path='/graphql', **kwargs): - # backend = GraphQLCachedBackend(GraphQLQuiverBackend({"async_framework": "PROMISE"})) - backend = None +def create_app(path="/graphql", **kwargs): app = Flask(__name__) app.debug = True - app.add_url_rule(path, view_func=GraphQLView.as_view('graphql', schema=Schema, backend=backend, **kwargs)) + app.add_url_rule( + path, view_func=GraphQLView.as_view("graphql", schema=Schema, **kwargs) + ) return app -if __name__ == '__main__': +if __name__ == "__main__": app = create_app(graphiql=True) app.run() diff --git a/tests/schema.py b/tests/schema.py index f841672..fdb5c9a 100644 --- a/tests/schema.py +++ b/tests/schema.py @@ -1,4 +1,5 @@ -from graphql.type.definition import GraphQLArgument, GraphQLField, GraphQLNonNull, GraphQLObjectType +from graphql.type.definition import (GraphQLArgument, GraphQLField, + GraphQLNonNull, GraphQLObjectType) from graphql.type.scalars import GraphQLString from graphql.type.schema import GraphQLSchema @@ -8,31 +9,39 @@ def resolve_raises(*_): QueryRootType = GraphQLObjectType( - name='QueryRoot', + name="QueryRoot", fields={ - 'thrower': GraphQLField(GraphQLNonNull(GraphQLString), resolver=resolve_raises), - 'request': GraphQLField(GraphQLNonNull(GraphQLString), - resolver=lambda obj, info: info.context.args.get('q')), - 'context': GraphQLField(GraphQLNonNull(GraphQLString), - resolver=lambda obj, info: info.context), - 'test': GraphQLField( - type=GraphQLString, - args={ - 'who': GraphQLArgument(GraphQLString) - }, - resolver=lambda obj, info, who='World': 'Hello %s' % who - ) - } + "thrower": GraphQLField(GraphQLNonNull(GraphQLString), resolve=resolve_raises), + "request": GraphQLField( + GraphQLNonNull(GraphQLString), + resolve=lambda obj, info: info.context["request"].args.get("q"), + ), + "context": GraphQLField( + GraphQLObjectType( + name="context", + fields={ + "session": GraphQLField(GraphQLString), + "request": GraphQLField( + GraphQLNonNull(GraphQLString), + resolve=lambda obj, info: info.context["request"], + ), + }, + ), + resolve=lambda obj, info: info.context, + ), + "test": GraphQLField( + type_=GraphQLString, + args={"who": GraphQLArgument(GraphQLString)}, + resolve=lambda obj, info, who="World": "Hello %s" % who, + ), + }, ) MutationRootType = GraphQLObjectType( - name='MutationRoot', + name="MutationRoot", fields={ - 'writeTest': GraphQLField( - type=QueryRootType, - resolver=lambda *_: QueryRootType - ) - } + "writeTest": GraphQLField(type_=QueryRootType, resolve=lambda *_: QueryRootType) + }, ) Schema = GraphQLSchema(QueryRootType, MutationRootType) diff --git a/tests/test_graphiqlview.py b/tests/test_graphiqlview.py index 3a3698a..4a55710 100644 --- a/tests/test_graphiqlview.py +++ b/tests/test_graphiqlview.py @@ -1,39 +1,60 @@ import pytest +from flask import url_for from .app import create_app -from flask import url_for @pytest.fixture def app(): - return create_app(graphiql=True) + # import app factory pattern + app = create_app(graphiql=True) + + # pushes an application context manually + ctx = app.app_context() + ctx.push() + return app + + +@pytest.fixture +def client(app): + return app.test_client() -def test_graphiql_is_enabled(client): - response = client.get(url_for('graphql'), headers={'Accept': 'text/html'}) +def test_graphiql_is_enabled(app, client): + with app.test_request_context(): + response = client.get( + url_for("graphql", externals=False), headers={"Accept": "text/html"} + ) assert response.status_code == 200 -def test_graphiql_renders_pretty(client): - response = client.get(url_for('graphql', query='{test}'), headers={'Accept': 'text/html'}) +def test_graphiql_renders_pretty(app, client): + with app.test_request_context(): + response = client.get( + url_for("graphql", query="{test}"), headers={"Accept": "text/html"} + ) assert response.status_code == 200 pretty_response = ( - '{\n' + "{\n" ' "data": {\n' ' "test": "Hello World"\n' - ' }\n' - '}' - ).replace("\"","\\\"").replace("\n","\\n") + " }\n" + "}".replace('"', '\\"').replace("\n", "\\n") + ) - assert pretty_response in response.data.decode('utf-8') + assert pretty_response in response.data.decode("utf-8") -def test_graphiql_default_title(client): - response = client.get(url_for('graphql'), headers={'Accept': 'text/html'}) - assert 'GraphiQL' in response.data.decode('utf-8') +def test_graphiql_default_title(app, client): + with app.test_request_context(): + response = client.get(url_for("graphql"), headers={"Accept": "text/html"}) + assert "GraphiQL" in response.data.decode("utf-8") -@pytest.mark.parametrize('app', [create_app(graphiql=True, graphiql_html_title="Awesome")]) -def test_graphiql_custom_title(client): - response = client.get(url_for('graphql'), headers={'Accept': 'text/html'}) - assert 'Awesome' in response.data.decode('utf-8') +@pytest.mark.parametrize( + "app", [create_app(graphiql=True, graphiql_html_title="Awesome")] +) +def test_graphiql_custom_title(app, client): + with app.test_request_context(): + response = client.get(url_for("graphql"), headers={"Accept": "text/html"}) + assert "Awesome" in response.data.decode("utf-8") diff --git a/tests/test_graphqlview.py b/tests/test_graphqlview.py index 77626d4..961a8e0 100644 --- a/tests/test_graphqlview.py +++ b/tests/test_graphqlview.py @@ -1,29 +1,35 @@ -import pytest import json +from io import StringIO +from urllib.parse import urlencode -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - -try: - from urllib import urlencode -except ImportError: - from urllib.parse import urlencode +import pytest +from flask import url_for from .app import create_app -from flask import url_for @pytest.fixture -def app(): - return create_app() +def app(request): + # import app factory pattern + app = create_app() + + # pushes an application context manually + ctx = app.app_context() + ctx.push() + return app + + +@pytest.fixture +def client(app): + return app.test_client() -def url_string(**url_params): - string = url_for('graphql') + +def url_string(app, **url_params): + with app.test_request_context(): + string = url_for("graphql") if url_params: - string += '?' + urlencode(url_params) + string += "?" + urlencode(url_params) return string @@ -32,514 +38,561 @@ def response_json(response): return json.loads(response.data.decode()) -j = lambda **kwargs: json.dumps(kwargs) -jl = lambda **kwargs: json.dumps([kwargs]) +def json_dump_kwarg(**kwargs): + return json.dumps(kwargs) + +def json_dump_kwarg_list(**kwargs): + return json.dumps([kwargs]) -def test_allows_get_with_query_param(client): - response = client.get(url_string(query='{test}')) + +def test_allows_get_with_query_param(app, client): + response = client.get(url_string(app, query="{test}")) assert response.status_code == 200 - assert response_json(response) == { - 'data': {'test': "Hello World"} - } + assert response_json(response) == {"data": {"test": "Hello World"}} -def test_allows_get_with_variable_values(client): - response = client.get(url_string( - query='query helloWho($who: String){ test(who: $who) }', - variables=json.dumps({'who': "Dolly"}) - )) +def test_allows_get_with_variable_values(app, client): + response = client.get( + url_string( + app, + query="query helloWho($who: String){ test(who: $who) }", + variables=json.dumps({"who": "Dolly"}), + ) + ) assert response.status_code == 200 - assert response_json(response) == { - 'data': {'test': "Hello Dolly"} - } + assert response_json(response) == {"data": {"test": "Hello Dolly"}} -def test_allows_get_with_operation_name(client): - response = client.get(url_string( - query=''' +def test_allows_get_with_operation_name(app, client): + response = client.get( + url_string( + app, + query=""" query helloYou { test(who: "You"), ...shared } query helloWorld { test(who: "World"), ...shared } query helloDolly { test(who: "Dolly"), ...shared } fragment shared on QueryRoot { shared: test(who: "Everyone") } - ''', - operationName='helloWorld' - )) + """, + operationName="helloWorld", + ) + ) assert response.status_code == 200 assert response_json(response) == { - 'data': { - 'test': 'Hello World', - 'shared': 'Hello Everyone' - } + "data": {"test": "Hello World", "shared": "Hello Everyone"} } -def test_reports_validation_errors(client): - response = client.get(url_string( - query='{ test, unknownOne, unknownTwo }' - )) +def test_reports_validation_errors(app, client): + response = client.get(url_string(app, query="{ test, unknownOne, unknownTwo }")) assert response.status_code == 400 assert response_json(response) == { - 'errors': [ + "errors": [ { - 'message': 'Cannot query field "unknownOne" on type "QueryRoot".', - 'locations': [{'line': 1, 'column': 9}] + "message": "Cannot query field 'unknownOne' on type 'QueryRoot'.", + "locations": [{"line": 1, "column": 9}], + "path": None, }, { - 'message': 'Cannot query field "unknownTwo" on type "QueryRoot".', - 'locations': [{'line': 1, 'column': 21}] - } + "message": "Cannot query field 'unknownTwo' on type 'QueryRoot'.", + "locations": [{"line": 1, "column": 21}], + "path": None, + }, ] } -def test_errors_when_missing_operation_name(client): - response = client.get(url_string( - query=''' +def test_errors_when_missing_operation_name(app, client): + response = client.get( + url_string( + app, + query=""" query TestQuery { test } mutation TestMutation { writeTest { test } } - ''' - )) + """, + ) + ) assert response.status_code == 400 assert response_json(response) == { - 'errors': [ + "errors": [ { - 'message': 'Must provide operation name if query contains multiple operations.' + "message": "Must provide operation name if query contains multiple operations.", # noqa: E501 + "locations": None, + "path": None, } ] } -def test_errors_when_sending_a_mutation_via_get(client): - response = client.get(url_string( - query=''' +def test_errors_when_sending_a_mutation_via_get(app, client): + response = client.get( + url_string( + app, + query=""" mutation TestMutation { writeTest { test } } - ''' - )) + """, + ) + ) assert response.status_code == 405 assert response_json(response) == { - 'errors': [ + "errors": [ { - 'message': 'Can only perform a mutation operation from a POST request.' + "message": "Can only perform a mutation operation from a POST request.", + "locations": None, + "path": None, } ] } -def test_errors_when_selecting_a_mutation_within_a_get(client): - response = client.get(url_string( - query=''' +def test_errors_when_selecting_a_mutation_within_a_get(app, client): + response = client.get( + url_string( + app, + query=""" query TestQuery { test } mutation TestMutation { writeTest { test } } - ''', - operationName='TestMutation' - )) + """, + operationName="TestMutation", + ) + ) assert response.status_code == 405 assert response_json(response) == { - 'errors': [ + "errors": [ { - 'message': 'Can only perform a mutation operation from a POST request.' + "message": "Can only perform a mutation operation from a POST request.", + "locations": None, + "path": None, } ] } -def test_allows_mutation_to_exist_within_a_get(client): - response = client.get(url_string( - query=''' +def test_allows_mutation_to_exist_within_a_get(app, client): + response = client.get( + url_string( + app, + query=""" query TestQuery { test } mutation TestMutation { writeTest { test } } - ''', - operationName='TestQuery' - )) + """, + operationName="TestQuery", + ) + ) assert response.status_code == 200 - assert response_json(response) == { - 'data': {'test': "Hello World"} - } + assert response_json(response) == {"data": {"test": "Hello World"}} -def test_allows_post_with_json_encoding(client): - response = client.post(url_string(), data=j(query='{test}'), content_type='application/json') +def test_allows_post_with_json_encoding(app, client): + response = client.post( + url_string(app), + data=json_dump_kwarg(query="{test}"), + content_type="application/json", + ) assert response.status_code == 200 - assert response_json(response) == { - 'data': {'test': "Hello World"} - } + assert response_json(response) == {"data": {"test": "Hello World"}} -def test_allows_sending_a_mutation_via_post(client): - response = client.post(url_string(), data=j(query='mutation TestMutation { writeTest { test } }'), content_type='application/json') +def test_allows_sending_a_mutation_via_post(app, client): + response = client.post( + url_string(app), + data=json_dump_kwarg(query="mutation TestMutation { writeTest { test } }"), + content_type="application/json", + ) assert response.status_code == 200 - assert response_json(response) == { - 'data': {'writeTest': {'test': 'Hello World'}} - } + assert response_json(response) == {"data": {"writeTest": {"test": "Hello World"}}} -def test_allows_post_with_url_encoding(client): - response = client.post(url_string(), data=urlencode(dict(query='{test}')), content_type='application/x-www-form-urlencoded') +def test_allows_post_with_url_encoding(app, client): + response = client.post( + url_string(app), + data=urlencode(dict(query="{test}")), + content_type="application/x-www-form-urlencoded", + ) assert response.status_code == 200 - assert response_json(response) == { - 'data': {'test': "Hello World"} - } - - -# def test_benchmark(client, benchmark): -# url = url_string() -# data = urlencode(dict(query='{test}')) -# def fun(): -# return client.post(url_string(), data=data, content_type='application/x-www-form-urlencoded') - -# response = benchmark(fun) -# assert response.status_code == 200 -# assert response_json(response) == { -# 'data': {'test': "Hello World"} -# } + assert response_json(response) == {"data": {"test": "Hello World"}} -def test_supports_post_json_query_with_string_variables(client): - response = client.post(url_string(), data=j( - query='query helloWho($who: String){ test(who: $who) }', - variables=json.dumps({'who': "Dolly"}) - ), content_type='application/json') +def test_supports_post_json_query_with_string_variables(app, client): + response = client.post( + url_string(app), + data=json_dump_kwarg( + query="query helloWho($who: String){ test(who: $who) }", + variables=json.dumps({"who": "Dolly"}), + ), + content_type="application/json", + ) assert response.status_code == 200 - assert response_json(response) == { - 'data': {'test': "Hello Dolly"} - } + assert response_json(response) == {"data": {"test": "Hello Dolly"}} -def test_supports_post_json_query_with_json_variables(client): - response = client.post(url_string(), data=j( - query='query helloWho($who: String){ test(who: $who) }', - variables={'who': "Dolly"} - ), content_type='application/json') +def test_supports_post_json_query_with_json_variables(app, client): + response = client.post( + url_string(app), + data=json_dump_kwarg( + query="query helloWho($who: String){ test(who: $who) }", + variables={"who": "Dolly"}, + ), + content_type="application/json", + ) assert response.status_code == 200 - assert response_json(response) == { - 'data': {'test': "Hello Dolly"} - } + assert response_json(response) == {"data": {"test": "Hello Dolly"}} -def test_supports_post_url_encoded_query_with_string_variables(client): - response = client.post(url_string(), data=urlencode(dict( - query='query helloWho($who: String){ test(who: $who) }', - variables=json.dumps({'who': "Dolly"}) - )), content_type='application/x-www-form-urlencoded') +def test_supports_post_url_encoded_query_with_string_variables(app, client): + response = client.post( + url_string(app), + data=urlencode( + dict( + query="query helloWho($who: String){ test(who: $who) }", + variables=json.dumps({"who": "Dolly"}), + ) + ), + content_type="application/x-www-form-urlencoded", + ) assert response.status_code == 200 - assert response_json(response) == { - 'data': {'test': "Hello Dolly"} - } + assert response_json(response) == {"data": {"test": "Hello Dolly"}} -def test_supports_post_json_quey_with_get_variable_values(client): - response = client.post(url_string( - variables=json.dumps({'who': "Dolly"}) - ), data=j( - query='query helloWho($who: String){ test(who: $who) }', - ), content_type='application/json') +def test_supports_post_json_quey_with_get_variable_values(app, client): + response = client.post( + url_string(app, variables=json.dumps({"who": "Dolly"})), + data=json_dump_kwarg(query="query helloWho($who: String){ test(who: $who) }",), + content_type="application/json", + ) assert response.status_code == 200 - assert response_json(response) == { - 'data': {'test': "Hello Dolly"} - } + assert response_json(response) == {"data": {"test": "Hello Dolly"}} -def test_post_url_encoded_query_with_get_variable_values(client): - response = client.post(url_string( - variables=json.dumps({'who': "Dolly"}) - ), data=urlencode(dict( - query='query helloWho($who: String){ test(who: $who) }', - )), content_type='application/x-www-form-urlencoded') +def test_post_url_encoded_query_with_get_variable_values(app, client): + response = client.post( + url_string(app, variables=json.dumps({"who": "Dolly"})), + data=urlencode(dict(query="query helloWho($who: String){ test(who: $who) }",)), + content_type="application/x-www-form-urlencoded", + ) assert response.status_code == 200 - assert response_json(response) == { - 'data': {'test': "Hello Dolly"} - } + assert response_json(response) == {"data": {"test": "Hello Dolly"}} -def test_supports_post_raw_text_query_with_get_variable_values(client): - response = client.post(url_string( - variables=json.dumps({'who': "Dolly"}) - ), - data='query helloWho($who: String){ test(who: $who) }', - content_type='application/graphql' +def test_supports_post_raw_text_query_with_get_variable_values(app, client): + response = client.post( + url_string(app, variables=json.dumps({"who": "Dolly"})), + data="query helloWho($who: String){ test(who: $who) }", + content_type="application/graphql", ) assert response.status_code == 200 - assert response_json(response) == { - 'data': {'test': "Hello Dolly"} - } + assert response_json(response) == {"data": {"test": "Hello Dolly"}} -def test_allows_post_with_operation_name(client): - response = client.post(url_string(), data=j( - query=''' +def test_allows_post_with_operation_name(app, client): + response = client.post( + url_string(app), + data=json_dump_kwarg( + query=""" query helloYou { test(who: "You"), ...shared } query helloWorld { test(who: "World"), ...shared } query helloDolly { test(who: "Dolly"), ...shared } fragment shared on QueryRoot { shared: test(who: "Everyone") } - ''', - operationName='helloWorld' - ), content_type='application/json') + """, + operationName="helloWorld", + ), + content_type="application/json", + ) assert response.status_code == 200 assert response_json(response) == { - 'data': { - 'test': 'Hello World', - 'shared': 'Hello Everyone' - } + "data": {"test": "Hello World", "shared": "Hello Everyone"} } -def test_allows_post_with_get_operation_name(client): - response = client.post(url_string( - operationName='helloWorld' - ), data=''' +def test_allows_post_with_get_operation_name(app, client): + response = client.post( + url_string(app, operationName="helloWorld"), + data=""" query helloYou { test(who: "You"), ...shared } query helloWorld { test(who: "World"), ...shared } query helloDolly { test(who: "Dolly"), ...shared } fragment shared on QueryRoot { shared: test(who: "Everyone") } - ''', - content_type='application/graphql') + """, + content_type="application/graphql", + ) assert response.status_code == 200 assert response_json(response) == { - 'data': { - 'test': 'Hello World', - 'shared': 'Hello Everyone' - } + "data": {"test": "Hello World", "shared": "Hello Everyone"} } -@pytest.mark.parametrize('app', [create_app(pretty=True)]) -def test_supports_pretty_printing(client): - response = client.get(url_string(query='{test}')) +@pytest.mark.parametrize("app", [create_app(pretty=True)]) +def test_supports_pretty_printing(app, client): + response = client.get(url_string(app, query="{test}")) assert response.data.decode() == ( - '{\n' - ' "data": {\n' - ' "test": "Hello World"\n' - ' }\n' - '}' + "{\n" ' "data": {\n' ' "test": "Hello World"\n' " }\n" "}" ) -@pytest.mark.parametrize('app', [create_app(pretty=False)]) -def test_not_pretty_by_default(client): - response = client.get(url_string(query='{test}')) +@pytest.mark.parametrize("app", [create_app(pretty=False)]) +def test_not_pretty_by_default(app, client): + response = client.get(url_string(app, query="{test}")) - assert response.data.decode() == ( - '{"data":{"test":"Hello World"}}' - ) + assert response.data.decode() == '{"data":{"test":"Hello World"}}' -def test_supports_pretty_printing_by_request(client): - response = client.get(url_string(query='{test}', pretty='1')) +def test_supports_pretty_printing_by_request(app, client): + response = client.get(url_string(app, query="{test}", pretty="1")) assert response.data.decode() == ( - '{\n' - ' "data": {\n' - ' "test": "Hello World"\n' - ' }\n' - '}' + "{\n" ' "data": {\n' ' "test": "Hello World"\n' " }\n" "}" ) -def test_handles_field_errors_caught_by_graphql(client): - response = client.get(url_string(query='{thrower}')) +def test_handles_field_errors_caught_by_graphql(app, client): + response = client.get(url_string(app, query="{thrower}")) assert response.status_code == 200 assert response_json(response) == { - 'data': None, - 'errors': [{'locations': [{'column': 2, 'line': 1}], 'path': ['thrower'], 'message': 'Throws!'}] + "errors": [ + { + "locations": [{"column": 2, "line": 1}], + "path": ["thrower"], + "message": "Throws!", + } + ], + "data": None, } -def test_handles_syntax_errors_caught_by_graphql(client): - response = client.get(url_string(query='syntaxerror')) +def test_handles_syntax_errors_caught_by_graphql(app, client): + response = client.get(url_string(app, query="syntaxerror")) assert response.status_code == 400 assert response_json(response) == { - 'errors': [{'locations': [{'column': 1, 'line': 1}], - 'message': 'Syntax Error GraphQL (1:1) ' - 'Unexpected Name "syntaxerror"\n\n1: syntaxerror\n ^\n'}] + "errors": [ + { + "locations": [{"column": 1, "line": 1}], + "message": "Syntax Error: Unexpected Name 'syntaxerror'.", + "path": None, + } + ] } -def test_handles_errors_caused_by_a_lack_of_query(client): - response = client.get(url_string()) +def test_handles_errors_caused_by_a_lack_of_query(app, client): + response = client.get(url_string(app)) assert response.status_code == 400 assert response_json(response) == { - 'errors': [{'message': 'Must provide query string.'}] + "errors": [ + {"message": "Must provide query string.", "locations": None, "path": None} + ] } -def test_handles_batch_correctly_if_is_disabled(client): - response = client.post(url_string(), data='[]', content_type='application/json') +def test_handles_batch_correctly_if_is_disabled(app, client): + response = client.post(url_string(app), data="[]", content_type="application/json") assert response.status_code == 400 assert response_json(response) == { - 'errors': [{'message': 'Batch GraphQL requests are not enabled.'}] + "errors": [ + { + "message": "Batch GraphQL requests are not enabled.", + "locations": None, + "path": None, + } + ] } -def test_handles_incomplete_json_bodies(client): - response = client.post(url_string(), data='{"query":', content_type='application/json') +def test_handles_incomplete_json_bodies(app, client): + response = client.post( + url_string(app), data='{"query":', content_type="application/json" + ) assert response.status_code == 400 assert response_json(response) == { - 'errors': [{'message': 'POST body sent invalid JSON.'}] + "errors": [ + {"message": "POST body sent invalid JSON.", "locations": None, "path": None} + ] } -def test_handles_plain_post_text(client): - response = client.post(url_string( - variables=json.dumps({'who': "Dolly"}) - ), - data='query helloWho($who: String){ test(who: $who) }', - content_type='text/plain' +def test_handles_plain_post_text(app, client): + response = client.post( + url_string(app, variables=json.dumps({"who": "Dolly"})), + data="query helloWho($who: String){ test(who: $who) }", + content_type="text/plain", ) assert response.status_code == 400 assert response_json(response) == { - 'errors': [{'message': 'Must provide query string.'}] + "errors": [ + {"message": "Must provide query string.", "locations": None, "path": None} + ] } -def test_handles_poorly_formed_variables(client): - response = client.get(url_string( - query='query helloWho($who: String){ test(who: $who) }', - variables='who:You' - )) +def test_handles_poorly_formed_variables(app, client): + response = client.get( + url_string( + app, + query="query helloWho($who: String){ test(who: $who) }", + variables="who:You", + ) + ) assert response.status_code == 400 assert response_json(response) == { - 'errors': [{'message': 'Variables are invalid JSON.'}] + "errors": [ + {"message": "Variables are invalid JSON.", "locations": None, "path": None} + ] } -def test_handles_unsupported_http_methods(client): - response = client.put(url_string(query='{test}')) +def test_handles_unsupported_http_methods(app, client): + response = client.put(url_string(app, query="{test}")) assert response.status_code == 405 - assert response.headers['Allow'] in ['GET, POST', 'HEAD, GET, POST, OPTIONS'] + assert response.headers["Allow"] in ["GET, POST", "HEAD, GET, POST, OPTIONS"] assert response_json(response) == { - 'errors': [{'message': 'GraphQL only supports GET and POST requests.'}] + "errors": [ + { + "message": "GraphQL only supports GET and POST requests.", + "locations": None, + "path": None, + } + ] } -def test_passes_request_into_request_context(client): - response = client.get(url_string(query='{request}', q='testing')) +def test_passes_request_into_request_context(app, client): + response = client.get(url_string(app, query="{request}", q="testing")) assert response.status_code == 200 - assert response_json(response) == { - 'data': { - 'request': 'testing' - } - } + assert response_json(response) == {"data": {"request": "testing"}} + +@pytest.mark.parametrize("app", [create_app(context={"session": "CUSTOM CONTEXT"})]) +def test_passes_custom_context_into_context(app, client): + response = client.get(url_string(app, query="{context { session request }}")) + + assert response.status_code == 200 + res = response_json(response) + assert "data" in res + assert "session" in res["data"]["context"] + assert "request" in res["data"]["context"] + assert "CUSTOM CONTEXT" in res["data"]["context"]["session"] + assert "Request" in res["data"]["context"]["request"] -@pytest.mark.parametrize('app', [create_app(get_context=lambda:"CUSTOM CONTEXT")]) -def test_supports_pretty_printing(client): - response = client.get(url_string(query='{context}')) +@pytest.mark.parametrize("app", [create_app(context="CUSTOM CONTEXT")]) +def test_context_remapped_if_not_mapping(app, client): + response = client.get(url_string(app, query="{context { session request }}")) assert response.status_code == 200 - assert response_json(response) == { - 'data': { - 'context': 'CUSTOM CONTEXT' - } - } + res = response_json(response) + assert "data" in res + assert "session" in res["data"]["context"] + assert "request" in res["data"]["context"] + assert "CUSTOM CONTEXT" not in res["data"]["context"]["request"] + assert "Request" in res["data"]["context"]["request"] -def test_post_multipart_data(client): - query = 'mutation TestMutation { writeTest { test } }' +def test_post_multipart_data(app, client): + query = "mutation TestMutation { writeTest { test } }" response = client.post( - url_string(), - data= { - 'query': query, - 'file': (StringIO(), 'text1.txt'), - }, - content_type='multipart/form-data' + url_string(app), + data={"query": query, "file": (StringIO(), "text1.txt")}, + content_type="multipart/form-data", ) assert response.status_code == 200 - assert response_json(response) == {'data': {u'writeTest': {u'test': u'Hello World'}}} + assert response_json(response) == { + "data": {u"writeTest": {u"test": u"Hello World"}} + } -@pytest.mark.parametrize('app', [create_app(batch=True)]) -def test_batch_allows_post_with_json_encoding(client): +@pytest.mark.parametrize("app", [create_app(batch=True)]) +def test_batch_allows_post_with_json_encoding(app, client): response = client.post( - url_string(), - data=jl( + url_string(app), + data=json_dump_kwarg_list( # id=1, - query='{test}' + query="{test}" ), - content_type='application/json' + content_type="application/json", ) assert response.status_code == 200 - assert response_json(response) == [{ - # 'id': 1, - 'data': {'test': "Hello World"} - }] + assert response_json(response) == [ + { + # 'id': 1, + "data": {"test": "Hello World"} + } + ] -@pytest.mark.parametrize('app', [create_app(batch=True)]) -def test_batch_supports_post_json_query_with_json_variables(client): +@pytest.mark.parametrize("app", [create_app(batch=True)]) +def test_batch_supports_post_json_query_with_json_variables(app, client): response = client.post( - url_string(), - data=jl( + url_string(app), + data=json_dump_kwarg_list( # id=1, - query='query helloWho($who: String){ test(who: $who) }', - variables={'who': "Dolly"} + query="query helloWho($who: String){ test(who: $who) }", + variables={"who": "Dolly"}, ), - content_type='application/json' + content_type="application/json", ) assert response.status_code == 200 - assert response_json(response) == [{ - # 'id': 1, - 'data': {'test': "Hello Dolly"} - }] - - -@pytest.mark.parametrize('app', [create_app(batch=True)]) -def test_batch_allows_post_with_operation_name(client): + assert response_json(response) == [ + { + # 'id': 1, + "data": {"test": "Hello Dolly"} + } + ] + + +@pytest.mark.parametrize("app", [create_app(batch=True)]) +def test_batch_allows_post_with_operation_name(app, client): response = client.post( - url_string(), - data=jl( + url_string(app), + data=json_dump_kwarg_list( # id=1, - query=''' + query=""" query helloYou { test(who: "You"), ...shared } query helloWorld { test(who: "World"), ...shared } query helloDolly { test(who: "Dolly"), ...shared } fragment shared on QueryRoot { shared: test(who: "Everyone") } - ''', - operationName='helloWorld' + """, + operationName="helloWorld", ), - content_type='application/json' + content_type="application/json", ) assert response.status_code == 200 - assert response_json(response) == [{ - # 'id': 1, - 'data': { - 'test': 'Hello World', - 'shared': 'Hello Everyone' + assert response_json(response) == [ + { + # 'id': 1, + "data": {"test": "Hello World", "shared": "Hello Everyone"} } - }] + ] diff --git a/tox.ini b/tox.ini index 303a459..72ab365 100644 --- a/tox.ini +++ b/tox.ini @@ -1,31 +1,32 @@ [tox] -envlist = flake8,import-order,py35,py27,py33,py34,py36,py37,pypy -skipsdist = true +envlist = + py{36,37,38} + flake8,import-order,manifest +; requires = tox-conda [testenv] +passenv = * setenv = PYTHONPATH = {toxinidir} -deps = - pytest>=2.7.2 - pytest-flask>=0.10.0 - graphql-core>=2.1,<3 - graphql-server-core>=1.1,<2 - Flask>=0.10.0 - pytest-cov -commands = - py{py,27,34,35,36,37}: py.test tests {posargs} +install_command = python -m pip install --pre --ignore-installed {opts} {packages} +deps = -e.[test] +commands = + pytest tests --cov-report=term-missing --cov=flask_graphql {posargs} [testenv:flake8] -basepython=python3.6 -deps = flake8 +basepython=python3.8 +deps = -e.[dev] commands = - flake8 flask_graphql + flake8 setup.py flask_graphql tests [testenv:import-order] -basepython=python3.6 -deps = - isort - graphql-core>=2.1 - Flask>=0.10.0 +basepython=python3.8 +deps = -e.[dev] +commands = + isort -rc flask_graphql/ tests/ + +[testenv:manifest] +basepython = python3.8 +deps = -e.[dev] commands = - isort --check-only flask_graphql/ -rc + check-manifest -v \ No newline at end of file pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy