Compare commits
No commits in common. "8c231b5bf4fb429cba8c2ced1a684ae4d9090592" and "03df8bbb65946bfa90e7ad458ba11f5cff30147e" have entirely different histories.
8c231b5bf4
...
03df8bbb65
25 changed files with 2213 additions and 1459 deletions
|
|
@ -11,12 +11,9 @@
|
||||||
**/.dockerignore
|
**/.dockerignore
|
||||||
|
|
||||||
# found in python and JS dirs
|
# found in python and JS dirs
|
||||||
**/__pycache__/
|
**/__pycache__
|
||||||
**/node_modules/
|
**/node_modules
|
||||||
**/.pytest_cache/
|
**/.pytest_cache
|
||||||
**/.ruff_cache/
|
|
||||||
**/.uv_cache/
|
|
||||||
**/.venv/
|
|
||||||
|
|
||||||
# env files
|
# env files
|
||||||
**/.env
|
**/.env
|
||||||
|
|
@ -28,6 +25,3 @@
|
||||||
**/yarn-debug.log*
|
**/yarn-debug.log*
|
||||||
**/yarn-error.log*
|
**/yarn-error.log*
|
||||||
**/pnpm-debug.log*
|
**/pnpm-debug.log*
|
||||||
|
|
||||||
# custom files
|
|
||||||
api/api.conf
|
|
||||||
|
|
|
||||||
174
Dockerfile
174
Dockerfile
|
|
@ -1,6 +1,51 @@
|
||||||
ARG NODE_VERSION=24
|
ARG NODE_VERSION=24
|
||||||
ARG PYTHON_VERSION=3.14
|
ARG PYTHON_VERSION=3.14-slim
|
||||||
|
|
||||||
|
#############
|
||||||
|
# build api #
|
||||||
|
#############
|
||||||
|
|
||||||
|
ARG PYTHON_VERSION
|
||||||
|
FROM python:${PYTHON_VERSION} AS build-api
|
||||||
|
|
||||||
|
# env setup
|
||||||
|
WORKDIR /usr/local/src/advent22_api
|
||||||
|
ENV \
|
||||||
|
PATH="/root/.local/bin:${PATH}"
|
||||||
|
|
||||||
|
# install poetry with export plugin
|
||||||
|
RUN set -ex; \
|
||||||
|
\
|
||||||
|
python -m pip --no-cache-dir install --upgrade pip wheel; \
|
||||||
|
\
|
||||||
|
apt-get update; apt-get install --yes --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
; rm -rf /var/lib/apt/lists/*; \
|
||||||
|
\
|
||||||
|
curl -sSL https://install.python-poetry.org | python3 -; \
|
||||||
|
poetry self add poetry-plugin-export;
|
||||||
|
|
||||||
|
# build dependency wheels
|
||||||
|
COPY api/pyproject.toml api/poetry.lock ./
|
||||||
|
RUN set -ex; \
|
||||||
|
\
|
||||||
|
# # buildtime dependencies
|
||||||
|
# apt-get update; apt-get install --yes --no-install-recommends \
|
||||||
|
# build-essential \
|
||||||
|
# ; rm -rf /var/lib/apt/lists/*; \
|
||||||
|
\
|
||||||
|
# generate requirements.txt
|
||||||
|
poetry export \
|
||||||
|
--format requirements.txt \
|
||||||
|
--output requirements.txt; \
|
||||||
|
\
|
||||||
|
python3 -m pip --no-cache-dir wheel \
|
||||||
|
--wheel-dir ./dist \
|
||||||
|
--requirement requirements.txt;
|
||||||
|
|
||||||
|
# build advent22_api wheel
|
||||||
|
COPY api ./
|
||||||
|
RUN poetry build --format wheel --output ./dist
|
||||||
|
|
||||||
############
|
############
|
||||||
# build ui #
|
# build ui #
|
||||||
|
|
@ -9,91 +54,76 @@ ARG PYTHON_VERSION=3.14
|
||||||
ARG NODE_VERSION
|
ARG NODE_VERSION
|
||||||
FROM node:${NODE_VERSION} AS build-ui
|
FROM node:${NODE_VERSION} AS build-ui
|
||||||
|
|
||||||
# install ui dependencies
|
# env setup
|
||||||
WORKDIR /usr/local/src/advent22_ui
|
WORKDIR /usr/local/src/advent22_ui
|
||||||
RUN --mount=type=bind,source=ui/package.json,target=package.json \
|
|
||||||
--mount=type=bind,source=ui/yarn.lock,target=yarn.lock \
|
|
||||||
--mount=type=bind,source=ui/.yarn/releases,target=.yarn/releases \
|
|
||||||
--mount=type=bind,source=ui/.yarnrc.yml,target=.yarnrc.yml \
|
|
||||||
--mount=type=cache,id=ui,target=/root/.yarn \
|
|
||||||
\
|
|
||||||
yarn install --immutable --check-cache;
|
|
||||||
|
|
||||||
# copy and build ui
|
# install advent22_ui dependencies
|
||||||
|
COPY ui/package*.json ui/yarn*.lock ./
|
||||||
|
RUN set -ex; \
|
||||||
|
corepack enable; \
|
||||||
|
yarn install;
|
||||||
|
|
||||||
|
# copy and build advent22_ui
|
||||||
COPY ui ./
|
COPY ui ./
|
||||||
RUN --mount=type=cache,id=ui,target=/root/.yarn \
|
RUN set -ex; \
|
||||||
set -ex; \
|
|
||||||
\
|
|
||||||
yarn dlx update-browserslist-db@latest; \
|
yarn dlx update-browserslist-db@latest; \
|
||||||
yarn build --dest /opt/advent22/ui; \
|
yarn build --dest /tmp/advent22_ui/html; \
|
||||||
# exclude webpack-bundle-analyzer output
|
# exclude webpack-bundle-analyzer output
|
||||||
rm -f /opt/advent22/ui/report.html;
|
rm -f /tmp/advent22_ui/html/report.html;
|
||||||
|
|
||||||
|
######################
|
||||||
###############
|
# python preparation #
|
||||||
# install app #
|
######################
|
||||||
###############
|
|
||||||
|
|
||||||
ARG PYTHON_VERSION
|
ARG PYTHON_VERSION
|
||||||
FROM dhi.io/python:${PYTHON_VERSION}-dev AS install-app
|
FROM python:${PYTHON_VERSION} AS uvicorn-gunicorn
|
||||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
|
||||||
|
# where credit is due ...
|
||||||
|
LABEL maintainer="Sebastián Ramirez <tiangolo@gmail.com>"
|
||||||
|
WORKDIR /usr/local/share/uvicorn-gunicorn
|
||||||
|
|
||||||
|
# install uvicorn-gunicorn
|
||||||
|
COPY ./scripts/mini-tiangolo ./
|
||||||
|
|
||||||
|
RUN set -ex; \
|
||||||
|
chmod +x start.sh; \
|
||||||
|
python3 -m pip --no-cache-dir install gunicorn;
|
||||||
|
|
||||||
|
CMD ["/usr/local/share/uvicorn-gunicorn/start.sh"]
|
||||||
|
|
||||||
|
###########
|
||||||
|
# web app #
|
||||||
|
###########
|
||||||
|
|
||||||
|
FROM uvicorn-gunicorn AS production
|
||||||
|
|
||||||
# env setup
|
# env setup
|
||||||
WORKDIR /opt/advent22
|
ENV \
|
||||||
ENV UV_COMPILE_BYTECODE=1 \
|
PRODUCTION_MODE="true" \
|
||||||
UV_NO_DEV=1 \
|
PORT="8000" \
|
||||||
UV_LINK_MODE="copy"
|
MODULE_NAME="advent22_api.app"
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
RUN --mount=type=bind,source=api/uv.lock,target=api/uv.lock \
|
WORKDIR /opt/advent22
|
||||||
--mount=type=bind,source=api/pyproject.toml,target=api/pyproject.toml \
|
VOLUME [ "/opt/advent22" ]
|
||||||
--mount=type=bind,source=api/.python-version,target=api/.python-version \
|
|
||||||
--mount=type=cache,id=api,target=/root/.cache/uv \
|
COPY --from=build-api /usr/local/src/advent22_api/dist /usr/local/share/advent22_api.dist
|
||||||
set -ex; \
|
RUN set -ex; \
|
||||||
|
# remove example app
|
||||||
|
rm -rf /app; \
|
||||||
|
\
|
||||||
|
# # runtime dependencies
|
||||||
|
# apt-get update; apt-get install --yes --no-install-recommends \
|
||||||
|
# ; rm -rf /var/lib/apt/lists/*; \
|
||||||
|
\
|
||||||
|
# install advent22_api wheels
|
||||||
|
python3 -m pip --no-cache-dir install --no-deps /usr/local/share/advent22_api.dist/*.whl; \
|
||||||
\
|
\
|
||||||
# prepare data directory
|
# prepare data directory
|
||||||
mkdir data; \
|
chown nobody:nogroup ./
|
||||||
chown nobody:nobody data; \
|
|
||||||
chmod u=rwx,g=rx,o=rx data; \
|
|
||||||
\
|
|
||||||
# install api deps
|
|
||||||
uv sync \
|
|
||||||
--project api/ \
|
|
||||||
--locked \
|
|
||||||
--no-install-project \
|
|
||||||
--no-editable \
|
|
||||||
;
|
|
||||||
|
|
||||||
# install api
|
# add prepared advent22_ui
|
||||||
COPY api api/
|
COPY --from=build-ui /tmp/advent22_ui /usr/local/share/advent22_ui
|
||||||
RUN --mount=type=cache,id=api,target=/root/.cache/uv \
|
|
||||||
\
|
|
||||||
uv sync \
|
|
||||||
--project api/ \
|
|
||||||
--locked \
|
|
||||||
--no-editable \
|
|
||||||
;
|
|
||||||
|
|
||||||
# add prepared ui
|
|
||||||
COPY --from=build-ui /opt/advent22/ui ui/
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
|
||||||
# production image #
|
|
||||||
####################
|
|
||||||
|
|
||||||
ARG PYTHON_VERSION
|
|
||||||
FROM dhi.io/python:${PYTHON_VERSION} AS production
|
|
||||||
|
|
||||||
ENV PATH="/opt/advent22/api/.venv/bin:$PATH"
|
|
||||||
EXPOSE 8000
|
|
||||||
CMD [ "advent22" ]
|
|
||||||
|
|
||||||
ARG PYTHON_VERSION
|
|
||||||
COPY --from=install-app /opt/python/lib/python${PYTHON_VERSION} /opt/python/lib/python${PYTHON_VERSION}/
|
|
||||||
COPY --from=install-app /opt/advent22 /opt/advent22/
|
|
||||||
|
|
||||||
WORKDIR /opt/advent22/data
|
|
||||||
VOLUME [ "/opt/advent22/data" ]
|
|
||||||
|
|
||||||
# run as unprivileged user
|
# run as unprivileged user
|
||||||
USER nobody
|
USER nobody
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,7 @@
|
||||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/git-lfs:1": {},
|
"ghcr.io/devcontainers/features/git-lfs:1": {},
|
||||||
"ghcr.io/devcontainers-extra/features/uv:1": {},
|
"ghcr.io/devcontainers-extra/features/poetry:2": {},
|
||||||
"ghcr.io/devcontainers-extra/features/zsh-plugins:0": {
|
|
||||||
"plugins": "git-flow uv"
|
|
||||||
},
|
|
||||||
"ghcr.io/devcontainers-extra/features/apt-get-packages:1": {
|
"ghcr.io/devcontainers-extra/features/apt-get-packages:1": {
|
||||||
"packages": "git-flow"
|
"packages": "git-flow"
|
||||||
},
|
},
|
||||||
|
|
@ -20,8 +17,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"containerEnv": {
|
"containerEnv": {
|
||||||
"TZ": "Europe/Berlin",
|
"TZ": "Europe/Berlin"
|
||||||
"UV_CACHE_DIR": "/workspaces/advent22/.uv_cache"
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Configure tool-specific properties.
|
// Configure tool-specific properties.
|
||||||
|
|
@ -35,21 +31,22 @@
|
||||||
},
|
},
|
||||||
// Add the IDs of extensions you want installed when the container is created.
|
// Add the IDs of extensions you want installed when the container is created.
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"astral-sh.ty",
|
|
||||||
"charliermarsh.ruff",
|
|
||||||
"be5invis.toml",
|
"be5invis.toml",
|
||||||
"mhutchie.git-graph",
|
"mhutchie.git-graph",
|
||||||
"ms-python.python",
|
"ms-python.python",
|
||||||
|
"ms-python.black-formatter",
|
||||||
|
"ms-python.flake8",
|
||||||
|
"ms-python.isort",
|
||||||
"ms-python.vscode-pylance"
|
"ms-python.vscode-pylance"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Use 'postCreateCommand' to run commands after the container is created.
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
"postCreateCommand": "uv tool install uv-upx",
|
"postCreateCommand": "sudo /usr/local/py-utils/bin/poetry self add poetry-plugin-up",
|
||||||
|
|
||||||
// Use 'postStartCommand' to run commands after the container is started.
|
// Use 'postStartCommand' to run commands after the container is started.
|
||||||
"postStartCommand": "uv tool upgrade uv-upx && uv sync"
|
"postStartCommand": "poetry install"
|
||||||
|
|
||||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
// "forwardPorts": [],
|
// "forwardPorts": [],
|
||||||
|
|
|
||||||
4
api/.flake8
Normal file
4
api/.flake8
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
[flake8]
|
||||||
|
max-line-length = 80
|
||||||
|
extend-select = B950
|
||||||
|
extend-ignore = E203,E501
|
||||||
3
api/.isort.cfg
Normal file
3
api/.isort.cfg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[settings]
|
||||||
|
profile = black
|
||||||
|
line_length = 80
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
3.14
|
|
||||||
18
api/.vscode/launch.json
vendored
18
api/.vscode/launch.json
vendored
|
|
@ -5,22 +5,18 @@
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "FastAPI CLI (dev)",
|
"name": "Main Module",
|
||||||
"type": "debugpy",
|
"type": "python",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"module": "fastapi",
|
"module": "advent22_api.main",
|
||||||
"args": [
|
"pythonArgs": [
|
||||||
"dev",
|
"-Xfrozen_modules=off",
|
||||||
"--host", "0.0.0.0",
|
|
||||||
"--port", "8000",
|
|
||||||
"--entrypoint", "advent22_api.app:app",
|
|
||||||
"--reload-dir", "${workspaceFolder}/advent22_api",
|
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
|
"PYDEVD_DISABLE_FILE_VALIDATION": "1",
|
||||||
"WEBDAV__CACHE_TTL": "30",
|
"WEBDAV__CACHE_TTL": "30",
|
||||||
},
|
},
|
||||||
"justMyCode": true,
|
"justMyCode": true,
|
||||||
},
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
21
api/.vscode/settings.json
vendored
21
api/.vscode/settings.json
vendored
|
|
@ -3,16 +3,31 @@
|
||||||
|
|
||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.defaultFormatter": "charliermarsh.ruff",
|
"editor.defaultFormatter": "ms-python.black-formatter",
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.organizeImports": "explicit",
|
"source.organizeImports": "explicit",
|
||||||
"source.fixAll": "explicit",
|
"source.fixAll": "explicit",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"python.languageServer": "Pylance",
|
||||||
|
"python.analysis.autoImportCompletions": true,
|
||||||
|
"python.analysis.importFormat": "relative",
|
||||||
|
"python.analysis.fixAll": [
|
||||||
|
"source.convertImportFormat",
|
||||||
|
"source.unusedImports",
|
||||||
|
],
|
||||||
|
"python.analysis.typeCheckingMode": "basic",
|
||||||
|
"python.analysis.diagnosticMode": "workspace",
|
||||||
|
|
||||||
"python.testing.unittestEnabled": false,
|
"python.testing.unittestEnabled": false,
|
||||||
"python.testing.pytestEnabled": true,
|
"python.testing.pytestEnabled": true,
|
||||||
|
"python.testing.pytestArgs": [
|
||||||
|
"--import-mode=importlib",
|
||||||
|
"test",
|
||||||
|
],
|
||||||
|
|
||||||
"ty.diagnosticMode": "workspace",
|
"black-formatter.importStrategy": "fromEnvironment",
|
||||||
"ruff.nativeServer": "on",
|
"flake8.importStrategy": "fromEnvironment",
|
||||||
|
"isort.importStrategy": "fromEnvironment",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
api/LICENSE
21
api/LICENSE
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2022 Lenaisten e.V.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
@ -37,8 +37,7 @@ if SETTINGS.production_mode:
|
||||||
else:
|
else:
|
||||||
# Allow CORS in debug mode
|
# Allow CORS in debug mode
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
# HACK: suppress while unresolved https://github.com/astral-sh/ty/issues/1635
|
CORSMiddleware,
|
||||||
CORSMiddleware, # ty: ignore[invalid-argument-type]
|
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_origins=["*"],
|
allow_origins=["*"],
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import colorsys
|
import colorsys
|
||||||
import logging
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Self, cast
|
from typing import AnyStr, Self, TypeAlias
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image as PILImage
|
from PIL import Image as PILImage
|
||||||
|
|
@ -11,9 +11,9 @@ from PIL.ImageFont import FreeTypeFont
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
|
|
||||||
type _RGB = tuple[int, int, int]
|
_RGB: TypeAlias = tuple[int, int, int]
|
||||||
type _XY = tuple[float, float]
|
_XY: TypeAlias = tuple[float, float]
|
||||||
type _Box = tuple[int, int, int, int]
|
_Box: TypeAlias = tuple[int, int, int, int]
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ class AdventImage:
|
||||||
async def get_text_box(
|
async def get_text_box(
|
||||||
self,
|
self,
|
||||||
xy: _XY,
|
xy: _XY,
|
||||||
text: str,
|
text: AnyStr,
|
||||||
font: FreeTypeFont,
|
font: FreeTypeFont,
|
||||||
anchor: str | None = "mm",
|
anchor: str | None = "mm",
|
||||||
**text_kwargs,
|
**text_kwargs,
|
||||||
|
|
@ -95,12 +95,12 @@ class AdventImage:
|
||||||
pixel_data = np.asarray(self.img.crop(box))
|
pixel_data = np.asarray(self.img.crop(box))
|
||||||
mean_color: np.ndarray = np.mean(pixel_data, axis=(0, 1))
|
mean_color: np.ndarray = np.mean(pixel_data, axis=(0, 1))
|
||||||
|
|
||||||
return cast(_RGB, mean_color.astype(int))
|
return _RGB(mean_color.astype(int))
|
||||||
|
|
||||||
async def hide_text(
|
async def hide_text(
|
||||||
self,
|
self,
|
||||||
xy: _XY,
|
xy: _XY,
|
||||||
text: str,
|
text: AnyStr,
|
||||||
font: FreeTypeFont,
|
font: FreeTypeFont,
|
||||||
anchor: str | None = "mm",
|
anchor: str | None = "mm",
|
||||||
**text_kwargs,
|
**text_kwargs,
|
||||||
|
|
@ -134,10 +134,8 @@ class AdventImage:
|
||||||
else:
|
else:
|
||||||
tc_v -= 3
|
tc_v -= 3
|
||||||
|
|
||||||
text_color: tuple[int | float, int | float, int | float] = colorsys.hsv_to_rgb(
|
text_color = colorsys.hsv_to_rgb(tc_h, tc_s, tc_v)
|
||||||
tc_h, tc_s, tc_v
|
text_color = _RGB(int(val) for val in text_color)
|
||||||
)
|
|
||||||
text_color = cast(_RGB, tuple(int(val) for val in text_color))
|
|
||||||
|
|
||||||
# Buchstaben verstecken
|
# Buchstaben verstecken
|
||||||
ImageDraw.Draw(self.img).text(
|
ImageDraw.Draw(self.img).text(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import tomllib
|
import tomllib
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
import tomli_w
|
import tomli_w
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
|
|
@ -19,7 +20,7 @@ class DoorSaved(BaseModel):
|
||||||
y2: int
|
y2: int
|
||||||
|
|
||||||
|
|
||||||
type DoorsSaved = list[DoorSaved]
|
DoorsSaved: TypeAlias = list[DoorSaved]
|
||||||
|
|
||||||
|
|
||||||
class CalendarConfig(BaseModel):
|
class CalendarConfig(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,6 @@ class RedisCache(__RedisCache):
|
||||||
try:
|
try:
|
||||||
return super()._deserialize(s)
|
return super()._deserialize(s)
|
||||||
|
|
||||||
except UnicodeDecodeError, JSONDecodeError:
|
except (UnicodeDecodeError, JSONDecodeError):
|
||||||
assert isinstance(s, bytes)
|
assert isinstance(s, bytes)
|
||||||
return s
|
return s
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,7 @@ async def get_day_image(
|
||||||
image = await AdventImage.from_img(img, cfg)
|
image = await AdventImage.from_img(img, cfg)
|
||||||
return image.img
|
return image.img
|
||||||
|
|
||||||
except KeyError, RuntimeError:
|
except (KeyError, RuntimeError):
|
||||||
# Erstelle automatisch generiertes Bild
|
# Erstelle automatisch generiertes Bild
|
||||||
return await gen_day_auto_image(
|
return await gen_day_auto_image(
|
||||||
day=day,
|
day=day,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class Credentials(BaseModel):
|
class Credentials(BaseModel):
|
||||||
username: str = ""
|
username: str = ""
|
||||||
|
|
@ -51,7 +55,6 @@ class Settings(BaseSettings):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_prefix="ADVENT22__",
|
|
||||||
env_file="api.conf",
|
env_file="api.conf",
|
||||||
env_file_encoding="utf-8",
|
env_file_encoding="utf-8",
|
||||||
env_nested_delimiter="__",
|
env_nested_delimiter="__",
|
||||||
|
|
@ -62,13 +65,13 @@ class Settings(BaseSettings):
|
||||||
#####
|
#####
|
||||||
|
|
||||||
production_mode: bool = False
|
production_mode: bool = False
|
||||||
ui_directory: str = "/opt/advent22/ui"
|
ui_directory: str = "/usr/local/share/advent22_ui/html"
|
||||||
|
|
||||||
#####
|
#####
|
||||||
# openapi settings
|
# openapi settings
|
||||||
#####
|
#####
|
||||||
|
|
||||||
def __dev_value[T](self, value: T) -> T | None:
|
def __dev_value(self, value: T) -> T | None:
|
||||||
if self.production_mode:
|
if self.production_mode:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
||||||
22
api/advent22_api/main.py
Normal file
22
api/advent22_api/main.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
from .core.settings import SETTINGS
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""
|
||||||
|
If the `main` script is run, `uvicorn` is used to run the app.
|
||||||
|
"""
|
||||||
|
|
||||||
|
uvicorn.run(
|
||||||
|
app="advent22_api.app:app",
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=8000,
|
||||||
|
reload=not SETTINGS.production_mode,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
import os
|
|
||||||
|
|
||||||
from granian.cli import cli as granian_cli
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
||||||
|
|
||||||
|
|
||||||
class WorkersSettings(BaseModel):
|
|
||||||
per_core: int = Field(1, ge=1)
|
|
||||||
max: int | None = Field(None, ge=1)
|
|
||||||
exact: int | None = Field(None, ge=1)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def count(self) -> int:
|
|
||||||
# usage of "or" operator: values here are not allowed to be 0
|
|
||||||
base = self.exact or (self.per_core * (os.cpu_count() or 1))
|
|
||||||
return min(base, self.max or base)
|
|
||||||
|
|
||||||
|
|
||||||
class BindSettings(BaseModel):
|
|
||||||
host: str = "0.0.0.0"
|
|
||||||
port: int = 8000
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
|
||||||
model_config = SettingsConfigDict(
|
|
||||||
env_prefix="ADVENT22__",
|
|
||||||
env_nested_delimiter="__",
|
|
||||||
)
|
|
||||||
|
|
||||||
workers: WorkersSettings = WorkersSettings()
|
|
||||||
bind: BindSettings = BindSettings()
|
|
||||||
|
|
||||||
|
|
||||||
def start():
|
|
||||||
os.environ["ADVENT22__PRODUCTION_MODE"] = "true"
|
|
||||||
|
|
||||||
settings = Settings()
|
|
||||||
granian_cli(
|
|
||||||
[
|
|
||||||
"--host",
|
|
||||||
settings.bind.host,
|
|
||||||
"--port",
|
|
||||||
settings.bind.port,
|
|
||||||
"--workers",
|
|
||||||
settings.workers.count,
|
|
||||||
"--interface",
|
|
||||||
"asgi",
|
|
||||||
"--loop",
|
|
||||||
"uvloop",
|
|
||||||
"--process-name",
|
|
||||||
"advent22",
|
|
||||||
# app
|
|
||||||
"advent22_api.app:app",
|
|
||||||
],
|
|
||||||
auto_envvar_prefix="GRANIAN",
|
|
||||||
standalone_mode=False,
|
|
||||||
)
|
|
||||||
1876
api/poetry.lock
generated
Normal file
1876
api/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,44 +1,33 @@
|
||||||
[project]
|
[tool.poetry]
|
||||||
name = "advent22-api"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = ""
|
|
||||||
license = {file = "LICENSE"}
|
|
||||||
readme = "README.md"
|
|
||||||
requires-python = ">=3.14"
|
|
||||||
authors = [
|
authors = [
|
||||||
{name = "Jörn-Michael Miehe", email = "jmm@yavook.de"},
|
"Jörn-Michael Miehe <jmm@yavook.de>",
|
||||||
{name = "Penner42", email = "unbekannt42@web.de"},
|
"Penner42 <unbekannt42@web.de>",
|
||||||
]
|
|
||||||
dependencies = [
|
|
||||||
"asyncify>=0.12.1",
|
|
||||||
"cachetools>=7.0.1",
|
|
||||||
"cachetoolsutils>=11.0",
|
|
||||||
"fastapi>=0.129.0",
|
|
||||||
"granian[pname,uvloop]>=2.7.1",
|
|
||||||
"markdown>=3.10.2",
|
|
||||||
"numpy>=2.4.2",
|
|
||||||
"pillow>=12.1.1",
|
|
||||||
"pydantic-settings>=2.13.1",
|
|
||||||
"redis[hiredis]>=7.2.0",
|
|
||||||
"requests>=2.32.5",
|
|
||||||
"tomli-w>=1.2.0",
|
|
||||||
"webdavclient3>=3.14.7",
|
|
||||||
]
|
]
|
||||||
|
description = ""
|
||||||
|
license = "MIT"
|
||||||
|
name = "advent22_api"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[project.scripts]
|
[tool.poetry.dependencies]
|
||||||
advent22 = "advent22_api.production:start"
|
Pillow = "^12.1.1"
|
||||||
|
asyncify = "^0.12.1"
|
||||||
|
cachetools = "^7.0.1"
|
||||||
|
cachetoolsutils = "^11.0"
|
||||||
|
fastapi = "^0.129.0"
|
||||||
|
markdown = "^3.10.2"
|
||||||
|
numpy = "^2.4.2"
|
||||||
|
pydantic-settings = "^2.13.0"
|
||||||
|
python = ">=3.11,<3.15"
|
||||||
|
redis = {extras = ["hiredis"], version = "^7.1.1"}
|
||||||
|
tomli-w = "^1.2.0"
|
||||||
|
uvicorn = {extras = ["standard"], version = "^0.40.0"}
|
||||||
|
webdavclient3 = "^3.14.7"
|
||||||
|
|
||||||
[dependency-groups]
|
[tool.poetry.group.dev.dependencies]
|
||||||
dev = [
|
black = "^26.1.0"
|
||||||
"fastapi[standard]>=0.129.0",
|
flake8 = "^7.3.0"
|
||||||
"pytest>=9.0.2",
|
pytest = "^9.0.2"
|
||||||
"ruff>=0.15.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["uv_build>=0.10.4,<0.11.0", "packaging"]
|
build-backend = "poetry.core.masonry.api"
|
||||||
build-backend = "uv_build"
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
|
||||||
[tool.uv.build-backend]
|
|
||||||
# module-name = "advent22_api"
|
|
||||||
module-root = ""
|
|
||||||
|
|
|
||||||
1177
api/uv.lock
1177
api/uv.lock
File diff suppressed because it is too large
Load diff
67
scripts/mini-tiangolo/gunicorn_conf.py
Normal file
67
scripts/mini-tiangolo/gunicorn_conf.py
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import json
|
||||||
|
import multiprocessing
|
||||||
|
import os
|
||||||
|
|
||||||
|
workers_per_core_str = os.getenv("WORKERS_PER_CORE", "1")
|
||||||
|
max_workers_str = os.getenv("MAX_WORKERS")
|
||||||
|
use_max_workers = None
|
||||||
|
if max_workers_str:
|
||||||
|
use_max_workers = int(max_workers_str)
|
||||||
|
web_concurrency_str = os.getenv("WEB_CONCURRENCY", None)
|
||||||
|
|
||||||
|
host = os.getenv("HOST", "0.0.0.0")
|
||||||
|
port = os.getenv("PORT", "80")
|
||||||
|
bind_env = os.getenv("BIND", None)
|
||||||
|
use_loglevel = os.getenv("LOG_LEVEL", "info")
|
||||||
|
if bind_env:
|
||||||
|
use_bind = bind_env
|
||||||
|
else:
|
||||||
|
use_bind = f"{host}:{port}"
|
||||||
|
|
||||||
|
cores = multiprocessing.cpu_count()
|
||||||
|
workers_per_core = float(workers_per_core_str)
|
||||||
|
default_web_concurrency = workers_per_core * cores
|
||||||
|
if web_concurrency_str:
|
||||||
|
web_concurrency = int(web_concurrency_str)
|
||||||
|
assert web_concurrency > 0
|
||||||
|
else:
|
||||||
|
web_concurrency = max(int(default_web_concurrency), 2)
|
||||||
|
if use_max_workers:
|
||||||
|
web_concurrency = min(web_concurrency, use_max_workers)
|
||||||
|
accesslog_var = os.getenv("ACCESS_LOG", "-")
|
||||||
|
use_accesslog = accesslog_var or None
|
||||||
|
errorlog_var = os.getenv("ERROR_LOG", "-")
|
||||||
|
use_errorlog = errorlog_var or None
|
||||||
|
graceful_timeout_str = os.getenv("GRACEFUL_TIMEOUT", "120")
|
||||||
|
timeout_str = os.getenv("TIMEOUT", "120")
|
||||||
|
keepalive_str = os.getenv("KEEP_ALIVE", "5")
|
||||||
|
|
||||||
|
# Gunicorn config variables
|
||||||
|
loglevel = use_loglevel
|
||||||
|
workers = web_concurrency
|
||||||
|
bind = use_bind
|
||||||
|
errorlog = use_errorlog
|
||||||
|
worker_tmp_dir = "/dev/shm"
|
||||||
|
accesslog = use_accesslog
|
||||||
|
graceful_timeout = int(graceful_timeout_str)
|
||||||
|
timeout = int(timeout_str)
|
||||||
|
keepalive = int(keepalive_str)
|
||||||
|
|
||||||
|
|
||||||
|
# For debugging and testing
|
||||||
|
log_data = {
|
||||||
|
"loglevel": loglevel,
|
||||||
|
"workers": workers,
|
||||||
|
"bind": bind,
|
||||||
|
"graceful_timeout": graceful_timeout,
|
||||||
|
"timeout": timeout,
|
||||||
|
"keepalive": keepalive,
|
||||||
|
"errorlog": errorlog,
|
||||||
|
"accesslog": accesslog,
|
||||||
|
# Additional, non-gunicorn variables
|
||||||
|
"workers_per_core": workers_per_core,
|
||||||
|
"use_max_workers": use_max_workers,
|
||||||
|
"host": host,
|
||||||
|
"port": port,
|
||||||
|
}
|
||||||
|
print(json.dumps(log_data))
|
||||||
20
scripts/mini-tiangolo/start.sh
Normal file
20
scripts/mini-tiangolo/start.sh
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
MODULE_NAME=${MODULE_NAME:-"app.main"}
|
||||||
|
VARIABLE_NAME=${VARIABLE_NAME:-"app"}
|
||||||
|
export APP_MODULE="${APP_MODULE:-"$MODULE_NAME:$VARIABLE_NAME"}"
|
||||||
|
export GUNICORN_CONF="${GUNICORN_CONF:-"/usr/local/share/uvicorn-gunicorn/gunicorn_conf.py"}"
|
||||||
|
export WORKER_CLASS="${WORKER_CLASS:-"uvicorn.workers.UvicornWorker"}"
|
||||||
|
|
||||||
|
if [ -f "${PRE_START_PATH}" ] ; then
|
||||||
|
echo "Running script ${PRE_START_PATH}"
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
. "${PRE_START_PATH}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start Gunicorn
|
||||||
|
exec gunicorn \
|
||||||
|
-k "${WORKER_CLASS}" \
|
||||||
|
-c "${GUNICORN_CONF}" \
|
||||||
|
"${APP_MODULE}"
|
||||||
|
|
@ -1,51 +1,48 @@
|
||||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/javascript-node
|
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/javascript-node
|
||||||
{
|
{
|
||||||
"name": "Advent22 UI",
|
"name": "Advent22 UI",
|
||||||
|
|
||||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||||
"image": "mcr.microsoft.com/devcontainers/javascript-node:4-24-trixie",
|
"image": "mcr.microsoft.com/devcontainers/javascript-node:4-24-trixie",
|
||||||
|
|
||||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/git-lfs:1": {},
|
"ghcr.io/devcontainers/features/git-lfs:1": {},
|
||||||
"ghcr.io/devcontainers-extra/features/zsh-plugins:0": {
|
"ghcr.io/devcontainers-extra/features/apt-get-packages:1": {
|
||||||
"plugins": "git-flow npm nvm yarn"
|
"packages": "git-flow"
|
||||||
},
|
|
||||||
"ghcr.io/devcontainers-extra/features/apt-get-packages:1": {
|
|
||||||
"packages": "git-flow"
|
|
||||||
},
|
|
||||||
"ghcr.io/devcontainers-extra/features/vue-cli:2": {}
|
|
||||||
},
|
},
|
||||||
|
"ghcr.io/devcontainers-extra/features/vue-cli:2": {}
|
||||||
|
},
|
||||||
|
|
||||||
// Configure tool-specific properties.
|
// Configure tool-specific properties.
|
||||||
"customizations": {
|
"customizations": {
|
||||||
// Configure properties specific to VS Code.
|
// Configure properties specific to VS Code.
|
||||||
"vscode": {
|
"vscode": {
|
||||||
// Set *default* container specific settings.json values on container create.
|
// Set *default* container specific settings.json values on container create.
|
||||||
"settings": {
|
"settings": {
|
||||||
"terminal.integrated.defaultProfile.linux": "zsh"
|
"terminal.integrated.defaultProfile.linux": "zsh"
|
||||||
},
|
},
|
||||||
// Add the IDs of extensions you want installed when the container is created.
|
// Add the IDs of extensions you want installed when the container is created.
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"mhutchie.git-graph",
|
"mhutchie.git-graph",
|
||||||
"Syler.sass-indented",
|
"Syler.sass-indented",
|
||||||
"Vue.volar"
|
"Vue.volar"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Use 'postCreateCommand' to run commands after the container is created.
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
// "postCreateCommand": "yarn install",
|
// "postCreateCommand": "yarn install",
|
||||||
|
|
||||||
// Use 'postStartCommand' to run commands after the container is started.
|
// Use 'postStartCommand' to run commands after the container is started.
|
||||||
"postStartCommand": "yarn dlx update-browserslist-db@latest && yarn install"
|
"postStartCommand": "yarn dlx update-browserslist-db@latest && yarn install"
|
||||||
|
|
||||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
// "forwardPorts": [],
|
// "forwardPorts": [],
|
||||||
|
|
||||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||||
// "remoteUser": "root"
|
// "remoteUser": "root"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue