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

Potentially problematic release.


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

CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.3 - 2024-04-24
4
+
5
+ * Databricks: Extract table owners
6
+
7
+ ## 0.16.2 - 2024-04-09
8
+
9
+ * PowerBI: Extract pages from report
10
+
3
11
  ## 0.16.1 - 2024-04-02
4
12
 
5
13
  * Systematically escape nul bytes on CSV write
@@ -14,7 +14,7 @@ RawData = Iterator[dict]
14
14
 
15
15
  DOMO_PUBLIC_URL = "https://api.domo.com"
16
16
  FORMAT = "%Y-%m-%d %I:%M:%S %p"
17
- DEFAULT_TIMEOUT = 300
17
+ DEFAULT_TIMEOUT = 500
18
18
  TOKEN_EXPIRATION_SECONDS = timedelta(seconds=3000) # auth token lasts 1 hour
19
19
 
20
20
  IGNORED_ERROR_CODES = (
@@ -14,18 +14,18 @@ POST = "POST"
14
14
  class Urls:
15
15
  """PowerBi's urls"""
16
16
 
17
- REST_API_BASE_PATH = "https://api.powerbi.com/v1.0/myorg"
18
17
  CLIENT_APP_BASE = "https://login.microsoftonline.com/"
19
18
  DEFAULT_SCOPE = "https://analysis.windows.net/powerbi/api/.default"
19
+ REST_API_BASE_PATH = "https://api.powerbi.com/v1.0/myorg"
20
20
 
21
21
  # PBI rest API Routes
22
22
  ACTIVITY_EVENTS = f"{REST_API_BASE_PATH}/admin/activityevents"
23
- DATASETS = f"{REST_API_BASE_PATH}/admin/datasets"
24
23
  DASHBOARD = f"{REST_API_BASE_PATH}/admin/dashboards"
24
+ DATASETS = f"{REST_API_BASE_PATH}/admin/datasets"
25
25
  GROUPS = f"{REST_API_BASE_PATH}/admin/groups"
26
+ METADATA_GET = f"{REST_API_BASE_PATH}/admin/workspaces/scanResult"
26
27
  METADATA_POST = f"{REST_API_BASE_PATH}/admin/workspaces/getInfo"
27
28
  METADATA_WAIT = f"{REST_API_BASE_PATH}/admin/workspaces/scanStatus"
28
- METADATA_GET = f"{REST_API_BASE_PATH}/admin/workspaces/scanResult"
29
29
  REPORTS = f"{REST_API_BASE_PATH}/admin/reports"
30
30
  WORKSPACE_IDS = (
31
31
  "https://api.powerbi.com/v1.0/myorg/admin/workspaces/modified"
@@ -64,19 +64,15 @@ class Client:
64
64
 
65
65
  def __init__(self, credentials: Credentials):
66
66
  self.creds = credentials
67
-
68
- def _access_token(self) -> dict:
69
67
  client_app = f"{Urls.CLIENT_APP_BASE}{self.creds.tenant_id}"
70
- app = msal.ConfidentialClientApplication(
68
+ self.app = msal.ConfidentialClientApplication(
71
69
  client_id=self.creds.client_id,
72
70
  authority=client_app,
73
71
  client_credential=self.creds.secret,
74
72
  )
75
73
 
76
- token = app.acquire_token_silent(self.creds.scopes, account=None)
77
-
78
- if not token:
79
- token = app.acquire_token_for_client(scopes=self.creds.scopes)
74
+ def _access_token(self) -> dict:
75
+ token = self.app.acquire_token_for_client(scopes=self.creds.scopes)
80
76
 
81
77
  if Keys.ACCESS_TOKEN not in token:
82
78
  raise ValueError(f"No access token in token response: {token}")
@@ -248,7 +244,17 @@ class Client:
248
244
  Returns a list of reports for the organization.
249
245
  https://learn.microsoft.com/en-us/rest/api/power-bi/admin/reports-get-reports-as-admin
250
246
  """
251
- return self._get(Urls.REPORTS)[Keys.VALUE]
247
+ reports = self._get(Urls.REPORTS)[Keys.VALUE]
248
+ for report in reports:
249
+ report_id = report.get("id")
250
+ try:
251
+ url = Urls.REPORTS + f"/{report_id}/pages"
252
+ pages = self._get(url)[Keys.VALUE]
253
+ report["pages"] = pages
254
+ except (requests.HTTPError, requests.exceptions.Timeout) as e:
255
+ logger.debug(e)
256
+ continue
257
+ return reports
252
258
 
253
259
  def _dashboards(self) -> List[Dict]:
254
260
  """
@@ -31,7 +31,6 @@ def test__access_token(mock_app):
31
31
  # init mocks
32
32
  valid_response = {"access_token": "mock_token"}
33
33
  returning_valid_token = Mock(return_value=valid_response)
34
- mock_app.return_value.acquire_token_silent = Mock(return_value=None)
35
34
  mock_app.return_value.acquire_token_for_client = returning_valid_token
36
35
 
37
36
  # init client
@@ -40,30 +39,29 @@ def test__access_token(mock_app):
40
39
  # generated token
41
40
  assert client._access_token() == valid_response
42
41
 
43
- # via silent endpoint token
44
- mock_app.return_value.acquire_token_silent = returning_valid_token
45
- mock_app.return_value.acquire_token_for_client = None
46
- assert client._access_token() == valid_response
47
-
48
42
  # token missing in response
49
43
  invalid_response = {"not_access_token": "666"}
50
44
  returning_invalid_token = Mock(return_value=invalid_response)
51
- mock_app.return_value.acquire_token_silent = returning_invalid_token
45
+ mock_app.return_value.acquire_token_for_client = returning_invalid_token
52
46
 
53
47
  with pytest.raises(ValueError):
54
48
  client._access_token()
55
49
 
56
50
 
51
+ @patch.object(msal, "ConfidentialClientApplication")
57
52
  @patch.object(Client, "_access_token")
58
- def test__headers(mock_access_token):
53
+ def test__headers(mock_access_token, mock_app):
54
+ mock_app.return_value = None
59
55
  client = _client()
60
56
  mock_access_token.return_value = {Keys.ACCESS_TOKEN: "666"}
61
57
  assert client._header() == {"Authorization": "Bearer 666"}
62
58
 
63
59
 
60
+ @patch.object(msal, "ConfidentialClientApplication")
64
61
  @patch("requests.request")
65
62
  @patch.object(Client, "_access_token")
66
- def test__get(mocked_access_token, mocked_request):
63
+ def test__get(mocked_access_token, mocked_request, mock_app):
64
+ mock_app.return_value = None
67
65
  client = _client()
68
66
  mocked_access_token.return_value = {Keys.ACCESS_TOKEN: "666"}
69
67
  fact = {"fact": "Approximately 24 cat skins can make a coat.", "length": 43}
@@ -81,9 +79,11 @@ def test__get(mocked_access_token, mocked_request):
81
79
  result = client._get("https/whatev.er")
82
80
 
83
81
 
82
+ @patch.object(msal, "ConfidentialClientApplication")
84
83
  @patch("requests.request")
85
84
  @patch.object(Client, "_access_token")
86
- def test__workspace_ids(_, mocked_request):
85
+ def test__workspace_ids(_, mocked_request, mock_app):
86
+ mock_app.return_value = None
87
87
  client = _client()
88
88
  mocked_request.return_value = Mock(
89
89
  json=lambda: [{"id": 1000}, {"id": 1001}, {"id": 1003}],
@@ -112,9 +112,11 @@ def test__workspace_ids(_, mocked_request):
112
112
  )
113
113
 
114
114
 
115
+ @patch.object(msal, "ConfidentialClientApplication")
115
116
  @patch("requests.request")
116
117
  @patch.object(Client, "_access_token")
117
- def test__post_default(_, mocked_request):
118
+ def test__post_default(_, mocked_request, mock_app):
119
+ mock_app.return_value = None
118
120
  client = _client()
119
121
  url = "https://estcequecestbientotleweekend.fr/"
120
122
  params = QueryParams.METADATA_SCAN
@@ -129,9 +131,11 @@ def test__post_default(_, mocked_request):
129
131
  )
130
132
 
131
133
 
134
+ @patch.object(msal, "ConfidentialClientApplication")
132
135
  @patch("requests.request")
133
136
  @patch.object(Client, "_access_token")
134
- def test__post_with_processor(_, mocked_request):
137
+ def test__post_with_processor(_, mocked_request, mock_app):
138
+ mock_app.return_value = None
135
139
  client = _client()
136
140
  url = "https://estcequecestbientotleweekend.fr/"
137
141
  params = QueryParams.METADATA_SCAN
@@ -146,9 +150,11 @@ def test__post_with_processor(_, mocked_request):
146
150
  assert result == 1000
147
151
 
148
152
 
153
+ @patch.object(msal, "ConfidentialClientApplication")
149
154
  @patch("requests.request")
150
155
  @patch.object(Client, "_access_token")
151
- def test__datasets(_, mocked_request):
156
+ def test__datasets(_, mocked_request, mock_app):
157
+ mock_app.return_value = None
152
158
  client = _client()
153
159
  mocked_request.return_value = Mock(
154
160
  json=lambda: {"value": [{"id": 1, "type": "dataset"}]},
@@ -164,27 +170,50 @@ def test__datasets(_, mocked_request):
164
170
  assert datasets == [{"id": 1, "type": "dataset"}]
165
171
 
166
172
 
173
+ @patch.object(msal, "ConfidentialClientApplication")
167
174
  @patch("requests.request")
168
175
  @patch.object(Client, "_access_token")
169
- def test__reports(_, mocked_request):
176
+ def test__reports(_, mocked_request, mock_app):
177
+ mock_app.return_value = None
170
178
  client = _client()
171
- mocked_request.return_value = Mock(
172
- json=lambda: {"value": [{"id": 1, "type": "report"}]},
173
- )
179
+ page_url = f"{Urls.REPORTS}/1/pages"
180
+ calls = [
181
+ call(GET, Urls.REPORTS, data=None, headers=ANY, params=None),
182
+ call(
183
+ GET,
184
+ page_url,
185
+ data=None,
186
+ headers=ANY,
187
+ params=None,
188
+ ),
189
+ ]
190
+ mocked_request.side_effect = [
191
+ Mock(json=lambda: {"value": [{"id": 1, "type": "report"}]}),
192
+ Mock(
193
+ json=lambda: {
194
+ "value": [
195
+ {"name": "page_name", "displayName": "page", "order": 0}
196
+ ]
197
+ }
198
+ ),
199
+ ]
174
200
  reports = client._reports()
175
- mocked_request.assert_called_with(
176
- GET,
177
- Urls.REPORTS,
178
- data=None,
179
- headers=ANY,
180
- params=None,
181
- )
182
- assert reports == [{"id": 1, "type": "report"}]
201
+ mocked_request.assert_has_calls(calls)
202
+
203
+ assert reports == [
204
+ {
205
+ "id": 1,
206
+ "type": "report",
207
+ "pages": [{"name": "page_name", "displayName": "page", "order": 0}],
208
+ }
209
+ ]
183
210
 
184
211
 
212
+ @patch.object(msal, "ConfidentialClientApplication")
185
213
  @patch("requests.request")
186
214
  @patch.object(Client, "_access_token")
187
- def test__dashboards(_, mocked_request):
215
+ def test__dashboards(_, mocked_request, mock_app):
216
+ mock_app.return_value = None
188
217
  client = _client()
189
218
  mocked_request.return_value = Mock(
190
219
  json=lambda: {"value": [{"id": 1, "type": "dashboard"}]},
@@ -200,6 +229,7 @@ def test__dashboards(_, mocked_request):
200
229
  assert dashboards == [{"id": 1, "type": "dashboard"}]
201
230
 
202
231
 
232
+ @patch.object(msal, "ConfidentialClientApplication")
203
233
  @patch.object(Client, "_workspace_ids")
204
234
  @patch.object(Client, "_create_scan")
205
235
  @patch.object(Client, "_wait_for_scan_result")
@@ -209,7 +239,9 @@ def test__metadata(
209
239
  mocked_wait,
210
240
  mocked_create_scan,
211
241
  mocked_workspace_ids,
242
+ mock_app,
212
243
  ):
244
+ mock_app.return_value = None
213
245
  mocked_workspace_ids.return_value = list(range(200))
214
246
  mocked_create_scan.return_value = 314
215
247
  mocked_wait.return_value = True
@@ -240,8 +272,10 @@ _CALLS = [
240
272
  ]
241
273
 
242
274
 
275
+ @patch.object(msal, "ConfidentialClientApplication")
243
276
  @patch.object(Client, "_call")
244
- def test__activity_events(mocked):
277
+ def test__activity_events(mocked, mock_app):
278
+ mock_app.return_value = None
245
279
  client = _client()
246
280
  mocked.side_effect = _CALLS
247
281
 
@@ -12,10 +12,12 @@ class TableauAsset(ExternalAsset):
12
12
  CUSTOM_SQL_TABLE = "custom_sql_tables"
13
13
  CUSTOM_SQL_QUERY = "custom_sql_queries"
14
14
  DASHBOARD = "dashboards"
15
+ DASHBOARD_SHEET = "dashboards_sheets"
15
16
  DATASOURCE = "datasources"
16
17
  FIELD = "fields"
17
18
  PROJECT = "projects"
18
19
  PUBLISHED_DATASOURCE = "published_datasources"
20
+ SHEET = "sheets"
19
21
  USAGE = "views"
20
22
  USER = "users"
21
23
  WORKBOOK = "workbooks"
@@ -25,7 +27,9 @@ class TableauAsset(ExternalAsset):
25
27
  def optional(cls) -> Set["TableauAsset"]:
26
28
  return {
27
29
  TableauAsset.DASHBOARD,
30
+ TableauAsset.DASHBOARD_SHEET,
28
31
  TableauAsset.FIELD,
32
+ TableauAsset.SHEET,
29
33
  TableauAsset.PUBLISHED_DATASOURCE,
30
34
  }
31
35
 
@@ -42,4 +46,5 @@ class TableauGraphqlAsset(Enum):
42
46
  DASHBOARD = "dashboards"
43
47
  DATASOURCE = "datasources"
44
48
  GROUP_FIELD = "groupFields"
49
+ SHEETS = "sheets"
45
50
  WORKBOOK_TO_DATASOURCE = "workbooks"
@@ -173,6 +173,13 @@ class ApiClient:
173
173
  TableauAsset.DASHBOARD,
174
174
  )
175
175
 
176
+ def _fetch_sheets(self) -> SerializedAsset:
177
+ """Fetches sheets"""
178
+
179
+ return self._fetch_paginated_objects(
180
+ TableauAsset.SHEET,
181
+ )
182
+
176
183
  def _fetch_paginated_objects(self, asset: TableauAsset) -> SerializedAsset:
177
184
  """Fetches paginated objects"""
178
185
 
@@ -203,6 +210,9 @@ class ApiClient:
203
210
  if asset == TableauAsset.PUBLISHED_DATASOURCE:
204
211
  assets = self._fetch_published_datasources()
205
212
 
213
+ if asset == TableauAsset.SHEET:
214
+ assets = self._fetch_sheets()
215
+
206
216
  if asset == TableauAsset.USAGE:
207
217
  assets = self._fetch_usages(self._safe_mode)
208
218
 
@@ -111,15 +111,15 @@ class GQLQueryFields(Enum):
111
111
  """
112
112
 
113
113
  DASHBOARDS: str = """
114
- id
115
- name
116
- path
117
- tags {
118
- name
119
- }
120
- workbook {
121
- luid # to retrieve the parent
122
- }
114
+ id
115
+ name
116
+ path
117
+ tags {
118
+ name
119
+ }
120
+ workbook {
121
+ luid # to retrieve the parent
122
+ }
123
123
  """
124
124
 
125
125
  DATASOURCE: str = """
@@ -160,6 +160,21 @@ class GQLQueryFields(Enum):
160
160
  role
161
161
  """
162
162
 
163
+ SHEET: str = """
164
+ containedInDashboards {
165
+ id
166
+ }
167
+ id
168
+ index
169
+ name
170
+ upstreamFields{
171
+ name
172
+ }
173
+ workbook {
174
+ luid
175
+ }
176
+ """
177
+
163
178
  WORKBOOK_TO_DATASOURCE: str = """
164
179
  luid
165
180
  id
@@ -219,6 +234,12 @@ QUERY_FIELDS: Dict[TableauAsset, QueryInfo] = {
219
234
  OBJECT_TYPE: TableauGraphqlAsset.GROUP_FIELD,
220
235
  },
221
236
  ],
237
+ TableauAsset.SHEET: [
238
+ {
239
+ FIELDS: GQLQueryFields.SHEET,
240
+ OBJECT_TYPE: TableauGraphqlAsset.SHEETS,
241
+ },
242
+ ],
222
243
  TableauAsset.WORKBOOK_TO_DATASOURCE: [
223
244
  {
224
245
  FIELDS: GQLQueryFields.WORKBOOK_TO_DATASOURCE,
@@ -87,15 +87,32 @@ class DatabricksClient(APIClient):
87
87
  content.get("tables", []), schema
88
88
  )
89
89
 
90
- def tables_and_columns(self, schemas: List[dict]) -> TablesColumns:
90
+ @staticmethod
91
+ def _match_table_with_user(table: dict, user_id_by_email: dict) -> dict:
92
+ table_owner_email = table.get("owner_email")
93
+ if not table_owner_email:
94
+ return table
95
+ owner_external_id = user_id_by_email.get(table_owner_email)
96
+ if not owner_external_id:
97
+ return table
98
+ return {**table, "owner_external_id": owner_external_id}
99
+
100
+ def tables_and_columns(
101
+ self, schemas: List[dict], users: List[dict]
102
+ ) -> TablesColumns:
91
103
  """
92
104
  Get the databricks tables & columns leveraging the unity catalog API
93
105
  """
94
106
  tables: List[dict] = []
95
107
  columns: List[dict] = []
108
+ user_id_by_email = {user.get("email"): user.get("id") for user in users}
96
109
  for schema in schemas:
97
110
  t_to_add, c_to_add = self._tables_columns_of_schema(schema)
98
- tables.extend(t_to_add)
111
+ t_with_owner = [
112
+ self._match_table_with_user(table, user_id_by_email)
113
+ for table in t_to_add
114
+ ]
115
+ tables.extend(t_with_owner)
99
116
  columns.extend(c_to_add)
100
117
  return tables, columns
101
118
 
@@ -64,3 +64,17 @@ def test_DatabricksClient__keep_catalog():
64
64
  assert client._keep_catalog("staging")
65
65
  assert not client._keep_catalog("dev")
66
66
  assert not client._keep_catalog("something_unknown")
67
+
68
+
69
+ def test_DatabricksClient__match_table_with_user():
70
+ client = MockDatabricksClient()
71
+ users_by_email = {"bob@castordoc.com": 3}
72
+
73
+ table = {"id": 1, "owner_email": "bob@castordoc.com"}
74
+ table_with_owner = client._match_table_with_user(table, users_by_email)
75
+
76
+ assert table_with_owner == {**table, "owner_external_id": 3}
77
+
78
+ table_without_owner = {"id": 1, "owner_email": None}
79
+ actual = client._match_table_with_user(table_without_owner, users_by_email)
80
+ assert actual == table_without_owner
@@ -82,7 +82,8 @@ class DatabricksExtractionProcessor:
82
82
 
83
83
  del databases
84
84
 
85
- tables, columns = self._client.tables_and_columns(schemas)
85
+ users = self._client.users()
86
+ tables, columns = self._client.tables_and_columns(schemas, users)
86
87
 
87
88
  location = self._storage.put(WarehouseAsset.TABLE.value, tables)
88
89
  catalog_locations[WarehouseAsset.TABLE.value] = location
@@ -19,10 +19,11 @@ def _to_datetime_or_none(time_ms: Optional[int]) -> Optional[datetime]:
19
19
 
20
20
  def _table_payload(schema: dict, table: dict) -> dict:
21
21
  return {
22
+ "description": table.get("comment"),
22
23
  "id": table["table_id"],
24
+ "owner_email": table.get("owner"),
23
25
  "schema_id": f"{schema['id']}",
24
26
  "table_name": table["name"],
25
- "description": table.get("comment"),
26
27
  "tags": [],
27
28
  "type": table.get("table_type"),
28
29
  }
@@ -30,12 +31,12 @@ def _table_payload(schema: dict, table: dict) -> dict:
30
31
 
31
32
  def _column_payload(table: dict, column: dict) -> dict:
32
33
  return {
33
- "id": f"`{table['id']}`.`{column['name']}`",
34
34
  "column_name": column["name"],
35
- "table_id": table["id"],
36
- "description": column.get("comment"),
37
35
  "data_type": column["type_name"],
36
+ "description": column.get("comment"),
37
+ "id": f"`{table['id']}`.`{column['name']}`",
38
38
  "ordinal_position": column["position"],
39
+ "table_id": table["id"],
39
40
  }
40
41
 
41
42
 
@@ -0,0 +1,86 @@
1
+ # End-User License Agreement (EULA) of "Castor Extractor"
2
+
3
+ This End-User License Agreement ("EULA") is a legal agreement between you and Castor. Our EULA was created by EULA Template (https://www.eulatemplate.com) for "Castor Extractor".
4
+
5
+ This EULA agreement governs your acquisition and use of our "Castor Extractor" software ("Software") directly from Castor or indirectly through a Castor authorized reseller or distributor (a "Reseller").
6
+
7
+ Please read this EULA agreement carefully before completing the installation process and using the "Castor Extractor" software. It provides a license to use the "Castor Extractor" software and contains warranty information and liability disclaimers.
8
+
9
+ If you register for a free trial of the "Castor Extractor" software, this EULA agreement will also govern that trial. By clicking "accept" or installing and/or using the "Castor Extractor" software, you are confirming your acceptance of the Software and agreeing to become bound by the terms of this EULA agreement.
10
+
11
+ If you are entering into this EULA agreement on behalf of a company or other legal entity, you represent that you have the authority to bind such entity and its affiliates to these terms and conditions. If you do not have such authority or if you do not agree with the terms and conditions of this EULA agreement, do not install or use the Software, and you must not accept this EULA agreement.
12
+
13
+ This EULA agreement shall apply only to the Software supplied by Castor herewith regardless of whether other software is referred to or described herein. The terms also apply to any Castor updates, supplements, Internet-based services, and support services for the Software, unless other terms accompany those items on delivery. If so, those terms apply.
14
+
15
+ ## License Grant
16
+
17
+ Castor hereby grants you a personal, non-transferable, non-exclusive licence to use the "Castor Extractor" software on your devices in accordance with the terms of this EULA agreement.
18
+
19
+ You are permitted to load the "Castor Extractor" software (for example a PC, laptop, mobile or tablet) under your control. You are responsible for ensuring your device meets the minimum requirements of the "Castor Extractor" software.
20
+
21
+ You are not permitted to:
22
+
23
+ - Edit, alter, modify, adapt, translate or otherwise change the whole or any part of the Software nor permit the whole or any part of the Software to be combined with or become incorporated in any other software, nor decompile, disassemble or reverse engineer the Software or attempt to do any such things
24
+ - Reproduce, copy, distribute, resell or otherwise use the Software for any commercial purpose
25
+ - Allow any third party to use the Software on behalf of or for the benefit of any third party
26
+ - Use the Software in any way which breaches any applicable local, national or international law
27
+ - use the Software for any purpose that Castor considers is a breach of this EULA agreement
28
+
29
+ ## Intellectual Property and Ownership
30
+
31
+ Castor shall at all times retain ownership of the Software as originally downloaded by you and all subsequent downloads of the Software by you. The Software (and the copyright, and other intellectual property rights of whatever nature in the Software, including any modifications made thereto) are and shall remain the property of Castor.
32
+
33
+ Castor reserves the right to grant licences to use the Software to third parties.
34
+
35
+ ## Termination
36
+
37
+ This EULA agreement is effective from the date you first use the Software and shall continue until terminated. You may terminate it at any time upon written notice to Castor.
38
+
39
+ It will also terminate immediately if you fail to comply with any term of this EULA agreement. Upon such termination, the licenses granted by this EULA agreement will immediately terminate and you agree to stop all access and use of the Software. The provisions that by their nature continue and survive will survive any termination of this EULA agreement.
40
+
41
+ ## Governing Law
42
+
43
+ This EULA agreement, and any dispute arising out of or in connection with this EULA agreement, shall be governed by and construed in accordance with the laws of France.
44
+ # End-User License Agreement (EULA) of "Castor Extractor"
45
+
46
+ This End-User License Agreement ("EULA") is a legal agreement between you and Castor. Our EULA was created by EULA Template (https://www.eulatemplate.com) for "Castor Extractor".
47
+
48
+ This EULA agreement governs your acquisition and use of our "Castor Extractor" software ("Software") directly from Castor or indirectly through a Castor authorized reseller or distributor (a "Reseller").
49
+
50
+ Please read this EULA agreement carefully before completing the installation process and using the "Castor Extractor" software. It provides a license to use the "Castor Extractor" software and contains warranty information and liability disclaimers.
51
+
52
+ If you register for a free trial of the "Castor Extractor" software, this EULA agreement will also govern that trial. By clicking "accept" or installing and/or using the "Castor Extractor" software, you are confirming your acceptance of the Software and agreeing to become bound by the terms of this EULA agreement.
53
+
54
+ If you are entering into this EULA agreement on behalf of a company or other legal entity, you represent that you have the authority to bind such entity and its affiliates to these terms and conditions. If you do not have such authority or if you do not agree with the terms and conditions of this EULA agreement, do not install or use the Software, and you must not accept this EULA agreement.
55
+
56
+ This EULA agreement shall apply only to the Software supplied by Castor herewith regardless of whether other software is referred to or described herein. The terms also apply to any Castor updates, supplements, Internet-based services, and support services for the Software, unless other terms accompany those items on delivery. If so, those terms apply.
57
+
58
+ ## License Grant
59
+
60
+ Castor hereby grants you a personal, non-transferable, non-exclusive licence to use the "Castor Extractor" software on your devices in accordance with the terms of this EULA agreement.
61
+
62
+ You are permitted to load the "Castor Extractor" software (for example a PC, laptop, mobile or tablet) under your control. You are responsible for ensuring your device meets the minimum requirements of the "Castor Extractor" software.
63
+
64
+ You are not permitted to:
65
+
66
+ - Edit, alter, modify, adapt, translate or otherwise change the whole or any part of the Software nor permit the whole or any part of the Software to be combined with or become incorporated in any other software, nor decompile, disassemble or reverse engineer the Software or attempt to do any such things
67
+ - Reproduce, copy, distribute, resell or otherwise use the Software for any commercial purpose
68
+ - Allow any third party to use the Software on behalf of or for the benefit of any third party
69
+ - Use the Software in any way which breaches any applicable local, national or international law
70
+ - use the Software for any purpose that Castor considers is a breach of this EULA agreement
71
+
72
+ ## Intellectual Property and Ownership
73
+
74
+ Castor shall at all times retain ownership of the Software as originally downloaded by you and all subsequent downloads of the Software by you. The Software (and the copyright, and other intellectual property rights of whatever nature in the Software, including any modifications made thereto) are and shall remain the property of Castor.
75
+
76
+ Castor reserves the right to grant licences to use the Software to third parties.
77
+
78
+ ## Termination
79
+
80
+ This EULA agreement is effective from the date you first use the Software and shall continue until terminated. You may terminate it at any time upon written notice to Castor.
81
+
82
+ It will also terminate immediately if you fail to comply with any term of this EULA agreement. Upon such termination, the licenses granted by this EULA agreement will immediately terminate and you agree to stop all access and use of the Software. The provisions that by their nature continue and survive will survive any termination of this EULA agreement.
83
+
84
+ ## Governing Law
85
+
86
+ This EULA agreement, and any dispute arising out of or in connection with this EULA agreement, shall be governed by and construed in accordance with the laws of France.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: castor-extractor
3
- Version: 0.16.1
3
+ Version: 0.16.3
4
4
  Summary: Extract your metadata assets.
5
5
  Home-page: https://www.castordoc.com/
6
6
  License: EULA
@@ -27,10 +27,9 @@ Provides-Extra: redshift
27
27
  Provides-Extra: snowflake
28
28
  Provides-Extra: sqlserver
29
29
  Provides-Extra: tableau
30
- Requires-Dist: click (>=8.1,<8.2)
31
30
  Requires-Dist: cryptography (>=41.0.5) ; extra == "snowflake"
32
31
  Requires-Dist: google-api-core (>=2.1.1,<3.0.0)
33
- Requires-Dist: google-auth (>=1.6.3,<3.0.0)
32
+ Requires-Dist: google-auth (>=2,<3)
34
33
  Requires-Dist: google-cloud-core (>=2.1.0,<3.0.0)
35
34
  Requires-Dist: google-cloud-storage (>=2,<3)
36
35
  Requires-Dist: google-resumable-media (>=2.0.3,<3.0.0)
@@ -1,4 +1,4 @@
1
- CHANGELOG.md,sha256=t1xfX_GaaTJcrNGAJtvhbOZ-4fAeRdFKRH8eKil6xWM,9837
1
+ CHANGELOG.md,sha256=eRvmcZqJY1G4yDR2CzrA5wzf6xpeZM80HzVBw1tUynw,9959
2
2
  Dockerfile,sha256=HcX5z8OpeSvkScQsN-Y7CNMUig_UB6vTMDl7uqzuLGE,303
3
3
  LICENCE,sha256=sL-IGa4hweyya1HgzMskrRdybbIa2cktzxb5qmUgDg8,8254
4
4
  README.md,sha256=uF6PXm9ocPITlKVSh9afTakHmpLx3TvawLf-CbMP3wM,3578
@@ -95,7 +95,7 @@ castor_extractor/visualization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
95
95
  castor_extractor/visualization/domo/__init__.py,sha256=_mAYVfoVLizfLGF_f6ZiwBhdPpvoJY_diySf33dt3Jo,127
96
96
  castor_extractor/visualization/domo/assets.py,sha256=bK1urFR2tnlWkVkkhR32mAKMoKbESNlop-CNGx-65PY,206
97
97
  castor_extractor/visualization/domo/client/__init__.py,sha256=UDszV3IXNC9Wp_j55NZ-6ey2INo0TYtAg2QNIJOjglE,88
98
- castor_extractor/visualization/domo/client/client.py,sha256=iGQdsfOYEOvlfzYmraZuLqGZkJtmS33HLa4Cx9tehaw,11075
98
+ castor_extractor/visualization/domo/client/client.py,sha256=ZJJh0ZWuh1X4_uW6FEcZUUAxgRRVFh9vWUtS5aJly4Q,11075
99
99
  castor_extractor/visualization/domo/client/client_test.py,sha256=qGGmpJHnm-o2Ybko5J31nSM9xev5YS0yXjVNM9E92b4,2378
100
100
  castor_extractor/visualization/domo/client/credentials.py,sha256=CksQ9W9X6IGjTlYN0okwGAmURMRJKAjctxODAvAJUAo,1148
101
101
  castor_extractor/visualization/domo/client/endpoints.py,sha256=_D_gq99d77am4KHeRCkITM_XyYuAOscHagC7t3adscY,2019
@@ -157,11 +157,11 @@ castor_extractor/visualization/mode/extract.py,sha256=ZmIOmVZ8_fo4qXe15G5Sis_IzZ
157
157
  castor_extractor/visualization/powerbi/__init__.py,sha256=XSr_fNSsR-EPuGOFo7Ai1r7SttiN7bzD3jyYRFXUWgQ,106
158
158
  castor_extractor/visualization/powerbi/assets.py,sha256=SASUjxtoOMag3NAlZfhpCy0sLap7WfENEMaEZuBrw6o,801
159
159
  castor_extractor/visualization/powerbi/client/__init__.py,sha256=hU8LE1gV9RttTGJiwVpEa9xDLR4IMkUdshQGthg4zzE,62
160
- castor_extractor/visualization/powerbi/client/constants.py,sha256=Cx4pbgyAFc7t_aRQyWj7q-qfkltJl-JgKdMzeKmC9AI,2356
160
+ castor_extractor/visualization/powerbi/client/constants.py,sha256=gpcWE3Ov2xvZAZCqOsvzLtd3cWmfZBeQWvLnnt7-gac,2356
161
161
  castor_extractor/visualization/powerbi/client/credentials.py,sha256=iiYaCa2FM1PBHv4YA0Z1LgdX9gnaQhvHGD0LQb7Tcxw,465
162
162
  castor_extractor/visualization/powerbi/client/credentials_test.py,sha256=23ZlLCvsPB_fmqntnzULkv0mMRE8NCzBXtWS6wupJn4,787
163
- castor_extractor/visualization/powerbi/client/rest.py,sha256=0gwqqmmzX76MzNRGfmcNkXw_jxVRAdMTViQExTBQy2Y,9644
164
- castor_extractor/visualization/powerbi/client/rest_test.py,sha256=r5rS_1FMwHCDWbYdco11-zvDJ5jYk9l8-VVJcpCtbwM,7343
163
+ castor_extractor/visualization/powerbi/client/rest.py,sha256=_MhJYa9dKla4bMv01GZLFdAMrr6gwHdSWuc_D63gMF0,9949
164
+ castor_extractor/visualization/powerbi/client/rest_test.py,sha256=yAfsksL-4SZ6gxRjAWGqIGUAX5Gz44j56r-jRlPGDro,8514
165
165
  castor_extractor/visualization/powerbi/client/utils.py,sha256=0RcoWcKOdvIGH4f3lYDvufmiMo4tr_ABFlITSrvXjTs,541
166
166
  castor_extractor/visualization/powerbi/client/utils_test.py,sha256=ULHL2JLrcv0xjW2r7QF_ce2OaGeeSzajkMDywJ8ZdVA,719
167
167
  castor_extractor/visualization/powerbi/extract.py,sha256=0rTvI5CiWTpoJx6bGdpShdl4eMBWjuWbRpKvisuLPbw,1328
@@ -202,9 +202,9 @@ castor_extractor/visualization/sigma/client/pagination.py,sha256=EZGMaONTzZ15VIN
202
202
  castor_extractor/visualization/sigma/constants.py,sha256=6oQKTKNQkHP_9GWvSOKeFaXd3pKJLhn9Mfod4nvOLEs,144
203
203
  castor_extractor/visualization/sigma/extract.py,sha256=OgjUsc1o6lPPaO5XHgCrgQelBrqbdemxKggF4JBPBUI,2678
204
204
  castor_extractor/visualization/tableau/__init__.py,sha256=hDohrWjkorrX01JMc154aa9vi3ZqBKmA1lkfQtMFfYE,114
205
- castor_extractor/visualization/tableau/assets.py,sha256=QcHrRGBbZtVGXfgvmKeouFdAA0XN0sGCZ6X4MT3FEBc,1111
205
+ castor_extractor/visualization/tableau/assets.py,sha256=mfBUzcBCLyiU9gnTB_6rvtiB5yXSDU99nezhGC__HQo,1270
206
206
  castor_extractor/visualization/tableau/client/__init__.py,sha256=FQX1MdxS8Opn3Oyq8eby7suk3ANbLlpzzCPQ3zqvk0I,78
207
- castor_extractor/visualization/tableau/client/client.py,sha256=_R15GemmBKmn9kYmgH5_BvHAg6hutlVdr006hv4XoW8,7131
207
+ castor_extractor/visualization/tableau/client/client.py,sha256=YqLumujwaX3XkIGSTQMlpxqg7z3_7rm2Qof1HfSbUcY,7381
208
208
  castor_extractor/visualization/tableau/client/client_utils.py,sha256=taTCeK41nbwXTZeWCBbFxfCSlNnEq4Qfaxlle7yJVic,2094
209
209
  castor_extractor/visualization/tableau/client/credentials.py,sha256=szq2tM6sOZqtdyHZgPCNUAddETrwTZaDizLqp-aVBEw,3386
210
210
  castor_extractor/visualization/tableau/client/project.py,sha256=uLlZ5-eZI_4VxBmEB5d1gWy_X_w6uVt2EKoiX9cJ0UA,812
@@ -212,7 +212,7 @@ castor_extractor/visualization/tableau/client/safe_mode.py,sha256=MF_PTfR3oAA255
212
212
  castor_extractor/visualization/tableau/constants.py,sha256=O2CqeviFz122BumNHoJ1N-e1lzyqIHF9OYnGQttg4hg,126
213
213
  castor_extractor/visualization/tableau/errors.py,sha256=WWvmnp5pdxFJqanPKeDRADZc0URSPxkJqxDI6bwoifQ,91
214
214
  castor_extractor/visualization/tableau/extract.py,sha256=tCa5g1_RyhS7OeeEMhLJReMIYfQCF1cwHt8KbZoHFyI,3040
215
- castor_extractor/visualization/tableau/gql_fields.py,sha256=leyE_z723sZzFX_tt1O6YZr_coffxH76QI_3bb9qdNo,5041
215
+ castor_extractor/visualization/tableau/gql_fields.py,sha256=6Esb54CVd4j0Gw-kdCT4eVjwpPlnRF_GC4oxuNJpuas,5532
216
216
  castor_extractor/visualization/tableau/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
217
217
  castor_extractor/visualization/tableau/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
218
218
  castor_extractor/visualization/tableau/tests/unit/assets/graphql/metadata/metadata_1_get.json,sha256=4iMvJ_VakDa67xN2ROraAccaz_DDxX6Y5Y1XnTU5F5Y,446
@@ -262,11 +262,11 @@ castor_extractor/warehouse/bigquery/queries/view_ddl.sql,sha256=obCm-IN9V8_YSZTw
262
262
  castor_extractor/warehouse/bigquery/query.py,sha256=hrFfjd5jW2oQnZ6ozlkn-gDe6sCIzu5zSX19T9W6fIk,4162
263
263
  castor_extractor/warehouse/bigquery/types.py,sha256=LZVWSmE57lOemNbB5hBRyYmDk9bFAU4nbRaJWALl6N8,140
264
264
  castor_extractor/warehouse/databricks/__init__.py,sha256=bTvDxjGQGM2J3hOnVhfNmFP1y8DK0tySiD_EXe5_xWE,200
265
- castor_extractor/warehouse/databricks/client.py,sha256=iojSVTARx5JmGy2Tm8D2H5wHO5hqGigVG9Ql2vHNdz8,7375
266
- castor_extractor/warehouse/databricks/client_test.py,sha256=rsqHWmVOgvqQ3VmYKJrpWpcGATD_C9FD1sG4CJsin2E,2201
265
+ castor_extractor/warehouse/databricks/client.py,sha256=u1KpiG16IlFbaEVAIzBlxnzTk_bARGh-D0sZBXtgF4c,8043
266
+ castor_extractor/warehouse/databricks/client_test.py,sha256=ctOQnUXosuuFjWGJKgkxjUcV4vQUBWt2BQ_f0Tyzqe4,2717
267
267
  castor_extractor/warehouse/databricks/credentials.py,sha256=sMpOAKhBklcmTpcr3mi3o8qLud__8PTZbQUT3K_TRY8,678
268
- castor_extractor/warehouse/databricks/extract.py,sha256=eyt9LihZ9GfHEh8Z2c9PXAHqK6hibPsEIUOKGYfMwg8,5708
269
- castor_extractor/warehouse/databricks/format.py,sha256=tCBCApW5iZMBx04p-oCUs36d4JqNqJsBDHe6f-A7eiU,4925
268
+ castor_extractor/warehouse/databricks/extract.py,sha256=mgl1_b9Mlir9ZU3R5HV689YlhzzhlyVN8IaBHaNwY54,5752
269
+ castor_extractor/warehouse/databricks/format.py,sha256=LiPGCTPzL3gQQMMl1v6DvpcTk7BWxZFq03jnHdoYnuU,4968
270
270
  castor_extractor/warehouse/databricks/format_test.py,sha256=iPmdJof43fBYL1Sa_fBrCWDQHCHgm7IWCZag1kWkj9E,1970
271
271
  castor_extractor/warehouse/databricks/types.py,sha256=T2SyLy9pY_olLtstdC77moPxIiikVsuQLMxh92YMJQo,78
272
272
  castor_extractor/warehouse/mysql/__init__.py,sha256=2KFDogo9GNbApHqw3Vm5t_uNmIRjdp76nmP_WQQMfQY,116
@@ -346,7 +346,8 @@ castor_extractor/warehouse/synapse/queries/schema.sql,sha256=aX9xNrBD_ydwl-znGSF
346
346
  castor_extractor/warehouse/synapse/queries/table.sql,sha256=mCE8bR1Vb7j7SwZW2gafcXidQ2fo1HwxcybA8wP2Kfs,1049
347
347
  castor_extractor/warehouse/synapse/queries/user.sql,sha256=sTb_SS7Zj3AXW1SggKPLNMCd0qoTpL7XI_BJRMaEpBg,67
348
348
  castor_extractor/warehouse/synapse/queries/view_ddl.sql,sha256=3EVbp5_yTgdByHFIPLHmnoOnqqLE77SrjAwFDvu4e54,249
349
- castor_extractor-0.16.1.dist-info/METADATA,sha256=tjFndsdmxa0NO7qy3Ddcz1pFZGnDsfPmTQ3x3o-UEeA,6412
350
- castor_extractor-0.16.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
351
- castor_extractor-0.16.1.dist-info/entry_points.txt,sha256=EQUCoNjSHevxmY5ZathX_fLZPcuBHng23rj0SSUrLtI,1345
352
- castor_extractor-0.16.1.dist-info/RECORD,,
349
+ castor_extractor-0.16.3.dist-info/LICENCE,sha256=sL-IGa4hweyya1HgzMskrRdybbIa2cktzxb5qmUgDg8,8254
350
+ castor_extractor-0.16.3.dist-info/METADATA,sha256=CaGi5itpnLSjjCv5PpKayJ2Oi859ewvcyrPFzHNIdYM,6370
351
+ castor_extractor-0.16.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
352
+ castor_extractor-0.16.3.dist-info/entry_points.txt,sha256=EQUCoNjSHevxmY5ZathX_fLZPcuBHng23rj0SSUrLtI,1345
353
+ castor_extractor-0.16.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.8.1
2
+ Generator: poetry-core 1.9.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any