1
0
mirror of https://github.com/enpaul/kodak.git synced 2025-09-18 21:21:59 +00:00

Update resources and flask plumbing to remove previuos scope creep

This commit is contained in:
2021-10-28 23:07:13 -04:00
parent b61ef4624b
commit 99d2ca4816
9 changed files with 52 additions and 167 deletions

View File

@@ -1,19 +1,15 @@
from typing import Tuple
from fresnel_lens.resources._shared import FresnelResource
from fresnel_lens.resources._shared import ResponseBody
from fresnel_lens.resources._shared import ResponseHeaders
from fresnel_lens.resources.alias import ImageAlias
from fresnel_lens.resources.heartbeat import Heartbeat
from fresnel_lens.resources.image import Image
from fresnel_lens.resources.image import ImageUpload
from fresnel_lens.resources.openapi import OpenAPI
from fresnel_lens.resources.thumbnail import ThumbnailResize
from fresnel_lens.resources.thumbnail import ThumbnailScale
RESOURCES: Tuple[FresnelResource, ...] = (
ImageUpload,
Heartbeat,
Image,
ImageAlias,
OpenAPI,
ThumbnailScale,
ThumbnailResize,
)

View File

@@ -11,6 +11,8 @@ from typing import Union
import flask
import flask_restful
from fresnel_lens import __about__
ResponseBody = Optional[Union[Dict[str, Any], List[Dict[str, Any]], List[str]]]
@@ -99,7 +101,7 @@ class FresnelResource(flask_restful.Resource):
"""
headers = headers or {}
headers = {**headers, **flask.request.make_response_headers()}
headers.update({"Server": f"{__about__.__title__}-{__about__.__version__}"})
# 204 code specifies that it must never include a response body. Most clients will ignore
# any response body when a 204 is given, but that's no reason to abandon best practices here

View File

@@ -0,0 +1,10 @@
from fresnel_lens.resources._shared import FresnelResource
from fresnel_lens.resources._shared import ResponseTuple
class ImageAlias(FresnelResource):
routes = ("/image/<string:image_name>/<string:alias>",)
def get(self, image_name: str, alias: str) -> ResponseTuple:
raise NotImplementedError

View File

@@ -0,0 +1,18 @@
from fresnel_lens import configuration
from fresnel_lens import database
from fresnel_lens.resources._shared import FresnelResource
from fresnel_lens.resources._shared import ResponseTuple
class Heartbeat(FresnelResource):
routes = ("/heartbeat",)
def get(self) -> ResponseTuple:
configuration.load()
database.ImageRecord.select().count()
return self.make_response(None)
def head(self) -> ResponseTuple:
return self._head(self.get())

View File

@@ -1,113 +1,10 @@
import hashlib
import shutil
import uuid
import flask
from fresnel_lens import constants
from fresnel_lens import database
from fresnel_lens import exceptions
from fresnel_lens.resources._shared import FresnelResource
class ImageUpload(FresnelResource):
routes = ("/image/",)
def post(self):
if "image" not in flask.request.files:
raise
uploaded = flask.request.files["image"]
if not uploaded.filename:
raise
format = uploaded.filename.rpartition(".")[-1].lower()
if format not in flask.current_app.appconfig.upload.formats:
raise
image = database.ImageRecord(format=format, width=0, height=0, owner="foobar")
imagedir = flask.current_app.appconfig.storage_path / str(image.uuid)
imagedir.mkdir()
uploaded.save(imagedir / f"base.{format}")
with (imagedir / f"base.{format}").open() as infile:
image.sha256 = hashlib.sha256(infile.read()).hexdigest()
with database.interface.atomic():
image.save()
return None, 201
from fresnel_lens.resources._shared import ResponseTuple
class Image(FresnelResource):
routes = ("/image/<string:image_id>.jpeg",)
routes = ("/image/<string:image_name>",)
def get(self, image_id: str):
image = database.ImageRecord.get(
database.ImageRecord.uuid == uuid.UUID(image_id)
)
if image.deleted:
raise exceptions.ImageResourceDeletedError(
f"Image with ID '{image_id}' was deleted"
)
filepath = (
flask.current_app.appconfig.storage_path
/ str(image.uuid)
/ f"base.{image.format}"
)
if not filepath.exists():
with database.interface.atomic():
image.deleted = True
image.save()
raise exceptions.ImageFileRemovedError(
f"Image file with ID '{image_id}' removed from the server"
)
flask.send_file(
filepath,
mimetype=f"image/{'jpeg' if image.format == 'jpg' else image.format}",
# images are indexed by UUID with no ability to update, y'all should cache
# this thing 'till the sun explodes
cache_timeout=(60 * 60 * 24 * 365),
)
return (
None,
200,
{constants.HTTP_HEADER_RESPONSE_DIGEST: f"sha-256={image.sha256}"},
)
def delete(self, image_id: str, format: str):
image = database.ImageRecord.get(
database.ImageRecord.uuid
== uuid.UUID(image_id) & database.ImageRecord.format
== format
)
if image.deleted:
raise exceptions.ImageResourceDeletedError(
f"Image with ID '{image_id}' was deleted"
)
filepath = flask.current_app.appconfig.storage_path / str(image.uuid)
with database.interface.atomic():
image.deleted = True
image.save()
if filepath.exists():
shutil.rmtree(filepath)
return None, 204
def get(self, image_name: str) -> ResponseTuple:
raise NotImplementedError

View File

@@ -3,17 +3,23 @@ from pathlib import Path
from ruamel.yaml import YAML
from fresnel_lens.resources._shared import FresnelResource
from fresnel_lens.resources._shared import ResponseTuple
yaml = YAML(typ="safe")
class OpenAPI(FresnelResource):
"""Handle requests for the OpenAPI specification resource"""
routes = ("/openapi.json",)
def get(self):
with (Path(__file__).parent, "openapi.yaml").open() as infile:
def get(self) -> ResponseTuple:
"""Retrieve the OpenAPI specification document"""
with (Path(__file__).parent / "openapi.yaml").open() as infile:
data = yaml.load(infile)
return data, 200
return self.make_response(data)
def head(self) -> ResponseTuple:
"""Alias of GET with no response body"""
return self._head(self.get())

View File

@@ -1,17 +0,0 @@
from fresnel_lens.resources._shared import FresnelResource
class ThumbnailScale(FresnelResource):
routes = ("/thumb/<string:image_id>/scale/<int:scale_width>.jpg",)
def get(self, image_id: str, scale_width: int):
raise NotImplementedError
class ThumbnailResize(FresnelResource):
routes = ("/thumb/<string:image_id>/size/<int:width>x<int:height>.jpg",)
def get(self, image_id: str, width: int, height: int):
raise NotImplementedError