sqlmesh 0.193.1.dev1__py3-none-any.whl → 0.193.1.dev2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sqlmesh/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.193.1.dev1'
21
- __version_tuple__ = version_tuple = (0, 193, 1, 'dev1')
20
+ __version__ = version = '0.193.1.dev2'
21
+ __version_tuple__ = version_tuple = (0, 193, 1, 'dev2')
sqlmesh/cli/main.py CHANGED
@@ -33,6 +33,7 @@ SKIP_LOAD_COMMANDS = (
33
33
  "rollback",
34
34
  "run",
35
35
  "table_name",
36
+ "dbt",
36
37
  )
37
38
  SKIP_CONTEXT_COMMANDS = ("init", "ui")
38
39
 
@@ -1219,3 +1220,39 @@ def state_import(obj: Context, input_file: Path, replace: bool, no_confirm: bool
1219
1220
  """Import a state export file back into the state database"""
1220
1221
  confirm = not no_confirm
1221
1222
  obj.import_state(input_file=input_file, clear=replace, confirm=confirm)
1223
+
1224
+
1225
+ @cli.group(no_args_is_help=True, hidden=True)
1226
+ def dbt() -> None:
1227
+ """Commands for doing dbt-specific things"""
1228
+ pass
1229
+
1230
+
1231
+ @dbt.command("convert")
1232
+ @click.option(
1233
+ "-i",
1234
+ "--input-dir",
1235
+ help="Path to the DBT project",
1236
+ required=True,
1237
+ type=click.Path(exists=True, dir_okay=True, file_okay=False, readable=True, path_type=Path),
1238
+ )
1239
+ @click.option(
1240
+ "-o",
1241
+ "--output-dir",
1242
+ required=True,
1243
+ help="Path to write out the converted SQLMesh project",
1244
+ type=click.Path(exists=False, dir_okay=True, file_okay=False, readable=True, path_type=Path),
1245
+ )
1246
+ @click.option("--no-prompts", is_flag=True, help="Disable interactive prompts", default=False)
1247
+ @click.pass_obj
1248
+ @error_handler
1249
+ @cli_analytics
1250
+ def dbt_convert(obj: Context, input_dir: Path, output_dir: Path, no_prompts: bool) -> None:
1251
+ """Convert a DBT project to a SQLMesh project"""
1252
+ from sqlmesh.dbt.converter.convert import convert_project_files
1253
+
1254
+ convert_project_files(
1255
+ input_dir.absolute(),
1256
+ output_dir.absolute(),
1257
+ no_prompts=no_prompts,
1258
+ )
@@ -39,7 +39,7 @@ from sqlmesh.core.config.scheduler import (
39
39
  scheduler_config_validator,
40
40
  )
41
41
  from sqlmesh.core.config.ui import UIConfig
42
- from sqlmesh.core.loader import Loader, SqlMeshLoader
42
+ from sqlmesh.core.loader import Loader, SqlMeshLoader, MigratedDbtProjectLoader
43
43
  from sqlmesh.core.notification_target import NotificationTarget
44
44
  from sqlmesh.core.user import User
45
45
  from sqlmesh.utils.date import to_timestamp, now
@@ -219,6 +219,13 @@ class Config(BaseConfig):
219
219
  f"^{k}$": v for k, v in physical_schema_override.items()
220
220
  }
221
221
 
222
+ if (
223
+ (variables := data.get("variables", ""))
224
+ and isinstance(variables, dict)
225
+ and c.MIGRATED_DBT_PROJECT_NAME in variables
226
+ ):
227
+ data["loader"] = MigratedDbtProjectLoader
228
+
222
229
  return data
223
230
 
224
231
  @model_validator(mode="after")
sqlmesh/core/constants.py CHANGED
@@ -31,6 +31,9 @@ DEFAULT_TIME_COLUMN_FORMAT = "%Y-%m-%d"
31
31
  MAX_MODEL_DEFINITION_SIZE = 10000
32
32
  """Maximum number of characters in a model definition"""
33
33
 
34
+ MIGRATED_DBT_PROJECT_NAME = "__dbt_project_name__"
35
+ MIGRATED_DBT_PACKAGES = "__dbt_packages__"
36
+
34
37
 
35
38
  # The maximum number of fork processes, used for loading projects
36
39
  # None means default to process pool, 1 means don't fork, :N is number of processes
sqlmesh/core/loader.py CHANGED
@@ -38,7 +38,11 @@ from sqlmesh.core.signal import signal
38
38
  from sqlmesh.core.test import ModelTestMetadata, filter_tests_by_patterns
