-
-
Notifications
You must be signed in to change notification settings - Fork 1
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
Flatgeobuf + JSON + streaming renderers #89
Changes from all commits
6740a58
b178171
26c278a
7e04d88
b792cfa
06d1d67
24fe4ae
f227a01
55218dc
b7a9728
0d69c98
018aa5e
16eec2a
12c61c9
7ef1401
a79ba8d
553f2a1
06078f4
e805bd4
b9f9c50
7bc1818
b484e55
c608223
eedcb7d
632e18e
2e644f7
875fe8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,14 @@ | ||
django>=4 | ||
django-computedfields | ||
psycopg2-binary | ||
transifex-client | ||
djangorestframework-gis | ||
git+https://github.com/3nids/django-rest-framework-gis@93d9718d123c2158f4d1c5f6cf461ba8f6cf89e7 | ||
djangorestframework | ||
git+https://github.com/3nids/django-rest-framework-gis@patch-1 | ||
fiona | ||
json-stream-generator | ||
psycopg2-binary | ||
pyproj | ||
pyyaml | ||
transifex-client | ||
uritemplate | ||
pyproj | ||
orjson | ||
ujson |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import io | ||
from typing import Any, Generator, OrderedDict | ||
|
||
import fiona | ||
import orjson | ||
import ujson | ||
from django.conf import settings | ||
from fiona.crs import CRS | ||
from json_stream_generator import json_generator | ||
from rest_framework import renderers | ||
|
||
|
||
class FGBRenderer(renderers.BaseRenderer): | ||
format = "fgb" | ||
media_type = "application/event-stream" | ||
|
||
def render(self, data: OrderedDict, accepted_media_type=None, renderer_context=None) -> io.BytesIO: | ||
"""Renders data as a flatgeobuf binary stream""" | ||
assert renderer_context | ||
features_data = data["features"] if "features" in data else data["results"]["features"] | ||
features = (fiona.Feature.from_dict(obj) for obj in features_data) | ||
buffer_wrapper = io.BytesIO() | ||
with fiona.open( | ||
buffer_wrapper, | ||
mode="w", | ||
driver="FlatGeobuf", | ||
schema=renderer_context["schema"], | ||
crs=CRS.from_epsg(settings.GEOMETRY_SRID), | ||
) as fh: | ||
for feature in features: | ||
fh.write(feature) | ||
|
||
buffer_wrapper.seek(0) | ||
return buffer_wrapper | ||
|
||
|
||
class JSONStreamingRenderer(renderers.BaseRenderer): | ||
format = "json" | ||
media_type = "application/x-ndjson" | ||
|
||
def render(self, data: OrderedDict, accepted_media_type=None, renderer_context=None) -> Generator[Any, None, None]: | ||
"""Renders JSON encoded stream.""" | ||
features_data = data["features"] if "features" in data else data["results"]["features"] | ||
return json_generator(feature for feature in features_data) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. iterator object so that the response's rendered content can be streamed from the renderer. Using the default JSON renderer and / or the naked StreamingHttps response (naked as in: "without passing this constructor an iterator object") doesn't provide actual streaming. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just in case this PR remains open (or the other branch). What is the type of Otherwise if it is a list, we iterate over multiple times. Also, I am afraid the datastructure returned by this response is just an array of features, while the default json renderer returns an object (as it is in the specs?). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The What is "this response"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Agree this brings additional complexity we might not need for now. Just worth mentioning (and documenting?) one of the main benefits of extremely big responses is not to fit everything in the memory.
Nope, I meant with any serializer in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the unit tests succeed, since our custom renderers require a dict as first arg, and since this arg = the value of Apart from the issue of allocating the entire collection in memory, is there something that you worry about? Above you mentioned that the collection would be traversed more than once, but I am not sure. I have not audited the call tree, but I think there's nothing out of the ordinary in the way this is done here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I believe the third call is a different JSON structure, it is part of the specs, or I am missing something? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a good point. How would you configure a StreamingHttpResponse in such a way that it produces a dict? Iterators by themselves cannot produce dicts so I wonder. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is problematic, there are two things to be done about unit tests:
I don't think the problem is in
Also IMO there is no specific limitation with iterators, it is more that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, I know see what you meant, my bad, I was looking at the wrong thing. Yes, none of the renderers provided in this PR wraps the results in this "metadata" dict that you have seen returned by the standard renderers. Instead they just return the results as a list. I must admit I didn't pay much attention to conformance because Denis was mostly interested in timing the request-response round-trip. Or so I understood. If there is more for me to do regarding conformance, happy to do so, but I am not sure what the fate of the prototype is (I am not sure if we are still using it as a test bench or if we want to make everything clean and become a reference implementation). But if we want to make things clean and become a reference implementation, we probably need to talk about conformance, not just about "toy" renderers like these, but about the entire JSON API. |
||
|
||
|
||
class JSONorjson(renderers.BaseRenderer): | ||
format = "json" | ||
media_type = "application/json" | ||
|
||
def render(self, data: OrderedDict, accepted_media_type=None, renderer_context=None) -> bytes: | ||
features_data = data["features"] if "features" in data else data["results"]["features"] | ||
return orjson.dumps(features_data) | ||
|
||
|
||
class JSONujson(renderers.BaseRenderer): | ||
format = "json" | ||
media_type = "application/json" | ||
|
||
def render(self, data: OrderedDict, accepted_media_type=None, renderer_context=None) -> str: | ||
features_data = data["features"] if "features" in data else data["results"]["features"] | ||
return ujson.dumps(features_data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from the model, as requested