diff --git a/.github/workflows/slack.yml b/.github/workflows/slack.yml index 9afebf25b..2ecb0be7f 100644 --- a/.github/workflows/slack.yml +++ b/.github/workflows/slack.yml @@ -5,7 +5,7 @@ on: [push, pull_request, issues] jobs: slack-notifications: continue-on-error: true - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 name: Sends a message to Slack when a push, a pull request or an issue is made steps: - name: Send message to Slack API diff --git a/samples/extracts.py b/samples/extracts.py index d9289452a..8e7a66aac 100644 --- a/samples/extracts.py +++ b/samples/extracts.py @@ -42,6 +42,7 @@ def main(): server.add_http_options({"verify": False}) server.use_server_version() with server.auth.sign_in(tableau_auth): + wb = None ds = None if args.workbook: diff --git a/tableauserverclient/__init__.py b/tableauserverclient/__init__.py index 21e2c4760..957a820db 100644 --- a/tableauserverclient/__init__.py +++ b/tableauserverclient/__init__.py @@ -25,7 +25,6 @@ LinkedTaskItem, LinkedTaskStepItem, LinkedTaskFlowRunItem, - LocationItem, MetricItem, MonthlyInterval, PaginationItem, @@ -36,7 +35,6 @@ Resource, RevisionItem, ScheduleItem, - SiteAuthConfiguration, SiteItem, ServerInfoItem, SubscriptionItem, @@ -103,7 +101,6 @@ "LinkedTaskFlowRunItem", "LinkedTaskItem", "LinkedTaskStepItem", - "LocationItem", "MetricItem", "MissingRequiredFieldError", "MonthlyInterval", @@ -124,7 +121,6 @@ "ServerInfoItem", "ServerResponseError", "SiteItem", - "SiteAuthConfiguration", "Sort", "SubscriptionItem", "TableauAuth", diff --git a/tableauserverclient/helpers/strings.py b/tableauserverclient/helpers/strings.py index 6ba4e48d9..75534103b 100644 --- a/tableauserverclient/helpers/strings.py +++ b/tableauserverclient/helpers/strings.py @@ -1,6 +1,6 @@ from defusedxml.ElementTree import fromstring, tostring from functools import singledispatch -from typing import TypeVar, overload +from typing import TypeVar # the redact method can handle either strings or bytes, but it can't mix them. @@ -41,27 +41,3 @@ def _(xml: str) -> str: @redact_xml.register # type: ignore[no-redef] def _(xml: bytes) -> bytes: return _redact_any_type(bytearray(xml), b"password", b"..[redacted]") - - -@overload -def nullable_str_to_int(value: None) -> None: ... - - -@overload -def nullable_str_to_int(value: str) -> int: ... - - -def nullable_str_to_int(value): - return int(value) if value is not None else None - - -@overload -def nullable_str_to_bool(value: None) -> None: ... - - -@overload -def nullable_str_to_bool(value: str) -> bool: ... - - -def nullable_str_to_bool(value): - return str(value).lower() == "true" if value is not None else None diff --git a/tableauserverclient/models/__init__.py b/tableauserverclient/models/__init__.py index 30cd88104..e4131b720 100644 --- a/tableauserverclient/models/__init__.py +++ b/tableauserverclient/models/__init__.py @@ -28,7 +28,6 @@ LinkedTaskStepItem, LinkedTaskFlowRunItem, ) -from tableauserverclient.models.location_item import LocationItem from tableauserverclient.models.metric_item import MetricItem from tableauserverclient.models.pagination_item import PaginationItem from tableauserverclient.models.permissions_item import PermissionsRule, Permission @@ -36,7 +35,7 @@ from tableauserverclient.models.revision_item import RevisionItem from tableauserverclient.models.schedule_item import ScheduleItem from tableauserverclient.models.server_info_item import ServerInfoItem -from tableauserverclient.models.site_item import SiteItem, SiteAuthConfiguration +from tableauserverclient.models.site_item import SiteItem from tableauserverclient.models.subscription_item import SubscriptionItem from tableauserverclient.models.table_item import TableItem from tableauserverclient.models.tableau_auth import Credentials, TableauAuth, PersonalAccessTokenAuth, JWTAuth @@ -49,7 +48,6 @@ from tableauserverclient.models.virtual_connection_item import VirtualConnectionItem from tableauserverclient.models.webhook_item import WebhookItem from tableauserverclient.models.workbook_item import WorkbookItem -from tableauserverclient.models.extract_item import ExtractItem __all__ = [ "ColumnItem", @@ -77,7 +75,6 @@ "MonthlyInterval", "HourlyInterval", "BackgroundJobItem", - "LocationItem", "MetricItem", "PaginationItem", "Permission", @@ -86,7 +83,6 @@ "RevisionItem", "ScheduleItem", "ServerInfoItem", - "SiteAuthConfiguration", "SiteItem", "SubscriptionItem", "TableItem", @@ -107,5 +103,4 @@ "LinkedTaskItem", "LinkedTaskStepItem", "LinkedTaskFlowRunItem", - "ExtractItem", ] diff --git a/tableauserverclient/models/datasource_item.py b/tableauserverclient/models/datasource_item.py index 5501ee332..2005edf7e 100644 --- a/tableauserverclient/models/datasource_item.py +++ b/tableauserverclient/models/datasource_item.py @@ -6,11 +6,9 @@ from defusedxml.ElementTree import fromstring from tableauserverclient.datetime_helpers import parse_datetime -from tableauserverclient.helpers.strings import nullable_str_to_bool, nullable_str_to_int from tableauserverclient.models.connection_item import ConnectionItem from tableauserverclient.models.exceptions import UnpopulatedPropertyError from tableauserverclient.models.permissions_item import PermissionsRule -from tableauserverclient.models.project_item import ProjectItem from tableauserverclient.models.property_decorators import ( property_not_nullable, property_is_boolean, @@ -18,7 +16,6 @@ ) from tableauserverclient.models.revision_item import RevisionItem from tableauserverclient.models.tag_item import TagItem -from tableauserverclient.models.user_item import UserItem class DatasourceItem: @@ -43,9 +40,6 @@ class DatasourceItem: specified, it will default to SiteDefault. See REST API Publish Datasource for more information about ask_data_enablement. - connected_workbooks_count : Optional[int] - The number of workbooks connected to the datasource. - connections : list[ConnectionItem] The list of data connections (ConnectionItem) for the specified data source. You must first call the populate_connections method to access @@ -73,12 +67,6 @@ class DatasourceItem: A Boolean value to determine if a datasource should be encrypted or not. See Extract and Encryption Methods for more information. - favorites_total : Optional[int] - The number of users who have marked the data source as a favorite. - - has_alert : Optional[bool] - A Boolean value that indicates whether the data source has an alert. - has_extracts : Optional[bool] A Boolean value that indicates whether the datasource has extracts. @@ -87,22 +75,13 @@ class DatasourceItem: specific data source or to delete a data source with the get_by_id and delete methods. - is_published : Optional[bool] - A Boolean value that indicates whether the data source is published. - name : Optional[str] The name of the data source. If not specified, the name of the published data source file is used. - owner: Optional[UserItem] - The owner of the data source. - owner_id : Optional[str] The identifier of the owner of the data source. - project : Optional[ProjectItem] - The project that the data source belongs to. - project_id : Optional[str] The identifier of the project associated with the data source. You must provide this identifier when you create an instance of a DatasourceItem. @@ -110,9 +89,6 @@ class DatasourceItem: project_name : Optional[str] The name of the project associated with the data source. - server_name : Optional[str] - The name of the server where the data source is published. - tags : Optional[set[str]] The tags (list of strings) that have been added to the data source. @@ -167,13 +143,6 @@ def __init__(self, project_id: Optional[str] = None, name: Optional[str] = None) self.owner_id: Optional[str] = None self.project_id: Optional[str] = project_id self.tags: set[str] = set() - self._connected_workbooks_count: Optional[int] = None - self._favorites_total: Optional[int] = None - self._has_alert: Optional[bool] = None - self._is_published: Optional[bool] = None - self._server_name: Optional[str] = None - self._project: Optional[ProjectItem] = None - self._owner: Optional[UserItem] = None self._permissions = None self._data_quality_warnings = None @@ -305,42 +274,14 @@ def revisions(self) -> list[RevisionItem]: def size(self) -> Optional[int]: return self._size - @property - def connected_workbooks_count(self) -> Optional[int]: - return self._connected_workbooks_count - - @property - def favorites_total(self) -> Optional[int]: - return self._favorites_total - - @property - def has_alert(self) -> Optional[bool]: - return self._has_alert - - @property - def is_published(self) -> Optional[bool]: - return self._is_published - - @property - def server_name(self) -> Optional[str]: - return self._server_name - - @property - def project(self) -> Optional[ProjectItem]: - return self._project - - @property - def owner(self) -> Optional[UserItem]: - return self._owner - def _set_connections(self, connections) -> None: self._connections = connections def _set_permissions(self, permissions): self._permissions = permissions - def _set_data_quality_warnings(self, dqw): - self._data_quality_warnings = dqw + def _set_data_quality_warnings(self, dqws): + self._data_quality_warnings = dqws def _set_revisions(self, revisions): self._revisions = revisions @@ -369,13 +310,6 @@ def _parse_common_elements(self, datasource_xml, ns): use_remote_query_agent, webpage_url, size, - connected_workbooks_count, - favorites_total, - has_alert, - is_published, - server_name, - project, - owner, ) = self._parse_element(datasource_xml, ns) self._set_values( ask_data_enablement, @@ -397,13 +331,6 @@ def _parse_common_elements(self, datasource_xml, ns): use_remote_query_agent, webpage_url, size, - connected_workbooks_count, - favorites_total, - has_alert, - is_published, - server_name, - project, - owner, ) return self @@ -428,13 +355,6 @@ def _set_values( use_remote_query_agent, webpage_url, size, - connected_workbooks_count, - favorites_total, - has_alert, - is_published, - server_name, - project, - owner, ): if ask_data_enablement is not None: self._ask_data_enablement = ask_data_enablement @@ -474,20 +394,6 @@ def _set_values( self._webpage_url = webpage_url if size is not None: self._size = int(size) - if connected_workbooks_count is not None: - self._connected_workbooks_count = connected_workbooks_count - if favorites_total is not None: - self._favorites_total = favorites_total - if has_alert is not None: - self._has_alert = has_alert - if is_published is not None: - self._is_published = is_published - if server_name is not None: - self._server_name = server_name - if project is not None: - self._project = project - if owner is not None: - self._owner = owner @classmethod def from_response(cls, resp: str, ns: dict) -> list["DatasourceItem"]: @@ -522,11 +428,6 @@ def _parse_element(datasource_xml: ET.Element, ns: dict) -> tuple: use_remote_query_agent = datasource_xml.get("useRemoteQueryAgent", None) webpage_url = datasource_xml.get("webpageUrl", None) size = datasource_xml.get("size", None) - connected_workbooks_count = nullable_str_to_int(datasource_xml.get("connectedWorkbooksCount", None)) - favorites_total = nullable_str_to_int(datasource_xml.get("favoritesTotal", None)) - has_alert = nullable_str_to_bool(datasource_xml.get("hasAlert", None)) - is_published = nullable_str_to_bool(datasource_xml.get("isPublished", None)) - server_name = datasource_xml.get("serverName", None) tags = None tags_elem = datasource_xml.find(".//t:tags", namespaces=ns) @@ -537,14 +438,12 @@ def _parse_element(datasource_xml: ET.Element, ns: dict) -> tuple: project_name = None project_elem = datasource_xml.find(".//t:project", namespaces=ns) if project_elem is not None: - project = ProjectItem.from_xml(project_elem, ns) project_id = project_elem.get("id", None) project_name = project_elem.get("name", None) owner_id = None owner_elem = datasource_xml.find(".//t:owner", namespaces=ns) if owner_elem is not None: - owner = UserItem.from_xml(owner_elem, ns) owner_id = owner_elem.get("id", None) ask_data_enablement = None @@ -572,11 +471,4 @@ def _parse_element(datasource_xml: ET.Element, ns: dict) -> tuple: use_remote_query_agent, webpage_url, size, - connected_workbooks_count, - favorites_total, - has_alert, - is_published, - server_name, - project, - owner, ) diff --git a/tableauserverclient/models/extract_item.py b/tableauserverclient/models/extract_item.py deleted file mode 100644 index 7562ffdde..000000000 --- a/tableauserverclient/models/extract_item.py +++ /dev/null @@ -1,82 +0,0 @@ -from typing import Optional, List -from defusedxml.ElementTree import fromstring -import xml.etree.ElementTree as ET - - -class ExtractItem: - """ - An extract refresh task item. - - Attributes - ---------- - id : str - The ID of the extract refresh task - priority : int - The priority of the task - type : str - The type of extract refresh (incremental or full) - workbook_id : str, optional - The ID of the workbook if this is a workbook extract - datasource_id : str, optional - The ID of the datasource if this is a datasource extract - """ - - def __init__( - self, priority: int, refresh_type: str, workbook_id: Optional[str] = None, datasource_id: Optional[str] = None - ): - self._id: Optional[str] = None - self._priority = priority - self._type = refresh_type - self._workbook_id = workbook_id - self._datasource_id = datasource_id - - @property - def id(self) -> Optional[str]: - return self._id - - @property - def priority(self) -> int: - return self._priority - - @property - def type(self) -> str: - return self._type - - @property - def workbook_id(self) -> Optional[str]: - return self._workbook_id - - @property - def datasource_id(self) -> Optional[str]: - return self._datasource_id - - @classmethod - def from_response(cls, resp: str, ns: dict) -> List["ExtractItem"]: - """Create ExtractItem objects from XML response.""" - parsed_response = fromstring(resp) - return cls.from_xml_element(parsed_response, ns) - - @classmethod - def from_xml_element(cls, parsed_response: ET.Element, ns: dict) -> List["ExtractItem"]: - """Create ExtractItem objects from XML element.""" - all_extract_items = [] - all_extract_xml = parsed_response.findall(".//t:extract", namespaces=ns) - - for extract_xml in all_extract_xml: - extract_id = extract_xml.get("id", None) - priority = int(extract_xml.get("priority", 0)) - refresh_type = extract_xml.get("type", "") - - # Check for workbook or datasource - workbook_elem = extract_xml.find(".//t:workbook", namespaces=ns) - datasource_elem = extract_xml.find(".//t:datasource", namespaces=ns) - - workbook_id = workbook_elem.get("id", None) if workbook_elem is not None else None - datasource_id = datasource_elem.get("id", None) if datasource_elem is not None else None - - extract_item = cls(priority, refresh_type, workbook_id, datasource_id) - extract_item._id = extract_id - - all_extract_items.append(extract_item) - - return all_extract_items diff --git a/tableauserverclient/models/flow_item.py b/tableauserverclient/models/flow_item.py index 063897e41..0083776bb 100644 --- a/tableauserverclient/models/flow_item.py +++ b/tableauserverclient/models/flow_item.py @@ -146,8 +146,8 @@ def _set_connections(self, connections): def _set_permissions(self, permissions): self._permissions = permissions - def _set_data_quality_warnings(self, dqw): - self._data_quality_warnings = dqw + def _set_data_quality_warnings(self, dqws): + self._data_quality_warnings = dqws def _parse_common_elements(self, flow_xml, ns): if not isinstance(flow_xml, ET.Element): diff --git a/tableauserverclient/models/group_item.py b/tableauserverclient/models/group_item.py index 00f35e518..0afd5582c 100644 --- a/tableauserverclient/models/group_item.py +++ b/tableauserverclient/models/group_item.py @@ -44,11 +44,6 @@ class GroupItem: login to a site. When the mode is onSync, a license is granted for group members each time the domain is synced. - Attributes - ---------- - user_count: Optional[int] - The number of users in the group. - Examples -------- >>> # Create a new group item @@ -70,7 +65,6 @@ def __init__(self, name=None, domain_name=None) -> None: self._users: Optional[Callable[..., "Pager"]] = None self.name: Optional[str] = name self.domain_name: Optional[str] = domain_name - self._user_count: Optional[int] = None def __repr__(self): return f"{self.__class__.__name__}({self.__dict__!r})" @@ -124,10 +118,6 @@ def users(self) -> "Pager": def _set_users(self, users: Callable[..., "Pager"]) -> None: self._users = users - @property - def user_count(self) -> Optional[int]: - return self._user_count - @classmethod def from_response(cls, resp, ns) -> list["GroupItem"]: all_group_items = list() @@ -137,7 +127,6 @@ def from_response(cls, resp, ns) -> list["GroupItem"]: name = group_xml.get("name", None) group_item = cls(name) group_item._id = group_xml.get("id", None) - group_item._user_count = int(count) if (count := group_xml.get("userCount", None)) else None # Domain name is returned in a domain element for some calls domain_elem = group_xml.find(".//t:domain", namespaces=ns) diff --git a/tableauserverclient/models/interval_item.py b/tableauserverclient/models/interval_item.py index 14cec1878..d7cf891cc 100644 --- a/tableauserverclient/models/interval_item.py +++ b/tableauserverclient/models/interval_item.py @@ -2,13 +2,6 @@ class IntervalItem: - """ - This class sets the frequency and start time of the scheduled item. This - class contains the classes for the hourly, daily, weekly, and monthly - intervals. This class mirrors the options you can set using the REST API and - the Tableau Server interface. - """ - class Frequency: Hourly = "Hourly" Daily = "Daily" @@ -33,19 +26,6 @@ class Day: class HourlyInterval: - """ - Runs scheduled item hourly. To set the hourly interval, you create an - instance of the HourlyInterval class and assign the following values: - start_time, end_time, and interval_value. To set the start_time and - end_time, assign the time value using this syntax: start_time=time(hour, minute) - and end_time=time(hour, minute). The hour is specified in 24 hour time. - The interval_value specifies how often the to run the task within the - start and end time. The options are expressed in hours. For example, - interval_value=.25 is every 15 minutes. The values are .25, .5, 1, 2, 4, 6, - 8, 12. Hourly schedules that run more frequently than every 60 minutes must - have start and end times that are on the hour. - """ - def __init__(self, start_time, end_time, interval_value): self.start_time = start_time self.end_time = end_time @@ -129,12 +109,6 @@ def _interval_type_pairs(self): class DailyInterval: - """ - Runs the scheduled item daily. To set the daily interval, you create an - instance of the DailyInterval and assign the start_time. The start time uses - the syntax start_time=time(hour, minute). - """ - def __init__(self, start_time, *interval_values): self.start_time = start_time self.interval = interval_values @@ -203,15 +177,6 @@ def _interval_type_pairs(self): class WeeklyInterval: - """ - Runs the scheduled item once a week. To set the weekly interval, you create - an instance of the WeeklyInterval and assign the start time and multiple - instances for the interval_value (days of week and start time). The start - time uses the syntax time(hour, minute). The interval_value is the day of - the week, expressed as a IntervalItem. For example - TSC.IntervalItem.Day.Monday for Monday. - """ - def __init__(self, start_time, *interval_values): self.start_time = start_time self.interval = interval_values @@ -249,11 +214,6 @@ def _interval_type_pairs(self): class MonthlyInterval: - """ - Runs the scheduled item once a month. To set the monthly interval, you - create an instance of the MonthlyInterval and assign the start time and day. - """ - def __init__(self, start_time, interval_value): self.start_time = start_time diff --git a/tableauserverclient/models/location_item.py b/tableauserverclient/models/location_item.py deleted file mode 100644 index fa7c2ff2c..000000000 --- a/tableauserverclient/models/location_item.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Optional -import xml.etree.ElementTree as ET - - -class LocationItem: - """ - Details of where an item is located, such as a personal space or project. - - Attributes - ---------- - id : str | None - The ID of the location. - - type : str | None - The type of location, such as PersonalSpace or Project. - - name : str | None - The name of the location. - """ - - class Type: - PersonalSpace = "PersonalSpace" - Project = "Project" - - def __init__(self): - self._id: Optional[str] = None - self._type: Optional[str] = None - self._name: Optional[str] = None - - def __repr__(self): - return f"{self.__class__.__name__}({self.__dict__!r})" - - @property - def id(self) -> Optional[str]: - return self._id - - @property - def type(self) -> Optional[str]: - return self._type - - @property - def name(self) -> Optional[str]: - return self._name - - @classmethod - def from_xml(cls, xml: ET.Element, ns: Optional[dict] = None) -> "LocationItem": - if ns is None: - ns = {} - location = cls() - location._id = xml.get("id", None) - location._type = xml.get("type", None) - location._name = xml.get("name", None) - return location diff --git a/tableauserverclient/models/project_item.py b/tableauserverclient/models/project_item.py index 1ab369ba7..9be1196ba 100644 --- a/tableauserverclient/models/project_item.py +++ b/tableauserverclient/models/project_item.py @@ -1,11 +1,11 @@ +import logging import xml.etree.ElementTree as ET -from typing import Optional, overload +from typing import Optional from defusedxml.ElementTree import fromstring from tableauserverclient.models.exceptions import UnpopulatedPropertyError -from tableauserverclient.models.property_decorators import property_is_enum -from tableauserverclient.models.user_item import UserItem +from tableauserverclient.models.property_decorators import property_is_enum, property_not_empty class ProjectItem: @@ -39,32 +39,12 @@ class corresponds to the project resources you can access using the Tableau Attributes ---------- - datasource_count : int - The number of data sources in the project. - id : str The unique identifier for the project. - owner: Optional[UserItem] - The UserItem owner of the project. - owner_id : str The unique identifier for the UserItem owner of the project. - project_count : int - The number of projects in the project. - - top_level_project : bool - True if the project is a top-level project. - - view_count : int - The number of views in the project. - - workbook_count : int - The number of workbooks in the project. - - writeable : bool - True if the project is writeable. """ ERROR_MSG = "Project item must be populated with permissions first." @@ -95,8 +75,6 @@ def __init__( self.parent_id: Optional[str] = parent_id self._samples: Optional[bool] = samples self._owner_id: Optional[str] = None - self._top_level_project: Optional[bool] = None - self._writeable: Optional[bool] = None self._permissions = None self._default_workbook_permissions = None @@ -109,13 +87,6 @@ def __init__( self._default_database_permissions = None self._default_table_permissions = None - self._project_count: Optional[int] = None - self._workbook_count: Optional[int] = None - self._view_count: Optional[int] = None - self._datasource_count: Optional[int] = None - - self._owner: Optional[UserItem] = None - @property def content_permissions(self): return self._content_permissions @@ -205,53 +176,25 @@ def owner_id(self) -> Optional[str]: def owner_id(self, value: str) -> None: self._owner_id = value - @property - def top_level_project(self) -> Optional[bool]: - return self._top_level_project - - @property - def writeable(self) -> Optional[bool]: - return self._writeable - - @property - def project_count(self) -> Optional[int]: - return self._project_count - - @property - def workbook_count(self) -> Optional[int]: - return self._workbook_count - - @property - def view_count(self) -> Optional[int]: - return self._view_count - - @property - def datasource_count(self) -> Optional[int]: - return self._datasource_count - - @property - def owner(self) -> Optional[UserItem]: - return self._owner - def is_default(self): return self.name.lower() == "default" - def _set_values( - self, - project_id, - name, - description, - content_permissions, - parent_id, - owner_id, - top_level_project, - writeable, - project_count, - workbook_count, - view_count, - datasource_count, - owner, - ): + def _parse_common_tags(self, project_xml, ns): + if not isinstance(project_xml, ET.Element): + project_xml = fromstring(project_xml).find(".//t:project", namespaces=ns) + + if project_xml is not None: + ( + _, + name, + description, + content_permissions, + parent_id, + ) = self._parse_element(project_xml) + self._set_values(None, name, description, content_permissions, parent_id) + return self + + def _set_values(self, project_id, name, description, content_permissions, parent_id, owner_id): if project_id is not None: self._id = project_id if name: @@ -264,20 +207,6 @@ def _set_values( self.parent_id = parent_id if owner_id: self._owner_id = owner_id - if project_count is not None: - self._project_count = project_count - if workbook_count is not None: - self._workbook_count = workbook_count - if view_count is not None: - self._view_count = view_count - if datasource_count is not None: - self._datasource_count = datasource_count - if top_level_project is not None: - self._top_level_project = top_level_project - if writeable is not None: - self._writeable = writeable - if owner is not None: - self._owner = owner def _set_permissions(self, permissions): self._permissions = permissions @@ -291,71 +220,31 @@ def _set_default_permissions(self, permissions, content_type): ) @classmethod - def from_response(cls, resp: bytes, ns: Optional[dict]) -> list["ProjectItem"]: + def from_response(cls, resp, ns) -> list["ProjectItem"]: all_project_items = list() parsed_response = fromstring(resp) all_project_xml = parsed_response.findall(".//t:project", namespaces=ns) for project_xml in all_project_xml: - project_item = cls.from_xml(project_xml, namespace=ns) + project_item = cls.from_xml(project_xml) all_project_items.append(project_item) return all_project_items @classmethod - def from_xml(cls, project_xml: ET.Element, namespace: Optional[dict] = None) -> "ProjectItem": + def from_xml(cls, project_xml, namespace=None) -> "ProjectItem": project_item = cls() - project_item._set_values(*cls._parse_element(project_xml, namespace)) + project_item._set_values(*cls._parse_element(project_xml)) return project_item @staticmethod - def _parse_element(project_xml: ET.Element, namespace: Optional[dict]) -> tuple: + def _parse_element(project_xml): id = project_xml.get("id", None) name = project_xml.get("name", None) description = project_xml.get("description", None) content_permissions = project_xml.get("contentPermissions", None) parent_id = project_xml.get("parentProjectId", None) - top_level_project = str_to_bool(project_xml.get("topLevelProject", None)) - writeable = str_to_bool(project_xml.get("writeable", None)) owner_id = None - owner = None - if (owner_elem := project_xml.find(".//t:owner", namespaces=namespace)) is not None: - owner = UserItem.from_xml(owner_elem, namespace) - owner_id = owner_elem.get("id", None) - - project_count = None - workbook_count = None - view_count = None - datasource_count = None - if (count_elem := project_xml.find(".//t:contentsCounts", namespaces=namespace)) is not None: - project_count = int(count_elem.get("projectCount", 0)) - workbook_count = int(count_elem.get("workbookCount", 0)) - view_count = int(count_elem.get("viewCount", 0)) - datasource_count = int(count_elem.get("dataSourceCount", 0)) - - return ( - id, - name, - description, - content_permissions, - parent_id, - owner_id, - top_level_project, - writeable, - project_count, - workbook_count, - view_count, - datasource_count, - owner, - ) - - -@overload -def str_to_bool(value: str) -> bool: ... - - -@overload -def str_to_bool(value: None) -> None: ... - + for owner in project_xml: + owner_id = owner.get("id", None) -def str_to_bool(value): - return value.lower() == "true" if value is not None else None + return id, name, description, content_permissions, parent_id, owner_id diff --git a/tableauserverclient/models/schedule_item.py b/tableauserverclient/models/schedule_item.py index a2118e3d6..e39042058 100644 --- a/tableauserverclient/models/schedule_item.py +++ b/tableauserverclient/models/schedule_item.py @@ -20,63 +20,6 @@ class ScheduleItem: - """ - Using the TSC library, you can schedule extract refresh or subscription - tasks on Tableau Server. You can also get and update information about the - scheduled tasks, or delete scheduled tasks. - - If you have the identifier of the job, you can use the TSC library to find - out the status of the asynchronous job. - - The schedule properties are defined in the ScheduleItem class. The class - corresponds to the properties for schedules you can access in Tableau - Server or by using the Tableau Server REST API. The Schedule methods are - based upon the endpoints for jobs in the REST API and operate on the JobItem - class. - - Parameters - ---------- - name : str - The name of the schedule. - - priority : int - The priority of the schedule. Lower values represent higher priority, - with 0 indicating the highest priority. - - schedule_type : str - The type of task schedule. See ScheduleItem.Type for the possible values. - - execution_order : str - Specifies how the scheduled tasks should run. The choices are Parallel - which uses all avaiable background processes for a scheduled task, or - Serial, which limits the schedule to one background process. - - interval_item : Interval - Specifies the frequency that the scheduled task should run. The - interval_item is an instance of the IntervalItem class. The - interval_item has properties for frequency (hourly, daily, weekly, - monthly), and what time and date the scheduled item runs. You set this - value by declaring an IntervalItem object that is one of the following: - HourlyInterval, DailyInterval, WeeklyInterval, or MonthlyInterval. - - Attributes - ---------- - created_at : datetime - The date and time the schedule was created. - - end_schedule_at : datetime - The date and time the schedule ends. - - id : str - The unique identifier for the schedule. - - next_run_at : datetime - The date and time the schedule is next run. - - state : str - The state of the schedule. See ScheduleItem.State for the possible values. - """ - class Type: Extract = "Extract" Flow = "Flow" diff --git a/tableauserverclient/models/site_item.py b/tableauserverclient/models/site_item.py index ab65b97b5..e4e146f9c 100644 --- a/tableauserverclient/models/site_item.py +++ b/tableauserverclient/models/site_item.py @@ -1188,34 +1188,6 @@ def _parse_element(site_xml, ns): ) -class SiteAuthConfiguration: - """ - Authentication configuration for a site. - """ - - def __init__(self): - self.auth_setting: Optional[str] = None - self.enabled: Optional[bool] = None - self.idp_configuration_id: Optional[str] = None - self.idp_configuration_name: Optional[str] = None - self.known_provider_alias: Optional[str] = None - - @classmethod - def from_response(cls, resp: bytes, ns: dict) -> list["SiteAuthConfiguration"]: - all_auth_configs = list() - parsed_response = fromstring(resp) - all_auth_xml = parsed_response.findall(".//t:siteAuthConfiguration", namespaces=ns) - for auth_xml in all_auth_xml: - auth_config = cls() - auth_config.auth_setting = auth_xml.get("authSetting", None) - auth_config.enabled = string_to_bool(auth_xml.get("enabled", "")) - auth_config.idp_configuration_id = auth_xml.get("idpConfigurationId", None) - auth_config.idp_configuration_name = auth_xml.get("idpConfigurationName", None) - auth_config.known_provider_alias = auth_xml.get("knownProviderAlias", None) - all_auth_configs.append(auth_config) - return all_auth_configs - - # Used to convert string represented boolean to a boolean type def string_to_bool(s: str) -> bool: return s.lower() == "true" diff --git a/tableauserverclient/models/table_item.py b/tableauserverclient/models/table_item.py index 541f84360..0afdd4df3 100644 --- a/tableauserverclient/models/table_item.py +++ b/tableauserverclient/models/table_item.py @@ -1,12 +1,8 @@ -from typing import Callable, Optional, TYPE_CHECKING from defusedxml.ElementTree import fromstring from .exceptions import UnpopulatedPropertyError from .property_decorators import property_not_empty, property_is_boolean -if TYPE_CHECKING: - from tableauserverclient.models import DQWItem - class TableItem: def __init__(self, name, description=None): @@ -44,7 +40,7 @@ def dqws(self): return self._data_quality_warnings() @property - def id(self) -> Optional[str]: + def id(self): return self._id @property @@ -104,8 +100,8 @@ def columns(self): def _set_columns(self, columns): self._columns = columns - def _set_data_quality_warnings(self, dqw: Callable[[], list["DQWItem"]]) -> None: - self._data_quality_warnings = dqw + def _set_data_quality_warnings(self, dqws): + self._data_quality_warnings = dqws def _set_values(self, table_values): if "id" in table_values: diff --git a/tableauserverclient/models/tableau_types.py b/tableauserverclient/models/tableau_types.py index e69d02a06..01ee3d3a9 100644 --- a/tableauserverclient/models/tableau_types.py +++ b/tableauserverclient/models/tableau_types.py @@ -1,10 +1,8 @@ from typing import Union -from tableauserverclient.models.database_item import DatabaseItem from tableauserverclient.models.datasource_item import DatasourceItem from tableauserverclient.models.flow_item import FlowItem from tableauserverclient.models.project_item import ProjectItem -from tableauserverclient.models.table_item import TableItem from tableauserverclient.models.view_item import ViewItem from tableauserverclient.models.workbook_item import WorkbookItem from tableauserverclient.models.metric_item import MetricItem @@ -27,17 +25,7 @@ class Resource: # resource types that have permissions, can be renamed, etc # todo: refactoring: should actually define TableauItem as an interface and let all these implement it -TableauItem = Union[ - DatasourceItem, - FlowItem, - MetricItem, - ProjectItem, - ViewItem, - WorkbookItem, - VirtualConnectionItem, - DatabaseItem, - TableItem, -] +TableauItem = Union[DatasourceItem, FlowItem, MetricItem, ProjectItem, ViewItem, WorkbookItem, VirtualConnectionItem] def plural_type(content_type: Union[Resource, str]) -> str: diff --git a/tableauserverclient/models/user_item.py b/tableauserverclient/models/user_item.py index c995b4e07..365e44c1d 100644 --- a/tableauserverclient/models/user_item.py +++ b/tableauserverclient/models/user_item.py @@ -7,7 +7,6 @@ from defusedxml.ElementTree import fromstring from tableauserverclient.datetime_helpers import parse_datetime -from tableauserverclient.models.site_item import SiteAuthConfiguration from .exceptions import UnpopulatedPropertyError from .property_decorators import ( property_is_enum, @@ -38,49 +37,6 @@ class UserItem: auth_setting: str Required attribute for Tableau Cloud. How the user autenticates to the server. - - Attributes - ---------- - domain_name: Optional[str] - The name of the Active Directory domain ("local" if local authentication - is used). - - email: Optional[str] - The email address of the user. - - external_auth_user_id: Optional[str] - The unique identifier for the user in the external authentication system. - - id: Optional[str] - The unique identifier for the user. - - favorites: dict[str, list] - The favorites of the user. Must be populated with a call to - `populate_favorites()`. - - fullname: Optional[str] - The full name of the user. - - groups: Pager - The groups the user belongs to. Must be populated with a call to - `populate_groups()`. - - last_login: Optional[datetime] - The last time the user logged in. - - locale: Optional[str] - The locale of the user. - - language: Optional[str] - Language setting for the user. - - idp_configuration_id: Optional[str] - The ID of the identity provider configuration. - - workbooks: Pager - The workbooks owned by the user. Must be populated with a call to - `populate_workbooks()`. - """ tag_name: str = "user" @@ -138,9 +94,6 @@ def __init__( self.name: Optional[str] = name self.site_role: Optional[str] = site_role self.auth_setting: Optional[str] = auth_setting - self._locale: Optional[str] = None - self._language: Optional[str] = None - self._idp_configuration_id: Optional[str] = None return None @@ -231,26 +184,6 @@ def groups(self) -> "Pager": raise UnpopulatedPropertyError(error) return self._groups() - @property - def locale(self) -> Optional[str]: - return self._locale - - @property - def language(self) -> Optional[str]: - return self._language - - @property - def idp_configuration_id(self) -> Optional[str]: - """ - IDP configuration id for the user. This is only available on Tableau - Cloud, 3.24 or later - """ - return self._idp_configuration_id - - @idp_configuration_id.setter - def idp_configuration_id(self, value: str) -> None: - self._idp_configuration_id = value - def _set_workbooks(self, workbooks) -> None: self._workbooks = workbooks @@ -271,11 +204,8 @@ def _parse_common_tags(self, user_xml, ns) -> "UserItem": email, auth_setting, _, - _, - _, - _, ) = self._parse_element(user_xml, ns) - self._set_values(None, None, site_role, None, None, fullname, email, auth_setting, None, None, None, None) + self._set_values(None, None, site_role, None, None, fullname, email, auth_setting, None) return self def _set_values( @@ -289,9 +219,6 @@ def _set_values( email, auth_setting, domain_name, - locale, - language, - idp_configuration_id, ): if id is not None: self._id = id @@ -311,12 +238,6 @@ def _set_values( self._auth_setting = auth_setting if domain_name: self._domain_name = domain_name - if locale: - self._locale = locale - if language: - self._language = language - if idp_configuration_id: - self._idp_configuration_id = idp_configuration_id @classmethod def from_response(cls, resp, ns) -> list["UserItem"]: @@ -328,12 +249,6 @@ def from_response_as_owner(cls, resp, ns) -> list["UserItem"]: element_name = ".//t:owner" return cls._parse_xml(element_name, resp, ns) - @classmethod - def from_xml(cls, xml: ET.Element, ns: Optional[dict] = None) -> "UserItem": - item = cls() - item._set_values(*cls._parse_element(xml, ns)) - return item - @classmethod def _parse_xml(cls, element_name, resp, ns): all_user_items = [] @@ -350,9 +265,6 @@ def _parse_xml(cls, element_name, resp, ns): email, auth_setting, domain_name, - locale, - language, - idp_configuration_id, ) = cls._parse_element(user_xml, ns) user_item = cls(name, site_role) user_item._set_values( @@ -365,9 +277,6 @@ def _parse_xml(cls, element_name, resp, ns): email, auth_setting, domain_name, - locale, - language, - idp_configuration_id, ) all_user_items.append(user_item) return all_user_items @@ -386,9 +295,6 @@ def _parse_element(user_xml, ns): fullname = user_xml.get("fullName", None) email = user_xml.get("email", None) auth_setting = user_xml.get("authSetting", None) - locale = user_xml.get("locale", None) - language = user_xml.get("language", None) - idp_configuration_id = user_xml.get("idpConfigurationId", None) domain_name = None domain_elem = user_xml.find(".//t:domain", namespaces=ns) @@ -405,9 +311,6 @@ def _parse_element(user_xml, ns): email, auth_setting, domain_name, - locale, - language, - idp_configuration_id, ) class CSVImport: @@ -458,9 +361,6 @@ def create_user_from_line(line: str): values[UserItem.CSVImport.ColumnType.EMAIL], values[UserItem.CSVImport.ColumnType.AUTH], None, - None, - None, - None, ) return user diff --git a/tableauserverclient/models/view_item.py b/tableauserverclient/models/view_item.py index dc8eda9c8..88cec7328 100644 --- a/tableauserverclient/models/view_item.py +++ b/tableauserverclient/models/view_item.py @@ -1,21 +1,15 @@ import copy from datetime import datetime from requests import Response -from typing import TYPE_CHECKING, Callable, Optional, overload +from typing import Callable, Optional from collections.abc import Iterator from defusedxml.ElementTree import fromstring from tableauserverclient.datetime_helpers import parse_datetime from tableauserverclient.models.exceptions import UnpopulatedPropertyError -from tableauserverclient.models.location_item import LocationItem from tableauserverclient.models.permissions_item import PermissionsRule -from tableauserverclient.models.project_item import ProjectItem from tableauserverclient.models.tag_item import TagItem -from tableauserverclient.models.user_item import UserItem - -if TYPE_CHECKING: - from tableauserverclient.models.workbook_item import WorkbookItem class ViewItem: @@ -40,16 +34,9 @@ class ViewItem: The image of the view. You must first call the `views.populate_image` method to access the image. - location: Optional[LocationItem], default None - The location of the view. The location can be a personal space or a - project. - name: Optional[str], default None The name of the view. - owner: Optional[UserItem], default None - The owner of the view. - owner_id: Optional[str], default None The ID for the owner of the view. @@ -61,9 +48,6 @@ class ViewItem: The preview image of the view. You must first call the `views.populate_preview_image` method to access the preview image. - project: Optional[ProjectItem], default None - The project that contains the view. - project_id: Optional[str], default None The ID for the project that contains the view. @@ -76,11 +60,9 @@ class ViewItem: updated_at: Optional[datetime], default None The date and time when the view was last updated. - workbook: Optional[WorkbookItem], default None - The workbook that contains the view. - workbook_id: Optional[str], default None The ID for the workbook that contains the view. + """ def __init__(self) -> None: @@ -102,18 +84,11 @@ def __init__(self) -> None: self._workbook_id: Optional[str] = None self._permissions: Optional[Callable[[], list[PermissionsRule]]] = None self.tags: set[str] = set() - self._favorites_total: Optional[int] = None - self._view_url_name: Optional[str] = None self._data_acceleration_config = { "acceleration_enabled": None, "acceleration_status": None, } - self._owner: Optional[UserItem] = None - self._project: Optional[ProjectItem] = None - self._workbook: Optional["WorkbookItem"] = None - self._location: Optional[LocationItem] = None - def __str__(self): return "".format( self._id, self.name, self.content_url, self.project_id @@ -215,14 +190,6 @@ def updated_at(self) -> Optional[datetime]: def workbook_id(self) -> Optional[str]: return self._workbook_id - @property - def view_url_name(self) -> Optional[str]: - return self._view_url_name - - @property - def favorites_total(self) -> Optional[int]: - return self._favorites_total - @property def data_acceleration_config(self): return self._data_acceleration_config @@ -231,22 +198,6 @@ def data_acceleration_config(self): def data_acceleration_config(self, value): self._data_acceleration_config = value - @property - def project(self) -> Optional["ProjectItem"]: - return self._project - - @property - def workbook(self) -> Optional["WorkbookItem"]: - return self._workbook - - @property - def owner(self) -> Optional[UserItem]: - return self._owner - - @property - def location(self) -> Optional[LocationItem]: - return self._location - @property def permissions(self) -> list[PermissionsRule]: if self._permissions is None: @@ -277,7 +228,7 @@ def from_xml(cls, view_xml, ns, workbook_id="") -> "ViewItem": workbook_elem = view_xml.find(".//t:workbook", namespaces=ns) owner_elem = view_xml.find(".//t:owner", namespaces=ns) project_elem = view_xml.find(".//t:project", namespaces=ns) - tags_elem = view_xml.find("./t:tags", namespaces=ns) + tags_elem = view_xml.find(".//t:tags", namespaces=ns) data_acceleration_config_elem = view_xml.find(".//t:dataAccelerationConfig", namespaces=ns) view_item._created_at = parse_datetime(view_xml.get("createdAt", None)) view_item._updated_at = parse_datetime(view_xml.get("updatedAt", None)) @@ -285,35 +236,22 @@ def from_xml(cls, view_xml, ns, workbook_id="") -> "ViewItem": view_item._name = view_xml.get("name", None) view_item._content_url = view_xml.get("contentUrl", None) view_item._sheet_type = view_xml.get("sheetType", None) - view_item._favorites_total = string_to_int(view_xml.get("favoritesTotal", None)) - view_item._view_url_name = view_xml.get("viewUrlName", None) if usage_elem is not None: total_view = usage_elem.get("totalViewCount", None) if total_view: view_item._total_views = int(total_view) if owner_elem is not None: - user = UserItem.from_xml(owner_elem, ns) - view_item._owner = user view_item._owner_id = owner_elem.get("id", None) if project_elem is not None: - project_item = ProjectItem.from_xml(project_elem, ns) - view_item._project = project_item - view_item._project_id = project_item.id + view_item._project_id = project_elem.get("id", None) if workbook_id: view_item._workbook_id = workbook_id elif workbook_elem is not None: - from tableauserverclient.models.workbook_item import WorkbookItem - - workbook_item = WorkbookItem.from_xml(workbook_elem, ns) - view_item._workbook = workbook_item - view_item._workbook_id = workbook_item.id + view_item._workbook_id = workbook_elem.get("id", None) if tags_elem is not None: tags = TagItem.from_xml_element(tags_elem, ns) view_item.tags = tags view_item._initial_tags = copy.copy(tags) - if (location_elem := view_xml.find(".//t:location", namespaces=ns)) is not None: - location = LocationItem.from_xml(location_elem, ns) - view_item._location = location if data_acceleration_config_elem is not None: data_acceleration_config = parse_data_acceleration_config(data_acceleration_config_elem) view_item.data_acceleration_config = data_acceleration_config @@ -336,15 +274,3 @@ def parse_data_acceleration_config(data_acceleration_elem): def string_to_bool(s: str) -> bool: return s.lower() == "true" - - -@overload -def string_to_int(s: None) -> None: ... - - -@overload -def string_to_int(s: str) -> int: ... - - -def string_to_int(s): - return int(s) if s is not None else None diff --git a/tableauserverclient/models/workbook_item.py b/tableauserverclient/models/workbook_item.py index a3ede65d6..32ab413a4 100644 --- a/tableauserverclient/models/workbook_item.py +++ b/tableauserverclient/models/workbook_item.py @@ -2,14 +2,11 @@ import datetime import uuid import xml.etree.ElementTree as ET -from typing import Callable, Optional, overload +from typing import Callable, Optional from defusedxml.ElementTree import fromstring from tableauserverclient.datetime_helpers import parse_datetime -from tableauserverclient.models.location_item import LocationItem -from tableauserverclient.models.project_item import ProjectItem -from tableauserverclient.models.user_item import UserItem from .connection_item import ConnectionItem from .exceptions import UnpopulatedPropertyError from .permissions_item import PermissionsRule @@ -54,31 +51,13 @@ class as arguments. The workbook item specifies the project. created_at : Optional[datetime.datetime] The date and time the workbook was created. - default_view_id : Optional[str] - The identifier for the default view of the workbook. - description : Optional[str] User-defined description of the workbook. - encrypt_extracts : Optional[bool] - Indicates whether extracts are encrypted. - - has_extracts : Optional[bool] - Indicates whether the workbook has extracts. - id : Optional[str] The identifier for the workbook. You need this value to query a specific workbook or to delete a workbook with the get_by_id and delete methods. - last_published_at : Optional[datetime.datetime] - The date and time the workbook was last published. - - location : Optional[LocationItem] - The location of the workbook, such as a personal space or project. - - owner : Optional[UserItem] - The owner of the workbook. - owner_id : Optional[str] The identifier for the owner (UserItem) of the workbook. @@ -86,9 +65,6 @@ class as arguments. The workbook item specifies the project. The thumbnail image for the view. You must first call the workbooks.populate_preview_image method to access this data. - project: Optional[ProjectItem] - The project that contains the workbook. - project_name : Optional[str] The name of the project that contains the workbook. @@ -163,15 +139,6 @@ def __init__( self._permissions = None self.thumbnails_user_id = thumbnails_user_id self.thumbnails_group_id = thumbnails_group_id - self._sheet_count: Optional[int] = None - self._has_extracts: Optional[bool] = None - self._project: Optional[ProjectItem] = None - self._owner: Optional[UserItem] = None - self._location: Optional[LocationItem] = None - self._encrypt_extracts: Optional[bool] = None - self._default_view_id: Optional[str] = None - self._share_description: Optional[str] = None - self._last_published_at: Optional[datetime.datetime] = None return None @@ -267,14 +234,6 @@ def show_tabs(self, value: bool): def size(self): return self._size - @property - def sheet_count(self) -> Optional[int]: - return self._sheet_count - - @property - def has_extracts(self) -> Optional[bool]: - return self._has_extracts - @property def updated_at(self) -> Optional[datetime.datetime]: return self._updated_at @@ -341,34 +300,6 @@ def thumbnails_group_id(self) -> Optional[str]: def thumbnails_group_id(self, value: str): self._thumbnails_group_id = value - @property - def project(self) -> Optional[ProjectItem]: - return self._project - - @property - def owner(self) -> Optional[UserItem]: - return self._owner - - @property - def location(self) -> Optional[LocationItem]: - return self._location - - @property - def encrypt_extracts(self) -> Optional[bool]: - return self._encrypt_extracts - - @property - def default_view_id(self) -> Optional[str]: - return self._default_view_id - - @property - def share_description(self) -> Optional[str]: - return self._share_description - - @property - def last_published_at(self) -> Optional[datetime.datetime]: - return self._last_published_at - def _set_connections(self, connections): self._connections = connections @@ -411,15 +342,6 @@ def _parse_common_tags(self, workbook_xml, ns): views, data_acceleration_config, data_freshness_policy, - sheet_count, - has_extracts, - project, - owner, - location, - encrypt_extracts, - default_view_id, - share_description, - last_published_at, ) = self._parse_element(workbook_xml, ns) self._set_values( @@ -439,15 +361,6 @@ def _parse_common_tags(self, workbook_xml, ns): views, data_acceleration_config, data_freshness_policy, - sheet_count, - has_extracts, - project, - owner, - location, - encrypt_extracts, - default_view_id, - share_description, - last_published_at, ) return self @@ -470,15 +383,6 @@ def _set_values( views, data_acceleration_config, data_freshness_policy, - sheet_count, - has_extracts, - project, - owner, - location, - encrypt_extracts, - default_view_id, - share_description, - last_published_at, ): if id is not None: self._id = id @@ -513,24 +417,6 @@ def _set_values( self.data_acceleration_config = data_acceleration_config if data_freshness_policy is not None: self.data_freshness_policy = data_freshness_policy - if sheet_count is not None: - self._sheet_count = sheet_count - if has_extracts is not None: - self._has_extracts = has_extracts - if project: - self._project = project - if owner: - self._owner = owner - if location: - self._location = location - if encrypt_extracts is not None: - self._encrypt_extracts = encrypt_extracts - if default_view_id is not None: - self._default_view_id = default_view_id - if share_description is not None: - self._share_description = share_description - if last_published_at is not None: - self._last_published_at = last_published_at @classmethod def from_response(cls, resp: str, ns: dict[str, str]) -> list["WorkbookItem"]: @@ -557,12 +443,6 @@ def _parse_element(workbook_xml, ns): created_at = parse_datetime(workbook_xml.get("createdAt", None)) description = workbook_xml.get("description", None) updated_at = parse_datetime(workbook_xml.get("updatedAt", None)) - sheet_count = string_to_int(workbook_xml.get("sheetCount", None)) - has_extracts = string_to_bool(workbook_xml.get("hasExtracts", "")) - encrypt_extracts = string_to_bool(e) if (e := workbook_xml.get("encryptExtracts", None)) is not None else None - default_view_id = workbook_xml.get("defaultViewId", None) - share_description = workbook_xml.get("shareDescription", None) - last_published_at = parse_datetime(workbook_xml.get("lastPublishedAt", None)) size = workbook_xml.get("size", None) if size: @@ -572,18 +452,14 @@ def _parse_element(workbook_xml, ns): project_id = None project_name = None - project = None project_tag = workbook_xml.find(".//t:project", namespaces=ns) if project_tag is not None: - project = ProjectItem.from_xml(project_tag, ns) project_id = project_tag.get("id", None) project_name = project_tag.get("name", None) owner_id = None - owner = None owner_tag = workbook_xml.find(".//t:owner", namespaces=ns) if owner_tag is not None: - owner = UserItem.from_xml(owner_tag, ns) owner_id = owner_tag.get("id", None) tags = None @@ -597,11 +473,6 @@ def _parse_element(workbook_xml, ns): if views_elem is not None: views = ViewItem.from_xml_element(views_elem, ns) - location = None - location_elem = workbook_xml.find(".//t:location", namespaces=ns) - if location_elem is not None: - location = LocationItem.from_xml(location_elem, ns) - data_acceleration_config = { "acceleration_enabled": None, "accelerate_now": None, @@ -634,15 +505,6 @@ def _parse_element(workbook_xml, ns): views, data_acceleration_config, data_freshness_policy, - sheet_count, - has_extracts, - project, - owner, - location, - encrypt_extracts, - default_view_id, - share_description, - last_published_at, ) @@ -673,15 +535,3 @@ def parse_data_acceleration_config(data_acceleration_elem): # Used to convert string represented boolean to a boolean type def string_to_bool(s: str) -> bool: return s.lower() == "true" - - -@overload -def string_to_int(s: None) -> None: ... - - -@overload -def string_to_int(s: str) -> int: ... - - -def string_to_int(s): - return int(s) if s is not None else None diff --git a/tableauserverclient/server/endpoint/databases_endpoint.py b/tableauserverclient/server/endpoint/databases_endpoint.py index dc88ceaa5..c0e106eb2 100644 --- a/tableauserverclient/server/endpoint/databases_endpoint.py +++ b/tableauserverclient/server/endpoint/databases_endpoint.py @@ -1,8 +1,7 @@ import logging -from typing import TYPE_CHECKING, Optional, Union +from typing import Union from collections.abc import Iterable -from tableauserverclient.models.permissions_item import PermissionsRule from tableauserverclient.server.endpoint.default_permissions_endpoint import _DefaultPermissionsEndpoint from tableauserverclient.server.endpoint.dqw_endpoint import _DataQualityWarningEndpoint from tableauserverclient.server.endpoint.endpoint import api, Endpoint @@ -14,10 +13,6 @@ from tableauserverclient.helpers.logging import logger -if TYPE_CHECKING: - from tableauserverclient.models.dqw_item import DQWItem - from tableauserverclient.server.request_options import RequestOptions - class Databases(Endpoint, TaggingMixin): def __init__(self, parent_srv): @@ -28,29 +23,11 @@ def __init__(self, parent_srv): self._data_quality_warnings = _DataQualityWarningEndpoint(parent_srv, Resource.Database) @property - def baseurl(self) -> str: + def baseurl(self): return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/databases" @api(version="3.5") - def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[DatabaseItem], PaginationItem]: - """ - Get information about all databases on the site. Endpoint is paginated, - and will return a default of 100 items per page. Use the `req_options` - parameter to customize the request. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_metadata.htm#query_databases - - Parameters - ---------- - req_options : RequestOptions, optional - Options to customize the request. If not provided, defaults to None. - - Returns - ------- - tuple[list[DatabaseItem], PaginationItem] - A tuple containing a list of DatabaseItem objects and a - PaginationItem object. - """ + def get(self, req_options=None): logger.info("Querying all databases on site") url = self.baseurl server_response = self.get_request(url, req_options) @@ -60,27 +37,7 @@ def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[Data # Get 1 database @api(version="3.5") - def get_by_id(self, database_id: str) -> DatabaseItem: - """ - Get information about a single database asset on the site. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_metadata.htm#query_database - - Parameters - ---------- - database_id : str - The ID of the database to retrieve. - - Returns - ------- - DatabaseItem - A DatabaseItem object representing the database. - - Raises - ------ - ValueError - If the database ID is undefined. - """ + def get_by_id(self, database_id): if not database_id: error = "database ID undefined." raise ValueError(error) @@ -90,24 +47,7 @@ def get_by_id(self, database_id: str) -> DatabaseItem: return DatabaseItem.from_response(server_response.content, self.parent_srv.namespace)[0] @api(version="3.5") - def delete(self, database_id: str) -> None: - """ - Deletes a single database asset from the server. - - Parameters - ---------- - database_id : str - The ID of the database to delete. - - Returns - ------- - None - - Raises - ------ - ValueError - If the database ID is undefined. - """ + def delete(self, database_id): if not database_id: error = "Database ID undefined." raise ValueError(error) @@ -116,28 +56,7 @@ def delete(self, database_id: str) -> None: logger.info(f"Deleted single database (ID: {database_id})") @api(version="3.5") - def update(self, database_item: DatabaseItem) -> DatabaseItem: - """ - Update the database description, certify the database, set permissions, - or assign a User as the database contact. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_metadata.htm#update_database - - Parameters - ---------- - database_item : DatabaseItem - The DatabaseItem object to update. - - Returns - ------- - DatabaseItem - The updated DatabaseItem object. - - Raises - ------ - MissingRequiredFieldError - If the database item is missing an ID. - """ + def update(self, database_item): if not database_item.id: error = "Database item missing ID." raise MissingRequiredFieldError(error) @@ -169,45 +88,43 @@ def _get_tables_for_database(self, database_item): return tables @api(version="3.5") - def populate_permissions(self, item: DatabaseItem) -> None: + def populate_permissions(self, item): self._permissions.populate(item) @api(version="3.5") - def update_permissions(self, item: DatabaseItem, rules: list[PermissionsRule]) -> list[PermissionsRule]: + def update_permissions(self, item, rules): return self._permissions.update(item, rules) @api(version="3.5") - def delete_permission(self, item: DatabaseItem, rules: list[PermissionsRule]) -> None: + def delete_permission(self, item, rules): self._permissions.delete(item, rules) @api(version="3.5") - def populate_table_default_permissions(self, item: DatabaseItem): + def populate_table_default_permissions(self, item): self._default_permissions.populate_default_permissions(item, Resource.Table) @api(version="3.5") - def update_table_default_permissions( - self, item: DatabaseItem, rules: list[PermissionsRule] - ) -> list[PermissionsRule]: - return self._default_permissions.update_default_permissions(item, rules, Resource.Table) + def update_table_default_permissions(self, item): + return self._default_permissions.update_default_permissions(item, Resource.Table) @api(version="3.5") - def delete_table_default_permissions(self, rule: PermissionsRule, item: DatabaseItem) -> None: - self._default_permissions.delete_default_permission(item, rule, Resource.Table) + def delete_table_default_permissions(self, item): + self._default_permissions.delete_default_permission(item, Resource.Table) @api(version="3.5") - def populate_dqw(self, item: DatabaseItem) -> None: + def populate_dqw(self, item): self._data_quality_warnings.populate(item) @api(version="3.5") - def update_dqw(self, item: DatabaseItem, warning: "DQWItem") -> list["DQWItem"]: + def update_dqw(self, item, warning): return self._data_quality_warnings.update(item, warning) @api(version="3.5") - def add_dqw(self, item: DatabaseItem, warning: "DQWItem") -> list["DQWItem"]: + def add_dqw(self, item, warning): return self._data_quality_warnings.add(item, warning) @api(version="3.5") - def delete_dqw(self, item: DatabaseItem) -> None: + def delete_dqw(self, item): self._data_quality_warnings.clear(item) @api(version="3.9") diff --git a/tableauserverclient/server/endpoint/datasources_endpoint.py b/tableauserverclient/server/endpoint/datasources_endpoint.py index 168446974..69913a724 100644 --- a/tableauserverclient/server/endpoint/datasources_endpoint.py +++ b/tableauserverclient/server/endpoint/datasources_endpoint.py @@ -733,7 +733,7 @@ def populate_dqw(self, item) -> None: self._data_quality_warnings.populate(item) @api(version="3.5") - def update_dqw(self, item: DatasourceItem, warning: "DQWItem") -> list["DQWItem"]: + def update_dqw(self, item, warning): """ Update the warning type, status, and message of a data quality warning. @@ -755,7 +755,7 @@ def update_dqw(self, item: DatasourceItem, warning: "DQWItem") -> list["DQWItem" return self._data_quality_warnings.update(item, warning) @api(version="3.5") - def add_dqw(self, item: DatasourceItem, warning: "DQWItem") -> list["DQWItem"]: + def add_dqw(self, item, warning): """ Add a data quality warning to a datasource. @@ -786,7 +786,7 @@ def add_dqw(self, item: DatasourceItem, warning: "DQWItem") -> list["DQWItem"]: return self._data_quality_warnings.add(item, warning) @api(version="3.5") - def delete_dqw(self, item: DatasourceItem) -> None: + def delete_dqw(self, item): """ Delete a data quality warnings from an asset. diff --git a/tableauserverclient/server/endpoint/dqw_endpoint.py b/tableauserverclient/server/endpoint/dqw_endpoint.py index d2ad517ee..90e31483b 100644 --- a/tableauserverclient/server/endpoint/dqw_endpoint.py +++ b/tableauserverclient/server/endpoint/dqw_endpoint.py @@ -1,5 +1,4 @@ import logging -from typing import Callable, Optional, Protocol, TYPE_CHECKING from .endpoint import Endpoint from .exceptions import MissingRequiredFieldError @@ -8,15 +7,6 @@ from tableauserverclient.helpers.logging import logger -if TYPE_CHECKING: - from tableauserverclient.server.request_options import RequestOptions - - -class HasId(Protocol): - @property - def id(self) -> Optional[str]: ... - def _set_data_quality_warnings(self, dqw: Callable[[], list[DQWItem]]): ... - class _DataQualityWarningEndpoint(Endpoint): def __init__(self, parent_srv, resource_type): @@ -24,12 +14,12 @@ def __init__(self, parent_srv, resource_type): self.resource_type = resource_type @property - def baseurl(self) -> str: + def baseurl(self): return "{}/sites/{}/dataQualityWarnings/{}".format( self.parent_srv.baseurl, self.parent_srv.site_id, self.resource_type ) - def add(self, resource: HasId, warning: DQWItem) -> list[DQWItem]: + def add(self, resource, warning): url = f"{self.baseurl}/{resource.id}" add_req = RequestFactory.DQW.add_req(warning) response = self.post_request(url, add_req) @@ -38,7 +28,7 @@ def add(self, resource: HasId, warning: DQWItem) -> list[DQWItem]: return warnings - def update(self, resource: HasId, warning: DQWItem) -> list[DQWItem]: + def update(self, resource, warning): url = f"{self.baseurl}/{resource.id}" add_req = RequestFactory.DQW.update_req(warning) response = self.put_request(url, add_req) @@ -47,11 +37,11 @@ def update(self, resource: HasId, warning: DQWItem) -> list[DQWItem]: return warnings - def clear(self, resource: HasId) -> None: + def clear(self, resource): url = f"{self.baseurl}/{resource.id}" return self.delete_request(url) - def populate(self, item: HasId) -> None: + def populate(self, item): if not item.id: error = "Server item is missing ID. Item must be retrieved from server first." raise MissingRequiredFieldError(error) @@ -62,7 +52,7 @@ def dqw_fetcher(): item._set_data_quality_warnings(dqw_fetcher) logger.info(f"Populated permissions for item (ID: {item.id})") - def _get_data_quality_warnings(self, item: HasId, req_options: Optional["RequestOptions"] = None) -> list[DQWItem]: + def _get_data_quality_warnings(self, item, req_options=None): url = f"{self.baseurl}/{item.id}" server_response = self.get_request(url, req_options) dqws = DQWItem.from_response(server_response.content, self.parent_srv.namespace) diff --git a/tableauserverclient/server/endpoint/endpoint.py b/tableauserverclient/server/endpoint/endpoint.py index 21462af5f..9e1160705 100644 --- a/tableauserverclient/server/endpoint/endpoint.py +++ b/tableauserverclient/server/endpoint/endpoint.py @@ -14,7 +14,6 @@ TypeVar, Union, ) -from typing_extensions import Self from tableauserverclient.models.pagination_item import PaginationItem from tableauserverclient.server.request_options import RequestOptions @@ -354,41 +353,3 @@ def paginate(self, **kwargs) -> QuerySet[T]: @abc.abstractmethod def get(self, request_options: Optional[RequestOptions] = None) -> tuple[list[T], PaginationItem]: raise NotImplementedError(f".get has not been implemented for {self.__class__.__qualname__}") - - def fields(self: Self, *fields: str) -> QuerySet: - """ - Add fields to the request options. If no fields are provided, the - default fields will be used. If fields are provided, the default fields - will be used in addition to the provided fields. - - Parameters - ---------- - fields : str - The fields to include in the request options. - - Returns - ------- - QuerySet - """ - queryset = QuerySet(self) - queryset.request_options.fields |= set(fields) | set(("_default_",)) - return queryset - - def only_fields(self: Self, *fields: str) -> QuerySet: - """ - Add fields to the request options. If no fields are provided, the - default fields will be used. If fields are provided, the default fields - will be replaced by the provided fields. - - Parameters - ---------- - fields : str - The fields to include in the request options. - - Returns - ------- - QuerySet - """ - queryset = QuerySet(self) - queryset.request_options.fields |= set(fields) - return queryset diff --git a/tableauserverclient/server/endpoint/schedules_endpoint.py b/tableauserverclient/server/endpoint/schedules_endpoint.py index 090d400b6..eec4536f9 100644 --- a/tableauserverclient/server/endpoint/schedules_endpoint.py +++ b/tableauserverclient/server/endpoint/schedules_endpoint.py @@ -7,7 +7,7 @@ from .endpoint import Endpoint, api, parameter_added_in from .exceptions import MissingRequiredFieldError from tableauserverclient.server import RequestFactory -from tableauserverclient.models import PaginationItem, ScheduleItem, TaskItem, ExtractItem +from tableauserverclient.models import PaginationItem, ScheduleItem, TaskItem from tableauserverclient.helpers.logging import logger @@ -30,23 +30,6 @@ def siteurl(self) -> str: @api(version="2.3") def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[ScheduleItem], PaginationItem]: - """ - Returns a list of flows, extract, and subscription server schedules on - Tableau Server. For each schedule, the API returns name, frequency, - priority, and other information. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_jobs_tasks_and_schedules.htm#query_schedules - - Parameters - ---------- - req_options : Optional[RequestOptions] - Filtering and paginating options for request. - - Returns - ------- - Tuple[List[ScheduleItem], PaginationItem] - A tuple of list of ScheduleItem and PaginationItem - """ logger.info("Querying all schedules") url = self.baseurl server_response = self.get_request(url, req_options) @@ -55,22 +38,7 @@ def get(self, req_options: Optional["RequestOptions"] = None) -> tuple[list[Sche return all_schedule_items, pagination_item @api(version="3.8") - def get_by_id(self, schedule_id: str) -> ScheduleItem: - """ - Returns detailed information about the specified server schedule. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_jobs_tasks_and_schedules.htm#get-schedule - - Parameters - ---------- - schedule_id : str - The ID of the schedule to get information for. - - Returns - ------- - ScheduleItem - The schedule item that corresponds to the given ID. - """ + def get_by_id(self, schedule_id): if not schedule_id: error = "No Schedule ID provided" raise ValueError(error) @@ -81,20 +49,6 @@ def get_by_id(self, schedule_id: str) -> ScheduleItem: @api(version="2.3") def delete(self, schedule_id: str) -> None: - """ - Deletes the specified schedule from the server. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_jobs_tasks_and_schedules.htm#delete_schedule - - Parameters - ---------- - schedule_id : str - The ID of the schedule to delete. - - Returns - ------- - None - """ if not schedule_id: error = "Schedule ID undefined" raise ValueError(error) @@ -104,23 +58,6 @@ def delete(self, schedule_id: str) -> None: @api(version="2.3") def update(self, schedule_item: ScheduleItem) -> ScheduleItem: - """ - Modifies settings for the specified server schedule, including the name, - priority, and frequency details on Tableau Server. For Tableau Cloud, - see the tasks and subscritpions API. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_jobs_tasks_and_schedules.htm#update_schedule - - Parameters - ---------- - schedule_item : ScheduleItem - The schedule item to update. - - Returns - ------- - ScheduleItem - The updated schedule item. - """ if not schedule_item.id: error = "Schedule item missing ID." raise MissingRequiredFieldError(error) @@ -134,20 +71,6 @@ def update(self, schedule_item: ScheduleItem) -> ScheduleItem: @api(version="2.3") def create(self, schedule_item: ScheduleItem) -> ScheduleItem: - """ - Creates a new server schedule on Tableau Server. For Tableau Cloud, use - the tasks and subscriptions API. - - Parameters - ---------- - schedule_item : ScheduleItem - The schedule item to create. - - Returns - ------- - ScheduleItem - The newly created schedule. - """ if schedule_item.interval_item is None: error = "Interval item must be defined." raise MissingRequiredFieldError(error) @@ -169,41 +92,6 @@ def add_to_schedule( flow: Optional["FlowItem"] = None, task_type: Optional[str] = None, ) -> list[AddResponse]: - """ - Adds a workbook, datasource, or flow to a schedule on Tableau Server. - Only one of workbook, datasource, or flow can be passed in at a time. - - The task type is optional and will default to ExtractRefresh if a - workbook or datasource is passed in, and RunFlow if a flow is passed in. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_jobs_tasks_and_schedules.htm#add_workbook_to_schedule - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_jobs_tasks_and_schedules.htm#add_data_source_to_schedule - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_flow.htm#add_flow_task_to_schedule - - Parameters - ---------- - schedule_id : str - The ID of the schedule to add the item to. - - workbook : Optional[WorkbookItem] - The workbook to add to the schedule. - - datasource : Optional[DatasourceItem] - The datasource to add to the schedule. - - flow : Optional[FlowItem] - The flow to add to the schedule. - - task_type : Optional[str] - The type of task to add to the schedule. If not provided, it will - default to ExtractRefresh if a workbook or datasource is passed in, - and RunFlow if a flow is passed in. - - Returns - ------- - list[AddResponse] - A list of responses for each item added to the schedule. - """ # There doesn't seem to be a good reason to allow one item of each type? if workbook and datasource: warnings.warn("Passing in multiple items for add_to_schedule will be deprecated", PendingDeprecationWarning) @@ -261,21 +149,3 @@ def _add_to( ) else: return OK - - @api(version="2.3") - def get_extract_refresh_tasks( - self, schedule_id: str, req_options: Optional["RequestOptions"] = None - ) -> tuple[list["ExtractItem"], "PaginationItem"]: - """Get all extract refresh tasks for the specified schedule.""" - if not schedule_id: - error = "Schedule ID undefined" - raise ValueError(error) - - logger.info(f"Querying extract refresh tasks for schedule (ID: {schedule_id})") - url = f"{self.siteurl}/{schedule_id}/extracts" - server_response = self.get_request(url, req_options) - - pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace) - extract_items = ExtractItem.from_response(server_response.content, self.parent_srv.namespace) - - return extract_items, pagination_item diff --git a/tableauserverclient/server/endpoint/sites_endpoint.py b/tableauserverclient/server/endpoint/sites_endpoint.py index e2316fbb8..55d2a5ad0 100644 --- a/tableauserverclient/server/endpoint/sites_endpoint.py +++ b/tableauserverclient/server/endpoint/sites_endpoint.py @@ -4,7 +4,7 @@ from .endpoint import Endpoint, api from .exceptions import MissingRequiredFieldError from tableauserverclient.server import RequestFactory -from tableauserverclient.models import SiteAuthConfiguration, SiteItem, PaginationItem +from tableauserverclient.models import SiteItem, PaginationItem from tableauserverclient.helpers.logging import logger @@ -418,20 +418,3 @@ def re_encrypt_extracts(self, site_id: str) -> None: empty_req = RequestFactory.Empty.empty_req() self.post_request(url, empty_req) - - @api(version="3.24") - def list_auth_configurations(self) -> list[SiteAuthConfiguration]: - """ - Lists all authentication configurations on the current site. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_site.htm#list_authentication_configurations_site - - Returns - ------- - list[SiteAuthConfiguration] - A list of authentication configurations on the current site. - """ - url = f"{self.baseurl}/{self.parent_srv.site_id}/site-auth-configurations" - server_response = self.get_request(url) - auth_configurations = SiteAuthConfiguration.from_response(server_response.content, self.parent_srv.namespace) - return auth_configurations diff --git a/tableauserverclient/server/endpoint/tables_endpoint.py b/tableauserverclient/server/endpoint/tables_endpoint.py index ad80e7d0e..120d3ba9c 100644 --- a/tableauserverclient/server/endpoint/tables_endpoint.py +++ b/tableauserverclient/server/endpoint/tables_endpoint.py @@ -1,8 +1,7 @@ import logging -from typing import Optional, Union, TYPE_CHECKING +from typing import Union from collections.abc import Iterable -from tableauserverclient.models.permissions_item import PermissionsRule from tableauserverclient.server.endpoint.dqw_endpoint import _DataQualityWarningEndpoint from tableauserverclient.server.endpoint.endpoint import api, Endpoint from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError @@ -13,10 +12,6 @@ from tableauserverclient.server.pager import Pager from tableauserverclient.helpers.logging import logger -from tableauserverclient.server.request_options import RequestOptions - -if TYPE_CHECKING: - from tableauserverclient.models import DQWItem, PermissionsRule class Tables(Endpoint, TaggingMixin[TableItem]): @@ -27,29 +22,11 @@ def __init__(self, parent_srv): self._data_quality_warnings = _DataQualityWarningEndpoint(self.parent_srv, "table") @property - def baseurl(self) -> str: + def baseurl(self): return f"{self.parent_srv.baseurl}/sites/{self.parent_srv.site_id}/tables" @api(version="3.5") - def get(self, req_options: Optional[RequestOptions] = None) -> tuple[list[TableItem], PaginationItem]: - """ - Get information about all tables on the site. Endpoint is paginated, and - will return a default of 100 items per page. Use the `req_options` - parameter to customize the request. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_metadata.htm#query_tables - - Parameters - ---------- - req_options : RequestOptions, optional - Options to customize the request. If not provided, defaults to None. - - Returns - ------- - tuple[list[TableItem], PaginationItem] - A tuple containing a list of TableItem objects and a PaginationItem - object. - """ + def get(self, req_options=None): logger.info("Querying all tables on site") url = self.baseurl server_response = self.get_request(url, req_options) @@ -59,27 +36,7 @@ def get(self, req_options: Optional[RequestOptions] = None) -> tuple[list[TableI # Get 1 table @api(version="3.5") - def get_by_id(self, table_id: str) -> TableItem: - """ - Get information about a single table on the site. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_metadata.htm#query_table - - Parameters - ---------- - table_id : str - The ID of the table to retrieve. - - Returns - ------- - TableItem - A TableItem object representing the table. - - Raises - ------ - ValueError - If the table ID is not provided. - """ + def get_by_id(self, table_id): if not table_id: error = "table ID undefined." raise ValueError(error) @@ -89,24 +46,7 @@ def get_by_id(self, table_id: str) -> TableItem: return TableItem.from_response(server_response.content, self.parent_srv.namespace)[0] @api(version="3.5") - def delete(self, table_id: str) -> None: - """ - Delete a single table from the server. - - Parameters - ---------- - table_id : str - The ID of the table to delete. - - Returns - ------- - None - - Raises - ------ - ValueError - If the table ID is not provided. - """ + def delete(self, table_id): if not table_id: error = "Database ID undefined." raise ValueError(error) @@ -115,27 +55,7 @@ def delete(self, table_id: str) -> None: logger.info(f"Deleted single table (ID: {table_id})") @api(version="3.5") - def update(self, table_item: TableItem) -> TableItem: - """ - Update a table on the server. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_metadata.htm#update_table - - Parameters - ---------- - table_item : TableItem - The TableItem object to update. - - Returns - ------- - TableItem - The updated TableItem object. - - Raises - ------ - MissingRequiredFieldError - If the table item is missing an ID. - """ + def update(self, table_item): if not table_item.id: error = "table item missing ID." raise MissingRequiredFieldError(error) @@ -149,46 +69,21 @@ def update(self, table_item: TableItem) -> TableItem: # Get all columns of the table @api(version="3.5") - def populate_columns(self, table_item: TableItem, req_options: Optional[RequestOptions] = None) -> None: - """ - Populate the columns of a table item. Sets a fetcher function to - retrieve the columns when needed. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_metadata.htm#query_columns - - Parameters - ---------- - table_item : TableItem - The TableItem object to populate columns for. - - req_options : RequestOptions, optional - Options to customize the request. If not provided, defaults to None. - - Returns - ------- - None - - Raises - ------ - MissingRequiredFieldError - If the table item is missing an ID. - """ + def populate_columns(self, table_item, req_options=None): if not table_item.id: error = "Table item missing ID. table must be retrieved from server first." raise MissingRequiredFieldError(error) def column_fetcher(): return Pager( - lambda options: self._get_columns_for_table(table_item, options), # type: ignore + lambda options: self._get_columns_for_table(table_item, options), req_options, ) table_item._set_columns(column_fetcher) logger.info(f"Populated columns for table (ID: {table_item.id}") - def _get_columns_for_table( - self, table_item: TableItem, req_options: Optional[RequestOptions] = None - ) -> tuple[list[ColumnItem], PaginationItem]: + def _get_columns_for_table(self, table_item, req_options=None): url = f"{self.baseurl}/{table_item.id}/columns" server_response = self.get_request(url, req_options) columns = ColumnItem.from_response(server_response.content, self.parent_srv.namespace) @@ -196,25 +91,7 @@ def _get_columns_for_table( return columns, pagination_item @api(version="3.5") - def update_column(self, table_item: TableItem, column_item: ColumnItem) -> ColumnItem: - """ - Update the description of a column in a table. - - REST API: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_metadata.htm#update_column - - Parameters - ---------- - table_item : TableItem - The TableItem object representing the table. - - column_item : ColumnItem - The ColumnItem object representing the column to update. - - Returns - ------- - ColumnItem - The updated ColumnItem object. - """ + def update_column(self, table_item, column_item): url = f"{self.baseurl}/{table_item.id}/columns/{column_item.id}" update_req = RequestFactory.Column.update_req(column_item) server_response = self.put_request(url, update_req) @@ -224,31 +101,31 @@ def update_column(self, table_item: TableItem, column_item: ColumnItem) -> Colum return column @api(version="3.5") - def populate_permissions(self, item: TableItem) -> None: + def populate_permissions(self, item): self._permissions.populate(item) @api(version="3.5") - def update_permissions(self, item: TableItem, rules: list[PermissionsRule]) -> list[PermissionsRule]: + def update_permissions(self, item, rules): return self._permissions.update(item, rules) @api(version="3.5") - def delete_permission(self, item: TableItem, rules: list[PermissionsRule]) -> None: + def delete_permission(self, item, rules): return self._permissions.delete(item, rules) @api(version="3.5") - def populate_dqw(self, item: TableItem) -> None: + def populate_dqw(self, item): self._data_quality_warnings.populate(item) @api(version="3.5") - def update_dqw(self, item: TableItem, warning: "DQWItem") -> list["DQWItem"]: + def update_dqw(self, item, warning): return self._data_quality_warnings.update(item, warning) @api(version="3.5") - def add_dqw(self, item: TableItem, warning: "DQWItem") -> list["DQWItem"]: + def add_dqw(self, item, warning): return self._data_quality_warnings.add(item, warning) @api(version="3.5") - def delete_dqw(self, item: TableItem) -> None: + def delete_dqw(self, item): self._data_quality_warnings.clear(item) @api(version="3.9") diff --git a/tableauserverclient/server/endpoint/users_endpoint.py b/tableauserverclient/server/endpoint/users_endpoint.py index 17af21a03..d81907ae9 100644 --- a/tableauserverclient/server/endpoint/users_endpoint.py +++ b/tableauserverclient/server/endpoint/users_endpoint.py @@ -87,7 +87,7 @@ def get(self, req_options: Optional[RequestOptions] = None) -> tuple[list[UserIt if req_options is None: req_options = RequestOptions() - req_options.all_fields = True + req_options._all_fields = True url = self.baseurl server_response = self.get_request(url, req_options) @@ -381,15 +381,10 @@ def create_from_file(self, filepath: str) -> tuple[list[UserItem], list[tuple[Us # Get workbooks for user @api(version="2.0") - def populate_workbooks( - self, user_item: UserItem, req_options: Optional[RequestOptions] = None, owned_only: bool = False - ) -> None: + def populate_workbooks(self, user_item: UserItem, req_options: Optional[RequestOptions] = None) -> None: """ Returns information about the workbooks that the specified user owns - or has Read (view) permissions for. If owned_only is set to True, - only the workbooks that the user owns are returned. If owned_only is - set to False, all workbooks that the user has Read (view) permissions - for are returned. + and has Read (view) permissions for. This method retrieves the workbook information for the specified user. The REST API is designed to return only the information you ask for @@ -407,10 +402,6 @@ def populate_workbooks( req_options : Optional[RequestOptions] Optional request options to filter and sort the results. - owned_only : bool, default=False - If True, only the workbooks that the user owns are returned. - If False, all workbooks that the user has Read (view) permissions - Returns ------- None @@ -432,22 +423,14 @@ def populate_workbooks( raise MissingRequiredFieldError(error) def wb_pager(): - def func(req_options): - return self._get_wbs_for_user(user_item, req_options, owned_only=owned_only) - - return Pager(func, req_options) + return Pager(lambda options: self._get_wbs_for_user(user_item, options), req_options) user_item._set_workbooks(wb_pager) def _get_wbs_for_user( - self, - user_item: UserItem, - req_options: Optional[RequestOptions] = None, - owned_only: bool = False, + self, user_item: UserItem, req_options: Optional[RequestOptions] = None ) -> tuple[list[WorkbookItem], PaginationItem]: url = f"{self.baseurl}/{user_item.id}/workbooks" - if owned_only: - url += "?ownedBy=true" server_response = self.get_request(url, req_options) logger.info(f"Populated workbooks for user (ID: {user_item.id})") workbook_item = WorkbookItem.from_response(server_response.content, self.parent_srv.namespace) diff --git a/tableauserverclient/server/query.py b/tableauserverclient/server/query.py index 5137cee52..801ad4a13 100644 --- a/tableauserverclient/server/query.py +++ b/tableauserverclient/server/query.py @@ -208,42 +208,6 @@ def paginate(self: Self, **kwargs) -> Self: self.request_options.pagesize = kwargs["page_size"] return self - def fields(self: Self, *fields: str) -> Self: - """ - Add fields to the request options. If no fields are provided, the - default fields will be used. If fields are provided, the default fields - will be used in addition to the provided fields. - - Parameters - ---------- - fields : str - The fields to include in the request options. - - Returns - ------- - QuerySet - """ - self.request_options.fields |= set(fields) | set(("_default_")) - return self - - def only_fields(self: Self, *fields: str) -> Self: - """ - Add fields to the request options. If no fields are provided, the - default fields will be used. If fields are provided, the default fields - will be replaced by the provided fields. - - Parameters - ---------- - fields : str - The fields to include in the request options. - - Returns - ------- - QuerySet - """ - self.request_options.fields |= set(fields) - return self - @staticmethod def _parse_shorthand_filter(key: str) -> tuple[str, str]: tokens = key.split("__", 1) diff --git a/tableauserverclient/server/request_factory.py b/tableauserverclient/server/request_factory.py index c898004f7..575423612 100644 --- a/tableauserverclient/server/request_factory.py +++ b/tableauserverclient/server/request_factory.py @@ -913,8 +913,6 @@ def update_req(self, user_item: UserItem, password: Optional[str]) -> bytes: user_element.attrib["authSetting"] = user_item.auth_setting if password: user_element.attrib["password"] = password - if user_item.idp_configuration_id is not None: - user_element.attrib["idpConfigurationId"] = user_item.idp_configuration_id return ET.tostring(xml_request) def add_req(self, user_item: UserItem) -> bytes: @@ -931,9 +929,6 @@ def add_req(self, user_item: UserItem) -> bytes: if user_item.auth_setting: user_element.attrib["authSetting"] = user_item.auth_setting - - if user_item.idp_configuration_id is not None: - user_element.attrib["idpConfigurationId"] = user_item.idp_configuration_id return ET.tostring(xml_request) diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 4a104255f..504f7f3ca 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -1,6 +1,5 @@ import sys from typing import Optional -import warnings from typing_extensions import Self @@ -63,21 +62,8 @@ def __init__(self, pagenumber=1, pagesize=None): self.pagesize = pagesize or config.PAGE_SIZE self.sort = set() self.filter = set() - self.fields = set() # This is private until we expand all of our parsers to handle the extra fields - self.all_fields = False - - @property - def _all_fields(self) -> bool: - return self.all_fields - - @_all_fields.setter - def _all_fields(self, value): - warnings.warn( - "Directly setting _all_fields is deprecated, please use the all_fields property instead.", - DeprecationWarning, - ) - self.all_fields = value + self._all_fields = False def get_query_params(self) -> dict: params = {} @@ -89,14 +75,12 @@ def get_query_params(self) -> dict: filter_options = (str(filter_item) for filter_item in self.filter) ordered_filter_options = sorted(filter_options) params["filter"] = ",".join(ordered_filter_options) - if self.all_fields: + if self._all_fields: params["fields"] = "_all_" if self.pagenumber: params["pageNumber"] = self.pagenumber if self.pagesize: params["pageSize"] = self.pagesize - if self.fields: - params["fields"] = ",".join(self.fields) return params def page_size(self, page_size): @@ -197,116 +181,6 @@ class Direction: Desc = "desc" Asc = "asc" - class SelectFields: - class Common: - All = "_all_" - Default = "_default_" - - class ContentsCounts: - ProjectCount = "contentsCounts.projectCount" - ViewCount = "contentsCounts.viewCount" - DatasourceCount = "contentsCounts.datasourceCount" - WorkbookCount = "contentsCounts.workbookCount" - - class Datasource: - ContentUrl = "datasource.contentUrl" - ID = "datasource.id" - Name = "datasource.name" - Type = "datasource.type" - Description = "datasource.description" - CreatedAt = "datasource.createdAt" - UpdatedAt = "datasource.updatedAt" - EncryptExtracts = "datasource.encryptExtracts" - IsCertified = "datasource.isCertified" - UseRemoteQueryAgent = "datasource.useRemoteQueryAgent" - WebPageURL = "datasource.webpageUrl" - Size = "datasource.size" - Tag = "datasource.tag" - FavoritesTotal = "datasource.favoritesTotal" - DatabaseName = "datasource.databaseName" - ConnectedWorkbooksCount = "datasource.connectedWorkbooksCount" - HasAlert = "datasource.hasAlert" - HasExtracts = "datasource.hasExtracts" - IsPublished = "datasource.isPublished" - ServerName = "datasource.serverName" - - class Favorite: - Label = "favorite.label" - ParentProjectName = "favorite.parentProjectName" - TargetOwnerName = "favorite.targetOwnerName" - - class Group: - ID = "group.id" - Name = "group.name" - DomainName = "group.domainName" - UserCount = "group.userCount" - MinimumSiteRole = "group.minimumSiteRole" - - class Job: - ID = "job.id" - Status = "job.status" - CreatedAt = "job.createdAt" - StartedAt = "job.startedAt" - EndedAt = "job.endedAt" - Priority = "job.priority" - JobType = "job.jobType" - Title = "job.title" - Subtitle = "job.subtitle" - - class Owner: - ID = "owner.id" - Name = "owner.name" - FullName = "owner.fullName" - SiteRole = "owner.siteRole" - LastLogin = "owner.lastLogin" - Email = "owner.email" - - class Project: - ID = "project.id" - Name = "project.name" - Description = "project.description" - CreatedAt = "project.createdAt" - UpdatedAt = "project.updatedAt" - ContentPermissions = "project.contentPermissions" - ParentProjectID = "project.parentProjectId" - TopLevelProject = "project.topLevelProject" - Writeable = "project.writeable" - - class User: - ExternalAuthUserId = "user.externalAuthUserId" - ID = "user.id" - Name = "user.name" - SiteRole = "user.siteRole" - LastLogin = "user.lastLogin" - FullName = "user.fullName" - Email = "user.email" - AuthSetting = "user.authSetting" - - class View: - ID = "view.id" - Name = "view.name" - ContentUrl = "view.contentUrl" - CreatedAt = "view.createdAt" - UpdatedAt = "view.updatedAt" - Tags = "view.tags" - SheetType = "view.sheetType" - Usage = "view.usage" - - class Workbook: - ID = "workbook.id" - Description = "workbook.description" - Name = "workbook.name" - ContentUrl = "workbook.contentUrl" - ShowTabs = "workbook.showTabs" - Size = "workbook.size" - CreatedAt = "workbook.createdAt" - UpdatedAt = "workbook.updatedAt" - SheetCount = "workbook.sheetCount" - HasExtracts = "workbook.hasExtracts" - Tags = "workbook.tags" - WebpageUrl = "workbook.webpageUrl" - DefaultViewId = "workbook.defaultViewId" - """ These options can be used by methods that are fetching data exported from a specific content item diff --git a/tableauserverclient/server/server.py b/tableauserverclient/server/server.py index d5d163db3..30c635e31 100644 --- a/tableauserverclient/server/server.py +++ b/tableauserverclient/server/server.py @@ -2,7 +2,6 @@ import requests import urllib3 -import ssl from defusedxml.ElementTree import fromstring, ParseError from packaging.version import Version @@ -92,13 +91,6 @@ class Server: and a later version of the REST API. For more information, see REST API Versions. - http_options : dict, optional - Additional options to pass to the requests library when making HTTP requests. - - session_factory : callable, optional - A factory function that returns a requests.Session object. If not provided, - requests.session is used. - Examples -------- >>> import tableauserverclient as TSC @@ -115,16 +107,6 @@ class Server: >>> # for example, 2.8 >>> # server.version = '2.8' - >>> # if connecting to an older Tableau Server with weak DH keys (Python 3.12+ only) - >>> server.configure_ssl(allow_weak_dh=True) # Note: reduces security - - Notes - ----- - When using Python 3.12 or later with older versions of Tableau Server, you may encounter - SSL errors related to weak Diffie-Hellman keys. This is because newer Python versions - enforce stronger security requirements. You can temporarily work around this using - configure_ssl(allow_weak_dh=True), but this reduces security and should only be used - as a temporary measure until the server can be upgraded. """ class PublishMode: @@ -143,7 +125,6 @@ def __init__(self, server_address, use_server_version=False, http_options=None, self._auth_token = None self._site_id = None self._user_id = None - self._ssl_context = None # TODO: this needs to change to default to https, but without breaking existing code if not server_address.startswith("http://") and not server_address.startswith("https://"): @@ -332,26 +313,3 @@ def session(self): def is_signed_in(self): return self._auth_token is not None - - def configure_ssl(self, *, allow_weak_dh=False): - """Configure SSL/TLS settings for the server connection. - - Parameters - ---------- - allow_weak_dh : bool, optional - If True, allows connections to servers with DH keys that are considered too small by modern Python versions. - WARNING: This reduces security and should only be used as a temporary workaround. - """ - if allow_weak_dh: - logger.warning( - "WARNING: Allowing weak Diffie-Hellman keys. This reduces security and should only be used temporarily." - ) - self._ssl_context = ssl.create_default_context() - # Allow weak DH keys by setting minimum key size to 512 bits (default is 1024 in Python 3.12+) - self._ssl_context.set_dh_parameters(min_key_bits=512) - self.add_http_options({"verify": self._ssl_context}) - else: - self._ssl_context = None - # Remove any custom SSL context if we're reverting to default settings - if "verify" in self._http_options: - del self._http_options["verify"] diff --git a/test/assets/datasource_get_all_fields.xml b/test/assets/datasource_get_all_fields.xml deleted file mode 100644 index 46c4396d3..000000000 --- a/test/assets/datasource_get_all_fields.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/test/assets/group_get_all_fields.xml b/test/assets/group_get_all_fields.xml deleted file mode 100644 index 0118250e1..000000000 --- a/test/assets/group_get_all_fields.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/assets/project_get_all_fields.xml b/test/assets/project_get_all_fields.xml deleted file mode 100644 index d71ebd922..000000000 --- a/test/assets/project_get_all_fields.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/test/assets/schedule_get_extract_refresh_tasks.xml b/test/assets/schedule_get_extract_refresh_tasks.xml deleted file mode 100644 index 48906dde6..000000000 --- a/test/assets/schedule_get_extract_refresh_tasks.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/test/assets/site_auth_configurations.xml b/test/assets/site_auth_configurations.xml deleted file mode 100644 index c81d179ac..000000000 --- a/test/assets/site_auth_configurations.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/test/assets/user_get_all_fields.xml b/test/assets/user_get_all_fields.xml deleted file mode 100644 index 7e9a62568..000000000 --- a/test/assets/user_get_all_fields.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/test/assets/view_get_all_fields.xml b/test/assets/view_get_all_fields.xml deleted file mode 100644 index 236ebd726..000000000 --- a/test/assets/view_get_all_fields.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/assets/workbook_get_all_fields.xml b/test/assets/workbook_get_all_fields.xml deleted file mode 100644 index 007b79338..000000000 --- a/test/assets/workbook_get_all_fields.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/request_factory/test_task_requests.py b/test/request_factory/test_task_requests.py index 6287fa6ea..0258b8a93 100644 --- a/test/request_factory/test_task_requests.py +++ b/test/request_factory/test_task_requests.py @@ -5,6 +5,7 @@ class TestTaskRequest(unittest.TestCase): + def setUp(self): self.task_request = TaskRequest() self.xml_request = ET.Element("tsRequest") diff --git a/test/test_datasource.py b/test/test_datasource.py index a604ba8b0..b7e7e2721 100644 --- a/test/test_datasource.py +++ b/test/test_datasource.py @@ -10,7 +10,7 @@ import tableauserverclient as TSC from tableauserverclient import ConnectionItem -from tableauserverclient.datetime_helpers import format_datetime, parse_datetime +from tableauserverclient.datetime_helpers import format_datetime from tableauserverclient.server.endpoint.exceptions import InternalServerError from tableauserverclient.server.endpoint.fileuploads_endpoint import Fileuploads from tableauserverclient.server.request_factory import RequestFactory @@ -20,7 +20,6 @@ GET_XML = "datasource_get.xml" GET_EMPTY_XML = "datasource_get_empty.xml" GET_BY_ID_XML = "datasource_get_by_id.xml" -GET_XML_ALL_FIELDS = "datasource_get_all_fields.xml" POPULATE_CONNECTIONS_XML = "datasource_populate_connections.xml" POPULATE_PERMISSIONS_XML = "datasource_populate_permissions.xml" PUBLISH_XML = "datasource_publish.xml" @@ -734,39 +733,3 @@ def test_bad_download_response(self) -> None: ) file_path = self.server.datasources.download("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", td) self.assertTrue(os.path.exists(file_path)) - - def test_get_datasource_all_fields(self) -> None: - ro = TSC.RequestOptions() - ro.all_fields = True - with requests_mock.mock() as m: - m.get(f"{self.baseurl}?fields=_all_", text=read_xml_asset(GET_XML_ALL_FIELDS)) - datasources, _ = self.server.datasources.get(req_options=ro) - - assert datasources[0].connected_workbooks_count == 0 - assert datasources[0].content_url == "SuperstoreDatasource" - assert datasources[0].created_at == parse_datetime("2024-02-14T04:42:13Z") - assert not datasources[0].encrypt_extracts - assert datasources[0].favorites_total == 0 - assert not datasources[0].has_alert - assert not datasources[0].has_extracts - assert datasources[0].id == "a71cdd15-3a23-4ec1-b3ce-9956f5e00bb7" - assert not datasources[0].certified - assert datasources[0].is_published - assert datasources[0].name == "Superstore Datasource" - assert datasources[0].size == 1 - assert datasources[0].datasource_type == "excel-direct" - assert datasources[0].updated_at == parse_datetime("2024-02-14T04:42:14Z") - assert not datasources[0].use_remote_query_agent - assert datasources[0].server_name == "localhost" - assert datasources[0].webpage_url == "https://10ax.online.tableau.com/#/site/example/datasources/3566752" - assert isinstance(datasources[0].project, TSC.ProjectItem) - assert datasources[0].project.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a" - assert datasources[0].project.name == "Samples" - assert datasources[0].project.description == "This project includes automatically uploaded samples." - assert datasources[0].owner.email == "bob@example.com" - assert isinstance(datasources[0].owner, TSC.UserItem) - assert datasources[0].owner.fullname == "Bob Smith" - assert datasources[0].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9" - assert datasources[0].owner.last_login == parse_datetime("2025-02-04T06:39:20Z") - assert datasources[0].owner.name == "bob@example.com" - assert datasources[0].owner.site_role == "SiteAdministratorCreator" diff --git a/test/test_group.py b/test/test_group.py index b3de07963..41b5992be 100644 --- a/test/test_group.py +++ b/test/test_group.py @@ -10,7 +10,6 @@ # TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") GET_XML = os.path.join(TEST_ASSET_DIR, "group_get.xml") -GET_XML_ALL_FIELDS = TEST_ASSET_DIR / "group_get_all_fields.xml" POPULATE_USERS = os.path.join(TEST_ASSET_DIR, "group_populate_users.xml") POPULATE_USERS_EMPTY = os.path.join(TEST_ASSET_DIR, "group_populate_users_empty.xml") ADD_USER = os.path.join(TEST_ASSET_DIR, "group_add_user.xml") @@ -311,25 +310,3 @@ def test_update_ad_async(self) -> None: self.assertEqual(job.id, "c2566efc-0767-4f15-89cb-56acb4349c1b") self.assertEqual(job.mode, "Asynchronous") self.assertEqual(job.type, "GroupSync") - - def test_get_all_fields(self) -> None: - ro = TSC.RequestOptions() - ro.all_fields = True - self.server.version = "3.21" - self.baseurl = self.server.groups.baseurl - with requests_mock.mock() as m: - m.get(f"{self.baseurl}?fields=_all_", text=GET_XML_ALL_FIELDS.read_text()) - groups, pages = self.server.groups.get(req_options=ro) - - assert pages.total_available == 3 - assert len(groups) == 3 - assert groups[0].id == "28c5b855-16df-482f-ad0b-428c1df58859" - assert groups[0].name == "All Users" - assert groups[0].user_count == 2 - assert groups[0].domain_name == "local" - assert groups[1].id == "ace1ee2d-e7dd-4d7a-9504-a1ccaa5212ea" - assert groups[1].name == "group1" - assert groups[1].user_count == 1 - assert groups[2].id == "baf0ed9d-c25d-4114-97ed-5232b8a732fd" - assert groups[2].name == "test" - assert groups[2].user_count == 0 diff --git a/test/test_project.py b/test/test_project.py index c51f2e1e6..56787efac 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -10,7 +10,6 @@ TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") GET_XML = asset("project_get.xml") -GET_XML_ALL_FIELDS = asset("project_get_all_fields.xml") UPDATE_XML = asset("project_update.xml") SET_CONTENT_PERMISSIONS_XML = asset("project_content_permission.xml") CREATE_XML = asset("project_create.xml") @@ -411,28 +410,3 @@ def test_delete_virtualconnection_default_permimssions(self): m.delete(f"{base_url}/{endpoint}/Connect/Allow", status_code=204) self.server.projects.delete_virtualconnection_default_permissions(project, rule) - - def test_get_all_fields(self) -> None: - self.server.version = "3.23" - base_url = self.server.projects.baseurl - with open(GET_XML_ALL_FIELDS, "rb") as f: - response_xml = f.read().decode("utf-8") - - ro = TSC.RequestOptions() - ro.all_fields = True - - with requests_mock.mock() as m: - m.get(f"{base_url}?fields=_all_", text=response_xml) - all_projects, pagination_item = self.server.projects.get(req_options=ro) - - assert pagination_item.total_available == 3 - assert len(all_projects) == 1 - project: TSC.ProjectItem = all_projects[0] - assert isinstance(project, TSC.ProjectItem) - assert project.id == "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" - assert project.name == "Samples" - assert project.description == "This project includes automatically uploaded samples." - assert project.top_level_project is True - assert project.content_permissions == "ManagedByOwner" - assert project.parent_id is None - assert project.writeable is True diff --git a/test/test_request_option.py b/test/test_request_option.py index 57dfdc2a0..7405189a3 100644 --- a/test/test_request_option.py +++ b/test/test_request_option.py @@ -251,7 +251,7 @@ def test_all_fields(self) -> None: m.get(requests_mock.ANY) url = self.baseurl + "/views/456/data" opts = TSC.RequestOptions() - opts.all_fields = True + opts._all_fields = True resp = self.server.users.get_request(url, request_object=opts) self.assertTrue(re.search("fields=_all_", resp.request.query)) @@ -368,13 +368,3 @@ def test_language_export(self) -> None: resp = self.server.users.get_request(url, request_object=opts) self.assertTrue(re.search("language=en-us", resp.request.query)) - - def test_queryset_fields(self) -> None: - loop = self.server.users.fields("id") - assert "id" in loop.request_options.fields - assert "_default_" in loop.request_options.fields - - def test_queryset_only_fields(self) -> None: - loop = self.server.users.only_fields("id") - assert "id" in loop.request_options.fields - assert "_default_" not in loop.request_options.fields diff --git a/test/test_schedule.py b/test/test_schedule.py index 4fcc85e18..b072522a4 100644 --- a/test/test_schedule.py +++ b/test/test_schedule.py @@ -25,7 +25,6 @@ ADD_WORKBOOK_TO_SCHEDULE_WITH_WARNINGS = os.path.join(TEST_ASSET_DIR, "schedule_add_workbook_with_warnings.xml") ADD_DATASOURCE_TO_SCHEDULE = os.path.join(TEST_ASSET_DIR, "schedule_add_datasource.xml") ADD_FLOW_TO_SCHEDULE = os.path.join(TEST_ASSET_DIR, "schedule_add_flow.xml") -GET_EXTRACT_TASKS_XML = os.path.join(TEST_ASSET_DIR, "schedule_get_extract_refresh_tasks.xml") WORKBOOK_GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, "workbook_get_by_id.xml") DATASOURCE_GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, "datasource_get_by_id.xml") @@ -406,20 +405,3 @@ def test_add_flow(self) -> None: flow = self.server.flows.get_by_id("bar") result = self.server.schedules.add_to_schedule("foo", flow=flow) self.assertEqual(0, len(result), "Added properly") - - def test_get_extract_refresh_tasks(self) -> None: - self.server.version = "2.3" - - with open(GET_EXTRACT_TASKS_XML, "rb") as f: - response_xml = f.read().decode("utf-8") - with requests_mock.mock() as m: - schedule_id = "c9cff7f9-309c-4361-99ff-d4ba8c9f5467" - baseurl = f"{self.server.baseurl}/sites/{self.server.site_id}/schedules/{schedule_id}/extracts" - m.get(baseurl, text=response_xml) - - extracts = self.server.schedules.get_extract_refresh_tasks(schedule_id) - - self.assertIsNotNone(extracts) - self.assertIsInstance(extracts[0], list) - self.assertEqual(2, len(extracts[0])) - self.assertEqual("task1", extracts[0][0].id) diff --git a/test/test_site.py b/test/test_site.py index 243810254..96b75f9ff 100644 --- a/test/test_site.py +++ b/test/test_site.py @@ -13,7 +13,6 @@ GET_BY_NAME_XML = os.path.join(TEST_ASSET_DIR, "site_get_by_name.xml") UPDATE_XML = os.path.join(TEST_ASSET_DIR, "site_update.xml") CREATE_XML = os.path.join(TEST_ASSET_DIR, "site_create.xml") -SITE_AUTH_CONFIG_XML = os.path.join(TEST_ASSET_DIR, "site_auth_configurations.xml") class SiteTests(unittest.TestCase): @@ -261,28 +260,3 @@ def test_decrypt(self) -> None: with requests_mock.mock() as m: m.post(self.baseurl + "/0626857c-1def-4503-a7d8-7907c3ff9d9f/decrypt-extracts", status_code=200) self.server.sites.decrypt_extracts("0626857c-1def-4503-a7d8-7907c3ff9d9f") - - def test_list_auth_configurations(self) -> None: - self.server.version = "3.24" - self.baseurl = self.server.sites.baseurl - with open(SITE_AUTH_CONFIG_XML, "rb") as f: - response_xml = f.read().decode("utf-8") - - assert self.baseurl == self.server.sites.baseurl - - with requests_mock.mock() as m: - m.get(f"{self.baseurl}/{self.server.site_id}/site-auth-configurations", status_code=200, text=response_xml) - configs = self.server.sites.list_auth_configurations() - - assert len(configs) == 2, "Expected 2 auth configurations" - - assert configs[0].auth_setting == "OIDC" - assert configs[0].enabled - assert configs[0].idp_configuration_id == "00000000-0000-0000-0000-000000000000" - assert configs[0].idp_configuration_name == "Initial Salesforce" - assert configs[0].known_provider_alias == "Salesforce" - assert configs[1].auth_setting == "SAML" - assert configs[1].enabled - assert configs[1].idp_configuration_id == "11111111-1111-1111-1111-111111111111" - assert configs[1].idp_configuration_name == "Initial SAML" - assert configs[1].known_provider_alias is None diff --git a/test/test_ssl_config.py b/test/test_ssl_config.py deleted file mode 100644 index 036a326ca..000000000 --- a/test/test_ssl_config.py +++ /dev/null @@ -1,77 +0,0 @@ -import unittest -import ssl -from unittest.mock import patch, MagicMock -from tableauserverclient import Server -from tableauserverclient.server.endpoint import Endpoint -import logging - - -class TestSSLConfig(unittest.TestCase): - @patch("requests.session") - @patch("tableauserverclient.server.endpoint.Endpoint.set_parameters") - def setUp(self, mock_set_parameters, mock_session): - """Set up test fixtures with mocked session and request validation""" - # Mock the session - self.mock_session = MagicMock() - mock_session.return_value = self.mock_session - - # Mock request preparation - self.mock_request = MagicMock() - self.mock_session.prepare_request.return_value = self.mock_request - - # Create server instance with mocked components - self.server = Server("http://test") - - def test_default_ssl_config(self): - """Test that by default, no custom SSL context is used""" - self.assertIsNone(self.server._ssl_context) - self.assertNotIn("verify", self.server.http_options) - - @patch("ssl.create_default_context") - def test_weak_dh_config(self, mock_create_context): - """Test that weak DH keys can be allowed when configured""" - # Setup mock SSL context - mock_context = MagicMock() - mock_create_context.return_value = mock_context - - # Configure SSL with weak DH - self.server.configure_ssl(allow_weak_dh=True) - - # Verify SSL context was created and configured correctly - mock_create_context.assert_called_once() - mock_context.set_dh_parameters.assert_called_once_with(min_key_bits=512) - - # Verify context was added to http options - self.assertEqual(self.server.http_options["verify"], mock_context) - - @patch("ssl.create_default_context") - def test_disable_weak_dh_config(self, mock_create_context): - """Test that SSL config can be reset to defaults""" - # Setup mock SSL context - mock_context = MagicMock() - mock_create_context.return_value = mock_context - - # First enable weak DH - self.server.configure_ssl(allow_weak_dh=True) - self.assertIsNotNone(self.server._ssl_context) - self.assertIn("verify", self.server.http_options) - - # Then disable it - self.server.configure_ssl(allow_weak_dh=False) - self.assertIsNone(self.server._ssl_context) - self.assertNotIn("verify", self.server.http_options) - - @patch("ssl.create_default_context") - def test_warning_on_weak_dh(self, mock_create_context): - """Test that a warning is logged when enabling weak DH keys""" - logging.getLogger().setLevel(logging.WARNING) - with self.assertLogs(level="WARNING") as log: - self.server.configure_ssl(allow_weak_dh=True) - self.assertTrue( - any("WARNING: Allowing weak Diffie-Hellman keys" in record for record in log.output), - "Expected warning about weak DH keys was not logged", - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/test_user.py b/test/test_user.py index fa2ac3a12..a46624845 100644 --- a/test/test_user.py +++ b/test/test_user.py @@ -1,16 +1,14 @@ import os import unittest -from defusedxml import ElementTree as ET import requests_mock import tableauserverclient as TSC -from tableauserverclient.datetime_helpers import format_datetime, parse_datetime +from tableauserverclient.datetime_helpers import format_datetime TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") GET_XML = os.path.join(TEST_ASSET_DIR, "user_get.xml") -GET_XML_ALL_FIELDS = os.path.join(TEST_ASSET_DIR, "user_get_all_fields.xml") GET_EMPTY_XML = os.path.join(TEST_ASSET_DIR, "user_get_empty.xml") GET_BY_ID_XML = os.path.join(TEST_ASSET_DIR, "user_get_by_id.xml") UPDATE_XML = os.path.join(TEST_ASSET_DIR, "user_update.xml") @@ -164,22 +162,6 @@ def test_populate_workbooks(self) -> None: self.assertEqual("5de011f8-5aa9-4d5b-b991-f462c8dd6bb7", workbook_list[0].owner_id) self.assertEqual({"Safari", "Sample"}, workbook_list[0].tags) - def test_populate_owned_workbooks(self) -> None: - with open(POPULATE_WORKBOOKS_XML, "rb") as f: - response_xml = f.read().decode("utf-8") - # Query parameter ownedBy is case sensitive. - with requests_mock.mock(case_sensitive=True) as m: - m.get(self.baseurl + "/dd2239f6-ddf1-4107-981a-4cf94e415794/workbooks?ownedBy=true", text=response_xml) - single_user = TSC.UserItem("test", "Interactor") - single_user._id = "dd2239f6-ddf1-4107-981a-4cf94e415794" - self.server.users.populate_workbooks(single_user, owned_only=True) - list(single_user.workbooks) - - request_history = m.request_history[0] - - assert "ownedBy" in request_history.qs, "ownedBy not in request history" - assert "true" in request_history.qs["ownedBy"], "ownedBy not set to true in request history" - def test_populate_workbooks_missing_id(self) -> None: single_user = TSC.UserItem("test", "Interactor") self.assertRaises(TSC.MissingRequiredFieldError, self.server.users.populate_workbooks, single_user) @@ -251,72 +233,3 @@ def test_get_users_from_file(self): users, failures = self.server.users.create_from_file(USERS) assert users[0].name == "Cassie", users assert failures == [] - - def test_get_users_all_fields(self) -> None: - self.server.version = "3.7" - baseurl = self.server.users.baseurl - with open(GET_XML_ALL_FIELDS) as f: - response_xml = f.read() - - with requests_mock.mock() as m: - m.get(f"{baseurl}?fields=_all_", text=response_xml) - all_users, _ = self.server.users.get() - - assert all_users[0].auth_setting == "TableauIDWithMFA" - assert all_users[0].email == "bob@example.com" - assert all_users[0].external_auth_user_id == "38c870c3ac5e84ec66e6ced9fb23681835b07e56d5660371ac1f705cc65bd610" - assert all_users[0].fullname == "Bob Smith" - assert all_users[0].id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9" - assert all_users[0].last_login == parse_datetime("2025-02-04T06:39:20Z") - assert all_users[0].name == "bob@example.com" - assert all_users[0].site_role == "SiteAdministratorCreator" - assert all_users[0].locale is None - assert all_users[0].language == "en" - assert all_users[0].idp_configuration_id == "22222222-2222-2222-2222-222222222222" - assert all_users[0].domain_name == "TABID_WITH_MFA" - assert all_users[1].auth_setting == "TableauIDWithMFA" - assert all_users[1].email == "alice@example.com" - assert all_users[1].external_auth_user_id == "96f66b893b22669cdfa632275d354cd1d92cea0266f3be7702151b9b8c52be29" - assert all_users[1].fullname == "Alice Jones" - assert all_users[1].id == "f6d72445-285b-48e5-8380-f90b519ce682" - assert all_users[1].name == "alice@example.com" - assert all_users[1].site_role == "ExplorerCanPublish" - assert all_users[1].locale is None - assert all_users[1].language == "en" - assert all_users[1].idp_configuration_id == "22222222-2222-2222-2222-222222222222" - assert all_users[1].domain_name == "TABID_WITH_MFA" - - def test_add_user_idp_configuration(self) -> None: - with open(ADD_XML) as f: - response_xml = f.read() - user = TSC.UserItem(name="Cassie", site_role="Viewer") - user.idp_configuration_id = "012345" - - with requests_mock.mock() as m: - m.post(self.server.users.baseurl, text=response_xml) - user = self.server.users.add(user) - - history = m.request_history[0] - - tree = ET.fromstring(history.text) - user_elem = tree.find(".//user") - assert user_elem is not None - assert user_elem.attrib["idpConfigurationId"] == "012345" - - def test_update_user_idp_configuration(self) -> None: - with open(ADD_XML) as f: - response_xml = f.read() - user = TSC.UserItem(name="Cassie", site_role="Viewer") - user._id = "0123456789" - user.idp_configuration_id = "012345" - - with requests_mock.mock() as m: - m.put(f"{self.server.users.baseurl}/{user.id}", text=response_xml) - user = self.server.users.update(user) - - history = m.request_history[0] - - tree = ET.fromstring(history.text) - user_elem = tree.find(".//user") - assert user_elem is not None - assert user_elem.attrib["idpConfigurationId"] == "012345" diff --git a/test/test_view.py b/test/test_view.py index ee6d518de..3fdaf60e6 100644 --- a/test/test_view.py +++ b/test/test_view.py @@ -5,14 +5,13 @@ import tableauserverclient as TSC from tableauserverclient import UserItem, GroupItem, PermissionsRule -from tableauserverclient.datetime_helpers import format_datetime, parse_datetime +from tableauserverclient.datetime_helpers import format_datetime from tableauserverclient.server.endpoint.exceptions import UnsupportedAttributeError TEST_ASSET_DIR = os.path.join(os.path.dirname(__file__), "assets") ADD_TAGS_XML = os.path.join(TEST_ASSET_DIR, "view_add_tags.xml") GET_XML = os.path.join(TEST_ASSET_DIR, "view_get.xml") -GET_XML_ALL_FIELDS = os.path.join(TEST_ASSET_DIR, "view_get_all_fields.xml") GET_XML_ID = os.path.join(TEST_ASSET_DIR, "view_get_id.xml") GET_XML_USAGE = os.path.join(TEST_ASSET_DIR, "view_get_usage.xml") GET_XML_ID_USAGE = os.path.join(TEST_ASSET_DIR, "view_get_id_usage.xml") @@ -403,116 +402,3 @@ def test_pdf_errors(self) -> None: req_option = TSC.PDFRequestOptions(viz_width=1920) with self.assertRaises(ValueError): req_option.get_query_params() - - def test_view_get_all_fields(self) -> None: - self.server.version = "3.21" - self.baseurl = self.server.views.baseurl - with open(GET_XML_ALL_FIELDS) as f: - response_xml = f.read() - - ro = TSC.RequestOptions() - ro.all_fields = True - - with requests_mock.mock() as m: - m.get(f"{self.baseurl}?fields=_all_", text=response_xml) - views, _ = self.server.views.get(req_options=ro) - - assert views[0].id == "2bdcd787-dcc6-4a5d-bc61-2846f1ef4534" - assert views[0].name == "Overview" - assert views[0].content_url == "Superstore/sheets/Overview" - assert views[0].created_at == parse_datetime("2024-02-14T04:42:09Z") - assert views[0].updated_at == parse_datetime("2024-02-14T04:42:09Z") - assert views[0].sheet_type == "dashboard" - assert views[0].favorites_total == 0 - assert views[0].view_url_name == "Overview" - assert isinstance(views[0].workbook, TSC.WorkbookItem) - assert views[0].workbook.id == "9df3e2d1-070e-497a-9578-8cc557ced9df" - assert views[0].workbook.name == "Superstore" - assert views[0].workbook.content_url == "Superstore" - assert views[0].workbook.show_tabs - assert views[0].workbook.size == 2 - assert views[0].workbook.created_at == parse_datetime("2024-02-14T04:42:09Z") - assert views[0].workbook.updated_at == parse_datetime("2024-02-14T04:42:10Z") - assert views[0].workbook.sheet_count == 9 - assert not views[0].workbook.has_extracts - assert isinstance(views[0].owner, TSC.UserItem) - assert views[0].owner.email == "bob@example.com" - assert views[0].owner.fullname == "Bob" - assert views[0].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9" - assert views[0].owner.last_login == parse_datetime("2025-02-04T06:39:20Z") - assert views[0].owner.name == "bob@example.com" - assert views[0].owner.site_role == "SiteAdministratorCreator" - assert isinstance(views[0].project, TSC.ProjectItem) - assert views[0].project.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a" - assert views[0].project.name == "Samples" - assert views[0].project.description == "This project includes automatically uploaded samples." - assert views[0].total_views == 0 - assert isinstance(views[0].location, TSC.LocationItem) - assert views[0].location.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a" - assert views[0].location.type == "Project" - assert views[1].id == "2a3fd19d-9129-413d-9ff7-9dfc36bf7f7e" - assert views[1].name == "Product" - assert views[1].content_url == "Superstore/sheets/Product" - assert views[1].created_at == parse_datetime("2024-02-14T04:42:09Z") - assert views[1].updated_at == parse_datetime("2024-02-14T04:42:09Z") - assert views[1].sheet_type == "dashboard" - assert views[1].favorites_total == 0 - assert views[1].view_url_name == "Product" - assert isinstance(views[1].workbook, TSC.WorkbookItem) - assert views[1].workbook.id == "9df3e2d1-070e-497a-9578-8cc557ced9df" - assert views[1].workbook.name == "Superstore" - assert views[1].workbook.content_url == "Superstore" - assert views[1].workbook.show_tabs - assert views[1].workbook.size == 2 - assert views[1].workbook.created_at == parse_datetime("2024-02-14T04:42:09Z") - assert views[1].workbook.updated_at == parse_datetime("2024-02-14T04:42:10Z") - assert views[1].workbook.sheet_count == 9 - assert not views[1].workbook.has_extracts - assert isinstance(views[1].owner, TSC.UserItem) - assert views[1].owner.email == "bob@example.com" - assert views[1].owner.fullname == "Bob" - assert views[1].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9" - assert views[1].owner.last_login == parse_datetime("2025-02-04T06:39:20Z") - assert views[1].owner.name == "bob@example.com" - assert views[1].owner.site_role == "SiteAdministratorCreator" - assert isinstance(views[1].project, TSC.ProjectItem) - assert views[1].project.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a" - assert views[1].project.name == "Samples" - assert views[1].project.description == "This project includes automatically uploaded samples." - assert views[1].total_views == 0 - assert isinstance(views[1].location, TSC.LocationItem) - assert views[1].location.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a" - assert views[1].location.type == "Project" - assert views[2].id == "459eda9a-85e4-46bf-a2f2-62936bd2e99a" - assert views[2].name == "Customers" - assert views[2].content_url == "Superstore/sheets/Customers" - assert views[2].created_at == parse_datetime("2024-02-14T04:42:09Z") - assert views[2].updated_at == parse_datetime("2024-02-14T04:42:09Z") - assert views[2].sheet_type == "dashboard" - assert views[2].favorites_total == 0 - assert views[2].view_url_name == "Customers" - assert isinstance(views[2].workbook, TSC.WorkbookItem) - assert views[2].workbook.id == "9df3e2d1-070e-497a-9578-8cc557ced9df" - assert views[2].workbook.name == "Superstore" - assert views[2].workbook.content_url == "Superstore" - assert views[2].workbook.show_tabs - assert views[2].workbook.size == 2 - assert views[2].workbook.created_at == parse_datetime("2024-02-14T04:42:09Z") - assert views[2].workbook.updated_at == parse_datetime("2024-02-14T04:42:10Z") - assert views[2].workbook.sheet_count == 9 - assert not views[2].workbook.has_extracts - assert isinstance(views[2].owner, TSC.UserItem) - assert views[2].owner.email == "bob@example.com" - assert views[2].owner.fullname == "Bob" - assert views[2].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9" - assert views[2].owner.last_login == parse_datetime("2025-02-04T06:39:20Z") - assert views[2].owner.name == "bob@example.com" - assert views[2].owner.site_role == "SiteAdministratorCreator" - assert isinstance(views[2].project, TSC.ProjectItem) - assert views[2].project.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a" - assert views[2].project.name == "Samples" - assert views[2].project.description == "This project includes automatically uploaded samples." - assert views[2].total_views == 0 - assert isinstance(views[2].location, TSC.LocationItem) - assert views[2].location.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a" - assert views[2].location.type == "Project" diff --git a/test/test_workbook.py b/test/test_workbook.py index 84afd7fcb..f3c2dd147 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -10,7 +10,7 @@ import pytest import tableauserverclient as TSC -from tableauserverclient.datetime_helpers import format_datetime, parse_datetime +from tableauserverclient.datetime_helpers import format_datetime from tableauserverclient.models import UserItem, GroupItem, PermissionsRule from tableauserverclient.server.endpoint.exceptions import InternalServerError, UnsupportedAttributeError from tableauserverclient.server.request_factory import RequestFactory @@ -24,7 +24,6 @@ GET_EMPTY_XML = os.path.join(TEST_ASSET_DIR, "workbook_get_empty.xml") GET_INVALID_DATE_XML = os.path.join(TEST_ASSET_DIR, "workbook_get_invalid_date.xml") GET_XML = os.path.join(TEST_ASSET_DIR, "workbook_get.xml") -GET_XML_ALL_FIELDS = os.path.join(TEST_ASSET_DIR, "workbook_get_all_fields.xml") ODATA_XML = os.path.join(TEST_ASSET_DIR, "odata_connection.xml") POPULATE_CONNECTIONS_XML = os.path.join(TEST_ASSET_DIR, "workbook_populate_connections.xml") POPULATE_PDF = os.path.join(TEST_ASSET_DIR, "populate_pdf.pdf") @@ -979,106 +978,3 @@ def test_odata_connection(self) -> None: assert xml_connection is not None self.assertEqual(xml_connection.get("serverAddress"), url) - - def test_get_workbook_all_fields(self) -> None: - self.server.version = "3.21" - baseurl = self.server.workbooks.baseurl - - with open(GET_XML_ALL_FIELDS) as f: - response = f.read() - - ro = TSC.RequestOptions() - ro.all_fields = True - - with requests_mock.mock() as m: - m.get(f"{baseurl}?fields=_all_", text=response) - workbooks, _ = self.server.workbooks.get(req_options=ro) - - assert workbooks[0].id == "9df3e2d1-070e-497a-9578-8cc557ced9df" - assert workbooks[0].name == "Superstore" - assert workbooks[0].content_url == "Superstore" - assert workbooks[0].webpage_url == "https://10ax.online.tableau.com/#/site/exampledev/workbooks/265605" - assert workbooks[0].show_tabs - assert workbooks[0].size == 2 - assert workbooks[0].created_at == parse_datetime("2024-02-14T04:42:09Z") - assert workbooks[0].updated_at == parse_datetime("2024-02-14T04:42:10Z") - assert workbooks[0].sheet_count == 9 - assert not workbooks[0].has_extracts - assert not workbooks[0].encrypt_extracts - assert workbooks[0].default_view_id == "2bdcd787-dcc6-4a5d-bc61-2846f1ef4534" - assert workbooks[0].share_description == "Superstore" - assert workbooks[0].last_published_at == parse_datetime("2024-02-14T04:42:09Z") - assert isinstance(workbooks[0].project, TSC.ProjectItem) - assert workbooks[0].project.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a" - assert workbooks[0].project.name == "Samples" - assert workbooks[0].project.description == "This project includes automatically uploaded samples." - assert isinstance(workbooks[0].location, TSC.LocationItem) - assert workbooks[0].location.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a" - assert workbooks[0].location.type == "Project" - assert workbooks[0].location.name == "Samples" - assert isinstance(workbooks[0].owner, TSC.UserItem) - assert workbooks[0].owner.email == "bob@example.com" - assert workbooks[0].owner.fullname == "Bob Smith" - assert workbooks[0].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9" - assert workbooks[0].owner.last_login == parse_datetime("2025-02-04T06:39:20Z") - assert workbooks[0].owner.name == "bob@example.com" - assert workbooks[0].owner.site_role == "SiteAdministratorCreator" - assert workbooks[1].id == "6693cb26-9507-4174-ad3e-9de81a18c971" - assert workbooks[1].name == "World Indicators" - assert workbooks[1].content_url == "WorldIndicators" - assert workbooks[1].webpage_url == "https://10ax.online.tableau.com/#/site/exampledev/workbooks/265606" - assert workbooks[1].show_tabs - assert workbooks[1].size == 1 - assert workbooks[1].created_at == parse_datetime("2024-02-14T04:42:11Z") - assert workbooks[1].updated_at == parse_datetime("2024-02-14T04:42:12Z") - assert workbooks[1].sheet_count == 8 - assert not workbooks[1].has_extracts - assert not workbooks[1].encrypt_extracts - assert workbooks[1].default_view_id == "3d10dbcf-a206-47c7-91ba-ebab3ab33d7c" - assert workbooks[1].share_description == "World Indicators" - assert workbooks[1].last_published_at == parse_datetime("2024-02-14T04:42:11Z") - assert isinstance(workbooks[1].project, TSC.ProjectItem) - assert workbooks[1].project.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a" - assert workbooks[1].project.name == "Samples" - assert workbooks[1].project.description == "This project includes automatically uploaded samples." - assert isinstance(workbooks[1].location, TSC.LocationItem) - assert workbooks[1].location.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a" - assert workbooks[1].location.type == "Project" - assert workbooks[1].location.name == "Samples" - assert isinstance(workbooks[1].owner, TSC.UserItem) - assert workbooks[1].owner.email == "bob@example.com" - assert workbooks[1].owner.fullname == "Bob Smith" - assert workbooks[1].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9" - assert workbooks[1].owner.last_login == parse_datetime("2025-02-04T06:39:20Z") - assert workbooks[1].owner.name == "bob@example.com" - assert workbooks[1].owner.site_role == "SiteAdministratorCreator" - assert workbooks[2].id == "dbc0f162-909f-4edf-8392-0d12a80af955" - assert workbooks[2].name == "Superstore" - assert workbooks[2].description == "This is a superstore workbook" - assert workbooks[2].content_url == "Superstore_17078880698360" - assert workbooks[2].webpage_url == "https://10ax.online.tableau.com/#/site/exampledev/workbooks/265621" - assert not workbooks[2].show_tabs - assert workbooks[2].size == 1 - assert workbooks[2].created_at == parse_datetime("2024-02-14T05:21:09Z") - assert workbooks[2].updated_at == parse_datetime("2024-07-02T02:19:59Z") - assert workbooks[2].sheet_count == 7 - assert workbooks[2].has_extracts - assert not workbooks[2].encrypt_extracts - assert workbooks[2].default_view_id == "8c4b1d3e-3f31-4d2a-8b9f-492b92f27987" - assert workbooks[2].share_description == "Superstore" - assert workbooks[2].last_published_at == parse_datetime("2024-07-02T02:19:58Z") - assert isinstance(workbooks[2].project, TSC.ProjectItem) - assert workbooks[2].project.id == "9836791c-9468-40f0-b7f3-d10b9562a046" - assert workbooks[2].project.name == "default" - assert workbooks[2].project.description == "The default project that was automatically created by Tableau." - assert isinstance(workbooks[2].location, TSC.LocationItem) - assert workbooks[2].location.id == "9836791c-9468-40f0-b7f3-d10b9562a046" - assert workbooks[2].location.type == "Project" - assert workbooks[2].location.name == "default" - assert isinstance(workbooks[2].owner, TSC.UserItem) - assert workbooks[2].owner.email == "bob@example.com" - assert workbooks[2].owner.fullname == "Bob Smith" - assert workbooks[2].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9" - assert workbooks[2].owner.last_login == parse_datetime("2025-02-04T06:39:20Z") - assert workbooks[2].owner.name == "bob@example.com" - assert workbooks[2].owner.site_role == "SiteAdministratorCreator"