Compare commits

...

5 commits

18 changed files with 295 additions and 99 deletions

View file

@ -2,7 +2,9 @@
# build ui #
############
FROM node:lts AS build-ui
ARG NODE_VERSION=lts
ARG PYTHON_VERSION=3.12-slim
FROM node:${NODE_VERSION} AS build-ui
# env setup
WORKDIR /usr/local/src/ovdashboard_ui
@ -19,10 +21,8 @@ RUN yarn build --dest /tmp/ovdashboard_ui/html
# web app #
###########
FROM tiangolo/uvicorn-gunicorn:python3.12-slim AS production
# add prepared ovdashboard_ui
COPY --from=build-ui /tmp/ovdashboard_ui /usr/local/share/ovdashboard_ui
ARG PYTHON_VERSION
FROM tiangolo/uvicorn-gunicorn:python${PYTHON_VERSION} AS production
# env setup
WORKDIR /usr/local/src/ovdashboard_api
@ -46,5 +46,8 @@ RUN set -ex; \
# install ovdashboard_api
python -m pip --no-cache-dir install ./
# add prepared ovdashboard_ui
COPY --from=build-ui /tmp/ovdashboard_ui /usr/local/share/ovdashboard_ui
# run as unprivileged user
USER nobody

View file

@ -2,21 +2,9 @@
// README at: https://github.com/devcontainers/templates/tree/main/src/python
{
"name": "OVD API",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {
// 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.11-bullseye",
// Options
"NODE_VERSION": "none"
}
},
"containerEnv": {
"TZ": "Europe/Berlin"
},
"dockerComposeFile": "docker-compose.yml",
"service": "api",
"workspaceFolder": "/workspaces/ovdashboard/${localWorkspaceFolderBasename}",
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.

View file

@ -0,0 +1,32 @@
version: "3.8"
services:
api:
build:
context: "."
dockerfile: "Dockerfile"
args:
# Update 'VARIANT' to pick a Python version.
# Append -bookworm, -bullseye or -buster to pin to an OS version.
# Use -bookworm or -bullseye variants on local on arm64/Apple Silicon.
VARIANT: "3.12-bookworm"
NODE_VERSION: "none"
environment:
TZ: "Europe/Berlin"
volumes:
- "../..:/workspaces/ovdashboard:cached"
# Overrides default command so things don't shut down after the process ends.
command: "sleep infinity"
# Runs app on the same network as the redis container, allows "forwardPorts" in devcontainer.json function.
network_mode: "service:redis"
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
redis:
image: "redis:7"
restart: "unless-stopped"

View file

@ -13,8 +13,8 @@ from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from .core.dav.webdav import WebDAV
from .core.settings import SETTINGS
from .core.webdav import WebDAV
from .routers import v1_router
_logger = logging.getLogger(__name__)

View file

