CLI reference¶
The semolina command-line tool provides warehouse introspection and code
generation utilities. It is installed as a console script with the
semolina package, and its own dependencies come with the cli extra:
pip install "semolina[cli]"
Without the extra, the command prints that install line and exits with code 2.
$ semolina [OPTIONS] COMMAND [ARGS]...
Global options¶
--versionPrint the installed version and exit.
--helpShow the help message and exit.
semolina codegen¶
Introspect warehouse semantic views and generate
SemanticView model classes as Python source code.
$ semolina codegen [OPTIONS] VIEWS...
Arguments¶
VIEWSOne or more schema-qualified view names to introspect (e.g.
my_schema.sales_view). Required.
Options¶
--backend,-bTEXTBackend to connect to. Accepts one of:
snowflake– use the built-in Snowflake backenddatabricks– use the built-in Databricks backendduckdb– use the built-in DuckDB backendA dotted import path (e.g.
mypackage.backends.CustomEngine) – dynamically imported and instantiated with no arguments
Required.
--database,-dTEXTPath to a DuckDB database file. Only used with
--backend duckdb, where it is required. Falls back to theDUCKDB_DATABASEenvironment variable if not provided; with neither set, the command exits2.--output,-oPATHWrite the generated module to this file instead of stdout. Nothing is written until every view has rendered, and the file is replaced atomically, so a failed run leaves it untouched. The directory must exist. If the directory refuses new files but the target is writable, the target is written in place instead. With
--check, the committed file to check.--checkCompare a committed model’s annotations against the warehouse instead of generating one. Reads the file
--outputnames. Writes a per-field report to stderr and nothing to stdout. See Check a committed model for drift.--modelPATHOlder spelling of
--outputfor--check. Passing it without--check, or together with--output, exits2.
Exit codes¶
Code |
Meaning |
|---|---|
0 |
Success |
1 |
Unexpected error, including an unparseable committed file under |
2 |
Invalid option: an unrecognized or omitted |
3 |
View not found in the warehouse |
4 |
Connection or authentication failure
( |
5 |
Annotation drift found by |
6 |
The warehouse was reached but could not describe a view
( |
Environment variables¶
codegen reads the [connections.<backend>] section of .semolina.toml first, then
environment variables, then a .env file. That is not the same section
create_engine() reads: it defaults to [connections.default] and
takes any section name you pass, while codegen always looks under the backend’s own name.
--backend duckdb reads no section at all and takes --database or DUCKDB_DATABASE.
See How to configure codegen credentials for the full chain.
The variable names below are the config field names with a backend prefix. Which ones you need depends on the backend.
Variable |
Description |
|---|---|
|
Account identifier (e.g. |
|
Username |
|
Password. Use this or the key-pair variables below. |
|
Path to a PKCS1 or PKCS8 private key file (key-pair auth) |
|
Passphrase for an encrypted private key |
|
Database name. Needed to resolve the view name. |
|
Warehouse name. Needed to run the introspection query. |
|
Role name (optional) |
Variable |
Description |
|---|---|
|
Workspace hostname |
|
SQL warehouse HTTP path |
|
Personal access token |
|
Unity Catalog name (optional) |
Variable |
Description |
|---|---|
|
Path to DuckDB database file (fallback for |
A .env file in the working directory is read automatically. Set
SEMOLINA_ENV_FILE to read a different path instead. It fills only the fields
the TOML section and the environment leave unset.
Output¶
Generated Python source is written to stdout, or to the file --output
names. Diagnostic messages (errors, warnings) go to stderr. The two routes give
the same bytes:
$ semolina codegen my_schema.sales_view -b snowflake -o models.py
$ semolina codegen my_schema.sales_view -b snowflake > models.py
With the codegen-lint extra, the module is formatted with ruff’s default
settings (--isolated), whatever directory you run from.
The output contains one SemanticView subclass per
introspected view, with typed Metric,
Dimension, and Fact fields.
semolina codegen-dto¶
Probe a query and generate a Pydantic result DTO as Python source code, typed for the columns that query returns on that backend. Every result column carries its field’s name, so the fields are bare and the same class reads results from any backend. See How to generate a DTO class with semolina codegen-dto.
$ semolina codegen-dto [OPTIONS] [QUERY_PATHS]...
Which query to probe comes from exactly one of three routes, and giving more
than one exits 2:
one or more
QUERY_PATHS--viewwith--metricsand/or--dimensionsthe
[tool.semolina.dto]section ofpyproject.toml, used when neither of the other two is given
Arguments¶
QUERY_PATHSZero or more dotted paths to module-level query objects (e.g.
myapp.queries.revenue_by_region). Resolving one imports the module that holds it, which runs that module. The working directory is appended tosys.path. Several paths are emitted into one output block.
Options¶
--backend,-bTEXTBackend to connect to. Takes the same values as
codegen’s--backend:snowflake– use the built-in Snowflake backenddatabricks– use the built-in Databricks backendduckdb– use the built-in DuckDB backendA dotted import path (e.g.
mypackage.backends.CustomEngine) – dynamically imported and instantiated with no arguments
Required unless
[tool.semolina.dto]setsbackend. The flag wins over the config.--database,-dTEXTPath to a DuckDB database file. Only used with
--backend duckdb, where it is required. Falls back to theDUCKDB_DATABASEenvironment variable, then to the config’sdatabase.--viewTEXTGenerate from a view name and a field list instead of an importable query. Needs
--metricsand/or--dimensions, and imports nothing. Cannot be combined withQUERY_PATHSor--config.--metricsTEXTMetric field names for
--view. Repeatable, and each value may be a comma-separated list. Each name is both the warehouse field name and the generated attribute name, so it must be a plain Python identifier, not a Python keyword or soft keyword, and not one of the names the query builder reserves:query,metrics,dimensions,where,filter,order_by,limit,execute,to_sql,using,keys,values,items,get,pop,update,clear. Anything else exits2, naming the offending value.--dimensionsTEXTDimension field names for
--view, under the same rules as--metrics.--nameTEXTOverride the generated class name, which otherwise comes from the query attribute (
revenue_by_regionbecomesRevenueByRegion) or from the view’s last segment (analytics.salesbecomesSales). Renames a single class, so it takes a single query; passing it with more than one, or with--config, exits2.--output,-oPATHWrite the generated module to this file instead of stdout. The directory must already exist. The file is written once, after every class has rendered, so a failed run leaves a previously generated file untouched. Overrides the config’s
output.--checkCompare the committed DTO file against the warehouse’s current result schema instead of generating. Writes nothing to stdout, reports per field on stderr, and exits
5on drift. Reads the file--outputnames (or the config’soutput); with neither, exits2. Annotations are compared, and so is the result column each field binds (itsvalidation_alias, or its own name when it has none). Every backend names a column after its field, so a file generated against another backend drifts only where an annotation differs. A generatedAnyagrees with whatever annotation you replaced it with.--configPATHRead
[tool.semolina.dto]from this file instead of./pyproject.toml. The file must exist and must carry the section. Cannot be combined withQUERY_PATHSor--view.
Configuration¶
With no QUERY_PATHS and no --view, the command generates what
pyproject.toml declares:
[tool.semolina.dto]
backend = "snowflake"
database = "sales.db"
output = "myapp/dtos.py"
[[tool.semolina.dto.entries]]
query = "myapp.queries.revenue_by_region"
[[tool.semolina.dto.entries]]
name = "TopProducts"
view = "analytics.products"
metrics = ["units_sold"]
dimensions = ["product_name"]
backend, database and output are optional and are overridden by the
matching flags. Each [[tool.semolina.dto.entries]] table carries either
query or view (with metrics and dimensions), never both, plus an
optional name. Classes are emitted in the order the file declares them.
Relative output and database paths resolve against the directory holding
the config file, not against the working directory. Unrecognized keys are
errors, not ignored.
Exit codes¶
Code |
Meaning |
|---|---|
0 |
Success |
1 |
Unexpected error, including generated source that does not parse, an
installed ruff that fails on it, or a committed file under |
2 |
Invalid option: an unrecognized or omitted |
3 |
View not found, as reported by a custom |
4 |
Connection or authentication failure |
5 |
Annotation drift – a committed DTO no longer matches the result schema |
6 |
Probe failed, or a projected field matched no result column – no DTO was written |
5 means here what it means for codegen --check: a committed generated
file no longer matches the result schema. Only a --check run returns it.
4 is a connection failure on every backend. The built-in backends report a view
that does not exist, like any other query the warehouse refuses to describe, as 6;
3 comes only from a custom --backend engine that raises
SemolinaViewNotFoundError.
Environment variables¶
codegen-dto reads credentials from the same sources, in the same order, as
codegen. See the table above.
Output¶
Generated Python source is written to stdout unless --output (or the
config’s output) names a file. Diagnostic messages always go to stderr, so
redirecting stdout also works:
$ semolina codegen-dto myapp.queries.revenue_by_region -b snowflake -o dtos.py
$ semolina codegen-dto myapp.queries.revenue_by_region -b snowflake > dtos.py
The output contains one pydantic.BaseModel subclass per query, over a single
shared import block, with a provenance header naming the backend that was probed
and, per class, where the query came from. No row of your data is fetched.
See also¶
How to generate Semolina model classes from warehouse views – how to generate models from your warehouse
How to generate a DTO class with semolina codegen-dto – how to generate a result DTO from a query
How to configure codegen credentials – credential configuration for codegen
How to define models – understanding the generated model classes