Skip to content

Commit 7d4522d

Browse files
jacalataclaude
andcommitted
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>
1 parent 157019f commit 7d4522d

3 files changed

Lines changed: 25 additions & 18 deletions

File tree

tableauserverclient/models/user_item.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,27 @@ def _validate_attribute_value(item: str, possible_values: list[str], column_type
548548
return
549549
raise AttributeError(f"Invalid value {item} for {column_type}")
550550

551+
# Inverse of _evaluate_site_role: decompose a site role back to (license, admin_level, publish)
552+
# for writing the CSV import format.
553+
@staticmethod
554+
def _decompose_site_role(site_role: str) -> tuple[str, str, str]:
555+
"""Return (license, admin_level, publish) CSV column values for a given site role."""
556+
_role_map: dict[str, tuple[str, str, str]] = {
557+
"ServerAdministrator": ("Creator", "System", "1"),
558+
"SiteAdministratorCreator": ("Creator", "Site", "1"),
559+
"SiteAdministratorExplorer": ("Explorer", "Site", "1"),
560+
"SiteAdministrator": ("Explorer", "Site", "1"), # legacy role, treat as SiteAdministratorExplorer
561+
"Creator": ("Creator", "None", "1"),
562+
"ExplorerCanPublish": ("Explorer", "None", "1"),
563+
"Explorer": ("Explorer", "None", "0"),
564+
"Viewer": ("Viewer", "None", "0"),
565+
"Unlicensed": ("Unlicensed", "None", "0"),
566+
"ReadOnly": ("Viewer", "None", "0"),
567+
"Publisher": ("Explorer", "None", "1"),
568+
"Interactor": ("Explorer", "None", "0"),
569+
}
570+
return _role_map.get(site_role, ("Unlicensed", "None", "0"))
571+
551572
# https://help.tableau.com/current/server/en-us/csvguidelines.htm#settings_and_site_roles
552573
# This logic is hardcoded to match the existing rules for import csv files
553574
@staticmethod

tableauserverclient/server/endpoint/users_endpoint.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -764,22 +764,7 @@ def create_users_csv(users: Iterable[UserItem]) -> bytes:
764764
with io.StringIO() as output:
765765
writer = csv.writer(output, quoting=csv.QUOTE_MINIMAL)
766766
for user in users:
767-
site_role = user.site_role or "Unlicensed"
768-
if site_role == "ServerAdministrator":
769-
license = "Creator"
770-
admin_level = "System"
771-
elif site_role.startswith("SiteAdministrator"):
772-
admin_level = "Site"
773-
license = site_role.replace("SiteAdministrator", "")
774-
else:
775-
license = site_role
776-
admin_level = ""
777-
778-
if any(x in site_role for x in ("Creator", "Admin", "Publish")):
779-
publish = 1
780-
else:
781-
publish = 0
782-
767+
license, admin_level, publish = UserItem.CSVImport._decompose_site_role(user.site_role or "Unlicensed")
783768
writer.writerow(
784769
(
785770
f"{user.domain_name}\\{user.name}" if user.domain_name else user.name,

test/test_user.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ def test_create_users_csv() -> None:
405405
"ServerAdministrator": "System",
406406
}
407407

408-
csv_columns = ["name", "password", "fullname", "license", "admin", "publish", "email"]
408+
csv_columns = ["name", "password", "fullname", "license", "admin", "publish", "email", "auth"]
409409
csv_data = create_users_csv(users)
410410
csv_file = io.StringIO(csv_data.decode("utf-8"))
411411
csv_reader = csv.reader(csv_file)
@@ -417,8 +417,9 @@ def test_create_users_csv() -> None:
417417
assert (user.fullname or "") == csv_user["fullname"]
418418
assert (user.email or "") == csv_user["email"]
419419
assert license_map[site_role] == csv_user["license"]
420-
assert admin_map.get(site_role, "") == csv_user["admin"]
420+
assert admin_map.get(site_role, "None") == csv_user["admin"]
421421
assert publish_map[site_role] == int(csv_user["publish"])
422+
assert (user.auth_setting or "") == csv_user["auth"]
422423

423424

424425
def test_bulk_add(server: TSC.Server) -> None:

0 commit comments

Comments
 (0)