Skip to content

Commit

Permalink
ruff manual fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisLovering committed Sep 5, 2023
1 parent 8773613 commit 86ff71f
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 43 deletions.
19 changes: 9 additions & 10 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from os import getenv
from pathlib import Path
from time import time
from typing import Union

from fastapi import FastAPI, Request, Response
from fastapi.exceptions import HTTPException
Expand Down Expand Up @@ -32,7 +31,7 @@

def dicthash(data: dict) -> str:
"""Take a dictionary and convert it to a SHA-1 hash."""
return sha1(dumps(data).encode()).hexdigest()
return sha1(dumps(data).encode()).hexdigest() # noqa: S324


def make_file_path(dh: str, request_url: URL) -> str:
Expand All @@ -44,7 +43,7 @@ def make_file_path(dh: str, request_url: URL) -> str:
@app.get("/duck", response_model=DuckResponse, status_code=201)
async def get_duck(request: Request, response: Response, seed: int | None = None) -> DuckResponse:
"""Create a new random duck, with an optional seed."""
dh = sha1(str(time()).encode()).hexdigest()
dh = sha1(str(time()).encode()).hexdigest() # noqa: S324
file = CACHE / f"{dh}.png"

DuckBuilder(seed).generate().image.save(file)
Expand All @@ -64,9 +63,9 @@ async def post_duck(request: Request, response: Response, duck: DuckRequest = No
try:
DuckBuilder().generate(options=duck.dict()).image.save(file)
except ValueError as e:
raise HTTPException(400, e.args[0])
raise HTTPException(400, e.args[0]) from e
except KeyError as e:
raise HTTPException(400, f"Invalid configuration option provided: '{e.args[0]}'")
raise HTTPException(400, f"Invalid configuration option provided: '{e.args[0]}'") from e

file_path = make_file_path(dh, request.url)
response.headers["Location"] = file_path
Expand All @@ -76,7 +75,7 @@ async def post_duck(request: Request, response: Response, duck: DuckRequest = No
@app.get("/manduck", response_model=DuckResponse, status_code=201)
async def get_man_duck(request: Request, response: Response, seed: int | None = None) -> DuckResponse:
"""Create a new man_duck, with an optional seed."""
dh = sha1(str(time()).encode()).hexdigest()
dh = sha1(str(time()).encode()).hexdigest() # noqa: S324

ducky = DuckBuilder(seed).generate()
ducky = ManDuckBuilder(seed).generate(ducky=ducky)
Expand All @@ -97,18 +96,18 @@ async def post_man_duck(request: Request, response: Response, manduck: ManDuckRe
try:
ducky = ManDuckBuilder().generate(options=manduck.dict())
except ValueError as e:
raise HTTPException(400, e.args[0])
raise HTTPException(400, e.args[0]) from e
except KeyError as e:
raise HTTPException(400, f"Invalid configuration option provided: '{e.args[0]}'")
raise HTTPException(400, f"Invalid configuration option provided: '{e.args[0]}'") from e
ducky.image.save(CACHE / f"{dh}.png")

file_path = make_file_path(dh, request.url)
response.headers["Location"] = file_path
return DuckResponse(file=file_path)


@app.get("/details/{type}", response_model=Union[ManDuckDetails, DuckyDetails])
async def get_details(type: str) -> ManDuckDetails | DuckyDetails:
@app.get("/details/{type}", response_model=ManDuckDetails | DuckyDetails)
async def get_details(type: str) -> ManDuckDetails | DuckyDetails: # noqa: A002
"""Get details about accessories which can be used to build ducks/man-ducks."""
details = {
"ducky": DuckyDetails(
Expand Down
32 changes: 23 additions & 9 deletions quackstack/colors.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
from collections import namedtuple
from colorsys import hls_to_rgb, rgb_to_hls
from random import Random
from typing import NamedTuple

DuckyColors = namedtuple("DuckyColors", "eye eye_wing wing body beak")
DressColors = namedtuple("DressColors", "shirt pants")

class DuckyColors(NamedTuple):
"""RGB tuples of colours for each part of the Ducky."""

def make_color(random: Random, hue: float, dark_variant: bool) -> tuple[float, float, float]:
eye: tuple[int, int, int]
eye_wing: tuple[int, int, int]
wing: tuple[int, int, int]
body: tuple[int, int, int]
beak: tuple[int, int, int]


class DressColors(NamedTuple):
"""RGB tuples of colours for each part of the Ducky's dress."""

shirt: tuple[int, int, int]
pants: tuple[int, int, int]


def make_color(random: Random, hue: float, *, dark_variant: bool) -> tuple[float, float, float]:
"""Make a nice hls color to use in a duck."""
saturation = 1
lightness = random.uniform(.7, .85)
Expand All @@ -27,7 +41,7 @@ def make_duck_colors(random: Random) -> DuckyColors:
"""Create a matching DuckyColors object."""
hue = random.random()
dark_variant = random.choice([True, False])
eye, wing, body, beak = (make_color(random, hue, dark_variant) for _ in range(4))
eye, wing, body, beak = (make_color(random, hue, dark_variant=dark_variant) for _ in range(4))

# Lower the eye light
eye_main = (eye[0], max(.1, eye[1] - .7), eye[2])
Expand All @@ -46,16 +60,16 @@ def make_man_duck_colors(ducky: tuple) -> DressColors:
hls_ = tuple(rgb_to_hls(ducky[0] / 255, ducky[1] / 255, ducky[2] / 255))

# Find the first triadic hls color
first_varient = [((hls_[0] * 360 + 120) % 360) / 360, hls_[1], hls_[2]]
first_variant = [((hls_[0] * 360 + 120) % 360) / 360, hls_[1], hls_[2]]

# Find the second triadic hls color
second_varient = [((hls_[0] * 360 + 240) % 360) / 360, hls_[1], hls_[2]]
second_variant = [((hls_[0] * 360 + 240) % 360) / 360, hls_[1], hls_[2]]

first = tuple(
round(x * 255) for x in hls_to_rgb(first_varient[0], first_varient[1], first_varient[2])
round(x * 255) for x in hls_to_rgb(first_variant[0], first_variant[1], first_variant[2])
)
second = tuple(
round(x * 255) for x in hls_to_rgb(second_varient[0], second_varient[1], second_varient[2])
round(x * 255) for x in hls_to_rgb(second_variant[0], second_variant[1], second_variant[2])
)

return DressColors(first, second)
34 changes: 21 additions & 13 deletions quackstack/ducky.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import os
from collections import namedtuple
from pathlib import Path
from random import Random
from typing import NamedTuple

from PIL import Image, ImageChops
from frozendict import frozendict

from quackstack import __file__ as qs_file

Expand All @@ -12,24 +12,32 @@
ASSETS_PATH = Path(qs_file).parent / Path("assets", "ducky")
DUCK_SIZE = (499, 600)

ProceduralDucky = namedtuple("ProceduralDucky", "image colors hat equipment outfit")

class ProceduralDucky(NamedTuple):
"""Represents a Ducky and all its defined features/colours."""

image: Image.Image
colors: DuckyColors
hat: str
equipment: str
outfit: str


class DuckBuilder:
"""A class used to build new ducks."""

templates = {
templates = frozendict({
int(filename.name[0]): filename for filename in (ASSETS_PATH / "templates").iterdir()
}
hats = {
})
hats = frozendict({
filename.stem: filename for filename in (ASSETS_PATH / "accessories/hats").iterdir()
}
equipments = {
})
equipments = frozendict({
filename.stem: filename for filename in (ASSETS_PATH / "accessories/equipment").iterdir()
}
outfits = {
})
outfits = frozendict({
filename.stem: filename for filename in (ASSETS_PATH / "accessories/outfits").iterdir()
}
})

def __init__(self, seed: int | None = None) -> None:
self.random = Random(seed)
Expand Down Expand Up @@ -85,8 +93,8 @@ def apply_layer(self, layer_path: str, recolor: tuple[int, int, int] | None = No
"""Add the given layer on top of the ducky. Can be recolored with the recolor argument."""
try:
layer = Image.open(layer_path)
except FileNotFoundError:
raise ValueError(f"Invalid option provided: {os.path.basename(layer_path)} not found.")
except FileNotFoundError as e:
raise ValueError(f"Invalid option provided: {Path.name(layer_path)} not found.") from e

if recolor:
if isinstance(recolor, dict):
Expand Down
28 changes: 17 additions & 11 deletions quackstack/manducky.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import os
from collections import namedtuple
from pathlib import Path
from random import Random
from typing import NamedTuple

from PIL import Image, ImageChops
from frozendict import frozendict

from quackstack import __file__ as qs_file

from .colors import DressColors, DuckyColors, make_man_duck_colors
from .ducky import ProceduralDucky

ManDucky = namedtuple("ManDucky", "image")

class ManDucky(NamedTuple):
"""Holds a reference to the ManDucky's source image."""

image: Image.Image


Color = tuple[int, int, int]

ASSETS_PATH = Path(qs_file).parent / Path("assets", "manduck")
Expand All @@ -21,25 +27,25 @@ class ManDuckBuilder:
"""Temporary class used to generate a ducky human."""

VARIATIONS = (1, 2)
HATS = {
HATS = frozendict({
filename.stem: filename for filename in (ASSETS_PATH / "accessories/hats").iterdir()
}
OUTFITS = {
})
OUTFITS = frozendict({
"variation_1": {
filename.stem: filename for filename in (ASSETS_PATH / "accessories/outfits/variation_1").iterdir()
},
"variation_2": {
filename.stem: filename for filename in (ASSETS_PATH / "accessories/outfits/variation_2").iterdir()
},
}
EQUIPMENTS = {
})
EQUIPMENTS = frozendict({
"variation_1": {
filename.stem: filename for filename in (ASSETS_PATH / "accessories/equipment/variation_1").iterdir()
},
"variation_2": {
filename.stem: filename for filename in (ASSETS_PATH / "accessories/equipment/variation_2").iterdir()
},
}
})

def __init__(self, seed: int | None = None) -> None:
self.random = Random(seed)
Expand Down Expand Up @@ -127,8 +133,8 @@ def apply_layer(self, layer_path: str, recolor: Color | None = None) -> None:
"""Add the given layer on top of the ducky. Can be recolored with the recolor argument."""
try:
layer = Image.open(layer_path)
except FileNotFoundError:
raise ValueError(f"Invalid option provided: {os.path.basename(layer_path)} not found.")
except FileNotFoundError as e:
raise ValueError(f"Invalid option provided: {Path.name(layer_path)} not found.") from e

if recolor:
if isinstance(recolor, dict):
Expand Down

0 comments on commit 86ff71f

Please sign in to comment.