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
@@ -0,0 +1,74 @@
1
+ import re
2
+ from datetime import datetime, timedelta
3
+ from difflib import SequenceMatcher
4
+
5
+
6
+ class ParseError(ValueError):
7
+ pass
8
+
9
+
10
+ TIME_UNITS = dict(
11
+ seconds="seconds",
12
+ minutes="minutes",
13
+ hours="hours",
14
+ days="days",
15
+ weeks="weeks",
16
+ months="months",
17
+ years="years",
18
+ # Shortcuts
19
+ s="seconds",
20
+ min="minutes",
21
+ h="hours",
22
+ d="days",
23
+ w="weeks",
24
+ mon="months",
25
+ y="years",
26
+ )
27
+
28
+ EXTRAPOLATED = {"months": (30, "days"), "years": (365, "days")}
29
+ assert set(EXTRAPOLATED) <= set(TIME_UNITS)
30
+
31
+ TIME_RE = re.compile(r"(\d+)([a-z]+)")
32
+
33
+ UNITS_STR = ", ".join(sorted(TIME_UNITS.keys()))
34
+
35
+
36
+ def string_similarity(a, b) -> SequenceMatcher:
37
+ return SequenceMatcher(None, a, b).ratio()
38
+
39
+
40
+ def parse_time_atom(count, unit):
41
+ count = int(count)
42
+ try:
43
+ unit = TIME_UNITS[unit]
44
+ except KeyError:
45
+ most_similar = max(TIME_UNITS, key=lambda k: string_similarity(k, unit))
46
+ raise ParseError(
47
+ f"'{unit}' is not a recognized time unit. Did you mean '{most_similar}'?" f"\nSupported units: {UNITS_STR}"
48
+ )
49
+
50
+ if unit in EXTRAPOLATED:
51
+ mul, unit = EXTRAPOLATED[unit]
52
+ count *= mul
53
+ return count, unit
54
+
55
+
56
+ def parse_time_delta(t: str) -> timedelta:
57
+ time_dict = {}
58
+ while t:
59
+ m = TIME_RE.match(t)
60
+ if not m:
61
+ raise ParseError(f"Cannot parse '{t}': Not a recognized time delta")
62
+ count, unit = parse_time_atom(*m.groups())
63
+ if unit in time_dict:
64
+ raise ParseError(f"Time unit {unit} specified more than once")
65
+ time_dict[unit] = count
66
+ t = t[m.end() :]
67
+
68
+ if not time_dict:
69
+ raise ParseError("No time difference specified")
70
+ return timedelta(**time_dict)
71
+
72
+
73
+ def parse_time_before(time: datetime, delta: str) -> datetime:
74
+ return time - parse_time_delta(delta)
data_diff/py.typed ADDED
File without changes
File without changes
@@ -0,0 +1,200 @@
1
+ from data_diff.utils import CaseAwareMapping, CaseSensitiveDict
2
+ from data_diff.queries.ast_classes import *
3
+ from data_diff.queries.base import args_as_tuple
4
+
5
+
6
+ this = This()
7
+
8
+
9
+ def join(*tables: ITable) -> Join:
10
+ """Inner-join a sequence of table expressions"
11
+
12
+ When joining, it's recommended to use explicit tables names, instead of `this`, in order to avoid potential name collisions.
13
+
14
+ Example:
15
+ ::
16
+
17
+ person = table('person')
18
+ city = table('city')
19
+
20
+ name_and_city = (
21
+ join(person, city)
22
+ .on(person['city_id'] == city['id'])
23
+ .select(person['id'], city['name'])
24
+ )
25
+ """
26
+ return Join(tables)
27
+
28
+
29
+ def leftjoin(*tables: ITable) -> Join:
30
+ """Left-joins a sequence of table expressions.
31
+
32
+ See Also: ``join()``
33
+ """
34
+ return Join(tables, "LEFT")
35
+
36
+
37
+ def rightjoin(*tables: ITable) -> Join:
38
+ """Right-joins a sequence of table expressions.
39
+
40
+ See Also: ``join()``
41
+ """
42
+ return Join(tables, "RIGHT")
43
+
44
+
45
+ def outerjoin(*tables: ITable) -> Join:
46
+ """Outer-joins a sequence of table expressions.
47
+
48
+ See Also: ``join()``
49
+ """
50
+ return Join(tables, "FULL OUTER")
51
+
52
+
53
+ def cte(expr: Expr, *, name: Optional[str] = None, params: Sequence[str] = None) -> Cte:
54
+ """Define a CTE"""
55
+ return Cte(expr, name, params)
56
+
57
+
58
+ def table(*path: str, schema: Union[dict, CaseAwareMapping] = None) -> TablePath:
59
+ """Defines a table with a path (dotted name), and optionally a schema.
60
+
61
+ Parameters:
62
+ path: A list of names that make up the path to the table.
63
+ schema: a dictionary of {name: type}
64
+ """
65
+ if len(path) == 1 and isinstance(path[0], tuple):
66
+ (path,) = path
67
+ if not all(isinstance(i, str) for i in path):
68
+ raise TypeError(f"All elements of table path must be of type 'str'. Got: {path}")
69
+ if schema and not isinstance(schema, CaseAwareMapping):
70
+ assert isinstance(schema, dict)
71
+ schema = CaseSensitiveDict(schema)
72
+ return TablePath(path, schema)
73
+
74
+
75
+ def or_(*exprs: Expr) -> Union[BinBoolOp, Expr]:
76
+ """Apply OR between a sequence of boolean expressions"""
77
+ exprs = args_as_tuple(exprs)
78
+ if len(exprs) == 1:
79
+ return exprs[0]
80
+ return BinBoolOp("OR", exprs)
81
+
82
+
83
+ def and_(*exprs: Expr) -> Union[BinBoolOp, Expr]:
84
+ """Apply AND between a sequence of boolean expressions"""
85
+ exprs = args_as_tuple(exprs)
86
+ if len(exprs) == 1:
87
+ return exprs[0]
88
+ return BinBoolOp("AND", exprs)
89
+
90
+
91
+ def sum_(expr: Expr) -> Func:
92
+ """Call SUM(expr)"""
93
+ return Func("sum", [expr])
94
+
95
+
96
+ def avg(expr: Expr) -> Func:
97
+ """Call AVG(expr)"""
98
+ return Func("avg", [expr])
99
+
100
+
101
+ def min_(expr: Expr) -> Func:
102
+ """Call MIN(expr)"""
103
+ return Func("min", [expr])
104
+
105
+
106
+ def max_(expr: Expr) -> Func:
107
+ """Call MAX(expr)"""
108
+ return Func("max", [expr])
109
+
110
+
111
+ def exists(expr: Expr) -> Func:
112
+ """Call EXISTS(expr)"""
113
+ return Func("exists", [expr])
114
+
115
+
116
+ def if_(cond: Expr, then: Expr, else_: Optional[Expr] = None) -> CaseWhen:
117
+ """Conditional expression, shortcut to when-then-else.
118
+
119
+ Example:
120
+ ::
121
+
122
+ # SELECT CASE WHEN b THEN c ELSE d END FROM foo
123
+ table('foo').select(if_(b, c, d))
124
+ """
125
+ return when(cond).then(then).else_(else_)
126
+
127
+
128
+ def when(*when_exprs: Expr) -> QB_When:
129
+ """Start a when-then expression
130
+
131
+ Example:
132
+ ::
133
+
134
+ # SELECT CASE
135
+ # WHEN (type = 'text') THEN text
136
+ # WHEN (type = 'number') THEN number
137
+ # ELSE 'unknown type' END
138
+ # FROM foo
139
+ rows = table('foo').select(
140
+ when(this.type == 'text').then(this.text)
141
+ .when(this.type == 'number').then(this.number)
142
+ .else_('unknown type')
143
+ )
144
+ """
145
+ return CaseWhen([]).when(*when_exprs)
146
+
147
+
148
+ def coalesce(*exprs) -> Func:
149
+ "Returns a call to COALESCE"
150
+ exprs = args_as_tuple(exprs)
151
+ return Func("COALESCE", exprs)
152
+
153
+
154
+ def insert_rows_in_batches(db, tbl: TablePath, rows, *, columns=None, batch_size=1024 * 8) -> None:
155
+ assert batch_size > 0
156
+ rows = list(rows)
157
+
158
+ while rows:
159
+ batch, rows = rows[:batch_size], rows[batch_size:]
160
+ db.query(tbl.insert_rows(batch, columns=columns))
161
+
162
+
163
+ def current_timestamp() -> CurrentTimestamp:
164
+ """Returns CURRENT_TIMESTAMP() or NOW()"""
165
+ return CurrentTimestamp()
166
+
167
+
168
+ def code(code: str, **kw: Dict[str, Expr]) -> Code:
169
+ """Inline raw SQL code.
170
+
171
+ It allows users to use features and syntax that Sqeleton doesn't yet support.
172
+
173
+ It's the user's responsibility to make sure the contents of the string given to `code()` are correct and safe for execution.
174
+
175
+ Strings given to `code()` are actually templates, and can embed query expressions given as arguments:
176
+
177
+ Parameters:
178
+ code: template string of SQL code. Templated variables are signified with '{var}'.
179
+ kw: optional parameters for SQL template.
180
+
181
+ Examples:
182
+ ::
183
+
184
+ # SELECT b, <x> FROM tmp WHERE <y>
185
+ table('tmp').select(this.b, code("<x>")).where(code("<y>"))
186
+
187
+ ::
188
+
189
+ def tablesample(tbl, size):
190
+ return code("SELECT * FROM {tbl} TABLESAMPLE BERNOULLI ({size})", tbl=tbl, size=size)
191
+
192
+ nonzero = table('points').where(this.x > 0, this.y > 0)
193
+
194
+ # SELECT * FROM points WHERE (x > 0) AND (y > 0) TABLESAMPLE BERNOULLI (10)
195
+ sample_expr = tablesample(nonzero)
196
+ """
197
+ return Code(code, kw)
198
+
199
+
200
+ commit = Commit()