From ec133ceb668eb4fc74bf196bf6304f470008c222 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Wed, 17 Jun 2026 17:56:54 -0700 Subject: [PATCH 1/2] feat: add vizWidth/vizHeight to PDFRequestOptions Fixes #1102 - PDFRequestOptions already inherited viz_width/viz_height from _ImagePDFCommonExportOptions (shared with ImageRequestOptions), which serialises them as vizWidth/vizHeight query params - Added version guard in views.populate_pdf: raises UnsupportedAttributeError when viz_height or viz_width are used below API 3.26 - Added tests for the new version guard, the happy path (API 3.26+), unit- level query-param serialisation, and the existing XOR validation - Removed stray bare literal '44' from test_request_option.py Co-Authored-By: Claude Sonnet 4.6 --- .../server/endpoint/views_endpoint.py | 8 +++- .../server/endpoint/workbooks_endpoint.py | 15 ++++--- test/test_request_option.py | 41 +++++++++++++++++-- test/test_view.py | 19 ++++++++- test/test_workbook.py | 3 +- 5 files changed, 74 insertions(+), 12 deletions(-) diff --git a/tableauserverclient/server/endpoint/views_endpoint.py b/tableauserverclient/server/endpoint/views_endpoint.py index 19a30849a..15e876d89 100644 --- a/tableauserverclient/server/endpoint/views_endpoint.py +++ b/tableauserverclient/server/endpoint/views_endpoint.py @@ -204,7 +204,8 @@ def populate_pdf(self, view_item: ViewItem, req_options: "PDFRequestOptions | No req_options: PDFRequestOptions | None, default None Optional request options for the request. These options can include - parameters such as orientation and paper size. + parameters such as orientation, paper size, and viz dimensions + (viz_width and viz_height, which require API version 3.26+). Returns ------- @@ -214,6 +215,11 @@ def populate_pdf(self, view_item: ViewItem, req_options: "PDFRequestOptions | No error = "View item missing ID." raise MissingRequiredFieldError(error) + if req_options is not None: + if not self.parent_srv.check_at_least_version("3.26"): + if req_options.viz_height or req_options.viz_width: + raise UnsupportedAttributeError("viz_height and viz_width are only supported in API 3.26+") + def pdf_fetcher(): return self._get_view_pdf(view_item, req_options) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index ce605806f..674cbce19 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -604,12 +604,15 @@ def populate_pdf(self, workbook_item: WorkbookItem, req_options: "PDFRequestOpti 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+") + if req_options is not None: + if not self.parent_srv.check_at_least_version("3.23"): + if req_options.view_filters or req_options.view_parameters: + raise UnsupportedAttributeError("view_filters and view_parameters are only supported in 3.23+") + # vizWidth/vizHeight were added to the PDF endpoint in API 3.26 + # (same version as views.populate_pdf; confirmed in REST API reference). + if not self.parent_srv.check_at_least_version("3.26"): + if req_options.viz_height or req_options.viz_width: + raise UnsupportedAttributeError("viz_height and viz_width are only supported in 3.26+") workbook_item._set_pdf(pdf_fetcher) logger.info(f"Populated pdf for workbook (ID: {workbook_item.id})") diff --git a/test/test_request_option.py b/test/test_request_option.py index 2c5354b2a..3709e18d3 100644 --- a/test/test_request_option.py +++ b/test/test_request_option.py @@ -380,9 +380,6 @@ def test_queryset_endpoint_pagesize_filter(server: TSC.Server, page_size: int) - _ = list(queryset) -44 - - @pytest.mark.parametrize("page_size", [1, 10, 100, 1_000]) def test_queryset_pagesize_filter(server: TSC.Server, page_size: int) -> None: with requests_mock.mock() as m: @@ -441,3 +438,41 @@ def test_queryset_field_all(server: TSC.Server) -> None: fields = history.qs.get("fields", [""])[0] assert fields == "_all_" + + +def test_pdf_viz_dimensions_query_params() -> None: + opts = TSC.PDFRequestOptions(viz_width=1920, viz_height=1080) + params = opts.get_query_params() + assert params["vizWidth"] == 1920 + assert params["vizHeight"] == 1080 + + +def test_pdf_viz_dimensions_only_one_raises() -> None: + opts = TSC.PDFRequestOptions(viz_width=1920) + with pytest.raises(ValueError): + opts.get_query_params() + + opts2 = TSC.PDFRequestOptions(viz_height=1080) + with pytest.raises(ValueError): + opts2.get_query_params() + + +def test_pdf_viz_dimensions_none_by_default() -> None: + opts = TSC.PDFRequestOptions() + params = opts.get_query_params() + assert "vizWidth" not in params + assert "vizHeight" not in params + + +def test_pdf_viz_dimensions_via_request(server: TSC.Server) -> None: + with requests_mock.mock() as m: + m.get(requests_mock.ANY) + url = server.views.baseurl + "/abc/pdf" + opts = TSC.PDFRequestOptions(viz_width=800, viz_height=600) + + resp = server.views.get_request(url, request_object=opts) + query_string = parse_qs(resp.request.query) + assert "vizwidth" in query_string + assert ["800"] == query_string["vizwidth"] + assert "vizheight" in query_string + assert ["600"] == query_string["vizheight"] diff --git a/test/test_view.py b/test/test_view.py index a940e1d18..e39bdd76e 100644 --- a/test/test_view.py +++ b/test/test_view.py @@ -427,7 +427,7 @@ def test_filter_excel(server: TSC.Server) -> None: assert response == excel_file -def test_pdf_height(server: TSC.Server) -> None: +def test_pdf_viz_dimensions_unsupported(server: TSC.Server) -> None: server.version = "3.8" response = POPULATE_PDF.read_bytes() with requests_mock.mock() as m: @@ -438,6 +438,23 @@ def test_pdf_height(server: TSC.Server) -> None: single_view = TSC.ViewItem() single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5" + req_option = TSC.PDFRequestOptions(viz_height=1080, viz_width=1920) + + with pytest.raises(UnsupportedAttributeError): + server.views.populate_pdf(single_view, req_option) + + +def test_pdf_viz_dimensions(server: TSC.Server) -> None: + server.version = "3.26" + response = POPULATE_PDF.read_bytes() + with requests_mock.mock() as m: + m.get( + server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/pdf?vizHeight=1080&vizWidth=1920", + content=response, + ) + single_view = TSC.ViewItem() + single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5" + req_option = TSC.PDFRequestOptions( viz_height=1080, viz_width=1920, diff --git a/test/test_workbook.py b/test/test_workbook.py index c5c4f6662..fa7edabbc 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -506,7 +506,8 @@ def test_populate_pdf_unsupported(server: TSC.Server) -> None: def test_populate_pdf_vf_dims(server: TSC.Server) -> None: - server.version = "3.23" + # vizWidth/vizHeight require API 3.26 on both workbooks and views populate_pdf + server.version = "3.26" server.workbooks.baseurl response = POPULATE_PDF.read_bytes() with requests_mock.mock() as m: From f76044de78a893c24a96890e63f3cba565556098 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Fri, 19 Jun 2026 03:07:04 -0700 Subject: [PATCH 2/2] fix: remove incorrect version gate on vizWidth/vizHeight for PDF export The REST API docs claimed vizHeight/vizWidth required API 3.26 and were Cloud-only, but server-side code shows these params have been accepted unconditionally since at least 2021.4 with no platform distinction. Remove the 3.26 guard; the endpoint's own minimum version is sufficient. Co-Authored-By: Claude Sonnet 4.6 --- .../server/endpoint/views_endpoint.py | 7 +------ .../server/endpoint/workbooks_endpoint.py | 5 ----- test/test_view.py | 18 ------------------ test/test_workbook.py | 4 +--- 4 files changed, 2 insertions(+), 32 deletions(-) diff --git a/tableauserverclient/server/endpoint/views_endpoint.py b/tableauserverclient/server/endpoint/views_endpoint.py index 15e876d89..c2779b17d 100644 --- a/tableauserverclient/server/endpoint/views_endpoint.py +++ b/tableauserverclient/server/endpoint/views_endpoint.py @@ -205,7 +205,7 @@ def populate_pdf(self, view_item: ViewItem, req_options: "PDFRequestOptions | No req_options: PDFRequestOptions | None, default None Optional request options for the request. These options can include parameters such as orientation, paper size, and viz dimensions - (viz_width and viz_height, which require API version 3.26+). + (viz_width and viz_height). Returns ------- @@ -215,11 +215,6 @@ def populate_pdf(self, view_item: ViewItem, req_options: "PDFRequestOptions | No error = "View item missing ID." raise MissingRequiredFieldError(error) - if req_options is not None: - if not self.parent_srv.check_at_least_version("3.26"): - if req_options.viz_height or req_options.viz_width: - raise UnsupportedAttributeError("viz_height and viz_width are only supported in API 3.26+") - def pdf_fetcher(): return self._get_view_pdf(view_item, req_options) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 674cbce19..665b50b5d 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -608,11 +608,6 @@ def pdf_fetcher() -> bytes: if not self.parent_srv.check_at_least_version("3.23"): if req_options.view_filters or req_options.view_parameters: raise UnsupportedAttributeError("view_filters and view_parameters are only supported in 3.23+") - # vizWidth/vizHeight were added to the PDF endpoint in API 3.26 - # (same version as views.populate_pdf; confirmed in REST API reference). - if not self.parent_srv.check_at_least_version("3.26"): - if req_options.viz_height or req_options.viz_width: - raise UnsupportedAttributeError("viz_height and viz_width are only supported in 3.26+") workbook_item._set_pdf(pdf_fetcher) logger.info(f"Populated pdf for workbook (ID: {workbook_item.id})") diff --git a/test/test_view.py b/test/test_view.py index e39bdd76e..f4d5b7e1f 100644 --- a/test/test_view.py +++ b/test/test_view.py @@ -427,25 +427,7 @@ def test_filter_excel(server: TSC.Server) -> None: assert response == excel_file -def test_pdf_viz_dimensions_unsupported(server: TSC.Server) -> None: - server.version = "3.8" - response = POPULATE_PDF.read_bytes() - with requests_mock.mock() as m: - m.get( - server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/pdf?vizHeight=1080&vizWidth=1920", - content=response, - ) - single_view = TSC.ViewItem() - single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5" - - req_option = TSC.PDFRequestOptions(viz_height=1080, viz_width=1920) - - with pytest.raises(UnsupportedAttributeError): - server.views.populate_pdf(single_view, req_option) - - def test_pdf_viz_dimensions(server: TSC.Server) -> None: - server.version = "3.26" response = POPULATE_PDF.read_bytes() with requests_mock.mock() as m: m.get( diff --git a/test/test_workbook.py b/test/test_workbook.py index fa7edabbc..ca9628738 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -506,9 +506,7 @@ def test_populate_pdf_unsupported(server: TSC.Server) -> None: def test_populate_pdf_vf_dims(server: TSC.Server) -> None: - # vizWidth/vizHeight require API 3.26 on both workbooks and views populate_pdf - server.version = "3.26" - server.workbooks.baseurl + server.version = "3.23" response = POPULATE_PDF.read_bytes() with requests_mock.mock() as m: m.get(