sqlmesh 0.193.1.dev1__py3-none-any.whl → 0.193.1.dev3__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 +2 -2
- sqlmesh/cli/main.py +37 -0
- sqlmesh/core/config/root.py +8 -1
- sqlmesh/core/constants.py +3 -0
- sqlmesh/core/loader.py +108 -1
- sqlmesh/core/model/definition.py +55 -4
- sqlmesh/core/model/kind.py +3 -2
- sqlmesh/core/renderer.py +1 -0
- sqlmesh/dbt/adapter.py +3 -0
- sqlmesh/dbt/builtin.py +1 -1
- sqlmesh/dbt/converter/__init__.py +0 -0
- sqlmesh/dbt/converter/common.py +40 -0
- sqlmesh/dbt/converter/console.py +117 -0
- sqlmesh/dbt/converter/convert.py +414 -0
- sqlmesh/dbt/converter/jinja.py +604 -0
- sqlmesh/dbt/converter/jinja_builtins.py +109 -0
- sqlmesh/dbt/converter/jinja_transforms.py +465 -0
- sqlmesh/dbt/loader.py +6 -5
- sqlmesh/dbt/model.py +1 -0
- sqlmesh/dbt/target.py +73 -20
- sqlmesh/utils/jinja.py +95 -4
- {sqlmesh-0.193.1.dev1.dist-info → sqlmesh-0.193.1.dev3.dist-info}/METADATA +1 -1
- {sqlmesh-0.193.1.dev1.dist-info → sqlmesh-0.193.1.dev3.dist-info}/RECORD +27 -20
- {sqlmesh-0.193.1.dev1.dist-info → sqlmesh-0.193.1.dev3.dist-info}/WHEEL +0 -0
- {sqlmesh-0.193.1.dev1.dist-info → sqlmesh-0.193.1.dev3.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.193.1.dev1.dist-info → sqlmesh-0.193.1.dev3.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.193.1.dev1.dist-info → sqlmesh-0.193.1.dev3.dist-info}/top_level.txt +0 -0
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.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 193, 1, '
|
|
20
|
+
__version__ = version = '0.193.1.dev3'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 193, 1, 'dev3')
|
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
|
+
)
|
sqlmesh/core/config/root.py
CHANGED
|
@@ -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
|
|
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
|
+
)
|
sqlmesh/core/model/definition.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
2464
|
-
|
|
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}
|
sqlmesh/core/model/kind.py
CHANGED
|
@@ -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
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
|
+
)
|