Skip to content

Commit

Permalink
Add secondary_profiles to profile.py (#11308)
Browse files Browse the repository at this point in the history
* Add secondary_profiles to profile.py

* Add more tests for edge cases

* Add changie

* Allow inferring target name and add tests for the same

* Incorporate review feedback

* remove unnecessary nesting

* Use typing_extensions.Self

* use quoted type again

* address pr comments round 2
  • Loading branch information
aranke authored Feb 20, 2025
1 parent 7bdf27a commit 71a93b0
Show file tree
Hide file tree
Showing 4 changed files with 357 additions and 2 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/Under the Hood-20250214-123853.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Under the Hood
body: Add secondary profiles to profile.py
time: 2025-02-14T12:38:53.964266Z
custom:
Author: aranke
Issue: XPLAT-241
37 changes: 35 additions & 2 deletions core/dbt/config/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class Profile(HasCredentials):
credentials: Credentials
profile_env_vars: Dict[str, Any]
log_cache_events: bool
secondary_profiles: Dict[str, "Profile"]

def __init__(
self,
Expand All @@ -79,6 +80,7 @@ def __init__(
self.log_cache_events = (
get_flags().LOG_CACHE_EVENTS
) # never available on init, set for adapter instantiation via AdapterRequiredConfig
self.secondary_profiles = {}

def to_profile_info(self, serialize_credentials: bool = False) -> Dict[str, Any]:
"""Unlike to_project_config, this dict is not a mirror of any existing
Expand Down Expand Up @@ -257,6 +259,7 @@ def render_profile(
profile_name: str,
target_override: Optional[str],
renderer: ProfileRenderer,
is_secondary: bool = False,
) -> Tuple[str, Dict[str, Any]]:
"""This is a containment zone for the hateful way we're rendering
profiles.
Expand All @@ -273,6 +276,12 @@ def render_profile(
elif "target" in raw_profile:
# render the target if it was parsed from yaml
target_name = renderer.render_value(raw_profile["target"])
elif is_secondary and len(raw_profile.get("outputs", [])) == 1:
# if we only have one target, we can infer the target name
# currently, this is only used for secondary profiles
target_name = next(iter(raw_profile["outputs"]))
# the event name is slightly misleading, but the message indicates that we inferred the target name for a profile
fire_event(MissingProfileTarget(profile_name=profile_name, target_name=target_name))
else:
target_name = "default"
fire_event(MissingProfileTarget(profile_name=profile_name, target_name=target_name))
Expand All @@ -293,6 +302,7 @@ def from_raw_profile_info(
renderer: ProfileRenderer,
target_override: Optional[str] = None,
threads_override: Optional[int] = None,
is_secondary: bool = False,
) -> "Profile":
"""Create a profile from its raw profile information.
Expand All @@ -312,9 +322,14 @@ def from_raw_profile_info(
"""
# TODO: should it be, and the values coerced to bool?
target_name, profile_data = cls.render_profile(
raw_profile, profile_name, target_override, renderer
raw_profile, profile_name, target_override, renderer, is_secondary=is_secondary
)

if is_secondary and "secondary_profiles" in profile_data:
raise DbtProfileError(
f"Secondary profile '{profile_name}' cannot have nested secondary profiles"
)

# valid connections never include the number of threads, but it's
# stored on a per-connection level in the raw configs
threads = profile_data.pop("threads", DEFAULT_THREADS)
Expand All @@ -325,13 +340,31 @@ def from_raw_profile_info(
profile_data, profile_name, target_name
)

return cls.from_credentials(
profile = cls.from_credentials(
credentials=credentials,
profile_name=profile_name,
target_name=target_name,
threads=threads,
)

for p in profile_data.pop("secondary_profiles", []):
for secondary_profile_name, secondary_raw_profile in p.items():
if secondary_profile_name in profile.secondary_profiles:
raise DbtProfileError(
f"Secondary profile '{secondary_profile_name}' is already defined"
)

profile.secondary_profiles[secondary_profile_name] = cls.from_raw_profile_info(
secondary_raw_profile,
secondary_profile_name,
renderer,
target_override=target_override,
threads_override=threads_override,
is_secondary=True,
)

return profile

@classmethod
def from_raw_profiles(
cls,
Expand Down
1 change: 1 addition & 0 deletions core/dbt/config/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ def from_parts(
project_env_vars=project.project_env_vars,
restrict_access=project.restrict_access,
profile_env_vars=profile.profile_env_vars,
secondary_profiles=profile.secondary_profiles,
profile_name=profile.profile_name,
target_name=profile.target_name,
threads=profile.threads,
Expand Down
Loading

0 comments on commit 71a93b0

Please sign in to comment.