Skip to content

Commit 12d9bbd

Browse files
authored
Merge pull request #23 from Cito/improve-tests-and-docs
Improve tests and docstrings
2 parents e0fcd01 + 52b4efc commit 12d9bbd

13 files changed

+1024
-607
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@
1414

1515
/build/
1616
/dist/
17+
18+
docs

.travis.yml

+14-12
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
language: python
2-
sudo: false
32
matrix:
43
include:
5-
- python: pypy
6-
env: TOX_ENV=pypy
7-
- python: '2.7'
8-
env: TOX_ENV=py27
9-
- python: '3.4'
10-
env: TOX_ENV=py34
11-
- python: '3.5'
12-
env: TOX_ENV=py35
13-
- python: '3.6'
14-
env: TOX_ENV=py36
154
- python: '3.7'
16-
env: TOX_ENV=py37,import-order,flake8,mypy
5+
env: TOX_ENV=black,flake8,mypy,py37
176
dist: xenial
7+
sudo: true # required workaround for https://github.com/travis-ci/travis-ci/issues/9815
8+
- python: '3.6'
9+
env: TOX_ENV=py36
10+
- python: '3.5'
11+
env: TOX_ENV=py35
12+
- python: '3.4'
13+
env: TOX_ENV=py34
14+
- python: '2.7'
15+
env: TOX_ENV=py27
16+
- python: 'pypy3.5'
17+
env: TOX_ENV=pypy3
18+
- python: 'pypy'
19+
env: TOX_ENV=pypy
1820
cache:
1921
directories:
2022
- $HOME/.cache/pip

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ for building GraphQL servers or integrations into existing web frameworks using
2525

2626
## Documentation
2727

28-
The `graphql_server` package provides these three public helper functions:
28+
The `graphql_server` package provides these public helper functions:
2929

3030
* `run_http_query`
3131
* `encode_execution_results`
3232
* `laod_json_body`
33+
* `json_encode`
34+
* `json_encode_pretty`
3335

3436
All functions in the package are annotated with type hints and docstrings,
3537
and you can build HTML documentation from these using `bin/build_docs`.

