11import typing as t
22import sys
33import click
4+ from click .core import ParameterSource
45from sqlmesh_dbt .operations import DbtOperations , create
56from sqlmesh_dbt .error import cli_global_error_handler , ErrorHandlingGroup
67from pathlib import Path
7- from sqlmesh_dbt .options import YamlParamType
8- import functools
8+ from sqlmesh_dbt .options import global_options , run_options , list_options
99
1010
1111def _get_dbt_operations (
12- ctx : click .Context , vars : t .Optional [ t . Dict [str , t .Any ]], threads : t . Optional [ int ] = None
13- ) -> DbtOperations :
14- if not isinstance (ctx .obj , functools . partial ):
12+ ctx : click .Context , ** kwargs : t .Dict [str , t .Any ]
13+ ) -> t . Tuple [ DbtOperations , t . Dict [ str , t . Any ]] :
14+ if not isinstance (ctx .obj , dict ):
1515 raise ValueError (f"Unexpected click context object: { type (ctx .obj )} " )
1616
17- dbt_operations = ctx .obj (vars = vars , threads = threads )
17+ all_options = {
18+ ** kwargs ,
19+ # ctx.obj only contains global options that a user specifically provided, so these values take precedence
20+ # over **kwargs which contains all options (plus Click's defaults) whether or not they were explicitly set by a user
21+ ** ctx .obj ,
22+ }
23+
24+ T = t .TypeVar ("T" )
25+
26+ def _pop_global_option (name : str , expected_type : t .Type [T ]) -> t .Optional [T ]:
27+ value = all_options .pop (name , None )
28+ if value is not None and not isinstance (value , expected_type ):
29+ raise ValueError (
30+ f"Expecting option '{ name } ' to be type '{ expected_type } ', however it was '{ type (value )} '"
31+ )
32+ return value
33+
34+ dbt_operations = create (
35+ project_dir = _pop_global_option ("project_dir" , Path ),
36+ profiles_dir = _pop_global_option ("profiles_dir" , Path ),
37+ profile = _pop_global_option ("profile" , str ),
38+ target = _pop_global_option ("target" , str ),
39+ vars = _pop_global_option ("vars" , dict ),
40+ threads = _pop_global_option ("threads" , int ),
41+ debug = _pop_global_option ("debug" , bool ) or False ,
42+ log_level = _pop_global_option ("log_level" , str ),
43+ )
1844
1945 if not isinstance (dbt_operations , DbtOperations ):
2046 raise ValueError (f"Unexpected dbt operations type: { type (dbt_operations )} " )
@@ -23,88 +49,15 @@ def _get_dbt_operations(
2349 def _cleanup () -> None :
2450 dbt_operations .close ()
2551
26- return dbt_operations
27-
28-
29- vars_option = click .option (
30- "--vars" ,
31- type = YamlParamType (),
32- help = "Supply variables to the project. This argument overrides variables defined in your dbt_project.yml file. This argument should be a YAML string, eg. '{my_variable: my_value}'" ,
33- )
34-
35-
36- select_option = click .option (
37- "-s" ,
38- "--select" ,
39- multiple = True ,
40- help = "Specify the nodes to include." ,
41- )
42- model_option = click .option (
43- "-m" ,
44- "--models" ,
45- "--model" ,
46- multiple = True ,
47- help = "Specify the model nodes to include; other nodes are excluded." ,
48- )
49- exclude_option = click .option ("--exclude" , multiple = True , help = "Specify the nodes to exclude." )
50-
51- # TODO: expand this out into --resource-type/--resource-types and --exclude-resource-type/--exclude-resource-types
52- resource_types = [
53- "metric" ,
54- "semantic_model" ,
55- "saved_query" ,
56- "source" ,
57- "analysis" ,
58- "model" ,
59- "test" ,
60- "unit_test" ,
61- "exposure" ,
62- "snapshot" ,
63- "seed" ,
64- "default" ,
65- "all" ,
66- ]
67- resource_type_option = click .option (
68- "--resource-type" , type = click .Choice (resource_types , case_sensitive = False )
69- )
52+ # at this point, :all_options just contains what's left because we popped the global options
53+ return dbt_operations , all_options
7054
7155
7256@click .group (cls = ErrorHandlingGroup , invoke_without_command = True )
73- @click .option ("--profile" , help = "Which existing profile to load. Overrides output.profile" )
74- @click .option ("-t" , "--target" , help = "Which target to load for the given profile" )
75- @click .option (
76- "-d" ,
77- "--debug/--no-debug" ,
78- default = False ,
79- help = "Display debug logging during dbt execution. Useful for debugging and making bug reports events to help when debugging." ,
80- )
81- @click .option (
82- "--log-level" ,
83- default = "info" ,
84- type = click .Choice (["debug" , "info" , "warn" , "error" , "none" ]),
85- help = "Specify the minimum severity of events that are logged to the console and the log file." ,
86- )
87- @click .option (
88- "--profiles-dir" ,
89- type = click .Path (exists = True , file_okay = False , path_type = Path ),
90- help = "Which directory to look in for the profiles.yml file. If not set, dbt will look in the current working directory first, then HOME/.dbt/" ,
91- )
92- @click .option (
93- "--project-dir" ,
94- type = click .Path (exists = True , file_okay = False , path_type = Path ),
95- help = "Which directory to look in for the dbt_project.yml file. Default is the current working directory and its parents." ,
96- )
57+ @global_options
9758@click .pass_context
9859@cli_global_error_handler
99- def dbt (
100- ctx : click .Context ,
101- profile : t .Optional [str ] = None ,
102- target : t .Optional [str ] = None ,
103- debug : bool = False ,
104- log_level : t .Optional [str ] = None ,
105- profiles_dir : t .Optional [Path ] = None ,
106- project_dir : t .Optional [Path ] = None ,
107- ) -> None :
60+ def dbt (ctx : click .Context , ** kwargs : t .Any ) -> None :
10861 """
10962 An ELT tool for managing your SQL transformations and data models, powered by the SQLMesh engine.
11063 """
@@ -113,76 +66,45 @@ def dbt(
11366 # we dont need to import sqlmesh/load the project for CLI help
11467 return
11568
116- # we have a partially applied function here because subcommands might set extra options like --vars
117- # that need to be known before we attempt to load the project
118- ctx .obj = functools .partial (
119- create ,
120- project_dir = project_dir ,
121- profiles_dir = profiles_dir ,
122- profile = profile ,
123- target = target ,
124- debug = debug ,
125- log_level = log_level ,
126- )
69+ # only keep track of the global options that have been explicitly set
70+ # the subcommand handlers get invoked with the default values of the global options (even if the option is specified top level)
71+ # so we capture any explicitly set options to use as overrides in the subcommand handlers
72+ ctx .obj = {
73+ k : v for k , v in kwargs .items () if ctx .get_parameter_source (k ) != ParameterSource .DEFAULT
74+ }
12775
12876 if not ctx .invoked_subcommand :
129- if profile or target :
77+ if " profile" in ctx . obj or " target" in ctx . obj :
13078 # trigger a project load to validate the specified profile / target
131- ctx . obj ( )
79+ _get_dbt_operations ( ctx )
13280
13381 click .echo (
13482 f"No command specified. Run `{ ctx .info_name } --help` to see the available commands."
13583 )
13684
13785
13886@dbt .command ()
139- @select_option
140- @model_option
141- @exclude_option
142- @resource_type_option
143- @click .option (
144- "-f" ,
145- "--full-refresh" ,
146- is_flag = True ,
147- default = False ,
148- help = "If specified, sqlmesh will drop incremental models and fully-recalculate the incremental table from the model definition." ,
149- )
150- @click .option (
151- "--env" ,
152- "--environment" ,
153- help = "Run against a specific Virtual Data Environment (VDE) instead of the main environment" ,
154- )
155- @click .option (
156- "--empty/--no-empty" , default = False , help = "If specified, limit input refs and sources"
157- )
158- @click .option (
159- "--threads" ,
160- type = int ,
161- help = "Specify number of threads to use while executing models. Overrides settings in profiles.yml." ,
162- )
163- @vars_option
87+ @global_options
88+ @run_options
16489@click .pass_context
16590def run (
16691 ctx : click .Context ,
167- vars : t .Optional [t .Dict [str , t .Any ]],
168- threads : t .Optional [int ],
16992 env : t .Optional [str ] = None ,
17093 ** kwargs : t .Any ,
17194) -> None :
17295 """Compile SQL and execute against the current target database."""
173- _get_dbt_operations (ctx , vars , threads ).run (environment = env , ** kwargs )
96+ ops , remaining_kwargs = _get_dbt_operations (ctx , ** kwargs )
97+ ops .run (environment = env , ** remaining_kwargs )
17498
17599
176100@dbt .command (name = "list" )
177- @select_option
178- @model_option
179- @exclude_option
180- @resource_type_option
181- @vars_option
101+ @global_options
102+ @list_options
182103@click .pass_context
183- def list_ (ctx : click .Context , vars : t . Optional [ t . Dict [ str , t . Any ]], ** kwargs : t .Any ) -> None :
104+ def list_ (ctx : click .Context , ** kwargs : t .Any ) -> None :
184105 """List the resources in your project"""
185- _get_dbt_operations (ctx , vars ).list_ (** kwargs )
106+ ops , remaining_kwargs = _get_dbt_operations (ctx , ** kwargs )
107+ ops .list_ (** remaining_kwargs )
186108
187109
188110@dbt .command (name = "ls" , hidden = True ) # hidden alias for list
0 commit comments