Skip to content

Improve Caching Schema #50

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jan 20, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9]
python-version: ["3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v2
- uses: nanasess/setup-chromedriver@master
Expand Down
10 changes: 2 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,14 @@ You may configure additional options as well:
# the base URL for all IDOM-releated resources
IDOM_BASE_URL: str = "_idom/"

# Set cache size limit for loading JS files for IDOM.
# Only applies when not using Django's caching framework (see below).
IDOM_WEB_MODULE_LRU_CACHE_SIZE: int | None = None

# Maximum seconds between two reconnection attempts that would cause the client give up.
# 0 will disable reconnection.
IDOM_WS_MAX_RECONNECT_DELAY: int = 604800

# Configure a cache for loading JS files
CACHES = {
# Configure a cache for loading JS files for IDOM
"idom_web_modules": {"BACKEND": ...},
# If the above cache is not configured, then we'll use the "default" instead
"default": {"BACKEND": ...},
# If "idom" cache is not configured, then we'll use the "default" instead
"idom": {"BACKEND": ...},
}
```

Expand Down
1 change: 1 addition & 0 deletions requirements/pkg-deps.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
channels <4.0.0
idom >=0.34.0, <0.35.0
aiofile >=3.0, <4.0
18 changes: 4 additions & 14 deletions src/django_idom/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Dict

from django.conf import settings
from django.core.cache import DEFAULT_CACHE_ALIAS
from django.core.cache import DEFAULT_CACHE_ALIAS, caches
from idom.core.proto import ComponentConstructor


Expand All @@ -12,17 +12,7 @@
IDOM_WEB_MODULES_URL = IDOM_BASE_URL + "web_module/"
IDOM_WS_MAX_RECONNECT_DELAY = getattr(settings, "IDOM_WS_MAX_RECONNECT_DELAY", 604800)

_CACHES = getattr(settings, "CACHES", {})
if _CACHES:
if "idom_web_modules" in getattr(settings, "CACHES", {}):
IDOM_WEB_MODULE_CACHE = "idom_web_modules"
else:
IDOM_WEB_MODULE_CACHE = DEFAULT_CACHE_ALIAS
if "idom" in getattr(settings, "CACHES", {}):
IDOM_CACHE = caches["idom"]
else:
IDOM_WEB_MODULE_CACHE = None


# the LRU cache size for the route serving IDOM_WEB_MODULES_DIR files
IDOM_WEB_MODULE_LRU_CACHE_SIZE = getattr(
settings, "IDOM_WEB_MODULE_LRU_CACHE_SIZE", None
)
IDOM_CACHE = caches[DEFAULT_CACHE_ALIAS]
57 changes: 19 additions & 38 deletions src/django_idom/views.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,24 @@
import asyncio
import functools
import os

from django.core.cache import caches
from aiofile import async_open
from django.http import HttpRequest, HttpResponse
from idom.config import IDOM_WED_MODULES_DIR

from .config import IDOM_WEB_MODULE_CACHE, IDOM_WEB_MODULE_LRU_CACHE_SIZE


if IDOM_WEB_MODULE_CACHE is None:

def async_lru_cache(*lru_cache_args, **lru_cache_kwargs):
def async_lru_cache_decorator(async_function):
@functools.lru_cache(*lru_cache_args, **lru_cache_kwargs)
def cached_async_function(*args, **kwargs):
coroutine = async_function(*args, **kwargs)
return asyncio.ensure_future(coroutine)

return cached_async_function

return async_lru_cache_decorator

@async_lru_cache(IDOM_WEB_MODULE_LRU_CACHE_SIZE)
async def web_modules_file(request: HttpRequest, file: str) -> HttpResponse:
file_path = IDOM_WED_MODULES_DIR.current.joinpath(*file.split("/"))
return HttpResponse(file_path.read_text(), content_type="text/javascript")

else:
_web_module_cache = caches[IDOM_WEB_MODULE_CACHE]

async def web_modules_file(request: HttpRequest, file: str) -> HttpResponse:
file = IDOM_WED_MODULES_DIR.current.joinpath(*file.split("/")).absolute()
last_modified_time = os.stat(file).st_mtime
cache_key = f"{file}:{last_modified_time}"

response = _web_module_cache.get(cache_key)
if response is None:
response = HttpResponse(file.read_text(), content_type="text/javascript")
_web_module_cache.set(cache_key, response, timeout=None)

return response
from .config import IDOM_CACHE


async def web_modules_file(request: HttpRequest, file: str) -> HttpResponse:
"""Gets JavaScript required for IDOM modules at runtime. These modules are
returned from cache if available."""
path = IDOM_WED_MODULES_DIR.current.joinpath(*file.split("/")).absolute()
last_modified_time = os.stat(path).st_mtime
cache_key = f"django_idom:web_module:{path}"
response = await IDOM_CACHE.aget(cache_key, version=last_modified_time)
if response is None:
async with async_open(path, "r") as fp:
response = HttpResponse(await fp.read(), content_type="text/javascript")
await IDOM_CACHE.adelete(cache_key)
await IDOM_CACHE.aset(
cache_key, response, timeout=None, version=last_modified_time
)
return response