Proposed: Remove pre-8.3 XML namespace fallback - #1863
Draft
jacalata wants to merge 4 commits into
Draft
Conversation
The `Namespace` class detected `http://tableausoftware.com/api` (the pre-8.3 namespace) at runtime on every XML response and let the parser silently switch namespaces if it saw the old one. That behavior has been unreachable for years: the library's `minimum_supported_server_version` is 2.3, which corresponds to Tableau Server 10.0 -- shipped in 2016, three years after the namespace changed. Every server TSC has ever admitted uses `http://tableau.com/api`. Removes: - `tableauserverclient/namespace.py` (the module) and everything it exported: `Namespace`, `UnknownNamespaceError`, `OLD_NAMESPACE`, `NEW_NAMESPACE`, `NAMESPACE_RE`. - `Server._namespace` instance and the per-response `.detect(...)` call in `Endpoint._make_request` and both sign-in paths in `Auth`. Keeps: - The public `TSC.DEFAULT_NAMESPACE` re-export -- now sourced from `tableauserverclient.server.server.NAMESPACE`, which is the canonical constant. Same string value. - `Server.namespace` property -- still returns the `{"t": NAMESPACE}` dict callers pass to ElementTree's `namespaces=` kwarg. Same shape. Callers who imported directly from `tableauserverclient.namespace` (rather than `TSC.DEFAULT_NAMESPACE`) will break; the CHANGELOG entry calls this out. Fixes #1046. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR removes runtime XML namespace detection that allowed parsing legacy (pre-Tableau Server 8.3) REST API XML responses, standardizing the client on the modern http://tableau.com/api namespace in line with the library’s long-standing minimum supported server/API versions.
Changes:
- Removed the
tableauserverclient.namespacemodule and eliminated per-response namespace detection calls. - Centralized the namespace constant in
server.serverand updatedTSC.DEFAULT_NAMESPACEto keep the public re-export working. - Updated server/endpoint code paths to always use the fixed namespace map.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tableauserverclient/server/server.py | Removes per-instance namespace detector and introduces fixed namespace constants/map. |
| tableauserverclient/server/endpoint/endpoint.py | Removes the XML-response namespace detection hook in _make_request. |
| tableauserverclient/server/endpoint/auth_endpoint.py | Removes namespace detection during sign-in and site switching flows. |
| tableauserverclient/namespace.py | Deletes the legacy namespace detection module and exported symbols. |
| tableauserverclient/init.py | Updates DEFAULT_NAMESPACE re-export to come from the new namespace source. |
| CHANGELOG.md | Documents the breaking removal of legacy namespace fallback and module exports. |
Suppressed comments (2)
tableauserverclient/server/endpoint/auth_endpoint.py:159
- With namespace auto-detection removed, a legacy-namespace (or otherwise unexpected) XML response can make
find(...).get(...)raiseAttributeError. Adding an explicit check and raisingServerResponseErrorproduces a clearer, user-facing failure mode for unsupported namespaces/servers.
parsed_response = fromstring(server_response.content)
site_id = parsed_response.find(".//t:site", namespaces=self.parent_srv.namespace).get("id", None)
site_url = parsed_response.find(".//t:site", namespaces=self.parent_srv.namespace).get("contentUrl", None)
tableauserverclient/server/endpoint/auth_endpoint.py:86
- With namespace auto-detection removed, a legacy-namespace (or otherwise unexpected) XML response will make
find(...).get(...)raise anAttributeError, which is hard to diagnose. It’s better to explicitly check that the expected elements are present and raiseServerResponseErrorwith a message that points to a likely namespace / server-version mismatch.
This issue also appears on line 157 of the same file.
parsed_response = fromstring(server_response.content)
site_id = parsed_response.find(".//t:site", namespaces=self.parent_srv.namespace).get("id", None)
site_url = parsed_response.find(".//t:site", namespaces=self.parent_srv.namespace).get("contentUrl", None)
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…hared state Copilot flagged that Server.namespace returned the module-level _NAMESPACE_MAP directly, so a caller mutating the returned dict would have polluted every other Server instance in the process. Old namespace- handling code returned a per-instance dict, so no cross-instance pollution was possible. Wrap in dict(...) to restore that behavior.
jacalata
added a commit
that referenced
this pull request
Aug 20, 2026
Two adjustments from a post-merge fresh-eyes pass: - endpoint.py: the http -> https address-promotion match now compares hostnames case-insensitively (RFC 3986) and normalizes http://host vs http://host:80 so a same-host promotion doesn't silently drop. Split into two comparisons: current-vs-next is hostname-only (schemes differ so default port differs, comparing raw netloc would spuriously mismatch); old-address-vs-current is same-scheme and uses (hostname, effective port) so explicit-vs-implicit port compares equal. Expanded the auth-material comment to acknowledge that sign_in itself carries raw credentials in the POST body, not only the issued token on subsequent calls. - test_redirect_handling.py: added a docstring on test_all_supported_redirect_codes_preserve_post_body naming the RFC 7231 6.4.4 deviation on 303 -- if a future refactor "helpfully" converts 303 to GET, the parametrized test fails with a clear intent statement. Also considered a sign_in namespace-detect hedge for pre-8.3 Tableau responses (Copilot flagged this on #1848); dropped as theoretical because TSC's minimum_supported_server_version = 2.3 (Tableau 10.0, 2016) is eight years past the namespace change, and #1863 removes the whole subsystem anyway.
2 tasks
…e-old-namespace # Conflicts: # tableauserverclient/server/endpoint/auth_endpoint.py
Cosmetic import-graph cleanup on top of #1863's move of `NAMESPACE` from `namespace.py` into `server/server.py`. Before this change, `tableauserverclient/__init__.py:2` did `from tableauserverclient. server.server import NAMESPACE`, which forces `server.server` (and its transitive endpoint tree) to load just to read a string constant. Copilot flagged the circular-import path this creates: `server. server` -> `server.endpoint.endpoint` -> `from tableauserverclient import ...` -> `__init__` -> `server.server`. Python resolves the chain today because the reentrant lookups pull submodules whose own load doesn't back-reference the partially-initialized parent, but the shape is fragile and adding a new top-level import to `__init__.py` before line 2 could easily break it. Replaced the import with the string literal. Kept in sync with the `NAMESPACE` constant in `server/server.py`. Also removes one of the two entry points into `server.server` at package load, though everything else in `__init__.py` still transitively pulls the endpoint tree via the models block below, so no real startup savings -- this is defensive, not a perf fix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft -- opening for discussion. Happy to add a deprecation cycle instead of an outright removal if that's preferred.
Summary
Removes the runtime XML namespace detection that let TSC accept responses from Tableau Server versions predating 8.3 (2013,
http://tableausoftware.com/api). The library'sminimum_supported_server_versionhas been 2.3 for years (Tableau Server 10.0, 2016), so the detection has been unreachable for a decade.Fixes #1046.
What changes
Removed:
tableauserverclient/namespace.py(the whole module) and the symbols it exported:Namespace,UnknownNamespaceError,OLD_NAMESPACE,NEW_NAMESPACE,NAMESPACE_RE.Server._namespaceand the per-response.detect(...)call inEndpoint._make_requestand both sign-in paths inAuth.Kept:
TSC.DEFAULT_NAMESPACEre-export -- now sourced fromtableauserverclient.server.server.NAMESPACE. Same string value.Server.namespaceproperty -- still returns the{"t": NAMESPACE}dict callers pass to ElementTree'snamespaces=kwarg. Same shape.Compatibility
Callers who imported directly from
tableauserverclient.namespace(rather than the documentedTSC.DEFAULT_NAMESPACE) will break. The CHANGELOG entry calls this out. Given the module was internal in every sense except being top-level, I opted for outright removal, but a deprecation shim (namespace.pybecomes a warning-emitting alias, removed one minor release later) is easy to add if that's the house style.Why now
Discussion in #1039 (2022) already noted the fallback was safe to drop; the issue has been open since. Nothing in the diff is complicated -- the code is simply dead.
Test plan
from tableauserverclient.namespace,UnknownNamespaceError,_namespace.detect,OLD_NAMESPACE,NEW_NAMESPACE,namespace.Namespaceinside the repo -- only remaining reference is the CHANGELOG entry.mypyandblackclean via pre-commit.🤖 Generated with Claude Code