patx/mongokv
mongodb meshed with pickledb - mk0.1
Commit 466abe1 · patx · 2025-12-10T21:14:03-05:00
Comments
No comments yet.
Diff
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..211ab7c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,20 @@
+**[mkvDB](https://github.com/patx/mkvdb)** is a fast, easy to use, Python key-value store with first-class
+asynchronous support. It stands on the shoulders of [motor](https://motor.readthedocs.io/)
+and is safe across multiple processes and workers thanks to MongoDB’s concurrency model.
+
+Is it a little pointless if you already know Mongo? Yeah. But if ya just want to
+`set` and `get` without thinking about collections or schemas, it’s perfect. I made this
+because [pickleDB](https://patx.github.io/pickledb) doesn't play nice with ASGI but I
+like its dumb API anyway :) There's no docs yet, so just look at the source code -
+its really short I promise.
+
+```python
+from mkvdb import Mkv
+
+db = Mkv("mongodb://localhost:27017")
+
+db.set("key", "value")
+db.get("key") # returns "value"
+```
+
+Note: You can also `await db.set` or `db.whatever` for async usage.
diff --git a/mkvdb.py b/mkvdb.py
new file mode 100644
index 0000000..1791e52
--- /dev/null
+++ b/mkvdb.py
@@ -0,0 +1,86 @@
+"""
+mkvDB - https://patx.github.io/mkvDB
+Harrison Erd - https://harrisonerd.com/
+Licensed - BSD 3 Clause (see LICENSE)
+"""
+
+import asyncio
+from typing import Any, Optional, List
+
+import motor.motor_asyncio
+
+
+def in_async() -> bool:
+ """Check if running inside an active event loop."""
+ try:
+ asyncio.get_running_loop()
+ return True
+ except RuntimeError:
+ return False
+
+
+def dualmethod(func):
+ """Allows async methods to also be called synchronously."""
+ def wrapper(self, *args, **kwargs):
+ coro = func(self, *args, **kwargs)
+ if in_async():
+ return coro
+ return asyncio.run(coro)
+ return wrapper
+
+
+class Mkv:
+ """
+ A unified async/sync key-value store backed by MongoDB (Motor).
+ Each key is stored as a document:
+ { "_id": <str(key)>, "value": <BSON-serializable Python object> }
+ """
+
+ def __init__(self, mongo_uri: str, db_name: str = "mkv",
+ collection_name: str = "kv"):
+ self.client = motor.motor_asyncio.AsyncIOMotorClient(mongo_uri)
+ self.db = self.client[db_name]
+ self.collection = self.db[collection_name]
+
+ @dualmethod
+ async def set(self, key: Any, value: Any) -> bool:
+ """Set a key-value pair. Overwrites if the key already exists."""
+ await self.collection.update_one(
+ {"_id": str(key)}, {"$set": {"value": value}}, upsert=True)
+ return True
+
+ @dualmethod
+ async def get(self, key: Any, default: Optional[Any] = None) -> Any:
+ """
+ Get the value for a key. Returns `default` if the key does not exist.
+ """
+ key_str = str(key)
+ doc = await self.collection.find_one({"_id": key_str})
+ if doc is None:
+ return default
+ return doc.get("value", default)
+
+ @dualmethod
+ async def remove(self, key: Any) -> bool:
+ """
+ Remove a key-value pair. Returns True if a document was
+ deleted, False otherwise.
+ """
+ result = await self.collection.delete_one({"_id": str(key)})
+ return result.deleted_count > 0
+
+ @dualmethod
+ async def all(self) -> List[str]:
+ """Return a list of all keys in the database."""
+ keys: List[str] = []
+ cursor = self.collection.find({}, {"_id": 1})
+ async for doc in cursor:
+ keys.append(doc["_id"])
+ return keys
+
+ @dualmethod
+ async def purge(self) -> bool:
+ """Remove all key-value pairs from the database."""
+ await self.collection.delete_many({})
+ return True
+
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..ae3bcc0
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,42 @@
+[build-system]
+requires = ["flit_core>=3.9,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "mkvdb"
+version = "0.1"
+description = "MongoDB-backed async/sync key–value store with a super tiny API."
+keywords = [
+ "key-value",
+ "database",
+ "mongodb",
+ "asyncio",
+ "motor",
+ "kv",
+ "cache",
+]
+readme = "README.md"
+authors = [{ name = "Harrison Erd", email = "[email protected]" }]
+license = { file = "LICENSE" }
+requires-python = ">=3.10"
+dependencies = [
+ "motor>=3.6,<4",
+]
+
+classifiers = [
+ "Framework :: AsyncIO",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: BSD License",
+ "Topic :: Database",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+]
+
+[project.urls]
+Homepage = "https://patx.github.io/mkvdb"
+Repository = "https://github.com/patx/mkvdb"
+