Skip to content

Commit 8fced25

Browse files
authored
perf(cli): load command code only after argument parsing (#947)
* perf(cli): load command code only after argument parsing The CLI entry imported the dispatch, error-projection, compute-log and observability layers before parsing arguments, which pulled in compiler, planner, adapter and output models plus pydantic-settings. `sqb --version` and every `--help` paid about 0.7 s of CPU for roughly 930 modules. Parsing now loads only the parser and its type modules; the dispatch layers load after a command is parsed. The dispatch path itself no longer imports heavy models for annotations: the compile request models move beside the CLI entry models, the warehouse scope context moves out of the warehouse resolution module, provider and output annotations use lazy type aliases, and the provider base class, pydantic and rich load where they are used. Help text, parse errors, exit codes and JSON output are unchanged. A fresh-process test guards that version, help, parse errors and a plain compile do not load warehouse drivers, integrations, pydantic-settings or rich, and that parsing alone loads no compiler or pydantic modules. * test(cli): expect --version not to load the native extension
1 parent b7a179e commit 8fced25

32 files changed

Lines changed: 332 additions & 137 deletions

File tree

‎src/sqlbuild/adapter/discovery/main/session_warehouse_support.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44

55
from pathlib import Path
66

7-
from sqlbuild.adapter.discovery.main.resolve_adapter import resolve_adapter
8-
97

108
def adapter_supports_session_warehouse(*, adapter_name: str, project_dir: Path | None) -> bool:
119
"""Return whether the adapter, resolved like any project adapter, has a session warehouse."""
1210

11+
from sqlbuild.adapter.discovery.main.resolve_adapter import resolve_adapter
12+
1313
return resolve_adapter(
1414
adapter_name=adapter_name, project_dir=project_dir
1515
).supports_session_warehouse

‎src/sqlbuild/cli/commands/_helpers/compile/pipeline.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
from sqlbuild.cli.commands.types import CompileLineageMode
2727
from sqlbuild.cli.compile.models import (
2828
CompileAnalysis,
29-
CompileProfileFlags,
3029
CompileWriteResult,
3130
)
31+
from sqlbuild.cli.entry.models import CompileProfileFlags
3232
from sqlbuild.cli.output.models import (
3333
WrittenTarget,
3434
)

‎src/sqlbuild/cli/commands/_helpers/debug/checks.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
from sqlbuild.cli.commands._helpers.runtime.connection import (
1313
resolve_project_connection_config,
1414
)
15-
from sqlbuild.cli.commands._helpers.runtime.warehouses import (
16-
apply_command_warehouse,
15+
from sqlbuild.cli.commands._helpers.runtime.warehouse_scope import (
1716
command_warehouse_scope,
1817
current_command_warehouse_scope,
18+
)
19+
from sqlbuild.cli.commands._helpers.runtime.warehouses import (
20+
apply_command_warehouse,
1921
find_target_group_warehouse,
2022
resolve_command_warehouse,
2123
)

‎src/sqlbuild/cli/commands/_helpers/entry/dispatch.py‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@
77
from sqlbuild.cli.commands._helpers.diff.validation import parse_diff_name_range
88
from sqlbuild.cli.commands._helpers.entry.parsing import read_selector_file_inputs
99
from sqlbuild.cli.commands._helpers.lint.positional_paths import format_path_selectors
10-
from sqlbuild.cli.commands._helpers.runtime.warehouses import (
11-
command_warehouse_scope,
12-
validate_cli_warehouse,
13-
)
10+
from sqlbuild.cli.commands._helpers.runtime.warehouse_scope import command_warehouse_scope
1411
from sqlbuild.cli.commands.classes.cli_namespace import CliNamespace
1512
from sqlbuild.cli.commands.constants import (
1613
DBT_INIT_COMMAND,
@@ -22,12 +19,10 @@
2219
)
2320
from sqlbuild.cli.commands.exceptions import CliUserError
2421
from sqlbuild.cli.commands.types import CliCommand, CompileLineageMode
25-
from sqlbuild.cli.compile.models import (
26-
CompileCommandRequest,
27-
CompileProfileFlags,
28-
)
2922
from sqlbuild.cli.entry.models import (
3023
CliEntrypointHandlers,
24+
CompileCommandRequest,
25+
CompileProfileFlags,
3126
SelectorInputs,
3227
)
3328
from sqlbuild.compiler.lineage.types import ColumnLineageMode
@@ -37,10 +32,15 @@
3732
def dispatch_cli_command(*, args: CliNamespace, handlers: CliEntrypointHandlers) -> int:
3833
"""Route a parsed CLI namespace to its command handler and return its exit code."""
3934

40-
validate_cli_warehouse(
41-
cli_warehouse=args.warehouse,
42-
project_dir=Path.cwd() if args.project_dir is None else Path(args.project_dir).resolve(),
43-
)
35+
if args.warehouse is not None:
36+
from sqlbuild.cli.commands._helpers.runtime.warehouses import validate_cli_warehouse
37+
38+
validate_cli_warehouse(
39+
cli_warehouse=args.warehouse,
40+
project_dir=(
41+
Path.cwd() if args.project_dir is None else Path(args.project_dir).resolve()
42+
),
43+
)
4444
with command_warehouse_scope(
4545
command=args.command, cli_warehouse=args.warehouse, cli_vars=args.vars
4646
):

‎src/sqlbuild/cli/commands/_helpers/refactor/compile_facts.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
)
1111
from sqlbuild.cli.commands.models import RefactorCompile
1212
from sqlbuild.cli.commands.types import CompileLineageMode
13-
from sqlbuild.cli.compile.models import CompileAnalysis, CompileProfileFlags
13+
from sqlbuild.cli.compile.models import CompileAnalysis
14+
from sqlbuild.cli.entry.models import CompileProfileFlags
1415
from sqlbuild.compiler.compile.exceptions import CompileInputError
1516
from sqlbuild.compiler.compile.models import CompileAnalysisSelection, CompilerDiagnostic
1617
from sqlbuild.compiler.compile.types import DiagnosticPhase, DiagnosticSeverity
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Active command warehouse scope for CLI warehouse connections."""
2+
3+
from __future__ import annotations
4+
5+
from collections.abc import Iterator
6+
from contextlib import contextmanager
7+
from contextvars import ContextVar, Token
8+
9+
from sqlbuild.cli.commands.constants import COMMAND_WAREHOUSE_GROUPS
10+
from sqlbuild.cli.commands.types import CliCommand
11+
from sqlbuild.cli.entry.models import CommandWarehouseScope
12+
13+
_CURRENT_SCOPE: ContextVar[CommandWarehouseScope | None] = ContextVar(
14+
"sqlbuild_command_warehouse_scope", default=None
15+
)
16+
17+
18+
@contextmanager
19+
def command_warehouse_scope(
20+
*,
21+
command: CliCommand | str | None,
22+
cli_warehouse: str | None,
23+
cli_vars: dict[str, object] | None = None,
24+
) -> Iterator[CommandWarehouseScope]:
25+
"""Install the warehouse group, CLI override, and CLI vars for one command invocation."""
26+
27+
scope: CommandWarehouseScope = CommandWarehouseScope(
28+
group=None if command is None else COMMAND_WAREHOUSE_GROUPS.get(CliCommand(command)),
29+
cli_warehouse=cli_warehouse,
30+
cli_vars={} if cli_vars is None else dict(cli_vars),
31+
)
32+
token: Token[CommandWarehouseScope | None] = _CURRENT_SCOPE.set(scope)
33+
try:
34+
yield scope
35+
finally:
36+
_CURRENT_SCOPE.reset(token)
37+
38+
39+
def current_command_warehouse_scope() -> CommandWarehouseScope:
40+
"""Return the active command's warehouse scope, or an empty one outside the CLI."""
41+
42+
scope: CommandWarehouseScope | None = _CURRENT_SCOPE.get()
43+
return scope if scope is not None else CommandWarehouseScope(group=None)

‎src/sqlbuild/cli/commands/_helpers/runtime/warehouses.py‎

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,20 @@
33
from __future__ import annotations
44

55
import re
6-
from collections.abc import Iterator
7-
from contextlib import contextmanager
8-
from contextvars import ContextVar, Token
96
from pathlib import Path
107

118
from sqlbuild.adapter.discovery.main.session_warehouse_support import (
129
adapter_supports_session_warehouse,
1310
)
11+
from sqlbuild.cli.commands._helpers.runtime.warehouse_scope import (
12+
current_command_warehouse_scope,
13+
)
1414
from sqlbuild.cli.commands.constants import (
1515
CLI_WAREHOUSE_SOURCE,
16-
COMMAND_WAREHOUSE_GROUPS,
1716
CONNECTION_DEFAULT_WAREHOUSE_SOURCE,
1817
CONNECTION_WAREHOUSE_SOURCE,
1918
)
2019
from sqlbuild.cli.commands.exceptions import CliUserError
21-
from sqlbuild.cli.commands.types import CliCommand
2220
from sqlbuild.cli.entry.models import (
2321
AuthoredTargetWarehouse,
2422
CommandWarehouseScope,
@@ -47,37 +45,6 @@
4745
_CONNECTION_SOURCES: frozenset[str] = frozenset(
4846
{CONNECTION_WAREHOUSE_SOURCE, CONNECTION_DEFAULT_WAREHOUSE_SOURCE}
4947
)
50-
_CURRENT_SCOPE: ContextVar[CommandWarehouseScope | None] = ContextVar(
51-
"sqlbuild_command_warehouse_scope", default=None
52-
)
53-
54-
55-
@contextmanager
56-
def command_warehouse_scope(
57-
*,
58-
command: CliCommand | str | None,
59-
cli_warehouse: str | None,
60-
cli_vars: dict[str, object] | None = None,
61-
) -> Iterator[CommandWarehouseScope]:
62-
"""Install the warehouse group, CLI override, and CLI vars for one command invocation."""
63-
64-
scope: CommandWarehouseScope = CommandWarehouseScope(
65-
group=None if command is None else COMMAND_WAREHOUSE_GROUPS.get(CliCommand(command)),
66-
cli_warehouse=cli_warehouse,
67-
cli_vars={} if cli_vars is None else dict(cli_vars),
68-
)
69-
token: Token[CommandWarehouseScope | None] = _CURRENT_SCOPE.set(scope)
70-
try:
71-
yield scope
72-
finally:
73-
_CURRENT_SCOPE.reset(token)
74-
75-
76-
def current_command_warehouse_scope() -> CommandWarehouseScope:
77-
"""Return the active command's warehouse scope, or an empty one outside the CLI."""
78-
79-
scope: CommandWarehouseScope | None = _CURRENT_SCOPE.get()
80-
return scope if scope is not None else CommandWarehouseScope(group=None)
8148

8249

8350
def validate_cli_warehouse(*, cli_warehouse: str | None, project_dir: Path) -> None:

‎src/sqlbuild/cli/commands/main/entrypoint/entry.py‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,11 @@
55
import argparse
66
from collections.abc import Mapping, Sequence
77

8-
from sqlbuild.cli.commands._helpers.entry.dispatch_errors import dispatch_and_handle_errors
98
from sqlbuild.cli.commands._helpers.entry.errors import cli_error_use_color
109
from sqlbuild.cli.commands._helpers.entry.lazy_handlers import build_lazy_cli_handlers
1110
from sqlbuild.cli.commands._helpers.entry.parser import build_cli_parser
1211
from sqlbuild.cli.commands._helpers.entry.parsing import parse_cli_invocation
1312
from sqlbuild.cli.commands.classes.cli_namespace import CliNamespace
14-
from sqlbuild.cli.commands.main.entrypoint._dispatch_with_compute_logs import (
15-
dispatch_with_compute_logs,
16-
)
1713
from sqlbuild.cli.entry.models import (
1814
CliEntrypointHandlers,
1915
ParsedCliInvocation,
@@ -58,6 +54,13 @@ def _main_with_dependencies(
5854
if invocation.args is None:
5955
return invocation.exit_code if invocation.exit_code is not None else 1
6056
args: CliNamespace = invocation.args
57+
from sqlbuild.cli.commands._helpers.entry.dispatch_errors import (
58+
dispatch_and_handle_errors,
59+
)
60+
from sqlbuild.cli.commands.main.entrypoint._dispatch_with_compute_logs import (
61+
dispatch_with_compute_logs,
62+
)
63+
6164
return dispatch_with_compute_logs(
6265
args=args,
6366
identity=identity,

‎src/sqlbuild/cli/commands/main/project/_compile.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
from sqlbuild.cli.commands.types import CompileLineageMode
2626
from sqlbuild.cli.compile.models import (
2727
CompileAnalysis,
28-
CompileCommandRequest,
2928
CompileWriteResult,
3029
)
30+
from sqlbuild.cli.entry.models import CompileCommandRequest
3131
from sqlbuild.compiler.compile.models import CompileAnalysisSelection, CompilerDiagnostic
3232
from sqlbuild.compiler.compile.types import DiagnosticPhase
3333
from sqlbuild.compiler.profiling.main.collect import collect_compile_timings

‎src/sqlbuild/cli/commands/models.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@
2020
)
2121
from sqlbuild.cli.compile.models import ( # noqa: F401
2222
CompileAnalysis,
23-
CompileCommandRequest,
24-
CompileProfileFlags,
2523
CompileWriteResult,
2624
SqlTestArtifactCacheRecord,
2725
SqlTestArtifactIdentityContext,
2826
)
2927
from sqlbuild.cli.entry.models import ( # noqa: F401
3028
CliEntrypointHandlers,
29+
CompileCommandRequest,
30+
CompileProfileFlags,
3131
ParsedCliInvocation,
3232
SelectorFileSummary,
3333
SelectorInputs,

0 commit comments

Comments
 (0)