Skip to content

Commit

Permalink
allow "dbt debug" from subdirectories
Browse files Browse the repository at this point in the history
When dbt_project.yml is missing, fail more gracefully
When dbt_project.yml is missing and no profile is provided, validate all profiles:
 - read everything in profiles.yml
 - validate the default target for each profile
 - print no connection info
 - mark profiles.yml as valid
  • Loading branch information
Jacob Beck committed Feb 4, 2020
1 parent ccab27a commit ab219e0
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 26 deletions.
4 changes: 2 additions & 2 deletions core/dbt/adapters/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ def load_plugin(self, name: str) -> Type[Credentials]:
mod: Any = import_module('.' + name, 'dbt.adapters')
except ModuleNotFoundError as exc:
# if we failed to import the target module in particular, inform
# the user about it via a runtiem error
logger.info(f'Error importing adapter: {exc}')
# the user about it via a runtime error
if exc.name == 'dbt.adapters.' + name:
raise RuntimeException(f'Could not find adapter type {name}!')
logger.info(f'Error importing adapter: {exc}')
# otherwise, the error had to have come from some underlying
# library. Log the stack trace.
logger.debug('', exc_info=True)
Expand Down
58 changes: 34 additions & 24 deletions core/dbt/task/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from dbt.clients.yaml_helper import load_yaml_text
from dbt.ui.printer import green, red

from dbt.task.base import BaseTask
from dbt.task.base import BaseTask, get_nearest_project_dir

PROFILE_DIR_MESSAGE = """To view your profiles.yml file, run:
Expand Down Expand Up @@ -70,7 +70,10 @@ def __init__(self, args, config):
self.profiles_dir = getattr(self.args, 'profiles_dir',
dbt.config.PROFILES_DIR)
self.profile_path = os.path.join(self.profiles_dir, 'profiles.yml')
self.project_dir = args.project_dir or os.getcwd()
try:
self.project_dir = get_nearest_project_dir(self.args)
except dbt.exceptions.Exception:
self.project_dir = os.getcwd()
self.project_path = os.path.join(self.project_dir, 'dbt_project.yml')
self.cli_vars = dbt.utils.parse_cli_vars(
getattr(self.args, 'vars', '{}')
Expand Down Expand Up @@ -112,6 +115,7 @@ def run(self):
print('python path: {}'.format(sys.executable))
print('os info: {}'.format(platform.platform()))
print('Using profiles.yml file at {}'.format(self.profile_path))
print('Using dbt_project.yml file at {}'.format(self.project_path))
print('')
self.test_configuration()
self.test_dependencies()
Expand Down Expand Up @@ -160,7 +164,7 @@ def _target_found(self):
return red('ERROR not found')
return green('OK found')

def _choose_profile_name(self):
def _choose_profile_names(self) -> Optional[List[str]]:
assert self.project or self.project_fail_details, \
'_load_project() required'

Expand All @@ -171,7 +175,7 @@ def _choose_profile_name(self):
args_profile = getattr(self.args, 'profile', None)

try:
return Profile.pick_profile_name(args_profile, project_profile)
return [Profile.pick_profile_name(args_profile, project_profile)]
except dbt.exceptions.DbtConfigError:
pass
# try to guess
Expand All @@ -182,25 +186,25 @@ def _choose_profile_name(self):
self.messages.append('The profiles.yml has no profiles')
elif len(profiles) == 1:
self.messages.append(ONLY_PROFILE_MESSAGE.format(profiles[0]))
return profiles[0]
return profiles
else:
self.messages.append(MULTIPLE_PROFILE_MESSAGE.format(
'\n'.join(' - {}'.format(o) for o in profiles)
))
return profiles
return None

def _choose_target_name(self):
has_raw_profile = (self.raw_profile_data and self.profile_name and
self.profile_name in self.raw_profile_data)
# mypy appeasement, we checked just above
assert self.raw_profile_data is not None
assert self.profile_name is not None
def _choose_target_name(self, profile_name: str):
has_raw_profile = (self.raw_profile_data and profile_name and
profile_name in self.raw_profile_data)

if has_raw_profile:
raw_profile = self.raw_profile_data[self.profile_name]
# mypy appeasement, we checked just above
assert self.raw_profile_data is not None
raw_profile = self.raw_profile_data[profile_name]

target_name, _ = Profile.render_profile(
raw_profile, self.profile_name,
raw_profile, profile_name,
getattr(self.args, 'target', None), self.cli_vars
)
return target_name
Expand All @@ -224,16 +228,24 @@ def _load_profile(self):
if isinstance(raw_profile_data, dict):
self.raw_profile_data = raw_profile_data

self.profile_name = self._choose_profile_name()
self.target_name = self._choose_target_name()
try:
self.profile = QueryCommentedProfile.from_args(
self.args, self.profile_name
)
except dbt.exceptions.DbtConfigError as exc:
self.profile_fail_details = str(exc)
return red('ERROR invalid')
profile_errors = []
profile_names = self._choose_profile_names()
for profile_name in profile_names:
try:
profile: Profile = QueryCommentedProfile.from_args(
self.args, profile_name
)
except dbt.exceptions.DbtConfigError as exc:
profile_errors.append(str(exc))
else:
if len(profile_names) == 1:
# if a profile was specified, set it on the task
self.target_name = self._choose_target_name(profile_name)
self.profile = profile

if profile_errors:
self.profile_fail_details = '\n\n'.join(profile_errors)
return red('ERROR invalid')
return green('OK found and valid')

def test_git(self):
Expand Down Expand Up @@ -279,8 +291,6 @@ def _log_profile_fail(self):
return
if self.profile_fail_details == FILE_NOT_FOUND:
return
if self.profile_name is None:
return # we expect an error (no profile provided)
print('Profile loading failed for the following reason:')
print(self.profile_fail_details)
print('')
Expand Down

0 comments on commit ab219e0

Please sign in to comment.