Skip to content

Commit

Permalink
Merge flask-graphql (#37)
Browse files Browse the repository at this point in the history
* refactor: add flask-graphql as optional feature

* refactor(server): default_format_error to __all__

* chore: rename dir flask-graphql to flask

* chore: add extras require all key

* chore: update gitignore

* fix(sc): move params query check to try-except

* refactor(flask): remove unused backend param

* tests(flask): graphiqlview and graphqlview

* styles: apply black, isort, flake8 formatting

* chore: add all requires to test env

* chore(flask): remove blueprint module

* refactor(flask): remove py27 imports and unused test

* styles: apply black, isort and flake8 formatting
  • Loading branch information
KingDarBoja authored May 5, 2020
1 parent 865ee9d commit 66b8a2b
Show file tree
Hide file tree
Showing 11 changed files with 1,213 additions and 18 deletions.
208 changes: 196 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,19 +1,203 @@
*.pyc
*.pyo

# 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

.cache
# 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
.idea
.mypy_cache
.pytest_cache
.tox
.venv
.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

/build/
/dist/
### VisualStudioCode Patch ###
# Ignore all local history of files
.history

docs
# End of https://www.gitignore.io/api/python,intellij+all,visualstudiocode
7 changes: 4 additions & 3 deletions graphql_server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"GraphQLResponse",
"ServerResponse",
"format_execution_result",
"format_error_default",
]


Expand Down Expand Up @@ -230,11 +231,11 @@ def get_response(
as a parameter.
"""

if not params.query:
raise HttpQueryError(400, "Must provide query string.")

# noinspection PyBroadException
try:
if not params.query:
raise HttpQueryError(400, "Must provide query string.")

# Parse document to trigger a new HttpQueryError if allow_only_query is True
try:
document = parse(params.query)
Expand Down
3 changes: 3 additions & 0 deletions graphql_server/flask/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .graphqlview import GraphQLView

__all__ = ["GraphQLView"]
151 changes: 151 additions & 0 deletions graphql_server/flask/graphqlview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
from functools import partial

from flask import Response, request
from flask.views import View
from graphql.error import GraphQLError
from graphql.type.schema import GraphQLSchema

from graphql_server import (
HttpQueryError,
encode_execution_results,
format_error_default,
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
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_value(self):
return request

def get_middleware(self):
return self.middleware

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(format_error_default)
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,
# Execute options
root_value=self.get_root_value(),
context_value=self.get_context_value(),
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:
parsed_error = GraphQLError(e.message)
return Response(
self.encode(dict(errors=[self.format_error(parsed_error)])),
status=e.status_code,
headers=e.headers,
content_type="application/json",
)

# Flask
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"]
)
Loading

0 comments on commit 66b8a2b

Please sign in to comment.