Skip to content

Conversation

@jcheng5
Copy link

@jcheng5 jcheng5 commented Dec 16, 2025

Vibe coded; just for discussion purposes for now Extremely carefully reviewed by @jcheng5, except unit tests (life is too short)


Add support for loading connection parameters from TOML config files (~/.snowflake/connections.toml and config.toml) following the Python Snowflake connector conventions.

New features:

  • connection_name parameter to load a named connection profile
  • connections_file_path parameter to specify custom config location
  • Support for SNOWFLAKE_CONNECTIONS env var (full TOML override)
  • Support for SNOWFLAKE_DEFAULT_CONNECTION_NAME env var
  • File permission security checks (error on writable, warn on readable)
  • Graceful fallback when toml package not installed

Connection resolution:

  • Case 1: connection_name provided → load and merge with kwargs
  • Case 2: No args → load default connection from config
  • Case 3: Programmatic params → use params only, skip config loading

SNOWFLAKE_ACCOUNT env var is only used in Case 3 (programmatic mode), not when loading from config files.

Requires the toml package (Suggests) for config file parsing.

🤖 Generated with Claude Code

jcheng5 and others added 2 commits December 15, 2025 17:09
Add support for loading connection parameters from TOML config files
(~/.snowflake/connections.toml and config.toml) following the Python
Snowflake connector conventions.

New features:
- `connection_name` parameter to load a named connection profile
- `connections_file_path` parameter to specify custom config location
- Support for SNOWFLAKE_CONNECTIONS env var (full TOML override)
- Support for SNOWFLAKE_DEFAULT_CONNECTION_NAME env var
- File permission security checks (error on writable, warn on readable)
- Graceful fallback when toml package not installed

Connection resolution:
- Case 1: connection_name provided → load and merge with kwargs
- Case 2: No args → load default connection from config
- Case 3: Programmatic params → use params only, skip config loading

SNOWFLAKE_ACCOUNT env var is only used in Case 3 (programmatic mode),
not when loading from config files.

Requires the toml package (Suggests) for config file parsing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Two issues fixed:

1. When config file had `user` + `authenticator` (no password), the
   `uid` and `authenticator` values ended up in both `args` and `auth`,
   causing "formal argument matched by multiple actual arguments" error.
   Fixed by nulling auth params from `args` before merging with `auth`.

2. `snowflake_auth_args()` wasn't returning `authenticator` when using
   uid + authenticator (externalbrowser/SNOWFLAKE_JWT), so the
   authenticator from config file was lost and the "Failed to detect
   ambient credentials" error was thrown.

Added tests for config files with user + authenticator (no password).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@jcheng5 jcheng5 marked this pull request as draft December 16, 2025 01:53
@jcheng5 jcheng5 changed the title Snowflake config files Use Snowflake config.toml/connection.toml files Dec 16, 2025
if (!is.null(database)) programmatic_params$database <- database
if (!is.null(schema)) programmatic_params$schema <- schema
if (!is.null(uid)) programmatic_params$uid <- uid
if (!is.null(pwd)) programmatic_params$pwd <- pwd
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We interpret NULL as "not provided", which is a little different than how the Snowflake Connector for Python interprets None. In Python, you can pass None to un-set a parameter that's set in connections.toml; in R, this will have no effect.

If we care about this, we could:

  1. Change the is.null check to a missing() check, and remove = NULL from the signature. Downside is it makes it look like those parameters are required. Perhaps if we put them after ... that will be a hint that they're not.
  2. Continue to treat NULL as missing but have NA mean unset. This doesn't feel very idiomatic.

I have to admit it was slightly difficult for me to think of a case where this behavior is desirable or a user would be surprised that a NULL value is ignored, so I just left it.

jcheng5 and others added 2 commits December 16, 2025 16:50
Two issues fixed:

1. SNOWFLAKE_CONNECTIONS env var was preventing reading of
   default_connection_name from config.toml. Now each config option
   is resolved independently, matching Python connector behavior.

2. Missing toml package gave misleading "connection not found" error
   instead of "toml package required" when config files existed.
   Now errors appropriately when TOML parsing is actually needed.

Implementation uses delayedAssign() for lazy promise-based evaluation:
- Only parses TOML sources that are actually needed
- toml package error only triggers when parsing is required
- Clean precedence logic via %||% chains, no complex conditionals

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@jcheng5 jcheng5 marked this pull request as ready for review December 17, 2025 01:52
Copy link
Collaborator

@simonpcouch simonpcouch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have strong reflexes here on the big idea, but trust that this is a reasonable approach! Reviewing for R code style etc.

Could you add a NEWS entry?

Comment on lines +216 to +218
connection_name = NULL,
connections_file_path = NULL,
account = NULL,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this would break passing account by position. Could we move the new arguments to after pwd (or the ellipses, if you think those should always be named)?

#' @return NULL invisibly; throws error or warning on bad permissions
#' @noRd
check_toml_file_permissions <- function(file_path) {
# Skip on Windows
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Skip on Windows

An example "what" comment

Comment on lines +906 to +910
sprintf(
"Invalid connection_name '%s', known ones are [%s]",
connection_name,
paste0("'", known_names, "'", collapse = ", ")
),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use cli::cli_abort() with variable substitutions here? Fine with occasional sprintf()s, but we should lean on that dependency for ad-hoc paste0()s. This prompt should be enough for Claude to get all the way there.


#' Check if toml package is installed
#' @noRd
check_toml_installed <- function() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We @import rlang, so you should be able to use check_installed a la

check_installed("toml", "to load Snowflake configuration files")

expect_equal(config$default_connection_name, "staging")
})

test_that("Case 1: Named connection loads and merges with kwargs", {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test_that("Case 1: Named connection loads and merges with kwargs", {
test_that("Named connection loads and merges with kwargs", {

Similar feeling on the enumeration here

Comment on lines +383 to +385
config_dir <- tempfile()
dir.create(config_dir)
withr::defer(unlink(config_dir, recursive = TRUE))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
config_dir <- tempfile()
dir.create(config_dir)
withr::defer(unlink(config_dir, recursive = TRUE))
config_dir <- withr::local_tempdir()

And elsewhere in this test file

@simonpcouch
Copy link
Collaborator

Fine to ignore the two failing checks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants