Skip to content

Commit

Permalink
fix: Avoid access to profile when calling str(UnsetProfileConfig) (#5209
Browse files Browse the repository at this point in the history
)

* fix: Avoid access to profile when calling str(UnsetProfileConfig)

dbt.config.UnsetProfileConfig inherits __str__ from
dbt.config.Project. Moreover, UnsetProfileConfig also raises an
exception when attempting to access unset profile attributes. As
Project.__str__ ultimately calls to_project_config and accesses said
profile attributes, we override to_project_config in
UnsetProfileConfig to avoid accessing the attributes that raise an
exception.

This allows calling str(UnsetProfileConfig) and
repr(UnsetProfileConfig).

Basic unit testing is also included in commit.

* fix: Skip repr for profile fields in UnsetProfileConfig

* chore(changie): Add changie file
  • Loading branch information
tomasfarias authored May 13, 2022
1 parent 7e43f36 commit fc1fc2d
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 2 deletions.
7 changes: 7 additions & 0 deletions .changes/unreleased/Under the Hood-20220503-195212.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
kind: Under the Hood
body: 'Fix: Call str and repr for UnsetProfileConfig without a RuntimeException'
time: 2022-05-03T19:52:12.793729384+02:00
custom:
Author: tomasfarias
Issue: "5081"
PR: "5209"
55 changes: 54 additions & 1 deletion core/dbt/config/runtime.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import itertools
import os
from copy import deepcopy
from dataclasses import dataclass
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, Any, Optional, Mapping, Iterator, Iterable, Tuple, List, MutableSet, Type

Expand Down Expand Up @@ -417,6 +417,9 @@ class UnsetProfileConfig(RuntimeConfig):
missing, any access to profile members results in an exception.
"""

profile_name: str = field(repr=False)
target_name: str = field(repr=False)

def __post_init__(self):
# instead of futzing with InitVar overrides or rewriting __init__, just
# `del` the attrs we don't want users touching.
Expand All @@ -437,6 +440,56 @@ def to_target_dict(self):
# re-override the poisoned profile behavior
return DictDefaultEmptyStr({})

def to_project_config(self, with_packages=False):
"""Return a dict representation of the config that could be written to
disk with `yaml.safe_dump` to get this configuration.
Overrides dbt.config.Project.to_project_config to omit undefined profile
attributes.
:param with_packages bool: If True, include the serialized packages
file in the root.
:returns dict: The serialized profile.
"""
result = deepcopy(
{
"name": self.project_name,
"version": self.version,
"project-root": self.project_root,
"profile": "",
"model-paths": self.model_paths,
"macro-paths": self.macro_paths,
"seed-paths": self.seed_paths,
"test-paths": self.test_paths,
"analysis-paths": self.analysis_paths,
"docs-paths": self.docs_paths,
"asset-paths": self.asset_paths,
"target-path": self.target_path,
"snapshot-paths": self.snapshot_paths,
"clean-targets": self.clean_targets,
"log-path": self.log_path,
"quoting": self.quoting,
"models": self.models,
"on-run-start": self.on_run_start,
"on-run-end": self.on_run_end,
"dispatch": self.dispatch,
"seeds": self.seeds,
"snapshots": self.snapshots,
"sources": self.sources,
"tests": self.tests,
"vars": self.vars.to_dict(),
"require-dbt-version": [v.to_version_string() for v in self.dbt_version],
"config-version": self.config_version,
}
)
if self.query_comment:
result["query-comment"] = self.query_comment.to_dict(omit_none=True)

if with_packages:
result.update(self.packages.to_dict(omit_none=True))

return result

@classmethod
def from_parts(
cls,
Expand Down
41 changes: 40 additions & 1 deletion test/unit/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ def test_extra_path(self):
'model-paths': ['models'],
'source-paths': ['other-models'],
})
with self.assertRaises(dbt.exceptions.DbtProjectError) as exc:
with self.assertRaises(dbt.exceptions.DbtProjectError) as exc:
project = project_from_config_norender(self.default_project_data)

self.assertIn('source-paths and model-paths', str(exc.exception))
Expand Down Expand Up @@ -1230,6 +1230,45 @@ def test_from_args(self):
self.assertEqual(config.project_name, 'my_test_project')


class TestUnsetProfileConfig(BaseConfigTest):
def setUp(self):
self.profiles_dir = '/invalid-profiles-path'
self.project_dir = '/invalid-root-path'
super().setUp()
self.default_project_data['project-root'] = self.project_dir

def get_project(self):
return project_from_config_norender(self.default_project_data, verify_version=self.args.version_check)

def get_profile(self):
renderer = empty_profile_renderer()
return dbt.config.Profile.from_raw_profiles(
self.default_profile_data, self.default_project_data['profile'], renderer
)

def test_str(self):
project = self.get_project()
profile = self.get_profile()
config = dbt.config.UnsetProfileConfig.from_parts(project, profile, {})

str(config)

def test_repr(self):
project = self.get_project()
profile = self.get_profile()
config = dbt.config.UnsetProfileConfig.from_parts(project, profile, {})

repr(config)

def test_to_project_config(self):
project = self.get_project()
profile = self.get_profile()
config = dbt.config.UnsetProfileConfig.from_parts(project, profile, {})
project_config = config.to_project_config()

self.assertEqual(project_config["profile"], "")


class TestVariableRuntimeConfigFiles(BaseFileTest):
def setUp(self):
super().setUp()
Expand Down

0 comments on commit fc1fc2d

Please sign in to comment.