Skip to content

refactor: extract CSVImport._decompose_site_role, inverse of _evaluate_site_role#1812

Open
jacalata wants to merge 2 commits into
jac/csv-import-fixes-and-find-by-namefrom
jac/csv-site-role-decompose
Open

refactor: extract CSVImport._decompose_site_role, inverse of _evaluate_site_role#1812
jacalata wants to merge 2 commits into
jac/csv-import-fixes-and-find-by-namefrom
jac/csv-site-role-decompose

Conversation

@jacalata

@jacalata jacalata commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacks on #1811.

Moves the ad-hoc site role → CSV columns logic from create_users_csv into UserItem.CSVImport._decompose_site_role, making it the explicit inverse of _evaluate_site_role. Both encode/decode functions now live together in CSVImport.

What changed

New method: UserItem.CSVImport._decompose_site_role(site_role) -> (license, admin_level, publish)

Replaces the if/elif block in create_users_csv with a lookup table. create_users_csv becomes:

license, admin_level, publish = UserItem.CSVImport._decompose_site_role(user.site_role or "Unlicensed")

Pre-existing bugs fixed in the decomposition:

  • ExplorerCanPublish was emitted as license="ExplorerCanPublish" — not a valid CSV license value; now ("Explorer", "None", "1")
  • SiteAdministrator (legacy role) was emitting license="" via str.replace("SiteAdministrator", ""); now maps to ("Explorer", "Site", "1")
  • Non-admin roles now emit admin_level="None" (explicit CSV spec value) rather than "" — both accepted by the server but "None" is consistent with the spec and what _evaluate_site_role expects as input

Test updated: test_create_users_csv now asserts "None" for non-admin roles (previously asserted "") and verifies the auth column.

Test plan

  • pytest test/test_user.py test/test_user_model.py — 46 passed
  • Full suite — 848 passed, 1 skipped

🤖 Generated with Claude Code

jacalata and others added 2 commits July 7, 2026 12:33
…ion 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>
…th _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>
@bcantoni

Copy link
Copy Markdown
Contributor

Correctness: silent behavior change for several Roles values

The new _role_map lookup table doesn't faithfully preserve the old if/elif logic for every UserItem.Roles value. Diffing old vs. new decomposition for each role constant:

Role Old CSV output (license, admin, publish) New CSV output
Interactor ("Interactor", "", 1) ("Explorer", "None", "0")
Publisher ("Publisher", "", 1) ("Explorer", "None", "1")
ReadOnly ("ReadOnly", "", 0) ("Viewer", "None", "0")
UnlicensedWithPublish ("UnlicensedWithPublish", "", 1) ("Unlicensed", "None", "0")falls into the .get() default, silently drops publish
ViewerWithPublish ("ViewerWithPublish", "", 1) ("Unlicensed", "None", "0")same silent drop
Guest ("Guest", "", 0) ("Unlicensed", "None", "0")
SupportUser ("SupportUser", "", 0) ("Unlicensed", "None", "0")

Some of these (Interactor, Publisher, ReadOnly) look like reasonable improvements — their old outputs already emitted invalid license strings, so mapping them to real license values is a fix. But UnlicensedWithPublish and ViewerWithPublish look like a real regression: the old code at least preserved publish=1 even though license was garbage. The new _role_map.get(..., default) silently coerces both to Unlicensed/publish=0. Any caller using site_role = UserItem.Roles.ViewerWithPublish will now get a user provisioned as fully unlicensed instead of a licensed publisher, with no error or warning.

Suggestion: add explicit entries for Guest, SupportUser, UnlicensedWithPublish, and ViewerWithPublish to _role_map (matching whatever the CSV import spec actually expects for these), or if they're genuinely unsupported by CSV import, raise/log rather than silently defaulting to Unlicensed. At minimum, worth calling out in the PR description that these four roles' behavior is changing — two of them silently and detrimentally.

@bcantoni

Copy link
Copy Markdown
Contributor

@jacalata take a look at the consistency check that claude called out - does that need to be fixed/improved?

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.

2 participants