Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the ability to specify the JSON encoder #25

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions flask_graphql/graphqlview.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class GraphQLView(View):
graphiql_template = None
middleware = None
batch = False
json_encoder = None
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also include a json_decoder member? Roughly equivalent change with the json.loads -> self.json_decoder(…).decode(s) a couple places below.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completely agree, I've just updated the branch with json_decoder support.

json_decoder = None

methods = ['GET', 'POST', 'PUT', 'DELETE']

Expand Down Expand Up @@ -142,10 +144,10 @@ def get_response(self, request, data, show_graphiql=False):
def json_encode(self, request, d, show_graphiql=False):
pretty = self.pretty or show_graphiql or request.args.get('pretty')
if not pretty:
return json.dumps(d, separators=(',', ':'))
return json.dumps(d, separators=(',', ':'), cls=self.json_encoder)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to continue using json.dumps here rather than the encoder directly?

return self.json_encoder(separators=(',' ':')).encode(d)

I believe they're functionally equivalent but the .encode() route reduces the dependency on the builtin json module, since there are several json libraries that could be reasonably used: http://artem.krylysov.com/blog/2015/09/29/benchmark-python-json-libraries/

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not an expert on json encoding/decoding in Python. What you've said sounds great but I'd rather leave this to someone in a better position to comment.


return json.dumps(d, sort_keys=True,
indent=2, separators=(',', ': '))
return json.dumps(d, sort_keys=True, indent=2,
separators=(',', ': '), cls=self.json_encoder)

# noinspection PyBroadException
def parse_body(self, request):
Expand All @@ -155,7 +157,7 @@ def parse_body(self, request):

elif content_type == 'application/json':
try:
request_json = json.loads(request.data.decode('utf8'))
request_json = json.loads(request.data.decode('utf8'), cls=self.json_decoder)
if self.batch:
assert isinstance(request_json, list)
else:
Expand Down
12 changes: 12 additions & 0 deletions tests/encoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from json import JSONEncoder, JSONDecoder
from json.decoder import WHITESPACE


class TestJSONEncoder(JSONEncoder):
def encode(self, o):
return 'TESTSTRING'


class TestJSONDecoder(JSONDecoder):
def decode(self, s, _w=WHITESPACE.match):
return {'query': '{test}'}
25 changes: 23 additions & 2 deletions tests/test_graphqlview.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from urllib.parse import urlencode

from .app import create_app
from .encoder import TestJSONEncoder, TestJSONDecoder
from flask import url_for


Expand Down Expand Up @@ -500,8 +501,8 @@ def test_batch_supports_post_json_query_with_json_variables(client):
'payload': { 'data': {'test': "Hello Dolly"} },
'status': 200,
}]


@pytest.mark.parametrize('app', [create_app(batch=True)])
def test_batch_allows_post_with_operation_name(client):
response = client.post(
Expand Down Expand Up @@ -532,3 +533,23 @@ def test_batch_allows_post_with_operation_name(client):
},
'status': 200,
}]


@pytest.mark.parametrize('app', [create_app(json_encoder=TestJSONEncoder)])
def test_custom_encoder(client):
response = client.get(url_string(query='{test}'))

# TestJSONEncoder just encodes everything to 'TESTSTRING'
assert response.data.decode() == 'TESTSTRING'


@pytest.mark.parametrize('app', [create_app(json_decoder=TestJSONDecoder)])
def test_custom_decoder(client):
# The submitted data here of 'TEST' is clearly not valid JSON. The TestJSONDecoder will
# decode this into valid JSON with a valid gql query.
response = client.post(url_string(), data='TEST', content_type='application/json')

assert response.status_code == 200
assert response_json(response) == {
'data': {'test': "Hello World"}
}