Add an opendata endpoint in API
This commit is contained in:
parent
f29204265c
commit
49ebfe9111
@ -35,3 +35,14 @@ class PostalCode(BASE):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<PostalCode(id=%s)>" % self.id
|
return "<PostalCode(id=%s)>" % self.id
|
||||||
|
|
||||||
|
def json_api_repr(self):
|
||||||
|
"""
|
||||||
|
Return a dict representation of this postal code object that is JSON
|
||||||
|
serializable.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
k: v
|
||||||
|
for k, v in self.__dict__.items()
|
||||||
|
if not k.startswith("_")
|
||||||
|
}
|
||||||
|
@ -82,7 +82,7 @@ def get_app(config):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# API v1 routes
|
# API v1 routes
|
||||||
app.route("/api/v1/", ["GET", "OPTIONS"], api_routes.index_v1)
|
app.route("/api/v1", ["GET", "OPTIONS"], api_routes.index_v1)
|
||||||
|
|
||||||
app.route("/api/v1/time_to_places", ["GET", "OPTIONS"],
|
app.route("/api/v1/time_to_places", ["GET", "OPTIONS"],
|
||||||
api_routes.time_to_places_v1)
|
api_routes.time_to_places_v1)
|
||||||
@ -97,6 +97,10 @@ def get_app(config):
|
|||||||
|
|
||||||
app.route("/api/v1/search", "POST", api_routes.search_v1)
|
app.route("/api/v1/search", "POST", api_routes.search_v1)
|
||||||
|
|
||||||
|
app.route("/api/v1/opendata", "GET", api_routes.opendata_index_v1)
|
||||||
|
app.route("/api/v1/opendata/postal_codes", "GET",
|
||||||
|
api_routes.opendata_postal_codes_v1)
|
||||||
|
|
||||||
# Index
|
# Index
|
||||||
app.route("/", "GET", lambda: _serve_static_file("index.html"))
|
app.route("/", "GET", lambda: _serve_static_file("index.html"))
|
||||||
|
|
||||||
|
@ -35,11 +35,14 @@ def JSONError(error_code, error_str):
|
|||||||
return json.dumps(dict(error=error_str, status_code=error_code))
|
return json.dumps(dict(error=error_str, status_code=error_code))
|
||||||
|
|
||||||
|
|
||||||
def _JSONApiSpec(query):
|
def _JSONApiSpec(query, model, default_sorting=None):
|
||||||
"""
|
"""
|
||||||
Implementing JSON API spec for filtering, sorting and paginating results.
|
Implementing JSON API spec for filtering, sorting and paginating results.
|
||||||
|
|
||||||
:param query: A Bottle query dict.
|
:param query: A Bottle query dict.
|
||||||
|
:param model: Database model used in this query.
|
||||||
|
:param default_sorting: Optional field to sort on if no sort options are
|
||||||
|
passed through parameters.
|
||||||
:return: A tuple of filters, page number, page size (items per page) and
|
:return: A tuple of filters, page number, page size (items per page) and
|
||||||
sorting to apply.
|
sorting to apply.
|
||||||
"""
|
"""
|
||||||
@ -70,7 +73,7 @@ def _JSONApiSpec(query):
|
|||||||
if 'sort' in query:
|
if 'sort' in query:
|
||||||
for index in query['sort'].split(','):
|
for index in query['sort'].split(','):
|
||||||
try:
|
try:
|
||||||
sort_field = getattr(flat_model.Flat, index.lstrip('-'))
|
sort_field = getattr(model, index.lstrip('-'))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Invalid sorting key provided: {}.".format(index)
|
"Invalid sorting key provided: {}.".format(index)
|
||||||
@ -78,6 +81,16 @@ def _JSONApiSpec(query):
|
|||||||
if index.startswith('-'):
|
if index.startswith('-'):
|
||||||
sort_field = sort_field.desc()
|
sort_field = sort_field.desc()
|
||||||
sorting.append(sort_field)
|
sorting.append(sort_field)
|
||||||
|
# Default sorting options
|
||||||
|
if not sorting and default_sorting:
|
||||||
|
try:
|
||||||
|
sorting.append(getattr(model, default_sorting))
|
||||||
|
except AttributeError:
|
||||||
|
raise ValueError(
|
||||||
|
"Invalid default sorting key provided: {}.".format(
|
||||||
|
default_sorting
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return filters, page_number, page_size, sorting
|
return filters, page_number, page_size, sorting
|
||||||
|
|
||||||
@ -128,6 +141,7 @@ def index_v1():
|
|||||||
GET /api/v1/
|
GET /api/v1/
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
|
"opendata": "/api/v1/opendata",
|
||||||
"flats": "/api/v1/flats",
|
"flats": "/api/v1/flats",
|
||||||
"flat": "/api/v1/flat/:id",
|
"flat": "/api/v1/flat/:id",
|
||||||
"search": "/api/v1/search",
|
"search": "/api/v1/search",
|
||||||
@ -170,7 +184,9 @@ def flats_v1(config, db):
|
|||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
filters, page_number, page_size, sorting = _JSONApiSpec(
|
filters, page_number, page_size, sorting = _JSONApiSpec(
|
||||||
bottle.request.query
|
bottle.request.query,
|
||||||
|
flat_model.Flat,
|
||||||
|
default_sorting='cost'
|
||||||
)
|
)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
return JSONError(400, str(exc))
|
return JSONError(400, str(exc))
|
||||||
@ -339,7 +355,9 @@ def search_v1(db, config):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
filters, page_number, page_size, sorting = _JSONApiSpec(
|
filters, page_number, page_size, sorting = _JSONApiSpec(
|
||||||
bottle.request.query
|
bottle.request.query,
|
||||||
|
flat_model.Flat,
|
||||||
|
default_sorting='cost'
|
||||||
)
|
)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
return JSONError(400, str(exc))
|
return JSONError(400, str(exc))
|
||||||
@ -409,3 +427,75 @@ def ics_feed_v1(config, db):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
return cal.serialize()
|
return cal.serialize()
|
||||||
|
|
||||||
|
|
||||||
|
def opendata_index_v1():
|
||||||
|
"""
|
||||||
|
API v1 data index route.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
GET /api/v1/opendata
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"postal_codes": "/api/v1/opendata/postal_codes"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def opendata_postal_codes_v1(db):
|
||||||
|
"""
|
||||||
|
API v1 data postal codes route.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
GET /api/v1/opendata/postal_codes
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Filtering can be done through the ``filter`` GET param, according
|
||||||
|
to JSON API spec (http://jsonapi.org/recommendations/#filtering).
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
By default no pagination is done. Pagination can be forced using
|
||||||
|
``page[size]`` to specify a number of items per page and
|
||||||
|
``page[number]`` to specify which page to return. Pages are numbered
|
||||||
|
starting from 0.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Sorting can be handled through the ``sort`` GET param, according to
|
||||||
|
JSON API spec (http://jsonapi.org/format/#fetching-sorting).
|
||||||
|
|
||||||
|
|
||||||
|
:return: The postal codes data from opendata.
|
||||||
|
"""
|
||||||
|
if bottle.request.method == 'OPTIONS':
|
||||||
|
# CORS
|
||||||
|
return {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
filters, page_number, page_size, sorting = _JSONApiSpec(
|
||||||
|
bottle.request.query,
|
||||||
|
PostalCode,
|
||||||
|
default_sorting='postal_code'
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
return JSONError(400, str(exc))
|
||||||
|
|
||||||
|
db_query = db.query(PostalCode).filter_by(**filters).order_by(*sorting)
|
||||||
|
postal_codes = [
|
||||||
|
x.json_api_repr() for x in itertools.islice(
|
||||||
|
db_query,
|
||||||
|
page_number * page_size if page_size else None,
|
||||||
|
page_number * page_size + page_size if page_size else None
|
||||||
|
)
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
"data": postal_codes,
|
||||||
|
"page": page_number,
|
||||||
|
"items_per_page": page_size if page_size else len(postal_codes)
|
||||||
|
}
|
||||||
|
except Exception as exc: # pylint: disable= broad-except
|
||||||
|
return JSONError(500, str(exc))
|
||||||
|
Loading…
Reference in New Issue
Block a user