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.
- alt_python_pynosqlc_core-1.0.4/.gitignore +30 -0
- alt_python_pynosqlc_core-1.0.4/PKG-INFO +28 -0
- alt_python_pynosqlc_core-1.0.4/README.md +5 -0
- alt_python_pynosqlc_core-1.0.4/pynosqlc/core/__init__.py +39 -0
- alt_python_pynosqlc_core-1.0.4/pynosqlc/core/client.py +112 -0
- alt_python_pynosqlc_core-1.0.4/pynosqlc/core/collection.py +109 -0
- alt_python_pynosqlc_core-1.0.4/pynosqlc/core/cursor.py +88 -0
- alt_python_pynosqlc_core-1.0.4/pynosqlc/core/driver.py +41 -0
- alt_python_pynosqlc_core-1.0.4/pynosqlc/core/driver_manager.py +66 -0
- alt_python_pynosqlc_core-1.0.4/pynosqlc/core/errors.py +12 -0
- alt_python_pynosqlc_core-1.0.4/pynosqlc/core/filter.py +176 -0
- alt_python_pynosqlc_core-1.0.4/pynosqlc/core/py.typed +0 -0
- alt_python_pynosqlc_core-1.0.4/pynosqlc/core/testing/__init__.py +10 -0
- alt_python_pynosqlc_core-1.0.4/pynosqlc/core/testing/compliance.py +280 -0
- alt_python_pynosqlc_core-1.0.4/pyproject.toml +41 -0
- alt_python_pynosqlc_core-1.0.4/tests/__init__.py +0 -0
- alt_python_pynosqlc_core-1.0.4/tests/test_driver_manager.py +117 -0
- alt_python_pynosqlc_core-1.0.4/tests/test_filter.py +189 -0
|
@@ -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
|
+
"""
|