-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use profile specified in --profile with dbt init #7450
Changes from all commits
9179070
2755820
f8d66f8
7e921ce
9a58a15
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
kind: Fixes | ||
body: If --profile specified with dbt-init, create the project with the specified | ||
profile | ||
time: 2023-04-24T16:16:42.994547-04:00 | ||
custom: | ||
Author: ezraerb | ||
Issue: "6154" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,13 @@ | ||
import click | ||
import os | ||
import yaml | ||
import pytest | ||
from pathlib import Path | ||
from unittest import mock | ||
from unittest.mock import Mock, call | ||
|
||
from dbt.exceptions import DbtRuntimeError | ||
|
||
from dbt.tests.util import run_dbt | ||
|
||
|
||
|
@@ -84,6 +87,11 @@ def test_init_task_in_project_with_existing_profiles_yml( | |
""" | ||
) | ||
|
||
def test_init_task_in_project_specifying_profile_errors(self): | ||
with pytest.raises(DbtRuntimeError) as error: | ||
run_dbt(["init", "--profile", "test"], expect_pass=False) | ||
assert "Can not init existing project with specified profile" in str(error) | ||
|
||
|
||
class TestInitProjectWithoutExistingProfilesYml: | ||
@mock.patch("dbt.task.init._get_adapter_plugin_names") | ||
|
@@ -159,6 +167,20 @@ def exists_side_effect(path): | |
""" | ||
) | ||
|
||
@mock.patch.object(Path, "exists", autospec=True) | ||
def test_init_task_in_project_without_profile_yml_specifying_profile_errors(self, exists): | ||
def exists_side_effect(path): | ||
# Override responses on specific files, default to 'real world' if not overriden | ||
return {"profiles.yml": False}.get(path.name, os.path.exists(path)) | ||
|
||
exists.side_effect = exists_side_effect | ||
|
||
# Even through no profiles.yml file exists, the init will not modify project.yml, | ||
# so this errors | ||
with pytest.raises(DbtRuntimeError) as error: | ||
run_dbt(["init", "--profile", "test"], expect_pass=False) | ||
assert "Could not find profile named test" in str(error) | ||
|
||
|
||
class TestInitProjectWithoutExistingProfilesYmlOrTemplate: | ||
@mock.patch("dbt.task.init._get_adapter_plugin_names") | ||
|
@@ -708,3 +730,128 @@ def test_init_inside_project_and_skip_profile_setup( | |
# skip interactive profile setup | ||
run_dbt(["init", "--skip-profile-setup"]) | ||
assert len(manager.mock_calls) == 0 | ||
|
||
|
||
class TestInitOutsideOfProjectWithSpecifiedProfile(TestInitOutsideOfProjectBase): | ||
@mock.patch("dbt.task.init._get_adapter_plugin_names") | ||
@mock.patch("click.prompt") | ||
def test_init_task_outside_of_project_with_specified_profile( | ||
self, mock_prompt, mock_get_adapter, project, project_name, unique_schema, dbt_profile_data | ||
): | ||
manager = Mock() | ||
manager.attach_mock(mock_prompt, "prompt") | ||
manager.prompt.side_effect = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious why Black didn't reformat this or the line below it to be the same. |
||
project_name, | ||
] | ||
mock_get_adapter.return_value = [project.adapter.type()] | ||
run_dbt(["init", "--profile", "test"]) | ||
|
||
manager.assert_has_calls( | ||
[ | ||
call.prompt("Enter a name for your project (letters, digits, underscore)"), | ||
] | ||
) | ||
|
||
# profiles.yml is NOT overwritten, so assert that the text matches that of the | ||
# original fixture | ||
with open(os.path.join(project.profiles_dir, "profiles.yml"), "r") as f: | ||
assert f.read() == yaml.safe_dump(dbt_profile_data) | ||
|
||
with open(os.path.join(project.project_root, project_name, "dbt_project.yml"), "r") as f: | ||
assert ( | ||
f.read() | ||
== f""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make this a fixture in a constant at file top or in a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checking the other init tests shows that all of them use this pattern. Its probably worth getting more opinions before changing it. |
||
# Name your project! Project names should contain only lowercase characters | ||
# and underscores. A good package name should reflect your organization's | ||
# name or the intended use of these models | ||
name: '{project_name}' | ||
version: '1.0.0' | ||
config-version: 2 | ||
|
||
# This setting configures which "profile" dbt uses for this project. | ||
profile: 'test' | ||
|
||
# These configurations specify where dbt should look for different types of files. | ||
# The `model-paths` config, for example, states that models in this project can be | ||
# found in the "models/" directory. You probably won't need to change these! | ||
model-paths: ["models"] | ||
analysis-paths: ["analyses"] | ||
test-paths: ["tests"] | ||
seed-paths: ["seeds"] | ||
macro-paths: ["macros"] | ||
snapshot-paths: ["snapshots"] | ||
|
||
clean-targets: # directories to be removed by `dbt clean` | ||
- "target" | ||
- "dbt_packages" | ||
|
||
|
||
# Configuring models | ||
# Full documentation: https://docs.getdbt.com/docs/configuring-models | ||
|
||
# In this example config, we tell dbt to build all models in the example/ | ||
# directory as views. These settings can be overridden in the individual model | ||
# files using the `{{{{ config(...) }}}}` macro. | ||
models: | ||
{project_name}: | ||
# Config indicated by + and applies to all files under models/example/ | ||
example: | ||
+materialized: view | ||
""" | ||
) | ||
|
||
|
||
class TestInitOutsideOfProjectSpecifyingInvalidProfile(TestInitOutsideOfProjectBase): | ||
@mock.patch("dbt.task.init._get_adapter_plugin_names") | ||
@mock.patch("click.prompt") | ||
def test_init_task_outside_project_specifying_invalid_profile_errors( | ||
self, mock_prompt, mock_get_adapter, project, project_name | ||
): | ||
manager = Mock() | ||
manager.attach_mock(mock_prompt, "prompt") | ||
manager.prompt.side_effect = [ | ||
project_name, | ||
] | ||
mock_get_adapter.return_value = [project.adapter.type()] | ||
|
||
with pytest.raises(DbtRuntimeError) as error: | ||
run_dbt(["init", "--profile", "invalid"], expect_pass=False) | ||
assert "Could not find profile named invalid" in str(error) | ||
|
||
manager.assert_has_calls( | ||
[ | ||
call.prompt("Enter a name for your project (letters, digits, underscore)"), | ||
] | ||
) | ||
|
||
|
||
class TestInitOutsideOfProjectSpecifyingProfileNoProfilesYml(TestInitOutsideOfProjectBase): | ||
@mock.patch("dbt.task.init._get_adapter_plugin_names") | ||
@mock.patch("click.prompt") | ||
def test_init_task_outside_project_specifying_profile_no_profiles_yml_errors( | ||
self, mock_prompt, mock_get_adapter, project, project_name | ||
): | ||
manager = Mock() | ||
manager.attach_mock(mock_prompt, "prompt") | ||
manager.prompt.side_effect = [ | ||
project_name, | ||
] | ||
mock_get_adapter.return_value = [project.adapter.type()] | ||
|
||
# Override responses on specific files, default to 'real world' if not overriden | ||
original_isfile = os.path.isfile | ||
with mock.patch( | ||
"os.path.isfile", | ||
new=lambda path: {"profiles.yml": False}.get( | ||
os.path.basename(path), original_isfile(path) | ||
), | ||
): | ||
with pytest.raises(DbtRuntimeError) as error: | ||
run_dbt(["init", "--profile", "test"], expect_pass=False) | ||
assert "Could not find profile named invalid" in str(error) | ||
|
||
manager.assert_has_calls( | ||
[ | ||
call.prompt("Enter a name for your project (letters, digits, underscore)"), | ||
] | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ezraerb Can you help me understand why we'd want or need
self.get_profile_name_from_current_project()
to be guarded byif not self.args.skip_profile_setup:
?Could we safely do the following outside of
if
statement?If so, then we could avoid repeating
self.setup_profile(profile_name)
twice (once for each case) and instead we can just do it once at the very end (IFF profile setup is being skipped).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are four cases that need to be considered:
The difference between cases 3 and 4 prohibits having a catch-all profile creation at the end of the method. Either signal variables are needed or the calls need to be duplicated in the various paths. Previous viewers expressed a desire for the latter as they believe it is easier to follow.