Skip to content

Commit 71a93b0

Browse files
authored
Add secondary_profiles to profile.py (#11308)
* 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
1 parent 7bdf27a commit 71a93b0

File tree

4 files changed

+357
-2
lines changed

4 files changed

+357
-2
lines changed
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: Under the Hood
2+
body: Add secondary profiles to profile.py
3+
time: 2025-02-14T12:38:53.964266Z
4+
custom:
5+
Author: aranke
6+
Issue: XPLAT-241

Diff for: core/dbt/config/profile.py

+35-2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class Profile(HasCredentials):
6060
credentials: Credentials
6161
profile_env_vars: Dict[str, Any]
6262
log_cache_events: bool
63+
secondary_profiles: Dict[str, "Profile"]
6364

6465
def __init__(
6566
self,
@@ -79,6 +80,7 @@ def __init__(
7980
self.log_cache_events = (
8081
get_flags().LOG_CACHE_EVENTS
8182
) # never available on init, set for adapter instantiation via AdapterRequiredConfig
83+
self.secondary_profiles = {}
8284

8385
def to_profile_info(self, serialize_credentials: bool = False) -> Dict[str, Any]:
8486
"""Unlike to_project_config, this dict is not a mirror of any existing
@@ -257,6 +259,7 @@ def render_profile(
257259
profile_name: str,
258260
target_override: Optional[str],
259261
renderer: ProfileRenderer,
262+
is_secondary: bool = False,
260263
) -> Tuple[str, Dict[str, Any]]:
261264
"""This is a containment zone for the hateful way we're rendering
262265
profiles.
@@ -273,6 +276,12 @@ def render_profile(
273276
elif "target" in raw_profile:
274277
# render the target if it was parsed from yaml
275278
target_name = renderer.render_value(raw_profile["target"])
279+
elif is_secondary and len(raw_profile.get("outputs", [])) == 1:
280+
# if we only have one target, we can infer the target name
281+
# currently, this is only used for secondary profiles
282+
target_name = next(iter(raw_profile["outputs"]))
283+
# the event name is slightly misleading, but the message indicates that we inferred the target name for a profile
284+
fire_event(MissingProfileTarget(profile_name=profile_name, target_name=target_name))
276285
else:
277286
target_name = "default"
278287
fire_event(MissingProfileTarget(profile_name=profile_name, target_name=target_name))
@@ -293,6 +302,7 @@ def from_raw_profile_info(
293302
renderer: ProfileRenderer,
294303
target_override: Optional[str] = None,
295304
threads_override: Optional[int] = None,
305+
is_secondary: bool = False,
296306
) -> "Profile":
297307
"""Create a profile from its raw profile information.
298308
@@ -312,9 +322,14 @@ def from_raw_profile_info(
312322
"""
313323
# TODO: should it be, and the values coerced to bool?
314324
target_name, profile_data = cls.render_profile(
315-
raw_profile, profile_name, target_override, renderer
325+
raw_profile, profile_name, target_override, renderer, is_secondary=is_secondary
316326
)
317327

328+
if is_secondary and "secondary_profiles" in profile_data:
329+
raise DbtProfileError(
330+
f"Secondary profile '{profile_name}' cannot have nested secondary profiles"
331+
)
332+
318333
# valid connections never include the number of threads, but it's
319334
# stored on a per-connection level in the raw configs
320335
threads = profile_data.pop("threads", DEFAULT_THREADS)
@@ -325,13 +340,31 @@ def from_raw_profile_info(
325340
profile_data, profile_name, target_name
326341
)
327342

328-
return cls.from_credentials(
343+
profile = cls.from_credentials(
329344
credentials=credentials,
330345
profile_name=profile_name,
331346
target_name=target_name,
332347
threads=threads,
333348
)
334349

350+
for p in profile_data.pop("secondary_profiles", []):
351+
for secondary_profile_name, secondary_raw_profile in p.items():
352+
if secondary_profile_name in profile.secondary_profiles:
353+
raise DbtProfileError(
354+
f"Secondary profile '{secondary_profile_name}' is already defined"
355+
)
356+
357+
profile.secondary_profiles[secondary_profile_name] = cls.from_raw_profile_info(
358+
secondary_raw_profile,
359+
secondary_profile_name,
360+
renderer,
361+
target_override=target_override,
362+
threads_override=threads_override,
363+
is_secondary=True,
364+
)
365+
366+
return profile
367+
335368
@classmethod
336369
def from_raw_profiles(
337370
cls,

Diff for: core/dbt/config/runtime.py

+1
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ def from_parts(
184184
project_env_vars=project.project_env_vars,
185185
restrict_access=project.restrict_access,
186186
profile_env_vars=profile.profile_env_vars,
187+
secondary_profiles=profile.secondary_profiles,
187188
profile_name=profile.profile_name,
188189
target_name=profile.target_name,
189190
threads=profile.threads,

0 commit comments

Comments
 (0)