Repository: ibis-project/ibis

Issue:
feat: Enable Snowflake Client to accept OAuth Credentials 
### Is your feature request related to a problem?

_No response_

### Describe the solution you'd like

create a pass through from `ibis.snowflake.connect` method to initiate a snowflake client with OAuth credentials.

it could work like this
```python
import snowflake.connector
Import ibis
my_con = snowflake.connector.connect(**secrets)
ibis_con = ibis.snowflake.connect(connection=my_con)

`````

### What version of ibis are you running?

3.2.0

### What backend(s) are you using, if any?

Snowflake

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
@shaiksuhail Welcome 👋🏻! Thanks for the issue!

Are you tied to using `ibis.snowflake.connect`? Or would using `ibis.connect(url)` work?

With `ibis.connect` you should be able to connect by constructing the URL with the required oauth parameters like in [this StackOverflow answer](https://stackoverflow.com/a/75211693).

Would you mind giving that a try and reporting back whether that works?
Hi @cpcloud, 
Thanks for the welcome!

i did try your suggestion but it didn't work and gave me the following error 

`do_connect() missing 1 required positional argument: 'password'`

the code i tried with 

``` python
from snowflake.sqlalchemy import URL
import ibis
connection_url = URL(
    user=username,
    authenticator="oauth",
    token=access_token,
    account=account,
    warehouse=warehouse,
    database=database,
    schema=schema
)
con = ibis.connect(connection_url)
```
Ah, right. Let me look into that! We should be able to make that an optional parameter.

Resolving patch:
diff --git a/ibis/backends/snowflake/__init__.py b/ibis/backends/snowflake/__init__.py
--- a/ibis/backends/snowflake/__init__.py
+++ b/ibis/backends/snowflake/__init__.py
@@ -137,9 +137,10 @@ def _make_udf(self, name: str, defn) -> str:
     def do_connect(
         self,
         user: str,
-        password: str,
         account: str,
         database: str,
+        password: str | None = None,
+        authenticator: str | None = None,
         connect_args: Mapping[str, Any] | None = None,
         **kwargs: Any,
     ):
@@ -149,8 +150,6 @@ def do_connect(
         ----------
         user
             Username
-        password
-            Password
         account
             A Snowflake organization ID and a Snowflake user ID, separated by a hyphen.
             Note that a Snowflake user ID is a separate identifier from a username.
@@ -158,6 +157,17 @@ def do_connect(
         database
             A Snowflake database and a Snowflake schema, separated by a `/`.
             See https://ibis-project.org/backends/Snowflake/ for details
+        password
+            Password. If empty or `None` then `authenticator` must be passed.
+        authenticator
+            String indicating authentication method. See
+            https://docs.snowflake.com/en/developer-guide/python-connector/python-connector-example#connecting-with-oauth
+            for details.
+
+            Note that the authentication flow **will not take place** until a
+            database connection is made. This means that
+            `ibis.snowflake.connect(...)` can succeed, while subsequent API
+            calls fail if the authentication fails for any reason.
         connect_args
             Additional arguments passed to the SQLAlchemy engine creation call.
         kwargs:
@@ -171,10 +181,16 @@ def do_connect(
                 "Schema must be non-None. Pass the schema as part of the "
                 f"database e.g., {dbparams['database']}/my_schema"
             )
-        url = URL(account=account, user=user, password=password, **dbparams, **kwargs)
+
+        # snowflake-connector-python does not handle `None` for password, but
+        # accepts the empty string
+        url = URL(
+            account=account, user=user, password=password or "", **dbparams, **kwargs
+        )
         self.database_name = dbparams["database"]
         if connect_args is None:
             connect_args = {}
+
         connect_args.setdefault(
             "session_parameters",
             {
@@ -183,6 +199,9 @@ def do_connect(
                 "STRICT_JSON_OUTPUT": "TRUE",
             },
         )
+        if authenticator is not None:
+            connect_args.setdefault("authenticator", authenticator)
+
         engine = sa.create_engine(
             url, connect_args=connect_args, poolclass=sa.pool.StaticPool
         )


Test patch: