Skip to content

Commit

Permalink
[#4554] Don't require a profile for dbt deps and clean commands (#4610)
Browse files Browse the repository at this point in the history
  • Loading branch information
gshank authored Jan 25, 2022
1 parent 1df7a02 commit 3032594
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 58 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

### Fixes
- Projects created using `dbt init` now have the correct `seeds` directory created (instead of `data`) ([#4588](https://github.com/dbt-labs/dbt-core/issues/4588), [#4599](https://github.com/dbt-labs/dbt-core/pull/4589))
- Don't require a profile for dbt deps and clean commands ([#4554](https://github.com/dbt-labs/dbt-core/issues/4554), [#4610](https://github.com/dbt-labs/dbt-core/pull/4610))

## dbt-core 1.0.1 (January 03, 2022)

Expand Down
51 changes: 15 additions & 36 deletions 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, fields
from dataclasses import dataclass
from pathlib import Path
from typing import (
Dict, Any, Optional, Mapping, Iterator, Iterable, Tuple, List, MutableSet,
Expand All @@ -13,20 +13,17 @@
from .renderer import DbtProjectYamlRenderer, ProfileRenderer
from .utils import parse_cli_vars
from dbt import flags
from dbt import tracking
from dbt.adapters.factory import get_relation_class_by_name, get_include_paths
from dbt.helper_types import FQNPath, PathSet
from dbt.config.profile import read_user_config
from dbt.contracts.connection import AdapterRequiredConfig, Credentials
from dbt.contracts.graph.manifest import ManifestMetadata
from dbt.contracts.relation import ComponentName
from dbt.events.types import ProfileLoadError, ProfileNotFound
from dbt.events.functions import fire_event
from dbt.ui import warning_tag

from dbt.contracts.project import Configuration, UserConfig
from dbt.exceptions import (
RuntimeException,
DbtProfileError,
DbtProjectError,
validator_error_message,
warn_or_error,
Expand Down Expand Up @@ -191,6 +188,7 @@ def _get_rendered_profile(
profile_renderer: ProfileRenderer,
profile_name: Optional[str],
) -> Profile:

return Profile.render_from_args(
args, profile_renderer, profile_name
)
Expand Down Expand Up @@ -412,21 +410,12 @@ def _connection_keys(self):
return ()


class UnsetConfig(UserConfig):
def __getattribute__(self, name):
if name in {f.name for f in fields(UserConfig)}:
raise AttributeError(
f"'UnsetConfig' object has no attribute {name}"
)

def __post_serialize__(self, dct):
return {}


# This is used by UnsetProfileConfig, for commands which do
# not require a profile, i.e. dbt deps and clean
class UnsetProfile(Profile):
def __init__(self):
self.credentials = UnsetCredentials()
self.user_config = UnsetConfig()
self.user_config = UserConfig() # This will be read in _get_rendered_profile
self.profile_name = ''
self.target_name = ''
self.threads = -1
Expand All @@ -443,6 +432,8 @@ def __getattribute__(self, name):
return Profile.__getattribute__(self, name)


# This class is used by the dbt deps and clean commands, because they don't
# require a functioning profile.
@dataclass
class UnsetProfileConfig(RuntimeConfig):
"""This class acts a lot _like_ a RuntimeConfig, except if your profile is
Expand Down Expand Up @@ -525,7 +516,7 @@ def from_parts(
profile_env_vars=profile.profile_env_vars,
profile_name='',
target_name='',
user_config=UnsetConfig(),
user_config=UserConfig(),
threads=getattr(args, 'threads', 1),
credentials=UnsetCredentials(),
args=args,
Expand All @@ -540,21 +531,12 @@ def _get_rendered_profile(
profile_renderer: ProfileRenderer,
profile_name: Optional[str],
) -> Profile:
try:
profile = Profile.render_from_args(
args, profile_renderer, profile_name
)
except (DbtProjectError, DbtProfileError) as exc:
selected_profile_name = Profile.pick_profile_name(
args_profile_name=getattr(args, 'profile', None),
project_profile_name=profile_name
)
fire_event(ProfileLoadError(exc=exc))
fire_event(ProfileNotFound(profile_name=selected_profile_name))
# return the poisoned form
profile = UnsetProfile()
# disable anonymous usage statistics
tracking.disable_tracking()

profile = UnsetProfile()
# The profile (for warehouse connection) is not needed, but we want
# to get the UserConfig, which is also in profiles.yml
user_config = read_user_config(flags.PROFILES_DIR)
profile.user_config = user_config
return profile

@classmethod
Expand All @@ -569,9 +551,6 @@ def from_args(cls: Type[RuntimeConfig], args: Any) -> 'RuntimeConfig':
:raises ValidationException: If the cli variables are invalid.
"""
project, profile = cls.collect_parts(args)
if not isinstance(profile, UnsetProfile):
# if it's a real profile, return a real config
cls = RuntimeConfig

return cls.from_parts(
project=project,
Expand Down
20 changes: 0 additions & 20 deletions core/dbt/events/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,24 +795,6 @@ def message(self) -> str:
return f"target not specified in profile '{self.profile_name}', using '{self.target_name}'"


@dataclass
class ProfileLoadError(ShowException, DebugLevel):
exc: Exception
code: str = "A006"

def message(self) -> str:
return f"Profile not loaded due to error: {self.exc}"


@dataclass
class ProfileNotFound(InfoLevel):
profile_name: Optional[str]
code: str = "A007"

def message(self) -> str:
return f'No profile "{self.profile_name}" found, continuing with no target'


@dataclass
class InvalidVarsYAML(ErrorLevel):
code: str = "A008"
Expand Down Expand Up @@ -2528,8 +2510,6 @@ def message(self) -> str:
TimingInfoCollected()
MergedFromState(nbr_merged=0, sample=[])
MissingProfileTarget(profile_name='', target_name='')
ProfileLoadError(exc=Exception(''))
ProfileNotFound(profile_name='')
InvalidVarsYAML()
GenericTestFileParse(path='')
MacroFileParse(path='')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,48 @@ def run_clean(self):
with tempfile.TemporaryDirectory() as tmpdir:
result = self.run_dbt(["clean", "--profiles-dir", tmpdir])
return result

class TestSimpleDependencyBadProfile(DBTIntegrationTest):

@property
def schema(self):
return "simple_dependency_006"

@property
def models(self):
return "models"

def postgres_profile(self):
# Need to set the environment variable here initially because
# the unittest setup does a load_config.
os.environ['PROFILE_TEST_HOST'] = self.database_host
return {
'config': {
'send_anonymous_usage_stats': False
},
'test': {
'outputs': {
'default2': {
'type': 'postgres',
'threads': 4,
'host': "{{ env_var('PROFILE_TEST_HOST') }}",
'port': 5432,
'user': 'root',
'pass': 'password',
'dbname': 'dbt',
'schema': self.unique_schema()
},
},
'target': 'default2'
}
}

@use_profile('postgres')
def test_postgres_deps_bad_profile(self):
del os.environ['PROFILE_TEST_HOST']
self.run_dbt(["deps"])

@use_profile('postgres')
def test_postgres_clean_bad_profile(self):
del os.environ['PROFILE_TEST_HOST']
self.run_dbt(["clean"])
2 changes: 0 additions & 2 deletions test/unit/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,6 @@ def MockNode():
TimingInfoCollected(),
MergedFromState(nbr_merged=0, sample=[]),
MissingProfileTarget(profile_name='', target_name=''),
ProfileLoadError(exc=Exception('')),
ProfileNotFound(profile_name=''),
InvalidVarsYAML(),
GenericTestFileParse(path=''),
MacroFileParse(path=''),
Expand Down

0 comments on commit 3032594

Please sign in to comment.