Skip to content
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

Feature/dbt snowflake/connection_name parameter support #848

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
7b956af
Added support for the connection_name parameter. This allows using a …
sfc-gh-dflippo Feb 24, 2025
d33ccc3
Added unit test for the connection_name parameter.
sfc-gh-dflippo Feb 24, 2025
65f594d
Added changie documentation
sfc-gh-dflippo Feb 24, 2025
86c8830
Improved test_connection_name unit test to not rely on external files…
sfc-gh-dflippo Feb 24, 2025
a011929
Executed pre-commit to reformat connections.py
sfc-gh-dflippo Feb 24, 2025
edf630e
Removed dependency on toml library in unit test
sfc-gh-dflippo Feb 24, 2025
c1cbf12
Updated to use default location for connections.toml, ~/.snowflake/co…
sfc-gh-dflippo Feb 25, 2025
94c11af
Moved initialization logic to new fixture
sfc-gh-dflippo Feb 25, 2025
87c1ad8
switch env var setting to monkeypatch and file creation to tmp_path f…
sfc-gh-dflippo Feb 25, 2025
fca803b
hard coding connection_name to `default` to avoid name collision.
sfc-gh-dflippo Feb 25, 2025
8c9858c
Figured out that it wasn't a name collision
sfc-gh-dflippo Feb 25, 2025
663abb9
changed scope to function for connection_name fixture.
sfc-gh-dflippo Feb 25, 2025
378fadc
removed connection_name from dbt_profile_target fixture to avoid scop…
sfc-gh-dflippo Feb 25, 2025
58019b6
It isn't creating toml files so trying moving logic from fixture to t…
sfc-gh-dflippo Feb 26, 2025
3e801fe
Added connections_file_path parameter to be able to pytest connection…
sfc-gh-dflippo Feb 26, 2025
78e7159
Added connections_file_path parameter to be able to pytest connection…
sfc-gh-dflippo Feb 26, 2025
c3ef04b
Switched to having the profile use ~ and setting HOME to tmp_path in …
sfc-gh-dflippo Feb 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Features
body: Added support for the connection_name parameter and associated connections.toml file when connecting to Snowflake.
time: 2025-02-24T16:03:53.872855-05:00
custom:
Author: sfc-gh-dflippo
Issue: "684"
53 changes: 34 additions & 19 deletions dbt-snowflake/src/dbt/adapters/snowflake/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,32 +112,41 @@ class SnowflakeCredentials(Credentials):
insecure_mode: Optional[bool] = False
# this needs to default to `None` so that we can tell if the user set it; see `__post_init__()`
reuse_connections: Optional[bool] = None
connection_name: Optional[str] = None
connections_file_path: Optional[str] = None

def __post_init__(self):
if self.authenticator != "oauth" and (self.oauth_client_secret or self.oauth_client_id):
# the user probably forgot to set 'authenticator' like I keep doing
warn_or_error(
AdapterEventWarning(
base_msg="Authenticator is not set to oauth, but an oauth-only parameter is set! Did you mean to set authenticator: oauth?"
)
)

if self.authenticator not in ["oauth", "jwt"]:
if self.token:
# skip authentication parameter checking if the connection_name is specified because additional
# parameters can be provided in a connections.toml file
if not self.connection_name:
if self.authenticator != "oauth" and (
self.oauth_client_secret or self.oauth_client_id
):
# the user probably forgot to set 'authenticator' like I keep doing
warn_or_error(
AdapterEventWarning(
base_msg=(
"The token parameter was set, but the authenticator was "
"not set to 'oauth' or 'jwt'."
)
base_msg="Authenticator is not set to oauth, but an oauth-only parameter is set! Did you mean to set authenticator: oauth?"
)
)

if not self.user:
# The user attribute is only optional if 'authenticator' is 'jwt' or 'oauth'
warn_or_error(
AdapterEventError(base_msg="Invalid profile: 'user' is a required property.")
)
if self.authenticator not in ["oauth", "jwt"]:
if self.token:
warn_or_error(
AdapterEventWarning(
base_msg=(
"The token parameter was set, but the authenticator was "
"not set to 'oauth' or 'jwt'."
)
)
)

if not self.user:
# The user attribute is only optional if 'authenticator' is 'jwt' or 'oauth'
warn_or_error(
AdapterEventError(
base_msg="Invalid profile: 'user' is a required property."
)
)

self.account = self.account.replace("_", "-")

Expand Down Expand Up @@ -179,12 +188,18 @@ def _connection_keys(self):
"retry_all",
"insecure_mode",
"reuse_connections",
"connection_name",
"connections_file_path",
)

def auth_args(self):
# Pull all of the optional authentication args for the connector,
# let connector handle the actual arg validation
result = {}
if self.connection_name:
result["connection_name"] = self.connection_name
if self.connections_file_path:
result["connections_file_path"] = self.connections_file_path
if self.user:
result["user"] = self.user
if self.password:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""
This class sets the profile to use a connection_name and connections_file_path.
The connection_name is "default" and the connections.toml file is set to "~/connections.toml"
During the test we set the HOME to a temporary folder where we write this file.

The script will populate the toml file based on the testing environment variables
but will look something like:

[default]
account = "SNOWFLAKE_TEST_ACCOUNT"
authenticator = "snowflake"
database = "SNOWFLAKE_TEST_DATABASE"
password = "SNOWFLAKE_TEST_PASSWORD"
role = "SNOWFLAKE_TEST_ROLE"
user = "SNOWFLAKE_TEST_USER"
warehouse = "SNOWFLAKE_TEST_WAREHOUSE"

By putting the password in the connections.toml file and the connection_name
& connections_file_path in the profiles.yml, we can test that we can connect
based on credentials in the connections.toml

"""

from dbt.tests.util import run_dbt
import tempfile
import pytest
import os
from pathlib import Path

connections_toml_template = """
[{name}]
account = "{account}"
authenticator = "snowflake"
database = "{database}"
password = "{password}"
role = "{role}"
user = "{user}"
warehouse = "{warehouse}"
"""


class TestConnectionName:

@pytest.fixture(scope="class", autouse=True)
def dbt_profile_target(self):
# We are returning a profile that does not contain the password
return {
"type": "snowflake",
"threads": 4,
"account": os.getenv("SNOWFLAKE_TEST_ACCOUNT"),
"database": os.getenv("SNOWFLAKE_TEST_DATABASE"),
"warehouse": os.getenv("SNOWFLAKE_TEST_WAREHOUSE"),
"connection_name": "default",
"connections_file_path": "~/connections.toml",
}

@pytest.fixture(scope="class")
def models(self):
return {"my_model.sql": "select 1 as id"}

# Test that we can write a connections.toml and use it to connect
def test_connection(self, project, tmp_path, monkeypatch):

with monkeypatch.context() as m:
# Set HOME to our temporary folder for later tilde expansion in the driver
m.setenv("HOME", str(tmp_path.absolute()))

connections_toml = tmp_path / "connections.toml"

# We are creating a toml file that contains the password
connections_toml.write_text(
connections_toml_template.format(
name="default",
account=os.getenv("SNOWFLAKE_TEST_ACCOUNT"),
database=os.getenv("SNOWFLAKE_TEST_DATABASE"),
password=os.getenv("SNOWFLAKE_TEST_PASSWORD"),
role=os.getenv("SNOWFLAKE_TEST_ROLE"),
user=os.getenv("SNOWFLAKE_TEST_USER"),
warehouse=os.getenv("SNOWFLAKE_TEST_WAREHOUSE"),
)
)
connections_toml.chmod(0o600)

run_dbt()
Loading