Skip to content

Commit 33debff

Browse files
committed
Initial version of the GraphQL server core package
0 parents  commit 33debff

14 files changed

+934
-0
lines changed

.gitignore

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
*.pyc
2+
.idea
3+
.cache
4+
.tox
5+
*.egg
6+
*.egg-info
7+
.coverage
8+
/build/
9+
10+
/dist/

.travis.yml

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
language: python
2+
sudo: false
3+
matrix:
4+
include:
5+
- python: pypy
6+
env: TOX_ENV=pypy
7+
- python: '2.7'
8+
env: TOX_ENV=py27
9+
- python: '3.3'
10+
env: TOX_ENV=py33
11+
- python: '3.4'
12+
env: TOX_ENV=py34
13+
- python: '3.5'
14+
env: TOX_ENV=py35,import-order,flake8
15+
cache:
16+
directories:
17+
- $HOME/.cache/pip
18+
- $TRAVIS_BUILD_DIR/.tox
19+
install:
20+
- pip install tox coveralls
21+
script:
22+
- tox -e $TOX_ENV -- --cov=flask_graphql
23+
after_success:
24+
- coveralls
25+
deploy:
26+
provider: pypi
27+
user: syrusakbary
28+
on:
29+
tags: true
30+
password:
31+
secure: GB3YHihKAbmfh9kzMxzsZQDMf5h1aIJYwESfaYMc4DjMA1Qms+ZhBN2RiH3irwJ6FW47ZsS/O6ZsrlNxu75J/Mc1NJfzFev1d0xt7cYp+s0umYHhheelR6wmP8KOt3ugK7qmWuk5bykljpxsRKzKJCvGH+LOM7mDQy3NZOfYPTAM2znWjuBr+X4iUv6pUCKk5N20GBbs9T+jNttE7K8TH4zuXCWxgbE7xVHth76pB5Q/9FZkB8hZQ7K2esO3QyajDO7FzsOk8Z/jXRJ3MOxZCI3vGgmSzKTH4fMqmSrtyr1sCaBO5tgS8ytqQBjsuV1vIWl+75bXrAXfdkin63zMne4Rsb+uSWj3djP+iy2yML8a2mWMizxr803v8lwaGnMZ26f4rwdZnHGUPlTp3geVKq23vidVTQwF8v2o1rHvtdD4KJ5Mi41TXAfnih3XUf+fCTXdbAXKqweDuhcZg09/r7U/6zo76wjnt1cePPZe63/xG6bAVQ+Gz1J+HZz55ofDZV9g9kwyNll4Jfdzj9hUHO8AfBMvXQJewRj/LbzbmbBp5peov+DFhx7TWofvqxjreVKxDiDN89pC+WKy5BwZMcpB3dRVGuZ25ZrENLgoTX7W4PHPb1+OF4edP6xM44egcJLamk7vhvpZQqukJGRQZFtIMw8KIkC7OWzpCFIXN08=

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2017-Present 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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include README.md

