Use :param x: for docstring comments, refs #413

This commit is contained in:
Simon Willison 2022-03-11 09:38:34 -08:00
commit 26e6d2622c
3 changed files with 327 additions and 129 deletions

View file

@ -32,6 +32,7 @@ from subprocess import Popen, PIPE
# ones.
extensions = ["sphinx.ext.extlinks", "sphinx.ext.autodoc"]
autodoc_member_order = "bysource"
autodoc_typehints = "description"
extlinks = {
"issue": ("https://github.com/simonw/sqlite-utils/issues/%s", "#"),

View file

@ -275,17 +275,17 @@ class Database:
# Create an in-memory database:
dB = Database(memory=True)
- ``filename_or_conn`` - String path to a file, or a ``pathlib.Path`` object, or a
:param filename_or_conn: String path to a file, or a ``pathlib.Path`` object, or a
``sqlite3`` connection
- ``memory`` - set to ``True`` to create an in-memory database
- ``memory_name`` - creates a named in-memory database that can be shared across multiple connections.
- ``recreate`` - set to ``True`` to delete and recreate a file database (**dangerous**)
- ``recursive_triggers`` - defaults to ``True``, which sets ``PRAGMA recursive_triggers=on;`` -
:param memory: set to ``True`` to create an in-memory database
:param memory_name: creates a named in-memory database that can be shared across multiple connections
:param recreate: set to ``True`` to delete and recreate a file database (**dangerous**)
:param recursive_triggers: defaults to ``True``, which sets ``PRAGMA recursive_triggers=on;`` -
set to ``False`` to avoid setting this pragma
- ``tracer`` - set a tracer function (``print`` works for this) which will be called with
:param tracer: set a tracer function (``print`` works for this) which will be called with
``sql, parameters`` every time a SQL query is executed
- ``use_counts_table`` - set to ``True`` to use a cached counts table, if available. See
:ref:`python_api_cached_table_counts`.
:param use_counts_table: set to ``True`` to use a cached counts table, if available. See
:ref:`python_api_cached_table_counts`
"""
_counts_table_name = "_counts"
@ -340,6 +340,8 @@ class Database:
db["creatures"].insert({"name": "Cleo"})
See :ref:`python_api_tracing`.
:param tracer: Callable accepting ``sql`` and ``parameters`` arguments
"""
prev_tracer = self._tracer
self._tracer = tracer or print
@ -352,6 +354,8 @@ class Database:
"""
``db[table_name]`` returns a :class:`.Table` object for the table with the specified name.
If the table does not exist yet it will be created the first time data is inserted into it.
:param table_name: The name of the table
"""
return self.table(table_name)
@ -375,10 +379,11 @@ class Database:
def upper(value):
return str(value).upper()
- ``deterministic`` - set ``True`` for functions that always returns the same output for a given input
- ``replace`` - set ``True`` to replace an existing function with the same name - otherwise throw an error
See :ref:`python_api_register_function`.
:param fn: Function to register
:param deterministic: set ``True`` for functions that always returns the same output for a given input
:param replace: set ``True`` to replace an existing function with the same name - otherwise throw an error
"""
def register(fn):
@ -411,6 +416,9 @@ class Database:
Attach another SQLite database file to this connection with the specified alias, equivalent to::
ATTACH DATABASE 'filepath.db' AS alias
:param alias: Alias name to use
:param filepath: Path to SQLite database file on disk
"""
attach_sql = """
ATTACH DATABASE '{}' AS [{}];
@ -422,7 +430,13 @@ class Database:
def query(
self, sql: str, params: Optional[Union[Iterable, dict]] = None
) -> Generator[dict, None, None]:
"Execute ``sql`` and return an iterable of dictionaries representing each row."
"""
Execute ``sql`` and return an iterable of dictionaries representing each row.
:param sql: SQL query to execute
:param params: Parameters to use in that query - an iterable for ``where id = ?``
parameters, or a dictionary for ``where id = :id``
"""
cursor = self.execute(sql, params or tuple())
keys = [d[0] for d in cursor.description]
for row in cursor:
@ -431,7 +445,13 @@ class Database:
def execute(
self, sql: str, parameters: Optional[Union[Iterable, dict]] = None
) -> sqlite3.Cursor:
"Execute SQL query and return a ``sqlite3.Cursor``."
"""
Execute SQL query and return a ``sqlite3.Cursor``.
:param sql: SQL query to execute
:param parameters: Parameters to use in that query - an iterable for ``where id = ?``
parameters, or a dictionary for ``where id = :id``
"""
if self._tracer:
self._tracer(sql, parameters)
if parameters is not None:
@ -440,18 +460,30 @@ class Database:
return self.conn.execute(sql)
def executescript(self, sql: str) -> sqlite3.Cursor:
"Execute multiple SQL statements separated by ; and return the ``sqlite3.Cursor``."
"""
Execute multiple SQL statements separated by ; and return the ``sqlite3.Cursor``.
:param sql: SQL to execute
"""
if self._tracer:
self._tracer(sql, None)
return self.conn.executescript(sql)
def table(self, table_name: str, **kwargs) -> Union["Table", "View"]:
"Return a table object, optionally configured with default options."
"""
Return a table object, optionally configured with default options.
:param table_name: Name of the table
"""
klass = View if table_name in self.view_names() else Table
return klass(self, table_name, **kwargs)
def quote(self, value: str) -> str:
"Apply SQLite string quoting to a value, including wrappping it in single quotes."
"""
Apply SQLite string quoting to a value, including wrappping it in single quotes.
:param value: String to quote
"""
# Normally we would use .execute(sql, [params]) for escaping, but
# occasionally that isn't available - most notable when we need
# to include a "... DEFAULT 'value'" in a column definition.
@ -462,16 +494,19 @@ class Database:
).fetchone()[0]
def quote_fts(self, query: str) -> str:
"Escape special characters in a SQLite full-text search query"
# NOTE: This is not a query validator for FTS. Sqlite has
# a well defined query syntax here:
# https://www2.sqlite.org/fts5.html#full_text_query_syntax
# but this function just aggressively quotes strings
# to ensure that they are valid. In particular, passing
# queries which make use of the query syntax will be incorrect,
# e.g 'NEAR(one, two, 3)'.
"""
Escape special characters in a SQLite full-text search query.
# If query has unbalanced ", add one at end
This works by surrounding each token within the query with double
quotes, in order to avoid words like ``NOT`` and ``OR`` having
special meaning as defined by the FTS query syntax here:
https://www.sqlite.org/fts5.html#full_text_query_syntax
If the query has unbalanced ``"`` characters, adds one at end.
:param query: String to escape
"""
if query.count('"') % 2:
query += '"'
bits = _quote_fts_re.split(query)
@ -481,7 +516,12 @@ class Database:
)
def table_names(self, fts4: bool = False, fts5: bool = False) -> List[str]:
"A list of string table names in this database."
"""
List of string table names in this database.
:param fts4: Only return tables that are part of FTS4 indexes
:param fts5: Only return tables that are part of FTS5 indexes
"""
where = ["type = 'table'"]
if fts4:
where.append("sql like '%USING FTS4%'")
@ -491,7 +531,7 @@ class Database:
return [r[0] for r in self.execute(sql).fetchall()]
def view_names(self) -> List[str]:
"A list of string view names in this database."
"List of string view names in this database."
return [
r[0]
for r in self.execute(
@ -501,17 +541,17 @@ class Database:
@property
def tables(self) -> List["Table"]:
"A list of Table objects in this database."
"List of Table objects in this database."
return cast(List["Table"], [self[name] for name in self.table_names()])
@property
def views(self) -> List["View"]:
"A list of View objects in this database."
"List of View objects in this database."
return cast(List["View"], [self[name] for name in self.view_names()])
@property
def triggers(self) -> List[Trigger]:
"A list of ``(name, table_name, sql)`` tuples representing triggers in this database."
"List of ``(name, table_name, sql)`` tuples representing triggers in this database."
return [
Trigger(*r)
for r in self.execute(
@ -526,7 +566,7 @@ class Database:
@property
def schema(self) -> str:
"SQL schema for this database"
"SQL schema for this database."
sqls = []
for row in self.execute(
"select sql from sqlite_master where sql is not null"
@ -553,22 +593,28 @@ class Database:
@property
def sqlite_version(self) -> Tuple[int, ...]:
"Version of SQLite, as a tuple of integers e.g. (3, 36, 0)"
"Version of SQLite, as a tuple of integers for example ``(3, 36, 0)``."
row = self.execute("select sqlite_version()").fetchall()[0]
return tuple(map(int, row[0].split(".")))
@property
def journal_mode(self) -> str:
"Current ``journal_mode`` of this database."
"""
Current ``journal_mode`` of this database.
https://www.sqlite.org/pragma.html#pragma_journal_mode
"""
return self.execute("PRAGMA journal_mode;").fetchone()[0]
def enable_wal(self):
"Set ``journal_mode`` to ``'wal'`` to enable Write-Ahead Log mode."
"""
Sets ``journal_mode`` to ``'wal'`` to enable Write-Ahead Log mode.
"""
if self.journal_mode != "wal":
self.execute("PRAGMA journal_mode=wal;")
def disable_wal(self):
"Set ``journal_mode`` back to ``'delete'`` to disable Write-Ahead Log mode."
"Sets ``journal_mode`` back to ``'delete'`` to disable Write-Ahead Log mode."
if self.journal_mode != "delete":
self.execute("PRAGMA journal_mode=delete;")
@ -594,6 +640,8 @@ class Database:
"""
Return ``{table_name: count}`` dictionary of cached counts for specified tables, or
all tables if ``tables`` not provided.
:param tables: Subset list of tables to return counts for.
"""
sql = "select [table], count from {}".format(self._counts_table_name)
if tables:
@ -680,7 +728,21 @@ class Database:
extracts: Optional[Union[Dict[str, str], List[str]]] = None,
if_not_exists: bool = False,
) -> str:
"Returns the SQL ``CREATE TABLE`` statement for creating the specified table."
"""
Returns the SQL ``CREATE TABLE`` statement for creating the specified table.
:param name: Name of table
:param columns: Dictionary mapping column names to their types, for example ``{"name": str, "age": int}``
:param pk: String name of column to use as a primary key, or a tuple of strings for a compound primary key covering multiple columns
:param foreign_keys: List of foreign key definitions for this table
:param column_order: List specifying which columns should come first
:param not_null: List of columns that should be created as ``NOT NULL``
:param defaults: Dictionary specifying default values for columns
:param hash_id: Name of column to be used as a primary key containing a hash of the other columns
:param hash_id_columns: List of columns to be used when calculating the hash ID for a row
:param extracts: List or dictionary of columns to be extracted during inserts, see :ref:`python_api_extracts`
:param if_not_exists: Use ``CREATE TABLE IF NOT EXISTS``
"""
if hash_id_columns and (hash_id is None):
hash_id = "id"
foreign_keys = self.resolve_foreign_keys(name, foreign_keys or [])
@ -800,6 +862,18 @@ class Database:
Create a table with the specified name and the specified ``{column_name: type}`` columns.
See :ref:`python_api_explicit_create`.
:param name: Name of table
:param columns: Dictionary mapping column names to their types, for example ``{"name": str, "age": int}``
:param pk: String name of column to use as a primary key, or a tuple of strings for a compound primary key covering multiple columns
:param foreign_keys: List of foreign key definitions for this table
:param column_order: List specifying which columns should come first
:param not_null: List of columns that should be created as ``NOT NULL``
:param defaults: Dictionary specifying default values for columns
:param hash_id: Name of column to be used as a primary key containing a hash of the other columns
:param hash_id_columns: List of columns to be used when calculating the hash ID for a row
:param extracts: List or dictionary of columns to be extracted during inserts, see :ref:`python_api_extracts`
:param if_not_exists: Use ``CREATE TABLE IF NOT EXISTS``
"""
sql = self.create_table_sql(
name=name,
@ -833,8 +907,10 @@ class Database:
"""
Create a new SQL view with the specified name - ``sql`` should start with ``SELECT ...``.
- ``ignore`` - set to ``True`` to do nothing if a view with this name already exists
- ``replace`` - set to ``True`` to replace the view if one with this name already exists
:param name: Name of the view
:param sql: SQL ``SELECT`` query to use for this view.
:param ignore: Set to ``True`` to do nothing if a view with this name already exists
:param replace: Set to ``True`` to replace the view if one with this name already exists
"""
assert not (
ignore and replace
@ -858,6 +934,9 @@ class Database:
Given two table names returns the name of tables that could define a
many-to-many relationship between those two tables, based on having
foreign keys to both of the provided tables.
:param table: Table name
:param other_table: Other table name
"""
candidates = []
tables = {table, other_table}
@ -872,8 +951,8 @@ class Database:
"""
See :ref:`python_api_add_foreign_keys`.
``foreign_keys`` should be a list of ``(table, column, other_table, other_column)``
tuples, see :ref:`python_api_add_foreign_keys`.
:param foreign_keys: A list of ``(table, column, other_table, other_column)``
tuples
"""
# foreign_keys is a list of explicit 4-tuples
assert all(
@ -957,7 +1036,11 @@ class Database:
self.execute("VACUUM;")
def analyze(self, name=None):
"Run ``ANALYZE`` against the entire database or a named table or index."
"""
Run ``ANALYZE`` against the entire database or a named table or index.
:param name: Run ``ANALYZE`` against this specific named table or index
"""
sql = "ANALYZE"
if name is not None:
sql += " [{}]".format(name)
@ -969,7 +1052,7 @@ class Database:
The ``path`` argument should be an absolute path to the compiled extension, which
can be found using ``find_spatialite``.
Returns true if SpatiaLite was successfully initialized.
Returns ``True`` if SpatiaLite was successfully initialized.
.. code-block:: python
@ -990,6 +1073,7 @@ class Database:
db = Database("mydb.db")
db.init_spatialite("./local/mod_spatialite.dylib")
:param path: Path to SpatiaLite module on disk
"""
if path is None:
path = find_spatialite()
@ -1018,7 +1102,13 @@ class Queryable:
where: str = None,
where_args: Optional[Union[Iterable, dict]] = None,
) -> int:
"Executes ``SELECT count(*) FROM table WHERE ...`` and returns a count."
"""
Executes ``SELECT count(*) FROM table WHERE ...`` and returns a count.
:param where: SQL where fragment to use, for example ``id > ?``
:param where_args: Parameters to use with that fragment - an iterable for ``id > ?``
parameters, or a dictionary for ``id > :id``
"""
sql = "select count(*) from [{}]".format(self.name)
if where is not None:
sql += " where " + where
@ -1050,14 +1140,15 @@ class Queryable:
"""
Iterate over every row in this table or view that matches the specified where clause.
- ``where`` - a SQL fragment to use as a ``WHERE`` clause, for example ``age > ?`` or ``age > :age``.
- ``where_args`` - a list of arguments (if using ``?``) or a dictionary (if using ``:age``).
- ``order_by`` - optional column or fragment of SQL to order by.
- ``select`` - optional comma-separated list of columns to select.
- ``limit`` - optional integer number of rows to limit to.
- ``offset`` - optional integer for SQL offset.
Returns each row as a dictionary. See :ref:`python_api_rows` for more details.
:param where: SQL where fragment to use, for example ``id > ?``
:param where_args: Parameters to use with that fragment - an iterable for ``id > ?``
parameters, or a dictionary for ``id > :id``
:param order_by: Column or fragment of SQL to order by
:param select: Comma-separated list of columns to select - defaults to ``*``
:param limit: Integer number of rows to limit to
:param offset: Integer for SQL offset
"""
if not self.exists():
return
@ -1083,7 +1174,17 @@ class Queryable:
limit: int = None,
offset: int = None,
) -> Generator[Tuple[Any, Dict], None, None]:
"Like ``.rows_where()`` but returns ``(pk, row)`` pairs - ``pk`` can be a single value or tuple."
"""
Like ``.rows_where()`` but returns ``(pk, row)`` pairs - ``pk`` can be a single value or tuple.
:param where: SQL where fragment to use, for example ``id > ?``
:param where_args: Parameters to use with that fragment - an iterable for ``id > ?``
parameters, or a dictionary for ``id > :id``
:param order_by: Column or fragment of SQL to order by
:param select: Comma-separated list of columns to select - defaults to ``*``
:param limit: Integer number of rows to limit to
:param offset: Integer for SQL offset
"""
column_names = [column.name for column in self.columns]
pks = [column.name for column in self.columns if column.is_pk]
if not pks:
@ -1205,9 +1306,9 @@ class Table(Queryable):
"""
Return row (as dictionary) for the specified primary key.
Primary key can be a single value, or a tuple for tables with a compound primary key.
Raises ``sqlite_utils.db.NotFoundError`` if a matching row cannot be found.
Raises ``NotFoundError`` if a matching row cannot be found.
:param pk_values: A single value, or a tuple of values for tables that have a compound primary key
"""
if not isinstance(pk_values, (list, tuple)):
pk_values = [pk_values]
@ -1340,6 +1441,17 @@ class Table(Queryable):
Create a table with the specified columns.
See :ref:`python_api_explicit_create` for full details.
:param columns: Dictionary mapping column names to their types, for example ``{"name": str, "age": int}``
:param pk: String name of column to use as a primary key, or a tuple of strings for a compound primary key covering multiple columns
:param foreign_keys: List of foreign key definitions for this table
:param column_order: List specifying which columns should come first
:param not_null: List of columns that should be created as ``NOT NULL``
:param defaults: Dictionary specifying default values for columns
:param hash_id: Name of column to be used as a primary key containing a hash of the other columns
:param hash_id_columns: List of columns to be used when calculating the hash ID for a row
:param extracts: List or dictionary of columns to be extracted during inserts, see :ref:`python_api_extracts`
:param if_not_exists: Use ``CREATE TABLE IF NOT EXISTS``
"""
columns = {name: value for (name, value) in columns.items()}
with self.db.conn:
@ -1361,20 +1473,30 @@ class Table(Queryable):
def transform(
self,
*,
types=None,
rename=None,
drop=None,
pk=DEFAULT,
not_null=None,
defaults=None,
drop_foreign_keys=None,
column_order=None,
types: Optional[dict] = None,
rename: Optional[dict] = None,
drop: Optional[Iterable] = None,
pk: Optional[Any] = None,
not_null: Optional[Set[str]] = None,
defaults: Optional[Dict[str, Any]] = None,
drop_foreign_keys: Optional[Iterable] = None,
column_order: Optional[List[str]] = None,
) -> "Table":
"""
Apply an advanced alter table, including operations that are not supported by
``ALTER TABLE`` in SQLite itself.
See :ref:`python_api_transform` for full details.
:param types: Columns that should have their type changed, for example ``{"weight": float}``
:param rename: Columns to rename, for example ``{"headline": "title"}``
:param drop: Columns to drop
:param pk: New primary key for the table
:param not_null: Columns to set as ``NOT NULL``
:param defaults: Default values for columns
:param drop_foreign_keys: Names of columns that should have their foreign key constraints removed
:param column_order: List of strings specifying a full or partial column order
to use when creating the table.
"""
assert self.exists(), "Cannot transform a table that doesn't exist yet"
sqls = self.transform_sql(
@ -1417,7 +1539,19 @@ class Table(Queryable):
column_order=None,
tmp_suffix=None,
) -> List[str]:
"Returns a list of SQL statements that would be executed in order to apply this transformation."
"""
Return a list of SQL statements that should be executed in order to apply this transformation.
:param types: Columns that should have their type changed, for example ``{"weight": float}``
:param rename: Columns to rename, for example ``{"headline": "title"}``
:param drop: Columns to drop
:param pk: New primary key for the table
:param not_null: Columns to set as ``NOT NULL``
:param defaults: Default values for columns
:param drop_foreign_keys: Names of columns that should have their foreign key constraints removed
:param column_order: List of strings specifying a full or partial column order
to use when creating the table.
"""
types = types or {}
rename = rename or {}
drop = drop or set()
@ -1534,6 +1668,11 @@ class Table(Queryable):
Extract specified columns into a separate table.
See :ref:`python_api_extract` for details.
:param columns: Single column or list of columns that should be extracted
:param table: Name of table in which the new records should be created
:param fk_column: Name of the foreign key column to populate in the original table
:param rename: Dictionary of columns that should be renamed when populating the new table
"""
rename = rename or {}
if isinstance(columns, str):
@ -1638,14 +1777,14 @@ class Table(Queryable):
"""
Create an index on this table.
- ``columns`` - a single columns or list of columns to index. These can be strings or,
:param columns: A single columns or list of columns to index. These can be strings or,
to create an index using the column in descending order, ``db.DescIndex(column_name)`` objects.
- ``index_name`` - the name to use for the new index. Defaults to the column names joined on ``_``.
- ``unique`` - should the index be marked as unique, forcing unique values?
- ``if_not_exists`` - only create the index if one with that name does not already exist.
- ``find_unique_name`` - if ``index_name`` is not provided and the automatically derived name
:param index_name: The name to use for the new index. Defaults to the column names joined on ``_``.
:param unique: Should the index be marked as unique, forcing unique values?
:param if_not_exists: Only create the index if one with that name does not already exist.
:param find_unique_name: If ``index_name`` is not provided and the automatically derived name
already exists, keep incrementing a suffix number to find an available name.
- ``analyze`` - run ``ANALYZE`` against this index after creating it.
:param analyze: Run ``ANALYZE`` against this index after creating it.
See :ref:`python_api_create_index`.
"""
@ -1706,9 +1845,22 @@ class Table(Queryable):
return self
def add_column(
self, col_name: str, col_type=None, fk=None, fk_col=None, not_null_default=None
self,
col_name: str,
col_type: Optional[Any] = None,
fk: Optional[str] = None,
fk_col: Optional[str] = None,
not_null_default: Optional[Any] = None,
):
"Add a column to this table. See :ref:`python_api_add_column`."
"""
Add a column to this table. See :ref:`python_api_add_column`.
:param col_name: Name of the new column
:param col_type: Column type - a Python type such as ``str`` or a SQLite type string such as ``"BLOB"``
:param fk: Name of a table that this column should be a foreign key reference to
:param fk_col: Column in the foreign key table that this should reference
:param not_null_default: Set this column to ``not null`` and give it this default value
"""
fk_col_type = None
if fk is not None:
# fk must be a valid table
@ -1744,7 +1896,11 @@ class Table(Queryable):
return self
def drop(self, ignore: bool = False):
"Drop this table. ``ignore=True`` means errors will be ignored."
"""
Drop this table.
:param ignore: Set to ``True`` to ignore the error if the table does not exist
"""
try:
self.db.execute("DROP TABLE [{}]".format(self.name))
except sqlite3.OperationalError:
@ -1760,6 +1916,8 @@ class Table(Queryable):
a ``tag`` table, if one exists.
If no candidates can be found, raises a ``NoObviousTable`` exception.
:param column: Name of column
"""
column = column.lower()
possibilities = [column]
@ -1800,10 +1958,10 @@ class Table(Queryable):
"""
Alter the schema to mark the specified column as a foreign key to another table.
- ``column`` - the column to mark as a foreign key.
- ``other_table`` - the table it refers to - if omitted, will be guessed based on the column name.
- ``other_column`` - the column on the other table it - if omitted, will be guessed.
- ``ignore`` - set this to ``True`` to ignore an existing foreign key - otherwise a ``AlterError`` will be raised.
:param column: The column to mark as a foreign key.
:param other_table: The table it refers to - if omitted, will be guessed based on the column name.
:param other_column: The column on the other table it - if omitted, will be guessed.
:param ignore: Set this to ``True`` to ignore an existing foreign key - otherwise a ``AlterError`` will be raised.
"""
# Ensure column exists
if column not in self.columns_dict:
@ -1911,13 +2069,13 @@ class Table(Queryable):
"""
Enable SQLite full-text search against the specified columns.
- ``columns`` - list of column names to include in the search index.
- ``fts_version`` - FTS version to use - defaults to ``FTS5`` but you may want ``FTS4`` for older SQLite versions.
- ``create_triggers`` - should triggers be created to keep the search index up-to-date? Defaults to ``False``.
- ``tokenize`` - custom SQLite tokenizer to use, for example ``"porter"`` to enable Porter stemming.
- ``replace`` - should any existing FTS index for this table be replaced by the new one?
See :ref:`python_api_fts` for more details.
:param columns: List of column names to include in the search index.
:param fts_version: FTS version to use - defaults to ``FTS5`` but you may want ``FTS4`` for older SQLite versions.
:param create_triggers: Should triggers be created to keep the search index up-to-date? Defaults to ``False``.
:param tokenize: Custom SQLite tokenizer to use, for example ``"porter"`` to enable Porter stemming.
:param replace: Should any existing FTS index for this table be replaced by the new one?
"""
create_fts_sql = (
textwrap.dedent(
@ -1990,6 +2148,8 @@ class Table(Queryable):
"""
Update the associated SQLite full-text search index with the latest data from the
table for the specified columns.
:param columns: Columns to populate the data for
"""
sql = (
textwrap.dedent(
@ -2089,7 +2249,14 @@ class Table(Queryable):
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> str:
"Return SQL string that can be used to execute searches against this table."
""" "
Return SQL string that can be used to execute searches against this table.
:param columns: Columns to search against
:param order_by: Column or SQL expression to sort by
:param limit: SQL limit
:param offset: SQL offset
"""
# Pick names for table and rank column that don't clash
original = "original_" if self.name == "original" else "original"
columns_sql = "*"
@ -2159,12 +2326,12 @@ class Table(Queryable):
Execute a search against this table using SQLite full-text search, returning a sequence of
dictionaries for each row.
- ``q`` - terms to search for
- ``order_by`` - defaults to order by rank, or specify a column here.
- ``columns`` - list of columns to return, defaults to all columns.
- ``limit`` - optional integer limit for returned rows.
- ``offset`` - optional integer SQL offset.
- ``quote`` - apply quoting to disable any special characters in the search query
:param q: Terms to search for
:param order_by: Defaults to order by rank, or specify a column here.
:param columns: List of columns to return, defaults to all columns.
:param limit: Optional integer limit for returned rows.
:param offset: Optional integer SQL offset.
:param quote: Apply quoting to disable any special characters in the search query
See :ref:`python_api_fts_search`.
"""
@ -2185,7 +2352,11 @@ class Table(Queryable):
return self._defaults[key] if value is DEFAULT else value
def delete(self, pk_values: Union[list, tuple, str, int, float]) -> "Table":
"Delete row matching the specified primary key."
"""
Delete row matching the specified primary key.
:param pk_values: A single value, or a tuple of values for tables that have a compound primary key
"""
if not isinstance(pk_values, (list, tuple)):
pk_values = [pk_values]
self.get(pk_values)
@ -2206,11 +2377,12 @@ class Table(Queryable):
"""
Delete rows matching the specified where clause, or delete all rows in the table.
- ``where`` - a SQL fragment to use as a ``WHERE`` clause, for example ``age > ?`` or ``age > :age``.
- ``where_args`` - a list of arguments (if using ``?``) or a dictionary (if using ``:age``).
- ``analyze`` - set to ``True`` to run ``ANALYZE`` after the rows have been deleted.
See :ref:`python_api_delete_where`.
:param where: SQL where fragment to use, for example ``id > ?``
:param where_args: Parameters to use with that fragment - an iterable for ``id > ?``
parameters, or a dictionary for ``id > :id``
:param analyze: Set to ``True`` to run ``ANALYZE`` after the rows have been deleted.
"""
if not self.exists():
return self
@ -2232,14 +2404,14 @@ class Table(Queryable):
"""
Execute a SQL ``UPDATE`` against the specified row.
- ``pk_values`` - the primary key of an individual record - can be a tuple if the
table has a compound primary key.
- ``updates`` - a dictionary mapping columns to their updated values.
- ``alter`` - set to ``True`` to add any missing columns.
- ``conversions`` - optional dictionary of SQL functions to apply during the update, for example
``{"mycolumn": "upper(?)"}``.
See :ref:`python_api_update`.
:param pk_values: The primary key of an individual record - can be a tuple if the
table has a compound primary key.
:param updates: A dictionary mapping columns to their updated values.
:param alter: Set to ``True`` to add any missing columns.
:param conversions: Optional dictionary of SQL functions to apply during the update, for example
``{"mycolumn": "upper(?)"}``.
"""
updates = updates or {}
conversions = conversions or {}
@ -2293,18 +2465,18 @@ class Table(Queryable):
"""
Apply conversion function ``fn`` to every value in the specified columns.
- ``columns`` - a single column or list of string column names to convert.
- ``fn`` - a callable that takes a single argument, ``value``, and returns it converted.
- ``output`` - optional string column name to write the results to (defaults to the input column).
- ``output_type`` - if the output column needs to be created, this is the type that will be used
:param columns: A single column or list of string column names to convert.
:param fn: A callable that takes a single argument, ``value``, and returns it converted.
:param output: Optional string column name to write the results to (defaults to the input column).
:param output_type: If the output column needs to be created, this is the type that will be used
for the new column.
- ``drop`` - boolean, should the original column be dropped once the conversion is complete?
- ``multi`` - boolean, if ``True`` the return value of ``fn(value)`` will be expected to be a
:param drop: Should the original column be dropped once the conversion is complete?
:param multi: If ``True`` the return value of ``fn(value)`` will be expected to be a
dictionary, and new columns will be created for each key of that dictionary.
- ``where`` - a SQL fragment to use as a ``WHERE`` clause to limit the rows to which the conversion
:param where: SQL fragment to use as a ``WHERE`` clause to limit the rows to which the conversion
is applied, for example ``age > ?`` or ``age > :age``.
- ``where_args`` - a list of arguments (if using ``?``) or a dictionary (if using ``:age``).
- ``show_progress`` - boolean, should a progress bar be displayed?
:param where_args: List of arguments (if using ``?``) or a dictionary (if using ``:age``).
:param show_progress: Should a progress bar be displayed?
See :ref:`python_api_convert`.
"""
@ -2627,23 +2799,24 @@ class Table(Queryable):
Each of them defaults to ``DEFAULT``, which indicates that the default setting for the current
``Table`` object (specified in the table constructor) should be used.
- ``pk`` - if creating the table, which column should be the primary key.
- ``foreign_keys`` - see :ref:`python_api_foreign_keys`.
- ``column_order`` - optional list of strings specifying a full or partial column order
:param record: Dictionary record to be inserted
:param pk: If creating the table, which column should be the primary key.
:param foreign_keys: See :ref:`python_api_foreign_keys`.
:param column_order: List of strings specifying a full or partial column order
to use when creating the table.
- ``not_null`` - optional set of strings specifying columns that should be ``NOT NULL``.
- ``defaults`` - optional dictionary specifying default values for specific columns.
- ``hash_id`` - optional name of a column to create and use as a primary key, where the
:param not_null: Set of strings specifying columns that should be ``NOT NULL``.
:param defaults: Dictionary specifying default values for specific columns.
:param hash_id: Name of a column to create and use as a primary key, where the
value of thet primary key will be derived as a SHA1 hash of the other column values
in the record. ``hash_id="id"`` is a common column name used for this.
- ``alter`` - boolean, should any missing columns be added automatically?
- ``ignore`` - boolean, if a record already exists with this primary key, ignore this insert.
- ``replace`` - boolean, if a record already exists with this primary key, replace it with this new record.
- ``extracts`` - a list of columns to extract to other tables, or a dictionary that maps
:param alter: Boolean, should any missing columns be added automatically?
:param ignore: Boolean, if a record already exists with this primary key, ignore this insert.
:param replace: Boolean, if a record already exists with this primary key, replace it with this new record.
:param extracts: A list of columns to extract to other tables, or a dictionary that maps
``{column_name: other_table_name}``. See :ref:`python_api_extracts`.
- ``conversions`` - dictionary specifying SQL conversion functions to be applied to the data while it
:param conversions: Dictionary specifying SQL conversion functions to be applied to the data while it
is being inserted, for example ``{"name": "upper(?)"}``. See :ref:`python_api_conversions`.
- ``columns`` - dictionary over-riding the detected types used for the columns, for example
:param columns: Dictionary over-riding the detected types used for the columns, for example
``{"age": int, "weight": float}``.
"""
return self.insert_all(
@ -2904,9 +3077,12 @@ class Table(Queryable):
be included only if the record is being created for the first time. These will
be ignored on subsequent lookup calls for records that already exist.
All other keyword arguments are passed through to ``.insert()``.
See :ref:`python_api_lookup_tables` for more details.
All other keyword arguments are passed through to ``.insert()``.
:param lookup_values: Dictionary specifying column names and values to use for the lookup
:param extra_values: Additional column values to be used only if creating a new record
"""
assert isinstance(lookup_values, dict)
if extra_values is not None:
@ -2978,13 +3154,13 @@ class Table(Queryable):
See :ref:`python_api_m2m` for details.
- ``other_table`` - the name of the table to insert the new records into.
- ``record_or_iterable`` - a single dictionary record to insert, or a list of records.
- ``pk`` - the primary key to use if creating ``other_table``.
- ``lookup`` - same dictionary as for ``.lookup()``, to create a many-to-many lookup table.
- ``m2m_table`` - the string name to use for the many-to-many table, defaults to creating
:param other_table: The name of the table to insert the new records into.
:param record_or_iterable: A single dictionary record to insert, or a list of records.
:param pk: The primary key to use if creating ``other_table``.
:param lookup: Same dictionary as for ``.lookup()``, to create a many-to-many lookup table.
:param m2m_table: The string name to use for the many-to-many table, defaults to creating
this automatically based on the names of the two tables.
- ``alter`` - set to ``True`` to add any missing columns on ``other_table`` if that table
:param alter: Set to ``True`` to add any missing columns on ``other_table`` if that table
already exists.
"""
if isinstance(other_table, str):
@ -3053,6 +3229,11 @@ class Table(Queryable):
Return statistics about the specified column.
See :ref:`python_api_analyze_column`.
:param column: Column to analyze
:param common_limit: Show this many column values
:param value_truncate: Truncate display of common values to this many characters
:param total_rows: Optimization - pass the total number of rows in the table to save running a fresh ``count(*)`` query
"""
db = self.db
table = self.name
@ -3133,7 +3314,7 @@ class Table(Queryable):
`SRID 4326 <https://spatialreference.org/ref/epsg/wgs-84/>`__. This can
be customized using the ``column_name``, ``srid`` and ``not_null`` arguments.
Returns True if the column was successfully added, False if not.
Returns ``True`` if the column was successfully added, ``False`` if not.
.. code-block:: python
@ -3147,6 +3328,11 @@ class Table(Queryable):
table = db["locations"].create({"name": str})
table.add_geometry_column("geometry", "POINT")
:param column_name: Name of column to add
:param geometry_type: Type of geometry column, for example ``"GEOMETRY"`` or ``"POINT" or ``"POLYGON"``
:param srid: Integer SRID, defaults to 4326 for WGS84
:param coord_dimension: Dimensions to use, defaults to ``"XY"`` - set to ``"XYZ"`` to work in three dimensions
:param not_null: Should the column be ``NOT NULL``
"""
cursor = self.db.execute(
"SELECT AddGeometryColumn(?, ?, ?, ?, ?, ?);",
@ -3185,6 +3371,7 @@ class Table(Queryable):
# outputs:
# CREATE VIRTUAL TABLE "idx_locations_geometry" USING rtree(pkid, xmin, xmax, ymin, ymax)
:param column_name: Geometry column to create the spatial index against
"""
if f"idx_{self.name}_{column_name}" in self.db.table_names():
return False
@ -3206,6 +3393,12 @@ class View(Queryable):
)
def drop(self, ignore=False):
"""
Drop this view.
:param ignore: Set to ``True`` to ignore the error if the view does not exist
"""
try:
self.db.execute("DROP VIEW [{}]".format(self.name))
except sqlite3.OperationalError:
@ -3213,6 +3406,7 @@ class View(Queryable):
raise
def enable_fts(self, *args, **kwargs):
"``enable_fts()`` is supported on tables but not on views."
raise NotImplementedError(
"enable_fts() is supported on tables but not on views"
)

View file

@ -365,6 +365,9 @@ def hash_record(record: Dict, keys: Optional[Iterable[str]] = None):
{"name": "Cleo", "twitter": "CleoPaws", "age": 7},
keys=("name", "twitter")
)
:param record: Record to generate a hash for
:param keys: Subset of keys to use for that hash
"""
to_hash = record
if keys is not None: