castor-extractor 0.21.7__py3-none-any.whl → 0.21.9__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.

Potentially problematic release.


This version of castor-extractor might be problematic. Click here for more details.

CHANGELOG.md CHANGED
@@ -1,6 +1,10 @@
1
1
 
2
2
  # Changelog
3
3
 
4
+ ## 0.21.9 - 2024-12-04
5
+
6
+ * Tableau: fix handling of timeout retry
7
+
4
8
  ## 0.21.8 - 2024-11-26
5
9
 
6
10
  * Redshift: improve deduplication of columns
@@ -19,6 +19,7 @@ HttpMethod = Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"]
19
19
 
20
20
  DEFAULT_TIMEOUT = 60
21
21
  RETRY_ON_EXPIRED_TOKEN = 1
22
+ RETRY_ON_GATEWAY_TIMEOUT = 3
22
23
 
23
24
 
24
25
  def _generate_payloads(
@@ -102,6 +103,10 @@ class APIClient:
102
103
  timeout=self._timeout,
103
104
  )
104
105
 
106
+ @retry_request(
107
+ status_codes=(HTTPStatus.GATEWAY_TIMEOUT,),
108
+ max_retries=RETRY_ON_GATEWAY_TIMEOUT,
109
+ )
105
110
  @retry_request(
106
111
  status_codes=(HTTPStatus.UNAUTHORIZED,),
107
112
  max_retries=RETRY_ON_EXPIRED_TOKEN,
@@ -28,6 +28,7 @@ _DATA_ELEMENTS: Tuple[str, ...] = (
28
28
  )
29
29
 
30
30
  _AUTH_TIMEOUT_S = 60
31
+ _SIGMA_TIMEOUT = 120
31
32
 
32
33
  _SIGMA_HEADERS = {
33
34
  "Content-Type": _CONTENT_TYPE,
@@ -75,6 +76,7 @@ class SigmaClient(APIClient):
75
76
  host=credentials.host,
76
77
  auth=auth,
77
78
  headers=_SIGMA_HEADERS,
79
+ timeout=_SIGMA_TIMEOUT,
78
80
  safe_mode=safe_mode or SIGMA_SAFE_MODE,
79
81
  )
80
82
 
@@ -5,7 +5,7 @@ from pydantic.alias_generators import to_camel
5
5
 
6
6
  from ....utils import PaginationModel
7
7
 
8
- SIGMA_API_LIMIT = 500 # default number of records per page
8
+ SIGMA_API_LIMIT = 200 # default number of records per page
9
9
 
10
10
 
11
11
  class SigmaPagination(PaginationModel):
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from typing import Optional
2
3
 
3
4
  import tableauserverclient as TSC # type: ignore
4
5
 
@@ -121,12 +122,16 @@ class TableauRevampClient:
121
122
  credentials: TableauRevampCredentials,
122
123
  timeout_sec: int = DEFAULT_TIMEOUT_SECONDS,
123
124
  with_pulse: bool = False,
125
+ override_page_size: Optional[int] = None,
124
126
  ):
125
127
  self._credentials = credentials
126
128
  self._server = _server(credentials.server_url, timeout_sec)
127
129
  self._with_pulse = with_pulse
128
130
 
129
- self._client_metadata = TableauClientMetadataApi(server=self._server)
131
+ self._client_metadata = TableauClientMetadataApi(
132
+ server=self._server,
133
+ override_page_size=override_page_size,
134
+ )
130
135
  self._client_rest = TableauClientRestApi(server=self._server)
131
136
  self._client_tsc = TableauClientTSC(server=self._server)
132
137
 
@@ -2,10 +2,10 @@ from typing import Dict, Iterator, Optional
2
2
 
3
3
  import tableauserverclient as TSC # type: ignore
4
4
 
5
- from ....utils import SerializedAsset
5
+ from ....utils import SerializedAsset, retry
6
6
  from ..assets import TableauRevampAsset
7
7
  from ..constants import DEFAULT_PAGE_SIZE