@ -1,17 +1,16 @@
import functools
import logging
from datetime import datetime, timedelta
from typing import cast
from asyncify import asyncify
from cachetools import TTLCache, cachedmethod
from cachetools import cachedmethod
from caldav import Calendar, DAVClient, Event, Principal
from vobject.base import Component, toVName
from .calevent import CalEvent
from .config import Config
from .settings import SETTINGS
from .webdav import davkey
from ..calevent import CalEvent
from ..config import Config
from ..settings import SETTINGS
from .helpers import REDIS, RedisCache, davkey
_logger = logging.getLogger(__name__)
@ -23,9 +22,9 @@ class CalDAV:
password=SETTINGS.caldav.password,
)
_cache = TTLCache(
_cache = RedisCache(
cache=REDIS,
ttl=SETTINGS.caldav.cache_ttl,
maxsize=SETTINGS.caldav.cache_size,
)
@classmethod
@ -40,10 +39,7 @@ class CalDAV:
@classmethod
@property
@asyncify
@cachedmethod(
cache=lambda cls: cls._cache,
key=functools.partial(davkey, "calendars"),
)
@cachedmethod(cache=lambda cls: cls._cache, key=davkey("calendars"))
def calendars(cls) -> list[str]:
"""
Asynchroneously lists all calendars using the main WebDAV client.
@ -54,10 +50,7 @@ class CalDAV:
@classmethod
@asyncify
@cachedmethod(
cache=lambda cls: cls._cache,
key=functools.partial(davkey, "get_calendar"),
)
@cachedmethod(cache=lambda cls: cls._cache, key=davkey("get_calendar"))
def get_calendar(cls, calendar_name: str) -> Calendar:
"""
Get a calendar by name using the CalDAV principal object.
@ -67,10 +60,7 @@ class CalDAV:
@classmethod
@asyncify
@cachedmethod(
cache=lambda cls: cls._cache,
key=functools.partial(davkey, "get_events"),
)
@cachedmethod(cache=lambda cls: cls._cache, key=davkey("get_events", slice(1, 2)))
def get_events(cls, calendar_name: str, cfg: Config) -> list[CalEvent]:
"""
Get a sorted list of events by CalDAV calendar name.

View file

@ -0,0 +1,64 @@
import pickle
from typing import Callable, Hashable
import requests
from cachetools.keys import hashkey
from CacheToolsUtils import RedisCache as __RedisCache
from redis import Redis
from redis.commands.core import ResponseT
from redis.typing import EncodableT
from webdav3.client import Client as __WebDAVclient
from ..settings import SETTINGS
def davkey(
name: str,
slice: slice = slice(1, None),
) -> Callable[..., tuple[Hashable, ...]]:
def func(*args, **kwargs) -> tuple[Hashable, ...]:
"""Return a cache key for use with cached methods."""
key = hashkey(name, *args[slice], **kwargs)
return hashkey(*(str(key_item) for key_item in key))
return func
class WebDAVclient(__WebDAVclient):
def execute_request(
self,
action,
path,
data=None,
headers_ext=None,
) -> requests.Response:
res = super().execute_request(action, path, data, headers_ext)
# the "Content-Length" header can randomly be missing on txt files,
# this should fix that (probably serverside bug)
if action == "download" and "Content-Length" not in res.headers:
res.headers["Content-Length"] = str(len(res.text))
return res
class RedisCache(__RedisCache):
"""
Redis handles <bytes>, so ...
"""
def _serialize(self, s) -> EncodableT:
return pickle.dumps(s)
def _deserialize(self, s: ResponseT):
assert isinstance(s, bytes)
return pickle.loads(s)
REDIS = Redis(
host=SETTINGS.redis.host,
port=SETTINGS.redis.port,
db=SETTINGS.redis.db,
protocol=SETTINGS.redis.protocol,
)

View file

@ -1,63 +1,33 @@
import functools
import logging
import re
from io import BytesIO
import requests
from asyncify import asyncify
from cachetools import TTLCache, cachedmethod
from cachetools.keys import hashkey
from webdav3.client import Client as WebDAVclient
from cachetools import cachedmethod
from .settings import SETTINGS
from ..settings import SETTINGS
from .helpers import REDIS, RedisCache, WebDAVclient, davkey
_logger = logging.getLogger(__name__)
def davkey(name, _, *args, **kwargs):
"""Return a cache key for use with cached methods."""
return hashkey(name, *args, **kwargs)
class WebDAV:
class __WebDAVclient(WebDAVclient):
def execute_request(
self,
action,
path,
data=None,
headers_ext=None,
) -> requests.Response:
res = super().execute_request(action, path, data, headers_ext)
# the "Content-Length" header can randomly be missing on txt files,
# this should fix that (probably serverside bug)
if action == "download" and "Content-Length" not in res.headers:
res.headers["Content-Length"] = str(len(res.text))
return res
_webdav_client = __WebDAVclient(
_webdav_client = WebDAVclient(
{
"webdav_hostname": SETTINGS.webdav.url,
"webdav_login": SETTINGS.webdav.username,
"webdav_password": SETTINGS.webdav.password,
"disable_check": SETTINGS.webdav.disable_check,
}
)
_cache = TTLCache(
_cache = RedisCache(
cache=REDIS,
ttl=SETTINGS.webdav.cache_ttl,
maxsize=SETTINGS.webdav.cache_size,
)
@classmethod
@asyncify
@cachedmethod(
cache=lambda cls: cls._cache,
key=functools.partial(davkey, "list_files"),
)
@cachedmethod(cache=lambda cls: cls._cache, key=davkey("list_files"))
def list_files(
cls,
directory: str = "",
@ -75,10 +45,7 @@ class WebDAV:
@classmethod
@asyncify
@cachedmethod(
cache=lambda cls: cls._cache,
key=functools.partial(davkey, "exists"),
)
@cachedmethod(cache=lambda cls: cls._cache, key=davkey("exists"))
def exists(cls, path: str) -> bool:
"""
`True` iff there is a WebDAV resource at `path`
@ -89,10 +56,7 @@ class WebDAV:
@classmethod
@asyncify
@cachedmethod(
cache=lambda cls: cls._cache,
key=functools.partial(davkey, "read_bytes"),
)
@cachedmethod(cache=lambda cls: cls._cache, key=davkey("read_bytes"))
def read_bytes(cls, path: str) -> bytes:
"""
Load WebDAV file from `path` as bytes
@ -125,7 +89,7 @@ class WebDAV:
cls._webdav_client.upload_to(buffer, path)
# invalidate cache entry
cls._cache.pop(hashkey("read_bytes", path))
cls._cache.pop(davkey("read_bytes")(path))
@classmethod
async def write_str(cls, path: str, content: str, encoding="utf-8") -> None:

View file

@ -7,7 +7,7 @@ from os import path
from pathlib import Path
from .. import __file__ as OVD_INIT
from .webdav import WebDAV
from .dav.webdav import WebDAV
_logger = logging.getLogger(__name__)

View file

@ -26,7 +26,6 @@ class DAVSettings(BaseModel):
password: str | None = None
cache_ttl: int = 60 * 10
cache_size: int = 1024
@property
def url(self) -> str:
@ -66,12 +65,24 @@ class WebDAVSettings(DAVSettings):
return f"{self.protocol}://{self.host}{self.path}{self.prefix}"
class RedisSettings(BaseModel):
"""
Connection to a redis server.
"""
host: str = "redis"
port: int = 6379
db: int = 0
protocol: int = 3
class Settings(BaseSettings):
"""
Per-run settings.
"""
model_config = SettingsConfigDict(
extra="ignore",
env_file=".env",
env_file_encoding="utf-8",
env_nested_delimiter="__",
@ -123,6 +134,12 @@ class Settings(BaseSettings):
caldav: DAVSettings = DAVSettings()
#####
# redis settings
#####
redis: RedisSettings = RedisSettings()
@model_validator(mode="before")
@classmethod
def validate_dav_settings(cls, data) -> dict[str, Any]:

View file

@ -10,10 +10,10 @@ import tomli_w
from fastapi import Depends, HTTPException, params, status
from webdav3.exceptions import RemoteResourceNotFound
from ...core.caldav import CalDAV
from ...core.config import Config
from ...core.dav.caldav import CalDAV
from ...core.dav.webdav import WebDAV
from ...core.settings import SETTINGS
from ...core.webdav import WebDAV
from ._list_manager import Dependable, DependableFn, ListManager
_logger = logging.getLogger(__name__)

View file

@ -10,9 +10,9 @@ import logging
from fastapi import APIRouter, Depends
from ...core.caldav import CalDAV
from ...core.calevent import CalEvent
from ...core.config import Config
from ...core.dav.caldav import CalDAV
from ._common import LM_AGGREGATE, LM_CALENDAR, get_config
_logger = logging.getLogger(__name__)

View file

@ -10,8 +10,8 @@ import logging
from fastapi import APIRouter, Depends
from ...core.caldav import CalDAV, CalEvent
from ...core.config import CalendarUIConfig, Config
from ...core.dav.caldav import CalDAV, CalEvent
from ._common import LM_CALENDAR, get_config
_logger = logging.getLogger(__name__)

View file

@ -13,8 +13,8 @@ from fastapi import APIRouter, Depends
from fastapi.responses import StreamingResponse
from magic import Magic
from ...core.dav.webdav import WebDAV
from ...core.dav_common import webdav_ensure_files, webdav_ensure_path
from ...core.webdav import WebDAV
from ._common import LM_FILE, RP_FILE
_logger = logging.getLogger(__name__)

View file

@ -14,8 +14,8 @@ from fastapi.responses import StreamingResponse
from PIL import Image
from ...core.config import Config, ImageUIConfig
from ...core.dav.webdav import WebDAV
from ...core.dav_common import webdav_ensure_files, webdav_ensure_path
from ...core.webdav import WebDAV
from ._common import LM_IMAGE, RP_IMAGE, get_config
_logger = logging.getLogger(__name__)

View file

@ -12,8 +12,8 @@ import logging
import markdown
from fastapi import APIRouter, Depends
from ...core.dav.webdav import WebDAV
from ...core.dav_common import webdav_ensure_files, webdav_ensure_path
from ...core.webdav import WebDAV
from ._common import LM_TEXT, RP_TEXT
_logger = logging.getLogger(__name__)

View file

@ -13,8 +13,8 @@ import markdown
from fastapi import APIRouter, Depends
from ...core.config import Config, TickerUIConfig
from ...core.dav.webdav import WebDAV
from ...core.dav_common import webdav_ensure_files, webdav_ensure_path
from ...core.webdav import WebDAV
from ._common import LM_TEXT, RP_TEXT, get_config
_logger = logging.getLogger(__name__)

140
api/poetry.lock generated
View file

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand.
[[package]]
name = "annotated-types"
@ -97,6 +97,26 @@ files = [
{file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"},
]
[[package]]
name = "cachetoolsutils"
version = "8.2"
description = "Cachetools Utilities"
optional = false
python-versions = ">=3.10"
files = [
{file = "CacheToolsUtils-8.2-py3-none-any.whl", hash = "sha256:721cd4d63477f4de3ba2108f6b8e5b1a9708075eeb72e4770e814f4826815846"},
{file = "CacheToolsUtils-8.2.tar.gz", hash = "sha256:a8ece7af5fcc221dfec747dffce4bbfd7e6b45ef315c3aefedb844790c9d1f59"},
]
[package.dependencies]
cachetools = "*"
[package.extras]
dev = ["black", "coverage", "flake8", "mypy", "pymarkdownlnt", "pytest", "types-cachetools", "types-redis"]
doc = ["myst-parser", "sphinx", "sphinx-autoapi", "sphinx-lint", "sphinx-rtd-theme"]
pub = ["build", "twine", "wheel"]
tests = ["pymemcache", "redis"]
[[package]]
name = "caldav"
version = "1.3.6"
@ -330,6 +350,104 @@ files = [
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "hiredis"
version = "2.2.3"
description = "Python wrapper for hiredis"
optional = false
python-versions = ">=3.7"
files = [
{file = "hiredis-2.2.3-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:9a1a80a8fa767f2fdc3870316a54b84fe9fc09fa6ab6a2686783de6a228a4604"},
{file = "hiredis-2.2.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3f006c28c885deb99b670a5a66f367a175ab8955b0374029bad7111f5357dcd4"},
{file = "hiredis-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffaf841546905d90ff189de7397aa56413b1ce5e54547f17a98f0ebf3a3b0a3b"},
{file = "hiredis-2.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cadb0ac7ba3babfd804e425946bec9717b320564a1390f163a54af9365a720a"},
{file = "hiredis-2.2.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33bc4721632ef9708fa44e5df0066053fccc8e65410a2c48573192517a533b48"},
{file = "hiredis-2.2.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:227c5b4bcb60f89008c275d596e4a7b6625a6b3c827b8a66ae582eace7051f71"},
{file = "hiredis-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61995eb826009d99ed8590747bc0da683a5f4fbb4faa8788166bf3810845cd5c"},
{file = "hiredis-2.2.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f969edc851efe23010e0f53a64269f2629a9364135e9ec81c842e8b2277d0c1"},
{file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27e560eefb57914d742a837f1da98d3b29cb22eff013c8023b7cf52ae6e051d"},
{file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3759f4789ae1913b7df278dfc9e8749205b7a106f888cd2903d19461e24a7697"},
{file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c6cb613148422c523945cdb8b6bed617856f2602fd8750e33773ede2616e55d5"},
{file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:1d274d5c511dfc03f83f997d3238eaa9b6ee3f982640979f509373cced891e98"},
{file = "hiredis-2.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3b7fe075e91b9d9cff40eba4fb6a8eff74964d3979a39be9a9ef58b1b4cb3604"},
{file = "hiredis-2.2.3-cp310-cp310-win32.whl", hash = "sha256:77924b0d32fd1f493d3df15d9609ddf9d94c31a364022a6bf6b525ce9da75bea"},
{file = "hiredis-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:dcb0569dd5bfe6004658cd0f229efa699a3169dcb4f77bd72e188adda302063d"},
{file = "hiredis-2.2.3-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:d115790f18daa99b5c11a506e48923b630ef712e9e4b40482af942c3d40638b8"},
{file = "hiredis-2.2.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c3b8be557e08b234774925622e196f0ee36fe4eab66cd19df934d3efd8f3743"},
{file = "hiredis-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f5446068197b35a11ccc697720c41879c8657e2e761aaa8311783aac84cef20"},
{file = "hiredis-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa17a3b22b3726d54d7af20394f65d4a1735a842a4e0f557dc67a90f6965c4bc"},
{file = "hiredis-2.2.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7df645b6b7800e8b748c217fbd6a4ca8361bcb9a1ae6206cc02377833ec8a1aa"},
{file = "hiredis-2.2.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fb9300959a0048138791f3d68359d61a788574ec9556bddf1fec07f2dbc5320"},
{file = "hiredis-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d7e459fe7313925f395148d36d9b7f4f8dac65be06e45d7af356b187cef65fc"},
{file = "hiredis-2.2.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8eceffca3941775b646cd585cd19b275d382de43cc3327d22f7c75d7b003d481"},
{file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b17baf702c6e5b4bb66e1281a3efbb1d749c9d06cdb92b665ad81e03118f78fc"},
{file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e43e2b5acaad09cf48c032f7e4926392bb3a3f01854416cf6d82ebff94d5467"},
{file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a7205497d7276a81fe92951a29616ef96562ed2f91a02066f72b6f93cb34b40e"},
{file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:126623b03c31cb6ac3e0d138feb6fcc36dd43dd34fc7da7b7a0c38b5d75bc896"},
{file = "hiredis-2.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:071c5814b850574036506a8118034f97c3cbf2fe9947ff45a27b07a48da56240"},
{file = "hiredis-2.2.3-cp311-cp311-win32.whl", hash = "sha256:d1be9e30e675f5bc1cb534633324578f6f0944a1bcffe53242cf632f554f83b6"},
{file = "hiredis-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9a7c987e161e3c58f992c63b7e26fea7fe0777f3b975799d23d65bbb8cb5899"},
{file = "hiredis-2.2.3-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:f2dcb8389fa3d453927b1299f46bdb38473c293c8269d5c777d33ea0e526b610"},
{file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2df98f5e071320c7d84e8bd07c0542acdd0a7519307fc31774d60e4b842ec4f"},
{file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a72e4a523cdfc521762137559c08dfa360a3caef63620be58c699d1717dac1"},
{file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9b9e5bde7030cae83aa900b5bd660decc65afd2db8c400f3c568c815a47ca2a"},
{file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2614f17e261f72efc2f19f5e5ff2ee19e2296570c0dcf33409e22be30710de"},
{file = "hiredis-2.2.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46525fbd84523cac75af5bf524bc74aaac848beaf31b142d2df8a787d9b4bbc4"},
{file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d1a4ce40ba11da9382c14da31f4f9e88c18f7d294f523decd0fadfb81f51ad18"},
{file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:5cda592405bbd29d53942e0389dc3fa77b49c362640210d7e94a10c14a677d4d"},
{file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:5e6674a017629284ef373b50496d9fb1a89b85a20a7fa100ecd109484ec748e5"},
{file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:e62ec131816c6120eff40dffe43424e140264a15fa4ab88c301bd6a595913af3"},
{file = "hiredis-2.2.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17e938d9d3ee92e1adbff361706f1c36cc60eeb3e3eeca7a3a353eae344f4c91"},
{file = "hiredis-2.2.3-cp37-cp37m-win32.whl", hash = "sha256:95d2305fd2a7b179cacb48b10f618872fc565c175f9f62b854e8d1acac3e8a9e"},
{file = "hiredis-2.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8f9dbe12f011a9b784f58faecc171d22465bb532c310bd588d769ba79a59ef5a"},
{file = "hiredis-2.2.3-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:5a4bcef114fc071d5f52c386c47f35aae0a5b43673197b9288a15b584da8fa3a"},
{file = "hiredis-2.2.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:232d0a70519865741ba56e1dfefd160a580ae78c30a1517bad47b3cf95a3bc7d"},
{file = "hiredis-2.2.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9076ce8429785c85f824650735791738de7143f61f43ae9ed83e163c0ca0fa44"},
{file = "hiredis-2.2.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec58fb7c2062f835595c12f0f02dcda76d0eb0831423cc191d1e18c9276648de"},
{file = "hiredis-2.2.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f2b34a6444b8f9c1e9f84bd2c639388e5d14f128afd14a869dfb3d9af893aa2"},
{file = "hiredis-2.2.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:818dfd310aa1020a13cd08ee48e116dd8c3bb2e23b8161f8ac4df587dd5093d7"},
{file = "hiredis-2.2.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d9ea6c8d4cbdeee2e0d43379ce2881e4af0454b00570677c59f33f2531cd38"},
{file = "hiredis-2.2.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1eadbcd3de55ac42310ff82550d3302cb4efcd4e17d76646a17b6e7004bb42b"},
{file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:477c34c4489666dc73cb5e89dafe2617c3e13da1298917f73d55aac4696bd793"},
{file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:14824e457e4f5cda685c3345d125da13949bcf3bb1c88eb5d248c8d2c3dee08f"},
{file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9cd32326dfa6ce87edf754153b0105aca64486bebe93b9600ccff74fa0b224df"},
{file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:51341e70b467004dcbec3a6ce8c478d2d6241e0f6b01e4c56764afd5022e1e9d"},
{file = "hiredis-2.2.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2443659c76b226267e2a04dbbb21bc2a3f91aa53bdc0c22964632753ae43a247"},
{file = "hiredis-2.2.3-cp38-cp38-win32.whl", hash = "sha256:4e3e3e31423f888d396b1fc1f936936e52af868ac1ec17dd15e3eeba9dd4de24"},
{file = "hiredis-2.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:20f509e3a1a20d6e5f5794fc37ceb21f70f409101fcfe7a8bde783894d51b369"},
{file = "hiredis-2.2.3-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:d20891e3f33803b26d54c77fd5745878497091e33f4bbbdd454cf6e71aee8890"},
{file = "hiredis-2.2.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:50171f985e17970f87d5a29e16603d1e5b03bdbf5c2691a37e6c912942a6b657"},
{file = "hiredis-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9944a2cac25ffe049a7e89f306e11b900640837d1ef38d9be0eaa4a4e2b73a52"},
{file = "hiredis-2.2.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a5c8019ff94988d56eb49b15de76fe83f6b42536d76edeb6565dbf7fe14b973"},
{file = "hiredis-2.2.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a286ded34eb16501002e3713b3130c987366eee2ba0d58c33c72f27778e31676"},
{file = "hiredis-2.2.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b3e974ad15eb32b1f537730dea70b93a4c3db7b026de3ad2b59da49c6f7454d"},
{file = "hiredis-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08415ea74c1c29b9d6a4ca3dd0e810dc1af343c1d1d442e15ba133b11ab5be6a"},
{file = "hiredis-2.2.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e17d04ea58ab8cf3f2dc52e875db16077c6357846006780086fff3189fb199d"},
{file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6ccdcb635dae85b006592f78e32d97f4bc7541cb27829d505f9c7fefcef48298"},
{file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69536b821dd1bc78058a6e7541743f8d82bf2d981b91280b14c4daa6cdc7faba"},
{file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:3753df5f873d473f055e1f8837bfad0bd3b277c86f3c9bf058c58f14204cd901"},
{file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6f88cafe46612b6fa68e6dea49e25bebf160598bba00101caa51cc8c1f18d597"},
{file = "hiredis-2.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33ee3ea5cad3a8cb339352cd230b411eb437a2e75d7736c4899acab32056ccdb"},
{file = "hiredis-2.2.3-cp39-cp39-win32.whl", hash = "sha256:b4f3d06dc16671b88a13ae85d8ca92534c0b637d59e49f0558d040a691246422"},
{file = "hiredis-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4f674e309cd055ee7a48304ceb8cf43265d859faf4d7d01d270ce45e976ae9d3"},
{file = "hiredis-2.2.3-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8f280ab4e043b089777b43b4227bdc2035f88da5072ab36588e0ccf77d45d058"},
{file = "hiredis-2.2.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15c2a551f3b8a26f7940d6ee10b837810201754b8d7e6f6b1391655370882c5a"},
{file = "hiredis-2.2.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60c4e3c258eafaab21b174b17270a0cc093718d61cdbde8c03f85ec4bf835343"},
{file = "hiredis-2.2.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc36a9dded458d4e37492fe3e619c6c83caae794d26ad925adbce61d592f8428"},
{file = "hiredis-2.2.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4ed68a3b1ccb4313d2a42546fd7e7439ad4745918a48b6c9bcaa61e1e3e42634"},
{file = "hiredis-2.2.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3bf4b5bae472630c229518e4a814b1b68f10a3d9b00aeaec45f1a330f03a0251"},
{file = "hiredis-2.2.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33a94d264e6e12a79d9bb8af333b01dc286b9f39c99072ab5fef94ce1f018e17"},
{file = "hiredis-2.2.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fa6811a618653164f918b891a0fa07052bd71a799defa5c44d167cac5557b26"},
{file = "hiredis-2.2.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af33f370be90b48bbaf0dab32decbdcc522b1fa95d109020a963282086518a8e"},
{file = "hiredis-2.2.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b9953d87418ac228f508d93898ab572775e4d3b0eeb886a1a7734553bcdaf291"},
{file = "hiredis-2.2.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5e7bb4dd524f50b71c20ef5a12bd61da9b463f8894b18a06130942fe31509881"},
{file = "hiredis-2.2.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89a258424158eb8b3ed9f65548d68998da334ef155d09488c5637723eb1cd697"},
{file = "hiredis-2.2.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f4a65276f6ecdebe75f2a53f578fbc40e8d2860658420d5e0611c56bbf5054c"},
{file = "hiredis-2.2.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:334f2738700b20faa04a0d813366fb16ed17287430a6b50584161d5ad31ca6d7"},
{file = "hiredis-2.2.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d194decd9608f11c777946f596f31d5aacad13972a0a87829ae1e6f2d26c1885"},
{file = "hiredis-2.2.3.tar.gz", hash = "sha256:e75163773a309e56a9b58165cf5a50e0f84b755f6ff863b2c01a38918fe92daa"},
]
[[package]]
name = "httptools"
version = "0.6.1"
@ -969,6 +1087,24 @@ python-dateutil = ">=2.8.1"
pytz = ">=2023.3"
x-wr-timezone = ">=0.0.5,<1.0.0"
[[package]]
name = "redis"
version = "5.0.1"
description = "Python client for Redis database and key-value store"
optional = false
python-versions = ">=3.7"
files = [
{file = "redis-5.0.1-py3-none-any.whl", hash = "sha256:ed4802971884ae19d640775ba3b03aa2e7bd5e8fb8dfaed2decce4d0fc48391f"},
{file = "redis-5.0.1.tar.gz", hash = "sha256:0dab495cd5753069d3bc650a0dde8a8f9edde16fc5691b689a566eda58100d0f"},
]
[package.dependencies]
hiredis = {version = ">=1.0.0", optional = true, markers = "extra == \"hiredis\""}
[package.extras]
hiredis = ["hiredis (>=1.0.0)"]
ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"]
[[package]]
name = "requests"
version = "2.31.0"
@ -1405,4 +1541,4 @@ typing-extensions = ">=4.4.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "ec9e330d56137c0c7cc7b88b894553ada7693ba2902e2064a94355b38affa327"
content-hash = "fa02b5cf385dac14ae6e86ea8c33aa960a2a8394d401566b99f7dcece2c66a6a"

View file

@ -10,11 +10,13 @@ Markdown = "^3.5"
Pillow = "^10.1.0"
asyncify = "^0.9.2"
cachetools = "^5.3.2"
cachetoolsutils = "^8.2"
caldav = "^1.3.6"
fastapi = "^0.103.2"
pydantic-settings = "^2.0.3"
python = "^3.12"
python-magic = "^0.4.27"
redis = {extras = ["hiredis"], version = "^5.0.1"}
tomli-w = "^1.0.0"
uvicorn = {extras = ["standard"], version = "^0.23.2"}
webdavclient3 = "^3.14.6"