CLI¶
sqlty rewrite . # "--sql\n..." -> SQL[T]("--sql\n...")
sqlty check . # lint row types, drift, SELECT *
sqlty check --fix . # apply the fixable rules, report the rest
sqlty strip . # SQL[T]("--sql\n...") -> "--sql\n..."
Each command takes files or directories (default: the current directory) and
processes every .py file under them, skipping the default and configured
excludes (.venv, .git,
node_modules, ...) while walking a directory; a file named on the command
line is always processed unless --force-exclude is given. A path that does
not exist is an error. Each file uses [tool.sqlty] from the
nearest pyproject.toml above it that has one; see
Configuration. Command-line flags override it.
Configuration flags¶
To run sqlty on a project without editing its pyproject.toml, pass the
settings on the command line or in a file of your own. The flags follow
ty and
ruff:
sqlty check --config-file ~/sqlty.toml . # one file for every path
sqlty check -c 'schema = "db/"' -c 'dialect = "mysql"' . # override single keys
sqlty check --isolated --schema db/ . # defaults plus flags only
sqlty check --ignore select-star --error stale-type . # rule severities
sqlty check --extend-exclude 'legacy/*' . # skip more than the config does
sqlty rewrite --exclude migrations --exclude build . # replace the excludes
--config-file PATH reads [tool.sqlty] from a file named pyproject.toml and
the same keys at the top level of any other file, such as sqlty.toml.
sqlty.toml:
It replaces the per-file lookup for every path, and its relative paths
(schema, exclude, extend-exclude) start from the current directory, so
the file can live outside the project. SQLTY_CONFIG_FILE sets it when
--config-file is not given. --isolated ignores every configuration file,
including SQLTY_CONFIG_FILE; it cannot be combined with --config-file.
-c/--config KEY=VALUE is a TOML assignment applied on top of whichever
configuration applies, --isolated included. Repeat it for several keys;
the last value of a key wins, and tables merge key by key, so
-c 'rules.select-star = "ignore"' keeps the other configured rules. Its
schema, exclude and extend-exclude are relative to the current directory,
like the flags of the same name, which take precedence.
--error RULE, --warn RULE and --ignore RULE set a rule's severity over
[tool.sqlty.rules] and -c; for a rule given several times, the last flag wins.
--exclude GLOB replaces exclude from the config together with the
default excludes; the config's extend-exclude still applies.
--extend-exclude GLOB adds to whichever list applies. Both repeat, and
their globs follow the rules of exclude,
relative to the current directory. --force-exclude applies the excludes to
files and directories named on the command line too, which is useful when a
tool such as pre-commit passes file names.
Exit codes¶
| Code | Meaning |
|---|---|
| 0 | Nothing to report: no error diagnostic from check, no file changed by --check or --diff, or the files were rewritten or stripped |
| 1 | check found an error-severity diagnostic, breaking found an error-severity breaking change, or rewrite / strip with --check or --diff found files that would change |
| 2 | Usage or configuration error (unknown option, unknown key or rule in [tool.sqlty], a path that does not exist), a Python file that is not UTF-8 (reported and skipped; the other files are still processed), or an internal error |
Common options¶
rewrite, strip and check accept:
| Option | Description |
|---|---|
-h, --help |
show this help message and exit |
paths ... |
Files or directories (default: .); each file uses its nearest pyproject.toml |
--dialect DIALECT |
SQL dialect: postgres, mysql, sqlite, or another sqlparser dialect such as duckdb (default: [tool.sqlty] dialect, else postgres) |
--schema SCHEMA |
Schema .sql file, directory or glob for every path; repeat for several (default: [tool.sqlty] schema per project) |
--config-file PATH |
Use this file for every path instead of the nearest pyproject.toml: [tool.sqlty] of a pyproject.toml, or top-level keys of another file such as sqlty.toml; its relative paths are relative to the current directory. Env: SQLTY_CONFIG_FILE |
--isolated |
Ignore every configuration file; use the defaults and command-line flags |
-c, --config KEY=VALUE |
Override a configuration key with a TOML assignment, for example -c 'dialect = "mysql"' or -c 'rules.select-star = "ignore"'; repeat for several. Paths are relative to the current directory |
--error RULE |
Treat RULE as severity error; repeat for several, the last flag for a rule wins |
--warn RULE |
Treat RULE as severity warn; repeat for several, the last flag for a rule wins |
--ignore RULE |
Treat RULE as severity ignore; repeat for several, the last flag for a rule wins |
--exclude GLOB |
Skip this glob while walking directories, replacing [tool.sqlty] exclude and the default excludes; repeat for several. Relative to the current directory |
--extend-exclude GLOB |
Skip this glob in addition to the other excludes; repeat for several. Relative to the current directory |
--force-exclude |
Apply the excludes to files and directories named on the command line too |
--respect-gitignore, --no-respect-gitignore |
Skip the files .gitignore ignores while walking directories (default: [tool.sqlty] respect-gitignore, else true) |
-v, --verbose |
Print each file that rewrite, strip or check --fix changes |
-q, --quiet |
Suppress non-error output |
-j, --jobs N |
Files to work on at once; 1 works on one at a time. Output is the same in any case (default: the number of processors) |
strip accepts --dialect and --schema but does not use them.
sqlty reads Python files as UTF-8. A file it changes keeps its byte order mark
and its line endings, \n or \r\n, as the first line has them.
sqlty rewrite¶
Wrap --sql strings in SQL[T](...) with the inferred row type and add the
imports the types need. Implicitly concatenated strings are wrapped as one
query. Existing wrappers are left alone. --sql strings that are not plain
literals (f-, t-, raw or bytes strings, alone or in a concatenation) are
skipped and reported on stderr (fstring-skipped, tstring-skipped,
unchecked-query) unless -q is given or the rule is set to ignore; they
never change the exit code.
sqlty rewrite [-h] [--dialect DIALECT] [--schema SCHEMA]
[--config-file PATH | --isolated] [-c KEY=VALUE]
[--error RULE] [--warn RULE] [--ignore RULE]
[--exclude GLOB] [--extend-exclude GLOB]
[--force-exclude]
[--respect-gitignore | --no-respect-gitignore] [-v] [-q]
[-j N] [--check] [--diff] [--untyped {skip,sentinel}]
[paths ...]
| Option | Description |
|---|---|
--check |
Do not modify files; print Would rewrite <file> and exit 1 if any file needs rewriting |
--diff |
Print a unified diff instead of modifying files; exit 1 if any file would change |
--untyped {skip,sentinel} |
Row-returning queries of unknown row type: leave unwrapped (skip, default) or wrap as SQL[UNTYPED](...) (sentinel). Config: untyped in [tool.sqlty] |
Exit code: see Exit codes; 1 with --check or --diff when files would change.
sqlty strip¶
Remove SQL[...](...) wrappers around --sql strings, including
hand-written row types such as SQL[User], and the sqlty import line. When the
strings can't stand alone (a comment outside them, or line breaks that only the
call's parentheses allow), the parentheses stay: SQL[T]("..." # c\n) becomes
("..." # c\n). A wrapper whose argument is not only string literals
(SQL[T]("--sql..." + x)) is left in place, as is a dotted mod.SQL[T](...),
which check still checks.
sqlty strip [-h] [--dialect DIALECT] [--schema SCHEMA]
[--config-file PATH | --isolated] [-c KEY=VALUE]
[--error RULE] [--warn RULE] [--ignore RULE]
[--exclude GLOB] [--extend-exclude GLOB] [--force-exclude]
[--respect-gitignore | --no-respect-gitignore] [-v] [-q]
[-j N] [--check] [--diff]
[paths ...]
| Option | Description |
|---|---|
--check |
Do not modify files; print Would strip <file> and exit 1 if any file would change |
--diff |
Print a unified diff instead of modifying files; exit 1 if any file would change |
Exit code: see Exit codes; 1 with --check or --diff when files would change.
sqlty check¶
Lint --sql strings and SQL[T](...) wrappers. Prints one line per
diagnostic and a summary:
queries.py:2: [narrow-type] SQL[tuple[int, str]] should be SQL[tuple[int, str, str]]
Found 1 error(s)
sqlty check [-h] [--dialect DIALECT] [--schema SCHEMA]
[--config-file PATH | --isolated] [-c KEY=VALUE]
[--error RULE] [--warn RULE] [--ignore RULE]
[--exclude GLOB] [--extend-exclude GLOB] [--force-exclude]
[--respect-gitignore | --no-respect-gitignore] [-v] [-q]
[-j N] [--fix] [--exit-zero] [--exit-zero-on-warning]
[--error-on-warning] [--statistics] [--explain]
[--output-format {text,json,github}]
[paths ...]
| Option | Description |
|---|---|
--fix |
Apply the fixable rules (see sqlty rule), write the files, then report what remains |
--exit-zero |
Exit 0 even when an error-severity diagnostic is reported |
--exit-zero-on-warning |
Exit 0 when no diagnostic has severity error (the default); cannot be combined with --error-on-warning |
--error-on-warning |
Exit 1 when any diagnostic has severity warn; cannot be combined with --exit-zero or --exit-zero-on-warning |
--statistics |
After the summary, print how many queries were typed, partially typed, unparsed and skipped |
--explain |
Under a type diagnostic, print the inference rule (id, reference, reasoning) behind each column's type and nullability |
--output-format {text,json,github} |
How diagnostics are printed: text (file:line: [rule] message, the default), json (an array of objects with file, line, rule, severity and message) or github (GitHub Actions annotations). Summaries, --statistics and --explain are text only |
With --statistics, the last line counts the queries of the checked files:
A query is typed when every column of its row type is known, and partially
typed when some column's type is not (SELECT * with no schema, an unknown
column). Unparsed queries are those the parser rejects, whether or not
unparsed-query is enabled. Skipped strings are
f-, t- and raw strings, and other --sql strings that are not plain literals,
counted by the rule that reports them. Queries that return no rows are not
counted.
Rule severities come from [tool.sqlty.rules], -c and --error/--warn/--ignore; see Rules.
Exit code: 1 if any diagnostic has severity error; warnings alone exit 0.
As in ty, --exit-zero always exits 0, --error-on-warning also exits 1 on
a warn diagnostic, and --exit-zero-on-warning keeps the default; combining
--error-on-warning with either of the others is an error (exit 2).
sqlty breaking¶
Check the queries and schema of two revisions across, as during a rolling
deploy: the queries of --against on the schema of --head (backward), and the
queries of --head on the schema of --against (forward). Prints what the other
schema breaks. --against is a git revision, a snapshot that --save wrote, a
directory of snapshots, or a schema file (see
Baselines without git history):
app/q.py:2: [unknown-column] column `bio` does not exist in `users` (backward: the queries of origin/main against the schema of the working tree)
Found 1 breaking change(s)
sqlty breaking [-h] [--against BASELINE] [--save PATH] [--head REF]
[--direction {backward,forward,both}] [-q]
[--output-format {text,json,github}]
[paths ...]
| Option | Description |
|---|---|
--against BASELINE |
The baseline: a revision (a branch, tag, SHA or HEAD~1), a snapshot written by --save, a directory of snapshots (each checked), or a schema .sql file or directory (forward only, as it has no queries) |
--save PATH |
Write a snapshot of --head (its settings, schema and Python files) to PATH, a baseline for a later --against |
--head REF |
The new revision (default: the working tree, uncommitted changes included) |
--direction {backward,forward,both} |
backward: queries of --against on the schema of --head (migrate first); forward: queries of --head on the schema of --against (deploy first) |
--output-format {text,json,github} |
How findings are printed: text (the default), json (an array of objects with file, line, rule, severity, message, direction and against) or github (GitHub Actions annotations, each at its severity) |
Exit code: 1 when a change breaks either direction with an error diagnostic, 2 for an unknown revision or
outside a git repository. See Breaking schema changes.
sqlty rule¶
Print a rule's documentation, or list every rule with its default severity.
sqlty skill¶
Print an Agent Skill for coding agents (sqlty by default), list them, or write
them all into the directory your agent reads skills from. See
Coding agents.
| Option | Description |
|---|---|
name |
Skill to print (default: sqlty) |
--install DIR |
Write every skill to DIR/NAME/ instead of printing one |
--list |
List the skills and what each is for |
sqlty version¶
Print sqlty <version> and exit. sqlty --version does the same.
Other entry points¶
| Command | Description |
|---|---|
python -m sqlty.magic FILE |
Print the source the magic codec produces for FILE |
Environment variables¶
| Variable | Effect |
|---|---|
SQLTY_CONFIG_FILE |
Default for --config-file; ignored with --isolated |
SQLTY_NO_MAGIC |
Any non-empty value (even 0) stops sqlty.magic from registering the magic codec on import |