databricks-sql-connector 3.0.3__tar.gz → 3.1.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.0.3 → databricks_sql_connector-3.1.0}/CHANGELOG.md +27 -6
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/PKG-INFO +1 -1
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/pyproject.toml +2 -1
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/__init__.py +1 -1
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/auth/auth.py +10 -7
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/auth/authenticators.py +4 -6
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/auth/endpoint.py +30 -7
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/auth/retry.py +4 -2
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/client.py +11 -7
- databricks_sql_connector-3.1.0/src/databricks/sqlalchemy/test_local/conftest.py +44 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test_local/e2e/test_basic.py +26 -27
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test_local/test_parsing.py +4 -4
- databricks_sql_connector-3.0.3/src/databricks/sqlalchemy/pytest.ini +0 -3
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/LICENSE +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/README.md +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/__init__.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/auth/__init__.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/auth/oauth.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/auth/oauth_http_handler.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/auth/thrift_http_client.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/cloudfetch/download_manager.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/cloudfetch/downloader.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/exc.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/experimental/__init__.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/experimental/oauth_persistence.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/parameters/__init__.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/parameters/native.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/parameters/py.typed +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/thrift_api/TCLIService/TCLIService-remote +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/thrift_api/TCLIService/TCLIService.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/thrift_api/TCLIService/__init__.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/thrift_api/TCLIService/constants.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/thrift_api/TCLIService/ttypes.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/thrift_api/__init__.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/thrift_backend.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/types.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/utils.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/README.sqlalchemy.md +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/README.tests.md +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/__init__.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/_ddl.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/_parse.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/_types.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/base.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/requirements.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/setup.cfg +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test/_extra.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test/_future.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test/_regression.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test/_unsupported.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test/conftest.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test/overrides/_componentreflectiontest.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test/overrides/_ctetest.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test/test_suite.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test_local/__init__.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test_local/e2e/MOCK_DATA.xlsx +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test_local/test_ddl.py +0 -0
- {databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/test_local/test_types.py +0 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Release History
|
|
2
2
|
|
|
3
|
+
# 3.1.0 (2024-02-16)
|
|
4
|
+
|
|
5
|
+
- Revert retry-after behavior to be exponential backoff (#349)
|
|
6
|
+
- Support Databricks OAuth on Azure (#351)
|
|
7
|
+
- Support Databricks OAuth on GCP (#338)
|
|
8
|
+
|
|
3
9
|
# 3.0.3 (2024-02-02)
|
|
4
10
|
|
|
5
11
|
- Revised docstrings and examples for OAuth (#339)
|
|
@@ -49,7 +55,7 @@
|
|
|
49
55
|
|
|
50
56
|
## 2.9.2 (2023-08-17)
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
**Note: this release was yanked from Pypi on 13 September 2023 due to compatibility issues with environments where `urllib3<=2.0.0` were installed. The log changes are incorporated into version 2.9.3 and greater.**
|
|
53
59
|
|
|
54
60
|
- Other: Add `examples/v3_retries_query_execute.py` (#199)
|
|
55
61
|
- Other: suppress log message when `_enable_v3_retries` is not `True` (#199)
|
|
@@ -57,7 +63,7 @@ __Note: this release was yanked from Pypi on 13 September 2023 due to compatibil
|
|
|
57
63
|
|
|
58
64
|
## 2.9.1 (2023-08-11)
|
|
59
65
|
|
|
60
|
-
|
|
66
|
+
**Note: this release was yanked from Pypi on 13 September 2023 due to compatibility issues with environments where `urllib3<=2.0.0` were installed.**
|
|
61
67
|
|
|
62
68
|
- Other: Explicitly pin urllib3 to ^2.0.0 (#191)
|
|
63
69
|
|
|
@@ -110,6 +116,7 @@ __Note: this release was yanked from Pypi on 13 September 2023 due to compatibil
|
|
|
110
116
|
- Other: Relax sqlalchemy required version as it was unecessarily strict.
|
|
111
117
|
|
|
112
118
|
## 2.5.0 (2023-04-14)
|
|
119
|
+
|
|
113
120
|
- Add support for External Auth providers
|
|
114
121
|
- Fix: Python HTTP proxies were broken
|
|
115
122
|
- Other: All Thrift requests that timeout during connection will be automatically retried
|
|
@@ -131,8 +138,8 @@ __Note: this release was yanked from Pypi on 13 September 2023 due to compatibil
|
|
|
131
138
|
|
|
132
139
|
## 2.2.2 (2023-01-03)
|
|
133
140
|
|
|
134
|
-
- Support custom oauth client id and redirect port
|
|
135
|
-
- Fix: Add none check on _oauth_persistence in DatabricksOAuthProvider
|
|
141
|
+
- Support custom oauth client id and redirect port
|
|
142
|
+
- Fix: Add none check on \_oauth_persistence in DatabricksOAuthProvider
|
|
136
143
|
|
|
137
144
|
## 2.2.1 (2022-11-29)
|
|
138
145
|
|
|
@@ -164,57 +171,71 @@ Huge thanks to @dbaxa for contributing this change!
|
|
|
164
171
|
|
|
165
172
|
- Add retry logic for `GetOperationStatus` requests that fail with an `OSError`
|
|
166
173
|
- Reorganised code to use Poetry for dependency management.
|
|
174
|
+
|
|
167
175
|
## 2.0.2 (2022-05-04)
|
|
176
|
+
|
|
168
177
|
- Better exception handling in automatic connection close
|
|
169
178
|
|
|
170
179
|
## 2.0.1 (2022-04-21)
|
|
180
|
+
|
|
171
181
|
- Fixed Pandas dependency in setup.cfg to be >= 1.2.0
|
|
172
182
|
|
|
173
183
|
## 2.0.0 (2022-04-19)
|
|
184
|
+
|
|
174
185
|
- Initial stable release of V2
|
|
175
|
-
- Added better support for complex types, so that in Databricks runtime 10.3+, Arrays, Maps and Structs will get
|
|
186
|
+
- Added better support for complex types, so that in Databricks runtime 10.3+, Arrays, Maps and Structs will get
|
|
176
187
|
deserialized as lists, lists of tuples and dicts, respectively.
|
|
177
188
|
- Changed the name of the metadata arg to http_headers
|
|
178
189
|
|
|
179
190
|
## 2.0.b2 (2022-04-04)
|
|
191
|
+
|
|
180
192
|
- Change import of collections.Iterable to collections.abc.Iterable to make the library compatible with Python 3.10
|
|
181
193
|
- Fixed bug with .tables method so that .tables works as expected with Unity-Catalog enabled endpoints
|
|
182
194
|
|
|
183
195
|
## 2.0.0b1 (2022-03-04)
|
|
196
|
+
|
|
184
197
|
- Fix packaging issue (dependencies were not being installed properly)
|
|
185
198
|
- Fetching timestamp results will now return aware instead of naive timestamps
|
|
186
199
|
- The client will now default to using simplified error messages
|
|
187
200
|
|
|
188
201
|
## 2.0.0b (2022-02-08)
|
|
202
|
+
|
|
189
203
|
- Initial beta release of V2. V2 is an internal re-write of large parts of the connector to use Databricks edge features. All public APIs from V1 remain.
|
|
190
|
-
- Added Unity Catalog support (pass catalog and / or
|
|
204
|
+
- Added Unity Catalog support (pass catalog and / or schema key word args to the .connect method to select initial schema and catalog)
|
|
191
205
|
|
|
192
206
|
---
|
|
193
207
|
|
|
194
208
|
**Note**: The code for versions prior to `v2.0.0b` is not contained in this repository. The below entries are included for reference only.
|
|
195
209
|
|
|
196
210
|
---
|
|
211
|
+
|
|
197
212
|
## 1.0.0 (2022-01-20)
|
|
213
|
+
|
|
198
214
|
- Add operations for retrieving metadata
|
|
199
215
|
- Add the ability to access columns by name on result rows
|
|
200
216
|
- Add the ability to provide configuration settings on connect
|
|
201
217
|
|
|
202
218
|
## 0.9.4 (2022-01-10)
|
|
219
|
+
|
|
203
220
|
- Improved logging and error messages.
|
|
204
221
|
|
|
205
222
|
## 0.9.3 (2021-12-08)
|
|
223
|
+
|
|
206
224
|
- Add retries for 429 and 503 HTTP responses.
|
|
207
225
|
|
|
208
226
|
## 0.9.2 (2021-12-02)
|
|
227
|
+
|
|
209
228
|
- (Bug fix) Increased Thrift requirement from 0.10.0 to 0.13.0 as 0.10.0 was in fact incompatible
|
|
210
229
|
- (Bug fix) Fixed error message after query execution failed -SQLSTATE and Error message were misplaced
|
|
211
230
|
|
|
212
231
|
## 0.9.1 (2021-09-01)
|
|
232
|
+
|
|
213
233
|
- Public Preview release, Experimental tag removed
|
|
214
234
|
- minor updates in internal build/packaging
|
|
215
235
|
- no functional changes
|
|
216
236
|
|
|
217
237
|
## 0.9.0 (2021-08-04)
|
|
238
|
+
|
|
218
239
|
- initial (Experimental) release of pyhive-forked connector
|
|
219
240
|
- Python DBAPI 2.0 (PEP-0249), thrift based
|
|
220
241
|
- see docs for more info: https://docs.databricks.com/dev-tools/python-sql-connector.html
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "databricks-sql-connector"
|
|
3
|
-
version = "3.0
|
|
3
|
+
version = "3.1.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"
|
|
@@ -58,6 +58,7 @@ exclude = ['ttypes\.py$', 'TCLIService\.py$']
|
|
|
58
58
|
exclude = '/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_build|buck-out|build|dist|thrift_api)/'
|
|
59
59
|
|
|
60
60
|
[tool.pytest.ini_options]
|
|
61
|
+
markers = {"reviewed" = "Test case has been reviewed by Databricks"}
|
|
61
62
|
minversion = "6.0"
|
|
62
63
|
log_cli = "false"
|
|
63
64
|
log_cli_level = "INFO"
|
{databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/auth/auth.py
RENAMED
|
@@ -8,12 +8,11 @@ from databricks.sql.auth.authenticators import (
|
|
|
8
8
|
ExternalAuthProvider,
|
|
9
9
|
DatabricksOAuthProvider,
|
|
10
10
|
)
|
|
11
|
-
from databricks.sql.auth.endpoint import infer_cloud_from_host, CloudType
|
|
12
|
-
from databricks.sql.experimental.oauth_persistence import OAuthPersistence
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
class AuthType(Enum):
|
|
16
14
|
DATABRICKS_OAUTH = "databricks-oauth"
|
|
15
|
+
AZURE_OAUTH = "azure-oauth"
|
|
17
16
|
# other supported types (access_token, user/pass) can be inferred
|
|
18
17
|
# we can add more types as needed later
|
|
19
18
|
|
|
@@ -51,7 +50,7 @@ class ClientContext:
|
|
|
51
50
|
def get_auth_provider(cfg: ClientContext):
|
|
52
51
|
if cfg.credentials_provider:
|
|
53
52
|
return ExternalAuthProvider(cfg.credentials_provider)
|
|
54
|
-
if cfg.auth_type
|
|
53
|
+
if cfg.auth_type in [AuthType.DATABRICKS_OAUTH.value, AuthType.AZURE_OAUTH.value]:
|
|
55
54
|
assert cfg.oauth_redirect_port_range is not None
|
|
56
55
|
assert cfg.oauth_client_id is not None
|
|
57
56
|
assert cfg.oauth_scopes is not None
|
|
@@ -62,6 +61,7 @@ def get_auth_provider(cfg: ClientContext):
|
|
|
62
61
|
cfg.oauth_redirect_port_range,
|
|
63
62
|
cfg.oauth_client_id,
|
|
64
63
|
cfg.oauth_scopes,
|
|
64
|
+
cfg.auth_type,
|
|
65
65
|
)
|
|
66
66
|
elif cfg.access_token is not None:
|
|
67
67
|
return AccessTokenAuthProvider(cfg.access_token)
|
|
@@ -87,19 +87,22 @@ def normalize_host_name(hostname: str):
|
|
|
87
87
|
return f"{maybe_scheme}{hostname}{maybe_trailing_slash}"
|
|
88
88
|
|
|
89
89
|
|
|
90
|
-
def get_client_id_and_redirect_port(
|
|
90
|
+
def get_client_id_and_redirect_port(use_azure_auth: bool):
|
|
91
91
|
return (
|
|
92
92
|
(PYSQL_OAUTH_CLIENT_ID, PYSQL_OAUTH_REDIRECT_PORT_RANGE)
|
|
93
|
-
if
|
|
93
|
+
if not use_azure_auth
|
|
94
94
|
else (PYSQL_OAUTH_AZURE_CLIENT_ID, PYSQL_OAUTH_AZURE_REDIRECT_PORT_RANGE)
|
|
95
95
|
)
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def get_python_sql_connector_auth_provider(hostname: str, **kwargs):
|
|
99
|
-
|
|
99
|
+
auth_type = kwargs.get("auth_type")
|
|
100
|
+
(client_id, redirect_port_range) = get_client_id_and_redirect_port(
|
|
101
|
+
auth_type == AuthType.AZURE_OAUTH.value
|
|
102
|
+
)
|
|
100
103
|
cfg = ClientContext(
|
|
101
104
|
hostname=normalize_host_name(hostname),
|
|
102
|
-
auth_type=
|
|
105
|
+
auth_type=auth_type,
|
|
103
106
|
access_token=kwargs.get("access_token"),
|
|
104
107
|
username=kwargs.get("_username"),
|
|
105
108
|
password=kwargs.get("_password"),
|
|
@@ -18,6 +18,7 @@ class AuthProvider:
|
|
|
18
18
|
|
|
19
19
|
HeaderFactory = Callable[[], Dict[str, str]]
|
|
20
20
|
|
|
21
|
+
|
|
21
22
|
# In order to keep compatibility with SDK
|
|
22
23
|
class CredentialsProvider(abc.ABC):
|
|
23
24
|
"""CredentialsProvider is the protocol (call-side interface)
|
|
@@ -69,16 +70,13 @@ class DatabricksOAuthProvider(AuthProvider):
|
|
|
69
70
|
redirect_port_range: List[int],
|
|
70
71
|
client_id: str,
|
|
71
72
|
scopes: List[str],
|
|
73
|
+
auth_type: str = "databricks-oauth",
|
|
72
74
|
):
|
|
73
75
|
try:
|
|
74
|
-
|
|
75
|
-
if not cloud_type:
|
|
76
|
-
raise NotImplementedError("Cannot infer the cloud type from hostname")
|
|
77
|
-
|
|
78
|
-
idp_endpoint = get_oauth_endpoints(cloud_type)
|
|
76
|
+
idp_endpoint = get_oauth_endpoints(hostname, auth_type == "azure-oauth")
|
|
79
77
|
if not idp_endpoint:
|
|
80
78
|
raise NotImplementedError(
|
|
81
|
-
f"OAuth is not supported for
|
|
79
|
+
f"OAuth is not supported for host ${hostname}"
|
|
82
80
|
)
|
|
83
81
|
|
|
84
82
|
# Convert to the corresponding scopes in the corresponding IdP
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#
|
|
2
2
|
# It implements all the cloud specific OAuth configuration/metadata
|
|
3
3
|
#
|
|
4
|
-
# Azure:
|
|
4
|
+
# Azure: It uses Databricks internal IdP or Azure AD
|
|
5
5
|
# AWS: It uses Databricks internal IdP
|
|
6
|
-
# GCP:
|
|
6
|
+
# GCP: It uses Databricks internal IdP
|
|
7
7
|
#
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
9
|
from enum import Enum
|
|
@@ -21,6 +21,7 @@ class OAuthScope:
|
|
|
21
21
|
class CloudType(Enum):
|
|
22
22
|
AWS = "aws"
|
|
23
23
|
AZURE = "azure"
|
|
24
|
+
GCP = "gcp"
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
DATABRICKS_AWS_DOMAINS = [
|
|
@@ -34,6 +35,10 @@ DATABRICKS_AZURE_DOMAINS = [
|
|
|
34
35
|
".databricks.azure.cn",
|
|
35
36
|
".databricks.azure.us",
|
|
36
37
|
]
|
|
38
|
+
DATABRICKS_GCP_DOMAINS = [".gcp.databricks.com"]
|
|
39
|
+
|
|
40
|
+
# Domain supported by Databricks InHouse OAuth
|
|
41
|
+
DATABRICKS_OAUTH_AZURE_DOMAINS = [".azuredatabricks.net"]
|
|
37
42
|
|
|
38
43
|
|
|
39
44
|
# Infer cloud type from Databricks SQL instance hostname
|
|
@@ -45,10 +50,20 @@ def infer_cloud_from_host(hostname: str) -> Optional[CloudType]:
|
|
|
45
50
|
return CloudType.AZURE
|
|
46
51
|
elif any(e for e in DATABRICKS_AWS_DOMAINS if host.endswith(e)):
|
|
47
52
|
return CloudType.AWS
|
|
53
|
+
elif any(e for e in DATABRICKS_GCP_DOMAINS if host.endswith(e)):
|
|
54
|
+
return CloudType.GCP
|
|
48
55
|
else:
|
|
49
56
|
return None
|
|
50
57
|
|
|
51
58
|
|
|
59
|
+
def is_supported_databricks_oauth_host(hostname: str) -> bool:
|
|
60
|
+
host = hostname.lower().replace("https://", "").split("/")[0]
|
|
61
|
+
domains = (
|
|
62
|
+
DATABRICKS_AWS_DOMAINS + DATABRICKS_GCP_DOMAINS + DATABRICKS_OAUTH_AZURE_DOMAINS
|
|
63
|
+
)
|
|
64
|
+
return any(e for e in domains if host.endswith(e))
|
|
65
|
+
|
|
66
|
+
|
|
52
67
|
def get_databricks_oidc_url(hostname: str):
|
|
53
68
|
maybe_scheme = "https://" if not hostname.startswith("https://") else ""
|
|
54
69
|
maybe_trailing_slash = "/" if not hostname.endswith("/") else ""
|
|
@@ -94,7 +109,7 @@ class AzureOAuthEndpointCollection(OAuthEndpointCollection):
|
|
|
94
109
|
return "https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration"
|
|
95
110
|
|
|
96
111
|
|
|
97
|
-
class
|
|
112
|
+
class InHouseOAuthEndpointCollection(OAuthEndpointCollection):
|
|
98
113
|
def get_scopes_mapping(self, scopes: List[str]) -> List[str]:
|
|
99
114
|
# No scope mapping in AWS
|
|
100
115
|
return scopes.copy()
|
|
@@ -108,10 +123,18 @@ class AwsOAuthEndpointCollection(OAuthEndpointCollection):
|
|
|
108
123
|
return f"{idp_url}/.well-known/oauth-authorization-server"
|
|
109
124
|
|
|
110
125
|
|
|
111
|
-
def get_oauth_endpoints(
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
def get_oauth_endpoints(
|
|
127
|
+
hostname: str, use_azure_auth: bool
|
|
128
|
+
) -> Optional[OAuthEndpointCollection]:
|
|
129
|
+
cloud = infer_cloud_from_host(hostname)
|
|
130
|
+
|
|
131
|
+
if cloud in [CloudType.AWS, CloudType.GCP]:
|
|
132
|
+
return InHouseOAuthEndpointCollection()
|
|
114
133
|
elif cloud == CloudType.AZURE:
|
|
115
|
-
return
|
|
134
|
+
return (
|
|
135
|
+
InHouseOAuthEndpointCollection()
|
|
136
|
+
if is_supported_databricks_oauth_host(hostname) and not use_azure_auth
|
|
137
|
+
else AzureOAuthEndpointCollection()
|
|
138
|
+
)
|
|
116
139
|
else:
|
|
117
140
|
return None
|
{databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/auth/retry.py
RENAMED
|
@@ -283,8 +283,10 @@ class DatabricksRetryPolicy(Retry):
|
|
|
283
283
|
"""
|
|
284
284
|
retry_after = self.get_retry_after(response)
|
|
285
285
|
if retry_after:
|
|
286
|
-
self.
|
|
287
|
-
|
|
286
|
+
backoff = self.get_backoff_time()
|
|
287
|
+
proposed_wait = max(backoff, retry_after)
|
|
288
|
+
self.check_proposed_wait(proposed_wait)
|
|
289
|
+
time.sleep(proposed_wait)
|
|
288
290
|
return True
|
|
289
291
|
|
|
290
292
|
return False
|
{databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/client.py
RENAMED
|
@@ -96,7 +96,8 @@ class Connection:
|
|
|
96
96
|
legacy purposes and will be deprecated in a future release. When this parameter is `True` you will see
|
|
97
97
|
a warning log message. To suppress this log message, set `use_inline_params="silent"`.
|
|
98
98
|
auth_type: `str`, optional
|
|
99
|
-
`databricks-oauth` : to use
|
|
99
|
+
`databricks-oauth` : to use Databricks OAuth with fine-grained permission scopes, set to `databricks-oauth`.
|
|
100
|
+
`azure-oauth` : to use Microsoft Entra ID OAuth flow, set to `azure-oauth`.
|
|
100
101
|
|
|
101
102
|
oauth_client_id: `str`, optional
|
|
102
103
|
custom oauth client_id. If not specified, it will use the built-in client_id of databricks-sql-python.
|
|
@@ -107,9 +108,9 @@ class Connection:
|
|
|
107
108
|
|
|
108
109
|
experimental_oauth_persistence: configures preferred storage for persisting oauth tokens.
|
|
109
110
|
This has to be a class implementing `OAuthPersistence`.
|
|
110
|
-
When `auth_type` is set to `databricks-oauth` without persisting the oauth token in a
|
|
111
|
-
the oauth tokens will only be maintained in memory and if the python process
|
|
112
|
-
will have to login again.
|
|
111
|
+
When `auth_type` is set to `databricks-oauth` or `azure-oauth` without persisting the oauth token in a
|
|
112
|
+
persistence storage the oauth tokens will only be maintained in memory and if the python process
|
|
113
|
+
restarts the end user will have to login again.
|
|
113
114
|
Note this is beta (private preview)
|
|
114
115
|
|
|
115
116
|
For persisting the oauth token in a prod environment you should subclass and implement OAuthPersistence
|
|
@@ -605,12 +606,15 @@ class Cursor:
|
|
|
605
606
|
"Local file operations are restricted to paths within the configured staging_allowed_local_path"
|
|
606
607
|
)
|
|
607
608
|
|
|
608
|
-
#
|
|
609
|
-
|
|
609
|
+
# May be real headers, or could be json string
|
|
610
|
+
headers = (
|
|
611
|
+
json.loads(row.headers) if isinstance(row.headers, str) else row.headers
|
|
612
|
+
)
|
|
613
|
+
|
|
610
614
|
handler_args = {
|
|
611
615
|
"presigned_url": row.presignedUrl,
|
|
612
616
|
"local_file": abs_localFile,
|
|
613
|
-
"headers":
|
|
617
|
+
"headers": dict(headers) or {},
|
|
614
618
|
}
|
|
615
619
|
|
|
616
620
|
logger.debug(
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@pytest.fixture(scope="session")
|
|
6
|
+
def host():
|
|
7
|
+
return os.getenv("DATABRICKS_SERVER_HOSTNAME")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(scope="session")
|
|
11
|
+
def http_path():
|
|
12
|
+
return os.getenv("DATABRICKS_HTTP_PATH")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture(scope="session")
|
|
16
|
+
def access_token():
|
|
17
|
+
return os.getenv("DATABRICKS_TOKEN")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture(scope="session")
|
|
21
|
+
def ingestion_user():
|
|
22
|
+
return os.getenv("DATABRICKS_USER")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture(scope="session")
|
|
26
|
+
def catalog():
|
|
27
|
+
return os.getenv("DATABRICKS_CATALOG")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture(scope="session")
|
|
31
|
+
def schema():
|
|
32
|
+
return os.getenv("DATABRICKS_SCHEMA", "default")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
36
|
+
def connection_details(host, http_path, access_token, ingestion_user, catalog, schema):
|
|
37
|
+
return {
|
|
38
|
+
"host": host,
|
|
39
|
+
"http_path": http_path,
|
|
40
|
+
"access_token": access_token,
|
|
41
|
+
"ingestion_user": ingestion_user,
|
|
42
|
+
"catalog": catalog,
|
|
43
|
+
"schema": schema,
|
|
44
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import decimal
|
|
3
|
-
import os
|
|
4
3
|
from typing import Tuple, Union, List
|
|
5
4
|
from unittest import skipIf
|
|
6
5
|
|
|
@@ -19,7 +18,7 @@ from sqlalchemy.engine import Engine
|
|
|
19
18
|
from sqlalchemy.engine.reflection import Inspector
|
|
20
19
|
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
|
|
21
20
|
from sqlalchemy.schema import DropColumnComment, SetColumnComment
|
|
22
|
-
from sqlalchemy.types import BOOLEAN, DECIMAL, Date,
|
|
21
|
+
from sqlalchemy.types import BOOLEAN, DECIMAL, Date, Integer, String
|
|
23
22
|
|
|
24
23
|
try:
|
|
25
24
|
from sqlalchemy.orm import declarative_base
|
|
@@ -49,12 +48,12 @@ def version_agnostic_select(object_to_select, *args, **kwargs):
|
|
|
49
48
|
return select(object_to_select, *args, **kwargs)
|
|
50
49
|
|
|
51
50
|
|
|
52
|
-
def version_agnostic_connect_arguments(
|
|
53
|
-
HOST =
|
|
54
|
-
HTTP_PATH =
|
|
55
|
-
ACCESS_TOKEN =
|
|
56
|
-
CATALOG =
|
|
57
|
-
SCHEMA =
|
|
51
|
+
def version_agnostic_connect_arguments(connection_details) -> Tuple[str, dict]:
|
|
52
|
+
HOST = connection_details["host"]
|
|
53
|
+
HTTP_PATH = connection_details["http_path"]
|
|
54
|
+
ACCESS_TOKEN = connection_details["access_token"]
|
|
55
|
+
CATALOG = connection_details["catalog"]
|
|
56
|
+
SCHEMA = connection_details["schema"]
|
|
58
57
|
|
|
59
58
|
ua_connect_args = {"_user_agent_entry": USER_AGENT_TOKEN}
|
|
60
59
|
|
|
@@ -77,8 +76,8 @@ def version_agnostic_connect_arguments(catalog=None, schema=None) -> Tuple[str,
|
|
|
77
76
|
|
|
78
77
|
|
|
79
78
|
@pytest.fixture
|
|
80
|
-
def db_engine() -> Engine:
|
|
81
|
-
conn_string, connect_args = version_agnostic_connect_arguments()
|
|
79
|
+
def db_engine(connection_details) -> Engine:
|
|
80
|
+
conn_string, connect_args = version_agnostic_connect_arguments(connection_details)
|
|
82
81
|
return create_engine(conn_string, connect_args=connect_args)
|
|
83
82
|
|
|
84
83
|
|
|
@@ -92,10 +91,11 @@ def run_query(db_engine: Engine, query: Union[str, Text]):
|
|
|
92
91
|
|
|
93
92
|
|
|
94
93
|
@pytest.fixture
|
|
95
|
-
def samples_engine() -> Engine:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
def samples_engine(connection_details) -> Engine:
|
|
95
|
+
details = connection_details.copy()
|
|
96
|
+
details["catalog"] = "samples"
|
|
97
|
+
details["schema"] = "nyctaxi"
|
|
98
|
+
conn_string, connect_args = version_agnostic_connect_arguments(details)
|
|
99
99
|
return create_engine(conn_string, connect_args=connect_args)
|
|
100
100
|
|
|
101
101
|
|
|
@@ -141,7 +141,7 @@ def test_connect_args(db_engine):
|
|
|
141
141
|
def test_pandas_upload(db_engine, metadata_obj):
|
|
142
142
|
import pandas as pd
|
|
143
143
|
|
|
144
|
-
SCHEMA =
|
|
144
|
+
SCHEMA = "default"
|
|
145
145
|
try:
|
|
146
146
|
df = pd.read_excel(
|
|
147
147
|
"src/databricks/sqlalchemy/test_local/e2e/demo_data/MOCK_DATA.xlsx"
|
|
@@ -409,7 +409,9 @@ def test_get_table_names_smoke_test(samples_engine: Engine):
|
|
|
409
409
|
_names is not None, "get_table_names did not succeed"
|
|
410
410
|
|
|
411
411
|
|
|
412
|
-
def test_has_table_across_schemas(
|
|
412
|
+
def test_has_table_across_schemas(
|
|
413
|
+
db_engine: Engine, samples_engine: Engine, catalog: str, schema: str
|
|
414
|
+
):
|
|
413
415
|
"""For this test to pass these conditions must be met:
|
|
414
416
|
- Table samples.nyctaxi.trips must exist
|
|
415
417
|
- Table samples.tpch.customer must exist
|
|
@@ -426,9 +428,6 @@ def test_has_table_across_schemas(db_engine: Engine, samples_engine: Engine):
|
|
|
426
428
|
)
|
|
427
429
|
|
|
428
430
|
# 3) Check for a table within a different catalog
|
|
429
|
-
other_catalog = os.environ.get("catalog")
|
|
430
|
-
other_schema = os.environ.get("schema")
|
|
431
|
-
|
|
432
431
|
# Create a table in a different catalog
|
|
433
432
|
with db_engine.connect() as conn:
|
|
434
433
|
conn.execute(text("CREATE TABLE test_has_table (numbers_are_cool INT);"))
|
|
@@ -442,8 +441,8 @@ def test_has_table_across_schemas(db_engine: Engine, samples_engine: Engine):
|
|
|
442
441
|
assert samples_engine.dialect.has_table(
|
|
443
442
|
connection=conn,
|
|
444
443
|
table_name="test_has_table",
|
|
445
|
-
schema=
|
|
446
|
-
catalog=
|
|
444
|
+
schema=schema,
|
|
445
|
+
catalog=catalog,
|
|
447
446
|
)
|
|
448
447
|
finally:
|
|
449
448
|
conn.execute(text("DROP TABLE test_has_table;"))
|
|
@@ -503,12 +502,12 @@ def test_get_columns(db_engine, sample_table: str):
|
|
|
503
502
|
|
|
504
503
|
class TestCommentReflection:
|
|
505
504
|
@pytest.fixture(scope="class")
|
|
506
|
-
def engine(self):
|
|
507
|
-
HOST =
|
|
508
|
-
HTTP_PATH =
|
|
509
|
-
ACCESS_TOKEN =
|
|
510
|
-
CATALOG =
|
|
511
|
-
SCHEMA =
|
|
505
|
+
def engine(self, connection_details: dict):
|
|
506
|
+
HOST = connection_details["host"]
|
|
507
|
+
HTTP_PATH = connection_details["http_path"]
|
|
508
|
+
ACCESS_TOKEN = connection_details["access_token"]
|
|
509
|
+
CATALOG = connection_details["catalog"]
|
|
510
|
+
SCHEMA = connection_details["schema"]
|
|
512
511
|
|
|
513
512
|
connection_string = f"databricks://token:{ACCESS_TOKEN}@{HOST}?http_path={HTTP_PATH}&catalog={CATALOG}&schema={SCHEMA}"
|
|
514
513
|
connect_args = {"_user_agent_entry": USER_AGENT_TOKEN}
|
|
@@ -64,16 +64,16 @@ def test_extract_3l_namespace_from_bad_constraint_string():
|
|
|
64
64
|
extract_three_level_identifier_from_constraint_string(input)
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
@pytest.mark.parametrize("
|
|
68
|
-
def test_build_fk_dict(
|
|
67
|
+
@pytest.mark.parametrize("tschema", [None, "some_schema"])
|
|
68
|
+
def test_build_fk_dict(tschema):
|
|
69
69
|
fk_constraint_string = "FOREIGN KEY (`parent_user_id`) REFERENCES `main`.`some_schema`.`users` (`user_id`)"
|
|
70
70
|
|
|
71
|
-
result = build_fk_dict("some_fk_name", fk_constraint_string, schema_name=
|
|
71
|
+
result = build_fk_dict("some_fk_name", fk_constraint_string, schema_name=tschema)
|
|
72
72
|
|
|
73
73
|
assert result == {
|
|
74
74
|
"name": "some_fk_name",
|
|
75
75
|
"constrained_columns": ["parent_user_id"],
|
|
76
|
-
"referred_schema":
|
|
76
|
+
"referred_schema": tschema,
|
|
77
77
|
"referred_table": "users",
|
|
78
78
|
"referred_columns": ["user_id"],
|
|
79
79
|
}
|
|
File without changes
|
|
File without changes
|
{databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/auth/oauth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/types.py
RENAMED
|
File without changes
|
{databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sql/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/_ddl.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_sql_connector-3.0.3 → databricks_sql_connector-3.1.0}/src/databricks/sqlalchemy/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|