Skip to content

fix: CSVImport bugs (AUTH column, case-insensitive validation, username case) + QuerysetEndpoint.find_by_name - #1811

Open
jacalata wants to merge 7 commits into
developmentfrom
jac/csv-import-fixes-and-find-by-name
Open

fix: CSVImport bugs (AUTH column, case-insensitive validation, username case) + QuerysetEndpoint.find_by_name#1811
jacalata wants to merge 7 commits into
developmentfrom
jac/csv-import-fixes-and-find-by-name

Conversation

@jacalata

@jacalata jacalata commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Closes #1809.

Motivation

UserItem.CSVImport had six independent bugs that tabcmd currently
works around with per-line validation. Fixing them here so tabcmd can
eventually defer its validation to TSC (see #1836 for the follow-on
file-import method).

Also adds QuerysetEndpoint.find_by_name -- a shortcut previously
absent from the queryset API that tabcmd's user/group lookups will use
once tabcmd bumps its TSC pin.

Behavior change

For users:

  • AUTH column now readable. ColumnType.AUTH = 7 and
    ColumnType.MAX = 7 were equal, so any 8-column line was rejected as
    "too many columns". MAX is now 8 (column count, not last index).
  • create_user_from_line preserves username case.
    line.strip().lower() ran before splitting, destroying case for
    LDAP/mixed-case usernames. Only the comparison-relevant fields
    (license, admin, publisher, auth) are normalized now.
  • TableauIDWithMFA added to auth-column validation allowlist.
  • Validation is case-insensitive. _validate_attribute_value
    compared raw input against lowercase allowlists, so Viewer,
    Creator, SAML were all rejected. Normalizes each field before
    comparison.
  • _set_values no longer bypasses the auth-setting enum guard.
    Server-parsed UserItems will now raise on invalid auth values at the
    point of parsing (bypasses on _set_values write directly to the
    private attr for forward-compat; see e7b6c3f).
  • Unknown AUTH values raise instead of silently setting
    auth_setting=None.
    Callers who want lenient behavior can catch
    the exception.

Behavior change worth calling out for downstream: if any script was
inadvertently relying on rejected-because-too-many-columns behavior, or
on unknown-auth-values being silently null, those code paths change.

Test plan

  • test/test_user.py, test_user_model.py: 46 passed including
    new coverage for each of the six bugs
  • mypy clean

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Coverage

Coverage Report
FileStmtsMissCoverMissing
tableauserverclient
   __init__.py50100% 
   config.py150100% 
   datetime_helpers.py2511 96%
   exponential_backoff.py200100% 
   filesys_helpers.py310100% 
   namespace.py2633 88%
tableauserverclient/bin
   __init__.py20100% 
   _version.py358212212 41%
tableauserverclient/helpers
   __init__.py10100% 
   logging.py20100% 
   strings.py3111 97%
tableauserverclient/models
   __init__.py460100% 
   collection_item.py4177 83%
   column_item.py553232 42%
   connection_credentials.py351111 69%
   connection_item.py941414 85%
   custom_view_item.py1442121 85%
   data_acceleration_report_item.py5411 98%
   data_alert_item.py15844 97%
   data_freshness_policy_item.py1551515 90%
   database_item.py2073636 83%
   datasource_item.py3001212 96%
   dqw_item.py10455 95%
   exceptions.py40100% 
   extensions_item.py13244 97%
   extract_item.py4444 91%
   favorites_item.py6988 88%
   fileupload_item.py190100% 
   flow_item.py1491010 93%
   flow_run_item.py710100% 
   group_item.py8966 93%
   groupset_item.py4977 86%
   interval_item.py1823232 82%
   job_item.py1871010 95%
   linked_tasks_item.py7911 99%
   location_item.py2922 93%
   metric_item.py1291313 90%
   oidc_item.py6333 95%
   pagination_item.py3411 97%
   permissions_item.py1111212 89%
   project_item.py2073131 85%
   property_decorators.py1001818 82%
   reference_item.py2622 92%
   revision_item.py5911 98%
   schedule_item.py20966 97%
   server_info_item.py3777 81%
   site_item.py6361313 98%
   subscription_item.py10122 98%
   table_item.py1191818 85%
   tableau_auth.py612525 59%
   tableau_types.py2711 96%
   tag_item.py150100% 
   target.py60100% 
   task_item.py5622 96%
   user_item.py3231717 95%
   view_item.py2201616 93%
   virtual_connection_item.py6488 88%
   webhook_item.py6911 99%
   workbook_item.py3621616 96%
