From 30730e78e204b573d1c3eb755a0107e3f73021f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dion=20H=C3=A4fner?= Date: Sun, 10 Oct 2021 15:01:07 +0200 Subject: [PATCH] Allow CORS from localhost by default (#197) (#236) --- terracotta/cache.py | 12 +++++------- terracotta/config.py | 2 +- terracotta/server/flask_api.py | 29 ++++++++++++++++++++++------- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/terracotta/cache.py b/terracotta/cache.py index 8964133f..b7e6cab1 100644 --- a/terracotta/cache.py +++ b/terracotta/cache.py @@ -9,7 +9,7 @@ import zlib import numpy as np -from cachetools import LFUCache, Cache +from cachetools import LFUCache CompressionTuple = Tuple[bytes, bytes, str, Tuple[int, int]] SizeFunction = Callable[[CompressionTuple], int] @@ -22,16 +22,14 @@ def __init__(self, maxsize: int, compression_level: int): super().__init__(maxsize, self._get_size) self.compression_level = compression_level - def __getitem__(self, key: Any, - cache_getitem: Callable = Cache.__getitem__) -> np.ma.MaskedArray: - compressed_item = super().__getitem__(key, cache_getitem) + def __getitem__(self, key: Any) -> np.ma.MaskedArray: + compressed_item = super().__getitem__(key) return self._decompress_tuple(compressed_item) def __setitem__(self, key: Any, - value: np.ma.MaskedArray, - cache_setitem: Callable = Cache.__setitem__) -> None: + value: np.ma.MaskedArray) -> None: val_compressed = self._compress_ma(value, self.compression_level) - super().__setitem__(key, val_compressed, cache_setitem) + super().__setitem__(key, val_compressed) @staticmethod def _compress_ma(arr: np.ma.MaskedArray, compression_level: int) -> CompressionTuple: diff --git a/terracotta/config.py b/terracotta/config.py index c2296108..ee44f563 100644 --- a/terracotta/config.py +++ b/terracotta/config.py @@ -65,7 +65,7 @@ class TerracottaSettings(NamedTuple): ALLOWED_ORIGINS_METADATA: List[str] = ['*'] #: CORS allowed origins for tiles endpoints - ALLOWED_ORIGINS_TILES: List[str] = [] + ALLOWED_ORIGINS_TILES: List[str] = [r'http[s]?://(localhost|127\.0\.0\.1):*'] #: MySQL database username (if not given in driver path) MYSQL_USER: Optional[str] = None diff --git a/terracotta/server/flask_api.py b/terracotta/server/flask_api.py index 94311258..c9976c84 100644 --- a/terracotta/server/flask_api.py +++ b/terracotta/server/flask_api.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, cast, Callable, Type, TYPE_CHECKING import copy from apispec import APISpec @@ -12,7 +12,6 @@ from terracotta import exceptions, __version__ - # define blueprints, will be populated by submodules TILE_API = Blueprint('tile_api', 'terracotta.server') METADATA_API = Blueprint('metadata_api', 'terracotta.server') @@ -40,29 +39,45 @@ def _abort(status_code: int, message: str = '') -> Any: def _setup_error_handlers(app: Flask) -> None: - @app.errorhandler(exceptions.TileOutOfBoundsError) + def register_error_handler(exc: Type[Exception], func: Callable[[Exception], Any]) -> None: + if TYPE_CHECKING: # pragma: no cover + # Flask defines this type only during type checking + from flask.typing import ErrorHandlerCallable + func = cast(ErrorHandlerCallable, func) + + app.register_error_handler(exc, func) + def handle_tile_out_of_bounds_error(exc: Exception) -> Any: # send empty image from terracotta import get_settings, image settings = get_settings() return send_file(image.empty_image(settings.DEFAULT_TILE_SIZE), mimetype='image/png') - @app.errorhandler(exceptions.DatasetNotFoundError) + register_error_handler(exceptions.TileOutOfBoundsError, handle_tile_out_of_bounds_error) + def handle_dataset_not_found_error(exc: Exception) -> Any: # wrong path -> 404 if current_app.debug: raise exc return _abort(404, str(exc)) - @app.errorhandler(exceptions.InvalidArgumentsError) - @app.errorhandler(exceptions.InvalidKeyError) - @app.errorhandler(marshmallow.ValidationError) + register_error_handler(exceptions.DatasetNotFoundError, handle_dataset_not_found_error) + def handle_marshmallow_validation_error(exc: Exception) -> Any: # wrong query arguments -> 400 if current_app.debug: raise exc return _abort(400, str(exc)) + validation_errors = ( + exceptions.InvalidArgumentsError, + exceptions.InvalidKeyError, + marshmallow.ValidationError + ) + + for err in validation_errors: + register_error_handler(err, handle_marshmallow_validation_error) + def create_app(debug: bool = False, profile: bool = False) -> Flask: """Returns a Flask app"""