39
39
  from sqlmesh.utils import UniqueKeyDict, sys_path
40
40
  from sqlmesh.utils.errors import ConfigError
41
- from sqlmesh.utils.jinja import JinjaMacroRegistry, MacroExtractor
41
+ from sqlmesh.utils.jinja import (
42
+ JinjaMacroRegistry,
43
+ MacroExtractor,
44
+ SQLMESH_DBT_COMPATIBILITY_PACKAGE,
45
+ )
42
46
  from sqlmesh.utils.metaprogramming import import_python_file
43
47
  from sqlmesh.utils.pydantic import validation_error_message
44
48
  from sqlmesh.utils.process import create_process_pool_executor
@@ -548,6 +552,7 @@ class SqlMeshLoader(Loader):
548
552
  signals: UniqueKeyDict[str, signal],
549
553
  cache: CacheBase,
550
554
  gateway: t.Optional[str],
555
+ loading_default_kwargs: t.Optional[t.Dict[str, t.Any]] = None,
551
556
  ) -> UniqueKeyDict[str, Model]:
552
557
  """Loads the sql models into a Dict"""
553
558
  models: UniqueKeyDict[str, Model] = UniqueKeyDict("models")
@@ -590,6 +595,7 @@ class SqlMeshLoader(Loader):
590
595
  infer_names=self.config.model_naming.infer_names,
591
596
  signal_definitions=signals,
592
597
  default_catalog_per_gateway=self.context.default_catalog_per_gateway,
598
+ **loading_default_kwargs or {},
593
599
  )
594
600
 
595
601
  with create_process_pool_executor(
@@ -942,3 +948,104 @@ class SqlMeshLoader(Loader):
942
948
  self._loader.context.gateway or self._loader.config.default_gateway_name,
943
949
  ]
944
950
  )
951
+
952
+
953
+ class MigratedDbtProjectLoader(SqlMeshLoader):
954
+ @property
955
+ def migrated_dbt_project_name(self) -> str:
956
+ return self.config.variables[c.MIGRATED_DBT_PROJECT_NAME]
957
+
958
+ def _load_scripts(self) -> t.Tuple[MacroRegistry, JinjaMacroRegistry]:
959
+ from sqlmesh.dbt.converter.common import infer_dbt_package_from_path
960
+ from sqlmesh.dbt.target import TARGET_TYPE_TO_CONFIG_CLASS
961
+
962
+ # Store a copy of the macro registry
963
+ standard_macros = macro.get_registry()
964
+
965
+ jinja_macros = JinjaMacroRegistry(
966
+ create_builtins_module=SQLMESH_DBT_COMPATIBILITY_PACKAGE,
967
+ top_level_packages=["dbt", self.migrated_dbt_project_name],
968
+ )
969
+ extractor = MacroExtractor()
970
+
971
+ macros_max_mtime: t.Optional[float] = None
972
+
973
+ for path in self._glob_paths(
974
+ self.config_path / c.MACROS,
975
+ ignore_patterns=self.config.ignore_patterns,
976
+ extension=".py",
977
+ ):
978
+ if import_python_file(path, self.config_path):
979
+ self._track_file(path)
980
+ macro_file_mtime = self._path_mtimes[path]
981
+ macros_max_mtime = (
982
+ max(macros_max_mtime, macro_file_mtime)
983
+ if macros_max_mtime
984
+ else macro_file_mtime
985
+ )
986
+
987
+ for path in self._glob_paths(
988
+ self.config_path / c.MACROS,
989
+ ignore_patterns=self.config.ignore_patterns,
990
+ extension=".sql",
991
+ ):
992
+ self._track_file(path)
993
+ macro_file_mtime = self._path_mtimes[path]
994
+ macros_max_mtime = (
995
+ max(macros_max_mtime, macro_file_mtime) if macros_max_mtime else macro_file_mtime
996
+ )
997
+
998
+ with open(path, "r", encoding="utf-8") as file:
999
+ try:
1000
+ package = infer_dbt_package_from_path(path) or self.migrated_dbt_project_name
1001
+
1002
+ jinja_macros.add_macros(
1003
+ extractor.extract(file.read(), dialect=self.config.model_defaults.dialect),
1004
+ package=package,
1005
+ )
1006
+ except Exception as e:
1007
+ raise ConfigError(f"Failed to load macro file: {path}", e)
1008
+
1009
+ self._macros_max_mtime = macros_max_mtime
1010
+
1011
+ macros = macro.get_registry()
1012
+ macro.set_registry(standard_macros)
1013
+
1014
+ connection_config = self.context.connection_config
1015
+ # this triggers the DBT create_builtins_module to have a `target` property which is required for a bunch of DBT macros to work
1016
+ if dbt_config_type := TARGET_TYPE_TO_CONFIG_CLASS.get(connection_config.type_):
1017
+ try:
1018
+ jinja_macros.add_globals(
1019
+ {
1020
+ "target": dbt_config_type.from_sqlmesh(
1021
+ connection_config,
1022
+ name=self.config.default_gateway_name,
1023
+ ).attribute_dict()
1024
+ }
1025
+ )
1026
+ except NotImplementedError:
1027
+ raise ConfigError(f"Unsupported dbt target type: {connection_config.type_}")
1028
+
1029
+ return macros, jinja_macros
1030
+
1031
+ def _load_sql_models(
1032
+ self,
1033
+ macros: MacroRegistry,
1034
+ jinja_macros: JinjaMacroRegistry,
1035
+ audits: UniqueKeyDict[str, ModelAudit],
1036
+ signals: UniqueKeyDict[str, signal],
1037
+ cache: CacheBase,
1038
+ gateway: t.Optional[str],
1039
+ loading_default_kwargs: t.Optional[t.Dict[str, t.Any]] = None,
1040
+ ) -> UniqueKeyDict[str, Model]:
1041
+ return super()._load_sql_models(
1042
+ macros=macros,
1043
+ jinja_macros=jinja_macros,
1044
+ audits=audits,
1045
+ signals=signals,
1046
+ cache=cache,
1047
+ gateway=gateway,
1048
+ loading_default_kwargs=dict(
1049
+ migrated_dbt_project_name=self.migrated_dbt_project_name,
1050
+ ),
1051
+ )
@@ -2017,6 +2017,7 @@ def load_sql_based_model(
2017
2017
  variables: t.Optional[t.Dict[str, t.Any]] = None,
2018
2018
  infer_names: t.Optional[bool] = False,
2019
2019
  blueprint_variables: t.Optional[t.Dict[str, t.Any]] = None,
2020
+ migrated_dbt_project_name: t.Optional[str] = None,
2020
2021
  **kwargs: t.Any,
2021
2022
  ) -> Model:
