castor-extractor 0.21.9__py3-none-any.whl → 0.22.1__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 (142) hide show
  1. CHANGELOG.md +8 -0
  2. castor_extractor/commands/__init__.py +0 -3
  3. castor_extractor/commands/file_check.py +1 -2
  4. castor_extractor/file_checker/column.py +5 -5
  5. castor_extractor/file_checker/file.py +7 -7
  6. castor_extractor/file_checker/file_test.py +2 -2
  7. castor_extractor/file_checker/templates/generic_warehouse.py +4 -6
  8. castor_extractor/knowledge/confluence/client/client.py +2 -1
  9. castor_extractor/knowledge/confluence/extract.py +3 -2
  10. castor_extractor/knowledge/notion/client/client.py +3 -2
  11. castor_extractor/knowledge/notion/extract.py +3 -2
  12. castor_extractor/quality/soda/client/client.py +2 -1
  13. castor_extractor/quality/soda/client/pagination.py +1 -3
  14. castor_extractor/types.py +3 -3
  15. castor_extractor/uploader/env.py +2 -2
  16. castor_extractor/uploader/upload.py +4 -3
  17. castor_extractor/uploader/utils.py +1 -1
  18. castor_extractor/utils/__init__.py +1 -0
  19. castor_extractor/utils/client/abstract.py +2 -1
  20. castor_extractor/utils/client/api/auth.py +2 -2
  21. castor_extractor/utils/client/api/auth_test.py +2 -2
  22. castor_extractor/utils/client/api/client.py +3 -3
  23. castor_extractor/utils/client/api/pagination.py +3 -2
  24. castor_extractor/utils/client/api/safe_request.py +5 -5
  25. castor_extractor/utils/collection.py +7 -11
  26. castor_extractor/utils/dbt/client.py +3 -3
  27. castor_extractor/utils/dbt/client_test.py +2 -2
  28. castor_extractor/utils/deprecate.py +1 -2
  29. castor_extractor/utils/files.py +5 -5
  30. castor_extractor/utils/formatter.py +5 -4
  31. castor_extractor/utils/json_stream_write.py +2 -1
  32. castor_extractor/utils/object.py +2 -1
  33. castor_extractor/utils/pager/pager.py +2 -4
  34. castor_extractor/utils/pager/pager_on_id.py +2 -1
  35. castor_extractor/utils/pager/pager_on_id_test.py +5 -5
  36. castor_extractor/utils/pager/pager_test.py +3 -3
  37. castor_extractor/utils/retry.py +4 -3
  38. castor_extractor/utils/retry_test.py +2 -3
  39. castor_extractor/utils/safe.py +3 -3
  40. castor_extractor/utils/salesforce/client.py +2 -1
  41. castor_extractor/utils/salesforce/credentials.py +1 -3
  42. castor_extractor/utils/store.py +2 -1
  43. castor_extractor/utils/string.py +2 -2
  44. castor_extractor/utils/string_test.py +1 -3
  45. castor_extractor/utils/time.py +4 -0
  46. castor_extractor/utils/time_test.py +8 -1
  47. castor_extractor/utils/type.py +3 -2
  48. castor_extractor/utils/validation.py +4 -4
  49. castor_extractor/utils/write.py +2 -2
  50. castor_extractor/visualization/domo/client/client.py +8 -7
  51. castor_extractor/visualization/domo/client/credentials.py +2 -2
  52. castor_extractor/visualization/domo/client/endpoints.py +2 -2
  53. castor_extractor/visualization/domo/extract.py +3 -2
  54. castor_extractor/visualization/looker/api/client.py +17 -16
  55. castor_extractor/visualization/looker/api/utils.py +2 -2
  56. castor_extractor/visualization/looker/assets.py +1 -3
  57. castor_extractor/visualization/looker/extract.py +4 -3
  58. castor_extractor/visualization/looker/fields.py +3 -3
  59. castor_extractor/visualization/looker/multithreading.py +3 -3
  60. castor_extractor/visualization/looker_studio/__init__.py +6 -0
  61. castor_extractor/visualization/looker_studio/assets.py +6 -0
  62. castor_extractor/visualization/looker_studio/client/__init__.py +3 -0
  63. castor_extractor/visualization/looker_studio/client/admin_sdk_client.py +90 -0
  64. castor_extractor/visualization/looker_studio/client/client.py +37 -0
  65. castor_extractor/visualization/looker_studio/client/credentials.py +20 -0
  66. castor_extractor/visualization/looker_studio/client/endpoints.py +18 -0
  67. castor_extractor/visualization/looker_studio/client/enums.py +8 -0
  68. castor_extractor/visualization/looker_studio/client/looker_studio_api_client.py +102 -0
  69. castor_extractor/visualization/looker_studio/client/pagination.py +31 -0
  70. castor_extractor/visualization/looker_studio/client/scopes.py +6 -0
  71. castor_extractor/visualization/metabase/assets.py +1 -3
  72. castor_extractor/visualization/metabase/client/api/client.py +8 -7
  73. castor_extractor/visualization/metabase/extract.py +3 -2
  74. castor_extractor/visualization/metabase/types.py +1 -3
  75. castor_extractor/visualization/mode/client/client.py +6 -6
  76. castor_extractor/visualization/mode/extract.py +2 -2
  77. castor_extractor/visualization/powerbi/assets.py +1 -3
  78. castor_extractor/visualization/powerbi/client/client.py +12 -11
  79. castor_extractor/visualization/powerbi/client/credentials.py +3 -3
  80. castor_extractor/visualization/powerbi/client/endpoints.py +2 -2
  81. castor_extractor/visualization/powerbi/extract.py +3 -2
  82. castor_extractor/visualization/qlik/assets.py +1 -3
  83. castor_extractor/visualization/qlik/client/constants.py +1 -3
  84. castor_extractor/visualization/qlik/client/engine/error.py +1 -3
  85. castor_extractor/visualization/qlik/client/master.py +3 -3
  86. castor_extractor/visualization/qlik/client/rest.py +12 -12
  87. castor_extractor/visualization/qlik/extract.py +4 -3
  88. castor_extractor/visualization/salesforce_reporting/client/rest.py +3 -2
  89. castor_extractor/visualization/salesforce_reporting/client/soql.py +1 -3
  90. castor_extractor/visualization/salesforce_reporting/extract.py +3 -2
  91. castor_extractor/visualization/sigma/client/client.py +9 -8
  92. castor_extractor/visualization/sigma/client/credentials.py +1 -3
  93. castor_extractor/visualization/sigma/extract.py +3 -2
  94. castor_extractor/visualization/tableau/assets.py +1 -2
  95. castor_extractor/visualization/tableau/client/client.py +1 -2
  96. castor_extractor/visualization/tableau/client/client_utils.py +3 -2
  97. castor_extractor/visualization/tableau/client/credentials.py +3 -3
  98. castor_extractor/visualization/tableau/client/safe_mode.py +1 -2
  99. castor_extractor/visualization/tableau/extract.py +2 -2
  100. castor_extractor/visualization/tableau/gql_fields.py +3 -3
  101. castor_extractor/visualization/tableau/tsc_fields.py +1 -2
  102. castor_extractor/visualization/tableau/types.py +3 -3
  103. castor_extractor/visualization/tableau_revamp/client/client_metadata_api.py +3 -2
  104. castor_extractor/visualization/tableau_revamp/client/client_rest_api.py +3 -3
  105. castor_extractor/visualization/tableau_revamp/client/client_tsc.py +3 -2
  106. castor_extractor/visualization/tableau_revamp/client/gql_queries.py +1 -3
  107. castor_extractor/visualization/tableau_revamp/client/rest_fields.py +1 -3
  108. castor_extractor/visualization/tableau_revamp/extract.py +2 -2
  109. castor_extractor/visualization/thoughtspot/client/client.py +3 -2
  110. castor_extractor/visualization/thoughtspot/client/utils.py +1 -1
  111. castor_extractor/visualization/thoughtspot/extract.py +3 -2
  112. castor_extractor/warehouse/abstract/asset.py +4 -5
  113. castor_extractor/warehouse/abstract/extract.py +4 -3
  114. castor_extractor/warehouse/abstract/query.py +4 -4
  115. castor_extractor/warehouse/bigquery/client.py +8 -8
  116. castor_extractor/warehouse/bigquery/extract.py +1 -1
  117. castor_extractor/warehouse/bigquery/query.py +2 -2
  118. castor_extractor/warehouse/bigquery/types.py +2 -4
  119. castor_extractor/warehouse/databricks/api_client.py +15 -14
  120. castor_extractor/warehouse/databricks/client.py +16 -16
  121. castor_extractor/warehouse/databricks/extract.py +4 -4
  122. castor_extractor/warehouse/databricks/format.py +12 -12
  123. castor_extractor/warehouse/databricks/lineage.py +11 -11
  124. castor_extractor/warehouse/databricks/pagination.py +2 -2
  125. castor_extractor/warehouse/databricks/types.py +4 -4
  126. castor_extractor/warehouse/databricks/utils.py +5 -4
  127. castor_extractor/warehouse/mysql/query.py +2 -2
  128. castor_extractor/warehouse/postgres/query.py +2 -2
  129. castor_extractor/warehouse/redshift/client.py +1 -1
  130. castor_extractor/warehouse/redshift/query.py +2 -2
  131. castor_extractor/warehouse/salesforce/client.py +8 -8
  132. castor_extractor/warehouse/salesforce/extract.py +3 -4
  133. castor_extractor/warehouse/salesforce/format.py +19 -11
  134. castor_extractor/warehouse/salesforce/format_test.py +24 -10
  135. castor_extractor/warehouse/snowflake/query.py +5 -5
  136. castor_extractor/warehouse/sqlserver/client.py +1 -1
  137. castor_extractor/warehouse/sqlserver/query.py +2 -2
  138. {castor_extractor-0.21.9.dist-info → castor_extractor-0.22.1.dist-info}/METADATA +13 -6
  139. {castor_extractor-0.21.9.dist-info → castor_extractor-0.22.1.dist-info}/RECORD +142 -131
  140. {castor_extractor-0.21.9.dist-info → castor_extractor-0.22.1.dist-info}/LICENCE +0 -0
  141. {castor_extractor-0.21.9.dist-info → castor_extractor-0.22.1.dist-info}/WHEEL +0 -0
  142. {castor_extractor-0.21.9.dist-info → castor_extractor-0.22.1.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  from functools import partial
3
- from typing import Dict, List, Optional, Tuple
3
+ from typing import Optional
4
4
 
5
5
  from tqdm import tqdm # type: ignore
6
6
 
@@ -29,7 +29,7 @@ class SalesforceClient(SalesforceBaseClient):
29
29
  def name() -> str:
30
30
  return "Salesforce"
31
31
 
32
- def fetch_sobjects(self) -> List[dict]:
32
+ def fetch_sobjects(self) -> list[dict]:
33
33
  """Fetch all sobjects"""
34
34
  logger.info("Extracting sobjects")
35
35
  query = format_sobject_query()
@@ -39,7 +39,7 @@ class SalesforceClient(SalesforceBaseClient):
39
39
  results = fetch_all_pages(request_, SalesforceSQLPagination)
40
40
  return list(results)
41
41
 
42
- def fetch_fields(self, sobject_name: str) -> List[dict]:
42
+ def fetch_fields(self, sobject_name: str) -> list[dict]:
43
43
  """Fetches fields of a given sobject"""
44
44
  query = SOBJECT_FIELDS_QUERY_TPL.format(
45
45
  entity_definition_id=sobject_name
@@ -55,7 +55,7 @@ class SalesforceClient(SalesforceBaseClient):
55
55
  return None
56
56
  return response["records"][0]["Description"]
57
57
 
58
- def add_table_descriptions(self, sobjects: List[dict]) -> List[dict]:
58
+ def add_table_descriptions(self, sobjects: list[dict]) -> list[dict]:
59
59
  """
60
60
  Add table descriptions.
61
61
  We use the tooling API which does not handle well the LIMIT in SOQL
@@ -67,7 +67,7 @@ class SalesforceClient(SalesforceBaseClient):
67
67
  described_sobjects.append({**sobject, "Description": description})
68
68
  return described_sobjects
69
69
 
70
- def tables(self) -> List[dict]:
70
+ def tables(self) -> list[dict]:
71
71
  """
72
72
  Get Salesforce sobjects as tables
73
73
  """
@@ -77,13 +77,13 @@ class SalesforceClient(SalesforceBaseClient):
77
77
  return list(self.formatter.tables(described_sobjects))
78
78
 
79
79
  def columns(
80
- self, sobject_names: List[Tuple[str, str]], show_progress: bool = True
81
- ) -> List[dict]:
80
+ self, sobject_names: list[tuple[str, str]], show_progress: bool = True
81
+ ) -> list[dict]:
82
82
  """
83
83
  Get salesforce sobject fields as columns
84
84
  show_progress: optionally deactivate the tqdm progress bar
85
85
  """
86
- sobject_fields: Dict[str, List[dict]] = dict()
86
+ sobject_fields: dict[str, list[dict]] = dict()
87
87
  for api_name, table_name in tqdm(
88
88
  sobject_names, disable=not show_progress
89
89
  ):
@@ -1,5 +1,4 @@
1
1
  import logging
2
- from typing import Dict, List, Tuple
3
2
 
4
3
  from ...utils import AbstractStorage, LocalStorage, write_summary
5
4
  from ...utils.salesforce import SalesforceCredentials
@@ -14,9 +13,9 @@ from .client import SalesforceClient
14
13
  logger = logging.getLogger(__name__)
15
14
 
16
15
 
17
- Paths = Dict[str, str]
16
+ Paths = dict[str, str]
18
17
 
19
- SALESFORCE_CATALOG_ASSETS: Tuple[WarehouseAsset, ...] = (
18
+ SALESFORCE_CATALOG_ASSETS: tuple[WarehouseAsset, ...] = (
20
19
  WarehouseAsset.TABLE,
21
20
  WarehouseAsset.COLUMN,
22
21
  )
@@ -81,7 +80,7 @@ class SalesforceExtractionProcessor:
81
80
 
82
81
  def extract_role(self) -> Paths:
83
82
  """extract no users and return the empty file location"""
84
- users: List[dict] = []
83
+ users: list[dict] = []
85
84
  location = self._storage.put(WarehouseAsset.USER.value, users)
86
85
  logger.info(f"Extracted {len(users)} users to {location}")
87
86
  return {WarehouseAsset.USER.value: location}
@@ -1,9 +1,10 @@
1
- from typing import Any, Dict, Iterator, List
1
+ from collections.abc import Iterator
2
+ from typing import Any
2
3
 
3
4
  from ...utils import group_by
4
5
  from .constants import SCHEMA_NAME
5
6
 
6
- _HAS_DUPLICATE_KEY = "#has_duplicate"
7
+ _HAS_DUPLICATE_KEY = "#has_duplicate_label"
7
8
 
8
9
 
9
10
  def _clean(raw: str) -> str:
@@ -25,10 +26,10 @@ def _name(sobject: dict) -> str:
25
26
  return f"{label} ({api_name})"
26
27
 
27
28
 
28
- def _field_description(field: Dict[str, Any]) -> str:
29
- context: Dict[str, str] = {}
29
+ def _field_description(field: dict[str, Any]) -> str:
30
+ context: dict[str, str] = {}
30
31
 
31
- field_definition: Dict[str, str] = field.get("FieldDefinition") or {}
32
+ field_definition: dict[str, str] = field.get("FieldDefinition") or {}
32
33
  if description := field_definition.get("Description"):
33
34
  context["Description"] = _clean(description)
34
35
  if help_text := field.get("InlineHelpText"):
@@ -69,9 +70,15 @@ def _to_table_payload(sobject: dict) -> dict:
69
70
  }
70
71
 
71
72
 
72
- def _detect_duplicates(sobjects: List[dict]) -> List[dict]:
73
+ def _remove_duplicates(sobjects: list[dict]) -> list[dict]:
74
+ """only keep one object per QualifiedApiName"""
75
+ by_name = group_by("QualifiedApiName", sobjects)
76
+ return [objects[0] for _, objects in by_name.items()]
77
+
78
+
79
+ def _detect_duplicate_labels(sobjects: list[dict]) -> list[dict]:
73
80
  """
74
- enrich the given data with "has_duplicate" flag:
81
+ enrich the given data with "has_duplicate_label" flag:
75
82
  - True when another asset has the same Label in the list
76
83
  - False otherwise
77
84
  """
@@ -89,18 +96,19 @@ class SalesforceFormatter:
89
96
  """
90
97
 
91
98
  @staticmethod
92
- def tables(sobjects: List[dict]) -> Iterator[dict]:
99
+ def tables(sobjects: list[dict]) -> Iterator[dict]:
93
100
  """
94
101
  formats the raw list of sobjects to tables
95
102
  """
96
- sobjects = _detect_duplicates(sobjects)
103
+ sobjects = _remove_duplicates(sobjects)
104
+ sobjects = _detect_duplicate_labels(sobjects)
97
105
  for sobject in sobjects:
98
106
  yield _to_table_payload(sobject)
99
107
 
100
108
  @staticmethod
101
- def columns(sobject_fields: Dict[str, List[dict]]) -> Iterator[dict]:
109
+ def columns(sobject_fields: dict[str, list[dict]]) -> Iterator[dict]:
102
110
  """formats the raw list of sobject fields to columns"""
103
111
  for table_name, fields in sobject_fields.items():
104
- fields = _detect_duplicates(fields)
112
+ fields = _detect_duplicate_labels(fields)
105
113
  for index, field in enumerate(fields):
106
114
  yield _to_column_payload(field, index, table_name)
@@ -1,24 +1,24 @@
1
- from typing import Dict, List, Tuple
2
-
3
1
  from .format import (
4
2
  _HAS_DUPLICATE_KEY,
5
3
  SalesforceFormatter,
6
- _detect_duplicates,
4
+ _detect_duplicate_labels,
7
5
  _field_description,
8
6
  _name,
7
+ _remove_duplicates,
9
8
  )
10
9
 
11
10
 
12
- def _tables_sobjects() -> Tuple[Dict[str, str], ...]:
11
+ def _tables_sobjects() -> tuple[dict[str, str], ...]:
13
12
  """Returns 4 sobjects with 2 sharing the same label"""
14
13
  a = {"Label": "a", "QualifiedApiName": "a_one"}
15
14
  b = {"Label": "b", "QualifiedApiName": "b"}
16
- c = {"Label": "c", "QualifiedApiName": "c"}
15
+ c = {"Label": "c", "QualifiedApiName": "c_unique_so_doesnt_matter"}
17
16
  a_prime = {"Label": "a", "QualifiedApiName": "a_two"}
18
- return a, b, c, a_prime
17
+ b_exact_duplicate = {"Label": "b", "QualifiedApiName": "b"}
18
+ return a, b, c, a_prime, b_exact_duplicate
19
19
 
20
20
 
21
- def _columns_sobjects() -> Dict[str, List[dict]]:
21
+ def _columns_sobjects() -> dict[str, list[dict]]:
22
22
  a = {"Label": "First Name", "QualifiedApiName": "owner_name"}
23
23
  b = {"Label": "First Name", "QualifiedApiName": "editor_name"}
24
24
  c = {"Label": "Foo Bar", "QualifiedApiName": "foo_bar"}
@@ -81,14 +81,14 @@ def test__name():
81
81
  assert _name(empty_label_sobject) == "empty_label"
82
82
 
83
83
 
84
- def test__detect_duplicates():
84
+ def test__detect_duplicate_labels():
85
85
  objects = [
86
86
  {"Label": "Foo"},
87
87
  {"Label": "Bar"},
88
88
  {"Label": "Foo"},
89
89
  ]
90
90
 
91
- objects = _detect_duplicates(objects)
91
+ objects = _detect_duplicate_labels(objects)
92
92
  assert objects == [
93
93
  {"Label": "Foo", _HAS_DUPLICATE_KEY: True},
94
94
  {"Label": "Bar", _HAS_DUPLICATE_KEY: False},
@@ -96,11 +96,25 @@ def test__detect_duplicates():
96
96
  ]
97
97
 
98
98
 
99
+ def test__remove_duplicates():
100
+ objects = [
101
+ {"QualifiedApiName": "Foo"},
102
+ {"QualifiedApiName": "Bar"},
103
+ {"QualifiedApiName": "Foo"},
104
+ ]
105
+
106
+ objects = _remove_duplicates(objects)
107
+ assert len(objects) == 2
108
+ names = {sobject["QualifiedApiName"] for sobject in objects}
109
+ assert names == {"Foo", "Bar"}
110
+
111
+
99
112
  def test_salesforce_formatter_tables():
100
113
  sobjects = [*_tables_sobjects()]
101
- tables = SalesforceFormatter.tables(sobjects)
114
+ tables = [t for t in SalesforceFormatter.tables(sobjects)]
102
115
  expected_names = {"a (a_one)", "a (a_two)", "b", "c"}
103
116
  payload_names = {t["table_name"] for t in tables}
117
+ assert len(tables) == 4 # we only keep one "b"
104
118
  assert payload_names == expected_names
105
119
 
106
120
 
@@ -1,4 +1,4 @@
1
- from typing import List, Optional
1
+ from typing import Optional
2
2
 
3
3
  from ..abstract import (
4
4
  CATALOG_ASSETS,
@@ -14,7 +14,7 @@ DB_FILTERED_ASSETS = (
14
14
  )
15
15
 
16
16
 
17
- def _database_filter(db_list: Optional[List[str]], allow: bool) -> str:
17
+ def _database_filter(db_list: Optional[list[str]], allow: bool) -> str:
18
18
  if not db_list:
19
19
  return ""
20
20
  keyword = "IN" if allow else "NOT IN"
@@ -34,8 +34,8 @@ class SnowflakeQueryBuilder(AbstractQueryBuilder):
34
34
  def __init__(
35
35
  self,
36
36
  time_filter: Optional[TimeFilter] = None,
37
- db_allowed: Optional[List[str]] = None,
38
- db_blocked: Optional[List[str]] = None,
37
+ db_allowed: Optional[list[str]] = None,
38
+ db_blocked: Optional[list[str]] = None,
39
39
  fetch_transient: Optional[bool] = False,
40
40
  ):
41
41
  super().__init__(time_filter=time_filter)
@@ -52,7 +52,7 @@ class SnowflakeQueryBuilder(AbstractQueryBuilder):
52
52
 
53
53
  return statement
54
54
 
55
- def build(self, asset: WarehouseAsset) -> List[ExtractionQuery]:
55
+ def build(self, asset: WarehouseAsset) -> list[ExtractionQuery]:
56
56
  query = self.build_default(asset)
57
57
 
58
58
  if asset in DB_FILTERED_ASSETS:
@@ -1,4 +1,4 @@
1
- from typing import Iterator
1
+ from collections.abc import Iterator
2
2
 
3
3
  from sqlalchemy import text
4
4
 
@@ -1,4 +1,4 @@
1
- from typing import List, Optional
1
+ from typing import Optional
2
2
 
3
3
  from ..abstract import (
4
4
  AbstractQueryBuilder,
@@ -19,6 +19,6 @@ class MSSQLQueryBuilder(AbstractQueryBuilder):
19
19
  ):
20
20
  super().__init__(time_filter=time_filter)
21
21
 
22
- def build(self, asset: WarehouseAsset) -> List[ExtractionQuery]:
22
+ def build(self, asset: WarehouseAsset) -> list[ExtractionQuery]:
23
23
  query = self.build_default(asset)
24
24
  return [query]
@@ -1,16 +1,15 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: castor-extractor
3
- Version: 0.21.9
3
+ Version: 0.22.1
4
4
  Summary: Extract your metadata assets.
5
5
  Home-page: https://www.castordoc.com/
6
6
  License: EULA
7
7
  Author: Castor
8
8
  Author-email: support@castordoc.com
9
- Requires-Python: >=3.8,<3.13
9
+ Requires-Python: >=3.9,<3.13
10
10
  Classifier: License :: Other/Proprietary License
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.8
14
13
  Classifier: Programming Language :: Python :: 3.9
15
14
  Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
@@ -20,6 +19,7 @@ Provides-Extra: bigquery
20
19
  Provides-Extra: databricks
21
20
  Provides-Extra: dbt
22
21
  Provides-Extra: looker
22
+ Provides-Extra: lookerstudio
23
23
  Provides-Extra: metabase
24
24
  Provides-Extra: mysql
25
25
  Provides-Extra: postgres
@@ -32,6 +32,7 @@ Provides-Extra: tableau
32
32
  Requires-Dist: cryptography (>=43.0.0,<44.0.0) ; extra == "snowflake"
33
33
  Requires-Dist: databricks-sql-connector (>=3.2.0,<4.0.0) ; extra == "databricks" or extra == "all"
34
34
  Requires-Dist: google-api-core (>=2.1.1,<3.0.0)
35
+ Requires-Dist: google-api-python-client (>=2.121.0,<3.0.0) ; extra == "lookerstudio" or extra == "all"
35
36
  Requires-Dist: google-auth (>=2,<3)
36
37
  Requires-Dist: google-cloud-core (>=2.1.0,<3.0.0)
37
38
  Requires-Dist: google-cloud-storage (>=2,<3)
@@ -39,10 +40,8 @@ Requires-Dist: google-resumable-media (>=2.0.3,<3.0.0)
39
40
  Requires-Dist: googleapis-common-protos (>=1.53.0,<2.0.0)
40
41
  Requires-Dist: looker-sdk (>=24.16.0,<24.17.0) ; extra == "looker" or extra == "all"
41
42
  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") and (extra == "bigquery" or extra == "databricks" or extra == "all")
43
43
  Requires-Dist: numpy (<2) ; extra == "bigquery" or extra == "databricks" or extra == "all"
44
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
45
  Requires-Dist: pandas (>=2.1) ; (python_version >= "3.12" and python_version < "3.13") and (extra == "databricks" or extra == "all")
47
46
  Requires-Dist: psycopg2-binary (>=2.0.0,<3.0.0) ; extra == "metabase" or extra == "postgres" or extra == "redshift" or extra == "all"
48
47
  Requires-Dist: pycryptodome (>=3.0.0,<4.0.0) ; extra == "metabase" or extra == "all"
@@ -52,7 +51,7 @@ Requires-Dist: pymssql (>=2.2.11,<3.0.0) ; extra == "sqlserver" or extra == "all
52
51
  Requires-Dist: pymysql[rsa] (>=1.1.0,<2.0.0) ; extra == "mysql" or extra == "all"
53
52
  Requires-Dist: python-dateutil (>=2.0.0,<=3.0.0)
54
53
  Requires-Dist: requests (>=2.0.0,<3.0.0)
55
- Requires-Dist: setuptools (>=75.3.0,<75.4.0)
54
+ Requires-Dist: setuptools (>=75.6)
56
55
  Requires-Dist: snowflake-connector-python (>=3.4.0,<4.0.0) ; extra == "snowflake" or extra == "all"
57
56
  Requires-Dist: snowflake-sqlalchemy (!=1.2.5,<2.0.0) ; extra == "snowflake" or extra == "all"
58
57
  Requires-Dist: sqlalchemy (>=1.4,<1.5)
@@ -208,6 +207,14 @@ For any questions or bug report, contact us at [support@castordoc.com](mailto:su
208
207
 
209
208
  # Changelog
210
209
 
210
+ ## 0.22.1 - 2024-12-05
211
+
212
+ * Salesforce: deduplicate tables
213
+
214
+ ## 0.22.0 - 2024-12-04
215
+
216
+ * Stop supporting python3.8
217
+
211
218
  ## 0.21.9 - 2024-12-04
212
219
 
213
220
  * Tableau: fix handling of timeout retry