sqlmesh 0.213.1.dev1__py3-none-any.whl → 0.227.2.dev4__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/__init__.py +12 -2
- sqlmesh/_version.py +2 -2
- sqlmesh/cli/main.py +0 -44
- sqlmesh/cli/project_init.py +11 -2
- sqlmesh/core/_typing.py +1 -0
- sqlmesh/core/audit/definition.py +8 -2
- sqlmesh/core/config/__init__.py +1 -1
- sqlmesh/core/config/connection.py +17 -5
- sqlmesh/core/config/dbt.py +13 -0
- sqlmesh/core/config/janitor.py +12 -0
- sqlmesh/core/config/loader.py +7 -0
- sqlmesh/core/config/model.py +2 -0
- sqlmesh/core/config/root.py +3 -0
- sqlmesh/core/console.py +81 -3
- sqlmesh/core/constants.py +1 -1
- sqlmesh/core/context.py +69 -26
- sqlmesh/core/dialect.py +3 -0
- sqlmesh/core/engine_adapter/_typing.py +2 -0
- sqlmesh/core/engine_adapter/base.py +322 -22
- sqlmesh/core/engine_adapter/base_postgres.py +17 -1
- sqlmesh/core/engine_adapter/bigquery.py +146 -7
- sqlmesh/core/engine_adapter/clickhouse.py +17 -13
- sqlmesh/core/engine_adapter/databricks.py +33 -2
- sqlmesh/core/engine_adapter/fabric.py +10 -29
- sqlmesh/core/engine_adapter/mixins.py +142 -48
- sqlmesh/core/engine_adapter/mssql.py +15 -4
- sqlmesh/core/engine_adapter/mysql.py +2 -2
- sqlmesh/core/engine_adapter/postgres.py +9 -3
- sqlmesh/core/engine_adapter/redshift.py +4 -0
- sqlmesh/core/engine_adapter/risingwave.py +1 -0
- sqlmesh/core/engine_adapter/shared.py +6 -0
- sqlmesh/core/engine_adapter/snowflake.py +82 -11
- sqlmesh/core/engine_adapter/spark.py +14 -10
- sqlmesh/core/engine_adapter/trino.py +4 -2
- sqlmesh/core/environment.py +2 -0
- sqlmesh/core/janitor.py +181 -0
- sqlmesh/core/lineage.py +1 -0
- sqlmesh/core/linter/definition.py +13 -13
- sqlmesh/core/linter/rules/builtin.py +29 -0
- sqlmesh/core/macros.py +35 -13
- sqlmesh/core/model/common.py +2 -0
- sqlmesh/core/model/definition.py +82 -28
- sqlmesh/core/model/kind.py +66 -2
- sqlmesh/core/model/meta.py +108 -4
- sqlmesh/core/node.py +101 -1
- sqlmesh/core/plan/builder.py +18 -10
- sqlmesh/core/plan/common.py +199 -2
- sqlmesh/core/plan/definition.py +25 -6
- sqlmesh/core/plan/evaluator.py +75 -113
- sqlmesh/core/plan/explainer.py +90 -8
- sqlmesh/core/plan/stages.py +42 -21
- sqlmesh/core/renderer.py +78 -32
- sqlmesh/core/scheduler.py +102 -22
- sqlmesh/core/selector.py +137 -9
- sqlmesh/core/signal.py +64 -1
- sqlmesh/core/snapshot/__init__.py +2 -0
- sqlmesh/core/snapshot/definition.py +146 -34
- sqlmesh/core/snapshot/evaluator.py +689 -124
- sqlmesh/core/state_sync/__init__.py +0 -1
- sqlmesh/core/state_sync/base.py +55 -33
- sqlmesh/core/state_sync/cache.py +12 -7
- sqlmesh/core/state_sync/common.py +216 -111
- sqlmesh/core/state_sync/db/environment.py +6 -4
- sqlmesh/core/state_sync/db/facade.py +42 -24
- sqlmesh/core/state_sync/db/interval.py +27 -7
- sqlmesh/core/state_sync/db/migrator.py +34 -16
- sqlmesh/core/state_sync/db/snapshot.py +177 -169
- sqlmesh/core/table_diff.py +2 -2
- sqlmesh/core/test/context.py +2 -0
- sqlmesh/core/test/definition.py +14 -9
- sqlmesh/dbt/adapter.py +22 -16
- sqlmesh/dbt/basemodel.py +75 -56
- sqlmesh/dbt/builtin.py +116 -12
- sqlmesh/dbt/column.py +17 -5
- sqlmesh/dbt/common.py +19 -5
- sqlmesh/dbt/context.py +14 -1
- sqlmesh/dbt/loader.py +61 -9
- sqlmesh/dbt/manifest.py +174 -16
- sqlmesh/dbt/model.py +183 -85
- sqlmesh/dbt/package.py +16 -1
- sqlmesh/dbt/profile.py +3 -3
- sqlmesh/dbt/project.py +12 -7
- sqlmesh/dbt/seed.py +6 -1
- sqlmesh/dbt/source.py +13 -1
- sqlmesh/dbt/target.py +25 -6
- sqlmesh/dbt/test.py +36 -5
- sqlmesh/migrations/v0000_baseline.py +95 -0
- sqlmesh/migrations/v0061_mysql_fix_blob_text_type.py +5 -7
- sqlmesh/migrations/v0062_add_model_gateway.py +5 -1
- sqlmesh/migrations/v0063_change_signals.py +5 -3
- sqlmesh/migrations/v0064_join_when_matched_strings.py +5 -3
- sqlmesh/migrations/v0065_add_model_optimize.py +5 -1
- sqlmesh/migrations/v0066_add_auto_restatements.py +8 -3
- sqlmesh/migrations/v0067_add_tsql_date_full_precision.py +5 -1
- sqlmesh/migrations/v0068_include_unrendered_query_in_metadata_hash.py +5 -1
- sqlmesh/migrations/v0069_update_dev_table_suffix.py +5 -3
- sqlmesh/migrations/v0070_include_grains_in_metadata_hash.py +5 -1
- sqlmesh/migrations/v0071_add_dev_version_to_intervals.py +9 -5
- sqlmesh/migrations/v0072_add_environment_statements.py +5 -3
- sqlmesh/migrations/v0073_remove_symbolic_disable_restatement.py +5 -3
- sqlmesh/migrations/v0074_add_partition_by_time_column_property.py +5 -1
- sqlmesh/migrations/v0075_remove_validate_query.py +5 -3
- sqlmesh/migrations/v0076_add_cron_tz.py +5 -1
- sqlmesh/migrations/v0077_fix_column_type_hash_calculation.py +5 -1
- sqlmesh/migrations/v0078_warn_if_non_migratable_python_env.py +5 -3
- sqlmesh/migrations/v0079_add_gateway_managed_property.py +10 -5
- sqlmesh/migrations/v0080_add_batch_size_to_scd_type_2_models.py +5 -1
- sqlmesh/migrations/v0081_update_partitioned_by.py +5 -3
- sqlmesh/migrations/v0082_warn_if_incorrectly_duplicated_statements.py +5 -3
- sqlmesh/migrations/v0083_use_sql_for_scd_time_data_type_data_hash.py +5 -1
- sqlmesh/migrations/v0084_normalize_quote_when_matched_and_merge_filter.py +5 -1
- sqlmesh/migrations/v0085_deterministic_repr.py +5 -3
- sqlmesh/migrations/v0086_check_deterministic_bug.py +5 -3
- sqlmesh/migrations/v0087_normalize_blueprint_variables.py +5 -3
- sqlmesh/migrations/v0088_warn_about_variable_python_env_diffs.py +5 -3
- sqlmesh/migrations/v0089_add_virtual_environment_mode.py +5 -1
- sqlmesh/migrations/v0090_add_forward_only_column.py +9 -5
- sqlmesh/migrations/v0091_on_additive_change.py +5 -1
- sqlmesh/migrations/v0092_warn_about_dbt_data_type_diff.py +5 -3
- sqlmesh/migrations/v0093_use_raw_sql_in_fingerprint.py +5 -1
- sqlmesh/migrations/v0094_add_dev_version_and_fingerprint_columns.py +123 -0
- sqlmesh/migrations/v0095_warn_about_dbt_raw_sql_diff.py +49 -0
- sqlmesh/migrations/v0096_remove_plan_dags_table.py +13 -0
- sqlmesh/migrations/v0097_add_dbt_name_in_node.py +9 -0
- sqlmesh/migrations/{v0060_move_audits_to_model.py → v0098_add_dbt_node_info_in_node.py} +33 -16
- sqlmesh/migrations/v0099_add_last_altered_to_intervals.py +25 -0
- sqlmesh/migrations/v0100_add_grants_and_grants_target_layer.py +9 -0
- sqlmesh/utils/__init__.py +8 -1
- sqlmesh/utils/cache.py +5 -1
- sqlmesh/utils/connection_pool.py +2 -1
- sqlmesh/utils/dag.py +65 -10
- sqlmesh/utils/date.py +8 -1
- sqlmesh/utils/errors.py +8 -0
- sqlmesh/utils/jinja.py +54 -4
- sqlmesh/utils/pydantic.py +6 -6
- sqlmesh/utils/windows.py +13 -3
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/METADATA +7 -10
- sqlmesh-0.227.2.dev4.dist-info/RECORD +370 -0
- sqlmesh_dbt/cli.py +70 -7
- sqlmesh_dbt/console.py +14 -6
- sqlmesh_dbt/operations.py +103 -24
- sqlmesh_dbt/selectors.py +39 -1
- web/client/dist/assets/{Audits-Ucsx1GzF.js → Audits-CBiYyyx-.js} +1 -1
- web/client/dist/assets/{Banner-BWDzvavM.js → Banner-DSRbUlO5.js} +1 -1
- web/client/dist/assets/{ChevronDownIcon-D2VL13Ah.js → ChevronDownIcon-MK_nrjD_.js} +1 -1
- web/client/dist/assets/{ChevronRightIcon-DWGYbf1l.js → ChevronRightIcon-CLWtT22Q.js} +1 -1
- web/client/dist/assets/{Content-DdHDZM3I.js → Content-BNuGZN5l.js} +1 -1
- web/client/dist/assets/{Content-Bikfy8fh.js → Content-CSHJyW0n.js} +1 -1
- web/client/dist/assets/{Data-CzAJH7rW.js → Data-C1oRDbLx.js} +1 -1
- web/client/dist/assets/{DataCatalog-BJF11g8f.js → DataCatalog-HXyX2-_j.js} +1 -1
- web/client/dist/assets/{Editor-s0SBpV2y.js → Editor-BDyfpUuw.js} +1 -1
- web/client/dist/assets/{Editor-DgLhgKnm.js → Editor-D0jNItwC.js} +1 -1
- web/client/dist/assets/{Errors-D0m0O1d3.js → Errors-BfuFLcPi.js} +1 -1
- web/client/dist/assets/{FileExplorer-CEv0vXkt.js → FileExplorer-BR9IE3he.js} +1 -1
- web/client/dist/assets/{Footer-BwzXn8Ew.js → Footer-CgBEtiAh.js} +1 -1
- web/client/dist/assets/{Header-6heDkEqG.js → Header-DSqR6nSO.js} +1 -1
- web/client/dist/assets/{Input-obuJsD6k.js → Input-B-oZ6fGO.js} +1 -1
- web/client/dist/assets/Lineage-DYQVwDbD.js +1 -0
- web/client/dist/assets/{ListboxShow-HM9_qyrt.js → ListboxShow-BE5-xevs.js} +1 -1
- web/client/dist/assets/{ModelLineage-zWdKo0U2.js → ModelLineage-DkIFAYo4.js} +1 -1
- web/client/dist/assets/{Models-Bcu66SRz.js → Models-D5dWr8RB.js} +1 -1
- web/client/dist/assets/{Page-BWEEQfIt.js → Page-C-XfU5BR.js} +1 -1
- web/client/dist/assets/{Plan-C4gXCqlf.js → Plan-ZEuTINBq.js} +1 -1
- web/client/dist/assets/{PlusCircleIcon-CVDO651q.js → PlusCircleIcon-DVXAHG8_.js} +1 -1
- web/client/dist/assets/{ReportErrors-BT6xFwAr.js → ReportErrors-B7FEPzMB.js} +1 -1
- web/client/dist/assets/{Root-ryJoBK4h.js → Root-8aZyhPxF.js} +1 -1
- web/client/dist/assets/{SearchList-DB04sPb9.js → SearchList-W_iT2G82.js} +1 -1
- web/client/dist/assets/{SelectEnvironment-CUYcXUu6.js → SelectEnvironment-C65jALmO.js} +1 -1
- web/client/dist/assets/{SourceList-Doo_9ZGp.js → SourceList-DSLO6nVJ.js} +1 -1
- web/client/dist/assets/{SourceListItem-D5Mj7Dly.js → SourceListItem-BHt8d9-I.js} +1 -1
- web/client/dist/assets/{SplitPane-qHmkD1qy.js → SplitPane-CViaZmw6.js} +1 -1
- web/client/dist/assets/{Tests-DH1Z74ML.js → Tests-DhaVt5t1.js} +1 -1
- web/client/dist/assets/{Welcome-DqUJUNMF.js → Welcome-DvpjH-_4.js} +1 -1
- web/client/dist/assets/context-BctCsyGb.js +71 -0
- web/client/dist/assets/{context-Dr54UHLi.js → context-DFNeGsFF.js} +1 -1
- web/client/dist/assets/{editor-DYIP1yQ4.js → editor-CcO28cqd.js} +1 -1
- web/client/dist/assets/{file-DarlIDVi.js → file-CvJN3aZO.js} +1 -1
- web/client/dist/assets/{floating-ui.react-dom-BH3TFvkM.js → floating-ui.react-dom-CjE-JNW1.js} +1 -1
- web/client/dist/assets/{help-Bl8wqaQc.js → help-DuPhjipa.js} +1 -1
- web/client/dist/assets/{index-D1sR7wpN.js → index-C-dJH7yZ.js} +1 -1
- web/client/dist/assets/{index-O3mjYpnE.js → index-Dj0i1-CA.js} +2 -2
- web/client/dist/assets/{plan-CehRrJUG.js → plan-BTRSbjKn.js} +1 -1
- web/client/dist/assets/{popover-CqgMRE0G.js → popover-_Sf0yvOI.js} +1 -1
- web/client/dist/assets/{project-6gxepOhm.js → project-BvSOI8MY.js} +1 -1
- web/client/dist/index.html +1 -1
- sqlmesh/integrations/llm.py +0 -56
- sqlmesh/migrations/v0001_init.py +0 -60
- sqlmesh/migrations/v0002_remove_identify.py +0 -5
- sqlmesh/migrations/v0003_move_batch_size.py +0 -34
- sqlmesh/migrations/v0004_environmnent_add_finalized_at.py +0 -23
- sqlmesh/migrations/v0005_create_seed_table.py +0 -24
- sqlmesh/migrations/v0006_change_seed_hash.py +0 -5
- sqlmesh/migrations/v0007_env_table_info_to_kind.py +0 -99
- sqlmesh/migrations/v0008_create_intervals_table.py +0 -38
- sqlmesh/migrations/v0009_remove_pre_post_hooks.py +0 -62
- sqlmesh/migrations/v0010_seed_hash_batch_size.py +0 -5
- sqlmesh/migrations/v0011_add_model_kind_name.py +0 -63
- sqlmesh/migrations/v0012_update_jinja_expressions.py +0 -86
- sqlmesh/migrations/v0013_serde_using_model_dialects.py +0 -87
- sqlmesh/migrations/v0014_fix_dev_intervals.py +0 -14
- sqlmesh/migrations/v0015_environment_add_promoted_snapshot_ids.py +0 -26
- sqlmesh/migrations/v0016_fix_windows_path.py +0 -59
- sqlmesh/migrations/v0017_fix_windows_seed_path.py +0 -55
- sqlmesh/migrations/v0018_rename_snapshot_model_to_node.py +0 -53
- sqlmesh/migrations/v0019_add_env_suffix_target.py +0 -28
- sqlmesh/migrations/v0020_remove_redundant_attributes_from_dbt_models.py +0 -80
- sqlmesh/migrations/v0021_fix_table_properties.py +0 -62
- sqlmesh/migrations/v0022_move_project_to_model.py +0 -54
- sqlmesh/migrations/v0023_fix_added_models_with_forward_only_parents.py +0 -65
- sqlmesh/migrations/v0024_replace_model_kind_name_enum_with_value.py +0 -55
- sqlmesh/migrations/v0025_fix_intervals_and_missing_change_category.py +0 -117
- sqlmesh/migrations/v0026_remove_dialect_from_seed.py +0 -55
- sqlmesh/migrations/v0027_minute_interval_to_five.py +0 -57
- sqlmesh/migrations/v0028_add_plan_dags_table.py +0 -29
- sqlmesh/migrations/v0029_generate_schema_types_using_dialect.py +0 -69
- sqlmesh/migrations/v0030_update_unrestorable_snapshots.py +0 -65
- sqlmesh/migrations/v0031_remove_dbt_target_fields.py +0 -65
- sqlmesh/migrations/v0032_add_sqlmesh_version.py +0 -25
- sqlmesh/migrations/v0033_mysql_fix_blob_text_type.py +0 -45
- sqlmesh/migrations/v0034_add_default_catalog.py +0 -367
- sqlmesh/migrations/v0035_add_catalog_name_override.py +0 -22
- sqlmesh/migrations/v0036_delete_plan_dags_bug_fix.py +0 -14
- sqlmesh/migrations/v0037_remove_dbt_is_incremental_macro.py +0 -61
- sqlmesh/migrations/v0038_add_expiration_ts_to_snapshot.py +0 -73
- sqlmesh/migrations/v0039_include_environment_in_plan_dag_spec.py +0 -68
- sqlmesh/migrations/v0040_add_previous_finalized_snapshots.py +0 -26
- sqlmesh/migrations/v0041_remove_hash_raw_query_attribute.py +0 -59
- sqlmesh/migrations/v0042_trim_indirect_versions.py +0 -66
- sqlmesh/migrations/v0043_fix_remove_obsolete_attributes_in_plan_dags.py +0 -61
- sqlmesh/migrations/v0044_quote_identifiers_in_model_attributes.py +0 -5
- sqlmesh/migrations/v0045_move_gateway_variable.py +0 -70
- sqlmesh/migrations/v0046_add_batch_concurrency.py +0 -8
- sqlmesh/migrations/v0047_change_scd_string_to_column.py +0 -5
- sqlmesh/migrations/v0048_drop_indirect_versions.py +0 -59
- sqlmesh/migrations/v0049_replace_identifier_with_version_in_seeds_table.py +0 -57
- sqlmesh/migrations/v0050_drop_seeds_table.py +0 -11
- sqlmesh/migrations/v0051_rename_column_descriptions.py +0 -65
- sqlmesh/migrations/v0052_add_normalize_name_in_environment_naming_info.py +0 -28
- sqlmesh/migrations/v0053_custom_model_kind_extra_attributes.py +0 -5
- sqlmesh/migrations/v0054_fix_trailing_comments.py +0 -5
- sqlmesh/migrations/v0055_add_updated_ts_unpaused_ts_ttl_ms_unrestorable_to_snapshot.py +0 -132
- sqlmesh/migrations/v0056_restore_table_indexes.py +0 -118
- sqlmesh/migrations/v0057_add_table_format.py +0 -5
- sqlmesh/migrations/v0058_add_requirements.py +0 -26
- sqlmesh/migrations/v0059_add_physical_version.py +0 -5
- sqlmesh-0.213.1.dev1.dist-info/RECORD +0 -421
- web/client/dist/assets/Lineage-D0Hgdz2v.js +0 -1
- web/client/dist/assets/context-DgX0fp2E.js +0 -68
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/WHEEL +0 -0
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.213.1.dev1.dist-info → sqlmesh-0.227.2.dev4.dist-info}/top_level.txt +0 -0
sqlmesh/dbt/loader.py
CHANGED
|
@@ -5,11 +5,13 @@ import sys
|
|
|
5
5
|
import typing as t
|
|
6
6
|
import sqlmesh.core.dialect as d
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from collections import defaultdict
|
|
8
9
|
from sqlmesh.core.config import (
|
|
9
10
|
Config,
|
|
10
11
|
ConnectionConfig,
|
|
11
12
|
GatewayConfig,
|
|
12
13
|
ModelDefaultsConfig,
|
|
14
|
+
DbtConfig as RootDbtConfig,
|
|
13
15
|
)
|
|
14
16
|
from sqlmesh.core.environment import EnvironmentStatements
|
|
15
17
|
from sqlmesh.core.loader import CacheBase, LoadedProject, Loader
|
|
@@ -48,11 +50,21 @@ def sqlmesh_config(
|
|
|
48
50
|
dbt_profile_name: t.Optional[str] = None,
|
|
49
51
|
dbt_target_name: t.Optional[str] = None,
|
|
50
52
|
variables: t.Optional[t.Dict[str, t.Any]] = None,
|
|
53
|
+
threads: t.Optional[int] = None,
|
|
51
54
|
register_comments: t.Optional[bool] = None,
|
|
55
|
+
infer_state_schema_name: bool = False,
|
|
56
|
+
profiles_dir: t.Optional[Path] = None,
|
|
52
57
|
**kwargs: t.Any,
|
|
53
58
|
) -> Config:
|
|
54
59
|
project_root = project_root or Path()
|
|
55
|
-
context = DbtContext(
|
|
60
|
+
context = DbtContext(
|
|
61
|
+
project_root=project_root, profiles_dir=profiles_dir, profile_name=dbt_profile_name
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# note: Profile.load() is called twice with different DbtContext's:
|
|
65
|
+
# - once here with the above DbtContext (to determine connnection / gateway config which has to be set up before everything else)
|
|
66
|
+
# - again on the SQLMesh side via GenericContext.load() -> DbtLoader._load_projects() -> Project.load() which constructs a fresh DbtContext and ignores the above one
|
|
67
|
+
# it's important to ensure that the DbtContext created within the DbtLoader uses the same project root / profiles dir that we use here
|
|
56
68
|
profile = Profile.load(context, target_name=dbt_target_name)
|
|
57
69
|
model_defaults = kwargs.pop("model_defaults", ModelDefaultsConfig())
|
|
58
70
|
if model_defaults.dialect is None:
|
|
@@ -66,16 +78,45 @@ def sqlmesh_config(
|
|
|
66
78
|
if not issubclass(loader, DbtLoader):
|
|
67
79
|
raise ConfigError("The loader must be a DbtLoader.")
|
|
68
80
|
|
|
81
|
+
if threads is not None:
|
|
82
|
+
# the to_sqlmesh() function on TargetConfig maps self.threads -> concurrent_tasks
|
|
83
|
+
profile.target.threads = threads
|
|
84
|
+
|
|
85
|
+
gateway_kwargs = {}
|
|
86
|
+
if infer_state_schema_name:
|
|
87
|
+
profile_name = context.profile_name
|
|
88
|
+
|
|
89
|
+
# Note: we deliberately isolate state based on the target *schema* and not the target name.
|
|
90
|
+
# It is assumed that the project will define a target, eg 'dev', and then in each users own ~/.dbt/profiles.yml the schema
|
|
91
|
+
# for the 'dev' target is overriden to something user-specific, rather than making the target name itself user-specific.
|
|
92
|
+
# This means that the schema name is the indicator of isolated state, not the target name which may be re-used across multiple schemas.
|
|
93
|
+
target_schema = profile.target.schema_
|
|
94
|
+
|
|
95
|
+
# dbt-core doesnt allow schema to be undefined, but it does allow an empty string, and then just
|
|
96
|
+
# fails at runtime when `CREATE SCHEMA ""` doesnt work
|
|
97
|
+
if not target_schema:
|
|
98
|
+
raise ConfigError(
|
|
99
|
+
f"Target '{profile.target_name}' does not specify a schema.\n"
|
|
100
|
+
"A schema is required in order to infer where to store SQLMesh state"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
inferred_state_schema_name = f"sqlmesh_state_{profile_name}_{target_schema}"
|
|
104
|
+
logger.info("Inferring state schema: %s", inferred_state_schema_name)
|
|
105
|
+
gateway_kwargs["state_schema"] = inferred_state_schema_name
|
|
106
|
+
|
|
69
107
|
return Config(
|
|
70
108
|
loader=loader,
|
|
109
|
+
loader_kwargs=dict(profiles_dir=profiles_dir),
|
|
71
110
|
model_defaults=model_defaults,
|
|
72
111
|
variables=variables or {},
|
|
112
|
+
dbt=RootDbtConfig(infer_state_schema_name=infer_state_schema_name),
|
|
73
113
|
**{
|
|
74
114
|
"default_gateway": profile.target_name if "gateways" not in kwargs else "",
|
|
75
115
|
"gateways": {
|
|
76
116
|
profile.target_name: GatewayConfig(
|
|
77
117
|
connection=profile.target.to_sqlmesh(**target_to_sqlmesh_args),
|
|
78
118
|
state_connection=state_connection,
|
|
119
|
+
**gateway_kwargs,
|
|
79
120
|
)
|
|
80
121
|
}, # type: ignore
|
|
81
122
|
**kwargs,
|
|
@@ -84,9 +125,12 @@ def sqlmesh_config(
|
|
|
84
125
|
|
|
85
126
|
|
|
86
127
|
class DbtLoader(Loader):
|
|
87
|
-
def __init__(
|
|
128
|
+
def __init__(
|
|
129
|
+
self, context: GenericContext, path: Path, profiles_dir: t.Optional[Path] = None
|
|
130
|
+
) -> None:
|
|
88
131
|
self._projects: t.List[Project] = []
|
|
89
132
|
self._macros_max_mtime: t.Optional[float] = None
|
|
133
|
+
self._profiles_dir = profiles_dir
|
|
90
134
|
super().__init__(context, path)
|
|
91
135
|
|
|
92
136
|
def load(self) -> LoadedProject:
|
|
@@ -137,16 +181,22 @@ class DbtLoader(Loader):
|
|
|
137
181
|
package_context.set_and_render_variables(package.variables, package.name)
|
|
138
182
|
package_models: t.Dict[str, BaseModelConfig] = {**package.models, **package.seeds}
|
|
139
183
|
|
|
184
|
+
package_models_by_path: t.Dict[Path, t.List[BaseModelConfig]] = defaultdict(list)
|
|
140
185
|
for model in package_models.values():
|
|
141
|
-
if isinstance(model, ModelConfig) and not model.
|
|
186
|
+
if isinstance(model, ModelConfig) and not model.sql.strip():
|
|
142
187
|
logger.info(f"Skipping empty model '{model.name}' at path '{model.path}'.")
|
|
143
188
|
continue
|
|
189
|
+
package_models_by_path[model.path].append(model)
|
|
144
190
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
191
|
+
for path, path_models in package_models_by_path.items():
|
|
192
|
+
sqlmesh_models = cache.get_or_load_models(
|
|
193
|
+
path,
|
|
194
|
+
loader=lambda: [
|
|
195
|
+
_to_sqlmesh(model, package_context) for model in path_models
|
|
196
|
+
],
|
|
197
|
+
)
|
|
198
|
+
for sqlmesh_model in sqlmesh_models:
|
|
199
|
+
models[sqlmesh_model.fqn] = sqlmesh_model
|
|
150
200
|
|
|
151
201
|
models.update(self._load_external_models(audits, cache))
|
|
152
202
|
|
|
@@ -165,7 +215,8 @@ class DbtLoader(Loader):
|
|
|
165
215
|
for test in package.tests.values():
|
|
166
216
|
logger.debug("Converting '%s' to sqlmesh format", test.name)
|
|
167
217
|
try:
|
|
168
|
-
audits[test.
|
|
218
|
+
audits[test.canonical_name] = test.to_sqlmesh(package_context)
|
|
219
|
+
|
|
169
220
|
except BaseMissingReferenceError as e:
|
|
170
221
|
ref_type = "model" if isinstance(e, MissingModelError) else "source"
|
|
171
222
|
logger.warning(
|
|
@@ -186,6 +237,7 @@ class DbtLoader(Loader):
|
|
|
186
237
|
project = Project.load(
|
|
187
238
|
DbtContext(
|
|
188
239
|
project_root=self.config_path,
|
|
240
|
+
profiles_dir=self._profiles_dir,
|
|
189
241
|
target_name=target_name,
|
|
190
242
|
sqlmesh_config=self.config,
|
|
191
243
|
),
|
sqlmesh/dbt/manifest.py
CHANGED
|
@@ -11,7 +11,7 @@ from collections import defaultdict
|
|
|
11
11
|
from functools import cached_property
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
|
-
from dbt import
|
|
14
|
+
from dbt import flags
|
|
15
15
|
|
|
16
16
|
from sqlmesh.dbt.util import DBT_VERSION
|
|
17
17
|
from sqlmesh.utils.conversions import make_serializable
|
|
@@ -19,6 +19,8 @@ from sqlmesh.utils.conversions import make_serializable
|
|
|
19
19
|
# Override the file name to prevent dbt commands from invalidating the cache.
|
|
20
20
|
|
|
21
21
|
if DBT_VERSION >= (1, 6, 0):
|
|
22
|
+
from dbt import constants as dbt_constants
|
|
23
|
+
|
|
22
24
|
dbt_constants.PARTIAL_PARSE_FILE_NAME = "sqlmesh_partial_parse.msgpack" # type: ignore
|
|
23
25
|
else:
|
|
24
26
|
from dbt.parser import manifest as dbt_manifest # type: ignore
|
|
@@ -44,10 +46,10 @@ from dbt.tracking import do_not_track
|
|
|
44
46
|
from sqlmesh.core import constants as c
|
|
45
47
|
from sqlmesh.utils.errors import SQLMeshError
|
|
46
48
|
from sqlmesh.core.config import ModelDefaultsConfig
|
|
47
|
-
from sqlmesh.dbt.basemodel import Dependencies
|
|
48
49
|
from sqlmesh.dbt.builtin import BUILTIN_FILTERS, BUILTIN_GLOBALS, OVERRIDDEN_MACROS
|
|
50
|
+
from sqlmesh.dbt.common import Dependencies
|
|
49
51
|
from sqlmesh.dbt.model import ModelConfig
|
|
50
|
-
from sqlmesh.dbt.package import HookConfig, MacroConfig
|
|
52
|
+
from sqlmesh.dbt.package import HookConfig, MacroConfig, MaterializationConfig
|
|
51
53
|
from sqlmesh.dbt.seed import SeedConfig
|
|
52
54
|
from sqlmesh.dbt.source import SourceConfig
|
|
53
55
|
from sqlmesh.dbt.target import TargetConfig
|
|
@@ -61,6 +63,7 @@ from sqlmesh.utils.jinja import (
|
|
|
61
63
|
extract_call_names,
|
|
62
64
|
jinja_call_arg_name,
|
|
63
65
|
)
|
|
66
|
+
from sqlglot.helper import ensure_list
|
|
64
67
|
|
|
65
68
|
if t.TYPE_CHECKING:
|
|
66
69
|
from dbt.contracts.graph.manifest import Macro, Manifest
|
|
@@ -75,6 +78,7 @@ SeedConfigs = t.Dict[str, SeedConfig]
|
|
|
75
78
|
SourceConfigs = t.Dict[str, SourceConfig]
|
|
76
79
|
MacroConfigs = t.Dict[str, MacroConfig]
|
|
77
80
|
HookConfigs = t.Dict[str, HookConfig]
|
|
81
|
+
MaterializationConfigs = t.Dict[str, MaterializationConfig]
|
|
78
82
|
|
|
79
83
|
|
|
80
84
|
IGNORED_PACKAGES = {"elementary"}
|
|
@@ -135,6 +139,7 @@ class ManifestHelper:
|
|
|
135
139
|
|
|
136
140
|
self._on_run_start_per_package: t.Dict[str, HookConfigs] = defaultdict(dict)
|
|
137
141
|
self._on_run_end_per_package: t.Dict[str, HookConfigs] = defaultdict(dict)
|
|
142
|
+
self._materializations: MaterializationConfigs = {}
|
|
138
143
|
|
|
139
144
|
def tests(self, package_name: t.Optional[str] = None) -> TestConfigs:
|
|
140
145
|
self._load_all()
|
|
@@ -164,6 +169,10 @@ class ManifestHelper:
|
|
|
164
169
|
self._load_all()
|
|
165
170
|
return self._on_run_end_per_package[package_name or self._project_name]
|
|
166
171
|
|
|
172
|
+
def materializations(self) -> MaterializationConfigs:
|
|
173
|
+
self._load_all()
|
|
174
|
+
return self._materializations
|
|
175
|
+
|
|
167
176
|
@property
|
|
168
177
|
def all_macros(self) -> t.Dict[str, t.Dict[str, MacroInfo]]:
|
|
169
178
|
self._load_all()
|
|
@@ -213,6 +222,7 @@ class ManifestHelper:
|
|
|
213
222
|
self._calls = {k: (v, False) for k, v in (self._call_cache.get("") or {}).items()}
|
|
214
223
|
|
|
215
224
|
self._load_macros()
|
|
225
|
+
self._load_materializations()
|
|
216
226
|
self._load_sources()
|
|
217
227
|
self._load_tests()
|
|
218
228
|
self._load_models_and_seeds()
|
|
@@ -250,11 +260,14 @@ class ManifestHelper:
|
|
|
250
260
|
|
|
251
261
|
def _load_macros(self) -> None:
|
|
252
262
|
for macro in self._manifest.macros.values():
|
|
263
|
+
if macro.name.startswith("materialization_"):
|
|
264
|
+
continue
|
|
265
|
+
|
|
253
266
|
if macro.name.startswith("test_"):
|
|
254
267
|
macro.macro_sql = _convert_jinja_test_to_macro(macro.macro_sql)
|
|
255
268
|
|
|
256
269
|
dependencies = Dependencies(macros=_macro_references(self._manifest, macro))
|
|
257
|
-
if not macro.name.startswith("
|
|
270
|
+
if not macro.name.startswith("test_"):
|
|
258
271
|
dependencies = dependencies.union(
|
|
259
272
|
self._extra_dependencies(macro.macro_sql, macro.package_name)
|
|
260
273
|
)
|
|
@@ -281,6 +294,32 @@ class ManifestHelper:
|
|
|
281
294
|
if pos > 0 and name[pos + 2 :] in adapter_macro_names:
|
|
282
295
|
macro_config.info.is_top_level = True
|
|
283
296
|
|
|
297
|
+
def _load_materializations(self) -> None:
|
|
298
|
+
for macro in self._manifest.macros.values():
|
|
299
|
+
if macro.name.startswith("materialization_"):
|
|
300
|
+
# Extract name and adapter ( "materialization_{name}_{adapter}" or "materialization_{name}_default")
|
|
301
|
+
name_parts = macro.name.split("_")
|
|
302
|
+
if len(name_parts) >= 3:
|
|
303
|
+
mat_name = "_".join(name_parts[1:-1])
|
|
304
|
+
adapter = name_parts[-1]
|
|
305
|
+
|
|
306
|
+
dependencies = Dependencies(macros=_macro_references(self._manifest, macro))
|
|
307
|
+
macro.macro_sql = _strip_jinja_materialization_tags(macro.macro_sql)
|
|
308
|
+
dependencies = dependencies.union(
|
|
309
|
+
self._extra_dependencies(macro.macro_sql, macro.package_name)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
materialization_config = MaterializationConfig(
|
|
313
|
+
name=mat_name,
|
|
314
|
+
adapter=adapter,
|
|
315
|
+
definition=macro.macro_sql,
|
|
316
|
+
dependencies=dependencies,
|
|
317
|
+
path=Path(macro.original_file_path),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
key = f"{mat_name}_{adapter}"
|
|
321
|
+
self._materializations[key] = materialization_config
|
|
322
|
+
|
|
284
323
|
def _load_tests(self) -> None:
|
|
285
324
|
for node in self._manifest.nodes.values():
|
|
286
325
|
if node.resource_type != "test":
|
|
@@ -317,15 +356,17 @@ class ManifestHelper:
|
|
|
317
356
|
)
|
|
318
357
|
|
|
319
358
|
test_model = _test_model(node)
|
|
359
|
+
node_config = _node_base_config(node)
|
|
360
|
+
node_config["name"] = _build_test_name(node, dependencies)
|
|
320
361
|
|
|
321
362
|
test = TestConfig(
|
|
322
363
|
sql=sql,
|
|
323
364
|
model_name=test_model,
|
|
324
365
|
test_kwargs=node.test_metadata.kwargs if hasattr(node, "test_metadata") else {},
|
|
325
366
|
dependencies=dependencies,
|
|
326
|
-
**
|
|
367
|
+
**node_config,
|
|
327
368
|
)
|
|
328
|
-
self._tests_per_package[node.package_name][node.
|
|
369
|
+
self._tests_per_package[node.package_name][node.unique_id] = test
|
|
329
370
|
if test_model:
|
|
330
371
|
self._tests_by_owner[test_model].append(test)
|
|
331
372
|
|
|
@@ -338,10 +379,12 @@ class ManifestHelper:
|
|
|
338
379
|
continue
|
|
339
380
|
|
|
340
381
|
macro_references = _macro_references(self._manifest, node)
|
|
341
|
-
|
|
382
|
+
all_tests = (
|
|
342
383
|
self._tests_by_owner[node.name]
|
|
343
384
|
+ self._tests_by_owner[f"{node.package_name}.{node.name}"]
|
|
344
385
|
)
|
|
386
|
+
# Only include non-standalone tests (tests that don't reference other models)
|
|
387
|
+
tests = [test for test in all_tests if not test.is_standalone]
|
|
345
388
|
node_config = _node_base_config(node)
|
|
346
389
|
|
|
347
390
|
node_name = node.name
|
|
@@ -354,7 +397,15 @@ class ManifestHelper:
|
|
|
354
397
|
dependencies = Dependencies(
|
|
355
398
|
macros=macro_references, refs=_refs(node), sources=_sources(node)
|
|
356
399
|
)
|
|
357
|
-
dependencies = dependencies.union(
|
|
400
|
+
dependencies = dependencies.union(
|
|
401
|
+
self._extra_dependencies(sql, node.package_name, track_all_model_attrs=True)
|
|
402
|
+
)
|
|
403
|
+
for hook in [*node_config.get("pre-hook", []), *node_config.get("post-hook", [])]:
|
|
404
|
+
dependencies = dependencies.union(
|
|
405
|
+
self._extra_dependencies(
|
|
406
|
+
hook["sql"], node.package_name, track_all_model_attrs=True
|
|
407
|
+
)
|
|
408
|
+
)
|
|
358
409
|
dependencies = dependencies.union(
|
|
359
410
|
self._flatten_dependencies_from_macros(dependencies.macros, node.package_name)
|
|
360
411
|
)
|
|
@@ -552,17 +603,37 @@ class ManifestHelper:
|
|
|
552
603
|
dependencies = dependencies.union(macro_dependencies)
|
|
553
604
|
return dependencies
|
|
554
605
|
|
|
555
|
-
def _extra_dependencies(
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
606
|
+
def _extra_dependencies(
|
|
607
|
+
self,
|
|
608
|
+
target: str,
|
|
609
|
+
package: str,
|
|
610
|
+
track_all_model_attrs: bool = False,
|
|
611
|
+
) -> Dependencies:
|
|
612
|
+
"""
|
|
613
|
+
We sometimes observe that the manifest doesn't capture all macros, refs, and sources within a macro.
|
|
614
|
+
This behavior has been observed with macros like dbt.current_timestamp(), dbt_utils.slugify(), and source().
|
|
615
|
+
Here we apply our custom extractor to make a best effort to supplement references captured in the manifest.
|
|
616
|
+
"""
|
|
559
617
|
dependencies = Dependencies()
|
|
618
|
+
|
|
619
|
+
# Whether all `model` attributes (e.g., `model.config`) should be included in the dependencies
|
|
620
|
+
all_model_attrs = False
|
|
621
|
+
|
|
560
622
|
for call_name, node in extract_call_names(target, cache=self._calls):
|
|
561
623
|
if call_name[0] == "config":
|
|
562
624
|
continue
|
|
563
|
-
|
|
625
|
+
|
|
626
|
+
if (
|
|
627
|
+
track_all_model_attrs
|
|
628
|
+
and not all_model_attrs
|
|
629
|
+
and isinstance(node, jinja2.nodes.Call)
|
|
630
|
+
and any(isinstance(a, jinja2.nodes.Name) and a.name == "model" for a in node.args)
|
|
631
|
+
):
|
|
632
|
+
all_model_attrs = True
|
|
633
|
+
|
|
634
|
+
if isinstance(node, jinja2.nodes.Getattr):
|
|
564
635
|
if call_name[0] == "model":
|
|
565
|
-
dependencies.model_attrs.add(call_name[1])
|
|
636
|
+
dependencies.model_attrs.attrs.add(call_name[1])
|
|
566
637
|
elif call_name[0] == "source":
|
|
567
638
|
args = [jinja_call_arg_name(arg) for arg in node.args]
|
|
568
639
|
if args and all(arg for arg in args):
|
|
@@ -606,6 +677,14 @@ class ManifestHelper:
|
|
|
606
677
|
call_name[0], call_name[1], dependencies.macros.append
|
|
607
678
|
)
|
|
608
679
|
|
|
680
|
+
# When `model` is referenced as-is, e.g. it's passed as an argument to a macro call like
|
|
681
|
+
# `{{ foo(model) }}`, we can't easily track the attributes that are actually used, because
|
|
682
|
+
# it may be aliased and hence tracking actual uses of `model` requires a proper data flow
|
|
683
|
+
# analysis. We conservatively deal with this by including all of its supported attributes
|
|
684
|
+
# if a standalone reference is found.
|
|
685
|
+
if all_model_attrs:
|
|
686
|
+
dependencies.model_attrs.all_attrs = True
|
|
687
|
+
|
|
609
688
|
return dependencies
|
|
610
689
|
|
|
611
690
|
|
|
@@ -629,7 +708,7 @@ def _macro_references(
|
|
|
629
708
|
return result
|
|
630
709
|
|
|
631
710
|
for macro_node_id in node.depends_on.macros:
|
|
632
|
-
if not macro_node_id:
|
|
711
|
+
if not macro_node_id or macro_node_id == "None":
|
|
633
712
|
continue
|
|
634
713
|
|
|
635
714
|
macro_node = manifest.macros[macro_node_id]
|
|
@@ -667,7 +746,12 @@ def _test_model(node: ManifestNode) -> t.Optional[str]:
|
|
|
667
746
|
attached_node = getattr(node, "attached_node", None)
|
|
668
747
|
if attached_node:
|
|
669
748
|
pieces = attached_node.split(".")
|
|
670
|
-
|
|
749
|
+
if pieces[0] in ["model", "seed"]:
|
|
750
|
+
# versioned models have format "model.package.model_name.v1" (4 parts)
|
|
751
|
+
if len(pieces) == 4:
|
|
752
|
+
return f"{pieces[2]}_{pieces[3]}"
|
|
753
|
+
return pieces[-1]
|
|
754
|
+
return None
|
|
671
755
|
|
|
672
756
|
key_name = getattr(node, "file_key_name", None)
|
|
673
757
|
if key_name:
|
|
@@ -700,3 +784,77 @@ def _convert_jinja_test_to_macro(test_jinja: str) -> str:
|
|
|
700
784
|
macro = macro_tag + test_jinja[match.span()[-1] :]
|
|
701
785
|
|
|
702
786
|
return re.sub(ENDTEST_REGEX, lambda m: m.group(0).replace("endtest", "endmacro"), macro)
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def _strip_jinja_materialization_tags(materialization_jinja: str) -> str:
|
|
790
|
+
MATERIALIZATION_TAG_REGEX = r"\s*{%-?\s*materialization\s+[^%]*%}\s*\n?"
|
|
791
|
+
ENDMATERIALIZATION_REGEX = r"{%-?\s*endmaterialization\s*-?%}\s*\n?"
|
|
792
|
+
|
|
793
|
+
if not re.match(MATERIALIZATION_TAG_REGEX, materialization_jinja):
|
|
794
|
+
return materialization_jinja
|
|
795
|
+
|
|
796
|
+
materialization_jinja = re.sub(
|
|
797
|
+
MATERIALIZATION_TAG_REGEX,
|
|
798
|
+
"",
|
|
799
|
+
materialization_jinja,
|
|
800
|
+
flags=re.IGNORECASE,
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
materialization_jinja = re.sub(
|
|
804
|
+
ENDMATERIALIZATION_REGEX,
|
|
805
|
+
"",
|
|
806
|
+
materialization_jinja,
|
|
807
|
+
flags=re.IGNORECASE,
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
return materialization_jinja.strip()
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def _build_test_name(node: ManifestNode, dependencies: Dependencies) -> str:
|
|
814
|
+
"""
|
|
815
|
+
Build a user-friendly test name that includes the test's model/source, column,
|
|
816
|
+
and args for tests with custom user names. Needed because dbt only generates these
|
|
817
|
+
names for tests that do not specify the "name" field in their YAML definition.
|
|
818
|
+
|
|
819
|
+
Name structure
|
|
820
|
+
- Model test: [namespace]_[test name]_[model name]_[column name]__[arg values]
|
|
821
|
+
- Source test: [namespace]_source_[test name]_[source name]_[table name]_[column name]__[arg values]
|
|
822
|
+
"""
|
|
823
|
+
# standalone test
|
|
824
|
+
if not hasattr(node, "test_metadata"):
|
|
825
|
+
return node.name
|
|
826
|
+
|
|
827
|
+
model_name = _test_model(node)
|
|
828
|
+
source_name = None
|
|
829
|
+
if not model_name and dependencies.sources:
|
|
830
|
+
# extract source and table names
|
|
831
|
+
source_parts = list(dependencies.sources)[0].split(".")
|
|
832
|
+
source_name = "_".join(source_parts) if len(source_parts) == 2 else source_parts[-1]
|
|
833
|
+
entity_name = model_name or source_name or ""
|
|
834
|
+
entity_name = f"_{entity_name}" if entity_name else ""
|
|
835
|
+
|
|
836
|
+
name_prefix = ""
|
|
837
|
+
if namespace := getattr(node.test_metadata, "namespace", None):
|
|
838
|
+
name_prefix += f"{namespace}_"
|
|
839
|
+
if source_name and not model_name:
|
|
840
|
+
name_prefix += "source_"
|
|
841
|
+
|
|
842
|
+
metadata_kwargs = node.test_metadata.kwargs
|
|
843
|
+
arg_val_parts = []
|
|
844
|
+
for arg, val in sorted(metadata_kwargs.items()):
|
|
845
|
+
if arg == "model":
|
|
846
|
+
continue
|
|
847
|
+
if isinstance(val, dict):
|
|
848
|
+
val = list(val.values())
|
|
849
|
+
val = [re.sub("[^0-9a-zA-Z_]+", "_", str(v)) for v in ensure_list(val)]
|
|
850
|
+
arg_val_parts.extend(val)
|
|
851
|
+
unique_args = "__".join(arg_val_parts) if arg_val_parts else ""
|
|
852
|
+
unique_args = f"_{unique_args}" if unique_args else ""
|
|
853
|
+
|
|
854
|
+
auto_name = f"{name_prefix}{node.test_metadata.name}{entity_name}{unique_args}"
|
|
855
|
+
|
|
856
|
+
if node.name == auto_name:
|
|
857
|
+
return node.name
|
|
858
|
+
|
|
859
|
+
custom_prefix = name_prefix if source_name and not model_name else ""
|
|
860
|
+
return f"{custom_prefix}{node.name}{entity_name}{unique_args}"
|