composeui.core.sqlitemodel¶
A model keeping the app’s data in a SQLite database of its own.
The app writes its schema and its queries, in SqliteTable classes; the model gives
them a connection, runs each block of work as one transaction, and saves and opens the
database with the study. It works on a scratch copy in a temporary file: the user’s
study only changes when it’s saved.
- class composeui.core.sqlitemodel.CSVSqliteTable[source]¶
Bases:
SqliteTableA CSV table whose schema comes from an attrs row class.
- source(*, headers=None)[source]¶
Build a source with schema-derived types and SQLite row ids.
By default, show every field under its CSV header (or its field name).
headersselects, orders and labels the fields to show. RaisesKeyErrorfor an unknown field, orValueErrorif fields shadow all SQLite row-id names. UseSqliteSourcedirectly for custom queries or explicit primary keys.- Parameters:
- Return type:
- fill(text, *, batch_size=100000, limit=None, **csv_options)[source]¶
Import CSV text and yield cumulative row counts.
Pass an open text file (with
newline='') or another iterable of lines. The caller owns and closes it. Extra keywords go tocsv.DictReader, for exampledelimiter=';'orfieldnames=['name', 'value']. Exhaust the generator to commit. Usecontextlib.closingwhen stopping early to roll back rows. Nested transactions followSqliteTable.insert_batches’s rules.
- fill_from_file(path, *, encoding='utf-8', sniff=False, sniff_sample_size=8192, batch_size=100000, limit=None, **csv_options)[source]¶
Import a plain or gzip CSV file and yield cumulative submitted-row counts.
Accept a path string or path-like object, relative or absolute. Detect gzip from the content. Own and close the file; other options follow
fill. Exhaust the generator to commit, or usecontextlib.closingwhen stopping early to close the file and roll back. File and decoding errors propagate. Setsniff=Trueto detect the dialect from at mostsniff_sample_sizedecoded characters (8192 by default). The size must be positive when sniffing and is ignored otherwise. Explicit CSV options override detection. Detection failure raisesRefusedError.
- fill_from_url(url, *, encoding='utf-8', timeout=30.0, sniff=False, sniff_sample_size=8192, batch_size=100000, limit=None, **csv_options)[source]¶
Import CSV from a trusted URL and yield cumulative submitted-row counts.
open_csv_urlhandles decoding, gzip and stream cleanup. Other options followfill. Exhaust the generator to commit; usecontextlib.closingif iteration can stop early, to close the streams and roll back. Existing rows are retained.sniff=Truedetects the dialect as infill_from_file, without seeking.sniff_sample_sizesets its limit in decoded characters (8192 by default).
- class composeui.core.sqlitemodel.SqliteModel[source]¶
Bases:
ModelA model whose data is a SQLite database, saved and opened with the study.
Declare the tables in the class,
runs = table(Runs), for the callbacks to reach (model.runs.add(...)), or add them in__init__withadd_table. A study’s database is created from the tables’createscripts atschema_version. When a study saved at an older version is opened,migrationsbring it up to date.- data_name: ClassVar[str | None] = 'data.sqlite'¶
the model’s file inside a saved study; None for a model with nothing to save
- schema_version: ClassVar[int] = 1¶
the tables’ version, kept in the database (
PRAGMA user_version); raise it with every change to the tables, adding the migration from the previous one
- migrations: ClassVar[Mapping[int, Callable[[sqlite3.Connection], None]]] = {}¶
by version: the function bringing a database at that version to the next one
- open_time_limit: ClassVar[float] = 30.0¶
seconds opening a study’s database may take, checks and migrations included: its schema comes from the file, and a view of it may never finish (e.g. a recursive one)
- pragmas: ClassVar[Mapping[str, int | str]] = mappingproxy({'journal_mode': 'WAL', 'foreign_keys': 'ON', 'synchronous': 'OFF', 'recursive_triggers': 'ON'})¶
set on the connections of every study’s database: SQLite keeps these per connection, not in the file, and New and Open each start a connection of their own
- property signals: list[Signal[Unpack[tuple[Any, ...]]]]¶
Get the model’s signals, its tables’ (
SqliteTable.changed) among them.
- add_table(table_type)[source]¶
Add a table, created in the current database too, and return it.
- Parameters:
table_type (type[SqliteTableT])
- Return type:
SqliteTableT
- connection(*, changing=None)[source]¶
Give the database’s connection for one transaction.
The transaction commits once the block ends, or rolls back if the block raises, so its writes land together or not at all. A block inside another (one table’s method calling another’s) joins the outer transaction. If the block changed rows,
changing’schangedis emitted after the commit. For a block in a job’s thread, it is emitted on the main thread (seeJob.call_on_main_thread), where the views it refreshes are.- Parameters:
changing (SqliteTable | None)
- Return type:
Generator[sqlite3.Connection]
- reading()[source]¶
Give a connection that only reads the database, for a long query.
Unlike
connection(), it waits for no other thread’s turn, and holds none: the model’s other queries run meanwhile, e.g. a large table’s cells while a job orders its rows or writes some (SqliteSource). In the WAL journal, it reads the rows committed, during a write too. A database New or Open replace meanwhile, or the model’sclose, is deleted once the query is over. Once the model is closed, raisessqlite3.ProgrammingError, asconnection()does: e.g. a web download its session’s end cuts short.The connection is reused, e.g. for the next window of a large table: read each query’s rows to the end in the block. A query left half read keeps the rows as they were when it started, for the next reader too.
- Return type:
Generator[sqlite3.Connection]
- save_data(path)[source]¶
Write the data into the file
path.- Parameters:
path (Path)
- Return type:
None
- open_data(path)[source]¶
Open a study’s database, refused unless it is made as a new one is.
The file may come from anyone (e.g. a web upload): its schema is the file’s own, whose views and triggers would run in the app. Until it is checked, its views and triggers may call no function with side effects (
trusted_schema), and the whole open is cut afteropen_time_limit.- Parameters:
path (Path)
- Return type:
None
- class composeui.core.sqlitemodel.SqliteSource[source]¶
Bases:
objectA
TableSourceof a query’s rows, which a large table reads a window at a time.It takes the
SqliteTablewhose rows the query reads, the query itself, the column of the rows’ ids (e.g. a primary key) and the query’s columns that the table shows. SQLite sorts and filters the rows: the order is aSELECTof the ids, and the cells aSELECTof some ids’ rows, so no more rows than those shown come to Python. The table’schangedreads them again. Only the declared columns sort and filter, by name: SQLite takes no column name as a parameter, the values are. It reads on connections of its own (SqliteModel.reading): the rows committed, never waiting for a write, nor the cells for the order a job reads.A text compares ignoring its case as a
ListSourcedoes, but text typed in ASCII alone compares in SQLite (see_folded). It sorts ignoring the case of ASCII letters alone (NOCASE). Equal cells, and the rows unsorted, keep the order of their ids. A/regex/filter, or a text not in ASCII, calls Python on each row: index the columns that sort instead.- __init__(table, query, *, id_column, columns)[source]¶
- Parameters:
table (SqliteTable)
query (str)
id_column (str)
columns (Sequence[SourceColumn])
- Return type:
None
- property columns: Sequence[SourceColumn]¶
- class composeui.core.sqlitemodel.SqliteTable[source]¶
Bases:
objectA table of a
SqliteModel(or a few related ones), with the queries the app needs.createis the SQL script that creates it, run in every new study. Its methods read and write throughconnection().changedis emitted once a transaction that changed rows through the table has committed, so the views that show them can refresh.A constraint that fails in a transaction refuses it:
connection()raises aRefusedError. Its message is the one of the first key ofrefusalsfound in SQLite’s message: a constraint’s name, or e.g. “UNIQUE constraint failed: room.name”. Else it is SQLite’s message.- refusals: ClassVar[Mapping[str, str]] = mappingproxy({})¶
by a part of SQLite’s message, the user’s message when a constraint fails, as written in the code: the view showing it translates it (see
View.refusing)
- __init__(model)[source]¶
- Parameters:
model (SqliteModel)
- Return type:
None
- connection()[source]¶
Give the database’s connection for one transaction; see
SqliteModel.connection.Raises
RefusedErrorwhen a constraint fails, the transaction rolled back.- Return type:
Generator[sqlite3.Connection]
- reading()[source]¶
Give a connection reading the database alone; see
SqliteModel.reading.- Return type:
Generator[sqlite3.Connection]
- fetch(cls, sql, parameters=())[source]¶
Run the query
sqland get its rows asclsinstances (anattrsclass).
- fetch_one(cls, sql, parameters=())[source]¶
Run the query
sqland get its first row as aclsinstance; None if none.
- fetch_column(sql, parameters=())[source]¶
Run the query
sqland get its rows’ first column, e.g. a list of names.
- insert_batches(sql, rows, *, db=None, batch_size=100000, limit=None)[source]¶
Insert rows in batches and yield the cumulative number of rows submitted.
Rows are positional or named SQL parameters. Read at most
limitrows, or all rows if None.batch_sizemust be positive;limitmust be nonnegative. Counts describe submitted rows, not affected rows (e.g. with INSERT OR IGNORE).Exhaust the generator to commit, or use
contextlib.closingto roll back when stopping early. All batches share one transaction. Insideconnection(), they join its transaction instead: the caller must raise to roll that transaction back. Ifdbis supplied, use it without committing, rolling back or closing it. Pass a connection fromconnection()to keep the table’s change notifications.- Parameters:
sql (str)
rows (Iterable[Parameters])
db (sqlite3.Connection | None)
batch_size (int)
limit (int | None)
- Return type:
Generator[int]
- composeui.core.sqlitemodel.as_parameters(row)[source]¶
Get an
attrsinstance’s fields by name, as named parameters of a query.
- composeui.core.sqlitemodel.csv_field(name: str, *, date_format: str, default: Any = attrs.NOTHING, factory: Callable[[], Any] | None = None, converter: Any = None, init: bool = True, alias: str | None = None, kw_only: bool = False, **kwargs: Any) datetime[source]¶
- composeui.core.sqlitemodel.csv_field(name: str, *, date_format: None = None, default: Any = attrs.NOTHING, factory: Callable[[], Any] | None = None, converter: Any = None, init: bool = True, alias: str | None = None, kw_only: bool = False, **kwargs: Any) Any
Declare a CSV field, with an optional strptime format for a date or datetime.
- composeui.core.sqlitemodel.define_csv_row(cls)[source]¶
Define an attrs row class with typed CSV field specifiers.
Use
@define_csv_rowinstead of@attrs.definewhen fields usecsv_field. For mypy, enable thecomposeui.mypyplugin.- Parameters:
cls (_RowClassT)
- Return type:
_RowClassT
- composeui.core.sqlitemodel.open_csv_url(url, *, encoding='utf-8', timeout=30.0)[source]¶
Open a trusted HTTP, HTTPS or file URL as CSV text, and close its streams on exit.
Detect gzip by its content, not its filename. Preserve CSV newlines and decode with
encoding.timeoutsets the socket timeout in seconds, not a total download deadline. Network, decompression and decoding errors propagate to the caller. Do not pass untrusted URLs: the helper does not restrict hosts or local file access.- Parameters:
- Return type:
Generator[io.TextIOWrapper]
- composeui.core.sqlitemodel.table(table_type)[source]¶
Declare a table of a
SqliteModel, in the model’s class.E.g.
runs = table(Runs). Each model builds its own table, as an attribute of the same name, before it creates its database. The tables are built in the order the classes declare them, a base class’s first.- Parameters:
table_type (type[SqliteTableT])
- Return type:
SqliteTableT
- composeui.core.sqlitemodel.unique_name(db, table, column, base)[source]¶
Get
base, or else “base2”, “base3”…, the first no row oftablehas.Names differing only by case are the same name (a
COLLATE NOCASEcolumn’s), e.g. to add a “Room” whatever rooms there are already.- Parameters:
db (Connection)
table (str)
column (str)
base (str)
- Return type: