Skip to content

Commit 807dc79

Browse files
author
Prathyusha Gidugu
committed
Fix Workbook constructor silently returning empty workbooks (0.11.3)
Workbook._prepare_datasources used a literal child search (xml_root.find('datasources')) while its sibling methods _prepare_dashboards and _prepare_worksheets both use descendant search (`.//`). Changes: - _prepare_datasources now uses xml_root.find('.//datasources'). - _prepare_worksheets's ds_index lookup is now tolerant of missing entries (uses `.get(...)` and `continue`) so a worksheet that references an un-indexed datasource doesn't abort the constructor. - Bumped wheel version 0.11.2 -> 0.11.3 in BUILD.bazel. - CHANGELOG.md entry.
1 parent 30f0a58 commit 807dc79

3 files changed

Lines changed: 22 additions & 4 deletions

File tree

BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,6 @@ py_wheel(
7676
requires = [
7777
"lxml",
7878
],
79-
version = "0.11.2",
79+
version = "0.11.3",
8080
deps = ["//tableaudocumentapi"],
8181
)

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.11.3 (June 2026)
2+
* Fix `Workbook._prepare_datasources` to use descendant XPath (`.//datasources`) instead of literal `find('datasources')`, matching the sibling `_prepare_dashboards` / `_prepare_worksheets` methods. Without this, workbooks where `<datasources>` was not a direct child of `<workbook>` (common in newer Tableau XML versions) returned an empty datasource list, which then caused `_prepare_worksheets` to raise `KeyError` on `ds_index[datasource_name]` and silently abort the `Workbook` constructor entirely, leaving `worksheets`, `dashboards` and `datasources` all empty.
3+
* Make `_prepare_worksheets`'s `ds_index` lookup tolerant of missing entries (use `.get(...)` and `continue`) so that workbooks referencing a datasource that wasn't indexed don't abort the constructor.
4+
15
## 011 (November 2022)
26
* Remove extraneous debug print statements
37

tableaudocumentapi/workbook.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,15 @@ def _prepare_datasource_index(datasources):
9797
def _prepare_datasources(xml_root):
9898
datasources = []
9999

100-
# loop through our datasources and append
101-
datasource_elements = xml_root.find('datasources')
100+
# Use descendant search (`.//`) so we find the <datasources> element
101+
# regardless of where it sits in the workbook XML. Newer Tableau
102+
# versions sometimes nest it below the top level, and matching only
103+
# the direct child here returned [], which in turn caused
104+
# _prepare_worksheets() to raise KeyError on ds_index lookups and
105+
# silently abort the whole Workbook() constructor. Aligned with the
106+
# sibling _prepare_dashboards / _prepare_worksheets methods which
107+
# already use `.//`.
108+
datasource_elements = xml_root.find('.//datasources')
102109
if datasource_elements is None:
103110
return []
104111

@@ -137,7 +144,14 @@ def _prepare_worksheets(xml_root, ds_index):
137144

138145
for dependency in dependencies:
139146
datasource_name = dependency.attrib['datasource']
140-
datasource = ds_index[datasource_name]
147+
# Defensive: a worksheet may reference a datasource that
148+
# isn't in the index (e.g. a parameter-only datasource).
149+
# Previously this raised KeyError, aborting the Workbook
150+
# constructor and leaving worksheets/dashboards/datasources
151+
# all empty.
152+
datasource = ds_index.get(datasource_name)
153+
if datasource is None:
154+
continue
141155
for column in dependency.findall('.//column'):
142156
column_name = column.attrib['name']
143157
if column_name in datasource.fields:

0 commit comments

Comments
 (0)