sqlite3_hat 0.1__tar.gz → 0.2__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.
- {sqlite3_hat-0.1 → sqlite3_hat-0.2}/PKG-INFO +9 -3
- sqlite3_hat-0.2/README.rst +21 -0
- sqlite3_hat-0.2/docs/Makefile +20 -0
- sqlite3_hat-0.2/docs/api.rst +74 -0
- sqlite3_hat-0.2/docs/conf.py +34 -0
- sqlite3_hat-0.1/README.rst → sqlite3_hat-0.2/docs/index.rst +20 -2
- sqlite3_hat-0.2/docs/make.bat +35 -0
- sqlite3_hat-0.2/docs/mapping.rst +112 -0
- sqlite3_hat-0.2/docs/tutorial.rst +278 -0
- sqlite3_hat-0.2/docs/vs-stdlib.rst +200 -0
- {sqlite3_hat-0.1 → sqlite3_hat-0.2}/pyproject.toml +4 -0
- sqlite3_hat-0.1/sqlite3_hat.py → sqlite3_hat-0.2/sqlite3_hat/__init__.py +152 -3
- sqlite3_hat-0.2/sqlite3_hat/mapping.py +185 -0
- sqlite3_hat-0.2/test_sqlite3_hat.py +385 -0
- {sqlite3_hat-0.1 → sqlite3_hat-0.2}/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sqlite3_hat
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2
|
|
4
4
|
Summary: Lightweight wrapper with improvements on sqlite3
|
|
5
5
|
Author-email: Thomas Kluyver <takluyver@fastmail.com>
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -17,13 +17,19 @@ module from the standard library.
|
|
|
17
17
|
``sqlite3_hat`` adds (or changes):
|
|
18
18
|
|
|
19
19
|
- Nested transaction support, using SQLite savepoints
|
|
20
|
-
- Autocommit is on (see
|
|
20
|
+
- Autocommit is on (see the `docs on autocommit
|
|
21
|
+
<https://sqlite3-hat.readthedocs.io/en/latest/vs-stdlib.html#autocommit>`_ for
|
|
22
|
+
the rationale)
|
|
21
23
|
- Foreign key enforcement is on
|
|
22
24
|
- Non-standard double-quoted string syntax is disabled by default
|
|
23
25
|
- Convenient way to open a database read-only
|
|
24
26
|
- Parameters can be supplied in t-strings, or as ``*args`` rather than tuples
|
|
25
27
|
- Shortcut ``.queryone()`` method for getting a single row
|
|
28
|
+
- ``Table`` objects wrapping database tables, and a `dict-style
|
|
29
|
+
interface <https://sqlite3-hat.readthedocs.io/en/latest/mapping.html>`_
|
|
30
|
+
to access a table by a unique key.
|
|
26
31
|
|
|
27
32
|
The API is very similar to ``sqlite3``, but it's not a complete drop-in
|
|
28
|
-
replacement.
|
|
33
|
+
replacement. The `porting guide <https://sqlite3-hat.readthedocs.io/en/latest/vs-stdlib.html#porting>`_
|
|
34
|
+
describes a strategy to adopt it in existing code.
|
|
29
35
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
``sqlite3_hat`` is a small, pure Python wrapper to improve the ``sqlite3``
|
|
2
|
+
module from the standard library.
|
|
3
|
+
|
|
4
|
+
``sqlite3_hat`` adds (or changes):
|
|
5
|
+
|
|
6
|
+
- Nested transaction support, using SQLite savepoints
|
|
7
|
+
- Autocommit is on (see the `docs on autocommit
|
|
8
|
+
<https://sqlite3-hat.readthedocs.io/en/latest/vs-stdlib.html#autocommit>`_ for
|
|
9
|
+
the rationale)
|
|
10
|
+
- Foreign key enforcement is on
|
|
11
|
+
- Non-standard double-quoted string syntax is disabled by default
|
|
12
|
+
- Convenient way to open a database read-only
|
|
13
|
+
- Parameters can be supplied in t-strings, or as ``*args`` rather than tuples
|
|
14
|
+
- Shortcut ``.queryone()`` method for getting a single row
|
|
15
|
+
- ``Table`` objects wrapping database tables, and a `dict-style
|
|
16
|
+
interface <https://sqlite3-hat.readthedocs.io/en/latest/mapping.html>`_
|
|
17
|
+
to access a table by a unique key.
|
|
18
|
+
|
|
19
|
+
The API is very similar to ``sqlite3``, but it's not a complete drop-in
|
|
20
|
+
replacement. The `porting guide <https://sqlite3-hat.readthedocs.io/en/latest/vs-stdlib.html#porting>`_
|
|
21
|
+
describes a strategy to adopt it in existing code.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Minimal makefile for Sphinx documentation
|
|
2
|
+
#
|
|
3
|
+
|
|
4
|
+
# You can set these variables from the command line, and also
|
|
5
|
+
# from the environment for the first two.
|
|
6
|
+
SPHINXOPTS ?=
|
|
7
|
+
SPHINXBUILD ?= sphinx-build
|
|
8
|
+
SOURCEDIR = .
|
|
9
|
+
BUILDDIR = _build
|
|
10
|
+
|
|
11
|
+
# Put it first so that "make" without argument is like "make help".
|
|
12
|
+
help:
|
|
13
|
+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
14
|
+
|
|
15
|
+
.PHONY: help Makefile
|
|
16
|
+
|
|
17
|
+
# Catch-all target: route all unknown targets to Sphinx using the new
|
|
18
|
+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
|
19
|
+
%: Makefile
|
|
20
|
+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
API Reference
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
.. currentmodule:: sqlite3_hat
|
|
5
|
+
|
|
6
|
+
The API is similar to :mod:`sqlite3`.
|
|
7
|
+
|
|
8
|
+
.. autofunction:: connect
|
|
9
|
+
|
|
10
|
+
.. autofunction:: open
|
|
11
|
+
|
|
12
|
+
Usage::
|
|
13
|
+
|
|
14
|
+
with sqlite3_hat.open("foo.db") as conn:
|
|
15
|
+
conn.execute(...)
|
|
16
|
+
|
|
17
|
+
.. class:: Connection
|
|
18
|
+
|
|
19
|
+
The primary interface to an open SQLite database.
|
|
20
|
+
Use :func:`connect` or :func:`open` to make a Connection object.
|
|
21
|
+
|
|
22
|
+
.. automethod:: execute
|
|
23
|
+
.. automethod:: executescript
|
|
24
|
+
.. automethod:: executemany
|
|
25
|
+
.. automethod:: queryone
|
|
26
|
+
|
|
27
|
+
.. automethod:: savepoint
|
|
28
|
+
|
|
29
|
+
.. seealso::
|
|
30
|
+
`Savepoints in SQLite docs <https://sqlite.org/lang_savepoint.html>`_
|
|
31
|
+
|
|
32
|
+
.. automethod:: transaction
|
|
33
|
+
|
|
34
|
+
.. seealso::
|
|
35
|
+
`Transactions in SQLite docs <https://sqlite.org/lang_transaction.html>`_
|
|
36
|
+
|
|
37
|
+
.. automethod:: commit
|
|
38
|
+
.. automethod:: rollback
|
|
39
|
+
|
|
40
|
+
.. automethod:: table
|
|
41
|
+
|
|
42
|
+
.. class:: Table
|
|
43
|
+
|
|
44
|
+
A wrapper for a database table. Call :meth:`Connection.table` to make a
|
|
45
|
+
Table object.
|
|
46
|
+
|
|
47
|
+
.. attribute:: columns
|
|
48
|
+
|
|
49
|
+
A tuple of selected column names. Initially this is all columns except for
|
|
50
|
+
hidden columns in virtual tables - the same columns you'd get with
|
|
51
|
+
``SELECT *``. Use :meth:`select_columns` to get a different selection.
|
|
52
|
+
|
|
53
|
+
.. automethod:: select_columns
|
|
54
|
+
.. automethod:: nrows
|
|
55
|
+
.. automethod:: read_cursor
|
|
56
|
+
.. automethod:: insert
|
|
57
|
+
.. automethod:: insertmany
|
|
58
|
+
.. automethod:: mapping
|
|
59
|
+
|
|
60
|
+
Other functions
|
|
61
|
+
---------------
|
|
62
|
+
|
|
63
|
+
.. autofunction:: quote_name
|
|
64
|
+
|
|
65
|
+
Exceptions
|
|
66
|
+
----------
|
|
67
|
+
|
|
68
|
+
:exc:`sqlite3.Error` and its subclasses can all be raised by this module.
|
|
69
|
+
|
|
70
|
+
.. autoexception:: NoResultsError
|
|
71
|
+
|
|
72
|
+
.. autoexception:: TableNameError
|
|
73
|
+
|
|
74
|
+
.. autoexception:: ColumnNameError
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Configuration file for the Sphinx documentation builder.
|
|
2
|
+
#
|
|
3
|
+
# For the full list of built-in configuration values, see the documentation:
|
|
4
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
|
5
|
+
|
|
6
|
+
# -- Project information -----------------------------------------------------
|
|
7
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
|
8
|
+
|
|
9
|
+
project = 'sqlite3_hat'
|
|
10
|
+
copyright = '2026, Thomas Kluyver'
|
|
11
|
+
author = 'Thomas Kluyver'
|
|
12
|
+
|
|
13
|
+
# -- General configuration ---------------------------------------------------
|
|
14
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
|
15
|
+
|
|
16
|
+
import os, sys
|
|
17
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
|
18
|
+
|
|
19
|
+
extensions = [
|
|
20
|
+
"sphinx.ext.doctest",
|
|
21
|
+
"sphinx.ext.autodoc",
|
|
22
|
+
"sphinx.ext.intersphinx",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
templates_path = ['_templates']
|
|
26
|
+
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
|
27
|
+
|
|
28
|
+
intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}
|
|
29
|
+
|
|
30
|
+
# -- Options for HTML output -------------------------------------------------
|
|
31
|
+
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
|
32
|
+
|
|
33
|
+
html_theme = 'alabaster'
|
|
34
|
+
html_static_path = ['_static']
|
|
@@ -1,15 +1,33 @@
|
|
|
1
|
+
sqlite3_hat
|
|
2
|
+
============
|
|
3
|
+
|
|
1
4
|
``sqlite3_hat`` is a small, pure Python wrapper to improve the ``sqlite3``
|
|
2
5
|
module from the standard library.
|
|
3
6
|
|
|
4
7
|
``sqlite3_hat`` adds (or changes):
|
|
5
8
|
|
|
6
9
|
- Nested transaction support, using SQLite savepoints
|
|
7
|
-
- Autocommit is on (see
|
|
10
|
+
- Autocommit is on (see :ref:`autocommit` for rationale)
|
|
8
11
|
- Foreign key enforcement is on
|
|
9
12
|
- Non-standard double-quoted string syntax is disabled by default
|
|
10
13
|
- Convenient way to open a database read-only
|
|
11
14
|
- Parameters can be supplied in t-strings, or as ``*args`` rather than tuples
|
|
12
15
|
- Shortcut ``.queryone()`` method for getting a single row
|
|
16
|
+
- :class:`~.Table` objects wrapping database tables, and a :doc:`dict-style
|
|
17
|
+
interface <mapping>` to access a table by a unique key.
|
|
18
|
+
|
|
19
|
+
Install it with::
|
|
20
|
+
|
|
21
|
+
pip install sqlite3_hat
|
|
13
22
|
|
|
14
23
|
The API is very similar to ``sqlite3``, but it's not a complete drop-in
|
|
15
|
-
replacement.
|
|
24
|
+
replacement. See :ref:`porting` for how to adopt it in existing code.
|
|
25
|
+
|
|
26
|
+
.. toctree::
|
|
27
|
+
:maxdepth: 2
|
|
28
|
+
:caption: Contents:
|
|
29
|
+
|
|
30
|
+
tutorial.rst
|
|
31
|
+
mapping.rst
|
|
32
|
+
vs-stdlib.rst
|
|
33
|
+
api.rst
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
@ECHO OFF
|
|
2
|
+
|
|
3
|
+
pushd %~dp0
|
|
4
|
+
|
|
5
|
+
REM Command file for Sphinx documentation
|
|
6
|
+
|
|
7
|
+
if "%SPHINXBUILD%" == "" (
|
|
8
|
+
set SPHINXBUILD=sphinx-build
|
|
9
|
+
)
|
|
10
|
+
set SOURCEDIR=.
|
|
11
|
+
set BUILDDIR=_build
|
|
12
|
+
|
|
13
|
+
%SPHINXBUILD% >NUL 2>NUL
|
|
14
|
+
if errorlevel 9009 (
|
|
15
|
+
echo.
|
|
16
|
+
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
|
17
|
+
echo.installed, then set the SPHINXBUILD environment variable to point
|
|
18
|
+
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
|
19
|
+
echo.may add the Sphinx directory to PATH.
|
|
20
|
+
echo.
|
|
21
|
+
echo.If you don't have Sphinx installed, grab it from
|
|
22
|
+
echo.https://www.sphinx-doc.org/
|
|
23
|
+
exit /b 1
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if "%1" == "" goto help
|
|
27
|
+
|
|
28
|
+
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
|
29
|
+
goto end
|
|
30
|
+
|
|
31
|
+
:help
|
|
32
|
+
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
|
33
|
+
|
|
34
|
+
:end
|
|
35
|
+
popd
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Mapping interface
|
|
2
|
+
=================
|
|
3
|
+
|
|
4
|
+
A lot of simple data access is based on unique IDs. The mapping interface
|
|
5
|
+
lets you use a database table like a dictionary:
|
|
6
|
+
|
|
7
|
+
.. testsetup:: mapping
|
|
8
|
+
|
|
9
|
+
import sqlite3_hat
|
|
10
|
+
conn = sqlite3_hat.connect(":memory:")
|
|
11
|
+
|
|
12
|
+
.. testcode:: mapping
|
|
13
|
+
|
|
14
|
+
conn.execute("""CREATE TABLE settings(
|
|
15
|
+
name TEXT PRIMARY KEY NOT NULL,
|
|
16
|
+
value,
|
|
17
|
+
user_set INTEGER DEFAULT 0
|
|
18
|
+
)""")
|
|
19
|
+
settings = conn.table("settings").mapping()
|
|
20
|
+
|
|
21
|
+
# name : (value, user_set)
|
|
22
|
+
settings["theme"] = ("light", 0)
|
|
23
|
+
settings.update(view_width=(800, 0), view_height=(600, 0))
|
|
24
|
+
|
|
25
|
+
if settings["theme"][0] == "light":
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
.. testcode:: mapping
|
|
29
|
+
:hide:
|
|
30
|
+
|
|
31
|
+
assert settings["theme"] == ("light", 0)
|
|
32
|
+
assert settings["view_width"][0] == 800
|
|
33
|
+
|
|
34
|
+
You can use almost all dictionary methods; more precisely, the mapping object
|
|
35
|
+
implements the :class:`~collections.abc.MutableMapping` interface.
|
|
36
|
+
|
|
37
|
+
By default, the keys are the table's primary key column (or columns), and the
|
|
38
|
+
values are all the other columns. But you can select specific columns:
|
|
39
|
+
|
|
40
|
+
.. testcode:: mapping
|
|
41
|
+
|
|
42
|
+
setting_values = conn.table("settings").mapping(value="value")
|
|
43
|
+
|
|
44
|
+
setting_values["time_zone"] = "UTC"
|
|
45
|
+
|
|
46
|
+
if setting_values.get("theme", "light") == "dark":
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
.. note::
|
|
50
|
+
|
|
51
|
+
Selecting columns for values, as shown here, is mostly simple.
|
|
52
|
+
|
|
53
|
+
You can also select different columns for keys, but the selected column(s)
|
|
54
|
+
should have a UNIQUE constraint (PRIMARY KEY also implies uniqueness).
|
|
55
|
+
|
|
56
|
+
You should not use ``None`` in keys, because a column with a UNIQUE
|
|
57
|
+
constraint can still have multiple NULL values. If possible, use a NOT NULL
|
|
58
|
+
constraint on key columns to ensure no NULLs creep in.
|
|
59
|
+
|
|
60
|
+
Transactions
|
|
61
|
+
------------
|
|
62
|
+
|
|
63
|
+
Mapping objects work with :ref:`database transactions <tutorial-transactions>`.
|
|
64
|
+
If a transaction is open, any changes you make to the mapping are only saved
|
|
65
|
+
when the transaction is committed.
|
|
66
|
+
|
|
67
|
+
.. testcode:: mapping
|
|
68
|
+
|
|
69
|
+
with conn:
|
|
70
|
+
settings["theme"] = ("high-contrast", 1)
|
|
71
|
+
settings["font_size"] = ("14pt", 1)
|
|
72
|
+
|
|
73
|
+
# Leaving the 'with conn' block commits the changes
|
|
74
|
+
|
|
75
|
+
Each database method is atomic, and will act on a consistent view of the
|
|
76
|
+
database. For instance, ``settings.update()`` will either make all the given
|
|
77
|
+
updates, or none of them.
|
|
78
|
+
|
|
79
|
+
.. testcode:: mapping
|
|
80
|
+
|
|
81
|
+
# Don't do this:
|
|
82
|
+
if 'theme' in settings:
|
|
83
|
+
# between these steps, another process could delete the entry
|
|
84
|
+
del settings['theme']
|
|
85
|
+
|
|
86
|
+
# Do this instead:
|
|
87
|
+
settings.pop('theme', None)
|
|
88
|
+
|
|
89
|
+
Iterators
|
|
90
|
+
---------
|
|
91
|
+
|
|
92
|
+
Iterating over keys, values or items in the mapping is *lazy*, loading the data
|
|
93
|
+
as needed rather than reading it all up front. However, it means keeping a read
|
|
94
|
+
transaction open on the database, which may block writing until it finishes.
|
|
95
|
+
So if the code in the loop might be slow, it's a good idea to read all the
|
|
96
|
+
entries into a list before looping:
|
|
97
|
+
|
|
98
|
+
.. testsetup:: mapping
|
|
99
|
+
|
|
100
|
+
def user_confirm(prompt): return
|
|
101
|
+
def settings_input_generator(): return []
|
|
102
|
+
|
|
103
|
+
.. testcode:: mapping
|
|
104
|
+
|
|
105
|
+
# list() collects the data in memory & finishes reading
|
|
106
|
+
for k, v in list(setting_values.items()):
|
|
107
|
+
user_confirm(f"Setting {k} is set to {v} - OK?")
|
|
108
|
+
|
|
109
|
+
Writing data with ``mapping.update(slow_iterator)`` can be even worse, because
|
|
110
|
+
this keeps a write transaction open until the iterator finishes, which can block
|
|
111
|
+
both readers and writers. Try to either group changes in memory before calling
|
|
112
|
+
``.update()``, or split it up into smaller changes.
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
Getting started with sqlite3_hat
|
|
2
|
+
=================================
|
|
3
|
+
|
|
4
|
+
This guide is about writing new code. See :ref:`porting` to adapt existing code
|
|
5
|
+
using ``sqlite3``.
|
|
6
|
+
|
|
7
|
+
.. _tutorial-connect:
|
|
8
|
+
|
|
9
|
+
Opening a database
|
|
10
|
+
------------------
|
|
11
|
+
|
|
12
|
+
Everything you'll do with SQLite starts with opening a database file:
|
|
13
|
+
|
|
14
|
+
::
|
|
15
|
+
|
|
16
|
+
import sqlite3_hat
|
|
17
|
+
conn = sqlite3_hat.connect("library.sqlite")
|
|
18
|
+
|
|
19
|
+
The default will create this file if it doesn't exist. If you don't want this,
|
|
20
|
+
pass ``mode="ro"`` (read-only) or ``mode="rw"`` (read-write).
|
|
21
|
+
|
|
22
|
+
The special filename ``:memory:`` will create a transient in-memory database.
|
|
23
|
+
This is especially useful for writing tests.
|
|
24
|
+
|
|
25
|
+
.. _tutorial-schema:
|
|
26
|
+
|
|
27
|
+
Setting up tables and indexes
|
|
28
|
+
-----------------------------
|
|
29
|
+
|
|
30
|
+
For many database use cases, the next step is to define your *schema*, the
|
|
31
|
+
tables, indexes, etc. that structure your data. Of course, you can skip this
|
|
32
|
+
if you're opening a database created by other code.
|
|
33
|
+
|
|
34
|
+
.. testsetup:: tutorial
|
|
35
|
+
|
|
36
|
+
import sqlite3_hat
|
|
37
|
+
conn = sqlite3_hat.connect(":memory:")
|
|
38
|
+
|
|
39
|
+
.. testcode:: tutorial
|
|
40
|
+
|
|
41
|
+
conn.executescript("""
|
|
42
|
+
CREATE TABLE IF NOT EXISTS book(
|
|
43
|
+
id INTEGER PRIMARY KEY,
|
|
44
|
+
title TEXT,
|
|
45
|
+
author TEXT
|
|
46
|
+
);
|
|
47
|
+
CREATE TABLE IF NOT EXISTS borrower(
|
|
48
|
+
id INTEGER PRIMARY KEY,
|
|
49
|
+
name TEXT,
|
|
50
|
+
card_num TEXT UNIQUE
|
|
51
|
+
);
|
|
52
|
+
CREATE TABLE IF NOT EXISTS loan(
|
|
53
|
+
book INTEGER REFERENCES book(id) ON DELETE CASCADE,
|
|
54
|
+
-- SQL comments look like this!
|
|
55
|
+
-- RESTRICT forbids deleting a borrower with loans
|
|
56
|
+
borrower INTEGER REFERENCES borrower(id) ON DELETE RESTRICT,
|
|
57
|
+
due TEXT -- ISO format: YYYY-MM-DD
|
|
58
|
+
);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS loan_by_book ON loan(book);
|
|
60
|
+
CREATE INDEX IF NOT EXISTS loan_by_borrower ON loan(borrower);
|
|
61
|
+
""")
|
|
62
|
+
|
|
63
|
+
Looking up data in a table with many rows is much faster if there's an index
|
|
64
|
+
on the field being used. PRIMARY KEY and UNIQUE columns get indexes
|
|
65
|
+
automatically, and you can choose to create indexes for other columns, as shown
|
|
66
|
+
above.
|
|
67
|
+
|
|
68
|
+
Designing a good schema for your data is a big topic, which I won't attempt to
|
|
69
|
+
cover here. You may well also need to think about *migrations*, how to modify
|
|
70
|
+
the structure of an existing database as your code evolves.
|
|
71
|
+
|
|
72
|
+
.. note::
|
|
73
|
+
|
|
74
|
+
The examples here use capitals for SQL keywords like ``CREATE``, and
|
|
75
|
+
lowercase for names like ``book``. This is a common convention, but it's
|
|
76
|
+
not required.
|
|
77
|
+
|
|
78
|
+
The SQL commands for creating objects are:
|
|
79
|
+
|
|
80
|
+
- `CREATE TABLE <https://sqlite.org/lang_createtable.html>`_
|
|
81
|
+
- `CREATE INDEX <https://sqlite.org/lang_createindex.html>`_
|
|
82
|
+
- `CREATE VIRTUAL TABLE <https://sqlite.org/lang_createvtab.html>`_ (used with extensions like full text search)
|
|
83
|
+
- `CREATE VIEW <https://sqlite.org/lang_createview.html>`_ (views are like tables generated from SQL queries)
|
|
84
|
+
- `CREATE TRIGGER <https://sqlite.org/lang_createtrigger.html>`_ (triggers can do things when data is modified)
|
|
85
|
+
|
|
86
|
+
.. _tutorial-write:
|
|
87
|
+
|
|
88
|
+
Writing data
|
|
89
|
+
------------
|
|
90
|
+
|
|
91
|
+
The key SQL statements for writing are `INSERT <https://sqlite.org/lang_insert.html>`_
|
|
92
|
+
and `UPDATE <https://sqlite.org/lang_update.html>`_:
|
|
93
|
+
|
|
94
|
+
.. testcode:: tutorial
|
|
95
|
+
|
|
96
|
+
cur = conn.execute(
|
|
97
|
+
"INSERT INTO borrower(name, card_num) VALUES (?, ?)",
|
|
98
|
+
"Thomas Klyuver", "D98765"
|
|
99
|
+
)
|
|
100
|
+
borrower_id = cur.lastrowid
|
|
101
|
+
|
|
102
|
+
# Fix a typo
|
|
103
|
+
conn.execute(
|
|
104
|
+
"UPDATE borrower SET name=? WHERE id=?", "Thomas Kluyver", borrower_id
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
The ``cursor.lastrowid`` attribute gives you the *row ID* after a successful
|
|
108
|
+
insert. Because we defined the ``id`` column in the table as
|
|
109
|
+
`INTEGER PRIMARY KEY <https://sqlite.org/lang_createtable.html#rowids_and_the_integer_primary_key>`_,
|
|
110
|
+
the row ID is also the value in this column.
|
|
111
|
+
|
|
112
|
+
It's often useful to use the ``executemany()`` method when writing to make
|
|
113
|
+
a batch of changes efficiently:
|
|
114
|
+
|
|
115
|
+
.. testcode:: tutorial
|
|
116
|
+
|
|
117
|
+
conn.executemany("INSERT INTO book(title, author) VALUES (?, ?)", [
|
|
118
|
+
("Northern Lights", "Philip Pullman"),
|
|
119
|
+
("Men at Arms", "Terry Pratchett"),
|
|
120
|
+
])
|
|
121
|
+
|
|
122
|
+
Finally, you can also `DELETE <https://sqlite.org/lang_delete.html>`_ rows::
|
|
123
|
+
|
|
124
|
+
conn.execute("DELETE FROM borrowers WHERE id=?", borrower_id)
|
|
125
|
+
|
|
126
|
+
.. _tutorial-params:
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
|
|
131
|
+
Use placeholders, like the ``?`` shown above, and parameters to combine data with
|
|
132
|
+
the SQL code. There are two styles for this:
|
|
133
|
+
|
|
134
|
+
.. testcode:: tutorial
|
|
135
|
+
|
|
136
|
+
# Parameters by order
|
|
137
|
+
conn.execute("INSERT INTO book(title, author) VALUES (?, ?)",
|
|
138
|
+
"4.50 from Paddington", "Agatha Christie")
|
|
139
|
+
|
|
140
|
+
# Parameters by name
|
|
141
|
+
conn.execute("INSERT INTO book(title, author) VALUES (:title, :author)",
|
|
142
|
+
{"title": "Death in the Clouds", "author": "Agatha Christie"})
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
**Don't** be tempted to build a string of SQL including the data. That is how
|
|
146
|
+
you get *SQL injection* vulnerabilities, a serious and sadly common kind of
|
|
147
|
+
security issue. Even if you don't think this could matter in your context, get
|
|
148
|
+
into the habit of using placeholders by default, and learn to see it as a code
|
|
149
|
+
smell if SQL is built with string formatting.
|
|
150
|
+
|
|
151
|
+
Using placeholders can also be more efficient: as the actual SQL is not
|
|
152
|
+
changing, SQLite can cache and reuse the parsed statement. In a simple test
|
|
153
|
+
storing 1000 numbers, using placeholders was twice as fast as formatting the
|
|
154
|
+
numbers into the SQL.
|
|
155
|
+
|
|
156
|
+
Template strings
|
|
157
|
+
~~~~~~~~~~~~~~~~
|
|
158
|
+
|
|
159
|
+
You can also use template strings to put values into SQL safely on Python 3.14
|
|
160
|
+
or above:
|
|
161
|
+
|
|
162
|
+
.. testcode:: tutorial
|
|
163
|
+
|
|
164
|
+
author = "J.R.R. Tolkein"
|
|
165
|
+
conn.execute(
|
|
166
|
+
t"INSERT INTO book(title, author) VALUES ({'The Hobbit'}, {author})"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
This uses the same placeholder & parameter mechanism to combine the data with
|
|
170
|
+
the SQL. So it's safe against SQL injection, but can only be used in the same
|
|
171
|
+
places that parameters work; e.g. you can't use it to fill in a table or column
|
|
172
|
+
name.
|
|
173
|
+
|
|
174
|
+
You can also use template strings with ``conn.executemany()`` with this
|
|
175
|
+
comprehension style:
|
|
176
|
+
|
|
177
|
+
.. testcode:: tutorial
|
|
178
|
+
|
|
179
|
+
lotr_books = [
|
|
180
|
+
"The Fellowship of the Ring",
|
|
181
|
+
"The Two Towers",
|
|
182
|
+
"The Return of the King",
|
|
183
|
+
]
|
|
184
|
+
conn.executemany([
|
|
185
|
+
t"INSERT INTO book(title, author) VALUES ({title}, {author})"
|
|
186
|
+
for title in lotr_books
|
|
187
|
+
])
|
|
188
|
+
|
|
189
|
+
Only the parameter parts can vary within an ``executemany()`` call - the SQL
|
|
190
|
+
template must be the same in all entries.
|
|
191
|
+
|
|
192
|
+
.. _tutorial-transactions:
|
|
193
|
+
|
|
194
|
+
Transactions
|
|
195
|
+
------------
|
|
196
|
+
|
|
197
|
+
The modifications above will all take effect as soon as they finish.
|
|
198
|
+
But you can also wrap a group of changes in a transaction. This achieves two
|
|
199
|
+
things:
|
|
200
|
+
|
|
201
|
+
1. If there's an error during a transaction, it can be rolled back. The changes
|
|
202
|
+
within the transaction are either all saved, or none of them are (the
|
|
203
|
+
transaction is *atomic*).
|
|
204
|
+
2. Making multiple changes within a transaction is more efficient than letting
|
|
205
|
+
SQLite commit each change separately.
|
|
206
|
+
|
|
207
|
+
For our library example, let's imagine members of another library are allowed to
|
|
208
|
+
borrow books, and we've decided to create a temporary borrower in our database
|
|
209
|
+
to track each outside loan. When the book is returned, the temporary borrower
|
|
210
|
+
should be deleted again.
|
|
211
|
+
|
|
212
|
+
Transactions can be made using the connection object as a context manager:
|
|
213
|
+
|
|
214
|
+
.. testcode:: tutorial
|
|
215
|
+
:hide:
|
|
216
|
+
|
|
217
|
+
def is_temp(borrower_id): return True # To make the example below work
|
|
218
|
+
|
|
219
|
+
.. testcode:: tutorial
|
|
220
|
+
|
|
221
|
+
# Borrow
|
|
222
|
+
with conn:
|
|
223
|
+
cur = conn.execute("INSERT INTO borrower(name) VALUES (?)",
|
|
224
|
+
"Card #1234 from Othersville library",)
|
|
225
|
+
conn.execute("INSERT INTO loan(book, borrower) VALUES (?, ?)",
|
|
226
|
+
4, cur.lastrowid)
|
|
227
|
+
|
|
228
|
+
# Return
|
|
229
|
+
borrower_id, = conn.queryone(
|
|
230
|
+
"SELECT borrower FROM loan WHERE book=?", 4
|
|
231
|
+
)
|
|
232
|
+
with conn:
|
|
233
|
+
conn.execute("DELETE FROM loan WHERE book=?", 4)
|
|
234
|
+
if is_temp(borrower_id):
|
|
235
|
+
conn.execute(
|
|
236
|
+
"DELETE FROM borrower WHERE id=?", borrower_id,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
The transaction is committed at the end of the ``with`` block if there's no
|
|
240
|
+
exception, or rolled back if an exception is raised.
|
|
241
|
+
|
|
242
|
+
Transactions can be nested, and each level of transaction can be committed or
|
|
243
|
+
rolled back. Only when the outermost transaction is committed are any changes
|
|
244
|
+
visible to other connections. See `Savepoints in the SQLite docs
|
|
245
|
+
<https://sqlite.org/lang_savepoint.html>`_ for more about how nested
|
|
246
|
+
transactions work.
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
.. _tutorial-read:
|
|
250
|
+
|
|
251
|
+
Reading data
|
|
252
|
+
------------
|
|
253
|
+
|
|
254
|
+
The `SELECT <https://sqlite.org/lang_select.html>`_ statement is the workhorse
|
|
255
|
+
of getting data out of the database:
|
|
256
|
+
|
|
257
|
+
.. testcode:: tutorial
|
|
258
|
+
|
|
259
|
+
rows = conn.execute(
|
|
260
|
+
"SELECT title FROM book WHERE author=?", "Agatha Christie"
|
|
261
|
+
).fetchall()
|
|
262
|
+
for title, in rows:
|
|
263
|
+
print(title)
|
|
264
|
+
|
|
265
|
+
.. testoutput:: tutorial
|
|
266
|
+
|
|
267
|
+
4.50 from Paddington
|
|
268
|
+
Death in the Clouds
|
|
269
|
+
|
|
270
|
+
The ``queryone`` method is a shortcut for reading a single row, and will raise
|
|
271
|
+
a clear error if no match is returned:
|
|
272
|
+
|
|
273
|
+
.. testcode:: tutorial
|
|
274
|
+
|
|
275
|
+
num_books = conn.queryone("SELECT count(*) FROM book")[0]
|
|
276
|
+
borrower_id, name = conn.queryone(
|
|
277
|
+
"SELECT id, name FROM borrower WHERE card_num=?", "D98765"
|
|
278
|
+
)
|