-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Eric Hauser
committed
Sep 24, 2017
0 parents
commit 45ec699
Showing
18 changed files
with
1,400 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[run] | ||
omit = */tests/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.eggs | ||
.idea | ||
|
||
*.pyc | ||
|
||
build | ||
dist | ||
graphene_tornado.egg-info/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# graphene-tornado | ||
|
||
A project for running [Graphene](http://graphene-python.org/) on top of [Tornado](http://www.tornadoweb.org/) in Python 2.7. The codebase is a port of [graphene-django](https://github.com/graphql-python/graphene-django). | ||
|
||
# Examples | ||
|
||
See the [example](examples/example.py) application for an example on how to create GraphQL handlers in a Tornado project. |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import tornado.web | ||
from tornado.ioloop import IOLoop | ||
|
||
from graphene_tornado.schema import schema | ||
from graphene_tornado.tornado_graphql_handler import TornadoGraphQLHandler | ||
|
||
|
||
class ExampleApplication(tornado.web.Application): | ||
|
||
def __init__(self): | ||
handlers = [ | ||
(r'/graphql', TornadoGraphQLHandler, dict(graphiql=True, schema=schema)), | ||
(r'/graphql/batch', TornadoGraphQLHandler, dict(graphiql=True, schema=schema, batch=True)), | ||
(r'/graphql/graphiql', TornadoGraphQLHandler, dict(graphiql=True, schema=schema)) | ||
] | ||
tornado.web.Application.__init__(self, handlers) | ||
|
||
if __name__ == '__main__': | ||
app = ExampleApplication() | ||
app.listen(5000) | ||
IOLoop.instance().start() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
__version__ = '2.0.dev2017083101' | ||
|
||
__all__ = [ | ||
'__version__' | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
import json | ||
|
||
from jinja2 import Environment, Undefined | ||
from markupsafe import Markup | ||
|
||
GRAPHIQL_VERSION = '0.10.2' | ||
|
||
TEMPLATE = '''<!-- | ||
The request to this GraphQL server provided the header "Accept: text/html" | ||
and as a result has been presented GraphiQL - an in-browser IDE for | ||
exploring GraphQL. | ||
If you wish to receive JSON, provide the header "Accept: application/json" or | ||
add "&raw" to the end of the URL within a browser. | ||
--> | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>{{graphiql_html_title|default("GraphiQL", true)}}</title> | ||
<style> | ||
html, body { | ||
height: 100%; | ||
margin: 0; | ||
overflow: hidden; | ||
width: 100%; | ||
} | ||
</style> | ||
<meta name="referrer" content="no-referrer"> | ||
<link href="//cdn.jsdelivr.net/graphiql/{{graphiql_version}}/graphiql.css" rel="stylesheet" /> | ||
<script src="//cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script> | ||
<script src="//cdn.jsdelivr.net/react/15.0.0/react.min.js"></script> | ||
<script src="//cdn.jsdelivr.net/react/15.0.0/react-dom.min.js"></script> | ||
<script src="//cdn.jsdelivr.net/graphiql/{{graphiql_version}}/graphiql.min.js"></script> | ||
</head> | ||
<body> | ||
<script> | ||
// Collect the URL parameters | ||
var parameters = {}; | ||
window.location.search.substr(1).split('&').forEach(function (entry) { | ||
var eq = entry.indexOf('='); | ||
if (eq >= 0) { | ||
parameters[decodeURIComponent(entry.slice(0, eq))] = | ||
decodeURIComponent(entry.slice(eq + 1)); | ||
} | ||
}); | ||
// Produce a Location query string from a parameter object. | ||
function locationQuery(params) { | ||
return '?' + Object.keys(params).map(function (key) { | ||
return encodeURIComponent(key) + '=' + | ||
encodeURIComponent(params[key]); | ||
}).join('&'); | ||
} | ||
// Derive a fetch URL from the current URL, sans the GraphQL parameters. | ||
var graphqlParamNames = { | ||
query: true, | ||
variables: true, | ||
operationName: true | ||
}; | ||
var otherParams = {}; | ||
for (var k in parameters) { | ||
if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) { | ||
otherParams[k] = parameters[k]; | ||
} | ||
} | ||
var fetchURL = locationQuery(otherParams); | ||
// Defines a GraphQL fetcher using the fetch API. | ||
function graphQLFetcher(graphQLParams) { | ||
return fetch(fetchURL, { | ||
method: 'post', | ||
headers: { | ||
'Accept': 'application/json', | ||
'Content-Type': 'application/json' | ||
}, | ||
body: JSON.stringify(graphQLParams), | ||
credentials: 'include', | ||
}).then(function (response) { | ||
return response.text(); | ||
}).then(function (responseBody) { | ||
try { | ||
return JSON.parse(responseBody); | ||
} catch (error) { | ||
return responseBody; | ||
} | ||
}); | ||
} | ||
// When the query and variables string is edited, update the URL bar so | ||
// that it can be easily shared. | ||
function onEditQuery(newQuery) { | ||
parameters.query = newQuery; | ||
updateURL(); | ||
} | ||
function onEditVariables(newVariables) { | ||
parameters.variables = newVariables; | ||
updateURL(); | ||
} | ||
function onEditOperationName(newOperationName) { | ||
parameters.operationName = newOperationName; | ||
updateURL(); | ||
} | ||
function updateURL() { | ||
history.replaceState(null, null, locationQuery(parameters)); | ||
} | ||
// Render <GraphiQL /> into the body. | ||
ReactDOM.render( | ||
React.createElement(GraphiQL, { | ||
fetcher: graphQLFetcher, | ||
onEditQuery: onEditQuery, | ||
onEditVariables: onEditVariables, | ||
onEditOperationName: onEditOperationName, | ||
query: {{ query|tojson }}, | ||
response: {{ result|tojson }}, | ||
variables: {{ variables|tojson }}, | ||
operationName: {{ operation_name|tojson }}, | ||
}), | ||
document.body | ||
); | ||
</script> | ||
</body> | ||
</html>''' | ||
|
||
_slash_escape = '\\/' not in json.dumps('/') | ||
jinja_options = { | ||
'extensions': ['jinja2.ext.autoescape', 'jinja2.ext.with_'] | ||
} | ||
|
||
|
||
# TODO Memoize | ||
def create_jinja_environment(): | ||
options = dict(jinja_options) | ||
options['autoescape'] = True | ||
rv = Environment() | ||
rv.filters['tojson'] = tojson_filter | ||
return rv | ||
|
||
|
||
def tojson_filter(obj, **kwargs): | ||
if isinstance(obj, Undefined): | ||
return str(obj) | ||
return Markup(htmlsafe_dumps(obj, **kwargs)) | ||
|
||
|
||
def htmlsafe_dumps(obj, **kwargs): | ||
"""Works exactly like :func:`dumps` but is safe for use in ``<script>`` | ||
tags. It accepts the same arguments and returns a JSON string. Note that | ||
this is available in templates through the ``|tojson`` filter which will | ||
also mark the result as safe. Due to how this function escapes certain | ||
characters this is safe even if used outside of ``<script>`` tags. | ||
The following characters are escaped in strings: | ||
- ``<`` | ||
- ``>`` | ||
- ``&`` | ||
- ``'`` | ||
This makes it safe to embed such strings in any place in HTML with the | ||
notable exception of double quoted attributes. In that case single | ||
quote your attributes or HTML escape it in addition. | ||
.. versionchanged:: 0.10 | ||
This function's return value is now always safe for HTML usage, even | ||
if outside of script tags or if used in XHTML. This rule does not | ||
hold true when using this function in HTML attributes that are double | ||
quoted. Always single quote attributes if you use the ``|tojson`` | ||
filter. Alternatively use ``|tojson|forceescape``. | ||
""" | ||
rv = json.dumps(obj, **kwargs) \ | ||
.replace(u'<', u'\\u003c') \ | ||
.replace(u'>', u'\\u003e') \ | ||
.replace(u'&', u'\\u0026') \ | ||
.replace(u"'", u'\\u0027') | ||
if not _slash_escape: | ||
rv = rv.replace('\\/', '/') | ||
return rv | ||
|
||
|
||
def render_graphiql(query, variables, operation_name, result, graphiql_version=None, | ||
graphiql_template=None, graphiql_html_title=None): | ||
graphiql_version = graphiql_version or GRAPHIQL_VERSION | ||
template = graphiql_template or TEMPLATE | ||
|
||
jinja = create_jinja_environment() | ||
context = dict( | ||
graphiql_version=graphiql_version, | ||
graphiql_html_title=graphiql_html_title, | ||
result=result, | ||
query=query, | ||
variables=variables, | ||
operation_name=operation_name | ||
) | ||
|
||
tmpl = jinja.from_string(template) | ||
return tmpl.render(context) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
from __future__ import absolute_import, division, print_function | ||
|
||
import graphene | ||
from graphene import ObjectType, Schema | ||
|
||
|
||
class QueryRoot(ObjectType): | ||
|
||
thrower = graphene.String(required=True) | ||
request = graphene.String(required=True) | ||
test = graphene.String(who=graphene.String()) | ||
|
||
def resolve_thrower(self, info): | ||
raise Exception("Throws!") | ||
|
||
def resolve_request(self, info): | ||
return info.context.arguments['q'][0] | ||
|
||
def resolve_test(self, info, who=None): | ||
return 'Hello %s' % (who or 'World') | ||
|
||
|
||
class MutationRoot(ObjectType): | ||
write_test = graphene.Field(QueryRoot) | ||
|
||
def resolve_write_test(self, info): | ||
return QueryRoot() | ||
|
||
|
||
schema = Schema(query=QueryRoot, mutation=MutationRoot) | ||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import json | ||
import urllib | ||
|
||
from tornado.testing import AsyncHTTPTestCase | ||
|
||
from examples.example import ExampleApplication | ||
|
||
|
||
class BaseTestCase(AsyncHTTPTestCase): | ||
|
||
def get_app(self): | ||
self.app = ExampleApplication() | ||
return self.app | ||
|
||
def setUp(self): | ||
pass | ||
super(BaseTestCase, self).setUp() | ||
|
||
def get(self, url, **kwargs): | ||
return self.http_client.fetch(self.get_url(url), **kwargs) | ||
|
||
def delete(self, url, **kwargs): | ||
kwargs['method'] = kwargs.get('method', 'DELETE') | ||
return self.get(url, **kwargs) | ||
|
||
def post(self, url, post_data, **kwargs): | ||
kwargs['method'] = kwargs.get('method', 'POST') | ||
kwargs['body'] = urllib.urlencode(post_data) | ||
return self.get(url, **kwargs) | ||
|
||
def post_body(self, url, **kwargs): | ||
kwargs['method'] = kwargs.get('method', 'POST') | ||
return self.http_client.fetch(self.get_url(url), **kwargs) | ||
|
||
def post_json(self, url, post_data, **kwargs): | ||
kwargs['method'] = kwargs.get('method', 'POST') | ||
kwargs['body'] = json.dumps(post_data) | ||
kwargs['headers'] = kwargs.get('headers', {}) | ||
kwargs['headers']['Content-Type'] = "application/json" | ||
return self.get(url, **kwargs) | ||
|
||
def put(self, url, put_data, **kwargs): | ||
kwargs['method'] = kwargs.get('method', 'PUT') | ||
return self.post(url, put_data, **kwargs) | ||
|
||
def get_url(self, path): | ||
return '%s://localhost:%s%s' % (self.get_protocol(), | ||
self.get_http_port(), path) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from graphql.type.definition import GraphQLArgument, GraphQLField, GraphQLNonNull, GraphQLObjectType | ||
from graphql.type.scalars import GraphQLString | ||
from graphql.type.schema import GraphQLSchema | ||
from tornado.gen import coroutine, Return | ||
|
||
|
||
@coroutine | ||
def resolve_raises(*_): | ||
raise Exception("Throws!") | ||
|
||
|
||
@coroutine | ||
def resolve1(obj, args, context, info): | ||
raise Return(context.args.get('q')) | ||
|
||
|
||
@coroutine | ||
def resolve2(obj, args, context, info): | ||
raise Return(context) | ||
|
||
|
||
@coroutine | ||
def resolve3(obj, args, context, info): | ||
raise Return('Hello %s' % (args.get('who') or 'World')) | ||
|
||
|
||
QueryRootType = GraphQLObjectType( | ||
name='QueryRoot', | ||
fields={ | ||
'thrower': GraphQLField(GraphQLNonNull(GraphQLString), resolver=resolve_raises), | ||
'request': GraphQLField(GraphQLNonNull(GraphQLString), | ||
resolver=resolve1), | ||
'context': GraphQLField(GraphQLNonNull(GraphQLString), | ||
resolver=resolve2), | ||
'test': GraphQLField( | ||
type=GraphQLString, | ||
args={ | ||
'who': GraphQLArgument(GraphQLString) | ||
}, | ||
resolver=resolve3 | ||
) | ||
} | ||
) | ||
|
||
MutationRootType = GraphQLObjectType( | ||
name='MutationRoot', | ||
fields={ | ||
'writeTest': GraphQLField( | ||
type=QueryRootType, | ||
resolver=lambda *_: QueryRootType | ||
) | ||
} | ||
) | ||
|
||
Schema = GraphQLSchema(QueryRootType, MutationRootType) |
Oops, something went wrong.