Skip to content

fix: remove vizHeight and vizWidth from ImageRequestOptions #1565

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tableauserverclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
ExcelRequestOptions,
ImageRequestOptions,
PDFRequestOptions,
PPTXRequestOptions,
RequestOptions,
MissingRequiredFieldError,
FailedSignInError,
Expand Down Expand Up @@ -107,6 +108,7 @@
"Pager",
"PaginationItem",
"PDFRequestOptions",
"PPTXRequestOptions",
"Permission",
"PermissionsRule",
"PersonalAccessTokenAuth",
Expand Down
2 changes: 2 additions & 0 deletions tableauserverclient/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
ExcelRequestOptions,
ImageRequestOptions,
PDFRequestOptions,
PPTXRequestOptions,
RequestOptions,
)
from tableauserverclient.server.filter import Filter
Expand Down Expand Up @@ -52,6 +53,7 @@
"ExcelRequestOptions",
"ImageRequestOptions",
"PDFRequestOptions",
"PPTXRequestOptions",
"RequestOptions",
"Filter",
"Sort",
Expand Down
4 changes: 4 additions & 0 deletions tableauserverclient/server/endpoint/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,7 @@ def __str__(self):

class FlowRunCancelledException(FlowRunFailedException):
pass


class UnsupportedAttributeError(TableauError):
pass
6 changes: 5 additions & 1 deletion tableauserverclient/server/endpoint/views_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from tableauserverclient.models.permissions_item import PermissionsRule
from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api
from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError
from tableauserverclient.server.endpoint.exceptions import MissingRequiredFieldError, UnsupportedAttributeError
from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin
from tableauserverclient.server.query import QuerySet
Expand Down Expand Up @@ -171,6 +171,10 @@ def populate_image(self, view_item: ViewItem, req_options: Optional["ImageReques
def image_fetcher():
return self._get_view_image(view_item, req_options)

if not self.parent_srv.check_at_least_version("3.23") and req_options is not None:
if req_options.viz_height or req_options.viz_width:
raise UnsupportedAttributeError("viz_height and viz_width are only supported in 3.23+")

view_item._set_image(image_fetcher)
logger.info(f"Populated image for view (ID: {view_item.id})")

Expand Down
32 changes: 23 additions & 9 deletions tableauserverclient/server/endpoint/workbooks_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
from tableauserverclient.server.query import QuerySet

from tableauserverclient.server.endpoint.endpoint import QuerysetEndpoint, api, parameter_added_in
from tableauserverclient.server.endpoint.exceptions import InternalServerError, MissingRequiredFieldError
from tableauserverclient.server.endpoint.exceptions import (
InternalServerError,
MissingRequiredFieldError,
UnsupportedAttributeError,
)
from tableauserverclient.server.endpoint.permissions_endpoint import _PermissionsEndpoint
from tableauserverclient.server.endpoint.resource_tagger import TaggingMixin

Expand All @@ -34,7 +38,7 @@

if TYPE_CHECKING:
from tableauserverclient.server import Server
from tableauserverclient.server.request_options import RequestOptions
from tableauserverclient.server.request_options import RequestOptions, PDFRequestOptions, PPTXRequestOptions
from tableauserverclient.models import DatasourceItem
from tableauserverclient.server.endpoint.schedules_endpoint import AddResponse

Expand Down Expand Up @@ -472,11 +476,12 @@ def _get_workbook_connections(
connections = ConnectionItem.from_response(server_response.content, self.parent_srv.namespace)
return connections

# Get the pdf of the entire workbook if its tabs are enabled, pdf of the default view if its tabs are disabled
@api(version="3.4")
def populate_pdf(self, workbook_item: WorkbookItem, req_options: Optional["RequestOptions"] = None) -> None:
def populate_pdf(self, workbook_item: WorkbookItem, req_options: Optional["PDFRequestOptions"] = None) -> None:
"""
Populates the PDF for the specified workbook item.
Populates the PDF for the specified workbook item. Get the pdf of the
entire workbook if its tabs are enabled, pdf of the default view if its
tabs are disabled.

This method populates a PDF with image(s) of the workbook view(s) you
specify.
Expand All @@ -488,7 +493,7 @@ def populate_pdf(self, workbook_item: WorkbookItem, req_options: Optional["Reque
workbook_item : WorkbookItem
The workbook item to populate the PDF for.

req_options : RequestOptions, optional
req_options : PDFRequestOptions, optional
(Optional) You can pass in request options to specify the page type
and orientation of the PDF content, as well as the maximum age of
the PDF rendered on the server. See PDFRequestOptions class for more
Expand All @@ -510,17 +515,26 @@ def populate_pdf(self, workbook_item: WorkbookItem, req_options: Optional["Reque
def pdf_fetcher() -> bytes:
return self._get_wb_pdf(workbook_item, req_options)

if not self.parent_srv.check_at_least_version("3.23") and req_options is not None:
if req_options.view_filters or req_options.view_parameters:
raise UnsupportedAttributeError("view_filters and view_parameters are only supported in 3.23+")

if req_options.viz_height or req_options.viz_width:
raise UnsupportedAttributeError("viz_height and viz_width are only supported in 3.23+")

workbook_item._set_pdf(pdf_fetcher)
logger.info(f"Populated pdf for workbook (ID: {workbook_item.id})")

def _get_wb_pdf(self, workbook_item: WorkbookItem, req_options: Optional["RequestOptions"]) -> bytes:
def _get_wb_pdf(self, workbook_item: WorkbookItem, req_options: Optional["PDFRequestOptions"]) -> bytes:
url = f"{self.baseurl}/{workbook_item.id}/pdf"
server_response = self.get_request(url, req_options)
pdf = server_response.content
return pdf

@api(version="3.8")
def populate_powerpoint(self, workbook_item: WorkbookItem, req_options: Optional["RequestOptions"] = None) -> None:
def populate_powerpoint(
self, workbook_item: WorkbookItem, req_options: Optional["PPTXRequestOptions"] = None
) -> None:
"""
Populates the PowerPoint for the specified workbook item.

Expand Down Expand Up @@ -561,7 +575,7 @@ def pptx_fetcher() -> bytes:
workbook_item._set_powerpoint(pptx_fetcher)
logger.info(f"Populated powerpoint for workbook (ID: {workbook_item.id})")

def _get_wb_pptx(self, workbook_item: WorkbookItem, req_options: Optional["RequestOptions"]) -> bytes:
def _get_wb_pptx(self, workbook_item: WorkbookItem, req_options: Optional["PPTXRequestOptions"]) -> bytes:
url = f"{self.baseurl}/{workbook_item.id}/powerpoint"
server_response = self.get_request(url, req_options)
pptx = server_response.content
Expand Down
34 changes: 34 additions & 0 deletions tableauserverclient/server/request_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ class PDFRequestOptions(_ImagePDFCommonExportOptions):
Options that can be used when exporting a view to PDF. Set the maxage to control the age of the data exported.
Filters to the underlying data can be applied using the `vf` and `parameter` methods.

vf and parameter filters are only supported in API version 3.23 and later.

Parameters
----------
page_type: str, optional
Expand Down Expand Up @@ -438,3 +440,35 @@ def get_query_params(self) -> dict:
params["orientation"] = self.orientation

return params


class PPTXRequestOptions(RequestOptionsBase):
"""
Options that can be used when exporting a view to PPTX. Set the maxage to control the age of the data exported.

Parameters
----------
maxage: int, optional
The maximum age of the data to export. Shortest possible duration is 1
minute. No upper limit. Default is -1, which means no limit.
"""

def __init__(self, maxage=-1):
super().__init__()
self.max_age = maxage

@property
def max_age(self) -> int:
return self._max_age

@max_age.setter
@property_is_int(range=(0, 240), allowed=[-1])
def max_age(self, value):
self._max_age = value

def get_query_params(self):
params = {}
if self.max_age != -1:
params["maxAge"] = self.max_age

return params
38 changes: 38 additions & 0 deletions test/test_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import tableauserverclient as TSC
from tableauserverclient import UserItem, GroupItem, PermissionsRule
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")

Expand Down Expand Up @@ -177,6 +178,43 @@ def test_populate_image(self) -> None:
self.server.views.populate_image(single_view)
self.assertEqual(response, single_view.image)

def test_populate_image_unsupported(self) -> None:
self.server.version = "3.8"
with open(POPULATE_PREVIEW_IMAGE, "rb") as f:
response = f.read()
with requests_mock.mock() as m:
m.get(
self.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?vizWidth=1920&vizHeight=1080",
content=response,
)
single_view = TSC.ViewItem()
single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"

req_option = TSC.ImageRequestOptions(viz_width=1920, viz_height=1080)

with self.assertRaises(UnsupportedAttributeError):
self.server.views.populate_image(single_view, req_option)

def test_populate_image_viz_dimensions(self) -> None:
self.server.version = "3.23"
self.baseurl = self.server.views.baseurl
with open(POPULATE_PREVIEW_IMAGE, "rb") as f:
response = f.read()
with requests_mock.mock() as m:
m.get(
self.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?vizWidth=1920&vizHeight=1080",
content=response,
)
single_view = TSC.ViewItem()
single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"

req_option = TSC.ImageRequestOptions(viz_width=1920, viz_height=1080)

self.server.views.populate_image(single_view, req_option)
self.assertEqual(response, single_view.image)

history = m.request_history

def test_populate_image_with_options(self) -> None:
with open(POPULATE_PREVIEW_IMAGE, "rb") as f:
response = f.read()
Expand Down
51 changes: 48 additions & 3 deletions test/test_workbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import tableauserverclient as TSC
from tableauserverclient.datetime_helpers import format_datetime
from tableauserverclient.models import UserItem, GroupItem, PermissionsRule
from tableauserverclient.server.endpoint.exceptions import InternalServerError
from tableauserverclient.server.endpoint.exceptions import InternalServerError, UnsupportedAttributeError
from tableauserverclient.server.request_factory import RequestFactory
from ._utils import asset

Expand Down Expand Up @@ -450,20 +450,65 @@ def test_populate_pdf(self) -> None:
self.server.workbooks.populate_pdf(single_workbook, req_option)
self.assertEqual(response, single_workbook.pdf)

def test_populate_pdf_unsupported(self) -> None:
self.server.version = "3.4"
self.baseurl = self.server.workbooks.baseurl
with requests_mock.mock() as m:
m.get(
self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/pdf?type=a5&orientation=landscape",
content=b"",
)
single_workbook = TSC.WorkbookItem("test")
single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"

type = TSC.PDFRequestOptions.PageType.A5
orientation = TSC.PDFRequestOptions.Orientation.Landscape
req_option = TSC.PDFRequestOptions(type, orientation)
req_option.vf("Region", "West")

with self.assertRaises(UnsupportedAttributeError):
self.server.workbooks.populate_pdf(single_workbook, req_option)

def test_populate_pdf_vf_dims(self) -> None:
self.server.version = "3.23"
self.baseurl = self.server.workbooks.baseurl
with open(POPULATE_PDF, "rb") as f:
response = f.read()
with requests_mock.mock() as m:
m.get(
self.baseurl
+ "/1f951daf-4061-451a-9df1-69a8062664f2/pdf?type=a5&orientation=landscape&vf_Region=West&vizWidth=1920&vizHeight=1080",
content=response,
)
single_workbook = TSC.WorkbookItem("test")
single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"

type = TSC.PDFRequestOptions.PageType.A5
orientation = TSC.PDFRequestOptions.Orientation.Landscape
req_option = TSC.PDFRequestOptions(type, orientation)
req_option.vf("Region", "West")
req_option.viz_width = 1920
req_option.viz_height = 1080

self.server.workbooks.populate_pdf(single_workbook, req_option)
self.assertEqual(response, single_workbook.pdf)

def test_populate_powerpoint(self) -> None:
self.server.version = "3.8"
self.baseurl = self.server.workbooks.baseurl
with open(POPULATE_POWERPOINT, "rb") as f:
response = f.read()
with requests_mock.mock() as m:
m.get(
self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/powerpoint",
self.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/powerpoint?maxAge=1",
content=response,
)
single_workbook = TSC.WorkbookItem("test")
single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"

self.server.workbooks.populate_powerpoint(single_workbook)
ro = TSC.PPTXRequestOptions(maxage=1)

self.server.workbooks.populate_powerpoint(single_workbook, ro)
self.assertEqual(response, single_workbook.powerpoint)

def test_populate_preview_image(self) -> None:
Expand Down
Loading