8
- from .errors import TableauApiError
8
+ from .errors import TableauApiError, TableauApiTimeout
9
9
  from .gql_queries import FIELDS_QUERIES, GQL_QUERIES, QUERY_TEMPLATE
10
10
 
11
11
  # increase the value when extraction is too slow
@@ -20,21 +20,58 @@ _CUSTOM_PAGE_SIZE: Dict[TableauRevampAsset, int] = {
20
20
  TableauRevampAsset.TABLE: 50,
21
21
  }
22
22
 
23
+ _TIMEOUT_MESSAGE = (
24
+ "Execution canceled because timeout of 30000 millis was reached"
25
+ )
26
+
27
+ _RETRY_BASE_MS = 10_000
28
+ _RETRY_COUNT = 4
29
+
30
+
31
+ def _check_errors(answer: dict) -> None:
32
+ """
33
+ handle errors in graphql response:
34
+ - return None when there's no errors in the answer
35
+ - TableauApiTimeout if any of the errors is a timeout
36
+ - TableauApiError (generic) otherwise
37
+ """
38
+ if "errors" not in answer:
39
+ return
40
+
41
+ errors = answer["errors"]
42
+
43
+ for error in errors:
44
+ if error.get("message") == _TIMEOUT_MESSAGE:
45
+ # we need specific handling for timeout issues (retry strategy)
46
+ raise TableauApiTimeout(errors)
47
+
48
+ raise TableauApiError(answer["errors"])
49
+
23
50
 
24
51
  def gql_query_scroll(
25
52
  server,
26
53
  query: str,
27
54
  resource: str,
28
55
  ) -> Iterator[SerializedAsset]:
29
- """Iterate over GQL query results, handling pagination and cursor"""
56
+ """
57
+ Iterate over GQL query results, handling pagination and cursor
30
58
 
59
+ We have a retry strategy when timeout issues arise.
60
+ It's a known issue on Tableau side, still waiting for their fix:
61
+ https://issues.salesforce.com/issue/a028c00000zKahoAAC/undefined
62
+ """
63
+
64
+ @retry(
65
+ exceptions=(TableauApiTimeout,),
66
+ max_retries=_RETRY_COUNT,
67
+ base_ms=_RETRY_BASE_MS,
68
+ )
31
69
  def _call(cursor: Optional[str]) -> dict:
32
70
  # If cursor is defined it must be quoted else use null token
33
71
  token = "null" if cursor is None else f'"{cursor}"'
34
72
  query_ = query.replace("AFTER_TOKEN_SIGNAL", token)
35
73
  answer = server.metadata.query(query_)
36
- if "errors" in answer:
37
- raise TableauApiError(answer["errors"])
74
+ _check_errors(answer)
38
75
  return answer["data"][f"{resource}Connection"]
39
76
 
40
77
  cursor = None
@@ -58,8 +95,10 @@ class TableauClientMetadataApi:
58
95
  def __init__(
59
96
  self,
60
97
  server: TSC.Server,
98
+ override_page_size: Optional[int] = None,
61
99
  ):
62
100
  self._server = server
101
+ self._forced_page_size = override_page_size
63
102
 
