Compare commits

...

3 commits

12 changed files with 953 additions and 543 deletions

View file

@ -1,12 +1,19 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.224.2/containers/python-3/.devcontainer/base.Dockerfile
# See here for image contents: https://github.com/devcontainers/images/blob/main/src/python/.devcontainer/Dockerfile
# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster
ARG VARIANT="3.10-bullseye"
# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon):
# - 3, 3.11, 3.10, 3.9, 3.8, 3.7, 3.6
# - 3-bullseye, 3.11-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye
# - 3-buster, 3.11-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster
ARG VARIANT="3.11-bullseye"
FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
# [Choice] Node.js version: none, lts/*, 18, 16, 14, 12, 10
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
RUN set -ex; \
\
if [ "${NODE_VERSION}" != "none" ]; then \
su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; \
fi
# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
# COPY requirements.txt /tmp/pip-tmp/
@ -20,7 +27,7 @@ RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/
RUN set -ex; \
\
export DEBIAN_FRONTEND=noninteractive; \
apt-get update; apt-get -y install --no-install-recommends \
apt-get update; apt-get install --yes --no-install-recommends \
git-flow \
libmagic1 \
; rm -rf /var/lib/apt/lists/*;
@ -28,5 +35,4 @@ RUN set -ex; \
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
USER vscode
RUN curl -sSL https://install.python-poetry.org | python3 -
RUN su vscode -c "curl -sSL https://install.python-poetry.org | python3 -" 2>&1

View file

@ -1,46 +1,51 @@
// 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.224.2/containers/python-3
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/python
{
"name": "Python 3",
"name": "OVD API",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {
// Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6
// Update 'VARIANT' to pick a Python version.
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"VARIANT": "3.9",
// "VARIANT": "3.11-bullseye",
// Options
"NODE_VERSION": "none"
}
},
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.defaultProfile.linux": "zsh",
"python.defaultInterpreterPath": "/usr/local/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint"
"containerEnv": {
"TZ": "Europe/Berlin"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"be5invis.toml"
],
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"terminal.integrated.defaultProfile.linux": "zsh"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"be5invis.toml",
"mhutchie.git-graph",
"ms-python.python",
"ms-python.black-formatter",
// "ms-python.flake8",
"ms-python.isort",
"ms-python.vscode-pylance"
]
}
},
// Use 'postStartCommand' to run commands after the container is started.
"postStartCommand": "poetry install"
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "pip3 install --user -r requirements.txt",
"postStartCommand": "poetry install",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View file

@ -8,7 +8,13 @@
"name": "Main Module",
"type": "python",
"request": "launch",
"module": "ovdashboard_api",
"module": "ovdashboard_api.main",
"pythonArgs": [
"-Xfrozen_modules=off",
],
"env": {
"PYDEVD_DISABLE_FILE_VALIDATION": "1",
},
"justMyCode": true
}
]

View file

@ -1,17 +1,22 @@
{
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.linting.enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.languageServer": "Pylance",
"editor.formatOnSave": true,
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
},
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"git.closeDiffOnOperation": true,
"python.analysis.typeCheckingMode": "basic"
"python.analysis.typeCheckingMode": "basic",
"python.analysis.diagnosticMode": "workspace",
"python.testing.pytestArgs": [
"test"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.formatting.provider": "none",
}

View file

@ -9,11 +9,8 @@ from logging.config import dictConfig
from pydantic import BaseModel
from .app import app
from .settings import SETTINGS
__all__ = ["app"]
class LogConfig(BaseModel):
"""
@ -22,23 +19,23 @@ class LogConfig(BaseModel):
"""
# Logging config
version = 1
disable_existing_loggers = False
formatters = {
version: int = 1
disable_existing_loggers: bool = False
formatters: dict = {
"default": {
"()": "uvicorn.logging.DefaultFormatter",
"fmt": "%(levelprefix)s [%(asctime)s] %(name)s: %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
}
handlers = {
handlers: dict = {
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
}
loggers = {
loggers: dict = {
"ovdashboard_api": {
"handlers": ["default"],
"level": SETTINGS.log_level,

View file

@ -30,33 +30,29 @@ app = FastAPI(
redoc_url=SETTINGS.redoc_url,
)
app.add_event_handler("startup", webdav_check)
@app.on_event("startup")
async def add_middlewares() -> None:
if SETTINGS.production_mode:
# Mount frontend in production mode
app.mount(
path="/",
app=StaticFiles(
directory=SETTINGS.ui_directory,
html=True,
),
name="frontend",
)
else:
# Allow CORS in debug mode
app.add_middleware(
CORSMiddleware,
allow_origins=[
"*",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["*"],
)
app.include_router(v1_router)
webdav_check()
if SETTINGS.production_mode:
# Mount frontend in production mode
app.mount(
path="/",
app=StaticFiles(
directory=SETTINGS.ui_directory,
html=True,
),
name="frontend",
)
else:
# Allow CORS in debug mode
app.add_middleware(
CORSMiddleware,
allow_origins=[
"*",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["*"],
)

View file

@ -4,10 +4,10 @@ Python representation of the "config.txt" file inside the WebDAV directory.
from io import BytesIO
from logging import getLogger
from tomllib import loads as toml_loads
from typing import Any
from pydantic import BaseModel
from tomli import loads as toml_loads
from tomli_w import dump as toml_dump
from webdav3.exceptions import RemoteResourceNotFound
@ -119,9 +119,7 @@ class Config(BaseModel):
dav_file = DavFile(SETTINGS.config_path)
try:
cfg = cls.parse_obj(
toml_loads(await dav_file.as_string)
)
cfg = cls.parse_obj(toml_loads(await dav_file.as_string))
except RemoteResourceNotFound:
_logger.warning(

View file

@ -8,12 +8,12 @@ from dataclasses import dataclass
from datetime import datetime, timedelta
from functools import total_ordering
from logging import getLogger
from typing import Iterator
from typing import Annotated, Iterator
from cache import AsyncTTL
from caldav import Calendar
from caldav.lib.error import ReportError
from pydantic import BaseModel, validator
from pydantic import AfterValidator, BaseModel
from vobject.base import Component
from .async_helpers import run_in_executor
@ -33,6 +33,9 @@ def _string_strip(in_str: str) -> str:
return in_str.strip()
StrippedStr = Annotated[str, AfterValidator(lambda s: s.strip())]
@total_ordering
class CalEvent(BaseModel):
"""
@ -44,8 +47,8 @@ class CalEvent(BaseModel):
https://icalendar.org/iCalendar-RFC-5545/3-6-1-event-component.html
"""
summary: str = ""
description: str = ""
summary: StrippedStr = ""
description: StrippedStr = ""
dtstart: datetime = datetime.utcnow()
dtend: datetime = datetime.utcnow()
@ -66,16 +69,6 @@ class CalEvent(BaseModel):
return self.dict() == other.dict()
_validate_summary = validator(
"summary",
allow_reuse=True,
)(_string_strip)
_validate_description = validator(
"description",
allow_reuse=True,
)(_string_strip)
@classmethod
def from_vevent(cls, event: Component) -> "CalEvent":
"""
@ -178,10 +171,7 @@ async def _get_calendar_events(
vobject: Component = event.vobject_instance # type: ignore
yield from vobject.vevent_list
return sorted([
CalEvent.from_vevent(vevent)
for vevent in await _inner()
])
return sorted([CalEvent.from_vevent(vevent) for vevent in await _inner()])
@dataclass(frozen=True)

View file

@ -9,7 +9,7 @@ def main() -> None:
"""
uvicorn_run(
app="ovdashboard_api:app",
app="ovdashboard_api.app:app",
host="0.0.0.0",
port=8000,
reload=not SETTINGS.production_mode,

View file

@ -9,7 +9,8 @@ Pydantic models might have convenience methods attached.
from typing import Any, Optional
from pydantic import BaseModel, BaseSettings, root_validator
from pydantic import BaseModel, root_validator
from pydantic_settings import BaseSettings
class DavSettings(BaseModel):

1261
api/poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,18 +6,17 @@ name = "ovdashboard-api"
version = "0.1.0"
[tool.poetry.dependencies]
Markdown = "^3.4.1"
Pillow = "^9.2.0"
caldav = "^0.9.1"
fastapi = "^0.81.0"
pydantic = {extras = ["dotenv"], version = "^1.9.2"}
python = "^3.9"
python-magic = "^0.4.27"
tomli = "^2.0.1"
tomli-w = "^1.0.0"
uvicorn = "^0.18.3"
webdavclient3 = "3.14.5"
Markdown = "^3.5"
Pillow = "^10.1.0"
async-cache = "^1.1.1"
caldav = "^1.3.6"
fastapi = "^0.103.2"
pydantic-settings = "^2.0.3"
python = "^3.11"
python-magic = "^0.4.27"
tomli-w = "^1.0.0"
uvicorn = {extras = ["standard"], version = "^0.23.2"}
webdavclient3 = "^3.14.6"
[tool.poetry.dev-dependencies]
# pytest = "^5.2"