-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support hierarchical config setting for SavedQueryExport configs (#9065)
* Add test asserting `SavedQuery` configs can be set from `dbt_project.yml` * Allow extraneous properties in Export configs This brings the Export config object more in line with how other config objects are specified in the unparsed definition. It allows for specifying of extra configs, although they won't get propagate to the final config. * Add `ExportConfig` options to `SavedQueryConfig` options This allows for specifying `ExportConfig` options at the `SavedQueryConfig` level. This also therefore allows these options to be specified in the dbt_project.yml config. The plan in the follow up commit is to merge the `SavedQueryConfig` options into all configs of `Exports` belonging to the saved query. There are a couple caveots to call out: 1. We've used `schema` instead of `schema_name` on the `SavedQueryConfig` despite it being called `schema_name` on the `ExportConfig`. This is because need `schema_name` to be the name of the property on the `ExportConfig`, but `schema` is the user facing specification. 2. We didn't add the `ExportConfig` `alias` property to the `SavedQueryConfig` This is because `alias` will always be specific to a single export, and thus it doesn't make sense to allow defining it on the `SavedQueryConfig` to then apply to all `Exports` belonging to the `SavedQuery` * Begin inheriting configs from saved query config, and transitively from project config Export configs will now inherit from saved query configs, with a preference for export config specifications. That is to say an export config will inherity a config attr from the saved query config only if a value hasn't been supplied on the export config directly. Additionally because the saved query config has a similar relationship with the project config, exports configs can inherit from the project config (again with a preference for export config specifications). * Correct conditional in export config building for map schema to schema_name I somehow wrote a really weird, but also valid, conditional statement. Previously the conditional was ``` if combined.get("schema") is not combined.get("schema_name") is None: ``` which basically checked whether `schema` was a boolean that didn't match the boolean of whether `schema_name` was None. This would pretty much always evaluate to True because `schema` should be a string or none, not a bool, and thus would never match the right hand side. Crazy. It has now been fixed to do the thing we want to it to do. If `schema` isn't `None`, and `schema_name` is `None`, then set `schema_name` to have the value of `schema`. * Update parameter names in `_get_export_config` to be more verbose
- Loading branch information
Showing
6 changed files
with
283 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
kind: Features | ||
body: Support setting export configs hierarchically via saved query and project configs | ||
time: 2023-11-10T15:42:55.042317-08:00 | ||
custom: | ||
Author: QMalcolm | ||
Issue: "8956" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import pytest | ||
|
||
from dbt.contracts.graph.manifest import Manifest | ||
from dbt.tests.util import update_config_file | ||
from dbt_semantic_interfaces.type_enums.export_destination_type import ExportDestinationType | ||
from tests.functional.assertions.test_runner import dbtTestRunner | ||
from tests.functional.configs.fixtures import BaseConfigProject | ||
from tests.functional.saved_queries.fixtures import ( | ||
saved_queries_yml, | ||
saved_query_description, | ||
saved_query_with_extra_config_attributes_yml, | ||
saved_query_with_export_configs_defined_at_saved_query_level_yml, | ||
saved_query_without_export_configs_defined_yml, | ||
) | ||
from tests.functional.semantic_models.fixtures import ( | ||
fct_revenue_sql, | ||
metricflow_time_spine_sql, | ||
schema_yml, | ||
) | ||
|
||
|
||
class TestSavedQueryConfigs(BaseConfigProject): | ||
@pytest.fixture(scope="class") | ||
def project_config_update(self): | ||
return { | ||
"saved-queries": { | ||
"test": { | ||
"test_saved_query": { | ||
"+enabled": True, | ||
"+export_as": ExportDestinationType.VIEW.value, | ||
"+schema": "my_default_export_schema", | ||
} | ||
}, | ||
}, | ||
} | ||
|
||
@pytest.fixture(scope="class") | ||
def models(self): | ||
return { | ||
"saved_queries.yml": saved_query_with_extra_config_attributes_yml, | ||
"schema.yml": schema_yml, | ||
"fct_revenue.sql": fct_revenue_sql, | ||
"metricflow_time_spine.sql": metricflow_time_spine_sql, | ||
"docs.md": saved_query_description, | ||
} | ||
|
||
def test_basic_saved_query_config( | ||
self, | ||
project, | ||
): | ||
runner = dbtTestRunner() | ||
|
||
# parse with default fixture project config | ||
result = runner.invoke(["parse"]) | ||
assert result.success | ||
assert isinstance(result.result, Manifest) | ||
assert len(result.result.saved_queries) == 1 | ||
saved_query = result.result.saved_queries["saved_query.test.test_saved_query"] | ||
assert saved_query.config.export_as == ExportDestinationType.VIEW | ||
assert saved_query.config.schema == "my_default_export_schema" | ||
|
||
# disable the saved_query via project config and rerun | ||
config_patch = {"saved-queries": {"test": {"test_saved_query": {"+enabled": False}}}} | ||
update_config_file(config_patch, project.project_root, "dbt_project.yml") | ||
result = runner.invoke(["parse"]) | ||
assert result.success | ||
assert len(result.result.saved_queries) == 0 | ||
|
||
|
||
class TestExportConfigsWithAdditionalProperties(BaseConfigProject): | ||
@pytest.fixture(scope="class") | ||
def models(self): | ||
return { | ||
"saved_queries.yml": saved_queries_yml, | ||
"schema.yml": schema_yml, | ||
"fct_revenue.sql": fct_revenue_sql, | ||
"metricflow_time_spine.sql": metricflow_time_spine_sql, | ||
"docs.md": saved_query_description, | ||
} | ||
|
||
def test_extra_config_properties_dont_break_parsing(self, project): | ||
runner = dbtTestRunner() | ||
|
||
# parse with default fixture project config | ||
result = runner.invoke(["parse"]) | ||
assert result.success | ||
assert isinstance(result.result, Manifest) | ||
assert len(result.result.saved_queries) == 1 | ||
saved_query = result.result.saved_queries["saved_query.test.test_saved_query"] | ||
assert len(saved_query.exports) == 1 | ||
assert saved_query.exports[0].config.__dict__.get("my_random_config") is None | ||
|
||
|
||
class TestInheritingExportConfigFromSavedQueryConfig(BaseConfigProject): | ||
@pytest.fixture(scope="class") | ||
def models(self): | ||
return { | ||
"saved_queries.yml": saved_query_with_export_configs_defined_at_saved_query_level_yml, | ||
"schema.yml": schema_yml, | ||
"fct_revenue.sql": fct_revenue_sql, | ||
"metricflow_time_spine.sql": metricflow_time_spine_sql, | ||
"docs.md": saved_query_description, | ||
} | ||
|
||
def test_export_config_inherits_from_saved_query(self, project): | ||
runner = dbtTestRunner() | ||
|
||
# parse with default fixture project config | ||
result = runner.invoke(["parse"]) | ||
assert result.success | ||
assert isinstance(result.result, Manifest) | ||
assert len(result.result.saved_queries) == 1 | ||
saved_query = result.result.saved_queries["saved_query.test.test_saved_query"] | ||
assert len(saved_query.exports) == 2 | ||
|
||
# assert Export `my_export` has its configs defined from itself because they should take priority | ||
export1 = next( | ||
(export for export in saved_query.exports if export.name == "my_export"), None | ||
) | ||
assert export1 is not None | ||
assert export1.config.export_as == ExportDestinationType.VIEW | ||
assert export1.config.export_as != saved_query.config.export_as | ||
assert export1.config.schema_name == "my_custom_export_schema" | ||
assert export1.config.schema_name != saved_query.config.schema | ||
|
||
# assert Export `my_export` has its configs defined from the saved_query because they should take priority | ||
export2 = next( | ||
(export for export in saved_query.exports if export.name == "my_export2"), None | ||
) | ||
assert export2 is not None | ||
assert export2.config.export_as == ExportDestinationType.TABLE | ||
assert export2.config.export_as == saved_query.config.export_as | ||
assert export2.config.schema_name == "my_default_export_schema" | ||
assert export2.config.schema_name == saved_query.config.schema | ||
|
||
|
||
class TestInheritingExportConfigsFromProject(BaseConfigProject): | ||
@pytest.fixture(scope="class") | ||
def project_config_update(self): | ||
return { | ||
"saved-queries": { | ||
"test": { | ||
"test_saved_query": { | ||
"+export_as": ExportDestinationType.VIEW.value, | ||
} | ||
}, | ||
}, | ||
} | ||
|
||
@pytest.fixture(scope="class") | ||
def models(self): | ||
return { | ||
"saved_queries.yml": saved_query_without_export_configs_defined_yml, | ||
"schema.yml": schema_yml, | ||
"fct_revenue.sql": fct_revenue_sql, | ||
"metricflow_time_spine.sql": metricflow_time_spine_sql, | ||
"docs.md": saved_query_description, | ||
} | ||
|
||
def test_export_config_inherits_from_project( | ||
self, | ||
project, | ||
): | ||
runner = dbtTestRunner() | ||
|
||
# parse with default fixture project config | ||
result = runner.invoke(["parse"]) | ||
assert result.success | ||
assert isinstance(result.result, Manifest) | ||
assert len(result.result.saved_queries) == 1 | ||
saved_query = result.result.saved_queries["saved_query.test.test_saved_query"] | ||
assert saved_query.config.export_as == ExportDestinationType.VIEW | ||
|
||
# change export's `export_as` to `TABLE` via project config | ||
config_patch = { | ||
"saved-queries": { | ||
"test": {"test_saved_query": {"+export_as": ExportDestinationType.TABLE.value}} | ||
} | ||
} | ||
update_config_file(config_patch, project.project_root, "dbt_project.yml") | ||
result = runner.invoke(["parse"]) | ||
assert result.success | ||
assert isinstance(result.result, Manifest) | ||
assert len(result.result.saved_queries) == 1 | ||
saved_query = result.result.saved_queries["saved_query.test.test_saved_query"] | ||
assert saved_query.config.export_as == ExportDestinationType.TABLE |