├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── flask_graphql └── __init__.py ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── app.py ├── schema.py ├── test_graphiqlview.py └── test_graphqlview.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/python,intellij+all,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=python,intellij+all,visualstudiocode 4 | 5 | ### Intellij+all ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # Generated files 17 | .idea/**/contentModel.xml 18 | 19 | # Sensitive or high-churn files 20 | .idea/**/dataSources/ 21 | .idea/**/dataSources.ids 22 | .idea/**/dataSources.local.xml 23 | .idea/**/sqlDataSources.xml 24 | .idea/**/dynamic.xml 25 | .idea/**/uiDesigner.xml 26 | .idea/**/dbnavigator.xml 27 | 28 | # Gradle 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | 32 | # Gradle and Maven with auto-import 33 | # When using Gradle or Maven with auto-import, you should exclude module files, 34 | # since they will be recreated, and may cause churn. Uncomment if using 35 | # auto-import. 36 | # .idea/modules.xml 37 | # .idea/*.iml 38 | # .idea/modules 39 | # *.iml 40 | # *.ipr 41 | 42 | # CMake 43 | cmake-build-*/ 44 | 45 | # Mongo Explorer plugin 46 | .idea/**/mongoSettings.xml 47 | 48 | # File-based project format 49 | *.iws 50 | 51 | # IntelliJ 52 | out/ 53 | 54 | # mpeltonen/sbt-idea plugin 55 | .idea_modules/ 56 | 57 | # JIRA plugin 58 | atlassian-ide-plugin.xml 59 | 60 | # Cursive Clojure plugin 61 | .idea/replstate.xml 62 | 63 | # Crashlytics plugin (for Android Studio and IntelliJ) 64 | com_crashlytics_export_strings.xml 65 | crashlytics.properties 66 | crashlytics-build.properties 67 | fabric.properties 68 | 69 | # Editor-based Rest Client 70 | .idea/httpRequests 71 | 72 | # Android studio 3.1+ serialized cache file 73 | .idea/caches/build_file_checksums.ser 74 | 75 | ### Intellij+all Patch ### 76 | # Ignores the whole .idea folder and all .iml files 77 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 78 | 79 | .idea/ 80 | 81 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 82 | 83 | *.iml 84 | modules.xml 85 | .idea/misc.xml 86 | *.ipr 87 | 88 | # Sonarlint plugin 89 | .idea/sonarlint 90 | 91 | ### Python ### 92 | # Byte-compiled / optimized / DLL files 93 | __pycache__/ 94 | *.py[cod] 95 | *$py.class 96 | 97 | # C extensions 98 | *.so 99 | 100 | # Distribution / packaging 101 | .Python 102 | build/ 103 | develop-eggs/ 104 | dist/ 105 | downloads/ 106 | eggs/ 107 | .eggs/ 108 | lib/ 109 | lib64/ 110 | parts/ 111 | sdist/ 112 | var/ 113 | wheels/ 114 | pip-wheel-metadata/ 115 | share/python-wheels/ 116 | *.egg-info/ 117 | .installed.cfg 118 | *.egg 119 | MANIFEST 120 | 121 | # PyInstaller 122 | # Usually these files are written by a python script from a template 123 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 124 | *.manifest 125 | *.spec 126 | 127 | # Installer logs 128 | pip-log.txt 129 | pip-delete-this-directory.txt 130 | 131 | # Unit test / coverage reports 132 | htmlcov/ 133 | .tox/ 134 | .nox/ 135 | .venv/ 136 | .coverage 137 | .coverage.* 138 | .cache 139 | nosetests.xml 140 | coverage.xml 141 | *.cover 142 | .hypothesis/ 143 | .pytest_cache/ 144 | 145 | # Translations 146 | *.mo 147 | *.pot 148 | 149 | # Scrapy stuff: 150 | .scrapy 151 | 152 | # Sphinx documentation 153 | docs/_build/ 154 | 155 | # PyBuilder 156 | target/ 157 | 158 | # pyenv 159 | .python-version 160 | 161 | # pipenv 162 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 163 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 164 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 165 | # install all needed dependencies. 166 | #Pipfile.lock 167 | 168 | # celery beat schedule file 169 | celerybeat-schedule 170 | 171 | # SageMath parsed files 172 | *.sage.py 173 | 174 | # Spyder project settings 175 | .spyderproject 176 | .spyproject 177 | 178 | # Rope project settings 179 | .ropeproject 180 | 181 | # Mr Developer 182 | .mr.developer.cfg 183 | .project 184 | .pydevproject 185 | 186 | # mkdocs documentation 187 | /site 188 | 189 | # mypy 190 | .mypy_cache/ 191 | .dmypy.json 192 | dmypy.json 193 | 194 | # Pyre type checker 195 | .pyre/ 196 | 197 | ### VisualStudioCode ### 198 | .vscode 199 | 200 | ### VisualStudioCode Patch ### 201 | # Ignore all local history of files 202 | .history 203 | 204 | # End of https://www.gitignore.io/api/python,intellij+all,visualstudiocode 205 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | python: 4 | - 3.6 5 | - 3.7 6 | - 3.8 7 | cache: pip 8 | 9 | install: 10 | - pip install tox-travis 11 | 12 | script: 13 | - tox 14 | 15 | after_success: 16 | - pip install coveralls 17 | - coveralls 18 | 19 | deploy: 20 | provider: pypi 21 | user: syrusakbary 22 | on: 23 | tags: true 24 | password: 25 | secure: GB3YHihKAbmfh9kzMxzsZQDMf5h1aIJYwESfaYMc4DjMA1Qms+ZhBN2RiH3irwJ6FW47ZsS/O6ZsrlNxu75J/Mc1NJfzFev1d0xt7cYp+s0umYHhheelR6wmP8KOt3ugK7qmWuk5bykljpxsRKzKJCvGH+LOM7mDQy3NZOfYPTAM2znWjuBr+X4iUv6pUCKk5N20GBbs9T+jNttE7K8TH4zuXCWxgbE7xVHth76pB5Q/9FZkB8hZQ7K2esO3QyajDO7FzsOk8Z/jXRJ3MOxZCI3vGgmSzKTH4fMqmSrtyr1sCaBO5tgS8ytqQBjsuV1vIWl+75bXrAXfdkin63zMne4Rsb+uSWj3djP+iy2yML8a2mWMizxr803v8lwaGnMZ26f4rwdZnHGUPlTp3geVKq23vidVTQwF8v2o1rHvtdD4KJ5Mi41TXAfnih3XUf+fCTXdbAXKqweDuhcZg09/r7U/6zo76wjnt1cePPZe63/xG6bAVQ+Gz1J+HZz55ofDZV9g9kwyNll4Jfdzj9hUHO8AfBMvXQJewRj/LbzbmbBp5peov+DFhx7TWofvqxjreVKxDiDN89pC+WKy5BwZMcpB3dRVGuZ25ZrENLgoTX7W4PHPb1+OF4edP6xM44egcJLamk7vhvpZQqukJGRQZFtIMw8KIkC7OWzpCFIXN08= 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Syrus Akbary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | 4 | include tox.ini 5 | include Makefile 6 | 7 | recursive-include flask_graphql *.py 8 | recursive-include tests *.py 9 | 10 | global-exclude *.py[co] __pycache__ 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | dev-setup: 2 | python pip install -e ".[test]" 3 | 4 | tests: 5 | py.test tests --cov=flask_graphql -vv -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask-GraphQL 2 | 3 | Adds GraphQL support to your Flask application. 4 | 5 | [![travis][travis-image]][travis-url] 6 | [![pypi][pypi-image]][pypi-url] 7 | [![Anaconda-Server Badge][conda-image]][conda-url] 8 | [![coveralls][coveralls-image]][coveralls-url] 9 | 10 | [travis-image]: https://travis-ci.org/graphql-python/flask-graphql.svg?branch=master 11 | [travis-url]: https://travis-ci.org/graphql-python/flask-graphql 12 | [pypi-image]: https://img.shields.io/pypi/v/flask-graphql.svg?style=flat 13 | [pypi-url]: https://pypi.org/project/flask-graphql/ 14 | [coveralls-image]: https://coveralls.io/repos/graphql-python/flask-graphql/badge.svg?branch=master&service=github 15 | [coveralls-url]: https://coveralls.io/github/graphql-python/flask-graphql?branch=master 16 | [conda-image]: https://img.shields.io/conda/vn/conda-forge/flask-graphql.svg 17 | [conda-url]: https://anaconda.org/conda-forge/flask-graphql 18 | 19 | ## Usage 20 | 21 | Just use the `GraphQLView` view from `flask_graphql` 22 | 23 | ```python 24 | from flask import Flask 25 | from flask_graphql import GraphQLView 26 | 27 | from schema import schema 28 | 29 | app = Flask(__name__) 30 | 31 | app.add_url_rule('/graphql', view_func=GraphQLView.as_view( 32 | 'graphql', 33 | schema=schema, 34 | graphiql=True, 35 | )) 36 | 37 | # Optional, for adding batch query support (used in Apollo-Client) 38 | app.add_url_rule('/graphql/batch', view_func=GraphQLView.as_view( 39 | 'graphql', 40 | schema=schema, 41 | batch=True 42 | )) 43 | 44 | if __name__ == '__main__': 45 | app.run() 46 | ``` 47 | 48 | This will add `/graphql` endpoint to your app and enable the GraphiQL IDE. 49 | 50 | ### Special Note for Graphene v3 51 | 52 | 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. 53 | 54 | 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). 55 | 56 | 57 | ### Supported options for GraphQLView 58 | 59 | * `schema`: The `GraphQLSchema` object that you want the view to execute when it gets a valid request. 60 | * `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`. 61 | * `root_value`: The `root_value` you want to provide to graphql `execute`. 62 | * `pretty`: Whether or not you want the response to be pretty printed JSON. 63 | * `graphiql`: If `True`, may present [GraphiQL](https://github.com/graphql/graphiql) when loaded directly from a browser (a useful tool for debugging and exploration). 64 | * `graphiql_version`: The graphiql version to load. Defaults to **"1.0.3"**. 65 | * `graphiql_template`: Inject a Jinja template string to customize GraphiQL. 66 | * `graphiql_html_title`: The graphiql title to display. Defaults to **"GraphiQL"**. 67 | * `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)) 68 | * `middleware`: A list of graphql [middlewares](http://docs.graphene-python.org/en/latest/execution/middleware/). 69 | * `encode`: the encoder to use for responses (sensibly defaults to `graphql_server.json_encode`). 70 | * `format_error`: the error formatter to use for responses (sensibly defaults to `graphql_server.default_format_error`. 71 | * `subscriptions`: The GraphiQL socket endpoint for using subscriptions in graphql-ws. 72 | * `headers`: An optional GraphQL string to use as the initial displayed request headers, if not provided, the stored headers will be used. 73 | * `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. 74 | * `header_editor_enabled`: An optional boolean which enables the header editor when true. Defaults to **false**. 75 | * `should_persist_headers`: An optional boolean which enables to persist headers to storage when true. Defaults to **false**. 76 | 77 | You can also subclass `GraphQLView` and overwrite `get_root_value(self, request)` to have a dynamic root value 78 | per request. 79 | 80 | ```python 81 | class UserRootValue(GraphQLView): 82 | def get_root_value(self, request): 83 | return request.user 84 | 85 | ``` 86 | 87 | ## Contributing 88 | 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). 89 | -------------------------------------------------------------------------------- /flask_graphql/__init__.py: -------------------------------------------------------------------------------- 1 | from graphql_server.flask.graphqlview import GraphQLView 2 | 3 | __all__ = ['GraphQLView'] 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = tests,scripts,setup.py,docs 3 | max-line-length = 88 4 | 5 | [isort] 6 | known_first_party=graphql 7 | 8 | [tool:pytest] 9 | norecursedirs = venv .venv .tox .git .cache .mypy_cache .pytest_cache 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | install_requires = [ 4 | "graphql-server[flask]>=3.0.0b1", 5 | ] 6 | 7 | tests_requires = [ 8 | "pytest>=5.4,<5.5", 9 | "pytest-cov>=2.8,<3", 10 | ] 11 | 12 | dev_requires = [ 13 | "flake8>=3.7,<4", 14 | "isort>=4,<5", 15 | "check-manifest>=0.40,<1", 16 | ] + tests_requires 17 | 18 | with open("README.md", encoding="utf-8") as readme_file: 19 | readme = readme_file.read() 20 | 21 | setup( 22 | name="Flask-GraphQL", 23 | version="2.0.1", 24 | description="Adds GraphQL support to your Flask application", 25 | long_description=readme, 26 | long_description_content_type="text/markdown", 27 | url="https://github.com/graphql-python/flask-graphql", 28 | download_url="https://github.com/graphql-python/flask-graphql/releases", 29 | author="Syrus Akbary", 30 | author_email="me@syrusakbary.com", 31 | license="MIT", 32 | classifiers=[ 33 | "Development Status :: 5 - Production/Stable", 34 | "Intended Audience :: Developers", 35 | "Topic :: Software Development :: Libraries", 36 | "Programming Language :: Python :: 3.6", 37 | "Programming Language :: Python :: 3.7", 38 | "Programming Language :: Python :: 3.8", 39 | "License :: OSI Approved :: MIT License", 40 | ], 41 | keywords="api graphql protocol rest flask", 42 | packages=find_packages(exclude=["tests"]), 43 | install_requires=install_requires, 44 | tests_require=tests_requires, 45 | extras_require={ 46 | 'test': tests_requires, 47 | 'dev': dev_requires, 48 | }, 49 | include_package_data=True, 50 | zip_safe=False, 51 | platforms="any", 52 | ) 53 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql-python/flask-graphql/0d45561607950b68458925537f116298fc81b4d3/tests/__init__.py -------------------------------------------------------------------------------- /tests/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | from flask_graphql import GraphQLView 4 | from tests.schema import Schema 5 | 6 | 7 | def create_app(path="/graphql", **kwargs): 8 | app = Flask(__name__) 9 | app.debug = True 10 | app.add_url_rule( 11 | path, view_func=GraphQLView.as_view("graphql", schema=Schema, **kwargs) 12 | ) 13 | return app 14 | 15 | 16 | if __name__ == "__main__": 17 | app = create_app(graphiql=True) 18 | app.run() 19 | -------------------------------------------------------------------------------- /tests/schema.py: -------------------------------------------------------------------------------- 1 | from graphql.type.definition import (GraphQLArgument, GraphQLField, 2 | GraphQLNonNull, GraphQLObjectType) 3 | from graphql.type.scalars import GraphQLString 4 | from graphql.type.schema import GraphQLSchema 5 | 6 | 7 | def resolve_raises(*_): 8 | raise Exception("Throws!") 9 | 10 | 11 | QueryRootType = GraphQLObjectType( 12 | name="QueryRoot", 13 | fields={ 14 | "thrower": GraphQLField(GraphQLNonNull(GraphQLString), resolve=resolve_raises), 15 | "request": GraphQLField( 16 | GraphQLNonNull(GraphQLString), 17 | resolve=lambda obj, info: info.context["request"].args.get("q"), 18 | ), 19 | "context": GraphQLField( 20 | GraphQLObjectType( 21 | name="context", 22 | fields={ 23 | "session": GraphQLField(GraphQLString), 24 | "request": GraphQLField( 25 | GraphQLNonNull(GraphQLString), 26 | resolve=lambda obj, info: info.context["request"], 27 | ), 28 | }, 29 | ), 30 | resolve=lambda obj, info: info.context, 31 | ), 32 | "test": GraphQLField( 33 | type_=GraphQLString, 34 | args={"who": GraphQLArgument(GraphQLString)}, 35 | resolve=lambda obj, info, who="World": "Hello %s" % who, 36 | ), 37 | }, 38 | ) 39 | 40 | MutationRootType = GraphQLObjectType( 41 | name="MutationRoot", 42 | fields={ 43 | "writeTest": GraphQLField(type_=QueryRootType, resolve=lambda *_: QueryRootType) 44 | }, 45 | ) 46 | 47 | Schema = GraphQLSchema(QueryRootType, MutationRootType) 48 | -------------------------------------------------------------------------------- /tests/test_graphiqlview.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from flask import url_for 3 | 4 | from .app import create_app 5 | 6 | 7 | @pytest.fixture 8 | def app(): 9 | # import app factory pattern 10 | app = create_app(graphiql=True) 11 | 12 | # pushes an application context manually 13 | ctx = app.app_context() 14 | ctx.push() 15 | return app 16 | 17 | 18 | @pytest.fixture 19 | def client(app): 20 | return app.test_client() 21 | 22 | 23 | def test_graphiql_is_enabled(app, client): 24 | with app.test_request_context(): 25 | response = client.get( 26 | url_for("graphql", externals=False), headers={"Accept": "text/html"} 27 | ) 28 | assert response.status_code == 200 29 | 30 | 31 | def test_graphiql_renders_pretty(app, client): 32 | with app.test_request_context(): 33 | response = client.get( 34 | url_for("graphql", query="{test}"), headers={"Accept": "text/html"} 35 | ) 36 | assert response.status_code == 200 37 | pretty_response = ( 38 | "{\n" 39 | ' "data": {\n' 40 | ' "test": "Hello World"\n' 41 | " }\n" 42 | "}".replace('"', '\\"').replace("\n", "\\n") 43 | ) 44 | 45 | assert pretty_response in response.data.decode("utf-8") 46 | 47 | 48 | def test_graphiql_default_title(app, client): 49 | with app.test_request_context(): 50 | response = client.get(url_for("graphql"), headers={"Accept": "text/html"}) 51 | assert "