From 39c10752557d53c6965720fc769d6a60189da63e Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Wed, 29 Jul 2026 15:57:03 -0700 Subject: [PATCH 1/9] docs: expand RequestOptions.vf() with vf_ wire-format rules The vf_ view filter has several server-side quirks that aren't obvious from the current one-line docstring. Document what values actually mean on the wire: exact match by default, comma as OR-list, backslash-escape for literal comma, empty-value override behavior, no wildcards, no operators, boolean 'true'/'false' only. This surfaces the behavior verified end-to-end against Tableau Cloud while working through --filter parsing issues in tabcmd, so callers who build vf_ requests through TSC know what the server will accept without having to reverse-engineer it. Co-Authored-By: Claude Opus 4.7 (1M context) --- tableauserverclient/server/request_options.py | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 5a8600908..4440ab94c 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -351,9 +351,37 @@ def get_query_params(self): def vf(self, name: str, value: str) -> Self: """Apply a filter based on a column within the view. - Note that when filtering on a boolean type field, the only valid values are 'true' and 'false' - For more detail see: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_concepts_filtering_and_sorting.htm#Filter-query-views + Serialized to the REST API as ``vf_=``. The rules below + describe how the server interprets the wire value; ``vf()`` itself + does not transform your input. + + Value syntax + ------------ + - **Exact match (default):** a single value matches rows where the + column equals that value exactly. Case-sensitivity follows the + underlying data source. + - **OR-list:** commas separate alternatives, so ``"East,West"`` + matches rows where the column is ``East`` OR ``West``. + - **Literal comma in a value:** escape with a backslash -- + ``"Rock\\, Paper\\, Scissors"`` matches the single value + ``Rock, Paper, Scissors``. URL-encoding the comma (``%2C``) + does NOT escape it. + - **Empty value** (``vf_=``) overrides any workbook-embedded + filter on that column, effectively widening it to all values. + This is undocumented but stable behavior that some users rely on. + - **Wildcards** (``*``) are NOT supported by the REST view-filter + layer. Wildcard matching is a viz-configured behavior on filter + controls inside a workbook; use ``.parameter()`` and design the + workbook accordingly if you need it. + - **Ranges, comparisons, operators** (``>``, ``<=``, ``BETWEEN``, + etc.) are NOT supported here. Use workbook-side filter design or + the ``filter=`` query parameter on list endpoints, which is a + separate syntax. + - **Booleans:** the only valid values are ``'true'`` and ``'false'``. + + For more detail see: + https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_concepts_filtering_and_sorting.htm#Filter-query-views Parameters ---------- From 8f3dc23ba4f13d4754b4619bb3f549a48f7e108c Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Wed, 29 Jul 2026 15:57:45 -0700 Subject: [PATCH 2/9] docs: drop irrelevant filter= reference from vf() docstring The `filter=` list-endpoint syntax is unrelated to view-filter export requests (it's for querying lists of workbooks/users/etc). Steering readers there was scope creep. Just say ranges/operators aren't supported and point at workbook-side design. Co-Authored-By: Claude Opus 4.7 (1M context) --- tableauserverclient/server/request_options.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 4440ab94c..b66f8aa0c 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -375,9 +375,8 @@ def vf(self, name: str, value: str) -> Self: controls inside a workbook; use ``.parameter()`` and design the workbook accordingly if you need it. - **Ranges, comparisons, operators** (``>``, ``<=``, ``BETWEEN``, - etc.) are NOT supported here. Use workbook-side filter design or - the ``filter=`` query parameter on list endpoints, which is a - separate syntax. + etc.) are NOT supported. Design the workbook's filters to expose + the shape you need at export time. - **Booleans:** the only valid values are ``'true'`` and ``'false'``. For more detail see: From c78f06033329d2c5df7671ca246093b490bd94db Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Wed, 29 Jul 2026 16:00:52 -0700 Subject: [PATCH 3/9] docs: document backslash as vf_ escape character Empirically verified: '\' in a vf_ value is the escape character. To match a literal backslash you must double it ('\\'). Consolidate the comma-escape and backslash rules into a single "backslash escapes" bullet, since it's one mechanism. Co-Authored-By: Claude Opus 4.7 (1M context) --- tableauserverclient/server/request_options.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index b66f8aa0c..1b7b6bc98 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -363,10 +363,12 @@ def vf(self, name: str, value: str) -> Self: underlying data source. - **OR-list:** commas separate alternatives, so ``"East,West"`` matches rows where the column is ``East`` OR ``West``. - - **Literal comma in a value:** escape with a backslash -- - ``"Rock\\, Paper\\, Scissors"`` matches the single value - ``Rock, Paper, Scissors``. URL-encoding the comma (``%2C``) - does NOT escape it. + - **Backslash is an escape character.** ``\\c`` in the wire value + means "literal ``c``, don't interpret it." To match a value + containing a literal ``,`` escape it: ``"Rock\\, Paper\\, Scissors"`` + matches ``Rock, Paper, Scissors``. To match a literal backslash, + double it: ``"C:\\\\temp\\\\file"`` matches ``C:\\temp\\file``. + URL-encoding (``%2C``, ``%5C``) does NOT escape either character. - **Empty value** (``vf_=``) overrides any workbook-embedded filter on that column, effectively widening it to all values. This is undocumented but stable behavior that some users rely on. From 98b4faf226c6b3db16309b686d326be036b32b94 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Wed, 29 Jul 2026 16:01:42 -0700 Subject: [PATCH 4/9] docs: rephrase vf_ backslash bullet to lead with examples Dropped the abstract "\c means literal c" phrasing (weird to read with a random letter) and led with the two concrete cases -- escape a comma, escape a backslash -- and noted why each escape matters (comma would otherwise start an OR-list; backslash otherwise consumed as escape). Co-Authored-By: Claude Opus 4.7 (1M context) --- tableauserverclient/server/request_options.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 1b7b6bc98..ac4a1de4a 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -363,10 +363,10 @@ def vf(self, name: str, value: str) -> Self: underlying data source. - **OR-list:** commas separate alternatives, so ``"East,West"`` matches rows where the column is ``East`` OR ``West``. - - **Backslash is an escape character.** ``\\c`` in the wire value - means "literal ``c``, don't interpret it." To match a value - containing a literal ``,`` escape it: ``"Rock\\, Paper\\, Scissors"`` - matches ``Rock, Paper, Scissors``. To match a literal backslash, + - **Backslash escapes.** To match a value that contains a literal + comma, escape it: ``"Rock\\, Paper\\, Scissors"`` matches + ``Rock, Paper, Scissors`` (without escaping, the comma would be + treated as an OR-list separator). To match a literal backslash, double it: ``"C:\\\\temp\\\\file"`` matches ``C:\\temp\\file``. URL-encoding (``%2C``, ``%5C``) does NOT escape either character. - **Empty value** (``vf_=``) overrides any workbook-embedded From 6b7a41fa1b85923335c6825fc9e70e3afe8ef785 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Wed, 29 Jul 2026 16:08:00 -0700 Subject: [PATCH 5/9] docs: clarify vf_ escaping is only for ',' and '\' Empirically tested 20+ candidate metacharacters (/ % + : ; { } [ ] ( ) ? # * @ = & " ' < >) against a live server; every one passes through untouched when URL-encoded. Only ',' and '\' are vf_ metacharacters that need escaping in the wire value. Explicitly call that out so readers don't assume everything URL-special needs a client-side workaround. Co-Authored-By: Claude Opus 4.7 (1M context) --- tableauserverclient/server/request_options.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index ac4a1de4a..8ea4cba13 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -363,12 +363,16 @@ def vf(self, name: str, value: str) -> Self: underlying data source. - **OR-list:** commas separate alternatives, so ``"East,West"`` matches rows where the column is ``East`` OR ``West``. - - **Backslash escapes.** To match a value that contains a literal - comma, escape it: ``"Rock\\, Paper\\, Scissors"`` matches - ``Rock, Paper, Scissors`` (without escaping, the comma would be - treated as an OR-list separator). To match a literal backslash, - double it: ``"C:\\\\temp\\\\file"`` matches ``C:\\temp\\file``. - URL-encoding (``%2C``, ``%5C``) does NOT escape either character. + - **Escaping** applies to two characters only, comma and backslash; + all other characters (``&``, ``=``, ``/``, ``#``, ``%``, ``+``, + quotes, brackets, etc.) pass through untouched -- ``vf()`` URL- + encodes them for transport and the server treats them as literal + data. To match a value containing a literal comma, escape it: + ``"Rock\\, Paper\\, Scissors"`` matches ``Rock, Paper, Scissors`` + (without escaping, the comma starts an OR-list). To match a + literal backslash, double it: ``"C:\\\\temp\\\\file"`` matches + ``C:\\temp\\file``. URL-encoding (``%2C``, ``%5C``) does NOT + escape either character. - **Empty value** (``vf_=``) overrides any workbook-embedded filter on that column, effectively widening it to all values. This is undocumented but stable behavior that some users rely on. From e7b6c3feff2fdf432830bd4e285985d685c109fa Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Sat, 8 Aug 2026 00:15:53 -0700 Subject: [PATCH 6/9] docs: clarify vf() percent-encoding vs escaping (Copilot review) Copilot flagged the vf() docstring as internally inconsistent: it said 'vf() does not transform your input' and later 'vf() URL-encodes them for transport' -- both true, but confusing side-by-side. Rewrite to distinguish (a) no Tableau-specific escaping/semantic transforms (b) percent-encoding for HTTP transport. Also fold in a note that percent-encoding is transport-layer only: %2C reaches the server as ',' and gets processed as a comma delimiter, so URL-encoding does NOT escape a literal comma or backslash. Also softened the 'empty value' bullet from 'undocumented but stable behavior' (reads as a compat promise the library can't make) to 'observed... may change without notice.' No behavior change. --- tableauserverclient/server/request_options.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 8ea4cba13..713ed8dd5 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -353,8 +353,10 @@ def vf(self, name: str, value: str) -> Self: """Apply a filter based on a column within the view. Serialized to the REST API as ``vf_=``. The rules below - describe how the server interprets the wire value; ``vf()`` itself - does not transform your input. + describe how the server interprets the wire value. ``vf()`` itself + does not apply any Tableau-specific escaping or semantic transforms + to your value; it only percent-encodes the value for HTTP transport, + which the server decodes back before applying the rules below. Value syntax ------------ @@ -365,17 +367,20 @@ def vf(self, name: str, value: str) -> Self: matches rows where the column is ``East`` OR ``West``. - **Escaping** applies to two characters only, comma and backslash; all other characters (``&``, ``=``, ``/``, ``#``, ``%``, ``+``, - quotes, brackets, etc.) pass through untouched -- ``vf()`` URL- - encodes them for transport and the server treats them as literal - data. To match a value containing a literal comma, escape it: + quotes, brackets, etc.) pass through untouched and the server + treats them as literal data. Percent-encoding is transport only: + ``%2C`` and ``%5C`` reach the server as ``,`` and ``\\`` and are + then processed like any other comma or backslash -- so URL- + encoding does NOT escape a literal comma or backslash. To match a + value containing a literal comma, escape it: ``"Rock\\, Paper\\, Scissors"`` matches ``Rock, Paper, Scissors`` (without escaping, the comma starts an OR-list). To match a literal backslash, double it: ``"C:\\\\temp\\\\file"`` matches - ``C:\\temp\\file``. URL-encoding (``%2C``, ``%5C``) does NOT - escape either character. - - **Empty value** (``vf_=``) overrides any workbook-embedded - filter on that column, effectively widening it to all values. - This is undocumented but stable behavior that some users rely on. + ``C:\\temp\\file``. + - **Empty value** (``vf_=``) is observed to override any + workbook-embedded filter on that column, effectively widening it + to all values. Not officially documented; behavior may change + without notice. - **Wildcards** (``*``) are NOT supported by the REST view-filter layer. Wildcard matching is a viz-configured behavior on filter controls inside a workbook; use ``.parameter()`` and design the From b0fc0d49c73164290b36c4564a10e864936d62fd Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Mon, 17 Aug 2026 13:20:56 -0700 Subject: [PATCH 7/9] docs: label empirical vf() claims and tighten wildcard note Fresh-eyes review noted two nits on the expanded docstring: - The percent-encoding-does-not-escape claim (that %2C/%5C reach the server as literal comma/backslash and are then processed by the escaping rules like any other) is not on the public REST API doc page -- it was verified end-to-end against Tableau Cloud. Mark it as empirical with a verification date so a future maintainer knows to re-check if the wire behavior changes. Note that only the \, escape is documented; \ and the percent-encoding claim are empirical. - The wildcard note previously suggested "use .parameter() and design the workbook accordingly" as a workaround. That conflates two mechanisms (workbook filter controls' wildcard behavior vs. parameters) and doesn't cleanly work around vf_'s exact-match limit. Reworded to just state the fact: vf_ is exact-match / OR-list only, no contains/starts-with/ends-with. Docstring-only change; 41 request_option tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- tableauserverclient/server/request_options.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 713ed8dd5..18966f792 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -368,11 +368,14 @@ def vf(self, name: str, value: str) -> Self: - **Escaping** applies to two characters only, comma and backslash; all other characters (``&``, ``=``, ``/``, ``#``, ``%``, ``+``, quotes, brackets, etc.) pass through untouched and the server - treats them as literal data. Percent-encoding is transport only: - ``%2C`` and ``%5C`` reach the server as ``,`` and ``\\`` and are - then processed like any other comma or backslash -- so URL- - encoding does NOT escape a literal comma or backslash. To match a - value containing a literal comma, escape it: + treats them as literal data. The ``\\,`` escape for a literal + comma is documented on the Tableau REST API filtering page. The + ``\\\\`` escape for a literal backslash, and the observation that + percent-encoding is transport-only (``%2C`` and ``%5C`` reach the + server as ``,`` and ``\\`` and are then processed like any other + comma or backslash, so URL-encoding does NOT escape them), are + empirical -- verified end-to-end against Tableau Cloud in + August 2026. To match a value containing a literal comma: ``"Rock\\, Paper\\, Scissors"`` matches ``Rock, Paper, Scissors`` (without escaping, the comma starts an OR-list). To match a literal backslash, double it: ``"C:\\\\temp\\\\file"`` matches @@ -381,10 +384,10 @@ def vf(self, name: str, value: str) -> Self: workbook-embedded filter on that column, effectively widening it to all values. Not officially documented; behavior may change without notice. - - **Wildcards** (``*``) are NOT supported by the REST view-filter - layer. Wildcard matching is a viz-configured behavior on filter - controls inside a workbook; use ``.parameter()`` and design the - workbook accordingly if you need it. + - **Wildcards** (``*``, ``%``) are NOT supported. ``vf_`` is + exact-match / OR-list only; there is no equivalent of the + "contains" / "starts with" / "ends with" behavior available on + filter controls inside a workbook. - **Ranges, comparisons, operators** (``>``, ``<=``, ``BETWEEN``, etc.) are NOT supported. Design the workbook's filters to expose the shape you need at export time. From 4c503f32b61ac0c0f6838a5cf451a67e9bf009e1 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Mon, 17 Aug 2026 13:22:31 -0700 Subject: [PATCH 8/9] docs: don't make a categorical negative claim about vf_ wildcards Previous wording said wildcards, ranges, and operators are "NOT supported" -- but I don't have live verification that `*` or `%` are inert in a vf_ value. The public filtering docs describe only exact match and OR-lists; behavior of other characters is undocumented, not verifiably absent. Rephrase to say what we know (docs don't cover it, TSC doesn't emit operator prefixes) and tell callers to verify against their target server before relying on the outcome. This is intentionally less prescriptive than the previous version. The related open PR #1854 was flagged by the same fresh-eyes pass for making an unverified `*`-as-wildcard claim in the opposite direction -- both PRs should stay in "docs describe X; other behaviors are untested" territory until we run the experiments. Co-Authored-By: Claude Opus 4.7 (1M context) --- tableauserverclient/server/request_options.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 18966f792..856f9c1c4 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -384,13 +384,14 @@ def vf(self, name: str, value: str) -> Self: workbook-embedded filter on that column, effectively widening it to all values. Not officially documented; behavior may change without notice. - - **Wildcards** (``*``, ``%``) are NOT supported. ``vf_`` is - exact-match / OR-list only; there is no equivalent of the - "contains" / "starts with" / "ends with" behavior available on - filter controls inside a workbook. - - **Ranges, comparisons, operators** (``>``, ``<=``, ``BETWEEN``, - etc.) are NOT supported. Design the workbook's filters to expose - the shape you need at export time. + - **Wildcards, ranges, comparisons, operators** -- not documented + for ``vf_``. The public filtering docs describe only exact match + and OR-lists, and TSC does not emit any operator prefix. Behavior + of ``*``, ``%``, ``>``, ``<=``, ``BETWEEN``, etc. inside a ``vf_`` + value is not covered by the docs; if you need them, verify against + your target server before relying on the outcome. For matching + shapes the REST API cannot express (e.g. "contains"), configure + the equivalent filter inside the workbook. - **Booleans:** the only valid values are ``'true'`` and ``'false'``. For more detail see: From adc4fad1f76fdf0aeea32d547289dc3f458b267a Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Mon, 17 Aug 2026 17:51:41 -0700 Subject: [PATCH 9/9] docs: verify vf_ wildcard behavior against a live server The prior wording ('not documented, verify against your target') was correct but weakly stated. Ran the actual experiments against Tableau Server 3.30: vf_ passes * and % through as literal data, so a value of 'Widget*' matches only a product literally named 'Widget*', 'META /star*/' matches its one row, and '*' or '%' on their own return zero rows. Docstring now states that outcome directly and cites the verification date. --- tableauserverclient/server/request_options.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py index 856f9c1c4..9d19364cb 100644 --- a/tableauserverclient/server/request_options.py +++ b/tableauserverclient/server/request_options.py @@ -384,14 +384,15 @@ def vf(self, name: str, value: str) -> Self: workbook-embedded filter on that column, effectively widening it to all values. Not officially documented; behavior may change without notice. - - **Wildcards, ranges, comparisons, operators** -- not documented - for ``vf_``. The public filtering docs describe only exact match - and OR-lists, and TSC does not emit any operator prefix. Behavior - of ``*``, ``%``, ``>``, ``<=``, ``BETWEEN``, etc. inside a ``vf_`` - value is not covered by the docs; if you need them, verify against - your target server before relying on the outcome. For matching - shapes the REST API cannot express (e.g. "contains"), configure - the equivalent filter inside the workbook. + - **No wildcards, ranges, comparisons, or operators.** Characters + like ``*`` and ``%`` are treated as literal data, not glob or SQL + wildcards; ``vf_Product=Widget*`` matches only a product literally + named ``Widget*``. Verified end-to-end against Tableau Server 3.30 + in August 2026 (``vf_Product Name=*`` and ``vf_Product Name=%`` + returned zero rows; ``vf_Product Name=META /star*/`` returned the + single row whose value contains a literal ``*``). If you need + contains / prefix / range matching, configure the equivalent + filter inside the workbook. - **Booleans:** the only valid values are ``'true'`` and ``'false'``. For more detail see: