dcs-sdk 1.6.1__py3-none-any.whl → 1.6.3__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.
@@ -121,13 +121,23 @@ class Dialect(BaseDialect):
121
121
  def to_string(self, s: str) -> str:
122
122
  s_temp = re.sub(r'["\[\]`]', "", s)
123
123
  col_info = self.get_column_raw_info(s_temp)
124
+
124
125
  ch_len = (col_info and col_info.character_maximum_length) or None
125
- if not ch_len:
126
- ch_len = 2500
127
- ch_len = max(ch_len, 2500)
128
- if col_info and col_info.data_type in ["nvarchar", "nchar", "ntext"]:
126
+
127
+ if ch_len is None or ch_len == 0:
128
+ ch_len = "MAX"
129
+ else:
130
+ ch_len = min(ch_len, 8000)
131
+
132
+ if col_info and col_info.data_type in ["nvarchar", "nchar"]:
129
133
  return f"CONVERT(NVARCHAR({ch_len}), {s})"
130
134
 
135
+ elif col_info and col_info.data_type == "text":
136
+ return f"CONVERT(VARCHAR(MAX), {s})"
137
+
138
+ elif col_info and col_info.data_type == "ntext":
139
+ return f"CONVERT(NVARCHAR(MAX), {s})"
140
+
131
141
  return f"CONVERT(VARCHAR({ch_len}), {s})"
132
142
 
133
143
  def type_repr(self, t) -> str:
@@ -151,7 +161,6 @@ class Dialect(BaseDialect):
151
161
  limit: Optional[int] = None,
152
162
  has_order_by: Optional[bool] = None,
153
163
  ) -> str:
154
- import re
155
164
 
156
165
  if offset:
157
166
  raise NotImplementedError("No support for OFFSET in query")
@@ -208,15 +217,7 @@ class Dialect(BaseDialect):
208
217
  return tuple(name.split("."))
209
218
 
210
219
  def normalize_uuid(self, value, coltype):
211
- s_temp = re.sub(r'["\[\]`]', "", value)
212
- col_info = self.get_column_raw_info(s_temp)
213
- ch_len = (col_info and col_info.character_maximum_length) or None
214
- if not ch_len:
215
- ch_len = 2500
216
- ch_len = max(ch_len, 2500)
217
- if isinstance(coltype, String_UUID):
218
- return f"CAST({value} AS VARCHAR({ch_len}))"
219
- return f"CAST({value} AS VARCHAR(36))"
220
+ return self.to_string(value)
220
221
 
221
222
 
222
223
  @attrs.define(frozen=False, init=False, kw_only=True)
