Skip to content

Commit 3538585

Browse files
committed
Start new CachedItem model
1 parent b644abb commit 3538585

File tree

7 files changed

+197
-0
lines changed

7 files changed

+197
-0
lines changed

bolt-cache/bolt/cache/cli.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import click
2+
3+
from .models import CachedItem
4+
5+
6+
@click.group()
7+
def cli():
8+
pass
9+
10+
11+
@cli.command()
12+
def clear_expired():
13+
click.echo("Clearing expired cache items...")
14+
result = CachedItem.objects.expired().delete()
15+
click.echo(f"Deleted {result[0]} expired cache items.")
16+
17+
18+
@cli.command()
19+
@click.option("--force", is_flag=True)
20+
def clear_all(force):
21+
if not force and not click.confirm(
22+
"Are you sure you want to delete all cache items?"
23+
):
24+
return
25+
click.echo("Clearing all cache items...")
26+
result = CachedItem.objects.all().delete()
27+
click.echo(f"Deleted {result[0]} cache items.")
28+
29+
30+
@cli.command()
31+
def stats():
32+
total = CachedItem.objects.count()
33+
expired = CachedItem.objects.expired().count()
34+
unexpired = CachedItem.objects.unexpired().count()
35+
forever = CachedItem.objects.forever().count()
36+
37+
click.echo(f"Total: {click.style(total, bold=True)}")
38+
click.echo(f"Expired: {click.style(expired, bold=True)}")
39+
click.echo(f"Unexpired: {click.style(unexpired, bold=True)}")
40+
click.echo(f"Forever: {click.style(forever, bold=True)}")

bolt-cache/bolt/cache/config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from bolt.packages import PackageConfig
2+
3+
4+
class BoltCacheConfig(PackageConfig):
5+
default_auto_field = "bolt.db.models.BigAutoField"
6+
name = "bolt.cache"
7+
label = "boltcache"

bolt-cache/bolt/cache/core.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from datetime import datetime, timedelta
2+
from functools import cached_property
3+
4+
from bolt.utils import timezone
5+
6+
from .models import CachedItem
7+
8+
9+
class Cached:
10+
def __init__(self, key):
11+
self.key = key
12+
13+
@cached_property
14+
def _model_instance(self):
15+
try:
16+
return CachedItem.objects.get(key=self.key)
17+
except CachedItem.DoesNotExist:
18+
return None
19+
20+
def reload(self):
21+
if hasattr(self, "_model_instance"):
22+
del self._model_instance
23+
24+
def _is_expired(self):
25+
if not self._model_instance:
26+
return True
27+
28+
if not self._model_instance.expires_at:
29+
return False
30+
31+
return self._model_instance.expires_at < timezone.now()
32+
33+
def exists(self):
34+
if self._model_instance is None:
35+
return False
36+
37+
return not self._is_expired()
38+
39+
@property
40+
def value(self):
41+
if not self.exists():
42+
return None
43+
44+
return self._model_instance.value
45+
46+
def set(self, value, expiration: datetime | timedelta | int | None = None):
47+
defaults = {
48+
"value": value,
49+
}
50+
51+
if isinstance(expiration, int):
52+
defaults["expires_at"] = timezone.now() + timedelta(seconds=expiration)
53+
elif isinstance(expiration, timedelta):
54+
defaults["expires_at"] = timezone.now() + expiration
55+
elif isinstance(expiration, datetime):
56+
defaults["expires_at"] = expiration
57+
else:
58+
# Keep existing expires_at value or None
59+
pass
60+
61+
item, _ = CachedItem.objects.update_or_create(key=self.key, defaults=defaults)
62+
63+
self.reload()
64+
65+
return item.value
66+
67+
def delete(self):
68+
if not self._model_instance:
69+
# A no-op, but a return value you can use to know whether it did anything
70+
return False
71+
72+
self._model_instance.delete()
73+
self.reload()
74+
return True
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Generated by Bolt 5.0.dev20231127233940 on 2023-12-22 03:47
2+
3+
from bolt.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
initial = True
8+
9+
dependencies = []
10+
11+
operations = [
12+
migrations.CreateModel(
13+
name="CacheItem",
14+
fields=[
15+
(
16+
"id",
17+
models.BigAutoField(
18+
auto_created=True,
19+
primary_key=True,
20+
serialize=False,
21+
verbose_name="ID",
22+
),
23+
),
24+
("key", models.CharField(max_length=255, unique=True)),
25+
("value", models.JSONField(blank=True, null=True)),
26+
(
27+
"expires_at",
28+
models.DateTimeField(blank=True, db_index=True, null=True),
29+
),
30+
("created_at", models.DateTimeField(auto_now_add=True)),
31+
("updated_at", models.DateTimeField(auto_now=True)),
32+
],
33+
),
34+
]
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Generated by Bolt 5.0.dev20231127233940 on 2023-12-22 17:40
2+
3+
from bolt.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("boltcache", "0001_initial"),
9+
]
10+
11+
operations = [
12+
migrations.RenameModel(
13+
old_name="CacheItem",
14+
new_name="CachedItem",
15+
),
16+
]

bolt-cache/bolt/cache/migrations/__init__.py

Whitespace-only changes.

bolt-cache/bolt/cache/models.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from bolt.db import models
2+
from bolt.utils import timezone
3+
4+
5+
class CachedItemQuerySet(models.QuerySet):
6+
def expired(self):
7+
return self.filter(expires_at__lt=timezone.now())
8+
9+
def unexpired(self):
10+
return self.filter(expires_at__gte=timezone.now())
11+
12+
def forever(self):
13+
return self.filter(expires_at=None)
14+
15+
16+
class CachedItem(models.Model):
17+
key = models.CharField(max_length=255, unique=True)
18+
value = models.JSONField(blank=True, null=True)
19+
expires_at = models.DateTimeField(blank=True, null=True, db_index=True)
20+
created_at = models.DateTimeField(auto_now_add=True)
21+
updated_at = models.DateTimeField(auto_now=True)
22+
23+
objects = CachedItemQuerySet.as_manager()
24+
25+
def __str__(self) -> str:
26+
return self.key

0 commit comments

Comments
 (0)