tableauserverclient/server
   __init__.py90100% 
   exceptions.py40100% 
   filter.py2911 97%
   pager.py3311 97%
   query.py1431515 90%
   request_factory.py1335195195 85%
   request_options.py38655 99%
   server.py1882323 88%
   sort.py60100% 
tableauserverclient/server/endpoint
   __init__.py350100% 
   auth_endpoint.py771111 86%
   custom_views_endpoint.py1521212 92%
   data_acceleration_report_endpoint.py210100% 
   data_alert_endpoint.py942323 76%
   databases_endpoint.py1113030 73%
   datasources_endpoint.py3233333 90%
   default_permissions_endpoint.py4433 93%
   dqw_endpoint.py451616 64%
   endpoint.py2122020 91%
   exceptions.py7766 92%
   extensions_endpoint.py310100% 
   favorites_endpoint.py942222 77%
   fileuploads_endpoint.py510100% 
   flow_runs_endpoint.py6299 85%
   flow_task_endpoint.py2122 90%
   flows_endpoint.py1985353 73%
   groups_endpoint.py12699 93%
   groupsets_endpoint.py7277 90%
   jobs_endpoint.py6799 87%
   linked_tasks_endpoint.py370100% 
   metadata_endpoint.py881414 84%
   metrics_endpoint.py5566 89%
   oidc_endpoint.py4211 98%
   permissions_endpoint.py4433 93%
   projects_endpoint.py1782424 87%
   resource_tagger.py1273535 72%
   schedules_endpoint.py1191111 91%
   server_info_endpoint.py361010 72%
   sites_endpoint.py1302727 79%
   subscriptions_endpoint.py561414 75%
   tables_endpoint.py1103636 67%
   tasks_endpoint.py6366 90%
   users_endpoint.py18388 96%
   views_endpoint.py15099 94%
   virtual_connections_endpoint.py1131010 91%
   webhooks_endpoint.py5499 83%
   workbooks_endpoint.py3382222 93%
TOTAL12015142288% 

…servation; add find_by_name

