tentaclio-databricks 2.1.0__py3-none-any.whl

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.
@@ -0,0 +1,16 @@
1
+ """This package implements the tentaclio databricks database client"""
2
+
3
+ from importlib.metadata import PackageNotFoundError
4
+ from importlib.metadata import version as _version
5
+
6
+ from tentaclio import * # noqa
7
+
8
+ from .clients.databricks_client import DatabricksClient
9
+
10
+ try:
11
+ __version__ = _version("tentaclio-databricks")
12
+ except PackageNotFoundError:
13
+ __version__ = "0.0.0"
14
+
15
+ # Add DB registry
16
+ DB_REGISTRY.register("databricks+thrift", DatabricksClient) # type: ignore
@@ -0,0 +1,6 @@
1
+ # Helper cli to encode urls.
2
+
3
+ from tentaclio.__main__ import main
4
+
5
+ if __name__ == "__main__":
6
+ main(prog_name="python -m tentaclio-databricks")
@@ -0,0 +1,9 @@
1
+ """The client module defines stream and query based clients.
2
+
3
+ Stream clients allow to read and write from stream based sources with ease,
4
+ and in an interchangable manner.
5
+
6
+ Query based clients unify how to access databases leveraging from sqlalchemy.
7
+ """
8
+
9
+ from .databricks_client import * # noqa
@@ -0,0 +1,167 @@
1
+ """Databricks query client."""
2
+
3
+ import importlib
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
5
+
6
+ import pandas as pd
7
+ from databricks import sql
8
+ from tentaclio import URL
9
+
10
+ if TYPE_CHECKING:
11
+ from polars import DataFrame as PolarsDataFrame # pyright: ignore[reportMissingImports]
12
+ else:
13
+ PolarsDataFrame = Any
14
+
15
+
16
+ class DatabricksClientException(Exception):
17
+ """Databricks client specific exception."""
18
+
19
+
20
+ class DatabricksClient:
21
+ """Databricks client, backed by an Apache Thrift connection."""
22
+
23
+ def __init__(
24
+ self,
25
+ url: URL,
26
+ arraysize: int = 1000000,
27
+ use_arrow: bool = False,
28
+ query_annotations: Optional[Dict[str, str]] = None,
29
+ **kwargs,
30
+ ):
31
+ # This is a very common issue reported by the users
32
+ if url.query is None or "HTTPPath" not in url.query:
33
+ raise DatabricksClientException(
34
+ "Missing the HTTPPath element in the http query. \n\n"
35
+ "The url should look like: "
36
+ "databricks+thrift://workspaceurl.databricks.com/?HTTPPath=value\n"
37
+ "Check the connection details of your databricks warehouse"
38
+ )
39
+ if url.username is None or url.username == "":
40
+ raise DatabricksClientException(
41
+ "Missing the token in the url:\n\n"
42
+ "The url should look like: "
43
+ "databricks+thrift://token@workspaceurl.databricks.com/?HTTPPath=value"
44
+ )
45
+
46
+ self.server_hostname = url.hostname
47
+ self.http_path = url.query["HTTPPath"]
48
+ self.access_token = url.username
49
+ self.arraysize = arraysize
50
+ self.use_arrow = use_arrow
51
+ self.query_annotations = query_annotations or {}
52
+
53
+ def __enter__(self):
54
+ conn_kwargs = {
55
+ "server_hostname": self.server_hostname,
56
+ "http_path": self.http_path,
57
+ "access_token": self.access_token,
58
+ }
59
+
60
+ if self.use_arrow:
61
+ conn_kwargs.update(
62
+ {
63
+ "use_arrow_native_complex_types": True,
64
+ "use_arrow_native_decimals": True,
65
+ "use_arrow_native_timestamps": True,
66
+ }
67
+ )
68
+
69
+ self.conn = sql.connect(**conn_kwargs)
70
+
71
+ # Only set arraysize if not using Arrow
72
+ if self.use_arrow:
73
+ self.cursor = self.conn.cursor()
74
+ else:
75
+ self.cursor = self.conn.cursor(arraysize=self.arraysize)
76
+
77
+ return self
78
+
79
+ def __exit__(self, exc_type, exc_val, exc_tb):
80
+ self.cursor.close()
81
+ self.conn.close()
82
+
83
+ def _build_query_comment(self) -> str:
84
+ """Build a SQL comment from query annotations.
85
+
86
+ Returns:
87
+ Formatted SQL comment string, or empty string if no annotations.
88
+ """
89
+ if not self.query_annotations:
90
+ return ""
91
+
92
+ parts = []
93
+ for key, value in self.query_annotations.items():
94
+ # Escape single quotes in values
95
+ escaped_value = value.replace("'", "''")
96
+ parts.append(f"{key}='{escaped_value}'")
97
+
98
+ if parts:
99
+ return f"/* {', '.join(parts)} */\n"
100
+ return ""
101
+
102
+ def _prepend_comment(self, sql_query: str) -> str:
103
+ """Prepend query comment to SQL query if configured.
104
+
105
+ Args:
106
+ sql_query: The SQL query string.
107
+
108
+ Returns:
109
+ SQL query with prepended comment if annotations configured, else original query.
110
+ """
111
+ comment = self._build_query_comment()
112
+ if comment:
113
+ return f"{comment}{sql_query}"
114
+ return sql_query
115
+
116
+ def query(self, sql_query: str, **kwargs) -> List[Any]:
117
+ """Execute a SQL query, and return results."""
118
+ sql_query = self._prepend_comment(sql_query)
119
+ self.cursor.execute(sql_query, **kwargs)
120
+ return self.cursor.fetchall()
121
+
122
+ def execute(self, sql_query: str, **kwargs) -> None:
123
+ """Execute a raw SQL query command."""
124
+ sql_query = self._prepend_comment(sql_query)
125
+ self.cursor.execute(sql_query, **kwargs)
126
+
127
+ def get_df(self, sql_query: str, **kwargs) -> pd.DataFrame:
128
+ """Run a raw SQL query and return a data frame."""
129
+ sql_query = self._prepend_comment(sql_query)
130
+ self.cursor.execute(sql_query, **kwargs)
131
+
132
+ if self.use_arrow:
133
+ # Default to Arrow for fast data transfer
134
+ try:
135
+ arrow_table = self.cursor.fetchall_arrow()
136
+ return arrow_table.to_pandas()
137
+ except AttributeError:
138
+ data = self.cursor.fetchall()
139
+ else:
140
+ # Original row-by-row method
141
+ data = self.cursor.fetchall()
142
+
143
+ # Build DataFrame from rows (fallback path)
144
+ columns = (
145
+ [col_desc[0] for col_desc in self.cursor.description]
146
+ if self.cursor.description
147
+ else []
148
+ )
149
+ return pd.DataFrame(data, columns=columns)
150
+
151
+ def get_pl(self, sql_query: str, **kwargs) -> PolarsDataFrame:
152
+ """Run a raw SQL query and return a polars DataFrame."""
153
+
154
+ try:
155
+ pl = importlib.import_module("polars")
156
+ except ModuleNotFoundError as exc:
157
+ raise ModuleNotFoundError(
158
+ "Polars is not installed. Install it with "
159
+ "`pip install tentaclio-databricks[polars]` or `pip install polars` to use "
160
+ "the `.get_pl()` method."
161
+ ) from exc
162
+
163
+ sql_query = self._prepend_comment(sql_query)
164
+ self.cursor.execute(sql_query, **kwargs)
165
+ arrow_table = self.cursor.fetchall_arrow()
166
+
167
+ return pl.DataFrame(arrow_table)
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: tentaclio-databricks
3
+ Version: 2.1.0
4
+ Summary: A python project containing all the dependencies for schema databricks for tentaclio.
5
+ Author-email: Octopus Energy <nerds@octopus.energy>
6
+ License-Expression: MIT
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Python
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE.txt
19
+ Requires-Dist: tentaclio
20
+ Requires-Dist: databricks-sql-connector
21
+ Requires-Dist: pandas<3
22
+ Requires-Dist: pyarrow
23
+ Provides-Extra: polars
24
+ Requires-Dist: polars>=0.16; extra == "polars"
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest; extra == "dev"
27
+ Requires-Dist: pytest-cov; extra == "dev"
28
+ Requires-Dist: pytest-mock; extra == "dev"
29
+ Requires-Dist: black; extra == "dev"
30
+ Requires-Dist: flake8; extra == "dev"
31
+ Requires-Dist: Flake8-pyproject; extra == "dev"
32
+ Requires-Dist: isort>=5.11.2; extra == "dev"
33
+ Requires-Dist: mypy; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # tentaclio-databricks
37
+
38
+ A package containing all the dependencies for the `databricks+thrift` tentaclio schema .
39
+
40
+ ## Quick Start
41
+
42
+ This project comes with a `Makefile` which is ready to do basic common tasks
43
+
44
+ ```
45
+ $ make help
46
+ install Initalise the virtual env installing deps
47
+ clean Remove all the unwanted clutter
48
+ lock Lock dependencies
49
+ update Update dependencies (whole tree)
50
+ sync Install dependencies as per the lock file
51
+ lint Lint files with flake and mypy
52
+ format Run black and isort
53
+ test Run unit tests
54
+ circleci Validate circleci configuration (needs circleci cli)
55
+ ```
56
+
57
+ ## Configuring access to Databricks
58
+
59
+ Your connection url should be in the following format:
60
+
61
+ ```
62
+ databricks+thrift://<token>@<host>?HTTPPath=<http_path>
63
+ ```
64
+
65
+ Example values:
66
+
67
+ - token: dapi1213456789abc
68
+ - host: myhost.databricks.com
69
+ - http_path: /sql/1.0/endpoints/123456789
70
+
71
+ ## Query Comments
72
+
73
+ Queries can be annotated with comments for observability using the `query_annotations` parameter.
74
+
75
+ ### Example: Basic usage
76
+
77
+ ```python
78
+ import os
79
+ from tentaclio import URL
80
+ from tentaclio_databricks.clients.databricks_client import DatabricksClient
81
+
82
+ url = URL("databricks+thrift://token@host.databricks.com?HTTPPath=/sql/1.0/endpoints/123")
83
+ client = DatabricksClient(
84
+ url,
85
+ query_annotations={
86
+ "app_name": "JupyterHub",
87
+ "user": os.environ.get("USER_NAME", "unknown"),
88
+ "pipeline_id": "456"
89
+ }
90
+ )
91
+ ```
92
+
93
+ ### Result
94
+
95
+ All queries executed by the client will have prepended comments:
96
+
97
+ ```sql
98
+ /* app_name='JupyterHub', user='john', pipeline_id='456' */
99
+ SELECT * FROM table
100
+ ```
101
+
102
+ ## Arrow Usage
103
+
104
+ `DatabricksClient` enables Arrow's usage for faster data transfer and more faithful type handling.
105
+
106
+ ### Example: Default row-based fetch
107
+
108
+ ```python
109
+ from tentaclio import URL
110
+ from tentaclio_databricks.clients.databricks_client import DatabricksClient
111
+
112
+ url = URL("databricks+thrift://token@host.databricks.com?HTTPPath=/sql/1.0/endpoints/123")
113
+ with DatabricksClient(url) as client:
114
+ df = client.get_df("SELECT * FROM sample_table")
115
+ ```
116
+
117
+ When Arrow is disabled, the client falls back to `fetchall()` and uses `arraysize` for the cursor.
118
+
119
+ ### Example: Arrow enabled
120
+
121
+ ```python
122
+ from tentaclio import URL
123
+ from tentaclio_databricks.clients.databricks_client import DatabricksClient
124
+
125
+ url = URL("databricks+thrift://token@host.databricks.com?HTTPPath=/sql/1.0/endpoints/123")
126
+ with DatabricksClient(url, use_arrow=True) as client:
127
+ df = client.get_df("SELECT * FROM sample_table")
128
+ ```
129
+
130
+ When Arrow is enabled, the client configures the connection with:
131
+
132
+ - `use_arrow_native_complex_types`
133
+ - `use_arrow_native_decimals`
134
+ - `use_arrow_native_timestamps`
135
+
136
+
@@ -0,0 +1,9 @@
1
+ tentaclio_databricks/__init__.py,sha256=Te6JwNyyV3Pjfii-n63bydjLcXLFaypwkKbwwYLxSKA,472
2
+ tentaclio_databricks/__main__.py,sha256=ed3jPmp9bmfc5ekIcjPxVEAPqQ3mXYacga2oUfEJgPI,147
3
+ tentaclio_databricks/clients/__init__.py,sha256=FRSm7eaquCUtXPiaba1oxaT2Lv6Zf4gRfqhcKIwPZKU,296
4
+ tentaclio_databricks/clients/databricks_client.py,sha256=9FWtMCVTa-xCg8rzcur5rE9tyWLCD4T1RtUytuTHncI,5636
5
+ tentaclio_databricks-2.1.0.dist-info/licenses/LICENSE.txt,sha256=Aqv2h5R53YlGaQXl7XgUDMZPUxJK1HftERN2ExXJ_u4,1080
6
+ tentaclio_databricks-2.1.0.dist-info/METADATA,sha256=W0CMq9tGkwHUxTI0TdchW_lBpFLLq-KALh-v7DVE77w,4158
7
+ tentaclio_databricks-2.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
8
+ tentaclio_databricks-2.1.0.dist-info/top_level.txt,sha256=ZoGTS3HcLGAiSp9c638A_y5eZ9J4uzl91KQylxs9ptY,21
9
+ tentaclio_databricks-2.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Octopus Energy Limited.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ tentaclio_databricks