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.
Files changed (61) hide show
  1. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/CHANGELOG.md +20 -0
  2. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/PKG-INFO +9 -11
  3. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/README.md +5 -7
  4. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/pyproject.toml +5 -5
  5. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/__init__.py +1 -1
  6. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/auth.py +29 -19
  7. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/authenticators.py +0 -15
  8. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/retry.py +16 -8
  9. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/thrift_http_client.py +25 -16
  10. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/client.py +32 -16
  11. databricks_sql_connector-3.4.0/src/databricks/sql/cloudfetch/download_manager.py +108 -0
  12. databricks_sql_connector-3.4.0/src/databricks/sql/cloudfetch/downloader.py +175 -0
  13. {databricks_sql_connector-3.2.0/src/databricks → databricks_sql_connector-3.4.0/src/databricks/sql/parameters}/py.typed +0 -0
  14. {databricks_sql_connector-3.2.0/src/databricks/sql/parameters → databricks_sql_connector-3.4.0/src/databricks/sql}/py.typed +0 -0
  15. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_backend.py +8 -35
  16. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/types.py +48 -0
  17. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/utils.py +17 -10
  18. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/README.sqlalchemy.md +1 -2
  19. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/README.tests.md +3 -3
  20. databricks_sql_connector-3.4.0/src/databricks/sqlalchemy/py.typed +0 -0
  21. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/requirements.py +1 -1
  22. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/overrides/_componentreflectiontest.py +1 -1
  23. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test_local/__init__.py +1 -1
  24. databricks_sql_connector-3.2.0/src/databricks/sql/cloudfetch/download_manager.py +0 -215
  25. databricks_sql_connector-3.2.0/src/databricks/sql/cloudfetch/downloader.py +0 -173
  26. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/LICENSE +0 -0
  27. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/__init__.py +0 -0
  28. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/__init__.py +0 -0
  29. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/endpoint.py +0 -0
  30. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/oauth.py +0 -0
  31. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/auth/oauth_http_handler.py +0 -0
  32. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/exc.py +0 -0
  33. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/experimental/__init__.py +0 -0
  34. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/experimental/oauth_persistence.py +0 -0
  35. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/parameters/__init__.py +0 -0
  36. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/parameters/native.py +0 -0
  37. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_api/TCLIService/TCLIService-remote +0 -0
  38. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_api/TCLIService/TCLIService.py +0 -0
  39. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_api/TCLIService/__init__.py +0 -0
  40. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_api/TCLIService/constants.py +0 -0
  41. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_api/TCLIService/ttypes.py +0 -0
  42. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sql/thrift_api/__init__.py +0 -0
  43. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/__init__.py +0 -0
  44. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/_ddl.py +0 -0
  45. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/_parse.py +0 -0
  46. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/_types.py +0 -0
  47. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/base.py +0 -0
  48. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/setup.cfg +0 -0
  49. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/_extra.py +0 -0
  50. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/_future.py +0 -0
  51. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/_regression.py +0 -0
  52. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/_unsupported.py +0 -0
  53. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/conftest.py +0 -0
  54. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/overrides/_ctetest.py +0 -0
  55. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test/test_suite.py +0 -0
  56. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test_local/conftest.py +0 -0
  57. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test_local/e2e/MOCK_DATA.xlsx +0 -0
  58. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test_local/e2e/test_basic.py +0 -0
  59. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test_local/test_ddl.py +0 -0
  60. {databricks_sql_connector-3.2.0 → databricks_sql_connector-3.4.0}/src/databricks/sqlalchemy/test_local/test_parsing.py +0 -0
  61. {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.2.0
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.2.0) ; python_version >= "3.8"
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
- - `personal-access-token` is the Databricks Personal Access Token for the account that will execute commands and queries
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
- - `personal-access-token` is the Databricks Personal Access Token for the account that will execute commands and queries
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.2.0"
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.2.0", python = ">=3.8" }
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 = ">=1.16.6", python = ">=3.8,<3.11" },
24
- { version = ">=1.23.4", python = ">=3.11" },
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 = "^0.981"
37
+ mypy = "^1.10.1"
38
38
  pylint = ">=2.12.0"
39
39
  black = "^22.3.0"
40
40
  pytest-dotenv = "^0.5.2"
@@ -68,7 +68,7 @@ DATETIME = DBAPITypeObject("timestamp")
68
68
  DATE = DBAPITypeObject("date")
69
69
  ROWID = DBAPITypeObject()
70
70
 
71
- __version__ = "3.2.0"
71
+ __version__ = "3.4.0"
72
72
  USER_AGENT_NAME = "PyDatabricksSqlConnector"
73
73
 
74
74
  # These two functions are pyhive legacy
@@ -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, user/pass) can be inferred
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
- username: str = None,
25
- password: str = None,
26
- access_token: str = None,
27
- auth_type: str = None,
28
- oauth_scopes: List[str] = None,
29
- oauth_client_id: str = None,
30
- oauth_redirect_port_range: List[int] = None,
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
- raise RuntimeError("No valid authentication settings!")
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):
@@ -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 # type: ignore
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, # type: ignore
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(self, **urllib3_incremented_counters: typing.Any) -> Retry:
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, # type: ignore
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: # type: ignore
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 403 response, because this can never succeed.
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): # type: ignore
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
- cafile=None,
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.certfile = cert_file
52
- self.keyfile = key_file
53
- self.context = (
54
- ssl.create_default_context(cafile=cafile)
55
- if (cafile and not ssl_context)
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
- _pool_kwargs = {"maxsize": self.max_connections}
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(
@@ -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 or username / password (internal use only)
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) # type: ignore
477
+ output.append(p)
464
478
  else:
465
- output.append(dbsql_parameter_from_primitive(value=p)) # type: ignore
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(self, presigned_url: str, headers: dict = None):
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 # type: ignore
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