databricks-sql-connector 3.2.0__tar.gz → 3.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/CHANGELOG.md +20 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/PKG-INFO +9 -11
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/README.md +5 -7
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/pyproject.toml +5 -5
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/__init__.py +1 -1
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/auth.py +29 -19
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/authenticators.py +0 -15
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/retry.py +16 -8
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/thrift_http_client.py +25 -16
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/client.py +32 -16
- databricks_sql_connector-3.4.0/src/databricks/sql/cloudfetch/download_manager.py +108 -0
- databricks_sql_connector-3.4.0/src/databricks/sql/cloudfetch/downloader.py +175 -0
- {databricks_sql_connector-3.2.0/src/databricks → databricks_sql_connector-3.4.0/src/databricks/sql/parameters}/py.typed +0 -0
- {databricks_sql_connector-3.2.0/src/databricks/sql/parameters → databricks_sql_connector-3.4.0/src/databricks/sql}/py.typed +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_backend.py +8 -35
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/types.py +48 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/utils.py +17 -10
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/README.sqlalchemy.md +1 -2
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/README.tests.md +3 -3
- databricks_sql_connector-3.4.0/src/databricks/sqlalchemy/py.typed +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/requirements.py +1 -1
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/overrides/_componentreflectiontest.py +1 -1
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test_local/__init__.py +1 -1
- databricks_sql_connector-3.2.0/src/databricks/sql/cloudfetch/download_manager.py +0 -215
- databricks_sql_connector-3.2.0/src/databricks/sql/cloudfetch/downloader.py +0 -173
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/LICENSE +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/__init__.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/__init__.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/endpoint.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/oauth.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/oauth_http_handler.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/exc.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/experimental/__init__.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/experimental/oauth_persistence.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/parameters/__init__.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/parameters/native.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_api/TCLIService/TCLIService-remote +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_api/TCLIService/TCLIService.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_api/TCLIService/__init__.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_api/TCLIService/constants.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_api/TCLIService/ttypes.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_api/__init__.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/__init__.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/_ddl.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/_parse.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/_types.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/base.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/setup.cfg +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/_extra.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/_future.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/_regression.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/_unsupported.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/conftest.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/overrides/_ctetest.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/test_suite.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test_local/conftest.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test_local/e2e/MOCK_DATA.xlsx +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test_local/e2e/test_basic.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test_local/test_ddl.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test_local/test_parsing.py +0 -0
- {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test_local/test_types.py +0 -0
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Release History
|
|
2
2
|
|
|
3
|
+
# 3.4.0 (2024-08-27)
|
|
4
|
+
|
|
5
|
+
- Unpin pandas to support v2.2.2 (databricks/databricks-sql-python#416 by @kfollesdal)
|
|
6
|
+
- Make OAuth as the default authenticator if no authentication setting is provided (databricks/databricks-sql-python#419 by @jackyhu-db)
|
|
7
|
+
- Fix (regression): use SSL options with HTTPS connection pool (databricks/databricks-sql-python#425 by @kravets-levko)
|
|
8
|
+
|
|
9
|
+
# 3.3.0 (2024-07-18)
|
|
10
|
+
|
|
11
|
+
- Don't retry requests that fail with HTTP code 401 (databricks/databricks-sql-python#408 by @Hodnebo)
|
|
12
|
+
- Remove username/password (aka "basic") auth option (databricks/databricks-sql-python#409 by @jackyhu-db)
|
|
13
|
+
- Refactor CloudFetch handler to fix numerous issues with it (databricks/databricks-sql-python#405 by @kravets-levko)
|
|
14
|
+
- Add option to disable SSL verification for CloudFetch links (databricks/databricks-sql-python#414 by @kravets-levko)
|
|
15
|
+
|
|
16
|
+
Databricks-managed passwords reached end of life on July 10, 2024. Therefore, Basic auth support was removed from
|
|
17
|
+
the library. See https://docs.databricks.com/en/security/auth-authz/password-deprecation.html
|
|
18
|
+
|
|
19
|
+
The existing option `_tls_no_verify=True` of `sql.connect(...)` will now also disable SSL cert verification
|
|
20
|
+
(but not the SSL itself) for CloudFetch links. This option should be used as a workaround only, when other ways
|
|
21
|
+
to fix SSL certificate errors didn't work.
|
|
22
|
+
|
|
3
23
|
# 3.2.0 (2024-06-06)
|
|
4
24
|
|
|
5
25
|
- Update proxy authentication (databricks/databricks-sql-python#354 by @amir-haroun)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: databricks-sql-connector
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: Databricks SQL Connector for Python
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Author: Databricks
|
|
@@ -17,11 +17,11 @@ Provides-Extra: alembic
|
|
|
17
17
|
Provides-Extra: sqlalchemy
|
|
18
18
|
Requires-Dist: alembic (>=1.0.11,<2.0.0) ; extra == "alembic"
|
|
19
19
|
Requires-Dist: lz4 (>=4.0.2,<5.0.0)
|
|
20
|
-
Requires-Dist: numpy (>=1.16.6) ; python_version >= "3.8" and python_version < "3.11"
|
|
21
|
-
Requires-Dist: numpy (>=1.23.4) ; python_version >= "3.11"
|
|
20
|
+
Requires-Dist: numpy (>=1.16.6,<2.0.0) ; python_version >= "3.8" and python_version < "3.11"
|
|
21
|
+
Requires-Dist: numpy (>=1.23.4,<2.0.0) ; python_version >= "3.11"
|
|
22
22
|
Requires-Dist: oauthlib (>=3.1.0,<4.0.0)
|
|
23
23
|
Requires-Dist: openpyxl (>=3.0.10,<4.0.0)
|
|
24
|
-
Requires-Dist: pandas (>=1.2.5,<2.
|
|
24
|
+
Requires-Dist: pandas (>=1.2.5,<2.3.0) ; python_version >= "3.8"
|
|
25
25
|
Requires-Dist: pyarrow (>=14.0.1,<17)
|
|
26
26
|
Requires-Dist: requests (>=2.18.1,<3.0.0)
|
|
27
27
|
Requires-Dist: sqlalchemy (>=2.0.21) ; extra == "sqlalchemy" or extra == "alembic"
|
|
@@ -57,12 +57,9 @@ For the latest documentation, see
|
|
|
57
57
|
|
|
58
58
|
Install the library with `pip install databricks-sql-connector`
|
|
59
59
|
|
|
60
|
-
Note: Don't hard-code authentication secrets into your Python. Use environment variables
|
|
61
|
-
|
|
62
60
|
```bash
|
|
63
61
|
export DATABRICKS_HOST=********.databricks.com
|
|
64
62
|
export DATABRICKS_HTTP_PATH=/sql/1.0/endpoints/****************
|
|
65
|
-
export DATABRICKS_TOKEN=dapi********************************
|
|
66
63
|
```
|
|
67
64
|
|
|
68
65
|
Example usage:
|
|
@@ -72,12 +69,10 @@ from databricks import sql
|
|
|
72
69
|
|
|
73
70
|
host = os.getenv("DATABRICKS_HOST")
|
|
74
71
|
http_path = os.getenv("DATABRICKS_HTTP_PATH")
|
|
75
|
-
access_token = os.getenv("DATABRICKS_TOKEN")
|
|
76
72
|
|
|
77
73
|
connection = sql.connect(
|
|
78
74
|
server_hostname=host,
|
|
79
|
-
http_path=http_path
|
|
80
|
-
access_token=access_token)
|
|
75
|
+
http_path=http_path)
|
|
81
76
|
|
|
82
77
|
cursor = connection.cursor()
|
|
83
78
|
cursor.execute('SELECT :param `p`, * FROM RANGE(10)', {"param": "foo"})
|
|
@@ -93,7 +88,10 @@ In the above example:
|
|
|
93
88
|
- `server-hostname` is the Databricks instance host name.
|
|
94
89
|
- `http-path` is the HTTP Path either to a Databricks SQL endpoint (e.g. /sql/1.0/endpoints/1234567890abcdef),
|
|
95
90
|
or to a Databricks Runtime interactive cluster (e.g. /sql/protocolv1/o/1234567890123456/1234-123456-slid123)
|
|
96
|
-
|
|
91
|
+
|
|
92
|
+
> Note: This example uses [Databricks OAuth U2M](https://docs.databricks.com/en/dev-tools/auth/oauth-u2m.html)
|
|
93
|
+
> to authenticate the target Databricks user account and needs to open the browser for authentication. So it
|
|
94
|
+
> can only run on the user's machine.
|
|
97
95
|
|
|
98
96
|
|
|
99
97
|
## Contributing
|
|
@@ -24,12 +24,9 @@ For the latest documentation, see
|
|
|
24
24
|
|
|
25
25
|
Install the library with `pip install databricks-sql-connector`
|
|
26
26
|
|
|
27
|
-
Note: Don't hard-code authentication secrets into your Python. Use environment variables
|
|
28
|
-
|
|
29
27
|
```bash
|
|
30
28
|
export DATABRICKS_HOST=********.databricks.com
|
|
31
29
|
export DATABRICKS_HTTP_PATH=/sql/1.0/endpoints/****************
|
|
32
|
-
export DATABRICKS_TOKEN=dapi********************************
|
|
33
30
|
```
|
|
34
31
|
|
|
35
32
|
Example usage:
|
|
@@ -39,12 +36,10 @@ from databricks import sql
|
|
|
39
36
|
|
|
40
37
|
host = os.getenv("DATABRICKS_HOST")
|
|
41
38
|
http_path = os.getenv("DATABRICKS_HTTP_PATH")
|
|
42
|
-
access_token = os.getenv("DATABRICKS_TOKEN")
|
|
43
39
|
|
|
44
40
|
connection = sql.connect(
|
|
45
41
|
server_hostname=host,
|
|
46
|
-
http_path=http_path
|
|
47
|
-
access_token=access_token)
|
|
42
|
+
http_path=http_path)
|
|
48
43
|
|
|
49
44
|
cursor = connection.cursor()
|
|
50
45
|
cursor.execute('SELECT :param `p`, * FROM RANGE(10)', {"param": "foo"})
|
|
@@ -60,7 +55,10 @@ In the above example:
|
|
|
60
55
|
- `server-hostname` is the Databricks instance host name.
|
|
61
56
|
- `http-path` is the HTTP Path either to a Databricks SQL endpoint (e.g. /sql/1.0/endpoints/1234567890abcdef),
|
|
62
57
|
or to a Databricks Runtime interactive cluster (e.g. /sql/protocolv1/o/1234567890123456/1234-123456-slid123)
|
|
63
|
-
|
|
58
|
+
|
|
59
|
+
> Note: This example uses [Databricks OAuth U2M](https://docs.databricks.com/en/dev-tools/auth/oauth-u2m.html)
|
|
60
|
+
> to authenticate the target Databricks user account and needs to open the browser for authentication. So it
|
|
61
|
+
> can only run on the user's machine.
|
|
64
62
|
|
|
65
63
|
|
|
66
64
|
## Contributing
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "databricks-sql-connector"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.4.0"
|
|
4
4
|
description = "Databricks SQL Connector for Python"
|
|
5
5
|
authors = ["Databricks <databricks-sql-connector-maintainers@databricks.com>"]
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -12,7 +12,7 @@ include = ["CHANGELOG.md"]
|
|
|
12
12
|
python = "^3.8.0"
|
|
13
13
|
thrift = ">=0.16.0,<0.21.0"
|
|
14
14
|
pandas = [
|
|
15
|
-
{ version = ">=1.2.5,<2.
|
|
15
|
+
{ version = ">=1.2.5,<2.3.0", python = ">=3.8" }
|
|
16
16
|
]
|
|
17
17
|
pyarrow = ">=14.0.1,<17"
|
|
18
18
|
|
|
@@ -20,8 +20,8 @@ lz4 = "^4.0.2"
|
|
|
20
20
|
requests = "^2.18.1"
|
|
21
21
|
oauthlib = "^3.1.0"
|
|
22
22
|
numpy = [
|
|
23
|
-
{ version = "
|
|
24
|
-
{ version = "
|
|
23
|
+
{ version = "^1.16.6", python = ">=3.8,<3.11" },
|
|
24
|
+
{ version = "^1.23.4", python = ">=3.11" },
|
|
25
25
|
]
|
|
26
26
|
sqlalchemy = { version = ">=2.0.21", optional = true }
|
|
27
27
|
openpyxl = "^3.0.10"
|
|
@@ -34,7 +34,7 @@ alembic = ["sqlalchemy", "alembic"]
|
|
|
34
34
|
|
|
35
35
|
[tool.poetry.dev-dependencies]
|
|
36
36
|
pytest = "^7.1.2"
|
|
37
|
-
mypy = "^
|
|
37
|
+
mypy = "^1.10.1"
|
|
38
38
|
pylint = ">=2.12.0"
|
|
39
39
|
black = "^22.3.0"
|
|
40
40
|
pytest-dotenv = "^0.5.2"
|
{databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/auth.py
RENAMED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
-
from typing import List
|
|
2
|
+
from typing import Optional, List
|
|
3
3
|
|
|
4
4
|
from databricks.sql.auth.authenticators import (
|
|
5
5
|
AuthProvider,
|
|
6
6
|
AccessTokenAuthProvider,
|
|
7
|
-
BasicAuthProvider,
|
|
8
7
|
ExternalAuthProvider,
|
|
9
8
|
DatabricksOAuthProvider,
|
|
10
9
|
)
|
|
@@ -13,7 +12,7 @@ from databricks.sql.auth.authenticators import (
|
|
|
13
12
|
class AuthType(Enum):
|
|
14
13
|
DATABRICKS_OAUTH = "databricks-oauth"
|
|
15
14
|
AZURE_OAUTH = "azure-oauth"
|
|
16
|
-
# other supported types (access_token
|
|
15
|
+
# other supported types (access_token) can be inferred
|
|
17
16
|
# we can add more types as needed later
|
|
18
17
|
|
|
19
18
|
|
|
@@ -21,21 +20,17 @@ class ClientContext:
|
|
|
21
20
|
def __init__(
|
|
22
21
|
self,
|
|
23
22
|
hostname: str,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
use_cert_as_auth: str = None,
|
|
32
|
-
tls_client_cert_file: str = None,
|
|
23
|
+
access_token: Optional[str] = None,
|
|
24
|
+
auth_type: Optional[str] = None,
|
|
25
|
+
oauth_scopes: Optional[List[str]] = None,
|
|
26
|
+
oauth_client_id: Optional[str] = None,
|
|
27
|
+
oauth_redirect_port_range: Optional[List[int]] = None,
|
|
28
|
+
use_cert_as_auth: Optional[str] = None,
|
|
29
|
+
tls_client_cert_file: Optional[str] = None,
|
|
33
30
|
oauth_persistence=None,
|
|
34
31
|
credentials_provider=None,
|
|
35
32
|
):
|
|
36
33
|
self.hostname = hostname
|
|
37
|
-
self.username = username
|
|
38
|
-
self.password = password
|
|
39
34
|
self.access_token = access_token
|
|
40
35
|
self.auth_type = auth_type
|
|
41
36
|
self.oauth_scopes = oauth_scopes
|
|
@@ -65,13 +60,24 @@ def get_auth_provider(cfg: ClientContext):
|
|
|
65
60
|
)
|
|
66
61
|
elif cfg.access_token is not None:
|
|
67
62
|
return AccessTokenAuthProvider(cfg.access_token)
|
|
68
|
-
elif cfg.username is not None and cfg.password is not None:
|
|
69
|
-
return BasicAuthProvider(cfg.username, cfg.password)
|
|
70
63
|
elif cfg.use_cert_as_auth and cfg.tls_client_cert_file:
|
|
71
64
|
# no op authenticator. authentication is performed using ssl certificate outside of headers
|
|
72
65
|
return AuthProvider()
|
|
73
66
|
else:
|
|
74
|
-
|
|
67
|
+
if (
|
|
68
|
+
cfg.oauth_redirect_port_range is not None
|
|
69
|
+
and cfg.oauth_client_id is not None
|
|
70
|
+
and cfg.oauth_scopes is not None
|
|
71
|
+
):
|
|
72
|
+
return DatabricksOAuthProvider(
|
|
73
|
+
cfg.hostname,
|
|
74
|
+
cfg.oauth_persistence,
|
|
75
|
+
cfg.oauth_redirect_port_range,
|
|
76
|
+
cfg.oauth_client_id,
|
|
77
|
+
cfg.oauth_scopes,
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
raise RuntimeError("No valid authentication settings!")
|
|
75
81
|
|
|
76
82
|
|
|
77
83
|
PYSQL_OAUTH_SCOPES = ["sql", "offline_access"]
|
|
@@ -100,12 +106,16 @@ def get_python_sql_connector_auth_provider(hostname: str, **kwargs):
|
|
|
100
106
|
(client_id, redirect_port_range) = get_client_id_and_redirect_port(
|
|
101
107
|
auth_type == AuthType.AZURE_OAUTH.value
|
|
102
108
|
)
|
|
109
|
+
if kwargs.get("username") or kwargs.get("password"):
|
|
110
|
+
raise ValueError(
|
|
111
|
+
"Username/password authentication is no longer supported. "
|
|
112
|
+
"Please use OAuth or access token instead."
|
|
113
|
+
)
|
|
114
|
+
|
|
103
115
|
cfg = ClientContext(
|
|
104
116
|
hostname=normalize_host_name(hostname),
|
|
105
117
|
auth_type=auth_type,
|
|
106
118
|
access_token=kwargs.get("access_token"),
|
|
107
|
-
username=kwargs.get("_username"),
|
|
108
|
-
password=kwargs.get("_password"),
|
|
109
119
|
use_cert_as_auth=kwargs.get("_use_cert_as_auth"),
|
|
110
120
|
tls_client_cert_file=kwargs.get("_tls_client_cert_file"),
|
|
111
121
|
oauth_scopes=PYSQL_OAUTH_SCOPES,
|
|
@@ -43,21 +43,6 @@ class AccessTokenAuthProvider(AuthProvider):
|
|
|
43
43
|
request_headers["Authorization"] = self.__authorization_header_value
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
# Private API: this is an evolving interface and it will change in the future.
|
|
47
|
-
# Please must not depend on it in your applications.
|
|
48
|
-
class BasicAuthProvider(AuthProvider):
|
|
49
|
-
def __init__(self, username: str, password: str):
|
|
50
|
-
auth_credentials = f"{username}:{password}".encode("UTF-8")
|
|
51
|
-
auth_credentials_base64 = base64.standard_b64encode(auth_credentials).decode(
|
|
52
|
-
"UTF-8"
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
self.__authorization_header_value = f"Basic {auth_credentials_base64}"
|
|
56
|
-
|
|
57
|
-
def add_headers(self, request_headers: Dict[str, str]):
|
|
58
|
-
request_headers["Authorization"] = self.__authorization_header_value
|
|
59
|
-
|
|
60
|
-
|
|
61
46
|
# Private API: this is an evolving interface and it will change in the future.
|
|
62
47
|
# Please must not depend on it in your applications.
|
|
63
48
|
class DatabricksOAuthProvider(AuthProvider):
|
{databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/retry.py
RENAMED
|
@@ -7,7 +7,7 @@ from typing import List, Optional, Tuple, Union
|
|
|
7
7
|
# We only use this import for type hinting
|
|
8
8
|
try:
|
|
9
9
|
# If urllib3~=2.0 is installed
|
|
10
|
-
from urllib3 import BaseHTTPResponse
|
|
10
|
+
from urllib3 import BaseHTTPResponse
|
|
11
11
|
except ImportError:
|
|
12
12
|
# If urllib3~=1.0 is installed
|
|
13
13
|
from urllib3 import HTTPResponse as BaseHTTPResponse
|
|
@@ -129,7 +129,7 @@ class DatabricksRetryPolicy(Retry):
|
|
|
129
129
|
urllib3_kwargs.update(**_urllib_kwargs_we_care_about)
|
|
130
130
|
|
|
131
131
|
super().__init__(
|
|
132
|
-
**urllib3_kwargs,
|
|
132
|
+
**urllib3_kwargs,
|
|
133
133
|
)
|
|
134
134
|
|
|
135
135
|
@classmethod
|
|
@@ -162,7 +162,9 @@ class DatabricksRetryPolicy(Retry):
|
|
|
162
162
|
new_object.command_type = command_type
|
|
163
163
|
return new_object
|
|
164
164
|
|
|
165
|
-
def new(
|
|
165
|
+
def new(
|
|
166
|
+
self, **urllib3_incremented_counters: typing.Any
|
|
167
|
+
) -> "DatabricksRetryPolicy":
|
|
166
168
|
"""This method is responsible for passing the entire Retry state to its next iteration.
|
|
167
169
|
|
|
168
170
|
urllib3 calls Retry.new() between successive requests as part of its `.increment()` method
|
|
@@ -210,7 +212,7 @@ class DatabricksRetryPolicy(Retry):
|
|
|
210
212
|
other=self.other,
|
|
211
213
|
allowed_methods=self.allowed_methods,
|
|
212
214
|
status_forcelist=self.status_forcelist,
|
|
213
|
-
backoff_factor=self.backoff_factor,
|
|
215
|
+
backoff_factor=self.backoff_factor,
|
|
214
216
|
raise_on_redirect=self.raise_on_redirect,
|
|
215
217
|
raise_on_status=self.raise_on_status,
|
|
216
218
|
history=self.history,
|
|
@@ -222,7 +224,7 @@ class DatabricksRetryPolicy(Retry):
|
|
|
222
224
|
urllib3_init_params.update(**urllib3_incremented_counters)
|
|
223
225
|
|
|
224
226
|
# Include urllib3's current state in our __init__ params
|
|
225
|
-
databricks_init_params["urllib3_kwargs"].update(**urllib3_init_params) # type: ignore
|
|
227
|
+
databricks_init_params["urllib3_kwargs"].update(**urllib3_init_params) # type: ignore[attr-defined]
|
|
226
228
|
|
|
227
229
|
return type(self).__private_init__(
|
|
228
230
|
retry_start_time=self._retry_start_time,
|
|
@@ -274,7 +276,7 @@ class DatabricksRetryPolicy(Retry):
|
|
|
274
276
|
f"Retry request would exceed Retry policy max retry duration of {self.stop_after_attempts_duration} seconds"
|
|
275
277
|
)
|
|
276
278
|
|
|
277
|
-
def sleep_for_retry(self, response: BaseHTTPResponse) -> bool:
|
|
279
|
+
def sleep_for_retry(self, response: BaseHTTPResponse) -> bool:
|
|
278
280
|
"""Sleeps for the duration specified in the response Retry-After header, if present
|
|
279
281
|
|
|
280
282
|
A MaxRetryDurationError will be raised if doing so would exceed self.max_attempts_duration
|
|
@@ -325,7 +327,8 @@ class DatabricksRetryPolicy(Retry):
|
|
|
325
327
|
default, this means ExecuteStatement is only retried for codes 429 and 503.
|
|
326
328
|
This limit prevents automatically retrying non-idempotent commands that could
|
|
327
329
|
be destructive.
|
|
328
|
-
5. The request received a
|
|
330
|
+
5. The request received a 401 response, because this can never succeed.
|
|
331
|
+
6. The request received a 403 response, because this can never succeed.
|
|
329
332
|
|
|
330
333
|
|
|
331
334
|
Q: What about OSErrors and Redirects?
|
|
@@ -339,6 +342,11 @@ class DatabricksRetryPolicy(Retry):
|
|
|
339
342
|
if status_code == 200:
|
|
340
343
|
return False, "200 codes are not retried"
|
|
341
344
|
|
|
345
|
+
if status_code == 401:
|
|
346
|
+
raise NonRecoverableNetworkError(
|
|
347
|
+
"Received 401 - UNAUTHORIZED. Confirm your authentication credentials."
|
|
348
|
+
)
|
|
349
|
+
|
|
342
350
|
if status_code == 403:
|
|
343
351
|
raise NonRecoverableNetworkError(
|
|
344
352
|
"Received 403 - FORBIDDEN. Confirm your authentication credentials."
|
|
@@ -349,7 +357,7 @@ class DatabricksRetryPolicy(Retry):
|
|
|
349
357
|
raise NonRecoverableNetworkError("Received code 501 from server.")
|
|
350
358
|
|
|
351
359
|
# Request failed and this method is not retryable. We only retry POST requests.
|
|
352
|
-
if not self._is_method_retryable(method):
|
|
360
|
+
if not self._is_method_retryable(method):
|
|
353
361
|
return False, "Only POST requests are retried"
|
|
354
362
|
|
|
355
363
|
# Request failed with 404 and was a GetOperationStatus. This is not recoverable. Don't retry.
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import logging
|
|
3
3
|
import urllib.parse
|
|
4
|
-
from typing import Dict, Union
|
|
4
|
+
from typing import Dict, Union, Optional
|
|
5
5
|
|
|
6
6
|
import six
|
|
7
7
|
import thrift
|
|
8
8
|
|
|
9
|
-
logger = logging.getLogger(__name__)
|
|
10
|
-
|
|
11
9
|
import ssl
|
|
12
10
|
import warnings
|
|
13
11
|
from http.client import HTTPResponse
|
|
@@ -16,6 +14,9 @@ from io import BytesIO
|
|
|
16
14
|
from urllib3 import HTTPConnectionPool, HTTPSConnectionPool, ProxyManager
|
|
17
15
|
from urllib3.util import make_headers
|
|
18
16
|
from databricks.sql.auth.retry import CommandType, DatabricksRetryPolicy
|
|
17
|
+
from databricks.sql.types import SSLOptions
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class THttpClient(thrift.transport.THttpClient.THttpClient):
|
|
@@ -25,13 +26,12 @@ class THttpClient(thrift.transport.THttpClient.THttpClient):
|
|
|
25
26
|
uri_or_host,
|
|
26
27
|
port=None,
|
|
27
28
|
path=None,
|
|
28
|
-
|
|
29
|
-
cert_file=None,
|
|
30
|
-
key_file=None,
|
|
31
|
-
ssl_context=None,
|
|
29
|
+
ssl_options: Optional[SSLOptions] = None,
|
|
32
30
|
max_connections: int = 1,
|
|
33
31
|
retry_policy: Union[DatabricksRetryPolicy, int] = 0,
|
|
34
32
|
):
|
|
33
|
+
self._ssl_options = ssl_options
|
|
34
|
+
|
|
35
35
|
if port is not None:
|
|
36
36
|
warnings.warn(
|
|
37
37
|
"Please use the THttpClient('http{s}://host:port/path') constructor",
|
|
@@ -48,13 +48,11 @@ class THttpClient(thrift.transport.THttpClient.THttpClient):
|
|
|
48
48
|
self.scheme = parsed.scheme
|
|
49
49
|
assert self.scheme in ("http", "https")
|
|
50
50
|
if self.scheme == "https":
|
|
51
|
-
self.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
else ssl_context
|
|
57
|
-
)
|
|
51
|
+
if self._ssl_options is not None:
|
|
52
|
+
# TODO: Not sure if those options are used anywhere - need to double-check
|
|
53
|
+
self.certfile = self._ssl_options.tls_client_cert_file
|
|
54
|
+
self.keyfile = self._ssl_options.tls_client_cert_key_file
|
|
55
|
+
self.context = self._ssl_options.create_ssl_context()
|
|
58
56
|
self.port = parsed.port
|
|
59
57
|
self.host = parsed.hostname
|
|
60
58
|
self.path = parsed.path
|
|
@@ -109,12 +107,23 @@ class THttpClient(thrift.transport.THttpClient.THttpClient):
|
|
|
109
107
|
def open(self):
|
|
110
108
|
|
|
111
109
|
# self.__pool replaces the self.__http used by the original THttpClient
|
|
110
|
+
_pool_kwargs = {"maxsize": self.max_connections}
|
|
111
|
+
|
|
112
112
|
if self.scheme == "http":
|
|
113
113
|
pool_class = HTTPConnectionPool
|
|
114
114
|
elif self.scheme == "https":
|
|
115
115
|
pool_class = HTTPSConnectionPool
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
_pool_kwargs.update(
|
|
117
|
+
{
|
|
118
|
+
"cert_reqs": ssl.CERT_REQUIRED
|
|
119
|
+
if self._ssl_options.tls_verify
|
|
120
|
+
else ssl.CERT_NONE,
|
|
121
|
+
"ca_certs": self._ssl_options.tls_trusted_ca_file,
|
|
122
|
+
"cert_file": self._ssl_options.tls_client_cert_file,
|
|
123
|
+
"key_file": self._ssl_options.tls_client_cert_key_file,
|
|
124
|
+
"key_password": self._ssl_options.tls_client_cert_key_password,
|
|
125
|
+
}
|
|
126
|
+
)
|
|
118
127
|
|
|
119
128
|
if self.using_proxy():
|
|
120
129
|
proxy_manager = ProxyManager(
|
{databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/client.py
RENAMED
|
@@ -35,7 +35,7 @@ from databricks.sql.parameters.native import (
|
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
from databricks.sql.types import Row
|
|
38
|
+
from databricks.sql.types import Row, SSLOptions
|
|
39
39
|
from databricks.sql.auth.auth import get_python_sql_connector_auth_provider
|
|
40
40
|
from databricks.sql.experimental.oauth_persistence import OAuthPersistence
|
|
41
41
|
|
|
@@ -59,7 +59,7 @@ class Connection:
|
|
|
59
59
|
http_path: str,
|
|
60
60
|
access_token: Optional[str] = None,
|
|
61
61
|
http_headers: Optional[List[Tuple[str, str]]] = None,
|
|
62
|
-
session_configuration: Dict[str, Any] = None,
|
|
62
|
+
session_configuration: Optional[Dict[str, Any]] = None,
|
|
63
63
|
catalog: Optional[str] = None,
|
|
64
64
|
schema: Optional[str] = None,
|
|
65
65
|
_use_arrow_native_complex_types: Optional[bool] = True,
|
|
@@ -96,7 +96,7 @@ class Connection:
|
|
|
96
96
|
sanitise parameterized inputs to prevent SQL injection. The inline parameter approach is maintained for
|
|
97
97
|
legacy purposes and will be deprecated in a future release. When this parameter is `True` you will see
|
|
98
98
|
a warning log message. To suppress this log message, set `use_inline_params="silent"`.
|
|
99
|
-
auth_type: `str`, optional
|
|
99
|
+
auth_type: `str`, optional (default is databricks-oauth if neither `access_token` nor `tls_client_cert_file` is set)
|
|
100
100
|
`databricks-oauth` : to use Databricks OAuth with fine-grained permission scopes, set to `databricks-oauth`.
|
|
101
101
|
`azure-oauth` : to use Microsoft Entra ID OAuth flow, set to `azure-oauth`.
|
|
102
102
|
|
|
@@ -163,23 +163,24 @@ class Connection:
|
|
|
163
163
|
# Internal arguments in **kwargs:
|
|
164
164
|
# _user_agent_entry
|
|
165
165
|
# Tag to add to User-Agent header. For use by partners.
|
|
166
|
-
# _username, _password
|
|
167
|
-
# Username and password Basic authentication (no official support)
|
|
168
166
|
# _use_cert_as_auth
|
|
169
|
-
# Use a TLS cert instead of a token
|
|
167
|
+
# Use a TLS cert instead of a token
|
|
170
168
|
# _enable_ssl
|
|
171
169
|
# Connect over HTTP instead of HTTPS
|
|
172
170
|
# _port
|
|
173
171
|
# Which port to connect to
|
|
174
172
|
# _skip_routing_headers:
|
|
175
173
|
# Don't set routing headers if set to True (for use when connecting directly to server)
|
|
174
|
+
# _tls_no_verify
|
|
175
|
+
# Set to True (Boolean) to completely disable SSL verification.
|
|
176
176
|
# _tls_verify_hostname
|
|
177
177
|
# Set to False (Boolean) to disable SSL hostname verification, but check certificate.
|
|
178
178
|
# _tls_trusted_ca_file
|
|
179
179
|
# Set to the path of the file containing trusted CA certificates for server certificate
|
|
180
180
|
# verification. If not provide, uses system truststore.
|
|
181
|
-
# _tls_client_cert_file, _tls_client_cert_key_file
|
|
181
|
+
# _tls_client_cert_file, _tls_client_cert_key_file, _tls_client_cert_key_password
|
|
182
182
|
# Set client SSL certificate.
|
|
183
|
+
# See https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_cert_chain
|
|
183
184
|
# _retry_stop_after_attempts_count
|
|
184
185
|
# The maximum number of attempts during a request retry sequence (defaults to 24)
|
|
185
186
|
# _socket_timeout
|
|
@@ -220,12 +221,25 @@ class Connection:
|
|
|
220
221
|
|
|
221
222
|
base_headers = [("User-Agent", useragent_header)]
|
|
222
223
|
|
|
224
|
+
self._ssl_options = SSLOptions(
|
|
225
|
+
# Double negation is generally a bad thing, but we have to keep backward compatibility
|
|
226
|
+
tls_verify=not kwargs.get(
|
|
227
|
+
"_tls_no_verify", False
|
|
228
|
+
), # by default - verify cert and host
|
|
229
|
+
tls_verify_hostname=kwargs.get("_tls_verify_hostname", True),
|
|
230
|
+
tls_trusted_ca_file=kwargs.get("_tls_trusted_ca_file"),
|
|
231
|
+
tls_client_cert_file=kwargs.get("_tls_client_cert_file"),
|
|
232
|
+
tls_client_cert_key_file=kwargs.get("_tls_client_cert_key_file"),
|
|
233
|
+
tls_client_cert_key_password=kwargs.get("_tls_client_cert_key_password"),
|
|
234
|
+
)
|
|
235
|
+
|
|
223
236
|
self.thrift_backend = ThriftBackend(
|
|
224
237
|
self.host,
|
|
225
238
|
self.port,
|
|
226
239
|
http_path,
|
|
227
240
|
(http_headers or []) + base_headers,
|
|
228
241
|
auth_provider,
|
|
242
|
+
ssl_options=self._ssl_options,
|
|
229
243
|
_use_arrow_native_complex_types=_use_arrow_native_complex_types,
|
|
230
244
|
**kwargs,
|
|
231
245
|
)
|
|
@@ -460,9 +474,9 @@ class Cursor:
|
|
|
460
474
|
output: List[TDbsqlParameter] = []
|
|
461
475
|
for p in params:
|
|
462
476
|
if isinstance(p, DbsqlParameterBase):
|
|
463
|
-
output.append(p)
|
|
477
|
+
output.append(p)
|
|
464
478
|
else:
|
|
465
|
-
output.append(dbsql_parameter_from_primitive(value=p))
|
|
479
|
+
output.append(dbsql_parameter_from_primitive(value=p))
|
|
466
480
|
|
|
467
481
|
return output
|
|
468
482
|
|
|
@@ -640,7 +654,7 @@ class Cursor:
|
|
|
640
654
|
)
|
|
641
655
|
|
|
642
656
|
def _handle_staging_put(
|
|
643
|
-
self, presigned_url: str, local_file: str, headers: dict = None
|
|
657
|
+
self, presigned_url: str, local_file: str, headers: Optional[dict] = None
|
|
644
658
|
):
|
|
645
659
|
"""Make an HTTP PUT request
|
|
646
660
|
|
|
@@ -655,7 +669,7 @@ class Cursor:
|
|
|
655
669
|
|
|
656
670
|
# fmt: off
|
|
657
671
|
# Design borrowed from: https://stackoverflow.com/a/2342589/5093960
|
|
658
|
-
|
|
672
|
+
|
|
659
673
|
OK = requests.codes.ok # 200
|
|
660
674
|
CREATED = requests.codes.created # 201
|
|
661
675
|
ACCEPTED = requests.codes.accepted # 202
|
|
@@ -675,7 +689,7 @@ class Cursor:
|
|
|
675
689
|
)
|
|
676
690
|
|
|
677
691
|
def _handle_staging_get(
|
|
678
|
-
self, local_file: str, presigned_url: str, headers: dict = None
|
|
692
|
+
self, local_file: str, presigned_url: str, headers: Optional[dict] = None
|
|
679
693
|
):
|
|
680
694
|
"""Make an HTTP GET request, create a local file with the received data
|
|
681
695
|
|
|
@@ -697,7 +711,9 @@ class Cursor:
|
|
|
697
711
|
with open(local_file, "wb") as fp:
|
|
698
712
|
fp.write(r.content)
|
|
699
713
|
|
|
700
|
-
def _handle_staging_remove(
|
|
714
|
+
def _handle_staging_remove(
|
|
715
|
+
self, presigned_url: str, headers: Optional[dict] = None
|
|
716
|
+
):
|
|
701
717
|
"""Make an HTTP DELETE request to the presigned_url"""
|
|
702
718
|
|
|
703
719
|
r = requests.delete(url=presigned_url, headers=headers)
|
|
@@ -757,7 +773,7 @@ class Cursor:
|
|
|
757
773
|
normalized_parameters = self._normalize_tparametercollection(parameters)
|
|
758
774
|
param_structure = self._determine_parameter_structure(normalized_parameters)
|
|
759
775
|
transformed_operation = transform_paramstyle(
|
|
760
|
-
operation, normalized_parameters, param_structure
|
|
776
|
+
operation, normalized_parameters, param_structure
|
|
761
777
|
)
|
|
762
778
|
prepared_operation, prepared_params = self._prepare_native_parameters(
|
|
763
779
|
transformed_operation, normalized_parameters, param_structure
|
|
@@ -861,7 +877,7 @@ class Cursor:
|
|
|
861
877
|
catalog_name: Optional[str] = None,
|
|
862
878
|
schema_name: Optional[str] = None,
|
|
863
879
|
table_name: Optional[str] = None,
|
|
864
|
-
table_types: List[str] = None,
|
|
880
|
+
table_types: Optional[List[str]] = None,
|
|
865
881
|
) -> "Cursor":
|
|
866
882
|
"""
|
|
867
883
|
Get tables corresponding to the catalog_name, schema_name and table_name.
|
|
@@ -1162,7 +1178,7 @@ class ResultSet:
|
|
|
1162
1178
|
timestamp_as_object=True,
|
|
1163
1179
|
)
|
|
1164
1180
|
|
|
1165
|
-
res = df.to_numpy(na_value=None)
|
|
1181
|
+
res = df.to_numpy(na_value=None, dtype="object")
|
|
1166
1182
|
return [ResultRow(*v) for v in res]
|
|
1167
1183
|
|
|
1168
1184
|
@property
|