-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
gh-100414: Add SQLite backend to dbm #114481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
48 commits
Select commit
Hold shift + click to select a range
8c19c81
gh-100414: Add SQLite backend to dbm
erlend-aasland 7f32772
Preliminary docs
erlend-aasland 3f8a119
Decode path a little bit earlier
erlend-aasland 4f82c88
Merge branch 'main' into sqlite/dbm
erlend-aasland 38a9b41
Substitutions are already in place
erlend-aasland db0f148
Use CSV-style table, as the rest of the .rst file does
erlend-aasland e50d1e9
dbm.sqlite3.error is a subclass of OSError
erlend-aasland 0abd7dc
Don't raise DB API exceptions; test this
erlend-aasland 98fba0f
Pull in main
erlend-aasland 2b97160
Don't panic on double close(); add more tests
erlend-aasland 917bfba
Corruption tests: check all basic operations for flags 'r', 'w', 'c'
erlend-aasland 874fb81
Add read-only specific test
erlend-aasland b655473
Address review: always import sqlite3; let ImportError deal with miss…
erlend-aasland 95eba93
Test whichdb
erlend-aasland 54751ba
test namespacing
erlend-aasland 906748e
Test that 'c' works even if you already have a database; make sure BU…
erlend-aasland 7320f18
Pull in main
erlend-aasland c89c5fb
Remove unneeded comma
erlend-aasland 34d8c7f
Close cursors explicitly for each query
erlend-aasland 608c229
Address review from Donghee, Serhiy, and myself:
erlend-aasland 3d53054
Pull in main
erlend-aasland 75f7c6a
Add URI tests, fix whitespace, catch errors during db creation
erlend-aasland 3ac010c
Enable test on Windows
erlend-aasland 08c3848
Use as_uri() instead
erlend-aasland 775ff91
Skip tests with relative paths for now
erlend-aasland 4ba9607
Add prelim docstring for dbm.sqlite3.open
erlend-aasland dc0ba26
Amend Windows URI expected results
erlend-aasland 37f99ab
For now, just remove the relative URI tests on Windows
erlend-aasland 177b200
Document dbm.sqlite3.open signature
erlend-aasland 1a661a5
Update Lib/test/test_dbm.py
erlend-aasland fae8603
Update Lib/test/test_dbm.py
erlend-aasland d5fc39c
Only test URI substitution and normalisation; discard prefix
erlend-aasland d350ce2
Pull in changes from PR
erlend-aasland a8db17f
Doc amendments
erlend-aasland 1f87517
Pull in main
erlend-aasland 898dd71
Pull in main
erlend-aasland bb49fab
Mark up flags as list; it renders better
erlend-aasland 26a2c69
Apply suggestions from code review
erlend-aasland ae53f95
Address review: use closing() in _execute() wrapper
erlend-aasland 926ef1a
Update Doc/library/dbm.rst
erlend-aasland c0878d9
Add support for 'mode' param in dbm.sqlite3.open()
erlend-aasland b7111ee
Pull in main
erlend-aasland 48972a6
Pull in main
erlend-aasland d6d7c66
Fix test_misuse_reinit()
erlend-aasland bc849c3
Merge branch 'main' into sqlite/dbm
erlend-aasland e782fad
Address Serhiy's offline remark: coerce keys/values to bytes
erlend-aasland b1b9a9b
Align docs to e782fad38f
erlend-aasland 34930cb
Compat with other backends: silently coerce keys to bytes
erlend-aasland File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import os | ||
import sqlite3 | ||
import sys | ||
from pathlib import Path | ||
from contextlib import suppress, closing | ||
from collections.abc import MutableMapping | ||
|
||
BUILD_TABLE = """ | ||
CREATE TABLE IF NOT EXISTS Dict ( | ||
key BLOB UNIQUE NOT NULL, | ||
value BLOB NOT NULL | ||
) | ||
""" | ||
GET_SIZE = "SELECT COUNT (key) FROM Dict" | ||
LOOKUP_KEY = "SELECT value FROM Dict WHERE key = CAST(? AS BLOB)" | ||
STORE_KV = "REPLACE INTO Dict (key, value) VALUES (CAST(? AS BLOB), CAST(? AS BLOB))" | ||
DELETE_KEY = "DELETE FROM Dict WHERE key = CAST(? AS BLOB)" | ||
ITER_KEYS = "SELECT key FROM Dict" | ||
|
||
|
||
class error(OSError): | ||
pass | ||
|
||
|
||
_ERR_CLOSED = "DBM object has already been closed" | ||
_ERR_REINIT = "DBM object does not support reinitialization" | ||
|
||
|
||
def _normalize_uri(path): | ||
path = Path(path) | ||
uri = path.absolute().as_uri() | ||
while "//" in uri: | ||
uri = uri.replace("//", "/") | ||
erlend-aasland marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return uri | ||
|
||
|
||
class _Database(MutableMapping): | ||
|
||
def __init__(self, path, /, *, flag, mode): | ||
if hasattr(self, "_cx"): | ||
raise error(_ERR_REINIT) | ||
|
||
path = os.fsdecode(path) | ||
match flag: | ||
case "r": | ||
flag = "ro" | ||
case "w": | ||
flag = "rw" | ||
case "c": | ||
flag = "rwc" | ||
Path(path).touch(mode=mode, exist_ok=True) | ||
case "n": | ||
flag = "rwc" | ||
Path(path).unlink(missing_ok=True) | ||
Path(path).touch(mode=mode) | ||
case _: | ||
raise ValueError("Flag must be one of 'r', 'w', 'c', or 'n', " | ||
f"not {flag!r}") | ||
|
||
# We use the URI format when opening the database. | ||
uri = _normalize_uri(path) | ||
uri = f"{uri}?mode={flag}" | ||
|
||
try: | ||
self._cx = sqlite3.connect(uri, autocommit=True, uri=True) | ||
except sqlite3.Error as exc: | ||
raise error(str(exc)) | ||
|
||
# This is an optimization only; it's ok if it fails. | ||
with suppress(sqlite3.OperationalError): | ||
self._cx.execute("PRAGMA journal_mode = wal") | ||
|
||
if flag == "rwc": | ||
self._execute(BUILD_TABLE) | ||
|
||
def _execute(self, *args, **kwargs): | ||
if not self._cx: | ||
raise error(_ERR_CLOSED) | ||
try: | ||
return closing(self._cx.execute(*args, **kwargs)) | ||
except sqlite3.Error as exc: | ||
raise error(str(exc)) | ||
|
||
def __len__(self): | ||
with self._execute(GET_SIZE) as cu: | ||
row = cu.fetchone() | ||
return row[0] | ||
|
||
def __getitem__(self, key): | ||
with self._execute(LOOKUP_KEY, (key,)) as cu: | ||
row = cu.fetchone() | ||
if not row: | ||
raise KeyError(key) | ||
return row[0] | ||
|
||
def __setitem__(self, key, value): | ||
self._execute(STORE_KV, (key, value)) | ||
|
||
def __delitem__(self, key): | ||
with self._execute(DELETE_KEY, (key,)) as cu: | ||
if not cu.rowcount: | ||
raise KeyError(key) | ||
|
||
def __iter__(self): | ||
try: | ||
with self._execute(ITER_KEYS) as cu: | ||
for row in cu: | ||
yield row[0] | ||
except sqlite3.Error as exc: | ||
raise error(str(exc)) | ||
|
||
def close(self): | ||
if self._cx: | ||
self._cx.close() | ||
self._cx = None | ||
|
||
def keys(self): | ||
return list(super().keys()) | ||
|
||
def __enter__(self): | ||
return self | ||
|
||
def __exit__(self, *args): | ||
self.close() | ||
|
||
|
||
def open(filename, /, flag="r", mode=0o666): | ||
"""Open a dbm.sqlite3 database and return the dbm object. | ||
|
||
The 'filename' parameter is the name of the database file. | ||
|
||
The optional 'flag' parameter can be one of ...: | ||
'r' (default): open an existing database for read only access | ||
'w': open an existing database for read/write access | ||
'c': create a database if it does not exist; open for read/write access | ||
'n': always create a new, empty database; open for read/write access | ||
|
||
The optional 'mode' parameter is the Unix file access mode of the database; | ||
only used when creating a new database. Default: 0o666. | ||
""" | ||
return _Database(filename, flag=flag, mode=mode) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.