alt-python-pynosqlc-core 1.0.4__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.
@@ -0,0 +1,30 @@
1
+
2
+ # ── GSD baseline (auto-generated) ──
3
+ .gsd
4
+ .DS_Store
5
+ Thumbs.db
6
+ *.swp
7
+ *.swo
8
+ *~
9
+ .idea/
10
+ .vscode/
11
+ *.code-workspace
12
+ .env
13
+ .env.*
14
+ !.env.example
15
+ node_modules/
16
+ .next/
17
+ dist/
18
+ build/
19
+ __pycache__/
20
+ *.pyc
21
+ .venv/
22
+ venv/
23
+ target/
24
+ vendor/
25
+ *.log
26
+ coverage/
27
+ .cache/
28
+ tmp/
29
+
30
+ /.bg-shell/
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 2.4
2
+ Name: alt-python-pynosqlc-core
3
+ Version: 1.0.4
4
+ Summary: Core abstraction hierarchy for pynosqlc — Driver, DriverManager, Client, Collection, Cursor, Filter, FieldCondition, UnsupportedOperationError, compliance suite
5
+ Project-URL: Homepage, https://github.com/alt-python/pynosqlc
6
+ Project-URL: Repository, https://github.com/alt-python/pynosqlc
7
+ Project-URL: Documentation, https://github.com/alt-python/pynosqlc#getting-started
8
+ Project-URL: Bug Tracker, https://github.com/alt-python/pynosqlc/issues
9
+ Author: Craig Parravicini, Claude (Anthropic)
10
+ License: MIT
11
+ Keywords: async,database,driver,nosql
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Database
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.12
22
+ Description-Content-Type: text/markdown
23
+
24
+ # pynosqlc-core
25
+
26
+ Core abstraction hierarchy for pynosqlc — a JDBC-inspired unified async NoSQL access layer for Python.
27
+
28
+ Provides `DriverManager`, `Driver`, `Client`, `Collection`, `Cursor`, `Filter`, `FieldCondition`, `UnsupportedOperationError`, and a portable driver compliance suite.
@@ -0,0 +1,5 @@
1
+ # pynosqlc-core
2
+
3
+ Core abstraction hierarchy for pynosqlc — a JDBC-inspired unified async NoSQL access layer for Python.
4
+
5
+ Provides `DriverManager`, `Driver`, `Client`, `Collection`, `Cursor`, `Filter`, `FieldCondition`, `UnsupportedOperationError`, and a portable driver compliance suite.
@@ -0,0 +1,39 @@
1
+ """
2
+ pynosqlc.core — Core abstraction hierarchy for pynosqlc.
3
+
4
+ Provides:
5
+ Driver — ABC for NoSQL drivers
6
+ DriverManager — Registry and URL-based connection dispatcher
7
+ Client — ABC for database clients (async context manager)
8
+ ClientDataSource — Connection factory wrapping DriverManager
9
+ Collection — ABC for collections / tables / buckets
10
+ Cursor — Cursor-based and bulk document access (async iterable)
11
+ Filter — Chainable query filter builder
12
+ FieldCondition — Field-level condition within a Filter
13
+ UnsupportedOperationError — Raised when a driver does not implement an operation
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from pynosqlc.core.errors import UnsupportedOperationError
19
+ from pynosqlc.core.driver import Driver
20
+ from pynosqlc.core.driver_manager import DriverManager
21
+ from pynosqlc.core.cursor import Cursor
22
+ from pynosqlc.core.collection import Collection
23
+ from pynosqlc.core.client import Client, ClientDataSource
24
+ from pynosqlc.core.filter import Filter, FieldCondition
25
+
26
+ __author__ = "Craig Parravicini"
27
+ __collaborators__ = ["Claude (Anthropic)"]
28
+
29
+ __all__ = [
30
+ "Driver",
31
+ "DriverManager",
32
+ "Client",
33
+ "ClientDataSource",
34
+ "Collection",
35
+ "Cursor",
36
+ "Filter",
37
+ "FieldCondition",
38
+ "UnsupportedOperationError",
39
+ ]
@@ -0,0 +1,112 @@
1
+ """
2
+ client.py — Abstract base class for a pynosqlc client session.
3
+
4
+ Drivers override ``_get_collection()`` and ``_close()``.
5
+ Manages a cache of Collection instances keyed by name.
6
+ Implements the async context manager protocol (``async with``).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from abc import ABC, abstractmethod
12
+ from typing import TYPE_CHECKING
13
+
14
+ from pynosqlc.core.driver_manager import DriverManager
15
+
16
+ if TYPE_CHECKING:
17
+ from pynosqlc.core.collection import Collection
18
+
19
+
20
+ class Client(ABC):
21
+ """A session to a NoSQL database.
22
+
23
+ Args:
24
+ config: optional dict; recognises key ``'url'``.
25
+ """
26
+
27
+ def __init__(self, config: dict | None = None) -> None:
28
+ cfg = config or {}
29
+ self._url: str | None = cfg.get("url")
30
+ self._closed: bool = False
31
+ self._collections: dict[str, "Collection"] = {}
32
+
33
+ # ── Async context manager ──────────────────────────────────────────────
34
+
35
+ async def __aenter__(self) -> "Client":
36
+ return self
37
+
38
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
39
+ await self.close()
40
+
41
+ # ── Public API ─────────────────────────────────────────────────────────
42
+
43
+ def get_collection(self, name: str) -> "Collection":
44
+ """Return a (cached) Collection by name.
45
+
46
+ Raises:
47
+ RuntimeError: if the client is closed
48
+ """
49
+ self._check_closed()
50
+ if name not in self._collections:
51
+ self._collections[name] = self._get_collection(name)
52
+ return self._collections[name]
53
+
54
+ async def close(self) -> None:
55
+ """Close the client and release all resources."""
56
+ self._closed = True
57
+ self._collections.clear()
58
+ await self._close()
59
+
60
+ def is_closed(self) -> bool:
61
+ """Return ``True`` if the client has been closed."""
62
+ return self._closed
63
+
64
+ def get_url(self) -> str | None:
65
+ """Return the pynosqlc URL this client was opened with."""
66
+ return self._url
67
+
68
+ # ── Abstract implementation hooks ──────────────────────────────────────
69
+
70
+ @abstractmethod
71
+ def _get_collection(self, name: str) -> "Collection":
72
+ """Create and return a new Collection instance for *name*."""
73
+
74
+ async def _close(self) -> None:
75
+ """Override to release driver-specific resources on close."""
76
+
77
+ # ── Internal helpers ───────────────────────────────────────────────────
78
+
79
+ def _check_closed(self) -> None:
80
+ if self._closed:
81
+ raise RuntimeError("Client is closed")
82
+
83
+
84
+ class ClientDataSource:
85
+ """Convenience factory that wraps :meth:`DriverManager.get_client`.
86
+
87
+ Mirrors pydbc's DataSource pattern.
88
+
89
+ Args:
90
+ config: dict with keys:
91
+ - ``url`` (required) — pynosqlc URL
92
+ - ``username`` (optional)
93
+ - ``password`` (optional)
94
+ - ``properties`` (optional) — additional driver-specific options
95
+ """
96
+
97
+ def __init__(self, config: dict | None = None) -> None:
98
+ cfg = config or {}
99
+ self._url: str = cfg["url"]
100
+ self._properties: dict = {
101
+ "username": cfg.get("username"),
102
+ "password": cfg.get("password"),
103
+ **(cfg.get("properties") or {}),
104
+ }
105
+
106
+ async def get_client(self) -> Client:
107
+ """Return a client from the configured data source."""
108
+ return await DriverManager.get_client(self._url, self._properties)
109
+
110
+ def get_url(self) -> str:
111
+ """Return the configured pynosqlc URL."""
112
+ return self._url
@@ -0,0 +1,109 @@
1
+ """
2
+ collection.py — Abstract base class for a named collection (table/bucket).
3
+
4
+ Driver implementations override the ``_`` methods. All base ``_`` methods
5
+ raise ``UnsupportedOperationError`` — drivers implement only what their
6
+ backend supports.
7
+
8
+ Operations
9
+ ----------
10
+ Key-value : get(key), store(key, doc), delete(key)
11
+ Document : insert(doc), update(key, patch)
12
+ Query : find(ast)
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from abc import ABC, abstractmethod
18
+ from typing import TYPE_CHECKING
19
+
20
+ from pynosqlc.core.errors import UnsupportedOperationError
21
+
22
+ if TYPE_CHECKING:
23
+ from pynosqlc.core.client import Client
24
+ from pynosqlc.core.cursor import Cursor
25
+
26
+
27
+ class Collection(ABC):
28
+ """Represents a named collection within a :class:`Client`."""
29
+
30
+ def __init__(self, client: "Client", name: str) -> None:
31
+ self._client = client
32
+ self._name = name
33
+ self._closed: bool = False
34
+
35
+ def get_name(self) -> str:
36
+ """Return the collection name."""
37
+ return self._name
38
+
39
+ # ── Public API ─────────────────────────────────────────────────────────
40
+
41
+ async def get(self, key: str) -> dict | None:
42
+ """Retrieve a document by its primary key.
43
+
44
+ Returns:
45
+ The document dict, or ``None`` if the key does not exist.
46
+ """
47
+ self._check_closed()
48
+ return await self._get(key)
49
+
50
+ async def store(self, key: str, doc: dict) -> None:
51
+ """Store (upsert) a document under *key*."""
52
+ self._check_closed()
53
+ await self._store(key, doc)
54
+
55
+ async def delete(self, key: str) -> None:
56
+ """Delete the document at *key*. No-op if the key does not exist."""
57
+ self._check_closed()
58
+ await self._delete(key)
59
+
60
+ async def insert(self, doc: dict) -> str:
61
+ """Insert a document and return the backend-assigned key / ``_id``."""
62
+ self._check_closed()
63
+ return await self._insert(doc)
64
+
65
+ async def update(self, key: str, patch: dict) -> None:
66
+ """Patch the document at *key*.
67
+
68
+ Only provided fields are updated; others are preserved (shallow merge).
69
+ """
70
+ self._check_closed()
71
+ await self._update(key, patch)
72
+
73
+ async def find(self, ast: dict) -> "Cursor":
74
+ """Find documents matching the given filter AST.
75
+
76
+ Args:
77
+ ast: a built filter AST from ``Filter.build()``
78
+
79
+ Returns:
80
+ A :class:`~pynosqlc.core.Cursor` over matching documents.
81
+ """
82
+ self._check_closed()
83
+ return await self._find(ast)
84
+
85
+ # ── Abstract implementation hooks ──────────────────────────────────────
86
+
87
+ async def _get(self, key: str) -> dict | None:
88
+ raise UnsupportedOperationError("get() is not supported by this driver")
89
+
90
+ async def _store(self, key: str, doc: dict) -> None:
91
+ raise UnsupportedOperationError("store() is not supported by this driver")
92
+
93
+ async def _delete(self, key: str) -> None:
94
+ raise UnsupportedOperationError("delete() is not supported by this driver")
95
+
96
+ async def _insert(self, doc: dict) -> str:
97
+ raise UnsupportedOperationError("insert() is not supported by this driver")
98
+
99
+ async def _update(self, key: str, patch: dict) -> None:
100
+ raise UnsupportedOperationError("update() is not supported by this driver")
101
+
102
+ async def _find(self, ast: dict) -> "Cursor":
103
+ raise UnsupportedOperationError("find() is not supported by this driver")
104
+
105
+ # ── Internal helpers ───────────────────────────────────────────────────
106
+
107
+ def _check_closed(self) -> None:
108
+ if self._closed:
109
+ raise RuntimeError("Collection is closed")
@@ -0,0 +1,88 @@
1
+ """
2
+ cursor.py — Cursor over a find() result set.
3
+
4
+ Provides cursor-based iteration (next/get_document) plus bulk access
5
+ (get_documents) and implements the async iterator protocol for use with
6
+ ``async for``.
7
+
8
+ The base class buffers all results in a list. Driver implementations may
9
+ subclass Cursor to support streaming from the database.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+
15
+ class Cursor:
16
+ """Async-iterable cursor over a collection of documents.
17
+
18
+ Args:
19
+ documents: pre-buffered result list (pass ``[]`` for an empty cursor)
20
+ """
21
+
22
+ def __init__(self, documents: list[dict] | None = None) -> None:
23
+ self._documents: list[dict] = documents if documents is not None else []
24
+ self._cursor: int = -1
25
+ self._closed: bool = False
26
+
27
+ async def next(self) -> bool:
28
+ """Advance to the next document.
29
+
30
+ Returns:
31
+ ``True`` if there is a current document; ``False`` when exhausted.
32
+ """
33
+ self._check_closed()
34
+ self._cursor += 1
35
+ return self._cursor < len(self._documents)
36
+
37
+ def get_document(self) -> dict:
38
+ """Return a shallow copy of the document at the current cursor position.
39
+
40
+ Raises:
41
+ RuntimeError: if the cursor is not on a valid document (call
42
+ ``next()`` first)
43
+ RuntimeError: if the cursor is closed
44
+ """
45
+ self._check_closed()
46
+ self._check_cursor()
47
+ return dict(self._documents[self._cursor])
48
+
49
+ def get_documents(self) -> list[dict]:
50
+ """Return all documents as a list of shallow copies.
51
+
52
+ Does not require ``next()`` to have been called — returns the full
53
+ buffered result set.
54
+ """
55
+ self._check_closed()
56
+ return [dict(d) for d in self._documents]
57
+
58
+ async def close(self) -> None:
59
+ """Close the cursor and release resources."""
60
+ self._closed = True
61
+
62
+ def is_closed(self) -> bool:
63
+ """Return ``True`` if the cursor has been closed."""
64
+ return self._closed
65
+
66
+ # ── Async iterator protocol ────────────────────────────────────────────
67
+
68
+ def __aiter__(self) -> "Cursor":
69
+ return self
70
+
71
+ async def __anext__(self) -> dict:
72
+ has_more = await self.next()
73
+ if has_more:
74
+ return self.get_document()
75
+ await self.close()
76
+ raise StopAsyncIteration
77
+
78
+ # ── Internal helpers ───────────────────────────────────────────────────
79
+
80
+ def _check_closed(self) -> None:
81
+ if self._closed:
82
+ raise RuntimeError("Cursor is closed")
83
+
84
+ def _check_cursor(self) -> None:
85
+ if self._cursor < 0 or self._cursor >= len(self._documents):
86
+ raise RuntimeError(
87
+ "Cursor is not on a valid document — call next() first"
88
+ )
@@ -0,0 +1,41 @@
1
+ """
2
+ driver.py — Abstract base class for pynosqlc drivers.
3
+
4
+ Each driver implementation registers itself with DriverManager on import
5
+ and declares which URL schemes it handles.
6
+
7
+ URL scheme: pynosqlc:<subprotocol>:<connection-details>
8
+ e.g. pynosqlc:mongodb://localhost:27017/mydb
9
+ pynosqlc:memory:
10
+ pynosqlc:dynamodb:us-east-1
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from abc import ABC, abstractmethod
16
+ from typing import TYPE_CHECKING
17
+
18
+ if TYPE_CHECKING:
19
+ from pynosqlc.core.client import Client
20
+
21
+
22
+ class Driver(ABC):
23
+ """Creates client connections to a specific NoSQL database type."""
24
+
25
+ @abstractmethod
26
+ def accepts_url(self, url: str) -> bool:
27
+ """Return True if this driver handles the given pynosqlc URL.
28
+
29
+ Args:
30
+ url: e.g. ``'pynosqlc:mongodb://localhost:27017/mydb'``
31
+ """
32
+
33
+ @abstractmethod
34
+ async def connect(self, url: str, properties: dict | None = None) -> "Client":
35
+ """Create a client connection to the database.
36
+
37
+ Args:
38
+ url: pynosqlc URL
39
+ properties: optional dict with driver-specific options
40
+ (e.g. username, password, endpoint)
41
+ """
@@ -0,0 +1,66 @@
1
+ """
2
+ driver_manager.py — Registry for pynosqlc drivers.
3
+
4
+ Drivers register themselves on import. When get_client() is called,
5
+ DriverManager iterates registered drivers to find one that accepts the URL.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ if TYPE_CHECKING:
13
+ from pynosqlc.core.driver import Driver
14
+ from pynosqlc.core.client import Client
15
+
16
+
17
+ class DriverManager:
18
+ """Class-level registry for pynosqlc drivers."""
19
+
20
+ _drivers: list["Driver"] = []
21
+
22
+ @classmethod
23
+ def register_driver(cls, driver: "Driver") -> None:
24
+ """Register a driver instance (idempotent — no duplicates)."""
25
+ if driver not in cls._drivers:
26
+ cls._drivers.append(driver)
27
+
28
+ @classmethod
29
+ def deregister_driver(cls, driver: "Driver") -> None:
30
+ """Remove a previously registered driver."""
31
+ cls._drivers = [d for d in cls._drivers if d is not driver]
32
+
33
+ @classmethod
34
+ async def get_client(
35
+ cls,
36
+ url: str,
37
+ properties: dict | None = None,
38
+ ) -> "Client":
39
+ """Return a client from the first driver that accepts *url*.
40
+
41
+ Args:
42
+ url: pynosqlc URL (e.g. ``'pynosqlc:memory:'``)
43
+ properties: optional driver-specific connection properties
44
+
45
+ Raises:
46
+ ValueError: if no registered driver accepts the URL
47
+ """
48
+ if properties is None:
49
+ properties = {}
50
+ for driver in cls._drivers:
51
+ if driver.accepts_url(url):
52
+ return await driver.connect(url, properties)
53
+ raise ValueError(
54
+ f"No suitable driver found for URL: {url!r}. "
55
+ f"Registered drivers: {[type(d).__name__ for d in cls._drivers]}"
56
+ )
57
+
58
+ @classmethod
59
+ def get_drivers(cls) -> list["Driver"]:
60
+ """Return a copy of the registered driver list."""
61
+ return list(cls._drivers)
62
+
63
+ @classmethod
64
+ def clear(cls) -> None:
65
+ """Clear all registered drivers (for testing)."""
66
+ cls._drivers = []
@@ -0,0 +1,12 @@
1
+ """
2
+ errors.py — Custom exception classes for pynosqlc.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+
8
+ class UnsupportedOperationError(Exception):
9
+ """Raised when a driver does not implement an optional Collection operation.
10
+
11
+ Callers can ``isinstance``-check this to handle gracefully.
12
+ """