README.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@ Django `graphene-django <https://github.com/graphql-python/graphene-
3333
Documentation
3434
-------------
3535

36-
The ``graphql_server`` package provides these three public helper
37-
functions:
36+
The ``graphql_server`` package provides these public helper functions:
3837

3938
- ``run_http_query``
4039
- ``encode_execution_results``
4140
- ``laod_json_body``
41+
- ``json_encode``
42+
- ``json_encode_pretty``
4243

4344
All functions in the package are annotated with type hints and
4445
docstrings, and you can build HTML documentation from these using

graphql_server/__init__.py

+70-41
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,49 @@
99

1010

1111
import json
12-
from collections import MutableMapping, namedtuple
12+
from collections import namedtuple
1313

1414
import six
1515
from graphql import get_default_backend
1616
from graphql.error import format_error as default_format_error
1717
from graphql.execution import ExecutionResult
18+
from graphql.type import GraphQLSchema
1819

1920
from .error import HttpQueryError
2021

22+
try: # pragma: no cover (Python >= 3.3)
23+
from collections.abc import MutableMapping
24+
except ImportError: # pragma: no cover (Python < 3.3)
25+
# noinspection PyUnresolvedReferences,PyProtectedMember
26+
from collections import MutableMapping
27+
2128
# Necessary for static type checking
29+
# noinspection PyUnreachableCode
2230
if False: # flake8: noqa
23-
from typing import List, Dict, Optional, Tuple, Any, Union, Callable, Type
24-
from graphql import GraphQLSchema, GraphQLBackend
31+
from typing import Any, Callable, Dict, List, Optional, Type, Union
32+
from graphql import GraphQLBackend
2533

2634

2735
__all__ = [
2836
"run_http_query",
2937
"encode_execution_results",
3038
"load_json_body",
31-
"HttpQueryError"]
39+
"json_encode",
40+
"json_encode_pretty",
41+
"HttpQueryError",
42+
"RequestParams",
43+
"ServerResults",
44+
"ServerResponse",
45+
]
46+
47+
48+
# The public data structures
49+
50+
RequestParams = namedtuple("RequestParams", "query variables operation_name")
51+
52+
ServerResults = namedtuple("ServerResults", "results params")
53+
54+
ServerResponse = namedtuple("ServerResponse", "body status_code")
3255

3356

3457
# The public helper functions
@@ -41,7 +64,7 @@ def run_http_query(
4164
query_data=None, # type: Optional[Dict]
4265
batch_enabled=False, # type: bool
4366
catch=False, # type: bool
44-
**execute_options # type: Dict
67+
**execute_options # type: Any
4568
):
4669
"""Execute GraphQL coming from an HTTP query against a given schema.
4770
@@ -55,9 +78,11 @@ def run_http_query(
5578
All other keyword arguments are passed on to the GraphQL-Core function for
5679
executing GraphQL queries.
5780
58-
This functions returns a tuple with the list of ExecutionResults as first item
81+
Returns a ServerResults tuple with the list of ExecutionResults as first item
5982
and the list of parameters that have been used for execution as second item.
6083
"""
84+
if not isinstance(schema, GraphQLSchema):
85+
raise TypeError("Expected a GraphQL schema, but received {!r}.".format(schema))
6186
if request_method not in ("get", "post"):
6287
raise HttpQueryError(
6388
405,
@@ -78,7 +103,7 @@ def run_http_query(
78103
if not is_batch:
79104
if not isinstance(data, (dict, MutableMapping)):
80105
raise HttpQueryError(
81-
400, "GraphQL params should be a dict. Received {}.".format(data)
106+
400, "GraphQL params should be a dict. Received {!r}.".format(data)
82107
)
83108
data = [data]
84109
elif not batch_enabled:
@@ -94,12 +119,12 @@ def run_http_query(
94119

95120
all_params = [get_graphql_params(entry, extra_data) for entry in data]
96121

97-
responses = [
122+
results = [
98123
get_response(schema, params, catch_exc, allow_only_query, **execute_options)
99124
for params in all_params
100125
]
101126

102-
return responses, all_params
127+
return ServerResults(results, all_params)
103128

104129

105130
def encode_execution_results(
@@ -108,7 +133,7 @@ def encode_execution_results(
108133
is_batch=False, # type: bool
109134
encode=None, # type: Callable[[Dict], Any]
110135
):
111-
# type: (...) -> Tuple[Any, int]
136+
# type: (...) -> ServerResponse
112137
"""Serialize the ExecutionResults.
113138
114139
This function takes the ExecutionResults that are returned by run_http_query()
@@ -117,18 +142,21 @@ def encode_execution_results(
117142
the first one will be used. You can also pass a custom function that formats the
118143
errors in the ExecutionResults, expecting a dictionary as result and another custom
119144
function that is used to serialize the output.
145+
146+
Returns a ServerResponse tuple with the serialized response as the first item and
147+
a status code of 200 or 400 in case any result was invalid as the second item.
120148
"""
121-
responses = [
149+
results = [
122150
format_execution_result(execution_result, format_error or default_format_error)
123151
for execution_result in execution_results
124152
]
125-
result, status_codes = zip(*responses)
153+
result, status_codes = zip(*results)
126154
status_code = max(status_codes)
127155

128156
if not is_batch:
129157
result = result[0]
130158

131-
return (encode or json_encode)(result), status_code
159+
return ServerResponse((encode or json_encode)(result), status_code)
132160

133161

134162
def load_json_body(data):
@@ -144,32 +172,46 @@ def load_json_body(data):
144172
raise HttpQueryError(400, "POST body sent invalid JSON.")
145173

146174

147-
# Some more private helpers
175+
def json_encode(data):
176+
# type: (Union[Dict,List]) -> str
177+
"""Serialize the given data(a dictionary or a list) using JSON.
178+
179+
The given data (a dictionary or a list) will be serialized using JSON
180+
and returned as a string that will be nicely formatted if you set pretty=True.
181+
"""
182+
return json.dumps(data, separators=(",", ":"))
183+
148184

149-
GraphQLParams = namedtuple("GraphQLParams", "query variables operation_name")
185+
def json_encode_pretty(data):
186+
# type: (Union[Dict,List]) -> str
187+
"""Serialize the given data using JSON with nice formatting."""
188+
return json.dumps(data, indent=2, separators=(",", ": "))
189+
190+
191+
# Some more private helpers
150192

151-
GraphQLResponse = namedtuple("GraphQLResponse", "result status_code")
193+
FormattedResult = namedtuple("FormattedResult", "result status_code")
152194

153195

154196
class _NoException(Exception):
155197
"""Private exception used when we don't want to catch any real exception."""
156198

157199

158200
def get_graphql_params(data, query_data):
159-
# type: (Dict, Dict) -> GraphQLParams
201+
# type: (Dict, Dict) -> RequestParams
160202
"""Fetch GraphQL query, variables and operation name parameters from given data.
161203
162204
You need to pass both the data from the HTTP request body and the HTTP query string.
163205
Params from the request body will take precedence over those from the query string.
164206
165-
You will get a GraphQLParams object with these parameters as attributes in return.
207+
You will get a RequestParams tuple with these parameters in return.
166208
"""
167209
query = data.get("query") or query_data.get("query")
168210
variables = data.get("variables") or query_data.get("variables")
169211
# document_id = data.get('documentId')
170212
operation_name = data.get("operationName") or query_data.get("operationName")
171213

172-
return GraphQLParams(query, load_json_variables(variables), operation_name)
214+
return RequestParams(query, load_json_variables(variables), operation_name)
173215

174216

175217
def load_json_variables(variables):
@@ -190,12 +232,12 @@ def load_json_variables(variables):
190232

191233
def execute_graphql_request(
192234
schema, # type: GraphQLSchema
193-
params, # type: GraphQLParams
235+
params, # type: RequestParams
194236
allow_only_query=False, # type: bool
195237
backend=None, # type: GraphQLBackend
196-
**kwargs # type: Dict
238+
**kwargs # type: Any
197239
):
198-
"""Execute a GraphQL request and return a ExecutionResult.
240+
"""Execute a GraphQL request and return an ExecutionResult.
199241
200242
You need to pass the GraphQL schema and the GraphQLParams that you can get
201243
with the get_graphql_params() function. If you only want to allow GraphQL query
@@ -235,18 +277,18 @@ def execute_graphql_request(
235277

236278
def get_response(
237279
schema, # type: GraphQLSchema
238-
params, # type: GraphQLParams
280+
params, # type: RequestParams
239281
catch_exc, # type: Type[BaseException]
240282
allow_only_query=False, # type: bool
241-
**kwargs # type: Dict
283+
**kwargs # type: Any
242284
):
243285
# type: (...) -> Optional[ExecutionResult]
244286
"""Get an individual execution result as response, with option to catch errors.
245287
246288
This does the same as execute_graphql_request() except that you can catch errors
247289
that belong to an exception class that you need to pass as a parameter.
248290
"""
249-
291+
# noinspection PyBroadException
250292
try:
251293
execution_result = execute_graphql_request(
252294
schema, params, allow_only_query, **kwargs
@@ -261,10 +303,10 @@ def format_execution_result(
261303
execution_result, # type: Optional[ExecutionResult]
262304
format_error, # type: Optional[Callable[[Exception], Dict]]
263305
):
264-
# type: (...) -> GraphQLResponse
306+
# type: (...) -> FormattedResult
265307
"""Format an execution result into a GraphQLResponse.
266308
267-
This converts the given execution result into a GraphQLResponse that contains
309+
This converts the given execution result into a FormattedResult that contains
268310
the ExecutionResult converted to a dictionary and an appropriate status code.
269311
"""
270312
status_code = 200
@@ -276,17 +318,4 @@ def format_execution_result(
276318
else:
277319
response = None
278320

279-
return GraphQLResponse(response, status_code)
280-
281-
282-
def json_encode(data, pretty=False):
283-
# type: (Union[Dict,List], bool) -> str
284-
"""Serialize the given data using JSON.
285-
286-
The given data (a dictionary or a list) will be serialized using JSON
287-
and returned as a string that will be nicely formatted if you set pretty=True.
288-
"""
289-
if not pretty:
290-
return json.dumps(data, separators=(",", ":"))
291-
292-
return json.dumps(data, indent=2, separators=(",", ": "))
321+
return FormattedResult(response, status_code)

setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from setuptools import setup, find_packages
22

3-
required_packages = ["graphql-core>=2.1", "promise"]
3+
required_packages = ["graphql-core>=2.1,<3", "promise"]
44

55
setup(
66
name="graphql-server-core",
@@ -30,7 +30,7 @@
3030
keywords="api graphql protocol rest",
3131
packages=find_packages(exclude=["tests"]),
3232
install_requires=required_packages,
33-
tests_require=["pytest>=2.7.3"],
33+
tests_require=["pytest>=3.0"],
3434
include_package_data=True,
3535
zip_safe=False,
3636
platforms="any",

tests/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""GraphQL-Server-Core Tests"""

0 commit comments

Comments
 (0)