collate-data-diff 0.11.2__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.
Files changed (54) hide show
  1. collate_data_diff-0.11.2.dist-info/LICENSE +18 -0
  2. collate_data_diff-0.11.2.dist-info/METADATA +77 -0
  3. collate_data_diff-0.11.2.dist-info/RECORD +54 -0
  4. collate_data_diff-0.11.2.dist-info/WHEEL +4 -0
  5. collate_data_diff-0.11.2.dist-info/entry_points.txt +3 -0
  6. data_diff/__init__.py +180 -0
  7. data_diff/__main__.py +618 -0
  8. data_diff/abcs/__init__.py +0 -0
  9. data_diff/abcs/compiler.py +13 -0
  10. data_diff/abcs/database_types.py +308 -0
  11. data_diff/cloud/__init__.py +2 -0
  12. data_diff/cloud/data_source.py +318 -0
  13. data_diff/cloud/datafold_api.py +304 -0
  14. data_diff/config.py +127 -0
  15. data_diff/databases/__init__.py +17 -0
  16. data_diff/databases/_connect.py +306 -0
  17. data_diff/databases/base.py +1291 -0
  18. data_diff/databases/bigquery.py +315 -0
  19. data_diff/databases/clickhouse.py +203 -0
  20. data_diff/databases/databricks.py +248 -0
  21. data_diff/databases/duckdb.py +192 -0
  22. data_diff/databases/mssql.py +229 -0
  23. data_diff/databases/mysql.py +159 -0
  24. data_diff/databases/oracle.py +195 -0
  25. data_diff/databases/postgresql.py +258 -0
  26. data_diff/databases/presto.py +197 -0
  27. data_diff/databases/redshift.py +217 -0
  28. data_diff/databases/snowflake.py +207 -0
  29. data_diff/databases/trino.py +50 -0
  30. data_diff/databases/vertica.py +160 -0
  31. data_diff/dbt.py +604 -0
  32. data_diff/dbt_config_validators.py +65 -0
  33. data_diff/dbt_parser.py +523 -0
  34. data_diff/diff_tables.py +416 -0
  35. data_diff/errors.py +74 -0
  36. data_diff/format.py +359 -0
  37. data_diff/hashdiff_tables.py +264 -0
  38. data_diff/info_tree.py +62 -0
  39. data_diff/joindiff_tables.py +399 -0
  40. data_diff/lexicographic_space.py +240 -0
  41. data_diff/parse_time.py +74 -0
  42. data_diff/py.typed +0 -0
  43. data_diff/queries/__init__.py +0 -0
  44. data_diff/queries/api.py +200 -0
  45. data_diff/queries/ast_classes.py +798 -0
  46. data_diff/queries/base.py +24 -0
  47. data_diff/queries/extras.py +29 -0
  48. data_diff/query_utils.py +56 -0
  49. data_diff/schema.py +52 -0
  50. data_diff/table_segment.py +286 -0
  51. data_diff/thread_utils.py +98 -0
  52. data_diff/tracking.py +237 -0
  53. data_diff/utils.py +625 -0
  54. data_diff/version.py +1 -0
