Skip to content

Commit cda018b

Browse files
jacalatajorwoodsbcantonicasey-crawford-cfa
authored
v0.38 - IDP configuration (#1606)
* docs: docstrings for schedules and intervals (#1528) * docs: Docstrings for new fields * feat: enable retrieving only owned workbooks * feat: Add support for multiple IDPs (jorwoods) * feat: Add fields:_all_ support (#1563) * feat: project support all fields * feat: groups all fields * feat: views support all fields * feat: user support _all_ fields * feat: workbook support all fields * feat: datasourceitem _all_ fields * feat: add fields methods to QuerySet * feat: add owner attribute to project * Add SSL option for connecting to Tableau Server with a weaker DH key length Fixes #1582 * feat: 1580 list extracts on schedule (#1604) * chore: type hint database and table objects (#1593) * ci: Switch Slack action to use `ubuntu-latest` like our other actions. --------- Co-authored-by: Jordan Woods <jorwoods@users.noreply.github.com> Co-authored-by: Jordan Woods <13803242+jorwoods@users.noreply.github.com> Co-authored-by: Brian Cantoni <bcantoni@salesforce.com> Co-authored-by: casey-crawford-cfa <91914995+casey-crawford-cfa@users.noreply.github.com>
1 parent bf85704 commit cda018b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2275
-106
lines changed

.github/workflows/slack.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on: [push, pull_request, issues]
55
jobs:
66
slack-notifications:
77
continue-on-error: true
8-
runs-on: ubuntu-20.04
8+
runs-on: ubuntu-latest
99
name: Sends a message to Slack when a push, a pull request or an issue is made
1010
steps:
1111
- name: Send message to Slack API

samples/extracts.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ def main():
4242
server.add_http_options({"verify": False})
4343
server.use_server_version()
4444
with server.auth.sign_in(tableau_auth):
45-
4645
wb = None
4746
ds = None
4847
if args.workbook:

tableauserverclient/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
LinkedTaskItem,
2626
LinkedTaskStepItem,
2727
LinkedTaskFlowRunItem,
28+
LocationItem,
2829
MetricItem,
2930
MonthlyInterval,
3031
PaginationItem,
@@ -35,6 +36,7 @@
3536
Resource,
3637
RevisionItem,
3738
ScheduleItem,
39+
SiteAuthConfiguration,
3840
SiteItem,
3941
ServerInfoItem,
4042
SubscriptionItem,
@@ -101,6 +103,7 @@
101103
"LinkedTaskFlowRunItem",
102104
"LinkedTaskItem",
103105
"LinkedTaskStepItem",
106+
"LocationItem",
104107
"MetricItem",
105108
"MissingRequiredFieldError",
106109
"MonthlyInterval",
@@ -121,6 +124,7 @@
121124
"ServerInfoItem",
122125
"ServerResponseError",
123126
"SiteItem",
127+
"SiteAuthConfiguration",
124128
"Sort",
125129
"SubscriptionItem",
126130
"TableauAuth",

tableauserverclient/helpers/strings.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from defusedxml.ElementTree import fromstring, tostring
22
from functools import singledispatch
3-
from typing import TypeVar
3+
from typing import TypeVar, overload
44

55

66
# the redact method can handle either strings or bytes, but it can't mix them.
@@ -41,3 +41,27 @@ def _(xml: str) -> str:
4141
@redact_xml.register # type: ignore[no-redef]
4242
def _(xml: bytes) -> bytes:
4343
return _redact_any_type(bytearray(xml), b"password", b"..[redacted]")
44+
45+
46+
@overload
47+
def nullable_str_to_int(value: None) -> None: ...
48+
49+
50+
@overload
51+
def nullable_str_to_int(value: str) -> int: ...
52+
53+
54+
def nullable_str_to_int(value):
55+
return int(value) if value is not None else None
56+
57+
58+
@overload
59+
def nullable_str_to_bool(value: None) -> None: ...
60+
61+
62+
@overload
63+
def nullable_str_to_bool(value: str) -> bool: ...
64+
65+
66+
def nullable_str_to_bool(value):
67+
return str(value).lower() == "true" if value is not None else None

tableauserverclient/models/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@
2828
LinkedTaskStepItem,
2929
LinkedTaskFlowRunItem,
3030
)
31+
from tableauserverclient.models.location_item import LocationItem
3132
from tableauserverclient.models.metric_item import MetricItem
3233
from tableauserverclient.models.pagination_item import PaginationItem
3334
from tableauserverclient.models.permissions_item import PermissionsRule, Permission
3435
from tableauserverclient.models.project_item import ProjectItem
3536
from tableauserverclient.models.revision_item import RevisionItem
3637
from tableauserverclient.models.schedule_item import ScheduleItem
3738
from tableauserverclient.models.server_info_item import ServerInfoItem
38-
from tableauserverclient.models.site_item import SiteItem
39+
from tableauserverclient.models.site_item import SiteItem, SiteAuthConfiguration
3940
from tableauserverclient.models.subscription_item import SubscriptionItem
4041
from tableauserverclient.models.table_item import TableItem
4142
from tableauserverclient.models.tableau_auth import Credentials, TableauAuth, PersonalAccessTokenAuth, JWTAuth
@@ -48,6 +49,7 @@
4849
from tableauserverclient.models.virtual_connection_item import VirtualConnectionItem
4950
from tableauserverclient.models.webhook_item import WebhookItem
5051
from tableauserverclient.models.workbook_item import WorkbookItem
52+
from tableauserverclient.models.extract_item import ExtractItem
5153

5254
__all__ = [
5355
"ColumnItem",
@@ -75,6 +77,7 @@
7577
"MonthlyInterval",
7678
"HourlyInterval",
7779
"BackgroundJobItem",
80+
"LocationItem",
7881
"MetricItem",
7982
"PaginationItem",
8083
"Permission",
@@ -83,6 +86,7 @@
8386
"RevisionItem",
8487
"ScheduleItem",
8588
"ServerInfoItem",
89+
"SiteAuthConfiguration",
8690
"SiteItem",
8791
"SubscriptionItem",
8892
"TableItem",
@@ -103,4 +107,5 @@
103107
"LinkedTaskItem",
104108
"LinkedTaskStepItem",
105109
"LinkedTaskFlowRunItem",
110+
"ExtractItem",
106111
]

tableauserverclient/models/datasource_item.py

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@
66
from defusedxml.ElementTree import fromstring
77

88
from tableauserverclient.datetime_helpers import parse_datetime
9+
from tableauserverclient.helpers.strings import nullable_str_to_bool, nullable_str_to_int
910
from tableauserverclient.models.connection_item import ConnectionItem
1011
from tableauserverclient.models.exceptions import UnpopulatedPropertyError
1112
from tableauserverclient.models.permissions_item import PermissionsRule
13+
from tableauserverclient.models.project_item import ProjectItem
1214
from tableauserverclient.models.property_decorators import (
1315
property_not_nullable,
1416
property_is_boolean,
1517
property_is_enum,
1618
)
1719
from tableauserverclient.models.revision_item import RevisionItem
1820
from tableauserverclient.models.tag_item import TagItem
21+
from tableauserverclient.models.user_item import UserItem
1922

2023

2124
class DatasourceItem:
@@ -40,6 +43,9 @@ class DatasourceItem:
4043
specified, it will default to SiteDefault. See REST API Publish
4144
Datasource for more information about ask_data_enablement.
4245
46+
connected_workbooks_count : Optional[int]
47+
The number of workbooks connected to the datasource.
48+
4349
connections : list[ConnectionItem]
4450
The list of data connections (ConnectionItem) for the specified data
4551
source. You must first call the populate_connections method to access
@@ -67,6 +73,12 @@ class DatasourceItem:
6773
A Boolean value to determine if a datasource should be encrypted or not.
6874
See Extract and Encryption Methods for more information.
6975
76+
favorites_total : Optional[int]
77+
The number of users who have marked the data source as a favorite.
78+
79+
has_alert : Optional[bool]
80+
A Boolean value that indicates whether the data source has an alert.
81+
7082
has_extracts : Optional[bool]
7183
A Boolean value that indicates whether the datasource has extracts.
7284
@@ -75,20 +87,32 @@ class DatasourceItem:
7587
specific data source or to delete a data source with the get_by_id and
7688
delete methods.
7789
90+
is_published : Optional[bool]
91+
A Boolean value that indicates whether the data source is published.
92+
7893
name : Optional[str]
7994
The name of the data source. If not specified, the name of the published
8095
data source file is used.
8196
97+
owner: Optional[UserItem]
98+
The owner of the data source.
99+
82100
owner_id : Optional[str]
83101
The identifier of the owner of the data source.
84102
103+
project : Optional[ProjectItem]
104+
The project that the data source belongs to.
105+
85106
project_id : Optional[str]
86107
The identifier of the project associated with the data source. You must
87108
provide this identifier when you create an instance of a DatasourceItem.
88109
89110
project_name : Optional[str]
90111
The name of the project associated with the data source.
91112
113+
server_name : Optional[str]
114+
The name of the server where the data source is published.
115+
92116
tags : Optional[set[str]]
93117
The tags (list of strings) that have been added to the data source.
94118
@@ -143,6 +167,13 @@ def __init__(self, project_id: Optional[str] = None, name: Optional[str] = None)
143167
self.owner_id: Optional[str] = None
144168
self.project_id: Optional[str] = project_id
145169
self.tags: set[str] = set()
170+
self._connected_workbooks_count: Optional[int] = None
171+
self._favorites_total: Optional[int] = None
172+
self._has_alert: Optional[bool] = None
173+
self._is_published: Optional[bool] = None
174+
self._server_name: Optional[str] = None
175+
self._project: Optional[ProjectItem] = None
176+
self._owner: Optional[UserItem] = None
146177

147178
self._permissions = None
148179
self._data_quality_warnings = None
@@ -274,14 +305,42 @@ def revisions(self) -> list[RevisionItem]:
274305
def size(self) -> Optional[int]:
275306
return self._size
276307

308+
@property
309+
def connected_workbooks_count(self) -> Optional[int]:
310+
return self._connected_workbooks_count
311+
312+
@property
313+
def favorites_total(self) -> Optional[int]:
314+
return self._favorites_total
315+
316+
@property
317+
def has_alert(self) -> Optional[bool]:
318+
return self._has_alert
319+
320+
@property
321+
def is_published(self) -> Optional[bool]:
322+
return self._is_published
323+
324+
@property
325+
def server_name(self) -> Optional[str]:
326+
return self._server_name
327+
328+
@property
329+
def project(self) -> Optional[ProjectItem]:
330+
return self._project
331+
332+
@property
333+
def owner(self) -> Optional[UserItem]:
334+
return self._owner
335+
277336
def _set_connections(self, connections) -> None:
278337
self._connections = connections
279338

280339
def _set_permissions(self, permissions):
281340
self._permissions = permissions
282341

283-
def _set_data_quality_warnings(self, dqws):
284-
self._data_quality_warnings = dqws
342+
def _set_data_quality_warnings(self, dqw):
343+
self._data_quality_warnings = dqw
285344

286345
def _set_revisions(self, revisions):
287346
self._revisions = revisions
@@ -310,6 +369,13 @@ def _parse_common_elements(self, datasource_xml, ns):
310369
use_remote_query_agent,
311370
webpage_url,
312371
size,
372+
connected_workbooks_count,
373+
favorites_total,
374+
has_alert,
375+
is_published,
376+
server_name,
377+
project,
378+
owner,
313379
) = self._parse_element(datasource_xml, ns)
314380
self._set_values(
315381
ask_data_enablement,
@@ -331,6 +397,13 @@ def _parse_common_elements(self, datasource_xml, ns):
331397
use_remote_query_agent,
332398
webpage_url,
333399
size,
400+
connected_workbooks_count,
401+
favorites_total,
402+
has_alert,
403+
is_published,
404+
server_name,
405+
project,
406+
owner,
334407
)
335408
return self
336409

