castor-extractor 0.17.4__py3-none-any.whl → 0.18.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.

Potentially problematic release.


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

Files changed (79) hide show
  1. CHANGELOG.md +16 -0
  2. castor_extractor/commands/extract_metabase_api.py +2 -1
  3. castor_extractor/commands/extract_metabase_db.py +3 -1
  4. castor_extractor/commands/extract_mode.py +2 -3
  5. castor_extractor/utils/__init__.py +2 -1
  6. castor_extractor/utils/collection.py +8 -0
  7. castor_extractor/utils/safe_request.py +57 -0
  8. castor_extractor/utils/safe_request_test.py +77 -0
  9. castor_extractor/utils/salesforce/__init__.py +1 -2
  10. castor_extractor/utils/salesforce/constants.py +0 -11
  11. castor_extractor/utils/salesforce/credentials.py +22 -45
  12. castor_extractor/visualization/domo/__init__.py +1 -1
  13. castor_extractor/visualization/domo/client/__init__.py +1 -1
  14. castor_extractor/visualization/domo/client/client.py +37 -52
  15. castor_extractor/visualization/domo/client/credentials.py +14 -27
  16. castor_extractor/visualization/domo/extract.py +6 -12
  17. castor_extractor/visualization/looker/__init__.py +6 -1
  18. castor_extractor/visualization/looker/api/__init__.py +2 -1
  19. castor_extractor/visualization/looker/api/client.py +6 -4
  20. castor_extractor/visualization/looker/api/client_test.py +5 -3
  21. castor_extractor/visualization/looker/api/credentials.py +33 -0
  22. castor_extractor/visualization/looker/api/extraction_parameters.py +38 -0
  23. castor_extractor/visualization/looker/api/sdk.py +2 -28
  24. castor_extractor/visualization/looker/constant.py +2 -27
  25. castor_extractor/visualization/looker/constants.py +17 -0
  26. castor_extractor/visualization/looker/extract.py +30 -29
  27. castor_extractor/visualization/metabase/__init__.py +6 -1
  28. castor_extractor/visualization/metabase/client/__init__.py +2 -2
  29. castor_extractor/visualization/metabase/client/api/__init__.py +1 -0
  30. castor_extractor/visualization/metabase/client/api/client.py +8 -14
  31. castor_extractor/visualization/metabase/client/api/credentials.py +13 -40
  32. castor_extractor/visualization/metabase/client/db/__init__.py +1 -0
  33. castor_extractor/visualization/metabase/client/db/client.py +13 -34
  34. castor_extractor/visualization/metabase/client/db/credentials.py +19 -73
  35. castor_extractor/visualization/metabase/errors.py +5 -3
  36. castor_extractor/visualization/metabase/extract.py +1 -1
  37. castor_extractor/visualization/mode/__init__.py +1 -1
  38. castor_extractor/visualization/mode/client/__init__.py +1 -0
  39. castor_extractor/visualization/mode/client/client.py +9 -12
  40. castor_extractor/visualization/mode/client/client_test.py +3 -3
  41. castor_extractor/visualization/mode/client/credentials.py +18 -51
  42. castor_extractor/visualization/mode/extract.py +5 -3
  43. castor_extractor/visualization/powerbi/__init__.py +1 -1
  44. castor_extractor/visualization/powerbi/client/__init__.py +2 -1
  45. castor_extractor/visualization/powerbi/client/credentials.py +17 -9
  46. castor_extractor/visualization/powerbi/client/credentials_test.py +12 -4
  47. castor_extractor/visualization/powerbi/client/rest.py +2 -2
  48. castor_extractor/visualization/powerbi/client/rest_test.py +2 -2
  49. castor_extractor/visualization/powerbi/extract.py +2 -2
  50. castor_extractor/visualization/qlik/__init__.py +5 -1
  51. castor_extractor/visualization/qlik/client/__init__.py +1 -0
  52. castor_extractor/visualization/qlik/client/engine/__init__.py +1 -0
  53. castor_extractor/visualization/qlik/client/engine/client.py +5 -6
  54. castor_extractor/visualization/qlik/client/engine/credentials.py +26 -0
  55. castor_extractor/visualization/qlik/client/master.py +5 -11
  56. castor_extractor/visualization/qlik/client/rest.py +4 -4
  57. castor_extractor/visualization/qlik/client/rest_test.py +6 -2
  58. castor_extractor/visualization/qlik/extract.py +4 -8
  59. castor_extractor/visualization/sigma/__init__.py +1 -1
  60. castor_extractor/visualization/sigma/client/__init__.py +1 -1
  61. castor_extractor/visualization/sigma/client/client.py +5 -4
  62. castor_extractor/visualization/sigma/client/credentials.py +12 -28
  63. castor_extractor/visualization/sigma/extract.py +4 -8
  64. castor_extractor/visualization/tableau_revamp/client/credentials.py +40 -87
  65. castor_extractor/warehouse/redshift/queries/column.sql +0 -5
  66. castor_extractor/warehouse/salesforce/extract.py +2 -2
  67. castor_extractor/warehouse/snowflake/queries/column.sql +0 -1
  68. castor_extractor/warehouse/synapse/queries/column.sql +0 -1
  69. {castor_extractor-0.17.4.dist-info → castor_extractor-0.18.2.dist-info}/METADATA +9 -9
  70. {castor_extractor-0.17.4.dist-info → castor_extractor-0.18.2.dist-info}/RECORD +73 -73
  71. castor_extractor/visualization/domo/client/client_test.py +0 -60
  72. castor_extractor/visualization/domo/constants.py +0 -6
  73. castor_extractor/visualization/looker/env.py +0 -48
  74. castor_extractor/visualization/looker/parameters.py +0 -78
  75. castor_extractor/visualization/qlik/constants.py +0 -3
  76. castor_extractor/visualization/sigma/constants.py +0 -4
  77. {castor_extractor-0.17.4.dist-info → castor_extractor-0.18.2.dist-info}/LICENCE +0 -0
  78. {castor_extractor-0.17.4.dist-info → castor_extractor-0.18.2.dist-info}/WHEEL +0 -0
  79. {castor_extractor-0.17.4.dist-info → castor_extractor-0.18.2.dist-info}/entry_points.txt +0 -0
