Skip to content

Commit

Permalink
cache management
Browse files Browse the repository at this point in the history
  • Loading branch information
saxix committed Nov 14, 2024
1 parent 3fd00e8 commit 19866c3
Show file tree
Hide file tree
Showing 41 changed files with 1,936 additions and 2,021 deletions.
24 changes: 24 additions & 0 deletions docs/src/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,30 @@ Install [uv](https://docs.astral.sh/uv/)
uv sync --all-extras
pre-commit install --hook-type pre-commit --hook-type pre-push

## Tailwind CSS

This project uses [django-tailwind](https://django-tailwind.readthedocs.io/en/latest/installation.html) to manage
CSS. CSS sources are located in the `country_workspace/workspaces/theme/static_src/src/`.
If you need to edit the CSS follow the below steps:

1. Install node dependencies

./manage.py tailwind install

1. Configure the enviroment

export EXTRA_APPS="country_workspace.contrib.hope,django_browser_reload"
export EXTRA_MIDDLEWARES="django_browser_reload.middleware.BrowserReloadMiddleware,"

1. Build the final CSS

./manage.py tailwind build

Or you can run the [development mode](https://django-tailwind.readthedocs.io/en/latest/usage.html#running-in-development-mode)

./manage.py tailwind start



## Run tests

Expand Down
58 changes: 0 additions & 58 deletions src/country_workspace/cache.py

This file was deleted.

Empty file.
66 changes: 66 additions & 0 deletions src/country_workspace/cache/ddt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import functools

from django.utils import timezone
from django.utils.translation import gettext as _

from debug_toolbar.panels import Panel

from country_workspace.cache.signals import cache_get, cache_set, cache_store
from country_workspace.state import state


class CacheHit:
def __init__(self, data, **kwargs):
self.timestamp = timezone.now()
self.key = data["key"]
self.tenant = state.tenant
self.program = state.program
self.hit = data.get("hit", "")
self.extra = kwargs

def __repr__(self):
return str(self.__dict__)


class WSCachePanel(Panel):
title = _("Cache")
nav_title = _("Cache")
template = "debug_toolbar/panels/cache.html"

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.gets = []
self.sets = []
self.stores = []

def _log(self, action, **kwargs):
if action == "get":
self.gets.append(CacheHit(kwargs))
elif action == "set":
self.sets.append(CacheHit(kwargs))
elif action == "store":
self.stores.append(CacheHit(kwargs, path=kwargs["request"].path))

def enable_instrumentation(self):
cache_get.connect(functools.partial(self._log, action="get"))
cache_set.connect(functools.partial(self._log, action="set"))
cache_store.connect(functools.partial(self._log, action="store"))

def process_request(self, request):
self.gets = []
self.sets = []
self.stores = []
return self.get_response(request)

@property
def nav_subtitle(self):
return "~{} gets / ~{} sets".format(len(self.gets), len(self.sets))

def generate_stats(self, request, response):
self.record_stats(
{
"gets": self.gets,
"sets": self.sets,
"stores": self.stores,
}
)
16 changes: 16 additions & 0 deletions src/country_workspace/cache/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.db.models.signals import post_save
from django.dispatch import receiver

from ..models import Household, Individual, Program
from ..workspaces.models import CountryHousehold, CountryIndividual, CountryProgram
from .manager import cache_manager


@receiver(post_save)
def update_cache(sender, instance, **kwargs):
if isinstance(instance, (Household, Individual, CountryHousehold, CountryIndividual)):
program = instance.program
cache_manager.incr_cache_version(program=program)
elif isinstance(instance, (Program, CountryProgram)):
program = instance
cache_manager.incr_cache_version(program=program)
97 changes: 97 additions & 0 deletions src/country_workspace/cache/manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from typing import TYPE_CHECKING, Any, Optional

from django.core.cache import cache
from django.utils.text import slugify

from country_workspace.state import state

from .signals import cache_get, cache_invalidate, cache_set

if TYPE_CHECKING:
from ..models import Office, Program


class CacheManager:
def __init__(self):
self.active = True

def init(self):
from . import handlers # noqa

def invalidate(self, key):
cache_invalidate.send(CacheManager, key=key)

def get(self, key):
if not self.active:
return None
data = cache.get(key)
cache_get.send(CacheManager, key=key, hit=bool(data))
return data

def set(self, key: str, value: Any, timeout: int = 0, **kwargs):
cache_set.send(self.__class__, key=key)
cache.set(key, value, **kwargs)

def _get_version_key(self, office: "Optional[Office]" = None, program: "Optional[Program]" = None):
if program:
program = program
office = program.country_office
elif office:
program = None

parts = ["key", office.slug if office else "-", str(program.pk) if program else "-"]
return ":".join(parts)

def reset_cache_version(self, *, office: "Optional[Office]" = None, program: "Optional[Program]" = None):
key = self._get_version_key(office, program)
cache.delete(key)

def get_cache_version(self, *, office: "Optional[Office]" = None, program: "Optional[Program]" = None):
key = self._get_version_key(office, program)
return cache.get(key) or 1

def incr_cache_version(self, *, office: "Optional[Office]" = None, program: "Optional[Program]" = None):

key = self._get_version_key(office, program)
try:
return cache.incr(key)
except ValueError:
return cache.set(key, 2)

def build_key_from_request(self, request, prefix="view", *args):
tenant = "-"
version = "-"
program = "-"
if state.tenant and state.program:
tenant = state.tenant.slug
program = str(state.program.pk)
version = str(self.get_cache_version(program=state.program))
elif state.tenant:
tenant = state.tenant.slug
version = str(self.get_cache_version(office=state.tenant))

parts = [
prefix,
version,
tenant,
program,
slugify(request.path),
slugify(str(sorted(request.GET.items()))),
*[str(e) for e in args],
]
return ":".join(parts)

#
# def store(self, request, value):
# key = self._build_key_from_request(request)
# cache_store.send(CacheManager, key=key, request=request, value=value)
# self.set(key, value)
#
# def retrieve(self, request, prefix=""):
# key = self.build_key_from_request(request)
# if data := cache.get(key):
# return pickle.loads(data)
# return None


cache_manager = CacheManager()
Loading

0 comments on commit 19866c3

Please sign in to comment.