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.
Files changed (53) hide show
  1. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/PKG-INFO +1 -1
  2. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/databricks.py +68 -1
  3. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/mssql.py +17 -4
  4. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/pyproject.toml +1 -1
  5. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/LICENSE +0 -0
  6. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/README.md +0 -0
  7. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/__init__.py +0 -0
  8. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/__main__.py +0 -0
  9. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/abcs/__init__.py +0 -0
  10. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/abcs/compiler.py +0 -0
  11. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/abcs/database_types.py +0 -0
  12. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/cloud/__init__.py +0 -0
  13. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/cloud/data_source.py +0 -0
  14. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/cloud/datafold_api.py +0 -0
  15. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/config.py +0 -0
  16. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/__init__.py +0 -0
  17. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/_connect.py +0 -0
  18. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/base.py +0 -0
  19. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/bigquery.py +0 -0
  20. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/clickhouse.py +0 -0
  21. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/duckdb.py +0 -0
  22. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/mysql.py +0 -0
  23. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/oracle.py +0 -0
  24. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/postgresql.py +0 -0
  25. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/presto.py +0 -0
  26. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/redshift.py +0 -0
  27. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/snowflake.py +0 -0
  28. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/trino.py +0 -0
  29. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/databases/vertica.py +0 -0
  30. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/dbt.py +0 -0
  31. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/dbt_config_validators.py +0 -0
  32. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/dbt_parser.py +0 -0
  33. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/diff_tables.py +0 -0
  34. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/errors.py +0 -0
  35. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/format.py +0 -0
  36. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/hashdiff_tables.py +0 -0
  37. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/info_tree.py +0 -0
  38. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/joindiff_tables.py +0 -0
  39. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/lexicographic_space.py +0 -0
  40. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/parse_time.py +0 -0
  41. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/py.typed +0 -0
  42. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/queries/__init__.py +0 -0
  43. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/queries/api.py +0 -0
  44. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/queries/ast_classes.py +0 -0
  45. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/queries/base.py +0 -0
  46. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/queries/extras.py +0 -0
  47. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/query_utils.py +0 -0
  48. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/schema.py +0 -0
  49. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/table_segment.py +0 -0
  50. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/thread_utils.py +0 -0
  51. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/tracking.py +0 -0
  52. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/utils.py +0 -0
  53. {collate_data_diff-0.11.14 → collate_data_diff-0.11.16}/data_diff/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: collate-data-diff
3
- Version: 0.11.14
3
+ Version: 0.11.16
4
4
  Summary: Command-line tool and Python library to efficiently diff rows across two different databases.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -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
- self._args["driver"] = "{ODBC Driver 18 for SQL Server}"
180
-
181
- # TODO temp dev debug
182
- self._args["TrustServerCertificate"] = "yes"
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.14"
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"