@@ -7,13 +7,11 @@ from ...utils import (
7
7
  deep_serialize,
8
8
  from_env,
9
9
  get_output_filename,
10
- validate_baseurl,
11
10
  write_json,
12
11
  write_summary,
13
12
  )
14
13
  from .assets import QlikAsset
15
- from .client import QlikClient
16
- from .constants import API_KEY, BASE_URL
14
+ from .client import QlikClient, QlikCredentials
17
15
 
18
16
  logger = logging.getLogger(__name__)
19
17
 
@@ -59,13 +57,11 @@ def extract_all(
59
57
  Store the output files locally under the given output_directory
60
58
  """
61
59
 
60
+ credentials = QlikCredentials(base_url=base_url, api_key=api_key)
62
61
  _output_directory = output_directory or from_env(OUTPUT_DIR)
63
- _base_url = validate_baseurl(base_url or from_env(BASE_URL))
64
- _api_key = api_key or from_env(API_KEY)
65
62
 
66
63
  client = QlikClient(
67
- server_url=_base_url,
68
- api_key=_api_key,
64
+ credentials=credentials,
69
65
  except_http_error_statuses=except_http_error_statuses,
70
66
  )
71
67
 
@@ -75,4 +71,4 @@ def extract_all(
75
71
  filename = get_output_filename(key.name.lower(), _output_directory, ts)
76
72
  write_json(filename, data)
77
73
 
78
- write_summary(_output_directory, ts, base_url=_base_url)
74
+ write_summary(_output_directory, ts, base_url=credentials.base_url)
@@ -1,3 +1,3 @@
1
1
  from .assets import SigmaAsset
2
- from .client import CredentialsKey, SigmaClient, SigmaCredentials
2
+ from .client import SigmaClient, SigmaCredentials
3
3
  from .extract import extract_all
@@ -1,2 +1,2 @@
1
1
  from .client import SigmaClient
2
- from .credentials import CredentialsKey, SigmaCredentials
2
+ from .credentials import SigmaCredentials
@@ -5,7 +5,7 @@ from urllib.parse import urljoin
5
5
  import requests
6
6
 
7
7
  from ..assets import SigmaAsset
8
- from .credentials import CredentialsKey, SigmaCredentials
8
+ from .credentials import SigmaCredentials
9
9
  from .endpoints import EndpointFactory
10
10
  from .pagination import Pagination
11
11
 
@@ -29,6 +29,7 @@ class SigmaClient:
29
29
  self.host = credentials.host
30
30
  self.client_id = credentials.client_id
31
31
  self.api_token = credentials.api_token
32
+ self.grant_type = credentials.grant_type
32
33
  self.headers: Optional[Dict[str, str]] = None
33
34
 
34
35
  def _get_token(self) -> Dict[str, str]:
@@ -37,9 +38,9 @@ class SigmaClient:
37
38
  token_response = requests.post( # noqa: S113
38
39
  token_api_path,
39
40
  data={
40
- CredentialsKey.GRANT_TYPE.value: "client_credentials",
41
- CredentialsKey.CLIENT_ID.value: self.client_id,
42
- CredentialsKey.CLIENT_SECRET.value: self.api_token,
41
+ "grant_type": self.grant_type,
42
+ "client_id": self.client_id,
43
+ "client_secret": self.api_token,
43
44
  },
44
45
  )
45
46
  if token_response.status_code != requests.codes.OK:
@@ -1,35 +1,19 @@
1
- from dataclasses import dataclass
2
- from enum import Enum
1
+ from pydantic import Field, SecretStr
2
+ from pydantic_settings import BaseSettings, SettingsConfigDict
3
3
 
4
+ CASTOR_ENV_PREFIX = "CASTOR_SIGMA_"
4
5
 
5
- class CredentialsKey(Enum):
6
- """Value enum object for the credentials"""
7
6
 
8
- CLIENT_SECRET = "client_secret" # noqa: S105
9
- CLIENT_ID = "client_id"
10
- HOST = "host"
11
- GRANT_TYPE = "grant_type"
12
- API_TOKEN = "api_token" # noqa: S105
13
-
14
-
15
- CLIENT_ALLOWED_KEYS = (
16
- CredentialsKey.HOST.value,
17
- CredentialsKey.CLIENT_ID.value,
18
- CredentialsKey.API_TOKEN.value,
19
- )
20
-
21
-
22
- @dataclass
23
- class SigmaCredentials:
7
+ class SigmaCredentials(BaseSettings):
24
8
  """Class to handle Sigma rest API permissions"""
25
9
 
26
- api_token: str
10
+ model_config = SettingsConfigDict(
11
+ env_prefix=CASTOR_ENV_PREFIX,
12
+ extra="ignore",
13
+ populate_by_name=True,
14
+ )
15
+
16
+ api_token: str = Field(repr=False)
27
17
  client_id: str
28
18
  host: str
29
-
30
- @classmethod
31
- def from_secret(cls, secret: dict) -> "SigmaCredentials":
32
- credentials = {
33
- k: v for k, v in secret.items() if k in CLIENT_ALLOWED_KEYS
34
- }
35
- return cls(**credentials)
19
+ grant_type: str = "client_credentials"
@@ -12,7 +12,6 @@ from ...utils import (
12
12
  )
13
13
  from .assets import SigmaAsset
14
14
  from .client import SigmaClient, SigmaCredentials
15
- from .constants import API_TOKEN, CLIENT_ID, HOST
16
15
 
17
16
  logger = logging.getLogger(__name__)
18
17
 
@@ -63,14 +62,11 @@ def extract_all(
63
62
  """
64
63
 
65
64
  _output_directory = output_directory or from_env(OUTPUT_DIR)
66
- _client_id = client_id or from_env(CLIENT_ID)
67
- _host = host or from_env(HOST)
68
- _api_token = api_token or from_env(API_TOKEN)
69
65
 
70
66
  credentials = SigmaCredentials(
71
- host=_host,
72
- client_id=_client_id,
73
- api_token=_api_token,
67
+ host=host,
68
+ client_id=client_id,
69
+ api_token=api_token,
74
70
  )
75
71
  client = SigmaClient(credentials=credentials)
76
72
 
@@ -80,4 +76,4 @@ def extract_all(
80
76
  filename = get_output_filename(key.name.lower(), _output_directory, ts)
81
77
  write_json(filename, data)
82
78
 
83
- write_summary(_output_directory, ts, host=_host)
79
+ write_summary(_output_directory, ts, host=credentials.host)
@@ -1,104 +1,57 @@
1
- from enum import Enum
2
- from typing import Dict, Literal, Optional, overload
1
+ from typing import Optional
3
2
 
4
- from ....utils import from_env
5
-
6
- _AUTH_ERROR_MSG = "Need either user and password or token_name and token"
3
+ from pydantic import field_validator, model_validator
4
+ from pydantic_settings import BaseSettings, SettingsConfigDict
7
5
 
8
6
  # To specify the default site on Tableau Server, you can use an empty string
9
7
  # https://tableau.github.io/server-client-python/docs/api-ref#authentication
10
8
  _DEFAULT_SERVER_SITE_ID = ""
11
9
 
12
-
13
- class CredentialsKey(Enum):
14
- """Value enum object for the credentials"""
15
-
16
- TABLEAU_USER = "user"
17
- TABLEAU_PASSWORD = "password" # noqa: S105
18
- TABLEAU_TOKEN_NAME = "token_name" # noqa: S105
19
- TABLEAU_TOKEN = "token" # noqa: S105
20
- TABLEAU_SITE_ID = "site_id"
21
- TABLEAU_SERVER_URL = "server_url"
22
-
23
-
24
- CREDENTIALS_ENV: Dict[CredentialsKey, str] = {
25
- CredentialsKey.TABLEAU_USER: "CASTOR_TABLEAU_USER",
26
- CredentialsKey.TABLEAU_PASSWORD: "CASTOR_TABLEAU_PASSWORD",
27
- CredentialsKey.TABLEAU_TOKEN_NAME: "CASTOR_TABLEAU_TOKEN_NAME",
28
- CredentialsKey.TABLEAU_TOKEN: "CASTOR_TABLEAU_TOKEN",
29
- CredentialsKey.TABLEAU_SITE_ID: "CASTOR_TABLEAU_SITE_ID",
30
- CredentialsKey.TABLEAU_SERVER_URL: "CASTOR_TABLEAU_SERVER_URL",
31
- }
32
-
33
-
34
- @overload
35
- def get_value(key: CredentialsKey, kwargs: dict) -> Optional[str]: ...
10
+ # In Castor APP, site_id is mandatory: users can't let this field empty
11
+ # In that case, we encourage users to write "Default" instead
12
+ _DEFAULT_SITE_ID_USER_INPUT = "default"
36
13
 
37
14
 
38
- @overload
39
- def get_value(
40
- key: CredentialsKey, kwargs: dict, optional: Literal[True]
41
- ) -> Optional[str]: ...
15
+ TABLEAU_ENV_PREFIX = "CASTOR_TABLEAU_"
42
16
 
43
17
 
44
- @overload
45
- def get_value(
46
- key: CredentialsKey, kwargs: dict, optional: Literal[False]
47
- ) -> str: ...
48
-
49
-
50
- def get_value(
51
- key: CredentialsKey,
52
- kwargs: dict,
53
- optional: bool = True,
54
- ) -> Optional[str]:
18
+ class TableauRevampCredentials(BaseSettings):
55
19
  """
56
- Returns the value of the given key:
57
- - from kwargs in priority
58
- - from ENV otherwise
59
- Raises an error if not found (unless optional)
20
+ Tableau's credentials to connect to both APIs (REST and GRAPHQL)
60
21
  """
61
22
 
62
- if key.value in kwargs:
63
- return kwargs[key.value]
64
-
65
- env_key = CREDENTIALS_ENV[key]
66
- return from_env(env_key, optional)
23
+ model_config = SettingsConfigDict(
24
+ env_prefix=TABLEAU_ENV_PREFIX,
25
+ extra="ignore",
26
+ populate_by_name=True,
27
+ )
67
28
 
29
+ server_url: str
30
+ site_id: str = ""
68
31
 
69
- class TableauRevampCredentials:
70
- """
71
- Tableau's credentials to connect to REST API
72
- """
73
-
74
- def __init__(
75
- self,
76
- *,
77
- server_url: str,
78
- site_id: Optional[str],
79
- user: Optional[str],
80
- password: Optional[str],
81
- token_name: Optional[str],
82
- token: Optional[str],
83
- ):
84
- self.user = user
85
- self.site_id = site_id or _DEFAULT_SERVER_SITE_ID
86
- self.server_url = server_url
87
- self.password = password
88
- self.token_name = token_name
89
- self.token = token
32
+ password: Optional[str] = None
33
+ token: Optional[str] = None
34
+ token_name: Optional[str] = None
35
+ user: Optional[str] = None
90
36
 
37
+ @field_validator("site_id", mode="before")
91
38
  @classmethod
92
- def from_env(cls, kwargs: dict) -> "TableauRevampCredentials":
93
- return TableauRevampCredentials(
94
- server_url=get_value(
95
- CredentialsKey.TABLEAU_SERVER_URL,
96
- kwargs,
97
- optional=False,
98
- ),
99
- site_id=get_value(CredentialsKey.TABLEAU_SITE_ID, kwargs),
100
- user=get_value(CredentialsKey.TABLEAU_USER, kwargs),
101
- password=get_value(CredentialsKey.TABLEAU_PASSWORD, kwargs),
102
- token_name=get_value(CredentialsKey.TABLEAU_TOKEN_NAME, kwargs),
103
- token=get_value(CredentialsKey.TABLEAU_TOKEN, kwargs),
104
- )
39
+ def _check_site_id(cls, site_id: Optional[str]) -> str:
40
+ if not site_id or site_id.lower() == _DEFAULT_SITE_ID_USER_INPUT:
41
+ return _DEFAULT_SERVER_SITE_ID
42
+ return site_id
43
+
44
+ @model_validator(mode="after")
45
+ def _check_user_xor_pat_login(self) -> "TableauRevampCredentials":
46
+ """
47
+ Checks that credentials are correctly input, it means either:
48
+ - User and password are filled
49
+ - Token and Token name are filled
50
+ """
51
+ user_login = self.password and self.user
52
+ pat_login = self.token_name and self.token
53
+ if not user_login and not pat_login:
54
+ raise ValueError("Either token or user identification is required")
55
+ if user_login and pat_login:
56
+ raise ValueError("Can't have both token and user identification")
57
+ return self
@@ -28,7 +28,6 @@ information_tables AS (
28
28
  i.table_id || '.' || c.column_name AS column_id,
29
29
  c.data_type,
30
30
  c.ordinal_position,
31
- c.column_default,
32
31
  c.is_nullable,
33
32
  c.character_maximum_length,
34
33
  c.character_octet_length,
@@ -59,7 +58,6 @@ raw_tables AS (
59
58
  a.attname AS column_name,
60
59
  c.oid::TEXT || '.' || a.attname AS column_id,
61
60
  a.attnum AS ordinal_position,
62
- ad.adsrc AS column_default,
63
61
  CASE
64
62
  WHEN t.typname = 'bpchar' THEN 'char'
65
63
  ELSE t.typname
@@ -90,7 +88,6 @@ tables AS (
90
88
  COALESCE(i.data_type, r.data_type) AS data_type,
91
89
  COALESCE(i.ordinal_position, r.ordinal_position) AS ordinal_position,
92
90
  COALESCE(i.is_nullable, r.is_nullable) AS is_nullable,
93
- COALESCE(i.column_default, r.column_default) AS column_default,
94
91
  i.character_maximum_length::INT AS character_maximum_length,
95
92
  i.character_octet_length::INT AS character_octet_length,
96
93
  i.numeric_precision::INT AS numeric_precision,
@@ -117,7 +114,6 @@ views_late_binding AS (
117
114
  c.data_type,
118
115
  c.ordinal_position,
119
116
  'YES' AS is_nullable,
120
- NULL::TEXT AS column_default,
121
117
  NULL::INT AS character_maximum_length,
122
118
  NULL::INT AS character_octet_length,
123
119
  NULL::INT AS numeric_precision,
@@ -162,7 +158,6 @@ external_columns AS (
162
158
  c.external_type AS data_type,
163
159
  MIN(c.columnnum) AS ordinal_position,
164
160
  CASE c.is_nullable WHEN 'false' THEN 'NO' ELSE 'YES' END AS is_nullable,
165
- NULL AS column_default,
166
161
  NULL AS character_maximum_length,
167
162
  NULL AS character_octet_length,
168
163
  NULL AS numeric_precision,
@@ -2,7 +2,7 @@ import logging
2
2
  from typing import Dict, List, Tuple
3
3
 
4
4
  from ...utils import AbstractStorage, LocalStorage, write_summary
5
- from ...utils.salesforce import to_credentials
5
+ from ...utils.salesforce import SalesforceCredentials
6
6
  from ..abstract import (
7
7
  SupportedAssets,
8
8
  WarehouseAsset,
@@ -93,7 +93,7 @@ def extract_all(**kwargs) -> None:
93
93
  """
94
94
  output_directory, skip_existing = common_args(kwargs)
95
95
 
96
- client = SalesforceClient(credentials=to_credentials(kwargs))
96
+ client = SalesforceClient(credentials=SalesforceCredentials(**kwargs))
97
97
  storage = LocalStorage(directory=output_directory)
98
98
  extractor = SalesforceExtractionProcessor(
99
99
  client=client,
@@ -28,7 +28,6 @@ SELECT
28
28
  c.table_catalog_id AS database_id,
29
29
  c.table_catalog AS database_name,
30
30
  c.ordinal_position,
31
- c.column_default,
32
31
  c.is_nullable,
33
32
  c.data_type,
34
33
  c.maximum_cardinality,
@@ -18,7 +18,6 @@ SELECT
18
18
  COALESCE(su.uid, pu.uid) AS column_owner_id,
19
19
  ty.name AS data_type,
20
20
  ty.is_nullable AS is_nullable,
21
- sc.column_default AS column_default,
22
21
  c.max_length AS character_maximum_length,
23
22
  o.create_date AS created_at,
24
23
  o.modify_date AS updated_at,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: castor-extractor
3
- Version: 0.17.4
3
+ Version: 0.18.2
4
4
  Summary: Extract your metadata assets.
5
5
  Home-page: https://www.castordoc.com/
6
6
  License: EULA
@@ -37,13 +37,13 @@ Requires-Dist: google-cloud-core (>=2.1.0,<3.0.0)
37
37
  Requires-Dist: google-cloud-storage (>=2,<3)
38
38
  Requires-Dist: google-resumable-media (>=2.0.3,<3.0.0)
39
39
  Requires-Dist: googleapis-common-protos (>=1.53.0,<2.0.0)
40
- Requires-Dist: looker-sdk (>=23.0.0) ; extra == "looker" or extra == "all"
40
+ Requires-Dist: looker-sdk (>=23.0.0,<24.0.0) ; extra == "looker" or extra == "all"
41
41
  Requires-Dist: msal (>=1.20.0,<2.0.0) ; extra == "powerbi" or extra == "all"
42
- Requires-Dist: numpy (<1.25) ; python_version >= "3.8" and python_version < "3.9"
43
- Requires-Dist: numpy (>=1.26,<2) ; python_version >= "3.12" and python_version < "3.13"
44
- Requires-Dist: pandas (>=2,<2.2.0) ; python_version >= "3.9" and python_full_version <= "3.11.0"
45
- Requires-Dist: pandas (>=2.0,<2.1) ; python_version >= "3.8" and python_version < "3.9"
46
- Requires-Dist: pandas (>=2.1,<2.2.0) ; python_version >= "3.12" and python_version < "3.13"
42
+ Requires-Dist: numpy (<1.25) ; (python_version >= "3.8" and python_version < "3.9") and (extra == "bigquery" or extra == "databricks" or extra == "all")
43
+ Requires-Dist: numpy (<2) ; extra == "bigquery" or extra == "databricks" or extra == "all"
44
+ Requires-Dist: numpy (>=1.26) ; (python_version >= "3.12" and python_version < "3.13") and (extra == "bigquery" or extra == "databricks" or extra == "all")
45
+ Requires-Dist: pandas (<2.1) ; (python_version >= "3.8" and python_version < "3.9") and (extra == "databricks" or extra == "all")
46
+ Requires-Dist: pandas (>=2.1) ; (python_version >= "3.12" and python_version < "3.13") and (extra == "databricks" or extra == "all")
47
47
  Requires-Dist: psycopg2-binary (>=2.0.0,<3.0.0) ; extra == "metabase" or extra == "postgres" or extra == "redshift" or extra == "all"
48
48
  Requires-Dist: pycryptodome (>=3.0.0,<4.0.0) ; extra == "metabase" or extra == "all"
49
49
  Requires-Dist: pydantic (>=2.6,<3.0)
@@ -52,13 +52,13 @@ Requires-Dist: pymssql (>=2.2.11,<3.0.0) ; extra == "sqlserver" or extra == "all
52
52
  Requires-Dist: pymysql[rsa] (>=1.1.0,<2.0.0) ; extra == "mysql" or extra == "all"
53
53
  Requires-Dist: python-dateutil (>=2.0.0,<=3.0.0)
54
54
  Requires-Dist: requests (>=2.0.0,<3.0.0)
55
- Requires-Dist: setuptools (>=69,<70)
55
+ Requires-Dist: setuptools (>=70,<71)
56
56
  Requires-Dist: snowflake-connector-python (>=3.4.0,<4.0.0) ; extra == "snowflake" or extra == "all"
57
57
  Requires-Dist: snowflake-sqlalchemy (!=1.2.5,<2.0.0) ; extra == "snowflake" or extra == "all"
58
58
  Requires-Dist: sqlalchemy (>=1.4,<1.5)
59
59
  Requires-Dist: sqlalchemy-bigquery[bqstorage] (>=1.0.0,<=2.0.0) ; extra == "bigquery" or extra == "all"
60
60
  Requires-Dist: sqlalchemy-redshift (>=0.8.14,<0.9.0) ; extra == "redshift" or extra == "all"
61
- Requires-Dist: tableauserverclient (==0.17.0) ; extra == "tableau" or extra == "all"
61
+ Requires-Dist: tableauserverclient (==0.25.0) ; extra == "tableau" or extra == "all"
62
62
  Requires-Dist: tqdm (>=4.0.0,<5.0.0)
63
63
  Requires-Dist: typing-extensions (>=4,<5)
64
64
  Requires-Dist: websocket-client (>=1,<2) ; extra == "qlik" or extra == "all"