64
103
  def _call(
65
104
  self,
@@ -75,9 +114,16 @@ class TableauClientMetadataApi:
75
114
  result_pages = gql_query_scroll(self._server, query, resource)
76
115
  return [asset for page in result_pages for asset in page]
77
116
 
117
+ def _page_size(self, asset: TableauRevampAsset) -> int:
118
+ return (
119
+ self._forced_page_size
120
+ or _CUSTOM_PAGE_SIZE.get(asset)
121
+ or DEFAULT_PAGE_SIZE
122
+ )
123
+
78
124
  def _fetch_fields(self) -> SerializedAsset:
79
125
  result: SerializedAsset = []
80
- page_size = _CUSTOM_PAGE_SIZE[TableauRevampAsset.FIELD]
126
+ page_size = self._page_size(TableauRevampAsset.FIELD)
81
127
  for resource, fields in FIELDS_QUERIES:
82
128
  current = self._call(resource, fields, page_size)
83
129
  result.extend(current)
@@ -90,6 +136,6 @@ class TableauClientMetadataApi:
90
136
  if asset == TableauRevampAsset.FIELD:
91
137
  return self._fetch_fields()
92
138
 
93
- page_size = _CUSTOM_PAGE_SIZE.get(asset) or DEFAULT_PAGE_SIZE
139
+ page_size = self._page_size(asset)
94
140
  resource, fields = GQL_QUERIES[asset]
95
141
  return self._call(resource, fields, page_size)
@@ -1,3 +1,8 @@
1
1
  class TableauApiError(ValueError):
2
2
  def __init__(self, error: str):
3
3
  super().__init__(f"Tableau API returned the following error: {error}")
4
+
5
+
6
+ class TableauApiTimeout(ValueError):
7
+ def __init__(self, error: str):
8
+ super().__init__(f"Tableau API returned a timeout error: {error}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: castor-extractor
3
- Version: 0.21.7
3
+ Version: 0.21.9
4
4
  Summary: Extract your metadata assets.
5
5
  Home-page: https://www.castordoc.com/
6
6
  License: EULA
@@ -208,6 +208,10 @@ For any questions or bug report, contact us at [support@castordoc.com](mailto:su
208
208
 
209
209
  # Changelog
210
210
 
211
+ ## 0.21.9 - 2024-12-04
212
+
213
+ * Tableau: fix handling of timeout retry
214
+
211
215
  ## 0.21.8 - 2024-11-26
212
216
 
213
217
  * Redshift: improve deduplication of columns
@@ -1,4 +1,4 @@
1
- CHANGELOG.md,sha256=HIo_bRiTbT5GZ6Hugs5Y1gHIHSsfh1VDVWXhoC_SXWM,14881
1
+ CHANGELOG.md,sha256=TiUeWCSqrrEdYSnHUjkzy8CH0kVXRfjNROxBnUFcEQY,14947
2
2
  Dockerfile,sha256=xQ05-CFfGShT3oUqaiumaldwA288dj9Yb_pxofQpufg,301
3
3
  DockerfileUsage.md,sha256=2hkJQF-5JuuzfPZ7IOxgM6QgIQW7l-9oRMFVwyXC4gE,998
4
4
  LICENCE,sha256=sL-IGa4hweyya1HgzMskrRdybbIa2cktzxb5qmUgDg8,8254
@@ -84,7 +84,7 @@ castor_extractor/utils/client/abstract.py,sha256=aA5Qcb9TwWDSMq8WpXbGkOB20hehwX2
84
84
  castor_extractor/utils/client/api/__init__.py,sha256=vlG7WXznYgLTn3XyMGsyUkgRkup8FbKM14EXJ8mv-b0,264
85
85
  castor_extractor/utils/client/api/auth.py,sha256=QDLM5h1zGibLaKyATxLF0gycg01SE92G-Y69f_YBClc,1896
86
86
  castor_extractor/utils/client/api/auth_test.py,sha256=NoZYsz7bcCyWBZdMF1TaOuK-s1j09DhTRyM4GSUW_YQ,1311
87
- castor_extractor/utils/client/api/client.py,sha256=_XkOMbkit0_YYJ2tY1rYrEuoPTUP826CJPnI9OLgmuQ,4434
87
+ castor_extractor/utils/client/api/client.py,sha256=NXNAbL4-2H2x0NPwmaYz_NKVsSVjuDw61OJUJgoaJSA,4587
88
88
  castor_extractor/utils/client/api/client_test.py,sha256=FM3ZxsLLfMOBn44cXX6FIgnA31-5TTNIyp9D4LBwtXE,1222
89
89
  castor_extractor/utils/client/api/pagination.py,sha256=Efg3P9ct_U5rtgXijMGV05oQxSzjldEopECWjIFWerM,2439
90
90
  castor_extractor/utils/client/api/pagination_test.py,sha256=jCOgXFXrH-jrCxe2dfk80ZksJF-EtmpJPU11BGabsqk,1385
@@ -241,10 +241,10 @@ castor_extractor/visualization/salesforce_reporting/extract.py,sha256=RMhlf7NeYi
241
241
  castor_extractor/visualization/sigma/__init__.py,sha256=GINql4yJLtjfOJgjHaWNpE13cMtnKNytiFRomwav27Q,114
242
242
  castor_extractor/visualization/sigma/assets.py,sha256=JZ1Cpxnml8P3mIJoTUM57hvylB18ErECQXaP5FF63O4,268
243
243
  castor_extractor/visualization/sigma/client/__init__.py,sha256=YQv06FBBQHvBMFg_tN0nUcmUp2NCL2s-eFTXG8rXaBg,74
244
- castor_extractor/visualization/sigma/client/client.py,sha256=Gi8GzP_xxjDZDmNRK4pXJUvSPzZCCJpn6ClUTML5amE,6218
244
+ castor_extractor/visualization/sigma/client/client.py,sha256=ngu4-fN7vquvmqGLVZGgBGhvJYR5srbDVCxhaCvwuHk,6275
245
245
  castor_extractor/visualization/sigma/client/credentials.py,sha256=0f2VeDFaNbaZOTN0fPdmx3s-nxxFe35yBF9D_2B2lGI,718
246
246
  castor_extractor/visualization/sigma/client/endpoints.py,sha256=DBFphbgoH78_MZUGM_bKBAq28Nl7LWSZ6VRsbxrxtDg,1162
247
- castor_extractor/visualization/sigma/client/pagination.py,sha256=gHBewkYoJaR0vADzU_VPsNkcAa-6KAC6hplpDL5gT8U,690
247
+ castor_extractor/visualization/sigma/client/pagination.py,sha256=kNEhNq08tTGbypyMjxs0w4uvDtQc_iaWpOZweaa_FsU,690
248
248
  castor_extractor/visualization/sigma/extract.py,sha256=pnArK5-F6DZcO0f3wp3_km_Od0f18Qmw9yTmynJ-2TU,2278
249
249
  castor_extractor/visualization/tableau/__init__.py,sha256=hDohrWjkorrX01JMc154aa9vi3ZqBKmA1lkfQtMFfYE,114
250
250
  castor_extractor/visualization/tableau/assets.py,sha256=mfBUzcBCLyiU9gnTB_6rvtiB5yXSDU99nezhGC__HQo,1270
@@ -284,12 +284,12 @@ castor_extractor/visualization/tableau/usage.py,sha256=LlFwlbEr-EnYUJjKZha99CRCR
284
284
  castor_extractor/visualization/tableau_revamp/__init__.py,sha256=a3DGjQhaz17gBqW-E84TAgupKbqLC40y5Ajo1yn-ot4,156
285
285
  castor_extractor/visualization/tableau_revamp/assets.py,sha256=8sJsK6Qixao6xVmVaO1usvs16SjNub9sIx7o-adYV14,659
286
286
  castor_extractor/visualization/tableau_revamp/client/__init__.py,sha256=wmS9uLtUiqNYVloi0-DgD8d2qzu3RVZEAtWiaDp6G_M,90
287
- castor_extractor/visualization/tableau_revamp/client/client.py,sha256=oaxvPsCccAcTWooXmDQNcJ6RFUVsCUzl6HxaHIwh5kU,7564
288
- castor_extractor/visualization/tableau_revamp/client/client_metadata_api.py,sha256=yNnGR3Tk32TUmaDejaz5fkw2p9DtmMeCv5rsZNOHUfY,3047
287
+ castor_extractor/visualization/tableau_revamp/client/client.py,sha256=Ju89lMDiLOZ2LjxylcFm5429WElxGxjc52bMIWoKCDA,7716
288
+ castor_extractor/visualization/tableau_revamp/client/client_metadata_api.py,sha256=gmWM5kn6iSOXd9G46kvOOrbs5tn3X9mCWUIRkFMNov8,4332
289
289
  castor_extractor/visualization/tableau_revamp/client/client_rest_api.py,sha256=HZ6KdNJ6sqhWElfpYlUwbZDJXxoKkNc3p-YAKExzDxM,4030
290
290
  castor_extractor/visualization/tableau_revamp/client/client_tsc.py,sha256=BBwIOqK2zU66udFRmLGmB_3J1ILGhVOY5Hq4nmsonF0,1853
291
291
  castor_extractor/visualization/tableau_revamp/client/credentials.py,sha256=qA-EaX-4rbQRsn8v4zWh5Kh784ndHLjJaoZwnkQgCyo,1905
292
- castor_extractor/visualization/tableau_revamp/client/errors.py,sha256=dTe1shqmWmAXpDpCz-E24m8dGYjt6rvIGV9qQb4jnvI,150
292
+ castor_extractor/visualization/tableau_revamp/client/errors.py,sha256=ecT8Tit5VtzrOBB9ykblA0nvd75j5-_QDFupjV48zJQ,300
293
293
  castor_extractor/visualization/tableau_revamp/client/gql_queries.py,sha256=-V3ToD5Gi7nmfVB2OxTOZw8dcOiF7_ciSWjjW2UdvvI,2270
294
294
  castor_extractor/visualization/tableau_revamp/client/rest_fields.py,sha256=gx39X1zMfRVpjmFbgvbgbvtlE0QwxOtk8rZFsIqeGRI,978
295
295
  castor_extractor/visualization/tableau_revamp/constants.py,sha256=lHGB50FgVNO2nXeIhkvQKivD8ZFBIjDrflgD5cTXKJw,104
@@ -425,8 +425,8 @@ castor_extractor/warehouse/sqlserver/queries/table.sql,sha256=kbBQP-TdG5px1IVgyx
425
425
  castor_extractor/warehouse/sqlserver/queries/user.sql,sha256=gOrZsMVypusR2dc4vwVs4E1a-CliRsr_UjnD2EbXs-A,94
426
426
  castor_extractor/warehouse/sqlserver/query.py,sha256=j_d5-HMnzBouwGfywVZMRSSwbXzPvzDWlFCZmvxcoGQ,539
427
427
  castor_extractor/warehouse/synapse/queries/column.sql,sha256=lNcFoIW3Y0PFOqoOzJEXmPvZvfAsY0AP63Mu2LuPzPo,1351
428
- castor_extractor-0.21.7.dist-info/LICENCE,sha256=sL-IGa4hweyya1HgzMskrRdybbIa2cktzxb5qmUgDg8,8254
429
- castor_extractor-0.21.7.dist-info/METADATA,sha256=-vrBbF2Ipl1Ar3SwUthFTrDdvjod8IXzEzP2Sb-TuWg,22109
430
- castor_extractor-0.21.7.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
431
- castor_extractor-0.21.7.dist-info/entry_points.txt,sha256=7aVSxc-_2dicp28Ow-S4y0p4wGoTm9zGmVptMvfLdw8,1649
432
- castor_extractor-0.21.7.dist-info/RECORD,,
428
+ castor_extractor-0.21.9.dist-info/LICENCE,sha256=sL-IGa4hweyya1HgzMskrRdybbIa2cktzxb5qmUgDg8,8254
429
+ castor_extractor-0.21.9.dist-info/METADATA,sha256=_ZFrW5NZYKjKn8CYc5SzxuosZcfUfVAiQQpjW5eI5TQ,22175
430
+ castor_extractor-0.21.9.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
431
+ castor_extractor-0.21.9.dist-info/entry_points.txt,sha256=7aVSxc-_2dicp28Ow-S4y0p4wGoTm9zGmVptMvfLdw8,1649
432
+ castor_extractor-0.21.9.dist-info/RECORD,,