add heavy caching to mapdata api
This commit is contained in:
parent
23da7e3605
commit
00193f7e11
22 changed files with 259 additions and 138 deletions
0
src/c3nav/mapdata/utils/__init__.py
Normal file
0
src/c3nav/mapdata/utils/__init__.py
Normal file
99
src/c3nav/mapdata/utils/cache.py
Normal file
99
src/c3nav/mapdata/utils/cache.py
Normal file
|
@ -0,0 +1,99 @@
|
|||
from calendar import timegm
|
||||
from functools import wraps
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.utils.http import http_date
|
||||
from rest_framework.response import Response as APIResponse
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from c3nav.mapdata.lastupdate import get_last_mapdata_update
|
||||
|
||||
|
||||
def cache_result(cache_key, timeout=900):
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def inner(*args, **kwargs):
|
||||
last_update = get_last_mapdata_update()
|
||||
if last_update is None:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
result = cache.get(cache_key)
|
||||
if not result:
|
||||
result = func(*args, **kwargs)
|
||||
cache.set(cache_key, result, timeout)
|
||||
return result
|
||||
return inner
|
||||
return decorator
|
||||
|
||||
|
||||
def cache_mapdata_api_response(timeout=900):
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def inner(self, request, *args, add_cache_key=None, **kwargs):
|
||||
last_update = get_last_mapdata_update()
|
||||
if last_update is None:
|
||||
return func(self, request, *args, **kwargs)
|
||||
|
||||
cache_key = '__'.join((
|
||||
'c3nav__mapdata__api',
|
||||
last_update.isoformat(),
|
||||
add_cache_key if add_cache_key is not None else '',
|
||||
request.accepted_renderer.format if isinstance(self, APIView) else '',
|
||||
request.path,
|
||||
))
|
||||
|
||||
response = cache.get(cache_key)
|
||||
if not response:
|
||||
response = func(self, request, *args, **kwargs)
|
||||
response['Last-Modifed'] = http_date(timegm(last_update.utctimetuple()))
|
||||
if isinstance(response, APIResponse):
|
||||
response = self.finalize_response(request, response, *args, **kwargs)
|
||||
response.render()
|
||||
if response.status_code < 400:
|
||||
cache.set(cache_key, response, timeout)
|
||||
|
||||
return response
|
||||
return inner
|
||||
return decorator
|
||||
|
||||
|
||||
class CachedReadOnlyViewSetMixin():
|
||||
include_package_access = False
|
||||
|
||||
def _get_unlocked_packages_ids(self, request):
|
||||
from c3nav.mapdata.permissions import get_unlocked_packages
|
||||
return ','.join(str(i) for i in sorted(package.id for package in get_unlocked_packages(request)))
|
||||
|
||||
def _get_add_cache_key(self, request, add_cache_key=''):
|
||||
cache_key = add_cache_key
|
||||
if self.include_package_access:
|
||||
cache_key += '__'+self._get_unlocked_packages_ids(request)
|
||||
return cache_key
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
kwargs['add_cache_key'] = self._get_add_cache_key(request, kwargs.get('add_cache_key', ''))
|
||||
return self._list(request, *args, **kwargs)
|
||||
|
||||
@cache_mapdata_api_response()
|
||||
def _list(self, request, *args, **kwargs):
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
kwargs['add_cache_key'] = self._get_add_cache_key(request, kwargs.get('add_cache_key', ''))
|
||||
return self._retrieve(request, *args, **kwargs)
|
||||
|
||||
@cache_mapdata_api_response()
|
||||
def _retrieve(self, request, *args, **kwargs):
|
||||
return super().retrieve(request, *args, **kwargs)
|
||||
|
||||
|
||||
@cache_result('c3nav__mapdata__levels')
|
||||
def get_levels_cached():
|
||||
from c3nav.mapdata.models import Level
|
||||
return {level.name: level for level in Level.objects.all()}
|
||||
|
||||
|
||||
@cache_result('c3nav__mapdata__packages')
|
||||
def get_packages_cached():
|
||||
from c3nav.mapdata.models import Package
|
||||
return {package.name: package for package in Package.objects.all()}
|
32
src/c3nav/mapdata/utils/geometry.py
Normal file
32
src/c3nav/mapdata/utils/geometry.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from shapely.geometry import Polygon
|
||||
|
||||
|
||||
def clean_geometry(geometry):
|
||||
"""
|
||||
if the given geometry is a Polygon and invalid, try to make it valid if it results in a Polygon (not MultiPolygon)
|
||||
"""
|
||||
if geometry.is_valid:
|
||||
return geometry
|
||||
|
||||
if isinstance(geometry, Polygon):
|
||||
p = Polygon(list(geometry.exterior.coords))
|
||||
for interior in geometry.interiors:
|
||||
p = p.difference(Polygon(list(interior.coords)))
|
||||
|
||||
if isinstance(p, Polygon) and p.is_valid:
|
||||
return p
|
||||
|
||||
return geometry
|
||||
|
||||
|
||||
def assert_multipolygon(geometry):
|
||||
"""
|
||||
given a Polygon or a MultiPolygon, return a list of Polygons
|
||||
:param geometry: a Polygon or a MultiPolygon
|
||||
:return: a list of Polygons
|
||||
"""
|
||||
if isinstance(geometry, Polygon):
|
||||
polygons = [geometry]
|
||||
else:
|
||||
polygons = geometry.geoms
|
||||
return polygons
|
47
src/c3nav/mapdata/utils/json.py
Normal file
47
src/c3nav/mapdata/utils/json.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
def _preencode(data, magic_marker, in_coords=False):
|
||||
if isinstance(data, dict):
|
||||
data = data.copy()
|
||||
for name, value in tuple(data.items()):
|
||||
if name in ('bounds', ):
|
||||
data[name] = magic_marker+json.dumps(value)+magic_marker
|
||||
else:
|
||||
data[name] = _preencode(value, magic_marker, in_coords=(name == 'coordinates'))
|
||||
return data
|
||||
elif isinstance(data, (tuple, list)):
|
||||
if in_coords and len(data) == 2 and isinstance(data[0], (int, float)) and isinstance(data[1], (int, float)):
|
||||
return magic_marker+json.dumps(data)+magic_marker
|
||||
else:
|
||||
return tuple(_preencode(value, magic_marker, in_coords) for value in data)
|
||||
else:
|
||||
return data
|
||||
|
||||
|
||||
def json_encoder_reindent(method, data, *args, **kwargs):
|
||||
magic_marker = '***JSON_MAGIC_MARKER***'
|
||||
test_encode = json.dumps(data)
|
||||
while magic_marker in test_encode:
|
||||
magic_marker += '*'
|
||||
result = method(_preencode(data, magic_marker), *args, **kwargs)
|
||||
if type(result) == str:
|
||||
return result.replace('"'+magic_marker, '').replace(magic_marker+'"', '')
|
||||
else:
|
||||
magic_marker = magic_marker.encode()
|
||||
return result.replace(b'"'+magic_marker, b'').replace(magic_marker+b'"', b'')
|
||||
|
||||
|
||||
def format_geojson(data, round=True):
|
||||
return OrderedDict((
|
||||
('type', data['type']),
|
||||
('coordinates', round_coordinates(data['coordinates']) if round else data['coordinates']),
|
||||
))
|
||||
|
||||
|
||||
def round_coordinates(data):
|
||||
if isinstance(data, (list, tuple)):
|
||||
return tuple(round_coordinates(item) for item in data)
|
||||
else:
|
||||
return round(data, 2)
|
Loading…
Add table
Add a link
Reference in a new issue