Fixes for UserItem.CSVImport (issue #1809):
- MAX=8 (was 7=AUTH index): 8-column lines with auth type no longer
  rejected as "too many columns"
- create_user_from_line no longer lowercases the whole line before
  splitting — username case is preserved
- _validate_import_line_or_throw normalizes license/admin/publisher to
  lowercase and auth to canonical form before comparison, so 'Viewer',
  'Creator', 'SAML', 'tableauidwithmfa' etc. are all accepted
- Add TableauIDWithMFA to valid auth values in validation (was missing)
- 5 new tests covering each fix

Add QuerysetEndpoint.find_by_name(name) (issue #1810):
- Thin wrapper over .filter(name=name) returning a list
- Available on all content-item endpoints (workbooks, datasources,
  views, users, projects, groups)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jacalata and others added 3 commits July 28, 2026 00:20
…rty setter

Two related fixes so unmapped auth values fail loudly at CSV parse time
rather than producing a UserItem with silently missing auth_setting:

- create_user_from_line: raise ValueError instead of silently setting
  auth to None when the AUTH column value isn't in _auth_canonical().
- _set_values: route auth_setting through the @property_is_enum(Auth)
  setter rather than writing to _auth_setting directly, so any invalid
  auth string is rejected at assignment.

These two together close bug #5 in #1809 (setter bypass) and the silent-
None finding surfaced in an adversarial review of the earlier commits
on this branch.

Callers who want lenient behavior (skip invalid rows, keep going) can
catch the exception in their own iteration loop — that's the model
tabcmd uses today via its --complete/--no-complete flag. Once this
lands, tabcmd can defer its per-line validation to TSC (see #1809 and
#1836).

Also tightens test_too_many_columns_raises to expect ValueError only
(was accepting either ValueError or AttributeError).
find_by_name was bundled with the CSVImport fixes in earlier commits
because it landed in the same working commit. It's orthogonal to the
CSV work and closes a different issue (#1810), so it belongs in its own
PR. Reverting the 3-line addition here; will land as a separate branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jacalata and others added 2 commits July 28, 2026 14:20
Call out the behavior change explicitly. The old code lowercased the entire
CSV line including usernames, display names, fullnames, and emails; the new
code preserves case for those fields and only normalizes the fields used
for validation comparisons. Callers relying on the previous lowercased
output need to know.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small cleanups on UserItem.CSVImport surfaced by an adversarial
code review of the earlier bug fixes:

- MAX renamed to COLUMN_COUNT and moved out of the ColumnType IntEnum.
  ColumnType(8) used to return ColumnType.MAX, a fake column mixed in
  with real column indices. Now the count is a class-level constant.
- _auth_canonical() no longer rebuilds its dict on every call. Promoted
  to _AUTH_CANONICAL class attribute.
- _valid_attributes[AUTH] no longer hardcodes the accepted auth values.
  Derived from _AUTH_CANONICAL.values() instead so there's a single
  source of truth for what AUTH strings are accepted.

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jacalata
jacalata requested a lite review from Copilot August 6, 2026 21:19
@jacalata
jacalata enabled auto-merge (squash) August 6, 2026 21:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Updates UserItem.CSVImport to preserve case for user-provided fields while making validation/parsing of role/admin/publisher/auth fields case-insensitive and stricter, with added regression coverage and a changelog note.

Changes:

  • Preserve original casing in create_user_from_line (notably username/display name/email) and normalize only comparison-relevant fields.
  • Add canonicalization + validation for the AUTH column (including TableauIDWithMFA) and fix column-count off-by-one handling.
  • Add tests covering mixed-case inputs, auth parsing, and error conditions; document the behavior change in the changelog.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
test/test_user_model.py Adds regression tests for mixed-case license/auth values, username case preservation, column count, and invalid auth handling.
tableauserverclient/models/user_item.py Preserves casing for non-enum CSV fields, canonicalizes/validates AUTH, fixes column count logic, and routes auth_setting assignment through the enum-guarded setter.
CHANGELOG.md Documents the behavior change that CSV parsing no longer lowercases the entire line.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tableauserverclient/models/user_item.py Outdated
Comment on lines +311 to +314
# Go through the @property_is_enum(Auth) setter rather than writing
# to _auth_setting directly, so CSV-parsed users can't carry an
# invalid auth_setting that only fails later at the API call.
self.auth_setting = auth_setting
Comment on lines +536 to 537
if len(line) > UserItem.CSVImport.COLUMN_COUNT:
raise AttributeError("Too many attributes in line")
Copilot review finding on #1811. The prior version of _set_values routed
auth_setting through the @property_is_enum(Auth) setter to catch bad
values in CSV import. But _set_values is also called by from_xml,
_parse_xml, and populate — the server-response paths. If a future
Tableau release adds a new Auth enum value that this TSC version
doesn't yet know about, response parsing would raise ValueError instead
of transparently carrying the new value forward.

CSV callers already validate against CSVImport._AUTH_CANONICAL before
reaching _set_values (create_user_from_line raises with a clean error
message for unknown auth strings), so the enum guard on _set_values was
redundant for the CSV path and harmful for the server-parse path.

Write directly to _auth_setting instead. Add a regression test that
parses a UserItem XML carrying a hypothetical future auth type and
asserts it survives.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UserItem.CSVImport: several bugs prevent reliable use and block tabcmd delegation

2 participants