@@ -0,0 +1,93 @@
1
+ # Copyright 2022-present, the Waterdip Labs Pvt. Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import os
16
+ import threading
17
+ import time
18
+
19
+ from loguru import logger
20
+ from redis import ConnectionPool, Redis, RedisError
21
+
22
+
23
+ class RedisBackend:
24
+ _INSTANCE = None
25
+ _lock = threading.Lock()
26
+
27
+ @classmethod
28
+ def get_instance(cls, force_recreate: bool = False):
29
+ with cls._lock:
30
+ if cls._INSTANCE is None or force_recreate:
31
+ logger.info("Creating a new RedisBackend instance (force_recreate=%s)", force_recreate)
32
+ cls._INSTANCE = cls._create_instance()
33
+ return cls._INSTANCE
34
+
35
+ @classmethod
36
+ def _create_instance(cls):
37
+ redis_url = (
38
+ os.getenv("DCS_REDIS_URL", None)
39
+ or os.getenv("DCS_RABBIT_URL", None)
40
+ or os.getenv("REDIS_URL", None)
41
+ or os.getenv("RABBIT_URL", None)
42
+ )
43
+ if not redis_url:
44
+ logger.warning("environment variable is not configured for redis")
45
+ return None
46
+ try:
47
+ pool = ConnectionPool.from_url(
48
+ redis_url,
49
+ max_connections=int(os.getenv("DCS_REDIS_MAX_CONNECTIONS", "100")),
50
+ health_check_interval=int(os.getenv("DCS_REDIS_HEALTH_CHECK_INTERVAL", "30")),
51
+ socket_connect_timeout=float(os.getenv("DCS_REDIS_SOCKET_CONNECT_TIMEOUT", "5")),
52
+ socket_timeout=float(os.getenv("DCS_REDIS_SOCKET_TIMEOUT", "5")),
53
+ socket_keepalive=True,
54
+ )
55
+ client = Redis(connection_pool=pool, decode_responses=False, retry_on_timeout=True)
56
+ # ping to ensure connection and raise early if config wrong
57
+ client.ping()
58
+ return cls(client)
59
+ except RedisError as exc:
60
+ logger.exception("Failed to create Redis client: %s", exc)
61
+ raise
62
+
63
+ def __init__(self, client: Redis):
64
+ self._client = client
65
+
66
+ @property
67
+ def client(self) -> Redis:
68
+ return self._client
69
+
70
+ def ensure_connected(self) -> bool:
71
+ try:
72
+ self._client.ping()
73
+ return True
74
+ except RedisError:
75
+ logger.warning("Redis ping failed; trying to recreate connection/pool")
76
+ try:
77
+ time.sleep(0.2)
78
+ new_instance = self.__class__._create_instance()
79
+ self.__class__._INSTANCE = new_instance
80
+ self._client = new_instance.client
81
+ self._client.ping()
82
+ logger.info("Recreated Redis client/pool successfully")
83
+ return True
84
+ except Exception as exc:
85
+ logger.exception("Failed to recreate Redis client: %s", exc)
86
+ return False
87
+
88
+ def close(self):
89
+ try:
90
+ self._client.connection_pool.disconnect()
91
+ logger.info("Closed Redis connection pool")
92
+ except Exception as e:
93
+ logger.exception("Error closing Redis connection pool: %s", e)
@@ -871,7 +871,7 @@ class HashDiffer(HashDiffer):
871
871
  level: int,
872
872
  ) -> List:
873
873
  """Download segment rows and perform in-memory diff."""
874
-
874
+ start_time = time.monotonic()
875
875
  mode_label = "[IN-MEMORY]" if self.in_memory_diff else "[STANDARD]"
876
876
  logger.info(
877
877
  ". " * level + f"{mode_label} Downloading rows for comparison: " f"{table1.min_key}..{table1.max_key}"
@@ -887,6 +887,7 @@ class HashDiffer(HashDiffer):
887
887
  logger.info(
888
888
  ". " * level + f"{mode_label} Downloaded {len(rows1)} and {len(rows2)} rows. "
889
889
  f"Total downloaded: {self.stats['rows_downloaded']}"
890
+ f"Time taken in ms: {int((time.monotonic() - start_time) * 1000)}ms"
890
891
  )
891
892
 
892
893
  # Perform in-memory diff
@@ -25,6 +25,7 @@ from typing_extensions import Self
25
25
 
26
26
  from data_diff.abcs.database_types import DbKey, DbPath, DbTime, IKey, NumericType
27
27
  from data_diff.databases.base import Database
28
+ from data_diff.databases.redis import RedisBackend
28
29
  from data_diff.queries.api import (
29
30
  SKIP,
30
31
  Code,
@@ -50,6 +51,7 @@ from data_diff.utils import (
50
51
  ArithTimestamp,
51
52
  ArithTimestampTZ,
52
53
  ArithUnicodeString,
54
+ JobCancelledError,
53
55
  Vector,
54
56
  safezip,
55
57
  split_space,
@@ -220,6 +222,7 @@ class TableSegment:
220
222
  },
221
223
  }
222
224
  )
225
+ job_id: Optional[int] = None
223
226
 
224
227
  def __attrs_post_init__(self) -> None:
225
228
  if not self.update_column and (self.min_update or self.max_update):
@@ -326,7 +329,8 @@ class TableSegment:
326
329
 
327
330
  # fetched_cols = [NormalizeAsString(this[c]) for c in self.relevant_columns]
328
331
  # select = self.make_select().select(*fetched_cols)
329
-
332
+ if self._is_cancelled():
333
+ raise JobCancelledError(self.job_id)
330
334
  select = self.make_select().select(*self._relevant_columns_repr)
331
335
  start_time = time.monotonic()
332
336
  result = self.database.query(select, List[Tuple])