data_diff/tracking.py ADDED
@@ -0,0 +1,237 @@
1
+ #
2
+ # This module contains all the functionality related to the anonymous tracking of data-diff use.
3
+ #
4
+
5
+ import logging
6
+ import os
7
+ import json
8
+ import platform
9
+ from time import time
10
+ from typing import Any, Dict, Optional
11
+ import urllib.request
12
+ from uuid import uuid4
13
+ import toml
14
+ from rich import get_console
15
+
16
+ from data_diff.version import __version__
17
+
18
+ TRACK_URL = "https://hosted.rudderlabs.com/v1/track"
19
+ START_EVENT = "os_diff_run_start"
20
+ END_EVENT = "os_diff_run_end"
21
+ TOKEN = "2HgtM4Hcq9BmeiCqNYhz7O9tkjM"
22
+ TIMEOUT = 8
23
+
24
+ DEFAULT_PROFILE = os.path.expanduser("~/.datadiff.toml")
25
+
26
+
27
+ def _load_profile():
28
+ try:
29
+ with open(DEFAULT_PROFILE) as f:
30
+ conf = toml.load(f)
31
+ except FileNotFoundError:
32
+ conf = {}
33
+
34
+ if "anonymous_id" not in conf:
35
+ conf["anonymous_id"] = str(uuid4())
36
+ with open(DEFAULT_PROFILE, "w") as f:
37
+ toml.dump(conf, f)
38
+ return conf
39
+
40
+
41
+ def bool_ask_for_email() -> bool:
42
+ """
43
+ Checks the .datadiff.toml profile file for the asked_for_email key
44
+
45
+ Returns False immediately if --no-tracking or not in an interactive terminal
46
+
47
+ If found, return False (already asked for email)
48
+
49
+ If not found, add a key "asked_for_email", and return True (we should ask for email)
50
+
51
+ Returns:
52
+ bool: decision on whether to prompt the user for their email
53
+ """
54
+ console = get_console()
55
+ if g_tracking_enabled and console.is_interactive:
56
+ profile = _load_profile()
57
+
58
+ if "asked_for_email" not in profile:
59
+ profile["asked_for_email"] = ""
60
+ with open(DEFAULT_PROFILE, "w") as conf:
61
+ toml.dump(profile, conf)
62
+ return True
63
+ return False
64
+
65
+
66
+ def bool_notify_about_extension() -> bool:
67
+ profile = _load_profile()
68
+ console = get_console()
69
+ if "notified_about_extension" not in profile and console.is_interactive:
70
+ profile["notified_about_extension"] = ""
71
+ with open(DEFAULT_PROFILE, "w") as conf:
72
+ toml.dump(profile, conf)
73
+ return True
74
+ return False
75
+
76
+
77
+ g_tracking_enabled = True
78
+ g_anonymous_id = None
79
+
80
+ entrypoint_name = "Python API"
81
+
82
+
83
+ def disable_tracking() -> None:
84
+ global g_tracking_enabled
85
+ g_tracking_enabled = False
86
+
87
+
88
+ def is_tracking_enabled() -> bool:
89
+ return g_tracking_enabled
90
+
91
+
92
+ def set_entrypoint_name(s) -> None:
93
+ global entrypoint_name
94
+ entrypoint_name = s
95
+
96
+
97
+ dbt_user_id = None
98
+ dbt_version = None
99
+ dbt_project_id = None
100
+
101
+
102
+ def set_dbt_user_id(s) -> None:
103
+ global dbt_user_id
104
+ dbt_user_id = s
105
+
106
+
107
+ def set_dbt_version(s) -> None:
108
+ global dbt_version
109
+ dbt_version = s
110
+
111
+
112
+ def set_dbt_project_id(s) -> None:
113
+ global dbt_project_id
114
+ dbt_project_id = s
115
+
116
+
117
+ def get_anonymous_id() -> str:
118
+ global g_anonymous_id
119
+ if g_anonymous_id is None:
120
+ profile = _load_profile()
121
+ g_anonymous_id = profile["anonymous_id"]
122
+ return g_anonymous_id
123
+
124
+
125
+ def create_start_event_json(diff_options: Dict[str, Any]):
126
+ return {
127
+ "event": "os_diff_run_start",
128
+ "properties": {
129
+ "distinct_id": get_anonymous_id(),
130
+ "token": TOKEN,
131
+ "time": time(),
132
+ "os_type": os.name,
133
+ "os_version": platform.platform(),
134
+ "python_version": f"{platform.python_version()}/{platform.python_implementation()}",
135
+ "diff_options": diff_options,
136
+ "data_diff_version:": __version__,
137
+ "entrypoint_name": entrypoint_name,
138
+ "dbt_user_id": dbt_user_id,
139
+ "dbt_version": dbt_version,
140
+ "dbt_project_id": dbt_project_id,
141
+ },
142
+ }
143
+
144
+
145
+ def create_end_event_json(
146
+ is_success: bool,
147
+ runtime_seconds: float,
148
+ data_source_1_type: str,
149
+ data_source_2_type: str,
150
+ table1_count: int,
151
+ table2_count: int,
152
+ diff_count: int,
153
+ error: Optional[str],
154
+ diff_id: Optional[int] = None,
155
+ is_cloud: bool = False,
156
+ org_id: Optional[int] = None,
157
+ org_name: Optional[str] = None,
158
+ user_id: Optional[int] = None,
159
+ ):
160
+ return {
161
+ "event": "os_diff_run_end",
162
+ "properties": {
163
+ "distinct_id": get_anonymous_id(),
164
+ "token": TOKEN,
165
+ "time": time(),
166
+ "is_success": is_success,
167
+ "runtime_seconds": runtime_seconds,
168
+ "data_source_1_type": data_source_1_type,
169
+ "data_source_2_type": data_source_2_type,
170
+ "table_1_rows_cnt": table1_count,
171
+ "table_2_rows_cnt": table2_count,
172
+ "diff_rows_cnt": diff_count,
173
+ "error_message": error,
174
+ "data_diff_version:": __version__,
175
+ "entrypoint_name": entrypoint_name,
176
+ "is_cloud": is_cloud,
177
+ "diff_id": diff_id,
178
+ "dbt_user_id": dbt_user_id,
179
+ "dbt_version": dbt_version,
180
+ "dbt_project_id": dbt_project_id,
181
+ "org_id": org_id,
182
+ "org_name": org_name,
183
+ "user_id": user_id,
184
+ },
185
+ }
186
+
187
+
188
+ def create_email_signup_event_json(email: str) -> Dict[str, Any]:
189
+ return {
190
+ "event": "os_diff_email_opt_in",
191
+ "properties": {
192
+ "distinct_id": get_anonymous_id(),
193
+ "token": TOKEN,
194
+ "time": time(),
195
+ "data_diff_version:": __version__,
196
+ "entrypoint_name": entrypoint_name,
197
+ "email": email,
198
+ "dbt_user_id": dbt_user_id,
199
+ "dbt_project_id": dbt_project_id,
200
+ },
201
+ }
202
+
203
+
204
+ def convert_sets_to_lists(obj):
205
+ """
206
+ Recursively convert sets in the given object to lists.
207
+ """
208
+ if isinstance(obj, set):
209
+ return list(obj)
210
+ elif isinstance(obj, dict):
211
+ return {k: convert_sets_to_lists(v) for k, v in obj.items()}
212
+ elif isinstance(obj, list):
213
+ return [convert_sets_to_lists(elem) for elem in obj]
214
+ else:
215
+ return obj
216
+
217
+
218
+ def send_event_json(event_json) -> None:
219
+ if not g_tracking_enabled:
220
+ raise RuntimeError("Won't send; tracking is disabled!")
221
+
222
+ # Convert sets to lists in event_json
223
+ event_json = convert_sets_to_lists(event_json)
224
+
225
+ headers = {
226
+ "Content-Type": "application/json",
227
+ "Authorization": "Basic MkhndE00SGNxOUJtZWlDcU5ZaHo3Tzl0a2pNOg==",
228
+ }
229
+ data = json.dumps(event_json).encode()
230
+ try:
231
+ req = urllib.request.Request(TRACK_URL, data=data, headers=headers)
232
+ with urllib.request.urlopen(req, timeout=TIMEOUT) as f:
233
+ res = f.read()
234
+ if f.code != 200:
235
+ raise RuntimeError(res)
236
+ except Exception as e:
237
+ logging.debug(f"Failed to post to Rudderstack: {e}")