From fa7f0baadfaa096a12c867c46856d765a68f9071 Mon Sep 17 00:00:00 2001 From: Kyle Wigley Date: Thu, 6 May 2021 11:42:57 -0400 Subject: [PATCH 1/5] add ability to set description on tests --- core/dbt/config/project.py | 7 ++- core/dbt/config/renderer.py | 3 + core/dbt/contracts/graph/manifest.py | 5 +- core/dbt/contracts/graph/parsed.py | 11 +++- core/dbt/contracts/graph/unparsed.py | 6 +- core/dbt/contracts/util.py | 1 + core/dbt/node_types.py | 3 +- core/dbt/parser/manifest.py | 55 ++++++++++++++----- core/dbt/parser/schemas.py | 30 +++++----- .../models-v2/test-properties/schema.yml | 29 ++++++++++ .../models-v2/test-properties/table_copy.sql | 8 +++ .../test_schema_v2_tests.py | 34 +++++++++++- test/unit/test_config.py | 6 +- 13 files changed, 157 insertions(+), 41 deletions(-) create mode 100644 test/integration/008_schema_tests_test/models-v2/test-properties/schema.yml create mode 100644 test/integration/008_schema_tests_test/models-v2/test-properties/table_copy.sql diff --git a/core/dbt/config/project.py b/core/dbt/config/project.py index 265ea12a427..c92744e5c3c 100644 --- a/core/dbt/config/project.py +++ b/core/dbt/config/project.py @@ -128,9 +128,10 @@ def _all_source_paths( snapshot_paths: List[str], analysis_paths: List[str], macro_paths: List[str], + test_paths: List[str], ) -> List[str]: return list(chain(source_paths, data_paths, snapshot_paths, analysis_paths, - macro_paths)) + macro_paths, test_paths)) T = TypeVar('T') @@ -333,7 +334,7 @@ def create_project(self, rendered: RenderComponents) -> 'Project': all_source_paths: List[str] = _all_source_paths( source_paths, data_paths, snapshot_paths, analysis_paths, - macro_paths + macro_paths, test_paths ) docs_paths: List[str] = value_or(cfg.docs_paths, all_source_paths) @@ -530,7 +531,7 @@ class Project: def all_source_paths(self) -> List[str]: return _all_source_paths( self.source_paths, self.data_paths, self.snapshot_paths, - self.analysis_paths, self.macro_paths + self.analysis_paths, self.macro_paths, self.test_paths ) def __str__(self): diff --git a/core/dbt/config/renderer.py b/core/dbt/config/renderer.py index 8df363903f1..e4ff3c9078d 100644 --- a/core/dbt/config/renderer.py +++ b/core/dbt/config/renderer.py @@ -220,6 +220,9 @@ def should_render_keypath(self, keypath: Keypath) -> bool: return False elif self._is_norender_key(keypath[1:]): return False + elif keypath[0] == NodeType.Test.pluralize(): + if keypath[2] == 'description': + return False else: # keypath[0] in self.DOCUMENTABLE_NODES: if self._is_norender_key(keypath[1:]): return False diff --git a/core/dbt/contracts/graph/manifest.py b/core/dbt/contracts/graph/manifest.py index 7c90cb10ecc..a08b241d91d 100644 --- a/core/dbt/contracts/graph/manifest.py +++ b/core/dbt/contracts/graph/manifest.py @@ -417,7 +417,7 @@ class Disabled(Generic[D]): def _update_into(dest: MutableMapping[str, T], new_item: T): """Update dest to overwrite whatever is at dest[new_item.unique_id] with - new_itme. There must be an existing value to overwrite, and they two nodes + new_item. There must be an existing value to overwrite, and they two nodes must have the same original file path. """ unique_id = new_item.unique_id @@ -730,7 +730,7 @@ def patch_nodes(self) -> None: # Q: could we save patches by node unique_ids instead, or convert # between names and node ids? for node in self.nodes.values(): - patch = self.patches.pop(node.name, None) + patch = self.patches.pop(node.patch_lookup_key, None) if not patch: continue @@ -745,7 +745,6 @@ def patch_nodes(self) -> None: raise_invalid_patch( node, patch.yaml_key, patch.original_file_path ) - node.patch(patch) # If anything is left in self.patches, it means that the node for diff --git a/core/dbt/contracts/graph/parsed.py b/core/dbt/contracts/graph/parsed.py index 0282292eb03..685a9a93f3c 100644 --- a/core/dbt/contracts/graph/parsed.py +++ b/core/dbt/contracts/graph/parsed.py @@ -368,6 +368,10 @@ class ParsedSchemaTestNode(ParsedNode, HasTestMetadata): column_name: Optional[str] = None config: TestConfig = field(default_factory=TestConfig) + @property + def patch_lookup_key(self) -> str: + return self.test_metadata.name + def same_config(self, other) -> bool: return ( self.unrendered_config.get('severity') == @@ -427,6 +431,11 @@ class ParsedMacroPatch(ParsedPatch): arguments: List[MacroArgument] = field(default_factory=list) +@dataclass +class ParsedTestPatch(ParsedMacroPatch): + pass + + @dataclass class ParsedMacro(UnparsedBaseNode, HasUniqueID): name: str @@ -445,7 +454,7 @@ class ParsedMacro(UnparsedBaseNode, HasUniqueID): def local_vars(self): return {} - def patch(self, patch: ParsedMacroPatch): + def patch(self, patch: Union[ParsedMacroPatch, ParsedTestPatch]): self.patch_path: Optional[str] = patch.original_file_path self.description = patch.description self.meta = patch.meta diff --git a/core/dbt/contracts/graph/unparsed.py b/core/dbt/contracts/graph/unparsed.py index e6656c92a40..6aef2b0eb4e 100644 --- a/core/dbt/contracts/graph/unparsed.py +++ b/core/dbt/contracts/graph/unparsed.py @@ -54,7 +54,11 @@ class UnparsedNode(UnparsedBaseNode, HasSQL): ]}) @property - def search_name(self): + def patch_lookup_key(self) -> str: + return self.name + + @property + def search_name(self) -> str: return self.name diff --git a/core/dbt/contracts/util.py b/core/dbt/contracts/util.py index 5dd329d193e..3979ee99d85 100644 --- a/core/dbt/contracts/util.py +++ b/core/dbt/contracts/util.py @@ -14,6 +14,7 @@ from dbt.tracking import get_invocation_id from dbt.dataclass_schema import dbtClassMixin +TestKey = Tuple[str, str] MacroKey = Tuple[str, str] SourceKey = Tuple[str, str] diff --git a/core/dbt/node_types.py b/core/dbt/node_types.py index b21d738c4c5..5feb216ad7b 100644 --- a/core/dbt/node_types.py +++ b/core/dbt/node_types.py @@ -46,7 +46,8 @@ def documentable(cls) -> List['NodeType']: cls.Source, cls.Macro, cls.Analysis, - cls.Exposure + cls.Exposure, + cls.Test ] def pluralize(self) -> str: diff --git a/core/dbt/parser/manifest.py b/core/dbt/parser/manifest.py index 9e4704c152e..f5244153cdb 100644 --- a/core/dbt/parser/manifest.py +++ b/core/dbt/parser/manifest.py @@ -32,7 +32,7 @@ Manifest, Disabled, MacroManifest, ManifestStateCheck ) from dbt.contracts.graph.parsed import ( - ParsedSourceDefinition, ParsedNode, ParsedMacro, ColumnInfo, ParsedExposure + ParsedSchemaTestNode, ParsedSourceDefinition, ParsedMacro, ColumnInfo, ParsedExposure ) from dbt.contracts.util import Writable from dbt.exceptions import ( @@ -693,12 +693,6 @@ def _get_node_column(node, column_name): return column -DocsContextCallback = Callable[ - [Union[ParsedNode, ParsedSourceDefinition]], - Dict[str, Any] -] - - # node and column descriptions def _process_docs_for_node( context: Dict[str, Any], @@ -749,12 +743,42 @@ def _process_docs_for_exposure( # exposures: exposure descriptions def process_docs(manifest: Manifest, config: RuntimeConfig): for node in manifest.nodes.values(): - ctx = generate_runtime_docs( - config, - node, - manifest, - config.project_name, - ) + if isinstance(node, ParsedSchemaTestNode): + # Idealing we should be able to put the test kwargs in the context, + # but kwargs have already been processed at this point + # ex: 'model' --> "{{ ref('model') }}" + ctx = generate_runtime_docs( + config, + node, + manifest, + config.project_name, + ) + + model = [] + + if len(node.refs): + model = node.refs[0] + elif len(node.sources): + model = node.sources[0] + + if len(model) == 1: + target_model_name = model[0] + elif len(model) == 2: + _, target_model_name = model + else: + raise dbt.exceptions.InternalException( + f'Refs and sources should always be 1 or 2 arguments - got {len(model)}' + ) + + ctx['column_name'] = node.column_name + ctx['model'] = target_model_name + else: + ctx = generate_runtime_docs( + config, + node, + manifest, + config.project_name, + ) _process_docs_for_node(ctx, node) for source in manifest.sources.values(): ctx = generate_runtime_docs( @@ -782,6 +806,10 @@ def process_docs(manifest: Manifest, config: RuntimeConfig): _process_docs_for_exposure(ctx, exposure) +def process_tests(manifest: Manifest): + pass + + def _process_refs_for_exposure( manifest: Manifest, current_project: str, exposure: ParsedExposure ): @@ -856,7 +884,6 @@ def _process_refs_for_node( node, target_model_name, target_model_package, disabled=(isinstance(target_model, Disabled)) ) - continue target_model_id = target_model.unique_id diff --git a/core/dbt/parser/schemas.py b/core/dbt/parser/schemas.py index 316ef5c9d70..143845ecfbf 100644 --- a/core/dbt/parser/schemas.py +++ b/core/dbt/parser/schemas.py @@ -33,6 +33,7 @@ ColumnInfo, ParsedSchemaTestNode, ParsedMacroPatch, + ParsedTestPatch, UnpatchedSourceDefinition, ParsedExposure, ) @@ -234,14 +235,14 @@ def _yaml_from_file( ) return None - def parse_column_tests( + def parse_column_test_instances( self, block: TestBlock, column: UnparsedColumn ) -> None: if not column.tests: return for test in column.tests: - self.parse_test(block, test, column) + self.parse_test_instance(block, test, column) def _generate_source_config(self, fqn: List[str], rendered: bool): generator: BaseContextConfigGenerator @@ -393,7 +394,6 @@ def _parse_generic_test( tags: List[str], column_name: Optional[str], ) -> ParsedSchemaTestNode: - render_ctx = generate_target_context( self.root_project, self.root_project.cli_vars ) @@ -564,7 +564,7 @@ def render_with_context( node.raw_sql, context, node, capture_macros=True ) - def parse_test( + def parse_test_instance( self, target_block: TestBlock, test: TestDef, @@ -594,12 +594,12 @@ def parse_test( ) self.parse_node(block) - def parse_tests(self, block: TestBlock) -> None: + def parse_test_instances(self, block: TestBlock) -> None: for column in block.columns: - self.parse_column_tests(block, column) + self.parse_column_test_instances(block, column) for test in block.tests: - self.parse_test(block, test, None) + self.parse_test_instance(block, test, None) def parse_exposures(self, block: YamlBlock) -> None: parser = ExposureParser(self, block) @@ -638,19 +638,19 @@ def parse_file(self, block: FileBlock) -> None: if 'models' in dct: parser = TestablePatchParser(self, yaml_block, 'models') for test_block in parser.parse(): - self.parse_tests(test_block) + self.parse_test_instances(test_block) # NonSourceParser.parse() if 'seeds' in dct: parser = TestablePatchParser(self, yaml_block, 'seeds') for test_block in parser.parse(): - self.parse_tests(test_block) + self.parse_test_instances(test_block) # NonSourceParser.parse() if 'snapshots' in dct: parser = TestablePatchParser(self, yaml_block, 'snapshots') for test_block in parser.parse(): - self.parse_tests(test_block) + self.parse_test_instances(test_block) # This parser uses SourceParser.parse() which doesn't return # any test blocks. Source tests are handled at a later point @@ -663,13 +663,17 @@ def parse_file(self, block: FileBlock) -> None: if 'macros' in dct: parser = MacroPatchParser(self, yaml_block, 'macros') for test_block in parser.parse(): - self.parse_tests(test_block) + self.parse_test_instances(test_block) + + if 'tests' in dct: + parser = TestablePatchParser(self, yaml_block, 'tests') + parser.parse() # NonSourceParser.parse() if 'analyses' in dct: parser = AnalysisPatchParser(self, yaml_block, 'analyses') for test_block in parser.parse(): - self.parse_tests(test_block) + self.parse_test_instances(test_block) # parse exposures if 'exposures' in dct: @@ -678,7 +682,7 @@ def parse_file(self, block: FileBlock) -> None: Parsed = TypeVar( 'Parsed', - UnpatchedSourceDefinition, ParsedNodePatch, ParsedMacroPatch + UnpatchedSourceDefinition, ParsedNodePatch, ParsedMacroPatch, ParsedTestPatch ) NodeTarget = TypeVar( 'NodeTarget', diff --git a/test/integration/008_schema_tests_test/models-v2/test-properties/schema.yml b/test/integration/008_schema_tests_test/models-v2/test-properties/schema.yml new file mode 100644 index 00000000000..e5839a107e1 --- /dev/null +++ b/test/integration/008_schema_tests_test/models-v2/test-properties/schema.yml @@ -0,0 +1,29 @@ +version: 2 + +models: + - name: table_copy + description: "A copy of the table" + columns: + - name: id + description: "The ID" + tests: + - not_null + - unique + - name: first_name + description: "The user's first name" + - name: ip_address + description: "The user's IP address" + - name: updated_at + description: "The update time of the user" + - name: email + description: "The user's email address" + - name: favorite_color + description: "The user's favorite color" + - name: fav_number + description: "The user's favorite number" + +tests: + - name: not_null + description: "test not null description for column {{ column_name }} on model {{ model }}" + - name: unique + description: "test unique description for column {{ column_name }} on model {{ model }}" diff --git a/test/integration/008_schema_tests_test/models-v2/test-properties/table_copy.sql b/test/integration/008_schema_tests_test/models-v2/test-properties/table_copy.sql new file mode 100644 index 00000000000..56e90a6d93c --- /dev/null +++ b/test/integration/008_schema_tests_test/models-v2/test-properties/table_copy.sql @@ -0,0 +1,8 @@ + +{{ + config( + materialized='table' + ) +}} + +select * from {{ this.schema }}.seed diff --git a/test/integration/008_schema_tests_test/test_schema_v2_tests.py b/test/integration/008_schema_tests_test/test_schema_v2_tests.py index 75c3e77b6f2..41ebd725e7d 100644 --- a/test/integration/008_schema_tests_test/test_schema_v2_tests.py +++ b/test/integration/008_schema_tests_test/test_schema_v2_tests.py @@ -366,8 +366,6 @@ def test_postgres_test_context_tests(self): results = self.run_dbt(['test'], expect_pass=False) self.assertEqual(len(results), 2) - result0 = results[0] - result1 = results[1] for result in results: if result.node.name == 'type_two_model_a_': # This will be WARN if the test macro was rendered correctly @@ -377,3 +375,35 @@ def test_postgres_test_context_tests(self): # was rendered correctly self.assertRegex(result.node.compiled_sql, r'union all') + +class TestSchemaTestProperties(DBTIntegrationTest): + def setUp(self): + DBTIntegrationTest.setUp(self) + self.run_sql_file("seed.sql") + + @property + def schema(self): + return "schema_tests_008" + + @property + def models(self): + return "models-v2/test-properties" + + @use_profile('postgres') + def test_postgres_test_description(self): + results = self.run_dbt() + self.assertEqual(len(results), 1) + results = self.run_dbt(['test']) + self.assertEqual(len(results), 2) + + test_descriptions = [ + 'test not null description for column id on model table_copy', + 'test unique description for column id on model table_copy' + ] + + test_result_descriptions = [] + + for result in results: + test_result_descriptions.append(result.node.description) + + self.assertListEqual(sorted(test_result_descriptions), sorted(test_descriptions)) diff --git a/test/unit/test_config.py b/test/unit/test_config.py index f875f04bf3c..abb69485dfe 100644 --- a/test/unit/test_config.py +++ b/test/unit/test_config.py @@ -621,7 +621,7 @@ def test_defaults(self): self.assertEqual(project.data_paths, ['data']) self.assertEqual(project.test_paths, ['test']) self.assertEqual(project.analysis_paths, []) - self.assertEqual(project.docs_paths, ['models', 'data', 'snapshots', 'macros']) + self.assertEqual(project.docs_paths, ['models', 'data', 'snapshots', 'macros', 'test']) self.assertEqual(project.asset_paths, []) self.assertEqual(project.target_path, 'target') self.assertEqual(project.clean_targets, ['target']) @@ -654,7 +654,7 @@ def test_implicit_overrides(self): 'target-path': 'other-target', }) project = project_from_config_norender(self.default_project_data) - self.assertEqual(project.docs_paths, ['other-models', 'data', 'snapshots', 'macros']) + self.assertEqual(project.docs_paths, ['other-models', 'data', 'snapshots', 'macros', 'test']) self.assertEqual(project.clean_targets, ['other-target']) def test_hashed_name(self): @@ -1223,7 +1223,7 @@ def test_from_args(self): self.assertEqual(config.data_paths, ['data']) self.assertEqual(config.test_paths, ['test']) self.assertEqual(config.analysis_paths, []) - self.assertEqual(config.docs_paths, ['models', 'data', 'snapshots', 'macros']) + self.assertEqual(config.docs_paths, ['models', 'data', 'snapshots', 'macros', 'test']) self.assertEqual(config.asset_paths, []) self.assertEqual(config.target_path, 'target') self.assertEqual(config.clean_targets, ['target']) From 22a0418b7d0903448f7bed86e419de20866f236d Mon Sep 17 00:00:00 2001 From: Kyle Wigley Date: Thu, 6 May 2021 12:30:39 -0400 Subject: [PATCH 2/5] patches need to be applied to multiple nodes -> one patch for all instances of a generic test --- core/dbt/contracts/graph/manifest.py | 16 ++++++++++++---- .../models-v2/test-properties/schema.yml | 2 ++ .../test_schema_v2_tests.py | 5 +++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/core/dbt/contracts/graph/manifest.py b/core/dbt/contracts/graph/manifest.py index a08b241d91d..913b00ebdb0 100644 --- a/core/dbt/contracts/graph/manifest.py +++ b/core/dbt/contracts/graph/manifest.py @@ -729,8 +729,12 @@ def patch_nodes(self) -> None: # were ok with doing an O(n*m) search (one nodes scan per patch) # Q: could we save patches by node unique_ids instead, or convert # between names and node ids? + patch_keys = set(self.patches.keys()) + used_patch_keys = set() + for node in self.nodes.values(): - patch = self.patches.pop(node.patch_lookup_key, None) + patch_lookup_key = node.patch_lookup_key + patch = self.patches.get(patch_lookup_key, None) if not patch: continue @@ -746,11 +750,15 @@ def patch_nodes(self) -> None: node, patch.yaml_key, patch.original_file_path ) node.patch(patch) + used_patch_keys.add(patch_lookup_key) + + unused_patch_keys = patch_keys - used_patch_keys - # If anything is left in self.patches, it means that the node for + # if there are any unused patch names, it means that the node for # that patch wasn't found. - if self.patches: - for patch in self.patches.values(): + if unused_patch_keys: + for patch_key in unused_patch_keys: + patch = self.patches[patch_key] # since patches aren't nodes, we can't use the existing # target_not_found warning logger.debug(( diff --git a/test/integration/008_schema_tests_test/models-v2/test-properties/schema.yml b/test/integration/008_schema_tests_test/models-v2/test-properties/schema.yml index e5839a107e1..21e037a6fa8 100644 --- a/test/integration/008_schema_tests_test/models-v2/test-properties/schema.yml +++ b/test/integration/008_schema_tests_test/models-v2/test-properties/schema.yml @@ -13,6 +13,8 @@ models: description: "The user's first name" - name: ip_address description: "The user's IP address" + tests: + - unique - name: updated_at description: "The update time of the user" - name: email diff --git a/test/integration/008_schema_tests_test/test_schema_v2_tests.py b/test/integration/008_schema_tests_test/test_schema_v2_tests.py index 41ebd725e7d..9ddaf00765c 100644 --- a/test/integration/008_schema_tests_test/test_schema_v2_tests.py +++ b/test/integration/008_schema_tests_test/test_schema_v2_tests.py @@ -394,11 +394,12 @@ def test_postgres_test_description(self): results = self.run_dbt() self.assertEqual(len(results), 1) results = self.run_dbt(['test']) - self.assertEqual(len(results), 2) + self.assertEqual(len(results), 3) test_descriptions = [ 'test not null description for column id on model table_copy', - 'test unique description for column id on model table_copy' + 'test unique description for column id on model table_copy', + 'test unique description for column ip_address on model table_copy' ] test_result_descriptions = [] From f04443adc8ea1b911c7dfce99659e856a40bbe37 Mon Sep 17 00:00:00 2001 From: Kyle Wigley Date: Thu, 6 May 2021 12:50:24 -0400 Subject: [PATCH 3/5] clean up unused code --- core/dbt/contracts/graph/parsed.py | 7 +------ core/dbt/contracts/util.py | 1 - core/dbt/parser/manifest.py | 4 ---- core/dbt/parser/schemas.py | 3 +-- 4 files changed, 2 insertions(+), 13 deletions(-) diff --git a/core/dbt/contracts/graph/parsed.py b/core/dbt/contracts/graph/parsed.py index 685a9a93f3c..83e8eda8d89 100644 --- a/core/dbt/contracts/graph/parsed.py +++ b/core/dbt/contracts/graph/parsed.py @@ -431,11 +431,6 @@ class ParsedMacroPatch(ParsedPatch): arguments: List[MacroArgument] = field(default_factory=list) -@dataclass -class ParsedTestPatch(ParsedMacroPatch): - pass - - @dataclass class ParsedMacro(UnparsedBaseNode, HasUniqueID): name: str @@ -454,7 +449,7 @@ class ParsedMacro(UnparsedBaseNode, HasUniqueID): def local_vars(self): return {} - def patch(self, patch: Union[ParsedMacroPatch, ParsedTestPatch]): + def patch(self, patch: Union[ParsedMacroPatch]): self.patch_path: Optional[str] = patch.original_file_path self.description = patch.description self.meta = patch.meta diff --git a/core/dbt/contracts/util.py b/core/dbt/contracts/util.py index 3979ee99d85..5dd329d193e 100644 --- a/core/dbt/contracts/util.py +++ b/core/dbt/contracts/util.py @@ -14,7 +14,6 @@ from dbt.tracking import get_invocation_id from dbt.dataclass_schema import dbtClassMixin -TestKey = Tuple[str, str] MacroKey = Tuple[str, str] SourceKey = Tuple[str, str] diff --git a/core/dbt/parser/manifest.py b/core/dbt/parser/manifest.py index f5244153cdb..7f7e87b4192 100644 --- a/core/dbt/parser/manifest.py +++ b/core/dbt/parser/manifest.py @@ -806,10 +806,6 @@ def process_docs(manifest: Manifest, config: RuntimeConfig): _process_docs_for_exposure(ctx, exposure) -def process_tests(manifest: Manifest): - pass - - def _process_refs_for_exposure( manifest: Manifest, current_project: str, exposure: ParsedExposure ): diff --git a/core/dbt/parser/schemas.py b/core/dbt/parser/schemas.py index 143845ecfbf..14f0aa9da41 100644 --- a/core/dbt/parser/schemas.py +++ b/core/dbt/parser/schemas.py @@ -33,7 +33,6 @@ ColumnInfo, ParsedSchemaTestNode, ParsedMacroPatch, - ParsedTestPatch, UnpatchedSourceDefinition, ParsedExposure, ) @@ -682,7 +681,7 @@ def parse_file(self, block: FileBlock) -> None: Parsed = TypeVar( 'Parsed', - UnpatchedSourceDefinition, ParsedNodePatch, ParsedMacroPatch, ParsedTestPatch + UnpatchedSourceDefinition, ParsedNodePatch, ParsedMacroPatch ) NodeTarget = TypeVar( 'NodeTarget', From f227ee9752a7234e4baf59e96cdc8cb4132b814a Mon Sep 17 00:00:00 2001 From: Kyle Wigley Date: Thu, 6 May 2021 13:34:39 -0400 Subject: [PATCH 4/5] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76076a9ac92..832b715e361 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## dbt 0.20.0 (Release TBD) +### Features +- Support descriptions defined as a yaml property for tests. ([#3249](https://github.com/fishtown-analytics/dbt/issues/3249), [#3302](https://github.com/fishtown-analytics/dbt/pull/3302)) + ### Fixes - Fix compiled sql for ephemeral models ([#3317](https://github.com/fishtown-analytics/dbt/issues/3317), [#3318](https://github.com/fishtown-analytics/dbt/pull/3318)) - Now generating `run_results.json` even when no nodes are selected ([#3313](https://github.com/fishtown-analytics/dbt/issues/3313), [#3315](https://github.com/fishtown-analytics/dbt/pull/3315)) From e456742e20fbc099f32253bf3e9bcffd5d348bb7 Mon Sep 17 00:00:00 2001 From: Kyle Wigley Date: Thu, 6 May 2021 14:45:52 -0400 Subject: [PATCH 5/5] Update core/dbt/contracts/graph/parsed.py --- core/dbt/contracts/graph/parsed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/dbt/contracts/graph/parsed.py b/core/dbt/contracts/graph/parsed.py index 83e8eda8d89..df7ab341383 100644 --- a/core/dbt/contracts/graph/parsed.py +++ b/core/dbt/contracts/graph/parsed.py @@ -449,7 +449,7 @@ class ParsedMacro(UnparsedBaseNode, HasUniqueID): def local_vars(self): return {} - def patch(self, patch: Union[ParsedMacroPatch]): + def patch(self, patch: ParsedMacroPatch): self.patch_path: Optional[str] = patch.original_file_path self.description = patch.description self.meta = patch.meta