2022
2023
  """Load a model from a parsed SQLMesh model SQL file.
@@ -2193,6 +2194,7 @@ Learn more at https://sqlmesh.readthedocs.io/en/stable/concepts/models/overview
2193
2194
  query_or_seed_insert,
2194
2195
  kind=kind,
2195
2196
  time_column_format=time_column_format,
2197
+ migrated_dbt_project_name=migrated_dbt_project_name,
2196
2198
  **common_kwargs,
2197
2199
  )
2198
2200
 
@@ -2400,6 +2402,7 @@ def _create_model(
2400
2402
  signal_definitions: t.Optional[SignalRegistry] = None,
2401
2403
  variables: t.Optional[t.Dict[str, t.Any]] = None,
2402
2404
  blueprint_variables: t.Optional[t.Dict[str, t.Any]] = None,
2405
+ migrated_dbt_project_name: t.Optional[str] = None,
2403
2406
  **kwargs: t.Any,
2404
2407
  ) -> Model:
2405
2408
  validate_extra_and_required_fields(
@@ -2455,13 +2458,28 @@ def _create_model(
2455
2458
 
2456
2459
  if jinja_macros:
2457
2460
  jinja_macros = (
2458
- jinja_macros if jinja_macros.trimmed else jinja_macros.trim(jinja_macro_references)
2461
+ jinja_macros
2462
+ if jinja_macros.trimmed
2463
+ else jinja_macros.trim(jinja_macro_references, package=migrated_dbt_project_name)
2459
2464
  )
2460
2465
  else:
2461
2466
  jinja_macros = JinjaMacroRegistry()
2462
2467
 
2463
- for jinja_macro in jinja_macros.root_macros.values():
2464
- used_variables.update(extract_macro_references_and_variables(jinja_macro.definition)[1])
2468
+ if migrated_dbt_project_name:
2469
+ # extract {{ var() }} references used in all jinja macro dependencies to check for any variables specific
2470
+ # to a migrated DBT package and resolve them accordingly
2471
+ # vars are added into __sqlmesh_vars__ in the Python env so that the native SQLMesh var() function can resolve them
2472
+ variables = variables or {}
2473
+
2474
+ nested_macro_used_variables, flattened_package_variables = (
2475
+ _extract_migrated_dbt_variable_references(jinja_macros, variables)
2476
+ )
2477
+
2478
+ used_variables.update(nested_macro_used_variables)
2479
+ variables.update(flattened_package_variables)
2480
+ else:
2481
+ for jinja_macro in jinja_macros.root_macros.values():
2482
+ used_variables.update(extract_macro_references_and_variables(jinja_macro.definition)[1])
2465
2483
 
2466
2484
  model = klass(
2467
2485
  name=name,
@@ -2844,7 +2862,7 @@ META_FIELD_CONVERTER: t.Dict[str, t.Callable] = {
2844
2862
  "cron_tz": lambda value: exp.Literal.string(value),
2845
2863
  "partitioned_by_": _single_expr_or_tuple,
2846
2864
  "clustered_by": _single_expr_or_tuple,
2847
- "depends_on_": lambda value: exp.Tuple(expressions=sorted(value)),
2865
+ "depends_on_": lambda value: exp.Tuple(expressions=sorted(value)) if value else "()",
2848
2866
  "pre": _list_of_calls_to_exp,
2849
2867
  "post": _list_of_calls_to_exp,
2850
2868
  "audits": _list_of_calls_to_exp,
@@ -2915,4 +2933,37 @@ def clickhouse_partition_func(
2915
2933
  )
2916
2934
 
2917
2935
 
2936
+ def _extract_migrated_dbt_variable_references(
2937
+ jinja_macros: JinjaMacroRegistry, project_variables: t.Dict[str, t.Any]
2938
+ ) -> t.Tuple[t.Set[str], t.Dict[str, t.Any]]:
2939
+ if not jinja_macros.trimmed:
2940
+ raise ValueError("Expecting a trimmed JinjaMacroRegistry")
2941
+
2942
+ used_variables = set()
2943
+ # note: JinjaMacroRegistry is trimmed here so "all_macros" should be just be all the macros used by this model
2944
+ for _, _, jinja_macro in jinja_macros.all_macros:
2945
+ _, extracted_variable_names = extract_macro_references_and_variables(jinja_macro.definition)
2946
+ used_variables.update(extracted_variable_names)
2947
+
2948
+ flattened = {}
2949
+ if (dbt_package_variables := project_variables.get(c.MIGRATED_DBT_PACKAGES)) and isinstance(
2950
+ dbt_package_variables, dict
2951
+ ):
2952
+ # flatten the nested dict structure from the migrated dbt package variables in the SQLmesh config into __dbt_packages.<package>.<variable>
2953
+ # to match what extract_macro_references_and_variables() returns. This allows the usage checks in create_python_env() to work
2954
+ def _flatten(prefix: str, root: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
2955
+ acc = {}
2956
+ for k, v in root.items():
2957
+ key_with_prefix = f"{prefix}.{k}"
2958
+ if isinstance(v, dict):
2959
+ acc.update(_flatten(key_with_prefix, v))
2960
+ else:
2961
+ acc[key_with_prefix] = v
2962
+ return acc
2963
+
2964
+ flattened = _flatten(c.MIGRATED_DBT_PACKAGES, dbt_package_variables)
2965
+
2966
+ return used_variables, flattened
2967
+
2968
+
2918
2969
  TIME_COL_PARTITION_FUNC = {"clickhouse": clickhouse_partition_func}
@@ -4,7 +4,7 @@ import typing as t
4
4
  from enum import Enum
5
5
  from typing_extensions import Self
6
6
 
7
- from pydantic import Field
7
+ from pydantic import Field, BeforeValidator
8
8
  from sqlglot import exp
9
9
  from sqlglot.optimizer.normalize_identifiers import normalize_identifiers
10
10
  from sqlglot.optimizer.qualify_columns import quote_identifiers
@@ -33,6 +33,7 @@ from sqlmesh.utils.pydantic import (
33
33
  field_validator,
34
34
  get_dialect,
35
35
  validate_string,
36
+ positive_int_validator,
36
37
  )
37
38
 
38
39
 
@@ -455,7 +456,7 @@ class IncrementalByUniqueKeyKind(_IncrementalBy):
455
456
  unique_key: SQLGlotListOfFields
456
457
  when_matched: t.Optional[exp.Whens] = None
457
458
  merge_filter: t.Optional[exp.Expression] = None
458
- batch_concurrency: t.Literal[1] = 1
459
+ batch_concurrency: t.Annotated[t.Literal[1], BeforeValidator(positive_int_validator)] = 1
459
460
 
460
461
  @field_validator("when_matched", mode="before")
461
462
  def _when_matched_validator(
sqlmesh/core/renderer.py CHANGED
@@ -179,6 +179,7 @@ class BaseExpressionRenderer:
179
179
  )
180
180
 
181
181
  render_kwargs = {
182
+ "dialect": self._dialect,
182
183
  **date_dict(
183
184
  to_datetime(execution_time or c.EPOCH),
184
185
  start_time,
sqlmesh/dbt/adapter.py CHANGED
@@ -38,6 +38,9 @@ class BaseAdapter(abc.ABC):
38
38
  self.jinja_globals = jinja_globals.copy() if jinja_globals else {}
39
39
  self.jinja_globals["adapter"] = self
40
40
  self.project_dialect = project_dialect
41
+ self.jinja_globals["dialect"] = (
42
+ project_dialect # so the dialect is available in the jinja env created by self.dispatch()
43
+ )
41
44
  self.quote_policy = quote_policy or Policy()
42
45
 
43
46
  @abc.abstractmethod
sqlmesh/dbt/builtin.py CHANGED
@@ -156,7 +156,7 @@ class Var:
156
156
  def __init__(self, variables: t.Dict[str, t.Any]) -> None:
157
157
  self.variables = variables
158
158
 
159
- def __call__(self, name: str, default: t.Optional[t.Any] = None) -> t.Any:
159
+ def __call__(self, name: str, default: t.Optional[t.Any] = None, **kwargs: t.Any) -> t.Any:
160
160
  return self.variables.get(name, default)
161
161
 
162
162
  def has_var(self, name: str) -> bool:
File without changes
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+ import jinja2.nodes as j
3
+ from sqlglot import exp
4
+ import typing as t
5
+ import sqlmesh.core.constants as c
6
+ from pathlib import Path
7
+
8
+
9
+ # jinja transform is a function that takes (current node, previous node, parent node) and returns a new Node or None
10
+ # returning None means the current node is removed from the tree
11
+ # returning a different Node means the current node is replaced with the new Node
12
+ JinjaTransform = t.Callable[[j.Node, t.Optional[j.Node], t.Optional[j.Node]], t.Optional[j.Node]]
13
+ SQLGlotTransform = t.Callable[[exp.Expression], t.Optional[exp.Expression]]
14
+
15
+
16
+ def _sqlmesh_predefined_macro_variables() -> t.Set[str]:
17
+ def _gen() -> t.Iterable[str]:
18
+ for suffix in ("dt", "date", "ds", "ts", "tstz", "hour", "epoch", "millis"):
19
+ for prefix in ("start", "end", "execution"):
20
+ yield f"{prefix}_{suffix}"
21
+
22
+ for item in ("runtime_stage", "gateway", "this_model", "this_env", "model_kind_name"):
23
+ yield item
24
+
25
+ return set(_gen())
26
+
27
+
28
+ SQLMESH_PREDEFINED_MACRO_VARIABLES = _sqlmesh_predefined_macro_variables()
29
+
30
+
31
+ def infer_dbt_package_from_path(path: Path) -> t.Optional[str]:
32
+ """
33
+ Given a path like "sqlmesh-project/macros/__dbt_packages__/foo/bar.sql"
34
+
35
+ Infer that 'foo' is the DBT package
36
+ """
37
+ if c.MIGRATED_DBT_PACKAGES in path.parts:
38
+ idx = path.parts.index(c.MIGRATED_DBT_PACKAGES)
39
+ return path.parts[idx + 1]
40
+ return None
@@ -0,0 +1,117 @@
1
+ from __future__ import annotations
2
+ import typing as t
3
+ from pathlib import Path
4
+ from rich.console import Console as RichConsole
5
+ from rich.tree import Tree
6
+ from rich.progress import Progress, TextColumn, BarColumn, MofNCompleteColumn, TimeElapsedColumn
7
+ from sqlmesh.core.console import PROGRESS_BAR_WIDTH
8
+ from sqlmesh.utils import columns_to_types_all_known
9
+ from sqlmesh.utils import rich as srich
10
+ import logging
11
+ from rich.prompt import Confirm
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ if t.TYPE_CHECKING:
16
+ from sqlmesh.dbt.converter.convert import ConversionReport
17
+
18
+
19
+ def make_progress_bar(
20
+ console: t.Optional[RichConsole] = None,
21
+ justify: t.Literal["default", "left", "center", "right", "full"] = "right",
22
+ ) -> Progress:
23
+ return Progress(
24
+ TextColumn("[bold blue]{task.description}", justify=justify),
25
+ BarColumn(bar_width=PROGRESS_BAR_WIDTH),
26
+ "[progress.percentage]{task.percentage:>3.1f}%",
27
+ "•",
28
+ MofNCompleteColumn(),
29
+ "•",
30
+ TimeElapsedColumn(),
31
+ console=console,
32
+ )
33
+
34
+
35
+ class DbtConversionConsole:
36
+ """Console for displaying DBT project conversion progress"""
37
+
38
+ def __init__(self, console: t.Optional[RichConsole] = None) -> None:
39
+ self.console: RichConsole = console or srich.console
40
+
41
+ def log_message(self, message: str) -> None:
42
+ self.console.print(message)
43
+
44
+ def start_project_conversion(self, input_path: Path) -> None:
45
+ self.log_message(f"DBT project loaded from {input_path}; starting conversion")
46
+
47
+ def prompt_clear_directory(self, prefix: str, path: Path) -> bool:
48
+ return Confirm.ask(
49
+ f"{prefix}'{path}' is not empty.\nWould you like to clear it?", console=self.console
50
+ )
51
+
52
+ # Models
53
+ def start_models_conversion(self, model_count: int) -> None:
54
+ self.progress_bar = make_progress_bar(justify="left", console=self.console)
55
+ self.progress_bar.start()
56
+ self.models_progress_task_id = self.progress_bar.add_task(
57
+ "Converting models", total=model_count
58
+ )
59
+
60
+ def start_model_conversion(self, model_name: str) -> None:
61
+ logger.debug(f"Converting model {model_name}")
62
+ self.progress_bar.update(self.models_progress_task_id, description=None, refresh=True)
63
+
64
+ def complete_model_conversion(self) -> None:
65
+ self.progress_bar.update(self.models_progress_task_id, refresh=True, advance=1)
66
+
67
+ def complete_models_conversion(self) -> None:
68
+ self.progress_bar.update(self.models_progress_task_id, description=None, refresh=True)
69
+
70
+ # Audits
71
+
72
+ def start_audits_conversion(self, audit_count: int) -> None:
73
+ self.audits_progress_task_id = self.progress_bar.add_task(
74
+ "Converting audits", total=audit_count
75
+ )
76
+
77
+ def start_audit_conversion(self, audit_name: str) -> None:
78
+ self.progress_bar.update(self.audits_progress_task_id, description=None, refresh=True)
79
+
80
+ def complete_audit_conversion(self) -> None:
81
+ self.progress_bar.update(self.audits_progress_task_id, refresh=True, advance=1)
82
+
83
+ def complete_audits_conversion(self) -> None:
84
+ self.progress_bar.update(self.audits_progress_task_id, description=None, refresh=True)
85
+
86
+ # Macros
87
+
88
+ def start_macros_conversion(self, macro_count: int) -> None:
89
+ self.macros_progress_task_id = self.progress_bar.add_task(
90
+ "Converting macros", total=macro_count
91
+ )
92
+
93
+ def start_macro_conversion(self, macro_name: str) -> None:
94
+ self.progress_bar.update(self.macros_progress_task_id, description=None, refresh=True)
95
+
96
+ def complete_macro_conversion(self) -> None:
97
+ self.progress_bar.update(self.macros_progress_task_id, refresh=True, advance=1)
98
+
99
+ def complete_macros_conversion(self) -> None:
100
+ self.progress_bar.update(self.macros_progress_task_id, description=None, refresh=True)
101
+ self.progress_bar.stop()
102
+
103
+ def output_report(self, report: ConversionReport) -> None:
104
+ tree = Tree(
105
+ "[blue]The following models are self-referencing and their column types could not be statically inferred:"
106
+ )
107
+
108
+ for output_path, model in report.self_referencing_models:
109
+ if not model.columns_to_types or not columns_to_types_all_known(model.columns_to_types):
110
+ tree_node = tree.add(f"[green]{model.name}")
111
+ tree_node.add(output_path.as_posix())
112
+
113
+ self.console.print(tree)
114
+
115
+ self.log_message(
116
+ "[red]These will need to be manually fixed.[/red]\nEither specify the column types in the MODEL block or ensure the outer SELECT lists all columns"
117
+ )