Skip to content

Commit 12ab55f

Browse files
authored
SNOW-538310: Error when using create_engine() and password containing % character (#338)
1 parent eea93a9 commit 12ab55f

File tree

3 files changed

+56
-0
lines changed

3 files changed

+56
-0
lines changed

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,46 @@ You can optionally specify the initial database and schema for the Snowflake ses
9494
'snowflake://<user_login_name>:<password>@<account_name>/<database_name>/<schema_name>?warehouse=<warehouse_name>&role=<role_name>'
9595
```
9696

97+
#### Escaping Special Characters such as `%, @` signs in Passwords
98+
99+
As pointed out in [SQLAlchemy](https://docs.sqlalchemy.org/en/14/core/engines.html#escaping-special-characters-such-as-signs-in-passwords), URLs
100+
containing special characters need to be URL encoded to be parsed correctly. This includes the `%, @` signs. Unescaped password containing special
101+
characters could lead to authentication failure.
102+
103+
The encoding for the password can be generated using `urllib.parse`:
104+
```python
105+
import urllib.parse
106+
urllib.parse.quote("kx@% jj5/g")
107+
'kx%40%25%20jj5/g'
108+
```
109+
110+
**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 `+`.
111+
112+
To create an engine with the proper encodings, either manually constructing the url string by formatting
113+
or taking advantage of the `snowflake.sqlalchemy.URL` helper method:
114+
```python
115+
import urllib.parse
116+
from snowflake.sqlalchemy import URL
117+
from sqlalchemy import create_engine
118+
119+
quoted_password = urllib.parse.quote("kx@% jj5/g")
120+
121+
# 1. manually constructing an url string
122+
url = f'snowflake://testuser1:{quoted_password}@abc123/testdb/public?warehouse=testwh&role=myrole'
123+
engine = create_engine(url)
124+
125+
# 2. using the snowflake.sqlalchemy.URL helper method
126+
engine = create_engine(URL(
127+
account = 'abc123',
128+
user = 'testuser1',
129+
password = quoted_password,
130+
database = 'testdb',
131+
schema = 'public',
132+
warehouse = 'testwh',
133+
role='myrole',
134+
))
135+
```
136+
97137
**Note**:
98138
After login, the initial database, schema, warehouse and role specified in the connection string can always be changed for the session.
99139

src/snowflake/sqlalchemy/util.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ def _url(**db_parameters):
1414
"""
1515
Composes a SQLAlchemy connect string from the given database connection
1616
parameters.
17+
18+
Password containing special characters (e.g., '@', '%') need to be encoded to be parsed correctly.
19+
Unescaped password containing special characters might lead to authentication failure.
20+
Please follow the instructions to encode the password:
21+
https://github.com/snowflakedb/snowflake-sqlalchemy#escaping-special-characters-such-as---signs-in-passwords
1722
"""
1823
specified_parameters = []
1924
if "account" not in db_parameters:

tests/test_unit_url.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#
22
# Copyright (c) 2012-2022 Snowflake Computing Inc. All rights reserved.
33
#
4+
import urllib.parse
45

56
from snowflake.sqlalchemy import URL
67

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

29+
quoted_password = urllib.parse.quote("kx@% jj5/g")
30+
assert (
31+
URL(
32+
account="testaccount",
33+
user="admin",
34+
password=quoted_password,
35+
)
36+
== "snowflake://admin:kx%40%25%20jj5%2Fg@testaccount/"
37+
)
38+
2839
assert (
2940
URL(account="testaccount", user="admin", password="test", database="testdb")
3041
== "snowflake://admin:test@testaccount/testdb"

0 commit comments

Comments
 (0)