Configuration¶
sqlty reads [tool.sqlty] from pyproject.toml. The CLI uses, for each
file, the nearest pyproject.toml above it, unless
--config-file or --isolated picks one file or none; the magic codec
searches from the current directory. Command-line flags override the file. An unknown key, rule or
severity is an error (exit 2) that names the file and suggests the closest match.
In every case a pyproject.toml without [tool.sqlty] is skipped, so a
package's packaging-only pyproject.toml in a monorepo does not hide the
root config. The closest table wins; settings are not merged with tables in
parent directories. To change one setting for a nested project, give it its
own complete [tool.sqlty] table. Command-line flags (--dialect,
--schema, --untyped, --exclude, --extend-exclude, --error/--warn/--ignore)
and -c KEY=VALUE assignments override the table for every path.
[tool.sqlty]
dialect = "postgres"
schema = "schema.sql"
import-style = "from sqlty import SQL"
untyped = "skip"
[tool.sqlty.rules]
select-star = "ignore"
stale-type = "error"
[[tool.sqlty.functions]]
import-path = "db.sql"
query-param = "query"
| Key | Type | Default | Used by | Summary |
|---|---|---|---|---|
dialect |
"postgres", "sqlite", "mysql" |
"postgres" |
rewrite, check |
SQL dialect of queries and schema |
schema |
string or list of strings | none | rewrite, check |
Schema .sql files, directories or globs, relative to pyproject.toml |
rules |
table of rule = severity | per-rule defaults | check |
Severity per lint rule: "error", "warn" or "ignore" |
overrides |
array of tables | [] |
check |
Rule severities for the files include matches and exclude does not, on top of rules |
import-style |
string | "from sqlty import SQL" |
rewrite, strip, check, magic codec |
How rewritten code imports and spells the wrapper |
untyped |
"skip", "sentinel" |
"skip" |
rewrite |
Whether queries of unknown row type are wrapped as SQL[UNTYPED] |
json-types |
"typing.Any", "typing.TypedDict" |
"typing.Any" |
rewrite, check --fix |
How a JSON column of known shape is typed: typing.Any, or a generated TypedDict |
param-types |
true, false |
false |
rewrite, check --fix |
Whether SQL[R] calls get the type of their parameters, SQL[R, P] |
preview |
boolean | false |
check |
Turn on features still in preview: query variants |
max-query-variants |
positive integer | 64 |
check |
With preview, the most variants of a query built in branches that check checks |
functions |
array of tables | [] |
check |
Helper functions whose string argument is a query |
types |
table of SQL type or table.column = Python type |
{} |
rewrite, check |
Python types a converter above the driver gives a SQL type or a column |
format-command |
list of strings | none | rewrite, check --fix |
Formatter run on each file rewrite or check --fix changes, with the path appended |
exclude |
list of globs | see Default excludes | rewrite, strip, check |
Globs skipped while walking a directory; replaces the default list |
extend-exclude |
list of globs | [] |
rewrite, strip, check |
Globs skipped in addition to exclude or the defaults |
respect-gitignore |
boolean | true |
rewrite, strip, check |
Skip the files .gitignore ignores while walking a directory |
dialect¶
SQL dialect for queries and the schema: "postgres" (default), "sqlite",
"mysql", or another dialect sqlparser-rs knows. CLI:
--dialect (the first three only), which overrides this key. See
Dialects.
schema¶
A .sql file with CREATE TABLE statements, or a list of them, relative to
the directory of pyproject.toml. Each entry is a file, a directory (every
*.sql below it) or a glob (tables/*.sql, **/*.sql). The files are joined
in the order of the list; directory and glob matches are sorted by path, and a
file named twice is read once. CLI: --schema, repeatable (relative to the
current directory). See Schema inference.
rules¶
Severity per lint rule: "error", "warn" or "ignore".
An unknown rule name or severity is an error.
overrides¶
Rule severities for some paths only, such as tests or migrations:
[tool.sqlty.rules]
select-star = "error"
[[tool.sqlty.overrides]]
include = ["tests/**", "conftest.py"]
[tool.sqlty.overrides.rules]
select-star = "ignore"
The table follows ty's
[[tool.ty.overrides]]:
include and exclude are lists of globs, and rules takes the same
severities as rules. An entry applies to the files include
matches (every file when it is left out) and exclude does not:
[[tool.sqlty.overrides]]
include = ["generated/**"]
exclude = ["generated/important.py"]
[tool.sqlty.overrides.rules]
select-star = "ignore"
Globs are relative to the pyproject.toml directory and match like
exclude: a glob without a slash matches a file
or directory name at any depth, and a matching directory covers every file in
it. Every entry that matches a file is merged over rules, later entries
winning. --error, --warn, --ignore and -c rules.X=... still win over
both.
An override changes severities only; dialect, schema and the other keys
apply to the whole project. check and check --fix read it.
import-style¶
How rewritten code imports and spells the wrapper. Default:
"from sqlty import SQL".
| Value | Wrapper |
|---|---|
"from sqlty import SQL" |
SQL[T](...) |
"from sqlty import SQL as Q" (any alias) |
Q[T](...) |
"import sqlty" |
sqlty.SQL[T](...) |
"import sqlty as sy" (any alias) |
sy.SQL[T](...) |
strip and check use the same setting to recognize wrappers, so change it
only on stripped sources. With untyped = "sentinel", UNTYPED follows the
style: from sqlty import UNTYPED, or sqlty.UNTYPED (sy.UNTYPED) for
module imports.
Example: examples/05_import_style/.
preview¶
true turns on features that may still change before they are on by default:
- Query variants: a query built from names bound in
branches is checked once per path that builds it, instead of being skipped as
dynamic-query.
max-query-variants¶
With preview, the most variants of one query that check checks, 64 by default. A
query with more is reported as dynamic-query, or unchecked-query for a
psycopg.sql composition, with the count: it is never checked on part of its variants.
untyped¶
"skip" (default) leaves row-returning queries of unknown type as plain
strings. "sentinel" wraps them as SQL[UNTYPED](...). CLI:
sqlty rewrite --untyped. See Untyped queries.
json-types¶
"typing.Any" (default) types a JSON column typing.Any. "typing.TypedDict" makes
rewrite and check --fix type a JSON column whose shape the query spells out
with generated TypedDicts, written after the imports under a
# sqlty: generated line. strip removes them. See
Generated TypedDicts.
param-types¶
false (default) leaves the parameters of a query untyped, SQL[R]. true
makes rewrite and check --fix write their type, SQL[R, P]: a tuple, or
a generated TypedDict for named parameters, written as json-types writes
its classes. strip removes them. See
Typed parameters.
Type checkers become stricter than the driver
A type checker then rejects calls that psycopg runs: a list for a tuple,
text for another type, a dict[str, Any] or extra keys for a TypedDict.
That is why it is off by default.
functions¶
An array of tables registering helper functions. sqlty check checks their
calls like those of an annotated helper
without editing the signature (a method is "pkg.db.Db.fetch_all"):
| Key | Required | Description |
|---|---|---|
import-path |
yes | Module path and function name, for example "app.db.sql"; a method is "pkg.db.Db.fetch_all". Use "__main__.sql" or the file name for a script run directly |
query-param |
yes | Name of the parameter that holds the query |
return-type-param |
no | Parameter whose argument, such as return_tp=int, is the row type of that call instead of an inferred one; check compares it with the query columns |
Entries without import-path or query-param are skipped with a warning.
types¶
The Python type a converter above the driver gives a SQL type or a column. sqlty
types the value the reference driver returns; when your code reads
values through something that converts them (an ORM, sqlite3 with
detect_types), say what it returns:
[tool.sqlty.types]
DATE = "datetime.date"
TIMESTAMP = "datetime.datetime"
BOOLEAN = "bool"
"events.duration" = "datetime.timedelta"
A key with a dot is table.column; any other is a SQL type name, read in the
project's dialect (NUMERIC covers DECIMAL(10, 2)). The type applies to
schema columns and casts; an expression over a column keeps its type when it
returns the column's values (MAX(day)). See
Drivers and ORMs.
rewrite and check --fix import the module a type names (import app.models for
app.models.Status), as they import datetime. strip leaves that import, since
it reads no configuration.
format-command¶
A formatter rewrite and check --fix run on each file they change, with the
file's path appended to the command. A long SQL[tuple[...]] wrapper then
leaves the file formatted, instead of failing your formatter's check until it
runs again.
Files that are not changed are never formatted, and --check and --diff
write nothing, so they run no formatter. A formatter that fails is reported as
a warning and the file keeps sqlty's changes. rewrite stays idempotent with a
formatter: a wrapper the formatter split over several lines is recognized as
already written.
rewrite adds its imports (from sqlty import SQL, import datetime) at the top of
the import block, below the module docstring, without sorting them into your groups.
To keep an import sorter's check passing, sort them as part of the command, for example
with a script:
#!/bin/sh
# scripts/format.sh: sort imports, then format
ruff check --select I --fix --quiet "$@" && ruff format --quiet "$@"
exclude and extend-exclude¶
Directories the CLI walks skip these globs. exclude replaces the default
list; extend-exclude adds to whichever list applies.
The default excludes are:
.bzr, .direnv, .eggs, .git, .git-rewrite, .hg, .ipynb_checkpoints, .mypy_cache, .nox, .pants.d, .pyenv, .pytest_cache, .pytype, .ruff_cache, .svn, .tox, .venv, .vscode, __pypackages__, _build, buck-out, dist, node_modules, site-packages, venv
A glob without a slash matches a file or
directory name at any depth; one with a slash matches the path relative to
the pyproject.toml that holds it, and ** recurses. Files named on the
command line are always processed, unless --force-exclude is given. The CLI's --exclude and
--extend-exclude take the same globs, relative to the current directory.
respect-gitignore¶
Directories the CLI walks also skip what git ignores: every .gitignore from
the repository root down, and .git/info/exclude, with negations (!keep.py)
and nested files read as git reads them. Build output and generated code are
usually listed there already, so they need no exclude entry. Outside a git
repository nothing is skipped this way; the global core.excludesFile is not
read.
Files named on the command line are always processed. The CLI's
--respect-gitignore and --no-respect-gitignore override the setting.