Skip to content

sqlmesh format should not fully load the project when paths are given #6025

Description

@cmgoffena13

Summary

sqlmesh format already accepts positional paths and does not load state. With paths it still fully loads the project, then formats only the matching .sql models and audits. That load is wasted: format pretty-prints file text using dialect and format config. It does not need other models.

Current behavior

sqlmesh format models/orders.sql

Constructs Context with load=True, loads every model, then filters with Path.samefile.

Proposed behavior

sqlmesh format models/a.sql audits/unique_ids.sql
  • Skip Context.load() when positional paths are present
  • Format only those files if they are SQL models or standalone audits
  • Honor formatting false
  • Take dialect and format: from the file’s project config and MODEL/AUDIT DDL
  • Python model paths: ignore
  • macros/*.sql and other non-model SQL: no-op (same as today)
  • No paths: still format all SQL models and audits (unchanged)

Acceptance

  • sqlmesh format models/a.sql does not parse unrelated models
  • Formatted output for that file matches today’s formatter
  • sqlmesh format with no args is unchanged
  • Unknown or non-model SQL paths are not rewritten

Activity

  1. tripleaceme commented on Sep 13, 2026

    @tripleaceme
    Contributor

    I'd like to take this one if it's free — unassigned with no linked PR right now.

    Plan: skip Context.load() when positional paths are given and format those files directly. _format only needs three things from the loaded object — _path, dialect and formatting — and all three are available from the file's own MODEL/AUDIT header plus config_for_path, which resolves a project config from a path without loading anything. is_meta_expression on the first parsed expression is what separates a model or audit file from macros/*.sql, so non-model SQL stays a no-op without needing the project graph.

    One behavioural question before I start. Today "is this file a model?" is answered by membership in the loaded project. Without the load it would be answered by parsing the header, and those two differ for a .sql file that has a MODEL header but is excluded from the project — sitting outside models/, or matched by ignore_patterns. Today sqlmesh format path/to/that.sql skips it; a pure header check would format it.

    I'd rather preserve today's behaviour and still honour ignore_patterns and the models//audits/ directories, since both are cheap to check against the config without loading. Say if you'd prefer the simpler "any file with a MODEL/AUDIT header gets formatted" instead.

    Also flagging that #5944 touches Context.format() as well. It's been quiet since August — I'll keep this change confined to the load path so the two stay separable.

  2. tripleaceme commented on Sep 13, 2026

    @tripleaceme
    Contributor

    One finding while mapping this out, since it affects the spec above.

    The acceptance says to format a path "if [it is] a SQL model or standalone audit", but standalone audits aren't formatted today. Context.format iterates chain(self._models.values(), self._audits.values()), and an AUDIT (..., standalone true) is loaded into self._standalone_audits, which is a separate dict. So it's skipped.

    Reproduced on main with a project holding one model and two audit files:

    audits/ma.sql  (AUDIT(name ma, dialect 'duckdb'))                   -> reformatted
    audits/sa.sql  (AUDIT(name sa, dialect 'duckdb', standalone true))  -> byte-identical, untouched
    

    So "format only those paths that are models or standalone audits" is a behaviour change on top of the load change, not just a restatement of what happens now.

    Happy to include it — the header-based check I described treats both kinds of AUDIT the same, so standalone audits would start being formatted, which I think is what you want given the wording. But it's a separate change in effect, so tell me if you'd rather I preserve the current skip and let the standalone-audit gap be its own issue. I'll keep it isolated in the diff either way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions