| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
Sorry, something went wrong.
…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>
…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>
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>
There was a problem hiding this comment.
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:
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.
Sorry, something went wrong.
| if len(line) > UserItem.CSVImport.COLUMN_COUNT: | ||
| raise AttributeError("Too many attributes in line") |
There was a problem hiding this comment.
Good catch, fixed in 9618c26. Aligning on ValueError — AttributeError was an outright bug here, not just an inconsistency, since the parameter isn't an object attribute at all.
Sorry, something went wrong.
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.
Two small fixes tied together: - `_validate_import_line_or_throw` raised AttributeError on the too-many-columns branch; `create_user_from_line` uses ValueError for the same condition. Copilot review flagged the inconsistency in #1811 since callers using `_validate_import_line_or_throw` directly have to catch a different exception than they would from `create_user_from_line` for the same malformed input. Use ValueError throughout for input-shape violations. - `test_set_values_rejects_invalid_auth_setting` asserted _set_values raises ValueError on an unknown auth string. That's exactly the behavior b088d3d removed (server-parse paths must accept unknown future auth values). b088d3d added the positive test (`test_from_xml_accepts_unknown_auth_setting` in test_user.py) but forgot to delete the contradictory one. Drop it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`CSVImport._validate_attribute_value` was raising `AttributeError` for values outside the allowed set for their column. `AttributeError` signals "object has no such attribute" -- wrong exception type for input validation. `create_user_from_line` uses `ValueError` for the equivalent "too many columns" condition; align on that. Callers catching `Exception` (like `validate_file_for_import`) are unaffected. No test expected the old `AttributeError` on this specific code path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- CHANGELOG: document the AttributeError -> ValueError shift in _validate_attribute_value and the too-many-columns branch of _validate_import_line_or_throw. Direct callers of the private validators who caught AttributeError specifically need to widen their handler; validate_file_for_import already catches Exception so it is unaffected. - test_user_model: add test_validate_import_line_rejects_unknown_auth covering the validator-side unknown-AUTH path (previously only the create_user_from_line side had coverage). Deleting the AUTH-column normalization line now fails this test. - Drop U+2014 em-dashes from the file where this PR added them (three test comments and one code comment on the _valid_attributes block). Repo has no checkstyle for non-ASCII but the rest of the file uses plain "--". No functional change beyond the additional test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Post-merge follow-up on #1811 -- the strict behavior it introduced (create_user_from_line raises ValueError on any auth value not in _AUTH_CANONICAL) would block CSV imports against newer servers as soon as Tableau ships an auth type TSC's hardcoded list doesn't yet know about. This is the same category of stale-list problem the _set_values enum-guard bypass exists to avoid on the server-parse path. Both entry points now warn and pass the value through: - create_user_from_line: unknown values raise a UserWarning naming the value and known set, then get assigned to auth_setting as-is. If the value really is a typo, the server rejects the row when the request posts -- a slightly-later error, but the import stays possible against forward-compatible servers. - _validate_import_line_or_throw: same shape. Skips the allowlist check for the AUTH column when the value isn't in _AUTH_CANONICAL, so validate_file_for_import doesn't return the row as invalid. Server-version-aware validation would be the cleaner long-term fix here (and for the enum-guard bypass in _set_values); noted for planning, not filing an issue. Tests updated: two former "raises ValueError" cases now assert pytest.warns(UserWarning) and confirm the raw value round-trips onto UserItem.auth_setting.
… _evaluate_site_role (#1812) * fix: CSVImport AUTH column, case-insensitive validation, username preservation; 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> * fix: add auth column to create_users_csv, fix create_from_file extension check and debug prints - create_users_csv was producing 7-column CSV, silently dropping auth_setting; bulk_add roundtrip would lose auth type - create_from_file extension check used filepath.find("csv") which evaluates as falsy only when "csv" is at index 0, letting all other paths through; fixed to "csv" not in filepath - remove two debug print() calls left in create_from_file Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: extract _decompose_site_role into CSVImport, symmetrical with _evaluate_site_role Moves the ad-hoc site role → (license, admin_level, publish) logic from create_users_csv into UserItem.CSVImport._decompose_site_role, making it the explicit inverse of _evaluate_site_role. Also fixes pre-existing bugs in the decomposition: - ExplorerCanPublish was emitted as license="ExplorerCanPublish" (not a valid CSV license value); now correctly "Explorer" with publish=1 - SiteAdministrator (legacy role) was emitting license="" via str.replace; now maps to ("Explorer", "Site", "1") - Non-admin roles now emit admin_level="None" (explicit CSV spec value) rather than "" (empty string); both are accepted by the server but "None" is consistent with the spec and _evaluate_site_role input Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: decompose unsupported site roles to license="Invalid" not "Unlicensed" Legacy values in UserItem.Roles (UnlicensedWithPublish, ViewerWithPublish, Guest, SupportUser) have never been accepted by the server-side CSV license parser (workgroup: CsvLicenseRoleTypeConverter). The initial _decompose_site_role default of ("Unlicensed", "None", "0") silently coerced these to a valid but semantically wrong Unlicensed user, replacing an old server-side per-row rejection with silent success. Default to license="Invalid" instead so the server continues to reject rows with USER_CSV_INVALID_LICENSE, preserving the pre-refactor observable behavior for callers who inspect job results for per-row failures. Batch resilience is unaffected: bad rows fail, good rows succeed, no client-side throw takes down the whole bulk_add call. Follow-up: deprecate UnlicensedWithPublish/ViewerWithPublish from UserItem.Roles (never worked on any code path); see forthcoming issue. * docs: clarify _decompose_site_role's asymmetric legacy-role handling The role map handles legacy UserItem.Roles values in two ways depending on whether the server has a modern equivalent. SiteAdministrator, Publisher, Interactor, and ReadOnly map to their current-model equivalents; UnlicensedWithPublish, ViewerWithPublish, Guest, and SupportUser fall to license="Invalid" so the server rejects the row. Reviewers otherwise read the code and see "why does Publisher get mapped but Guest doesn't?" - the answer is server behavior, not arbitrary choice. Docstring now says so. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: accept "1"/"0" and "true"/"false" for publisher, add round-trip test Two related changes to make `_evaluate_site_role` and `_decompose_site_role` a real inverse pair: - `_evaluate_site_role` now accepts "1", "true", and "yes" for the publisher column (and, symmetrically, treats anything else as "no"). Previously only "yes" was accepted, which meant `_decompose_site_role` emitting "1" for Creator/ExplorerCanPublish would round-trip as Explorer. The set of publisher values mirrors what `_valid_attributes[publisher]` already documents as legal (["yes", "true", "1", "no", "false", "0"]), so this brings the two code paths in agreement. - Added a parametrized `test_decompose_then_evaluate_round_trips` covering every entry in `_role_map` plus the two documented label asymmetries (ServerAdministrator -> SiteAdministrator on the way back; legacy roles Publisher/Interactor/ReadOnly/SiteAdministrator folded into their modern equivalents). If either function drifts, a specific input names the broken case rather than the whole loop dying on the first mismatch. - `_decompose_site_role` docstring updated with a Round-trip note naming the two intentional asymmetries so readers do not have to reason about them from the mapping table. Full suite still passes: 884 passed, 1 skipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address fresh-eyes review nits before un-drafting - Strip five non-ASCII characters (arrow + em-dashes) from docstrings and comments; project convention is ASCII-only. - create_user_from_line now passes an unknown auth value through unchanged (previously silently .get'd to None). _validate_import_ line_or_throw on the same input raises, so the two entry points no longer disagree on what counts as valid. Not addressed here (per fresh-eyes review, non-blocking): - Deprecated create_from_file's "csv" substring check remains loose (matches report.csv.bak, rejects USERS.CSV); pre-existing on a @deprecated method, out of scope for this refactor. - PR body test-plan checkbox that's actually complete: tick before un-drafting. 59 user tests pass. * Warn (don't raise) on unknown auth values during CSV import Post-merge follow-up on #1811 -- the strict behavior it introduced (create_user_from_line raises ValueError on any auth value not in _AUTH_CANONICAL) would block CSV imports against newer servers as soon as Tableau ships an auth type TSC's hardcoded list doesn't yet know about. This is the same category of stale-list problem the _set_values enum-guard bypass exists to avoid on the server-parse path. Both entry points now warn and pass the value through: - create_user_from_line: unknown values raise a UserWarning naming the value and known set, then get assigned to auth_setting as-is. If the value really is a typo, the server rejects the row when the request posts -- a slightly-later error, but the import stays possible against forward-compatible servers. - _validate_import_line_or_throw: same shape. Skips the allowlist check for the AUTH column when the value isn't in _AUTH_CANONICAL, so validate_file_for_import doesn't return the row as invalid. Server-version-aware validation would be the cleaner long-term fix here (and for the enum-guard bypass in _set_values); noted for planning, not filing an issue. Tests updated: two former "raises ValueError" cases now assert pytest.warns(UserWarning) and confirm the raw value round-trips onto UserItem.auth_setting. * Strip password-mask line: it belongs to #1862, not this PR Inadvertently pulled the password-column mask (safe_value = "***" if column == PASS else value) into this PR while restructuring the validation loop for the warn-on-unknown-auth change. The mask is #1862's core content and shouldn't sneak in through the site-role decompose PR -- reviewers on either PR would see mysterious overlap. #1862's full feature (log mask + INFO->DEBUG downgrade + _redact_password_column helper for invalid_lines sanitization) stays where it belongs, on jac/csv-import-privacy. The `column = ColumnType(i)` local rename stays because it's used by the log line's `{column.name}` format and by the AUTH branch's comparison; that's plain cleanup and doesn't overlap with #1862. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
| Back | FazBrowse Home | New Git URL |
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).
Behavior change
For users:
Test plan
Related
QuerysetEndpoint.find_by_name was originally bundled here but was moved to its own follow-up PR to keep this one focused on CSVImport.
🤖 Generated with Claude Code