Compare commits
5 commits
d9479eb2bc
...
a5348a9987
| Author | SHA1 | Date | |
|---|---|---|---|
| a5348a9987 | |||
| 5897d2f2c7 | |||
| 27de977bfe | |||
| 5524c4094b | |||
| c284274e75 |
18 changed files with 295 additions and 99 deletions
13
Dockerfile
13
Dockerfile
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
32
api/.devcontainer/docker-compose.yml
Normal file
32
api/.devcontainer/docker-compose.yml
Normal 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"
|
||||
|
|
@ -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__)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
64
api/ovdashboard_api/core/dav/helpers.py
Normal file
64
api/ovdashboard_api/core/dav/helpers.py
Normal 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,
|
||||
)
|
||||
|
|
@ -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:
|
||||
|
|
@ -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__)
|
||||
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
|
|
|||
|
|
@ -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
140
api/poetry.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue