diff --git a/api/ovdashboard_api/__init__.py b/api/ovdashboard_api/__init__.py index b266f94..e69de29 100644 --- a/api/ovdashboard_api/__init__.py +++ b/api/ovdashboard_api/__init__.py @@ -1,76 +0,0 @@ -import asyncio -import functools -import time -from typing import Any - -import caldav -from async_lru import alru_cache -from webdav3 import client as WebDAVclient - -from .settings import SETTINGS - -_WEBDAV_CLIENT = WebDAVclient.Client({ - "webdav_hostname": SETTINGS.webdav_url, - "webdav_login": SETTINGS.dav_username, - "webdav_password": SETTINGS.dav_password, -}) - - -def run_in_executor(f): - """ - Decorator to make blocking function call asyncio compatible - https://stackoverflow.com/questions/41063331/how-to-use-asyncio-with-existing-blocking-library/ - """ - - @functools.wraps(f) - def inner(*args, **kwargs): - loop = asyncio.get_running_loop() - return loop.run_in_executor( - None, - functools.partial(f, *args, **kwargs), - ) - - return inner - - -def get_ttl_hash(seconds: int = 20) -> int: - """ - Return the same value within `seconds` time period - https://stackoverflow.com/a/55900800 - """ - return round(time.time() / seconds) - - -def timed_alru_cache(**decorator_kwargs): - def decorate(f): - @alru_cache(**decorator_kwargs) - @functools.wraps(f) - async def wrapper(ttl_hash: int, *args, **kwargs): - del ttl_hash - - return await f(*args, **kwargs) - - return wrapper - - return decorate - - -@functools.lru_cache -def webdav_resource(remote_path: Any) -> WebDAVclient.Resource: - return _WEBDAV_CLIENT.resource(remote_path) - - -@run_in_executor -def webdav_list(remote_path: str) -> list: - return _WEBDAV_CLIENT.list(remote_path) - - -_CALDAV_CLIENT = caldav.DAVClient( - url=SETTINGS.caldav_url, - username=SETTINGS.dav_username, - password=SETTINGS.dav_password, -) - - -def caldav_principal() -> caldav.Principal: - return _CALDAV_CLIENT.principal() diff --git a/api/ovdashboard_api/async_helpers.py b/api/ovdashboard_api/async_helpers.py new file mode 100644 index 0000000..ce0ac4f --- /dev/null +++ b/api/ovdashboard_api/async_helpers.py @@ -0,0 +1,44 @@ +from asyncio import get_running_loop +from functools import partial, wraps +from time import time + +from async_lru import alru_cache + + +def run_in_executor(f): + """ + Decorator to make blocking function call asyncio compatible + https://stackoverflow.com/questions/41063331/how-to-use-asyncio-with-existing-blocking-library/ + """ + + @wraps(f) + def inner(*args, **kwargs): + loop = get_running_loop() + return loop.run_in_executor( + None, + partial(f, *args, **kwargs), + ) + + return inner + + +def get_ttl_hash(seconds: int = 20) -> int: + """ + Return the same value within `seconds` time period + https://stackoverflow.com/a/55900800 + """ + return round(time() / seconds) + + +def timed_alru_cache(**decorator_kwargs): + def decorate(f): + @alru_cache(**decorator_kwargs) + @wraps(f) + async def wrapper(ttl_hash: int, *args, **kwargs): + del ttl_hash + + return await f(*args, **kwargs) + + return wrapper + + return decorate diff --git a/api/ovdashboard_api/dav_calendar.py b/api/ovdashboard_api/dav_calendar.py index 8ce54f6..8649665 100644 --- a/api/ovdashboard_api/dav_calendar.py +++ b/api/ovdashboard_api/dav_calendar.py @@ -9,7 +9,8 @@ from caldav.lib.error import ReportError from pydantic import BaseModel, validator from vobject.icalendar import VEvent -from . import caldav_principal, get_ttl_hash, run_in_executor, timed_alru_cache +from .async_helpers import get_ttl_hash, run_in_executor, timed_alru_cache +from .dav_common import caldav_principal _logger = logging.getLogger(__name__) diff --git a/api/ovdashboard_api/dav_common.py b/api/ovdashboard_api/dav_common.py new file mode 100644 index 0000000..03146ff --- /dev/null +++ b/api/ovdashboard_api/dav_common.py @@ -0,0 +1,35 @@ +from functools import lru_cache +from typing import Any + +import caldav +from webdav3 import client as WebDAVclient + +from .async_helpers import run_in_executor +from .settings import SETTINGS + +_WEBDAV_CLIENT = WebDAVclient.Client({ + "webdav_hostname": SETTINGS.webdav_url, + "webdav_login": SETTINGS.dav_username, + "webdav_password": SETTINGS.dav_password, +}) + + +@lru_cache +def webdav_resource(remote_path: Any) -> WebDAVclient.Resource: + return _WEBDAV_CLIENT.resource(remote_path) + + +@run_in_executor +def webdav_list(remote_path: str) -> list: + return _WEBDAV_CLIENT.list(remote_path) + + +_CALDAV_CLIENT = caldav.DAVClient( + url=SETTINGS.caldav_url, + username=SETTINGS.dav_username, + password=SETTINGS.dav_password, +) + + +def caldav_principal() -> caldav.Principal: + return _CALDAV_CLIENT.principal() diff --git a/api/ovdashboard_api/dav_file.py b/api/ovdashboard_api/dav_file.py index 2f066bb..28f59ff 100644 --- a/api/ovdashboard_api/dav_file.py +++ b/api/ovdashboard_api/dav_file.py @@ -5,7 +5,8 @@ from typing import Any from webdav3.client import Resource -from . import get_ttl_hash, run_in_executor, timed_alru_cache, webdav_resource +from .async_helpers import get_ttl_hash, run_in_executor, timed_alru_cache +from .dav_common import webdav_resource _logger = logging.getLogger(__name__) @@ -34,7 +35,7 @@ class DavFile: @property async def __buffer(self) -> BytesIO: return await _get_buffer( - ttl_hash=get_ttl_hash(), + ttl_hash=get_ttl_hash(20), remote_path=self.remote_path, ) diff --git a/api/ovdashboard_api/routers/_common.py b/api/ovdashboard_api/routers/_common.py index b537a5e..ccbc6c2 100644 --- a/api/ovdashboard_api/routers/_common.py +++ b/api/ovdashboard_api/routers/_common.py @@ -5,7 +5,7 @@ from typing import Iterator, Protocol from fastapi import HTTPException, status from webdav3.exceptions import RemoteResourceNotFound -from .. import caldav_principal, webdav_list +from ..dav_common import caldav_principal, webdav_list @dataclass(frozen=True)