@@ -355,6 +428,13 @@ def _set_values(
355428
use_remote_query_agent,
356429
webpage_url,
357430
size,
431+
connected_workbooks_count,
432+
favorites_total,
433+
has_alert,
434+
is_published,
435+
server_name,
436+
project,
437+
owner,
358438
):
359439
if ask_data_enablement is not None:
360440
self._ask_data_enablement = ask_data_enablement
@@ -394,6 +474,20 @@ def _set_values(
394474
self._webpage_url = webpage_url
395475
if size is not None:
396476
self._size = int(size)
477+
if connected_workbooks_count is not None:
478+
self._connected_workbooks_count = connected_workbooks_count
479+
if favorites_total is not None:
480+
self._favorites_total = favorites_total
481+
if has_alert is not None:
482+
self._has_alert = has_alert
483+
if is_published is not None:
484+
self._is_published = is_published
485+
if server_name is not None:
486+
self._server_name = server_name
487+
if project is not None:
488+
self._project = project
489+
if owner is not None:
490+
self._owner = owner
397491

398492
@classmethod
399493
def from_response(cls, resp: str, ns: dict) -> list["DatasourceItem"]:
@@ -428,6 +522,11 @@ def _parse_element(datasource_xml: ET.Element, ns: dict) -> tuple:
428522
use_remote_query_agent = datasource_xml.get("useRemoteQueryAgent", None)
429523
webpage_url = datasource_xml.get("webpageUrl", None)
430524
size = datasource_xml.get("size", None)
525+
connected_workbooks_count = nullable_str_to_int(datasource_xml.get("connectedWorkbooksCount", None))
526+
favorites_total = nullable_str_to_int(datasource_xml.get("favoritesTotal", None))
527+
has_alert = nullable_str_to_bool(datasource_xml.get("hasAlert", None))
528+
is_published = nullable_str_to_bool(datasource_xml.get("isPublished", None))
529+
server_name = datasource_xml.get("serverName", None)
431530

432531
tags = None
433532
tags_elem = datasource_xml.find(".//t:tags", namespaces=ns)
@@ -438,12 +537,14 @@ def _parse_element(datasource_xml: ET.Element, ns: dict) -> tuple:
438537
project_name = None
439538
project_elem = datasource_xml.find(".//t:project", namespaces=ns)
440539
if project_elem is not None:
540+
project = ProjectItem.from_xml(project_elem, ns)
441541
project_id = project_elem.get("id", None)
442542
project_name = project_elem.get("name", None)
443543

444544
owner_id = None
445545
owner_elem = datasource_xml.find(".//t:owner", namespaces=ns)
446546
if owner_elem is not None:
547+
owner = UserItem.from_xml(owner_elem, ns)
447548
owner_id = owner_elem.get("id", None)
448549

449550
ask_data_enablement = None
@@ -471,4 +572,11 @@ def _parse_element(datasource_xml: ET.Element, ns: dict) -> tuple:
471572
use_remote_query_agent,
472573
webpage_url,
473574
size,
575+
connected_workbooks_count,
576+
favorites_total,
577+
has_alert,
578+
is_published,
579+
server_name,
580+
project,
581+
owner,
474582
)

0 commit comments

Comments
 (0)