Skip to content

Commit

Permalink
SNOW-538310: Error when using create_engine() and password containing…
Browse files Browse the repository at this point in the history
… % character (#338)
  • Loading branch information
sfc-gh-aling authored Aug 16, 2022
1 parent eea93a9 commit 12ab55f
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 0 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,46 @@ You can optionally specify the initial database and schema for the Snowflake ses
'snowflake://<user_login_name>:<password>@<account_name>/<database_name>/<schema_name>?warehouse=<warehouse_name>&role=<role_name>'
```

#### Escaping Special Characters such as `%, @` signs in Passwords

As pointed out in [SQLAlchemy](https://docs.sqlalchemy.org/en/14/core/engines.html#escaping-special-characters-such-as-signs-in-passwords), URLs
containing special characters need to be URL encoded to be parsed correctly. This includes the `%, @` signs. Unescaped password containing special
characters could lead to authentication failure.

The encoding for the password can be generated using `urllib.parse`:
```python
import urllib.parse
urllib.parse.quote("kx@% jj5/g")
'kx%40%25%20jj5/g'
```

**Note**: `urllib.parse.quote_plus` may also be used if there is no space in the string, as `urllib.parse.quote_plus` will replace space with `+`.

To create an engine with the proper encodings, either manually constructing the url string by formatting
or taking advantage of the `snowflake.sqlalchemy.URL` helper method:
```python
import urllib.parse
from snowflake.sqlalchemy import URL
from sqlalchemy import create_engine

quoted_password = urllib.parse.quote("kx@% jj5/g")

# 1. manually constructing an url string
url = f'snowflake://testuser1:{quoted_password}@abc123/testdb/public?warehouse=testwh&role=myrole'
engine = create_engine(url)

# 2. using the snowflake.sqlalchemy.URL helper method
engine = create_engine(URL(
account = 'abc123',
user = 'testuser1',
password = quoted_password,
database = 'testdb',
schema = 'public',
warehouse = 'testwh',
role='myrole',
))
```

**Note**:
After login, the initial database, schema, warehouse and role specified in the connection string can always be changed for the session.

Expand Down
5 changes: 5 additions & 0 deletions src/snowflake/sqlalchemy/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ def _url(**db_parameters):
"""
Composes a SQLAlchemy connect string from the given database connection
parameters.
Password containing special characters (e.g., '@', '%') need to be encoded to be parsed correctly.
Unescaped password containing special characters might lead to authentication failure.
Please follow the instructions to encode the password:
https://github.com/snowflakedb/snowflake-sqlalchemy#escaping-special-characters-such-as---signs-in-passwords
"""
specified_parameters = []
if "account" not in db_parameters:
Expand Down
11 changes: 11 additions & 0 deletions tests/test_unit_url.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#
# Copyright (c) 2012-2022 Snowflake Computing Inc. All rights reserved.
#
import urllib.parse

from snowflake.sqlalchemy import URL

Expand All @@ -25,6 +26,16 @@ def test_url():
== "snowflake://admin:1-pass 2-pass 3-%3A 4-%40 5-%2F 6-pass@testaccount/"
)

quoted_password = urllib.parse.quote("kx@% jj5/g")
assert (
URL(
account="testaccount",
user="admin",
password=quoted_password,
)
== "snowflake://admin:kx%40%25%20jj5%2Fg@testaccount/"
)

assert (
URL(account="testaccount", user="admin", password="test", database="testdb")
== "snowflake://admin:test@testaccount/testdb"
Expand Down

0 comments on commit 12ab55f

Please sign in to comment.