patx/mongokv

mongodb meshed with pickledb - mk0.1

Commit 466abe1 · patx · 2025-12-10T21:14:03-05:00

Changeset
466abe1331fb563af53a96fd2a4d250b984f3483

View source at this commit

Comments

No comments yet.

Log in to comment

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"
+