memorydb-django 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- memorydb_django-0.2.1/PKG-INFO +67 -0
- memorydb_django-0.2.1/README.md +46 -0
- memorydb_django-0.2.1/memorydb_django/__init__.py +13 -0
- memorydb_django-0.2.1/memorydb_django/cache.py +118 -0
- memorydb_django-0.2.1/memorydb_django/sessions.py +103 -0
- memorydb_django-0.2.1/memorydb_django.egg-info/PKG-INFO +67 -0
- memorydb_django-0.2.1/memorydb_django.egg-info/SOURCES.txt +10 -0
- memorydb_django-0.2.1/memorydb_django.egg-info/dependency_links.txt +1 -0
- memorydb_django-0.2.1/memorydb_django.egg-info/requires.txt +2 -0
- memorydb_django-0.2.1/memorydb_django.egg-info/top_level.txt +1 -0
- memorydb_django-0.2.1/pyproject.toml +22 -0
- memorydb_django-0.2.1/setup.cfg +4 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memorydb-django
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Django cache and session backends for MemoryDB (SQLite-style drop-in for the settings file)
|
|
5
|
+
Author: humaniods
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/humaniods/MemoryDB
|
|
8
|
+
Project-URL: Repository, https://github.com/humaniods/MemoryDB
|
|
9
|
+
Project-URL: Issues, https://github.com/humaniods/MemoryDB/issues
|
|
10
|
+
Keywords: django,cache,sessions,database,agent-memory
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Framework :: Django
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: POSIX
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Database
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: superb-memory
|
|
20
|
+
Requires-Dist: Django>=4.2
|
|
21
|
+
|
|
22
|
+
# memorydb-django
|
|
23
|
+
|
|
24
|
+
Django cache and session backends for [MemoryDB](https://github.com/humaniods/MemoryDB).
|
|
25
|
+
Two lines in `settings.py`, the way `sqlite3` is a drop-in for Django itself — no models, no
|
|
26
|
+
migrations, no new code in your views.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install memorydb-django
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
# settings.py
|
|
34
|
+
CACHES = {"default": {
|
|
35
|
+
"BACKEND": "memorydb_django.MemoryDBCache",
|
|
36
|
+
"LOCATION": "memorydb://TOKEN@127.0.0.1:8080", # or leave empty and set MEMORYDB_URL
|
|
37
|
+
}}
|
|
38
|
+
|
|
39
|
+
SESSION_ENGINE = "memorydb_django.sessions"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then use Django's own API:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from django.core.cache import cache
|
|
46
|
+
cache.set("hero", {"name": "Karna"}, timeout=30) # one record; the TTL is the record's expiry
|
|
47
|
+
cache.get("hero")
|
|
48
|
+
|
|
49
|
+
request.session["user_id"] = 42 # SESSION_COOKIE_AGE enforced server-side
|
|
50
|
+
request.session.save()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Details worth knowing:
|
|
54
|
+
|
|
55
|
+
- Each cache key and each session is one MemoryDB document in the `django_cache` /
|
|
56
|
+
`django_sessions` collection (typed, unique and indexed; created on first use).
|
|
57
|
+
- `timeout=` and `SESSION_COOKIE_AGE` are stored as the record's own `expires` field, so the
|
|
58
|
+
server stops serving them at the deadline. No cleanup cron, and `clear_expired()` is a no-op.
|
|
59
|
+
- Concurrent writers use optimistic versions and retry, so there are no row locks or deadlocks.
|
|
60
|
+
- Set `MEMORYDB_SESSIONS_COLLECTION` / `OPTIONS["collection"]` to move either store elsewhere, so
|
|
61
|
+
two Django projects on one server stay apart.
|
|
62
|
+
- Session values are stored through Django's own `SESSION_SERIALIZER` and signed with your
|
|
63
|
+
`SECRET_KEY`, exactly as the database-backed store signs them, so sessions your site already
|
|
64
|
+
issued keep working when you switch engines.
|
|
65
|
+
|
|
66
|
+
This is a cache/session backend, **not** a `django.db` backend — models, migrations, the admin
|
|
67
|
+
and ORM queries still need Postgres or SQLite.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# memorydb-django
|
|
2
|
+
|
|
3
|
+
Django cache and session backends for [MemoryDB](https://github.com/humaniods/MemoryDB).
|
|
4
|
+
Two lines in `settings.py`, the way `sqlite3` is a drop-in for Django itself — no models, no
|
|
5
|
+
migrations, no new code in your views.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install memorydb-django
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
# settings.py
|
|
13
|
+
CACHES = {"default": {
|
|
14
|
+
"BACKEND": "memorydb_django.MemoryDBCache",
|
|
15
|
+
"LOCATION": "memorydb://TOKEN@127.0.0.1:8080", # or leave empty and set MEMORYDB_URL
|
|
16
|
+
}}
|
|
17
|
+
|
|
18
|
+
SESSION_ENGINE = "memorydb_django.sessions"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then use Django's own API:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from django.core.cache import cache
|
|
25
|
+
cache.set("hero", {"name": "Karna"}, timeout=30) # one record; the TTL is the record's expiry
|
|
26
|
+
cache.get("hero")
|
|
27
|
+
|
|
28
|
+
request.session["user_id"] = 42 # SESSION_COOKIE_AGE enforced server-side
|
|
29
|
+
request.session.save()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Details worth knowing:
|
|
33
|
+
|
|
34
|
+
- Each cache key and each session is one MemoryDB document in the `django_cache` /
|
|
35
|
+
`django_sessions` collection (typed, unique and indexed; created on first use).
|
|
36
|
+
- `timeout=` and `SESSION_COOKIE_AGE` are stored as the record's own `expires` field, so the
|
|
37
|
+
server stops serving them at the deadline. No cleanup cron, and `clear_expired()` is a no-op.
|
|
38
|
+
- Concurrent writers use optimistic versions and retry, so there are no row locks or deadlocks.
|
|
39
|
+
- Set `MEMORYDB_SESSIONS_COLLECTION` / `OPTIONS["collection"]` to move either store elsewhere, so
|
|
40
|
+
two Django projects on one server stay apart.
|
|
41
|
+
- Session values are stored through Django's own `SESSION_SERIALIZER` and signed with your
|
|
42
|
+
`SECRET_KEY`, exactly as the database-backed store signs them, so sessions your site already
|
|
43
|
+
issued keep working when you switch engines.
|
|
44
|
+
|
|
45
|
+
This is a cache/session backend, **not** a `django.db` backend — models, migrations, the admin
|
|
46
|
+
and ORM queries still need Postgres or SQLite.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Django integration for MemoryDB: cache and session backends that drop into settings.py.
|
|
2
|
+
|
|
3
|
+
CACHES = {"default": {"BACKEND": "memorydb_django.MemoryDBCache",
|
|
4
|
+
"LOCATION": "memorydb://TOKEN@127.0.0.1:8080"}}
|
|
5
|
+
SESSION_ENGINE = "django.contrib.sessions.backends.memorydb" # via this package's session module
|
|
6
|
+
|
|
7
|
+
Values expire through the engine's own record expiry, not a swept key table:
|
|
8
|
+
an expired record stops being readable server-side.
|
|
9
|
+
"""
|
|
10
|
+
from memorydb_django.cache import MemoryDBCache
|
|
11
|
+
from memorydb_django.sessions import SessionStore
|
|
12
|
+
|
|
13
|
+
__all__ = ["MemoryDBCache", "SessionStore"]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Cache backend: Django's cache API on top of MemoryDB records.
|
|
2
|
+
|
|
3
|
+
settings.py:
|
|
4
|
+
CACHES = {"default": {
|
|
5
|
+
"BACKEND": "memorydb_django.MemoryDBCache",
|
|
6
|
+
"LOCATION": "memorydb://TOKEN@127.0.0.1:8080", # or set MEMORYDB_URL
|
|
7
|
+
"OPTIONS": {"collection": "django_cache"},
|
|
8
|
+
}}
|
|
9
|
+
"""
|
|
10
|
+
import base64
|
|
11
|
+
import pickle
|
|
12
|
+
from datetime import datetime, timedelta, timezone
|
|
13
|
+
|
|
14
|
+
from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
|
|
15
|
+
from memorydb import MemoryDB, MemoryDBError
|
|
16
|
+
|
|
17
|
+
MAX_PAGE = 500
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MemoryDBCache(BaseCache):
|
|
21
|
+
def __init__(self, server, params):
|
|
22
|
+
super().__init__(params)
|
|
23
|
+
options = params.get("OPTIONS", {})
|
|
24
|
+
location = server if isinstance(server, str) else options.get("url", "")
|
|
25
|
+
if location.startswith("memorydb"):
|
|
26
|
+
self.db = MemoryDB.from_url(location)
|
|
27
|
+
elif location:
|
|
28
|
+
self.db = MemoryDB(location, token=options.get("token"))
|
|
29
|
+
else:
|
|
30
|
+
self.db = MemoryDB() # reads MEMORYDB_URL
|
|
31
|
+
self.collection = options.get("collection", "django_cache")
|
|
32
|
+
self._ensure_collection()
|
|
33
|
+
|
|
34
|
+
# -- engine plumbing ----------------------------------------------------
|
|
35
|
+
def _ensure_collection(self):
|
|
36
|
+
try:
|
|
37
|
+
self.db.collections.create(self.collection,
|
|
38
|
+
fields={"cache_key": {"type": "string", "required": True},
|
|
39
|
+
"payload": {"type": "string"}},
|
|
40
|
+
unique=["cache_key"], indexes=["cache_key"])
|
|
41
|
+
except MemoryDBError as exc:
|
|
42
|
+
if exc.status != 409: # already exists
|
|
43
|
+
raise
|
|
44
|
+
|
|
45
|
+
def _find(self, key):
|
|
46
|
+
page = self.db.request("POST", "query/" + self.collection + "/",
|
|
47
|
+
{"where": [{"field": "cache_key", "op": "eq", "value": key}], "limit": 1})
|
|
48
|
+
return page["results"][0] if page["results"] else None
|
|
49
|
+
|
|
50
|
+
def _write(self, key, value, timeout):
|
|
51
|
+
payload = base64.b64encode(pickle.dumps(value)).decode()
|
|
52
|
+
expires = None
|
|
53
|
+
deadline = self.get_backend_timeout(timeout)
|
|
54
|
+
if deadline:
|
|
55
|
+
expires = datetime.fromtimestamp(deadline, timezone.utc).isoformat()
|
|
56
|
+
for attempt in range(3):
|
|
57
|
+
record = self._find(key)
|
|
58
|
+
try:
|
|
59
|
+
if record is None:
|
|
60
|
+
self.db.request("POST", "documents/" + self.collection + "/",
|
|
61
|
+
{"data": {"cache_key": key, "payload": payload}, "expires": expires})
|
|
62
|
+
else:
|
|
63
|
+
updated = dict(record)
|
|
64
|
+
updated["data"] = {"cache_key": key, "payload": payload}
|
|
65
|
+
updated["expires"] = expires
|
|
66
|
+
self.db.request("PUT", "documents/" + self.collection + "/" + record["id"] + "/", updated)
|
|
67
|
+
return
|
|
68
|
+
except MemoryDBError as exc:
|
|
69
|
+
if exc.status != 409 or attempt == 2:
|
|
70
|
+
raise # concurrent writer bumped the version; re-read and retry
|
|
71
|
+
|
|
72
|
+
def _read(self, key):
|
|
73
|
+
record = self._find(key)
|
|
74
|
+
if record is None:
|
|
75
|
+
return None
|
|
76
|
+
return pickle.loads(base64.b64decode(record["data"]["payload"]))
|
|
77
|
+
|
|
78
|
+
# -- BaseCache contract ---------------------------------------------------
|
|
79
|
+
def get(self, key, default=None, version=None):
|
|
80
|
+
envelope = self._read(self.make_key(key, version=version))
|
|
81
|
+
if envelope is None:
|
|
82
|
+
return default
|
|
83
|
+
value, flags = envelope
|
|
84
|
+
return value if flags == "c" else default
|
|
85
|
+
|
|
86
|
+
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
|
87
|
+
self._write(self.make_key(key, version=version), (value, "c"), timeout)
|
|
88
|
+
|
|
89
|
+
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
|
90
|
+
full = self.make_key(key, version=version)
|
|
91
|
+
if self._read(full) is None:
|
|
92
|
+
self.set(key, value, timeout=timeout, version=version)
|
|
93
|
+
return True
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
def delete(self, key, version=None):
|
|
97
|
+
record = self._find(self.make_key(key, version=version))
|
|
98
|
+
if record is None:
|
|
99
|
+
return
|
|
100
|
+
self.db.request("DELETE", "documents/" + self.collection + "/" + record["id"] + "/",
|
|
101
|
+
headers={"If-Match": str(record["version"])})
|
|
102
|
+
|
|
103
|
+
def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
|
|
104
|
+
full = self.make_key(key, version=version)
|
|
105
|
+
envelope = self._read(full)
|
|
106
|
+
if envelope is None:
|
|
107
|
+
return False
|
|
108
|
+
self._write(full, envelope, timeout)
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
def clear(self):
|
|
112
|
+
while True:
|
|
113
|
+
page = self.db.request("POST", "query/" + self.collection + "/", {"limit": MAX_PAGE})
|
|
114
|
+
if not page["results"]:
|
|
115
|
+
return
|
|
116
|
+
for record in page["results"]:
|
|
117
|
+
self.db.request("DELETE", "documents/" + self.collection + "/" + record["id"] + "/",
|
|
118
|
+
headers={"If-Match": str(record["version"])})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Session backend: django.contrib.sessions storage on MemoryDB records.
|
|
2
|
+
|
|
3
|
+
settings.py:
|
|
4
|
+
SESSION_ENGINE = "memorydb_django.sessions"
|
|
5
|
+
|
|
6
|
+
Sessions expire through the engine's own record expiry (SESSION_COOKIE_AGE);
|
|
7
|
+
expired sessions stop being readable server-side with no sweep cron.
|
|
8
|
+
"""
|
|
9
|
+
from datetime import datetime, timedelta, timezone
|
|
10
|
+
|
|
11
|
+
from django.conf import settings
|
|
12
|
+
from django.contrib.sessions.backends.base import CreateError, SessionBase
|
|
13
|
+
from memorydb import MemoryDB, MemoryDBError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _client():
|
|
17
|
+
location = getattr(settings, "MEMORYDB_URL", "")
|
|
18
|
+
if location.startswith("memorydb"):
|
|
19
|
+
return MemoryDB.from_url(location)
|
|
20
|
+
if location:
|
|
21
|
+
return MemoryDB(location, token=getattr(settings, "MEMORYDB_TOKEN", None))
|
|
22
|
+
return MemoryDB() # reads the MEMORYDB_URL env var
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _collection():
|
|
26
|
+
return getattr(settings, "MEMORYDB_SESSIONS_COLLECTION", "django_sessions")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SessionStore(SessionBase):
|
|
30
|
+
def __init__(self, session_key=None):
|
|
31
|
+
super().__init__(session_key)
|
|
32
|
+
self.db = _client()
|
|
33
|
+
self.collection = _collection()
|
|
34
|
+
try:
|
|
35
|
+
self.db.collections.create(self.collection,
|
|
36
|
+
fields={"session_key": {"type": "string", "required": True},
|
|
37
|
+
"payload": {"type": "string"}},
|
|
38
|
+
unique=["session_key"], indexes=["session_key"])
|
|
39
|
+
except MemoryDBError as exc:
|
|
40
|
+
if exc.status != 409:
|
|
41
|
+
raise
|
|
42
|
+
|
|
43
|
+
def _find(self, session_key):
|
|
44
|
+
page = self.db.request("POST", "query/" + self.collection + "/",
|
|
45
|
+
{"where": [{"field": "session_key", "op": "eq", "value": session_key}], "limit": 1})
|
|
46
|
+
return page["results"][0] if page["results"] else None
|
|
47
|
+
|
|
48
|
+
def load(self):
|
|
49
|
+
record = self._find(self.session_key) if self.session_key else None
|
|
50
|
+
if record is None:
|
|
51
|
+
self._session_key = None
|
|
52
|
+
return {}
|
|
53
|
+
return self.decode(record["data"]["payload"])
|
|
54
|
+
|
|
55
|
+
def exists(self, session_key):
|
|
56
|
+
return self._find(session_key) is not None
|
|
57
|
+
|
|
58
|
+
def create(self):
|
|
59
|
+
while True:
|
|
60
|
+
self._session_key = self._get_new_session_key()
|
|
61
|
+
try:
|
|
62
|
+
self.save(must_create=True)
|
|
63
|
+
except CreateError:
|
|
64
|
+
continue # key collision; try another
|
|
65
|
+
self.modified = True
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
def save(self, must_create=False):
|
|
69
|
+
if self.session_key is None:
|
|
70
|
+
return self.create()
|
|
71
|
+
payload = self.encode(self._get_session(no_load=must_create))
|
|
72
|
+
expires = (datetime.now(timezone.utc) + timedelta(seconds=self.get_expiry_age())).isoformat()
|
|
73
|
+
for attempt in range(3):
|
|
74
|
+
record = self._find(self.session_key)
|
|
75
|
+
try:
|
|
76
|
+
if record is None:
|
|
77
|
+
self.db.request("POST", "documents/" + self.collection + "/",
|
|
78
|
+
{"data": {"session_key": self.session_key, "payload": payload}, "expires": expires})
|
|
79
|
+
return
|
|
80
|
+
if must_create:
|
|
81
|
+
raise CreateError
|
|
82
|
+
updated = dict(record)
|
|
83
|
+
updated["data"] = {"session_key": self.session_key, "payload": payload}
|
|
84
|
+
updated["expires"] = expires
|
|
85
|
+
self.db.request("PUT", "documents/" + self.collection + "/" + record["id"] + "/", updated)
|
|
86
|
+
return
|
|
87
|
+
except MemoryDBError as exc:
|
|
88
|
+
if exc.status != 409 or attempt == 2:
|
|
89
|
+
raise
|
|
90
|
+
|
|
91
|
+
def delete(self, session_key=None):
|
|
92
|
+
if session_key is None:
|
|
93
|
+
if self.session_key is None:
|
|
94
|
+
return
|
|
95
|
+
session_key = self.session_key
|
|
96
|
+
record = self._find(session_key)
|
|
97
|
+
if record is not None:
|
|
98
|
+
self.db.request("DELETE", "documents/" + self.collection + "/" + record["id"] + "/",
|
|
99
|
+
headers={"If-Match": str(record["version"])})
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def clear_expired(cls):
|
|
103
|
+
pass # expiry is enforced by the engine at read time; nothing to sweep
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memorydb-django
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Django cache and session backends for MemoryDB (SQLite-style drop-in for the settings file)
|
|
5
|
+
Author: humaniods
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/humaniods/MemoryDB
|
|
8
|
+
Project-URL: Repository, https://github.com/humaniods/MemoryDB
|
|
9
|
+
Project-URL: Issues, https://github.com/humaniods/MemoryDB/issues
|
|
10
|
+
Keywords: django,cache,sessions,database,agent-memory
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Framework :: Django
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: POSIX
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Database
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: superb-memory
|
|
20
|
+
Requires-Dist: Django>=4.2
|
|
21
|
+
|
|
22
|
+
# memorydb-django
|
|
23
|
+
|
|
24
|
+
Django cache and session backends for [MemoryDB](https://github.com/humaniods/MemoryDB).
|
|
25
|
+
Two lines in `settings.py`, the way `sqlite3` is a drop-in for Django itself — no models, no
|
|
26
|
+
migrations, no new code in your views.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install memorydb-django
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
# settings.py
|
|
34
|
+
CACHES = {"default": {
|
|
35
|
+
"BACKEND": "memorydb_django.MemoryDBCache",
|
|
36
|
+
"LOCATION": "memorydb://TOKEN@127.0.0.1:8080", # or leave empty and set MEMORYDB_URL
|
|
37
|
+
}}
|
|
38
|
+
|
|
39
|
+
SESSION_ENGINE = "memorydb_django.sessions"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then use Django's own API:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from django.core.cache import cache
|
|
46
|
+
cache.set("hero", {"name": "Karna"}, timeout=30) # one record; the TTL is the record's expiry
|
|
47
|
+
cache.get("hero")
|
|
48
|
+
|
|
49
|
+
request.session["user_id"] = 42 # SESSION_COOKIE_AGE enforced server-side
|
|
50
|
+
request.session.save()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Details worth knowing:
|
|
54
|
+
|
|
55
|
+
- Each cache key and each session is one MemoryDB document in the `django_cache` /
|
|
56
|
+
`django_sessions` collection (typed, unique and indexed; created on first use).
|
|
57
|
+
- `timeout=` and `SESSION_COOKIE_AGE` are stored as the record's own `expires` field, so the
|
|
58
|
+
server stops serving them at the deadline. No cleanup cron, and `clear_expired()` is a no-op.
|
|
59
|
+
- Concurrent writers use optimistic versions and retry, so there are no row locks or deadlocks.
|
|
60
|
+
- Set `MEMORYDB_SESSIONS_COLLECTION` / `OPTIONS["collection"]` to move either store elsewhere, so
|
|
61
|
+
two Django projects on one server stay apart.
|
|
62
|
+
- Session values are stored through Django's own `SESSION_SERIALIZER` and signed with your
|
|
63
|
+
`SECRET_KEY`, exactly as the database-backed store signs them, so sessions your site already
|
|
64
|
+
issued keep working when you switch engines.
|
|
65
|
+
|
|
66
|
+
This is a cache/session backend, **not** a `django.db` backend — models, migrations, the admin
|
|
67
|
+
and ORM queries still need Postgres or SQLite.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
memorydb_django/__init__.py
|
|
4
|
+
memorydb_django/cache.py
|
|
5
|
+
memorydb_django/sessions.py
|
|
6
|
+
memorydb_django.egg-info/PKG-INFO
|
|
7
|
+
memorydb_django.egg-info/SOURCES.txt
|
|
8
|
+
memorydb_django.egg-info/dependency_links.txt
|
|
9
|
+
memorydb_django.egg-info/requires.txt
|
|
10
|
+
memorydb_django.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
memorydb_django
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
[project]
|
|
5
|
+
name = "memorydb-django"
|
|
6
|
+
version = "0.2.1"
|
|
7
|
+
description = "Django cache and session backends for MemoryDB (SQLite-style drop-in for the settings file)"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{ name = "humaniods" }]
|
|
12
|
+
keywords = ["django", "cache", "sessions", "database", "agent-memory"]
|
|
13
|
+
classifiers = ["Development Status :: 4 - Beta", "Framework :: Django",
|
|
14
|
+
"Intended Audience :: Developers", "Operating System :: POSIX",
|
|
15
|
+
"Programming Language :: Python :: 3", "Topic :: Database"]
|
|
16
|
+
dependencies = ["superb-memory", "Django>=4.2"]
|
|
17
|
+
[tool.setuptools]
|
|
18
|
+
packages = ["memorydb_django"]
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/humaniods/MemoryDB"
|
|
21
|
+
Repository = "https://github.com/humaniods/MemoryDB"
|
|
22
|
+
Issues = "https://github.com/humaniods/MemoryDB/issues"
|