1from __future__ import annotations
2
3from typing import Any, Self
4
5from plain import postgres
6from plain.postgres import types
7from plain.runtime import settings
8from plain.utils import timezone
9
10__all__ = ["CachedItem", "CachedItemQuerySet"]
11
12
13class CachedItemQuerySet(postgres.QuerySet["CachedItem"]):
14 def live(self) -> Self:
15 """Rows readable right now: never-expiring *or* not-yet-expired.
16
17 This is the filter cache reads use -- an entry past its `expires_at`
18 reads as absent. (Contrast `unexpired()`, which matches only rows with a
19 *future* expiry.)
20 """
21 return self.filter(
22 postgres.Q(expires_at__isnull=True)
23 | postgres.Q(expires_at__gte=timezone.now())
24 )
25
26 def expired(self) -> Self:
27 return self.filter(expires_at__lt=timezone.now())
28
29 def unexpired(self) -> Self:
30 return self.filter(expires_at__gte=timezone.now())
31
32 def forever(self) -> Self:
33 return self.filter(expires_at=None)
34
35
36@postgres.register_model
37class CachedItem(postgres.Model):
38 key = types.TextField(max_length=255)
39 value: Any = types.JSONField(required=False, allow_null=True)
40 expires_at = types.DateTimeField(required=False, allow_null=True)
41 created_at = types.DateTimeField(create_now=True)
42 updated_at = types.DateTimeField(create_now=True, update_now=True)
43
44 query: CachedItemQuerySet = CachedItemQuerySet()
45
46 model_options = postgres.Options(
47 indexes=[
48 postgres.Index(
49 name="plaincache_cacheditem_expires_at_idx", fields=["expires_at"]
50 ),
51 ],
52 constraints=[
53 postgres.UniqueConstraint(
54 fields=["key"], name="plaincache_cacheditem_unique_key"
55 ),
56 ],
57 storage_parameters={
58 "autovacuum_vacuum_scale_factor": settings.CACHE_AUTOVACUUM_SCALE_FACTOR,
59 "toast.autovacuum_vacuum_scale_factor": settings.CACHE_TOAST_AUTOVACUUM_SCALE_FACTOR,
60 },
61 )
62
63 def __str__(self) -> str:
64 return self.key