collate-data-diff 0.11.14__tar.gz → 0.11.16__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.
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/PKG-INFO +1 -1
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/databricks.py +68 -1
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/mssql.py +17 -4
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/pyproject.toml +1 -1
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/LICENSE +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/README.md +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/__init__.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/__main__.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/abcs/__init__.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/abcs/compiler.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/abcs/database_types.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/cloud/__init__.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/cloud/data_source.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/cloud/datafold_api.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/config.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/__init__.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/_connect.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/base.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/bigquery.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/clickhouse.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/duckdb.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/mysql.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/oracle.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/postgresql.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/presto.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/redshift.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/snowflake.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/trino.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/vertica.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/dbt.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/dbt_config_validators.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/dbt_parser.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/diff_tables.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/errors.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/format.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/hashdiff_tables.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/info_tree.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/joindiff_tables.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/lexicographic_space.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/parse_time.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/py.typed +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/queries/__init__.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/queries/api.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/queries/ast_classes.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/queries/base.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/queries/extras.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/query_utils.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/schema.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/table_segment.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/thread_utils.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/tracking.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/utils.py +0 -0
- {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/version.py +0 -0
|
@@ -37,6 +37,18 @@ def import_databricks():
|
|
|
37
37
|
return databricks
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
@import_helper(text="You can install it using 'pip install databricks-sdk'")
|
|
41
|
+
def import_databricks_sdk():
|
|
42
|
+
from databricks.sdk.core import Config, oauth_service_principal
|
|
43
|
+
|
|
44
|
+
return Config, oauth_service_principal
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
AUTH_PAT = "pat"
|
|
48
|
+
AUTH_OAUTH_M2M = "oauth-m2m"
|
|
49
|
+
AUTH_AZURE_SP_M2M = "azure-sp-m2m"
|
|
50
|
+
|
|
51
|
+
|
|
40
52
|
@attrs.define(frozen=False)
|
|
41
53
|
class Dialect(BaseDialect):
|
|
42
54
|
name = "Databricks"
|
|
@@ -133,6 +145,61 @@ class Databricks(ThreadedDatabase):
|
|
|
133
145
|
self.default_schema = kw.get("schema", "default")
|
|
134
146
|
self.catalog = kw.get("catalog", "hive_metastore")
|
|
135
147
|
|
|
148
|
+
def _require(self, key: str) -> str:
|
|
149
|
+
value = self._args.get(key)
|
|
150
|
+
if not value:
|
|
151
|
+
auth_method = self._args.get("auth_method", AUTH_PAT)
|
|
152
|
+
raise ValueError(f"{self.name}: '{key}' is required for auth_method '{auth_method}'")
|
|
153
|
+
return value
|
|
154
|
+
|
|
155
|
+
def _pat_kwargs(self) -> Dict[str, Any]:
|
|
156
|
+
return {"access_token": self._require("access_token")}
|
|
157
|
+
|
|
158
|
+
def _oauth_m2m_kwargs(self) -> Dict[str, Any]:
|
|
159
|
+
Config, oauth_service_principal = import_databricks_sdk()
|
|
160
|
+
hostname = self._args["server_hostname"]
|
|
161
|
+
client_id = self._require("databricks_client_id")
|
|
162
|
+
client_secret = self._require("databricks_client_secret")
|
|
163
|
+
|
|
164
|
+
def credentials_provider():
|
|
165
|
+
provider = oauth_service_principal(
|
|
166
|
+
Config(host=f"https://{hostname}", client_id=client_id, client_secret=client_secret)
|
|
167
|
+
)
|
|
168
|
+
if provider is None:
|
|
169
|
+
raise ConnectionError(f"{self.name}: OAuth is not available on {hostname}")
|
|
170
|
+
return provider
|
|
171
|
+
|
|
172
|
+
return {"credentials_provider": credentials_provider}
|
|
173
|
+
|
|
174
|
+
def _azure_sp_m2m_kwargs(self) -> Dict[str, Any]:
|
|
175
|
+
return {
|
|
176
|
+
"auth_type": AUTH_AZURE_SP_M2M,
|
|
177
|
+
"azure_client_id": self._require("azure_client_id"),
|
|
178
|
+
"azure_client_secret": self._require("azure_client_secret"),
|
|
179
|
+
"azure_tenant_id": self._require("azure_tenant_id"),
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
def _auth_kwargs(self) -> Dict[str, Any]:
|
|
183
|
+
"""Credential kwargs for ``databricks.sql.connect``.
|
|
184
|
+
|
|
185
|
+
Raises:
|
|
186
|
+
ValueError: on an unknown ``auth_method`` or a missing credential.
|
|
187
|
+
"""
|
|
188
|
+
# Callables cannot travel in the connection config: `Connect` caches on
|
|
189
|
+
# json.dumps of it. Providers are built here, from plain strings.
|
|
190
|
+
auth_method = self._args.get("auth_method", AUTH_PAT)
|
|
191
|
+
builder = {
|
|
192
|
+
AUTH_PAT: self._pat_kwargs,
|
|
193
|
+
AUTH_OAUTH_M2M: self._oauth_m2m_kwargs,
|
|
194
|
+
AUTH_AZURE_SP_M2M: self._azure_sp_m2m_kwargs,
|
|
195
|
+
}.get(auth_method)
|
|
196
|
+
if builder is None:
|
|
197
|
+
raise ValueError(
|
|
198
|
+
f"{self.name}: unknown auth_method '{auth_method}'. "
|
|
199
|
+
f"Expected one of {AUTH_PAT}, {AUTH_OAUTH_M2M}, {AUTH_AZURE_SP_M2M}"
|
|
200
|
+
)
|
|
201
|
+
return builder()
|
|
202
|
+
|
|
136
203
|
def create_connection(self):
|
|
137
204
|
databricks = import_databricks()
|
|
138
205
|
|
|
@@ -140,8 +207,8 @@ class Databricks(ThreadedDatabase):
|
|
|
140
207
|
return databricks.sql.connect(
|
|
141
208
|
server_hostname=self._args["server_hostname"],
|
|
142
209
|
http_path=self._args["http_path"],
|
|
143
|
-
access_token=self._args["access_token"],
|
|
144
210
|
catalog=self.catalog,
|
|
211
|
+
**self._auth_kwargs(),
|
|
145
212
|
)
|
|
146
213
|
except databricks.sql.exc.Error as e:
|
|
147
214
|
raise ConnectionError(*e.args) from e
|
|
@@ -28,6 +28,8 @@ from data_diff.abcs.database_types import (
|
|
|
28
28
|
Time,
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
+
DEFAULT_ODBC_DRIVER = "ODBC Driver 18 for SQL Server"
|
|
32
|
+
|
|
31
33
|
|
|
32
34
|
@import_helper("mssql")
|
|
33
35
|
def import_mssql():
|
|
@@ -174,12 +176,23 @@ class MsSQL(ThreadedDatabase):
|
|
|
174
176
|
def __init__(self, host, port, user, password, *, database, thread_count, **kw) -> None:
|
|
175
177
|
super().__init__(thread_count=thread_count)
|
|
176
178
|
|
|
179
|
+
# A separate keyword rather than `driver`: in a connection dict that name is
|
|
180
|
+
# already consumed by connect_with_dict as the *scheme*. Reading it off `kw`
|
|
181
|
+
# also covers the URI entry point, whose query params land there too.
|
|
182
|
+
odbc_driver = kw.pop("odbc_driver", None) or DEFAULT_ODBC_DRIVER
|
|
183
|
+
|
|
177
184
|
args = dict(server=host, port=port, database=database, user=user, password=password, **kw)
|
|
178
185
|
self._args = {k: v for k, v in args.items() if v is not None}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
#
|
|
182
|
-
|
|
186
|
+
# Only FreeTDS splits a `DOMAIN\user` login and negotiates NTLM. msodbcsql
|
|
187
|
+
# offers it as a SQL login name, which SQL Server rejects with 18456 - and a
|
|
188
|
+
# backslash is illegal in a SQL login, so such an account can only ever be a
|
|
189
|
+
# Windows one. Hard-coding the driver made domain accounts undiffable.
|
|
190
|
+
self._args["driver"] = "{%s}" % odbc_driver
|
|
191
|
+
|
|
192
|
+
# Kept as a default rather than removed: setups with self-signed certificates
|
|
193
|
+
# already depend on it, and dropping it fails them with
|
|
194
|
+
# "SSL Provider: certificate verify failed". Now overridable by the caller.
|
|
195
|
+
self._args.setdefault("TrustServerCertificate", "yes")
|
|
183
196
|
|
|
184
197
|
try:
|
|
185
198
|
self.default_database = self._args["database"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "collate-data-diff"
|
|
3
|
-
version = "0.11.
|
|
3
|
+
version = "0.11.16"
|
|
4
4
|
description = "Command-line tool and Python library to efficiently diff rows across two different databases."
|
|
5
5
|
authors = ["Collate <info@getcollate.io>", "Datafold <data-diff@datafold.com>"]
|
|
6
6
|
license = "MIT"
|
|
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
|
|
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
|
|
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
|