@@ -366,6 +370,8 @@ class TableSegment:
366
370
  Returns:
367
371
  list: List of tuples containing the queried row data.
368
372
  """
373
+ if self._is_cancelled():
374
+ raise JobCancelledError(self.job_id)
369
375
  select = self.make_select().select(*self._relevant_columns_repr)
370
376
 
371
377
  filters = []
@@ -471,6 +477,8 @@ class TableSegment:
471
477
 
472
478
  def count(self) -> int:
473
479
  """Count how many rows are in the segment, in one pass."""
480
+ if self._is_cancelled():
481
+ raise JobCancelledError(self.job_id)
474
482
  start_time = time.monotonic()
475
483
  result = self.database.query(self.make_select().select(Count()), int)
476
484
  query_time_ms = (time.monotonic() - start_time) * 1000
@@ -480,7 +488,8 @@ class TableSegment:
480
488
 
481
489
  def count_and_checksum(self) -> Tuple[int, int]:
482
490
  """Count and checksum the rows in the segment, in one pass."""
483
-
491
+ if self._is_cancelled():
492
+ raise JobCancelledError(self.job_id)
484
493
  checked_columns = [c for c in self.relevant_columns if c not in self.ignored_columns]
485
494
  # Build transformed expressions for checksum, honoring transforms and normalization
486
495
  checksum_exprs: List[Expr] = []
@@ -552,3 +561,23 @@ class TableSegment:
552
561
  return row_count if row_count is not None else self.count()
553
562
  else:
554
563
  return row_count if row_count is not None else self.count()
564
+
565
+ def _is_cancelled(self) -> bool:
566
+ run_id = self.job_id
567
+ if not run_id:
568
+ return False
569
+ run_id = f"revoke_job:{run_id}"
570
+ try:
571
+ backend = RedisBackend.get_instance()
572
+ val = backend.client.get(run_id)
573
+ if not val:
574
+ return False
575
+ if isinstance(val, bytes):
576
+ try:
577
+ val = val.decode()
578
+ except Exception:
579
+ val = str(val)
580
+ return isinstance(val, str) and val.strip().lower() == "revoke"
581
+ except Exception:
582
+ logger.warning("Unable to query Redis for cancellation for run_id=%s", run_id)
583
+ return False
data_diff/utils.py CHANGED
@@ -1014,3 +1014,9 @@ class SybaseDriverTypes:
1014
1014
  is_ase: bool = False
1015
1015
  is_iq: bool = False
1016
1016
  is_freetds: bool = False
1017
+
1018
+
1019
+ class JobCancelledError(RuntimeError):
1020
+ def __init__(self, job_id: str):
1021
+ super().__init__(f"Job ID {job_id} has been revoked.")
1022
+ self.job_id = job_id
dcs_sdk/__version__.py CHANGED
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = "1.6.1"
15
+ __version__ = "1.6.3"
@@ -86,6 +86,7 @@ class DiffAdvancedConfig(BaseModel):
86
86
 
87
87
  class Comparison(BaseModel):
88
88
  comparison_name: str
89
+ job_id: Optional[int] = None
89
90
  source: SourceTargetConnection
90
91
  target: SourceTargetConnection
91
92
  source_columns: Optional[List[str]] = None
@@ -429,6 +430,7 @@ class DataDiffConfig:
429
430
  )
430
431
  new_comparison = {
431
432
  "comparison_name": comparison_name,
433
+ "job_id": comparison_data.get("job_id", None),
432
434
  "source": self.create_connection_config(
433
435
  source_connection,
434
436
  comparison_data,
@@ -25,6 +25,7 @@ from rich.console import Console
25
25
 
26
26
  from data_diff import TableSegment, connect, connect_to_table, diff_tables
27
27
  from data_diff.databases import Database
28
+ from data_diff.databases.redis import RedisBackend
28
29
  from dcs_sdk.sdk.config.config_loader import Comparison, SourceTargetConnection
29
30
  from dcs_sdk.sdk.rules.rules_repository import RulesRepository
30
31
  from dcs_sdk.sdk.utils.serializer import serialize_table_schema
@@ -160,6 +161,7 @@ class DBTableDiffer:
160
161
  extra_columns=tuple(columns),
161
162
  where=where,
162
163
  transform_columns=config.transform_columns,
164
+ job_id=self.config.job_id,
163
165
  )
164
166
 
165
167
  def connect_to_db(self, config: SourceTargetConnection, is_source: bool):
@@ -322,7 +324,6 @@ class DBTableDiffer:
322
324
 
323
325
  self.table1 = self.connect_to_db_table(self.config.source, is_source=True)
324
326
  self.table2 = self.connect_to_db_table(self.config.target, is_source=False)
325
-
326
327
  table_1_sample_data = []
327
328
  table_2_sample_data = []
328
329
  db1_name = (
@@ -753,6 +754,8 @@ class DBTableDiffer:
753
754
 
754
755
  safe_close(self.source_db)
755
756
  safe_close(self.target_db)
757
+ if self.config.job_id:
758
+ safe_close(RedisBackend.get_instance())
756
759
 
757
760
  def cleanup_duckdb(self, src: str, target: str):
758
761
  if src and src.endswith("duckdb"):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dcs-sdk
3
- Version: 1.6.1
3
+ Version: 1.6.3
4
4
  Summary: SDK for DataChecks
5
5
  Author: Waterdip Labs
6
6
  Author-email: hello@waterdip.ai
@@ -49,6 +49,7 @@ Requires-Dist: pydantic (>=1.10.12)
49
49
  Requires-Dist: pyodbc (>=4.0.39) ; extra == "mssql" or extra == "sybase" or extra == "all-dbs"
50
50
  Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
51
51
  Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
52
+ Requires-Dist: redis[hiredis] (>=5.2.1,<6.0.0)
52
53
  Requires-Dist: requests (>=2.32.4,<3.0.0)
53
54
  Requires-Dist: rich (>=13.8.0)
54
55
  Requires-Dist: snowflake-connector-python (>=3.17.2) ; extra == "snowflake" or extra == "all-dbs"
@@ -61,7 +62,7 @@ Requires-Dist: vertica-python (>=1.4.0) ; extra == "vertica" or extra == "all-db
61
62
  Description-Content-Type: text/markdown
62
63
 
63
64
  <h1 align="center">
64
- DCS SDK v1.6.1
65
+ DCS SDK v1.6.3
65
66
  </h1>
66
67
 
67
68
  > SDK for DataChecks
@@ -11,11 +11,12 @@ data_diff/databases/bigquery.py,sha256=PDwSkmWRW26gUl2SMOyIsiYtgrqghptYqG8_SaaiX
11
11
  data_diff/databases/clickhouse.py,sha256=5DsW8UpyYsWI8I3AlPUvHWYdWdWpqzRsvsVwgqEyaLw,7554
12
12
  data_diff/databases/databricks.py,sha256=6cdwfAspg1GIgWFlQByvFcW_Hz1mnJeI9_2kZhOP8b4,9334
13
13
  data_diff/databases/duckdb.py,sha256=7r1PwPUpp61QN_ZorsBTyn6NTB1LcBKQofYl_wovm1U,7209
14
- data_diff/databases/mssql.py,sha256=mA6myRemhad-q9yyRSt6VRj6na_i4joD0HI7IgGt8zQ,11987
14
+ data_diff/databases/mssql.py,sha256=7MgMg1pvY8kAIl7LAGgEw_lzf5FsEON6qAzzuGrr5Wc,11845
15
15
  data_diff/databases/mysql.py,sha256=LTuLTkxac16y7w5_q1nEuKy6ZefqQUBQqV_tdnzjNT4,6148
16
16
  data_diff/databases/oracle.py,sha256=GIczARg96lOlmgUpHd-hOFl8etFJmxWMy-KkA2COG74,8031
17
17
  data_diff/databases/postgresql.py,sha256=dSZBUYbGwWnvCwTxSwMSiaElmQSpB2Q3chsmgvUeqQE,10787
18
18
  data_diff/databases/presto.py,sha256=1Z8iDV2pc35Bu7DuUerFuFLbrwgSHSkBYmJ72JlZSZ8,7116
19
+ data_diff/databases/redis.py,sha256=gspPgUr7uFyZrqhIHBgYZo7-ecKNn8Wc_S8CVSQasLU,3501
19
20
  data_diff/databases/redshift.py,sha256=-gFWs3NCcevO4s6c4zV3_LYihK24fUd5BADTKahubjw,8122
20
21
  data_diff/databases/snowflake.py,sha256=7G6fvVJXOtTvXmSfWCxTslF4WohoscQoiqcmJIN684A,7910
21
22
  data_diff/databases/sybase.py,sha256=RCZu2sqY08w8CN5NY0WN6QZFJBvQ3Ezk8OtfH8enxaY,31311
@@ -24,7 +25,7 @@ data_diff/databases/vertica.py,sha256=2dSDZp6qOEvUVPldI5Tgn7Sm3dCpC3vNXJL3qb3FDv
24
25
  data_diff/diff_tables.py,sha256=AyFJF6oaam06AH4ZPI8pj63BiYojHoZTykjrdJCX2fI,20899
25
26
  data_diff/errors.py,sha256=4Yru8yOwyuDuBlTABnGCvJMSpe6-rbLJpNnVHeTTyHU,745
26
27
  data_diff/format.py,sha256=QFDjdZaBVf_N-jfKiX4ppOUdpXTPZXmv1j0pc1RiOoc,10245
27
- data_diff/hashdiff_tables.py,sha256=oElu0rFAFaRGVZpiIl546yUVYQm02J6HFjVkpuHHFIQ,43134
28
+ data_diff/hashdiff_tables.py,sha256=c-YOOkjUGUok_MJpvkwVOxfJPIQdvtJkW686kPLUYsM,43252
28
29
  data_diff/info_tree.py,sha256=yHtFSoXuu6oBafLYOYQjUSKlB-DnAAd08U9HOEAdTPI,2799
29
30
  data_diff/joindiff_tables.py,sha256=fyrEYjyh2BX1vGibwVZLYM1V6JJTOY-uGXY-KInvMkw,17612
30
31
  data_diff/lexicographic_space.py,sha256=bBoCbbH1Mla9jNOq1b5RuwjAxSVU7gWkra673tPBwXQ,8305
@@ -37,20 +38,20 @@ data_diff/queries/base.py,sha256=pT-iaII7Nlu-w-Cuq9fhoNKX7-GSxkQ3Fk8K-tMkk60,964
37
38
  data_diff/queries/extras.py,sha256=aUm-ifj3BMlz4o4bbuHtmnvHZuptYAKGS5yWTHmNpvc,1270
38
39
  data_diff/query_utils.py,sha256=R7ZfRwcvv9Zf4zWXNln4tr_OxLmDI7CPmmCahYfHxlo,2101
39
40
  data_diff/schema.py,sha256=QoYSSB3k-svLXz680uRgsI4qjii8BFKOOQvheqtgEbs,2413
40
- data_diff/table_segment.py,sha256=SdBVHLndDWMuLLM9zuUbfyTXZtNQgXarVLOmQTUWCCY,23356
41
+ data_diff/table_segment.py,sha256=pGSrTmtVt4ioDj06U47XBlJf_p5EtwO2QVXR9iunkZM,24491
41
42
  data_diff/thread_utils.py,sha256=_692ERjnWfHKaZsLdg7CNfkKiRd66y7_kpgDwzntp44,3831
42
- data_diff/utils.py,sha256=ZSgK6utdYDWvnsYgl7sJQa6J3XK3T6xSB_BGdQVeK24,33457
43
+ data_diff/utils.py,sha256=A0ZoxIngdU-uiiFp1uXcNx1XlEsLWgXr8J36my_ViSw,33627
43
44
  data_diff/version.py,sha256=Wk0ovyBlLEF2UaWLWEcVBLFElREtIxi7TU1hD3CuTFI,634
44
45
  dcs_sdk/__init__.py,sha256=RkfhRKLXEForLCs4rZkTf0qc_b0TokSggSAcKI4yfZg,610
45
46
  dcs_sdk/__main__.py,sha256=Qn8stIaQGrdLjHQ-H7xO0T-brtq5RWZoWU9QvqoarV8,683
46
- dcs_sdk/__version__.py,sha256=vxk5MKyHn8a7ytdJK0S-dtOykeVZa6e7BDQEzJFqpOw,633
47
+ dcs_sdk/__version__.py,sha256=RvVGQ76kZW2OfwJpVVj20bYUk2-PqzCTsJ5eNGQ7LTY,633
47
48
  dcs_sdk/cli/__init__.py,sha256=RkfhRKLXEForLCs4rZkTf0qc_b0TokSggSAcKI4yfZg,610
48
49
  dcs_sdk/cli/cli.py,sha256=LyrRk972OL9pTqrvBeXWBu5rUDAN17lQ1g8FdSRW_8M,4299
49
50
  dcs_sdk/sdk/__init__.py,sha256=skrZcgWWJBL6NXTUERywJ3qRJRemgpDXyW7lPg1FJk8,2107
50
51
  dcs_sdk/sdk/config/__init__.py,sha256=RkfhRKLXEForLCs4rZkTf0qc_b0TokSggSAcKI4yfZg,610
51
- dcs_sdk/sdk/config/config_loader.py,sha256=hVdGidcm6ibqJ0yt7fZcL_0kFcorBfM7vHGhUiBH9Ww,21587
52
+ dcs_sdk/sdk/config/config_loader.py,sha256=oooSTV6QjbXKpCkwpl6vcBdjABGT-h99vBbWTbIkmjc,21683
52
53
  dcs_sdk/sdk/data_diff/__init__.py,sha256=RkfhRKLXEForLCs4rZkTf0qc_b0TokSggSAcKI4yfZg,610
53
- dcs_sdk/sdk/data_diff/data_differ.py,sha256=s6Iq3pZ7SkIzE47drnEqCW1d0g9-oRuFq5h7aTGguig,36206
54
+ dcs_sdk/sdk/data_diff/data_differ.py,sha256=zxWa-mYAdfZepNuXz1h_xxFQBC4tdhBqlbZCVEfb8Y8,36378
54
55
  dcs_sdk/sdk/rules/__init__.py,sha256=_BkKcE_jfdDQI_ECdOamJaefMKEXrKpYjPpnBQXl_Xs,657
55
56
  dcs_sdk/sdk/rules/rules_mappping.py,sha256=fxakVkf7B2cVkYSO946LTim_HmMsl6lBDBqZjTTsSPI,1292
56
57
  dcs_sdk/sdk/rules/rules_repository.py,sha256=x0Rli-wdnHAmXm5526go_qC3P-eFRt-4L7fs4hNqC-g,7564
@@ -65,7 +66,7 @@ dcs_sdk/sdk/utils/similarity_score/levenshtein_distance_provider.py,sha256=puAWP
65
66
  dcs_sdk/sdk/utils/table.py,sha256=X8HxdYTWyx_oVrBWPsXlmA-xJKXXDBW9RrhlWNqA1As,18224
66
67
  dcs_sdk/sdk/utils/themes.py,sha256=Meo2Yldv4uyPpEqI7qdA28Aa6sxtwUU1dLKKm4QavjM,1403
67
68
  dcs_sdk/sdk/utils/utils.py,sha256=vF2zAvgt__Y8limicWTEWRyn41SBVJN81ZCTBRy6hQg,11907
68
- dcs_sdk-1.6.1.dist-info/METADATA,sha256=18-7i48HaRp13f6cR2GxwbYEGVy-J5PIY6dUR72R0R0,6275
69
- dcs_sdk-1.6.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
70
- dcs_sdk-1.6.1.dist-info/entry_points.txt,sha256=zQtrZL7YuaKtt6WPwihCTV1BRXnqBkaY6zUGdYJbBSg,49
71
- dcs_sdk-1.6.1.dist-info/RECORD,,
69
+ dcs_sdk-1.6.3.dist-info/METADATA,sha256=-TqGqjsYC8-WuzAqF9e_J8F4_jIjgZVOn8xPbj_Qpmg,6322
70
+ dcs_sdk-1.6.3.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
71
+ dcs_sdk-1.6.3.dist-info/entry_points.txt,sha256=zQtrZL7YuaKtt6WPwihCTV1BRXnqBkaY6zUGdYJbBSg,49
72
+ dcs_sdk-1.6.3.dist-info/RECORD,,