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 +4 -0
- castor_extractor/utils/client/api/client.py +5 -0
- castor_extractor/visualization/sigma/client/client.py +2 -0
- castor_extractor/visualization/sigma/client/pagination.py +1 -1
- castor_extractor/visualization/tableau_revamp/client/client.py +6 -1
- castor_extractor/visualization/tableau_revamp/client/client_metadata_api.py +53 -7
- castor_extractor/visualization/tableau_revamp/client/errors.py +5 -0
- {castor_extractor-0.21.7.dist-info → castor_extractor-0.21.9.dist-info}/METADATA +5 -1
- {castor_extractor-0.21.7.dist-info → castor_extractor-0.21.9.dist-info}/RECORD +12 -12
- {castor_extractor-0.21.7.dist-info → castor_extractor-0.21.9.dist-info}/LICENCE +0 -0
- {castor_extractor-0.21.7.dist-info → castor_extractor-0.21.9.dist-info}/WHEEL +0 -0
- {castor_extractor-0.21.7.dist-info → castor_extractor-0.21.9.dist-info}/entry_points.txt +0 -0
CHANGELOG.md
CHANGED
|
@@ -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
|
|
|
@@ -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(
|
|
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
|
-
"""
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
288
|
-
castor_extractor/visualization/tableau_revamp/client/client_metadata_api.py,sha256=
|
|
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=
|
|
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.
|
|
429
|
-
castor_extractor-0.21.
|
|
430
|
-
castor_extractor-0.21.
|
|
431
|
-
castor_extractor-0.21.
|
|
432
|
-
castor_extractor-0.21.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|