From 28d386d242ca2ff7ce92322e16edb170c2256694 Mon Sep 17 00:00:00 2001 From: banesullivan Date: Sun, 24 Apr 2022 12:45:46 -0600 Subject: [PATCH 1/4] Use server-thread package --- localtileserver/client.py | 14 +++- localtileserver/manager.py | 16 ++++ localtileserver/report.py | 1 + localtileserver/server.py | 147 ------------------------------------- requirements.txt | 1 + setup.py | 1 + tests/test_client.py | 2 +- 7 files changed, 32 insertions(+), 150 deletions(-) create mode 100644 localtileserver/manager.py delete mode 100644 localtileserver/server.py diff --git a/localtileserver/client.py b/localtileserver/client.py index ba26ba52..98bcba76 100644 --- a/localtileserver/client.py +++ b/localtileserver/client.py @@ -14,9 +14,10 @@ DatasetReaderBase = None from localtileserver.configure import get_default_client_params -from localtileserver.server import ServerManager, launch_server +from localtileserver.manager import AppManager from localtileserver.tileserver import get_building_docs, get_clean_filename, palette_valid_or_raise from localtileserver.utilities import add_query_parameters, save_file_from_request +from server_thread import ServerManager, launch_server BUILDING_DOCS = get_building_docs() DEMO_REMOTE_TILE_SERVER = "https://tileserver.banesullivan.com/" @@ -365,7 +366,8 @@ def __init__( if DatasetReaderBase and isinstance(filename, DatasetReaderBase) and hasattr(filename, "name"): filename = filename.name super().__init__(filename) - self._key = launch_server(port, debug, host=host) + app = AppManager.get_or_create_app() + self._key = launch_server(app, port=port, debug=debug, host=host) # Store actual port just in case self._port = ServerManager.get_server(self._key).srv.port client_host, client_port, client_prefix = get_default_client_params( @@ -377,6 +379,14 @@ def __init__( if BUILDING_DOCS and not client_host: self._client_host = DEMO_REMOTE_TILE_SERVER + if not debug: + logging.getLogger("gdal").setLevel(logging.ERROR) + logging.getLogger("large_image").setLevel(logging.ERROR) + else: + logging.getLogger("gdal").setLevel(logging.DEBUG) + logging.getLogger("large_image").setLevel(logging.DEBUG) + logging.getLogger("large_image_source_gdal").setLevel(logging.DEBUG) + def shutdown(self, force: bool = False): if hasattr(self, "_key"): ServerManager.shutdown_server(self._key, force=force) diff --git a/localtileserver/manager.py b/localtileserver/manager.py new file mode 100644 index 00000000..7f130801 --- /dev/null +++ b/localtileserver/manager.py @@ -0,0 +1,16 @@ +from localtileserver.tileserver import create_app + + +class AppManager: + _APP = None + + def __init__(self): + raise NotImplementedError( + "The ServerManager class cannot be instantiated." + ) # pragma: no cover + + @staticmethod + def get_or_create_app(): + if not AppManager._APP: + AppManager._APP = create_app() + return AppManager._APP diff --git a/localtileserver/report.py b/localtileserver/report.py index 0104980f..02469b69 100644 --- a/localtileserver/report.py +++ b/localtileserver/report.py @@ -25,6 +25,7 @@ def __init__(self, additional=None, ncol=3, text_width=80, sort=False): "requests", "werkzeug", "click", + "server_thread", "scooby", ] + large_image_core diff --git a/localtileserver/server.py b/localtileserver/server.py deleted file mode 100644 index 20be3f4e..00000000 --- a/localtileserver/server.py +++ /dev/null @@ -1,147 +0,0 @@ -import logging -import os -import threading -from typing import Union - -from werkzeug.serving import make_server - -logger = logging.getLogger(__name__) - - -class ServerDownError(Exception): - """Raised when a TileServerThread is down.""" - - pass - - -class ServerManager: - _LIVE_SERVERS = {} - _APP = None - - def __init__(self): - raise NotImplementedError( - "The ServerManager class cannot be instantiated." - ) # pragma: no cover - - @staticmethod - def get_or_create_app(): - from localtileserver.tileserver import create_app - - if not ServerManager._APP: - ServerManager._APP = create_app() - return ServerManager._APP - - @staticmethod - def server_count(): - return len(ServerManager._LIVE_SERVERS) - - @staticmethod - def is_server_live(key: Union[int, str]): - return key in ServerManager._LIVE_SERVERS and ServerManager._LIVE_SERVERS[key].is_alive() - - @staticmethod - def add_server(key, val): - ServerManager._LIVE_SERVERS[key] = val - - @staticmethod - def pop_server(key): - try: - return ServerManager._LIVE_SERVERS.pop(key) - except KeyError: - raise ServerDownError("Tile server for this source has been shutdown.") - - @staticmethod - def get_server(key): - try: - return ServerManager._LIVE_SERVERS[key] - except KeyError: - raise ServerDownError("Tile server for this source has been shutdown.") - - @staticmethod - def shutdown_server(key: int, force: bool = False): - if not force and key == "default": - # We do not shut down the default server unless forced - return - try: - server = ServerManager.pop_server(key) - server.shutdown() - del server - except ServerDownError: - logger.error(f"Server for key ({key}) not found.") - - -class TileServerThread(threading.Thread): - """This is for internal use only.""" - - def __init__( - self, - port: int = 0, - debug: bool = False, - start: bool = True, - host: str = "127.0.0.1", - ): - self._lts_initialized = False - if not isinstance(port, int): - raise ValueError(f"Port must be an int, not {type(port)}") - - app = ServerManager.get_or_create_app() - - if not debug: - logging.getLogger("werkzeug").setLevel(logging.ERROR) - logging.getLogger("gdal").setLevel(logging.ERROR) - logging.getLogger("large_image").setLevel(logging.ERROR) - else: - app.config["DEBUG"] = True - logging.getLogger("werkzeug").setLevel(logging.DEBUG) - logging.getLogger("gdal").setLevel(logging.DEBUG) - logging.getLogger("large_image").setLevel(logging.DEBUG) - logging.getLogger("large_image_source_gdal").setLevel(logging.DEBUG) - # make_server -> passthrough_errors ? - - if os.name == "nt" and host == "127.0.0.1": - host = "localhost" - self.srv = make_server(host, port, app, threaded=True) - self.ctx = app.app_context() - self.ctx.push() - - # daemon = True # CRITICAL for safe exit - threading.Thread.__init__(self, daemon=True, target=self.srv.serve_forever) - self._lts_initialized = True - - if start: - self.start() - - def shutdown(self): - if self._lts_initialized and self.is_alive(): - self.srv.shutdown() - self.srv.server_close() - self.join() - - def __del__(self): - self.shutdown() - - @property - def port(self): - return self.srv.port - - @property - def host(self): - return self.srv.host - - -def launch_server( - port: Union[int, str] = "default", - debug: bool = False, - host: str = "127.0.0.1", -): - if ServerManager.is_server_live(port): - return port - if port == "default": - server = TileServerThread(0, debug, host=host) - else: - server = TileServerThread(port, debug, host=host) - if port == 0: - # Get reallocated port - port = server.port - ServerManager.add_server(port, server) - return port diff --git a/requirements.txt b/requirements.txt index d87455f2..40b8123b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ large-image[memcached]>=1.13 large-image-source-gdal>=1.13 large-image-source-pil>=1.13 requests +server-thread scooby pytest pytest-cov diff --git a/setup.py b/setup.py index 223b8922..0fd11177 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ "large-image>=1.13", "large-image-source-gdal>=1.13", "requests", + "server-thread", "scooby", ], extras_require={ diff --git a/tests/test_client.py b/tests/test_client.py index c68aeaf8..23671843 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -9,8 +9,8 @@ TileClient, get_or_create_tile_client, ) -from localtileserver.server import ServerDownError, ServerManager from localtileserver.tileserver.utilities import get_clean_filename, get_tile_source +from server_thread import ServerDownError, ServerManager skip_pil_source = True try: From 1c0ee8725d97175b9b73a271b70f4228ea731d60 Mon Sep 17 00:00:00 2001 From: banesullivan Date: Sun, 24 Apr 2022 12:47:10 -0600 Subject: [PATCH 2/4] Linting --- localtileserver/client.py | 9 +++++++-- tests/test_client.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/localtileserver/client.py b/localtileserver/client.py index 98bcba76..e42a3a47 100644 --- a/localtileserver/client.py +++ b/localtileserver/client.py @@ -13,11 +13,12 @@ except ImportError: DatasetReaderBase = None +from server_thread import ServerManager, launch_server + from localtileserver.configure import get_default_client_params from localtileserver.manager import AppManager from localtileserver.tileserver import get_building_docs, get_clean_filename, palette_valid_or_raise from localtileserver.utilities import add_query_parameters, save_file_from_request -from server_thread import ServerManager, launch_server BUILDING_DOCS = get_building_docs() DEMO_REMOTE_TILE_SERVER = "https://tileserver.banesullivan.com/" @@ -363,7 +364,11 @@ def __init__( client_host: str = None, client_prefix: str = None, ): - if DatasetReaderBase and isinstance(filename, DatasetReaderBase) and hasattr(filename, "name"): + if ( + DatasetReaderBase + and isinstance(filename, DatasetReaderBase) + and hasattr(filename, "name") + ): filename = filename.name super().__init__(filename) app = AppManager.get_or_create_app() diff --git a/tests/test_client.py b/tests/test_client.py index 23671843..a8c36842 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -2,6 +2,7 @@ import pytest import requests +from server_thread import ServerDownError, ServerManager from localtileserver.client import ( DEMO_REMOTE_TILE_SERVER, @@ -10,7 +11,6 @@ get_or_create_tile_client, ) from localtileserver.tileserver.utilities import get_clean_filename, get_tile_source -from server_thread import ServerDownError, ServerManager skip_pil_source = True try: From ea9f4d1f450fe0f1567b5eea6bb91971da24aedf Mon Sep 17 00:00:00 2001 From: banesullivan Date: Sun, 24 Apr 2022 12:53:42 -0600 Subject: [PATCH 3/4] Update conda env --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index 6a6e6bef..6c9392d2 100644 --- a/environment.yml +++ b/environment.yml @@ -13,6 +13,7 @@ dependencies: - flask-restx >=0.5.0 - pylibmc - requests + - server-thread - scooby - pytest - pytest-cov From 6b8a04099601d3590587a43d4befd8f64eeef231 Mon Sep 17 00:00:00 2001 From: banesullivan Date: Sun, 24 Apr 2022 12:54:11 -0600 Subject: [PATCH 4/4] Update reqs win --- requirements_win.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements_win.txt b/requirements_win.txt index b2cc20a3..e47835ab 100644 --- a/requirements_win.txt +++ b/requirements_win.txt @@ -5,6 +5,7 @@ large-image>=1.13 large-image-source-gdal>=1.13 large-image-source-pil>=1.13 requests +server-thread scooby pytest pytest-cov