From f46168e29d2dcf3abf4d4a2f408c3e1a2c4f80f1 Mon Sep 17 00:00:00 2001 From: Ethan Paul <24588726+enpaul@users.noreply.github.com> Date: Fri, 29 Oct 2021 19:45:02 -0400 Subject: [PATCH] Lint and document API resources Add unimplemented authentication decorator --- kodak/__main__.py | 2 +- kodak/resources/_shared.py | 21 ++++++++++++++++++--- kodak/resources/alias.py | 8 ++++++++ kodak/resources/heartbeat.py | 5 +++-- kodak/resources/image.py | 8 ++++++++ kodak/resources/openapi.py | 2 +- 6 files changed, 39 insertions(+), 7 deletions(-) diff --git a/kodak/__main__.py b/kodak/__main__.py index fe7f440..71afd73 100644 --- a/kodak/__main__.py +++ b/kodak/__main__.py @@ -20,8 +20,8 @@ import sys from kodak.application import APPLICATION -# pylint: disable=invalid-name def main(): + """Run the development server""" parser = argparse.ArgumentParser() parser.add_argument( "-b", diff --git a/kodak/resources/_shared.py b/kodak/resources/_shared.py index bccb63e..00068c8 100644 --- a/kodak/resources/_shared.py +++ b/kodak/resources/_shared.py @@ -1,6 +1,7 @@ """Shared resource base with common functionality""" import logging from typing import Any +from typing import Callable from typing import Dict from typing import List from typing import NamedTuple @@ -20,6 +21,20 @@ ResponseBody = Optional[Union[Dict[str, Any], List[Dict[str, Any]], List[str]]] ResponseHeaders = Dict[str, str] +def authenticated(func) -> Callable: + """Decorator to wrap endpoints that need a client to authenticate to access + + .. note:: This function has no effect if ``config.private`` is set to ``False`` + """ + + def _wrapper(*args, **kwargs): + return func(*args, **kwargs) + + # TODO: Implement this + + return _wrapper + + class ResponseTuple(NamedTuple): """Namedtuple representing the format of a flask-restful response tuple @@ -57,8 +72,8 @@ class KodakResource(flask_restful.Resource): self.logger = logging.getLogger() def options( - self, *args, **kwargs - ) -> ResponseTuple: # pylint: disable=unused-argument + self, *args, **kwargs # pylint: disable=unused-argument + ) -> ResponseTuple: """Implement HTTP ``OPTIONS`` support `Reference documentation `_ @@ -80,7 +95,7 @@ class KodakResource(flask_restful.Resource): """ return self.make_response(None, response.code, response.headers) - def make_response( + def make_response( # pylint: disable=no-self-use self, data: ResponseBody, code: int = 200, diff --git a/kodak/resources/alias.py b/kodak/resources/alias.py index 36dc625..5405cb9 100644 --- a/kodak/resources/alias.py +++ b/kodak/resources/alias.py @@ -1,10 +1,18 @@ +from kodak.resources._shared import authenticated from kodak.resources._shared import KodakResource from kodak.resources._shared import ResponseTuple class ImageAlias(KodakResource): + """Handle generating and returning a processed image alias""" routes = ("/image//",) + @authenticated def get(self, image_name: str, alias: str) -> ResponseTuple: + """Retrieve an image variation""" raise NotImplementedError + + def head(self, image_name: str, alias: str) -> ResponseTuple: + """Alias HEAD to GET""" + return self._head(self.get(image_name, alias)) diff --git a/kodak/resources/heartbeat.py b/kodak/resources/heartbeat.py index 154da08..8ba7480 100644 --- a/kodak/resources/heartbeat.py +++ b/kodak/resources/heartbeat.py @@ -1,18 +1,19 @@ -from kodak import configuration from kodak import database from kodak.resources._shared import KodakResource from kodak.resources._shared import ResponseTuple class Heartbeat(KodakResource): + """Expose a heartbeat endpoint to check service health""" routes = ("/heartbeat",) def get(self) -> ResponseTuple: - configuration.load() + """Perform a trivial database operation and return a-ok""" database.ImageRecord.select().count() return self.make_response(None) def head(self) -> ResponseTuple: + """Alias HEAD to GET""" return self._head(self.get()) diff --git a/kodak/resources/image.py b/kodak/resources/image.py index c6f71a4..26b3dee 100644 --- a/kodak/resources/image.py +++ b/kodak/resources/image.py @@ -1,10 +1,18 @@ +from kodak.resources._shared import authenticated from kodak.resources._shared import KodakResource from kodak.resources._shared import ResponseTuple class Image(KodakResource): + """Handle requests for original source images""" routes = ("/image/",) + @authenticated def get(self, image_name: str) -> ResponseTuple: + """Retrieve an original source image""" raise NotImplementedError + + def head(self, image_name: str) -> ResponseTuple: + """Alias HEAD to GET""" + return self._head(self.get(image_name)) diff --git a/kodak/resources/openapi.py b/kodak/resources/openapi.py index b7efa86..04ef566 100644 --- a/kodak/resources/openapi.py +++ b/kodak/resources/openapi.py @@ -21,5 +21,5 @@ class OpenAPI(KodakResource): return self.make_response(data) def head(self) -> ResponseTuple: - """Alias of GET with no response body""" + """Alias HEAD to GET""" return self._head(self.get())