README.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# GraphQL-Server
2+
3+
[![Build Status](https://travis-ci.org/graphql-python/graphql-server.svg?branch=master)](https://travis-ci.org/graphql-python/graphql-server) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphql-server/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphql-server?branch=master) [![PyPI version](https://badge.fury.io/py/graphql-server.svg)](https://badge.fury.io/py/graphql-server)
4+
5+
GraphQL Server core package.
6+
7+
## Integrations
8+
9+
GraphQL Server powers the following integrations
10+
11+
| Server integration | Package |
12+
|---------------|-------------------|
13+
| Django | [graphene-django](https://github.com/graphql-python/graphene-django/) |
14+
| Flask | [flask-graphql](https://github.com/graphql-python/flask-graphql/) |
15+
| Sanic | [sanic-graphql](https://github.com/graphql-python/sanic-graphql/) |
16+
| WebOb (Pyramid, Pylons) | [webob-graphql](https://github.com/graphql-python/webob-graphql/) |

README.rst

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
GraphQL-Server
2+
==============
3+
4+
|Build Status| |Coverage Status| |PyPI version|
5+
6+
GraphQL Server core package.
7+
8+
Integrations
9+
------------
10+
11+
GraphQL Server powers the following integrations
12+
13+
+---------------------------+----------------------------------------------------------------------------+
14+
| Server integration | Package |
15+
+===========================+============================================================================+
16+
| Django | `graphene-django <https://github.com/graphql-python/graphene-django/>`__ |
17+
+---------------------------+----------------------------------------------------------------------------+
18+
| Flask | `flask-graphql <https://github.com/graphql-python/flask-graphql/>`__ |
19+
+---------------------------+----------------------------------------------------------------------------+
20+
| Sanic | `sanic-graphql <https://github.com/graphql-python/sanic-graphql/>`__ |
21+
+---------------------------+----------------------------------------------------------------------------+
22+
| WebOb (Pyramid, Pylons) | `webob-graphql <https://github.com/graphql-python/webob-graphql/>`__ |
23+
+---------------------------+----------------------------------------------------------------------------+
24+
25+
.. |Build Status| image:: https://travis-ci.org/graphql-python/graphql-server.svg?branch=master
26+
:target: https://travis-ci.org/graphql-python/graphql-server
27+
.. |Coverage Status| image:: https://coveralls.io/repos/graphql-python/graphql-server/badge.svg?branch=master&service=github
28+
:target: https://coveralls.io/github/graphql-python/graphql-server?branch=master
29+
.. |PyPI version| image:: https://badge.fury.io/py/graphql-server.svg
30+
:target: https://badge.fury.io/py/graphql-server

bin/convert_documentation

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
pandoc README.md --from markdown --to rst -s -o README.rst

graphql_server/__init__.py

+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import json
2+
from collections import namedtuple, MutableMapping
3+
4+
import six
5+
from graphql import Source, execute, parse, validate
6+
from graphql.error import format_error as format_graphql_error
7+
from graphql.error import GraphQLError
8+
from graphql.execution import ExecutionResult
9+
from graphql.type.schema import GraphQLSchema
10+
from graphql.utils.get_operation_ast import get_operation_ast
11+
12+
from .error import HttpQueryError
13+
14+
15+
class SkipException(Exception):
16+
pass
17+
18+
19+
GraphQLParams = namedtuple('GraphQLParams', 'query,variables,operation_name')
20+
GraphQLResponse = namedtuple('GraphQLResponse', 'result,status_code')
21+
22+
23+
def default_format_error(error):
24+
if isinstance(error, GraphQLError):
25+
return format_graphql_error(error)
26+
27+
return {'message': six.text_type(error)}
28+
29+
30+
def run_http_query(schema, request_method, data, query_data=None, batch_enabled=False, catch=False, **execute_options):
31+
if request_method not in ('get', 'post'):
32+
raise HttpQueryError(
33+
405,
34+
'GraphQL only supports GET and POST requests.',
35+
headers={
36+
'Allow': 'GET, POST'
37+
}
38+
)
39+
if catch:
40+
catch = HttpQueryError
41+
else:
42+
catch = SkipException
43+
is_batch = isinstance(data, list)
44+
45+
is_get_request = request_method == 'get'
46+
allow_only_query = is_get_request
47+
48+
if not is_batch:
49+
if not isinstance(data, (dict, MutableMapping)):
50+
raise HttpQueryError(
51+
400,
52+
'GraphQL params should be a dict. Received {}.'.format(data)
53+
)
54+
data = [data]
55+
elif not batch_enabled:
56+
raise HttpQueryError(
57+
400,
58+
'Batch GraphQL requests are not enabled.'
59+
)
60+
61+
if not data:
62+
raise HttpQueryError(
63+
400,
64+
'Received an empty list in the batch request.'
65+
)
66+
67+
extra_data = {}
68+
# If is a batch request, we don't consume the data from the query
69+
if not is_batch:
70+
extra_data = query_data or {}
71+
72+
all_params = [get_graphql_params(entry, extra_data) for entry in data]
73+
74+
responses = [get_response(
75+
schema,
76+
params,
77+
catch,
78+
allow_only_query,
79+
**execute_options
80+
) for params in all_params]
81+
82+
return responses, all_params
83+
84+
85+
def encode_execution_results(execution_results, format_error, is_batch, encode):
86+
responses = [
87+
format_execution_result(execution_result, format_error)
88+
for execution_result in execution_results
89+
]
90+
result, status_codes = zip(*responses)
91+
status_code = max(status_codes)
92+
93+
if not is_batch:
94+
result = result[0]
95+
96+
return encode(result), status_code
97+
98+
99+
def json_encode(data, pretty=False):
100+
if not pretty:
101+
return json.dumps(data, separators=(',', ':'))
102+
103+
return json.dumps(
104+
data,
105+
indent=2,
106+
separators=(',', ': ')
107+
)
108+
109+
110+
def load_json_variables(variables):
111+
if variables and isinstance(variables, six.string_types):
112+
try:
113+
return json.loads(variables)
114+
except:
115+
raise HttpQueryError(400, 'Variables are invalid JSON.')
116+
return variables
117+
118+
119+
def get_graphql_params(data, query_data):
120+
query = data.get('query') or query_data.get('query')
121+
variables = data.get('variables') or query_data.get('variables')
122+
# id = data.get('id')
123+
operation_name = data.get('operationName') or query_data.get('operationName')
124+
125+
return GraphQLParams(query, load_json_variables(variables), operation_name)
126+
127+
128+
def get_response(schema, params, catch=None, allow_only_query=False, **kwargs):
129+
try:
130+
execution_result = execute_graphql_request(
131+
schema,
132+
params,
133+
allow_only_query,
134+
**kwargs
135+
)
136+
except catch:
137+
return None
138+
139+
return execution_result
140+
141+
142+
def format_execution_result(execution_result, format_error):
143+
status_code = 200
144+
145+
if execution_result:
146+
response = {}
147+
148+
if execution_result.errors:
149+
response['errors'] = [format_error(e) for e in execution_result.errors]
150+
151+
if execution_result.invalid:
152+
status_code = 400
153+
else:
154+
status_code = 200
155+
response['data'] = execution_result.data
156+
157+
else:
158+
response = None
159+
160+
return GraphQLResponse(response, status_code)
161+
162+
163+
def execute_graphql_request(schema, params, allow_only_query=False, **kwargs):
164+
if not params.query:
165+
raise HttpQueryError(400, 'Must provide query string.')
166+
167+
try:
168+
source = Source(params.query, name='GraphQL request')
169+
ast = parse(source)
170+
validation_errors = validate(schema, ast)
171+
if validation_errors:
172+
return ExecutionResult(
173+
errors=validation_errors,
174+
invalid=True,
175+
)
176+
except Exception as e:
177+
return ExecutionResult(errors=[e], invalid=True)
178+
179+
if allow_only_query:
180+
operation_ast = get_operation_ast(ast, params.operation_name)
181+
if operation_ast and operation_ast.operation != 'query':
182+
raise HttpQueryError(
183+
405,
184+
'Can only perform a {} operation from a POST request.'.format(operation_ast.operation),
185+
headers={
186+
'Allow': ['POST'],
187+
}
188+
)
189+
190+
try:
191+
return execute(
192+
schema,
193+
ast,
194+
operation_name=params.operation_name,
195+
variable_values=params.variables,
196+
**kwargs
197+
)
198+
199+
except Exception as e:
200+
return ExecutionResult(errors=[e], invalid=True)
201+
202+
203+
def load_json_body(data):
204+
try:
205+
return json.loads(data)
206+
except:
207+
raise HttpQueryError(
208+
400,
209+
'POST body sent invalid JSON.'
210+
)

graphql_server/error.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class HttpQueryError(Exception):
2+
def __init__(self, status_code, message=None, is_graphql_error=False, headers=None):
3+
self.status_code = status_code
4+
self.message = message
5+
self.is_graphql_error = is_graphql_error
6+
self.headers = headers
7+
super(HttpQueryError, self).__init__(message)
8+
9+
def __eq__(self, other):
10+
return isinstance(other, HttpQueryError) and \
11+
other.status_code == self.status_code and \
12+
other.message == self.message and \
13+
other.headers == self.headers

setup.cfg

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[flake8]
2+
exclude = tests,scripts,setup.py,docs
3+
max-line-length = 160
4+
5+
[isort]
6+
known_first_party=graphql_server
7+
8+
[pytest]
9+
norecursedirs = venv .tox .cache

setup.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from setuptools import setup, find_packages
2+
3+
required_packages = ['graphql-core', 'promise']
4+
5+
setup(
6+
name='graphql-server',
7+
version='1.0.dev20170322001',
8+
description='GraphQL Server tools for powering your server',
9+
long_description=open('README.rst').read(),
10+
url='https://github.com/graphql-python/graphql-server',
11+
download_url='https://github.com/graphql-python/graphql-server/releases',
12+
author='Syrus Akbary',
13+
author_email='me@syrusakbary.com',
14+
license='MIT',
15+
classifiers=[
16+
'Development Status :: 5 - Production/Stable',
17+
'Intended Audience :: Developers',
18+
'Topic :: Software Development :: Libraries',
19+
'Programming Language :: Python :: 2',
20+
'Programming Language :: Python :: 2.7',
21+
'Programming Language :: Python :: 3',
22+
'Programming Language :: Python :: 3.3',
23+
'Programming Language :: Python :: 3.4',
24+
'Programming Language :: Python :: 3.5',
25+
'Programming Language :: Python :: Implementation :: PyPy',
26+
'License :: OSI Approved :: MIT License',
27+
],
28+
keywords='api graphql protocol rest',
29+
packages=find_packages(exclude=['tests']),
30+
install_requires=required_packages,
31+
tests_require=['pytest>=2.7.3'],
32+
include_package_data=True,
33+
zip_safe=False,
34+
platforms='any',
35+
)

tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)