dcs-sdk 1.5.5__py3-none-any.whl → 1.5.7__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.
- data_diff/abcs/database_types.py +6 -0
- data_diff/databases/sybase.py +1 -1
- data_diff/diff_tables.py +36 -24
- data_diff/hashdiff_tables.py +66 -2
- data_diff/table_segment.py +41 -16
- data_diff/utils.py +61 -11
- dcs_sdk/__version__.py +1 -1
- {dcs_sdk-1.5.5.dist-info → dcs_sdk-1.5.7.dist-info}/METADATA +3 -3
- {dcs_sdk-1.5.5.dist-info → dcs_sdk-1.5.7.dist-info}/RECORD +11 -11
- {dcs_sdk-1.5.5.dist-info → dcs_sdk-1.5.7.dist-info}/WHEEL +1 -1
- {dcs_sdk-1.5.5.dist-info → dcs_sdk-1.5.7.dist-info}/entry_points.txt +0 -0
data_diff/abcs/database_types.py
CHANGED
|
@@ -326,6 +326,12 @@ class String_Alphanum(ColType_Alphanum, StringType):
|
|
|
326
326
|
except ValueError:
|
|
327
327
|
return False
|
|
328
328
|
|
|
329
|
+
def make_value(self, value) -> ArithAlphanumeric:
|
|
330
|
+
if isinstance(value, ArithAlphanumeric):
|
|
331
|
+
return value
|
|
332
|
+
# Coerce non-string primitives (e.g., integers) to string for alphanumeric representation
|
|
333
|
+
return ArithAlphanumeric(str(value))
|
|
334
|
+
|
|
329
335
|
|
|
330
336
|
@attrs.define(frozen=True)
|
|
331
337
|
class String_VaryingAlphanum(String_Alphanum):
|
data_diff/databases/sybase.py
CHANGED
|
@@ -475,7 +475,7 @@ class Sybase(ThreadedDatabase):
|
|
|
475
475
|
username = self._args.get("user", None)
|
|
476
476
|
password = self._args.get("password", None)
|
|
477
477
|
driver = self._args.get("driver", None)
|
|
478
|
-
max_query_timeout = 60 *
|
|
478
|
+
max_query_timeout = 60 * 5 # 5 minutes
|
|
479
479
|
|
|
480
480
|
if self.dialect.sybase_driver_type.is_freetds:
|
|
481
481
|
conn_dict = {
|
data_diff/diff_tables.py
CHANGED
|
@@ -28,7 +28,7 @@ import attrs
|
|
|
28
28
|
# logger = getLogger(__name__)
|
|
29
29
|
from loguru import logger
|
|
30
30
|
|
|
31
|
-
from data_diff.abcs.database_types import IKey
|
|
31
|
+
from data_diff.abcs.database_types import IKey, Integer, StringType
|
|
32
32
|
from data_diff.errors import DataDiffMismatchingKeyTypesError
|
|
33
33
|
from data_diff.info_tree import InfoTree, SegmentInfo
|
|
34
34
|
from data_diff.table_segment import TableSegment, create_mesh_from_points
|
|
@@ -340,13 +340,22 @@ class TableDiffer(ThreadBase, ABC):
|
|
|
340
340
|
if not isinstance(kt, IKey):
|
|
341
341
|
raise NotImplementedError(f"Cannot use a column of type {kt} as a key")
|
|
342
342
|
|
|
343
|
+
mismatched_key_types = False
|
|
343
344
|
for i, (kt1, kt2) in enumerate(safezip(key_types1, key_types2)):
|
|
344
345
|
if kt1.python_type is not kt2.python_type:
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
346
|
+
# Allow integer vs string, and string vs string variants for diffing, but mark as mismatched
|
|
347
|
+
if (isinstance(kt1, Integer) and isinstance(kt2, StringType)) or (
|
|
348
|
+
isinstance(kt2, Integer) and isinstance(kt1, StringType)
|
|
349
|
+
):
|
|
350
|
+
mismatched_key_types = True
|
|
351
|
+
elif isinstance(kt1, StringType) and isinstance(kt2, StringType):
|
|
352
|
+
mismatched_key_types = True
|
|
353
|
+
else:
|
|
354
|
+
k1 = table1.key_columns[i]
|
|
355
|
+
k2 = table2.key_columns[i]
|
|
356
|
+
raise DataDiffMismatchingKeyTypesError(
|
|
357
|
+
f"Key columns {k1} type: {kt1.python_type} and {k2} type: {kt2.python_type} can't be compared due to different types."
|
|
358
|
+
)
|
|
350
359
|
|
|
351
360
|
# Query min/max values
|
|
352
361
|
key_ranges = self._threaded_call_as_completed("query_key_range", [table1, table2])
|
|
@@ -380,24 +389,27 @@ class TableDiffer(ThreadBase, ABC):
|
|
|
380
389
|
# Overall, the max number of new regions in this 2nd pass is 3^|k| - 1
|
|
381
390
|
|
|
382
391
|
# Note: python types can be the same, but the rendering parameters (e.g. casing) can differ.
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
392
|
+
# If key types mismatched (e.g., int vs string), skip the second meshing pass to avoid
|
|
393
|
+
# attempting to sort mixed-type tuples (e.g., ArithAlphanumeric vs int).
|
|
394
|
+
if not mismatched_key_types:
|
|
395
|
+
min_key2, max_key2 = self._parse_key_range_result(key_types2, next(key_ranges))
|
|
396
|
+
|
|
397
|
+
points = [list(sorted(p)) for p in safezip(min_key1, min_key2, max_key1, max_key2)]
|
|
398
|
+
box_mesh = create_mesh_from_points(*points)
|
|
399
|
+
|
|
400
|
+
new_regions = [(p1, p2) for p1, p2 in box_mesh if p1 < p2 and not (p1 >= min_key1 and p2 <= max_key1)]
|
|
401
|
+
|
|
402
|
+
for p1, p2 in new_regions:
|
|
403
|
+
extra_table1 = table1.new_key_bounds(min_key=p1, max_key=p2, key_types=key_types1)
|
|
404
|
+
extra_table2 = table2.new_key_bounds(min_key=p1, max_key=p2, key_types=key_types2)
|
|
405
|
+
ti.submit(
|
|
406
|
+
self._bisect_and_diff_segments,
|
|
407
|
+
ti,
|
|
408
|
+
extra_table1,
|
|
409
|
+
extra_table2,
|
|
410
|
+
info_tree,
|
|
411
|
+
priority=999,
|
|
412
|
+
)
|
|
401
413
|
|
|
402
414
|
return ti
|
|
403
415
|
|
data_diff/hashdiff_tables.py
CHANGED
|
@@ -448,7 +448,39 @@ class HashDiffer(TableDiffer):
|
|
|
448
448
|
):
|
|
449
449
|
# Check if level exceeds maximum allowed recursion depth
|
|
450
450
|
if level > 15:
|
|
451
|
-
|
|
451
|
+
logger.warning(
|
|
452
|
+
". " * level
|
|
453
|
+
+ f"Maximum recursion level reached ({level}); switching to direct row comparison for segment {table1.min_key}..{table1.max_key}"
|
|
454
|
+
)
|
|
455
|
+
# Fallback: download rows and diff locally to prevent excessive recursion
|
|
456
|
+
rows1, rows2 = self._threaded_call("get_values", [table1, table2])
|
|
457
|
+
json_cols = {
|
|
458
|
+
i: colname
|
|
459
|
+
for i, colname in enumerate(table1.extra_columns)
|
|
460
|
+
if isinstance(table1._schema[colname], JSON)
|
|
461
|
+
}
|
|
462
|
+
diff = list(
|
|
463
|
+
diff_sets(
|
|
464
|
+
rows1,
|
|
465
|
+
rows2,
|
|
466
|
+
json_cols=json_cols,
|
|
467
|
+
columns1=table1.relevant_columns,
|
|
468
|
+
columns2=table2.relevant_columns,
|
|
469
|
+
key_columns1=table1.key_columns,
|
|
470
|
+
key_columns2=table2.key_columns,
|
|
471
|
+
ignored_columns1=self.ignored_columns1,
|
|
472
|
+
ignored_columns2=self.ignored_columns2,
|
|
473
|
+
diff_tracker=self._diff_tracker,
|
|
474
|
+
)
|
|
475
|
+
)
|
|
476
|
+
info_tree.info.set_diff(diff)
|
|
477
|
+
info_tree.info.rowcounts = {1: len(rows1), 2: len(rows2)}
|
|
478
|
+
self.stats["rows_downloaded"] = self.stats.get("rows_downloaded", 0) + max(len(rows1), len(rows2))
|
|
479
|
+
logger.info(
|
|
480
|
+
". " * level
|
|
481
|
+
+ f"Diff found {len(diff)} different rows, {self.stats['rows_downloaded']} total rows downloaded."
|
|
482
|
+
)
|
|
483
|
+
return diff
|
|
452
484
|
|
|
453
485
|
# Initialize diff tracker if not already done
|
|
454
486
|
self._initialize_diff_tracker(table1, table2)
|
|
@@ -553,7 +585,39 @@ class HashDiffer(TableDiffer):
|
|
|
553
585
|
):
|
|
554
586
|
# Check if level exceeds maximum allowed recursion depth
|
|
555
587
|
if level > 15:
|
|
556
|
-
|
|
588
|
+
logger.warning(
|
|
589
|
+
". " * level
|
|
590
|
+
+ f"Maximum recursion level reached ({level}); switching to direct row comparison for segment {table1.min_key}..{table1.max_key}"
|
|
591
|
+
)
|
|
592
|
+
# Fallback: download rows and diff locally to prevent excessive recursion
|
|
593
|
+
rows1, rows2 = self._threaded_call("get_values", [table1, table2])
|
|
594
|
+
json_cols = {
|
|
595
|
+
i: colname
|
|
596
|
+
for i, colname in enumerate(table1.extra_columns)
|
|
597
|
+
if isinstance(table1._schema[colname], JSON)
|
|
598
|
+
}
|
|
599
|
+
diff = list(
|
|
600
|
+
diff_sets(
|
|
601
|
+
rows1,
|
|
602
|
+
rows2,
|
|
603
|
+
json_cols=json_cols,
|
|
604
|
+
columns1=table1.relevant_columns,
|
|
605
|
+
columns2=table2.relevant_columns,
|
|
606
|
+
key_columns1=table1.key_columns,
|
|
607
|
+
key_columns2=table2.key_columns,
|
|
608
|
+
ignored_columns1=self.ignored_columns1,
|
|
609
|
+
ignored_columns2=self.ignored_columns2,
|
|
610
|
+
diff_tracker=self._diff_tracker,
|
|
611
|
+
)
|
|
612
|
+
)
|
|
613
|
+
info_tree.info.set_diff(diff)
|
|
614
|
+
info_tree.info.rowcounts = {1: len(rows1), 2: len(rows2)}
|
|
615
|
+
self.stats["rows_downloaded"] = self.stats.get("rows_downloaded", 0) + max(len(rows1), len(rows2))
|
|
616
|
+
logger.info(
|
|
617
|
+
". " * level
|
|
618
|
+
+ f"Diff found {len(diff)} different rows, {self.stats['rows_downloaded']} total rows downloaded."
|
|
619
|
+
)
|
|
620
|
+
return diff
|
|
557
621
|
|
|
558
622
|
assert table1.is_bounded and table2.is_bounded
|
|
559
623
|
|
data_diff/table_segment.py
CHANGED
|
@@ -252,6 +252,15 @@ class TableSegment:
|
|
|
252
252
|
def _where(self) -> Optional[str]:
|
|
253
253
|
return f"({self.where})" if self.where else None
|
|
254
254
|
|
|
255
|
+
def _column_expr(self, column_name: str) -> Expr:
|
|
256
|
+
"""Return expression for a column, applying configured SQL transform if present."""
|
|
257
|
+
quoted_column_name = self.database.quote(s=column_name)
|
|
258
|
+
if self.transform_columns and column_name in self.transform_columns:
|
|
259
|
+
transform_expr = self.transform_columns[column_name]
|
|
260
|
+
quoted_expr = transform_expr.format(column=quoted_column_name)
|
|
261
|
+
return Code(quoted_expr)
|
|
262
|
+
return this[column_name]
|
|
263
|
+
|
|
255
264
|
def _with_raw_schema(self, raw_schema: Dict[str, RawColumnInfo]) -> Self:
|
|
256
265
|
schema = self.database._process_table_schema(self.table_path, raw_schema, self.relevant_columns, self._where())
|
|
257
266
|
# return self.new(schema=create_schema(self.database.name, self.table_path, schema, self.case_sensitive))
|
|
@@ -277,10 +286,24 @@ class TableSegment:
|
|
|
277
286
|
def _make_key_range(self):
|
|
278
287
|
if self.min_key is not None:
|
|
279
288
|
for mn, k in safezip(self.min_key, self.key_columns):
|
|
280
|
-
|
|
289
|
+
quoted = self.database.dialect.quote(k)
|
|
290
|
+
base_expr_sql = (
|
|
291
|
+
self.transform_columns[k].format(column=quoted)
|
|
292
|
+
if self.transform_columns and k in self.transform_columns
|
|
293
|
+
else quoted
|
|
294
|
+
)
|
|
295
|
+
constant_val = self.database.dialect._constant_value(mn)
|
|
296
|
+
yield Code(f"{base_expr_sql} >= {constant_val}")
|
|
281
297
|
if self.max_key is not None:
|
|
282
298
|
for k, mx in safezip(self.key_columns, self.max_key):
|
|
283
|
-
|
|
299
|
+
quoted = self.database.dialect.quote(k)
|
|
300
|
+
base_expr_sql = (
|
|
301
|
+
self.transform_columns[k].format(column=quoted)
|
|
302
|
+
if self.transform_columns and k in self.transform_columns
|
|
303
|
+
else quoted
|
|
304
|
+
)
|
|
305
|
+
constant_val = self.database.dialect._constant_value(mx)
|
|
306
|
+
yield Code(f"{base_expr_sql} < {constant_val}")
|
|
284
307
|
|
|
285
308
|
def _make_update_range(self):
|
|
286
309
|
if self.min_update is not None:
|
|
@@ -353,9 +376,14 @@ class TableSegment:
|
|
|
353
376
|
and_exprs = []
|
|
354
377
|
for col, val in safezip(self.key_columns, key_values):
|
|
355
378
|
quoted = self.database.dialect.quote(col)
|
|
379
|
+
base_expr_sql = (
|
|
380
|
+
self.transform_columns[col].format(column=quoted)
|
|
381
|
+
if self.transform_columns and col in self.transform_columns
|
|
382
|
+
else quoted
|
|
383
|
+
)
|
|
356
384
|
schema = self._schema[col]
|
|
357
385
|
if val is None:
|
|
358
|
-
and_exprs.append(Code(
|
|
386
|
+
and_exprs.append(Code(base_expr_sql + " IS NULL"))
|
|
359
387
|
continue
|
|
360
388
|
mk_v = schema.make_value(val)
|
|
361
389
|
constant_val = self.database.dialect._constant_value(mk_v)
|
|
@@ -364,9 +392,9 @@ class TableSegment:
|
|
|
364
392
|
if hasattr(self.database.dialect, "timestamp_equality_condition") and hasattr(
|
|
365
393
|
mk_v, "_dt"
|
|
366
394
|
): # Check if it's a datetime-like object
|
|
367
|
-
where_expr = self.database.dialect.timestamp_equality_condition(
|
|
395
|
+
where_expr = self.database.dialect.timestamp_equality_condition(base_expr_sql, constant_val)
|
|
368
396
|
else:
|
|
369
|
-
where_expr = f"{
|
|
397
|
+
where_expr = f"{base_expr_sql} = {constant_val}"
|
|
370
398
|
|
|
371
399
|
and_exprs.append(Code(where_expr))
|
|
372
400
|
if and_exprs:
|
|
@@ -437,14 +465,8 @@ class TableSegment:
|
|
|
437
465
|
# return [NormalizeAsString(this[c]) for c in self.relevant_columns]
|
|
438
466
|
expressions = []
|
|
439
467
|
for c in self.relevant_columns:
|
|
440
|
-
quoted_column_name = self.database.quote(s=c)
|
|
441
468
|
schema = self._schema[c]
|
|
442
|
-
|
|
443
|
-
transform_expr = self.transform_columns[c]
|
|
444
|
-
quoted_expr = transform_expr.format(column=quoted_column_name)
|
|
445
|
-
expressions.append(NormalizeAsString(Code(quoted_expr), schema))
|
|
446
|
-
else:
|
|
447
|
-
expressions.append(NormalizeAsString(this[c], schema))
|
|
469
|
+
expressions.append(NormalizeAsString(self._column_expr(c), schema))
|
|
448
470
|
return expressions
|
|
449
471
|
|
|
450
472
|
def count(self) -> int:
|
|
@@ -460,10 +482,13 @@ class TableSegment:
|
|
|
460
482
|
"""Count and checksum the rows in the segment, in one pass."""
|
|
461
483
|
|
|
462
484
|
checked_columns = [c for c in self.relevant_columns if c not in self.ignored_columns]
|
|
463
|
-
#
|
|
485
|
+
# Build transformed expressions for checksum, honoring transforms and normalization
|
|
486
|
+
checksum_exprs: List[Expr] = []
|
|
487
|
+
for c in checked_columns:
|
|
488
|
+
schema = self._schema[c]
|
|
489
|
+
checksum_exprs.append(NormalizeAsString(self._column_expr(c), schema))
|
|
464
490
|
|
|
465
|
-
|
|
466
|
-
q = self.make_select().select(Count(), Checksum(self._relevant_columns_repr))
|
|
491
|
+
q = self.make_select().select(Count(), Checksum(checksum_exprs))
|
|
467
492
|
start_time = time.monotonic()
|
|
468
493
|
count, checksum = self.database.query(q, tuple)
|
|
469
494
|
query_time_ms = (time.monotonic() - start_time) * 1000
|
|
@@ -486,7 +511,7 @@ class TableSegment:
|
|
|
486
511
|
"""Query database for minimum and maximum key. This is used for setting the initial bounds."""
|
|
487
512
|
# Normalizes the result (needed for UUIDs) after the min/max computation
|
|
488
513
|
select = self.make_select().select(
|
|
489
|
-
ApplyFuncAndNormalizeAsString(
|
|
514
|
+
ApplyFuncAndNormalizeAsString(self._column_expr(k), f) for k in self.key_columns for f in (min_, max_)
|
|
490
515
|
)
|
|
491
516
|
result = tuple(self.database.query(select, tuple))
|
|
492
517
|
|
data_diff/utils.py
CHANGED
|
@@ -171,6 +171,10 @@ def _any_to_uuid(v: Union[str, int, UUID, "ArithUUID"]) -> UUID:
|
|
|
171
171
|
return v.uuid
|
|
172
172
|
elif isinstance(v, UUID):
|
|
173
173
|
return v
|
|
174
|
+
# Accept unicode/arithmetic strings that wrap a UUID
|
|
175
|
+
elif "ArithUnicodeString" in globals() and isinstance(v, ArithUnicodeString):
|
|
176
|
+
s = getattr(v, "_str", str(v))
|
|
177
|
+
return UUID(s)
|
|
174
178
|
elif isinstance(v, str):
|
|
175
179
|
return UUID(v)
|
|
176
180
|
elif isinstance(v, int):
|
|
@@ -625,6 +629,19 @@ def alphanums_to_numbers(s1: str, s2: str):
|
|
|
625
629
|
return n1, n2
|
|
626
630
|
|
|
627
631
|
|
|
632
|
+
def _alphanum_as_int_for_cmp(s: str) -> Optional[int]:
|
|
633
|
+
"""Interpret an alphanum string as base-10 int if it's purely numeric (optional leading minus).
|
|
634
|
+
|
|
635
|
+
Returns None if not purely numeric, in which case callers should fallback to alphanum base ordering.
|
|
636
|
+
"""
|
|
637
|
+
if re.fullmatch(r"-?\d+", s):
|
|
638
|
+
try:
|
|
639
|
+
return int(s)
|
|
640
|
+
except ValueError:
|
|
641
|
+
return None
|
|
642
|
+
return None
|
|
643
|
+
|
|
644
|
+
|
|
628
645
|
@attrs.define(frozen=True, eq=False, order=False, repr=False)
|
|
629
646
|
class ArithAlphanumeric(ArithString):
|
|
630
647
|
_str: str
|
|
@@ -678,20 +695,53 @@ class ArithAlphanumeric(ArithString):
|
|
|
678
695
|
|
|
679
696
|
return NotImplemented
|
|
680
697
|
|
|
681
|
-
def __ge__(self, other) -> bool:
|
|
682
|
-
if not isinstance(other, type(self)):
|
|
683
|
-
return NotImplemented
|
|
684
|
-
return self._str >= other._str
|
|
685
|
-
|
|
686
698
|
def __lt__(self, other) -> bool:
|
|
687
|
-
if
|
|
688
|
-
return
|
|
689
|
-
|
|
699
|
+
if isinstance(other, ArithAlphanumeric):
|
|
700
|
+
return self._str < other._str
|
|
701
|
+
if isinstance(other, int):
|
|
702
|
+
v = _alphanum_as_int_for_cmp(self._str)
|
|
703
|
+
return (v if v is not None else alphanumToNumber(self._str)) < other
|
|
704
|
+
return NotImplemented
|
|
705
|
+
|
|
706
|
+
def __le__(self, other) -> bool:
|
|
707
|
+
if isinstance(other, ArithAlphanumeric):
|
|
708
|
+
return self._str <= other._str
|
|
709
|
+
if isinstance(other, int):
|
|
710
|
+
v = _alphanum_as_int_for_cmp(self._str)
|
|
711
|
+
return (v if v is not None else alphanumToNumber(self._str)) <= other
|
|
712
|
+
return NotImplemented
|
|
713
|
+
|
|
714
|
+
def __gt__(self, other) -> bool:
|
|
715
|
+
if isinstance(other, ArithAlphanumeric):
|
|
716
|
+
return self._str > other._str
|
|
717
|
+
if isinstance(other, int):
|
|
718
|
+
v = _alphanum_as_int_for_cmp(self._str)
|
|
719
|
+
return (v if v is not None else alphanumToNumber(self._str)) > other
|
|
720
|
+
return NotImplemented
|
|
721
|
+
|
|
722
|
+
def __ge__(self, other) -> bool:
|
|
723
|
+
if isinstance(other, ArithAlphanumeric):
|
|
724
|
+
return self._str >= other._str
|
|
725
|
+
if isinstance(other, int):
|
|
726
|
+
v = _alphanum_as_int_for_cmp(self._str)
|
|
727
|
+
return (v if v is not None else alphanumToNumber(self._str)) >= other
|
|
728
|
+
return NotImplemented
|
|
690
729
|
|
|
691
730
|
def __eq__(self, other) -> bool:
|
|
692
|
-
if
|
|
693
|
-
return
|
|
694
|
-
|
|
731
|
+
if isinstance(other, ArithAlphanumeric):
|
|
732
|
+
return self._str == other._str
|
|
733
|
+
if isinstance(other, int):
|
|
734
|
+
v = _alphanum_as_int_for_cmp(self._str)
|
|
735
|
+
return (v if v is not None else alphanumToNumber(self._str)) == other
|
|
736
|
+
return NotImplemented
|
|
737
|
+
|
|
738
|
+
def __ne__(self, other) -> bool:
|
|
739
|
+
if isinstance(other, ArithAlphanumeric):
|
|
740
|
+
return self._str != other._str
|
|
741
|
+
if isinstance(other, int):
|
|
742
|
+
v = _alphanum_as_int_for_cmp(self._str)
|
|
743
|
+
return (v if v is not None else alphanumToNumber(self._str)) != other
|
|
744
|
+
return NotImplemented
|
|
695
745
|
|
|
696
746
|
def new(self, *args, **kw) -> Self:
|
|
697
747
|
return type(self)(*args, **kw, max_len=self._max_len)
|
dcs_sdk/__version__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: dcs-sdk
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.7
|
|
4
4
|
Summary: SDK for DataChecks
|
|
5
5
|
Author: Waterdip Labs
|
|
6
6
|
Author-email: hello@waterdip.ai
|
|
@@ -61,7 +61,7 @@ Requires-Dist: vertica-python (>=1.4.0) ; extra == "vertica" or extra == "all-db
|
|
|
61
61
|
Description-Content-Type: text/markdown
|
|
62
62
|
|
|
63
63
|
<h1 align="center">
|
|
64
|
-
DCS SDK v1.5.
|
|
64
|
+
DCS SDK v1.5.7
|
|
65
65
|
</h1>
|
|
66
66
|
|
|
67
67
|
> SDK for DataChecks
|
|
@@ -2,7 +2,7 @@ data_diff/__init__.py,sha256=TGNvifF-dIbitACA6PS4sORtHEcTUukcrZz2NEIsL9U,10336
|
|
|
2
2
|
data_diff/__main__.py,sha256=UvFvBKU74202bfRcIO_Wk-SU8WmnNuDK_1YVJpueMlc,16969
|
|
3
3
|
data_diff/abcs/__init__.py,sha256=RkfhRKLXEForLCs4rZkTf0qc_b0TokSggSAcKI4yfZg,610
|
|
4
4
|
data_diff/abcs/compiler.py,sha256=RuGhGlLTQuCzOJfYxa4gjcADsyvbZ9yZPuDuY6XH8Rk,785
|
|
5
|
-
data_diff/abcs/database_types.py,sha256=
|
|
5
|
+
data_diff/abcs/database_types.py,sha256=MKnWM9QBgeo-hjhgHhHWezd5i7HjozZ4ujsnzQ7ALOs,11326
|
|
6
6
|
data_diff/config.py,sha256=uRcoVVhPjVZqgQNwr18v6sPq06cGXDLemTUyitU57zA,4998
|
|
7
7
|
data_diff/databases/__init__.py,sha256=NrBm1Paj7jkHZ_hQCD-4-Q1eeDdh3v9_bz1DkPDOv9g,1680
|
|
8
8
|
data_diff/databases/_connect.py,sha256=nGsmtzDSKN8CK8zMkdcGZz0iExzkJDYw-PGebIkmQgc,11151
|
|
@@ -18,13 +18,13 @@ data_diff/databases/postgresql.py,sha256=QrvYDsMSECQiPfsl85UQxY9ivxTRXnEwjAfRjd-
|
|
|
18
18
|
data_diff/databases/presto.py,sha256=1Z8iDV2pc35Bu7DuUerFuFLbrwgSHSkBYmJ72JlZSZ8,7116
|
|
19
19
|
data_diff/databases/redshift.py,sha256=-gFWs3NCcevO4s6c4zV3_LYihK24fUd5BADTKahubjw,8122
|
|
20
20
|
data_diff/databases/snowflake.py,sha256=7G6fvVJXOtTvXmSfWCxTslF4WohoscQoiqcmJIN684A,7910
|
|
21
|
-
data_diff/databases/sybase.py,sha256=
|
|
21
|
+
data_diff/databases/sybase.py,sha256=ap_FWJk39ZM68y9c5B-luxlo-IgOXCi862HBh7MZutU,31248
|
|
22
22
|
data_diff/databases/trino.py,sha256=VIN3gMJvT4oSYuXCJ1QnngVL2gjjEYMFw87QTHgjs8c,2328
|
|
23
23
|
data_diff/databases/vertica.py,sha256=2dSDZp6qOEvUVPldI5Tgn7Sm3dCpC3vNXJL3qb3FDvQ,5529
|
|
24
|
-
data_diff/diff_tables.py,sha256=
|
|
24
|
+
data_diff/diff_tables.py,sha256=AyFJF6oaam06AH4ZPI8pj63BiYojHoZTykjrdJCX2fI,20899
|
|
25
25
|
data_diff/errors.py,sha256=4Yru8yOwyuDuBlTABnGCvJMSpe6-rbLJpNnVHeTTyHU,745
|
|
26
26
|
data_diff/format.py,sha256=QFDjdZaBVf_N-jfKiX4ppOUdpXTPZXmv1j0pc1RiOoc,10245
|
|
27
|
-
data_diff/hashdiff_tables.py,sha256=
|
|
27
|
+
data_diff/hashdiff_tables.py,sha256=EBhEAZz-2a-qNs3OlFArInAV1KszBdZPotZmX7Z_lwo,31315
|
|
28
28
|
data_diff/info_tree.py,sha256=yHtFSoXuu6oBafLYOYQjUSKlB-DnAAd08U9HOEAdTPI,2799
|
|
29
29
|
data_diff/joindiff_tables.py,sha256=fyrEYjyh2BX1vGibwVZLYM1V6JJTOY-uGXY-KInvMkw,17612
|
|
30
30
|
data_diff/lexicographic_space.py,sha256=bBoCbbH1Mla9jNOq1b5RuwjAxSVU7gWkra673tPBwXQ,8305
|
|
@@ -37,13 +37,13 @@ data_diff/queries/base.py,sha256=pT-iaII7Nlu-w-Cuq9fhoNKX7-GSxkQ3Fk8K-tMkk60,964
|
|
|
37
37
|
data_diff/queries/extras.py,sha256=aUm-ifj3BMlz4o4bbuHtmnvHZuptYAKGS5yWTHmNpvc,1270
|
|
38
38
|
data_diff/query_utils.py,sha256=R7ZfRwcvv9Zf4zWXNln4tr_OxLmDI7CPmmCahYfHxlo,2101
|
|
39
39
|
data_diff/schema.py,sha256=QoYSSB3k-svLXz680uRgsI4qjii8BFKOOQvheqtgEbs,2413
|
|
40
|
-
data_diff/table_segment.py,sha256=
|
|
40
|
+
data_diff/table_segment.py,sha256=SdBVHLndDWMuLLM9zuUbfyTXZtNQgXarVLOmQTUWCCY,23356
|
|
41
41
|
data_diff/thread_utils.py,sha256=_692ERjnWfHKaZsLdg7CNfkKiRd66y7_kpgDwzntp44,3831
|
|
42
|
-
data_diff/utils.py,sha256=
|
|
42
|
+
data_diff/utils.py,sha256=ZSgK6utdYDWvnsYgl7sJQa6J3XK3T6xSB_BGdQVeK24,33457
|
|
43
43
|
data_diff/version.py,sha256=Wk0ovyBlLEF2UaWLWEcVBLFElREtIxi7TU1hD3CuTFI,634
|
|
44
44
|
dcs_sdk/__init__.py,sha256=RkfhRKLXEForLCs4rZkTf0qc_b0TokSggSAcKI4yfZg,610
|
|
45
45
|
dcs_sdk/__main__.py,sha256=Qn8stIaQGrdLjHQ-H7xO0T-brtq5RWZoWU9QvqoarV8,683
|
|
46
|
-
dcs_sdk/__version__.py,sha256=
|
|
46
|
+
dcs_sdk/__version__.py,sha256=xvnvu6wSe7otkNeAkcLgPV0ELqT7G6RGaYllHIVN2Ts,633
|
|
47
47
|
dcs_sdk/cli/__init__.py,sha256=RkfhRKLXEForLCs4rZkTf0qc_b0TokSggSAcKI4yfZg,610
|
|
48
48
|
dcs_sdk/cli/cli.py,sha256=LyrRk972OL9pTqrvBeXWBu5rUDAN17lQ1g8FdSRW_8M,4299
|
|
49
49
|
dcs_sdk/sdk/__init__.py,sha256=skrZcgWWJBL6NXTUERywJ3qRJRemgpDXyW7lPg1FJk8,2107
|
|
@@ -65,7 +65,7 @@ dcs_sdk/sdk/utils/similarity_score/levenshtein_distance_provider.py,sha256=puAWP
|
|
|
65
65
|
dcs_sdk/sdk/utils/table.py,sha256=X8HxdYTWyx_oVrBWPsXlmA-xJKXXDBW9RrhlWNqA1As,18224
|
|
66
66
|
dcs_sdk/sdk/utils/themes.py,sha256=Meo2Yldv4uyPpEqI7qdA28Aa6sxtwUU1dLKKm4QavjM,1403
|
|
67
67
|
dcs_sdk/sdk/utils/utils.py,sha256=vF2zAvgt__Y8limicWTEWRyn41SBVJN81ZCTBRy6hQg,11907
|
|
68
|
-
dcs_sdk-1.5.
|
|
69
|
-
dcs_sdk-1.5.
|
|
70
|
-
dcs_sdk-1.5.
|
|
71
|
-
dcs_sdk-1.5.
|
|
68
|
+
dcs_sdk-1.5.7.dist-info/METADATA,sha256=eQ1wuGbpB6gmbCWfC5yrPLWWCJXRZYASpWt4IaBl97Q,6275
|
|
69
|
+
dcs_sdk-1.5.7.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
70
|
+
dcs_sdk-1.5.7.dist-info/entry_points.txt,sha256=zQtrZL7YuaKtt6WPwihCTV1BRXnqBkaY6zUGdYJbBSg,49
|
|
71
|
+
dcs_sdk-1.5.7.dist-info/RECORD,,
|
|
File without changes
|