From 5311253d644b9f101a9d427f2a9219ae24065512 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Mon, 3 Aug 2026 18:04:32 -0700 Subject: [PATCH 1/6] Fix #434: map "Local" auth_setting to ServerDefault tabcmd Classic accepts "Local" as an auth type on the CLI and CSV column 7. tabcmd 2 kept "Local" in the accepted list but TSC's UserItem.Auth enum has no Local value, so when the user reached server.users.add() the wire representation raised ValueError. Map Classic's "Local" -> TSC.UserItem.Auth.ServerDefault in Userdata.to_tsc_user() so CSVs authored for Classic import cleanly. Case-insensitive to match Classic behavior. Fixes #434. --- tabcmd/commands/user/user_data.py | 8 +++++++- tests/commands/test_user_utils.py | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/tabcmd/commands/user/user_data.py b/tabcmd/commands/user/user_data.py index 272a5375..5c5d10d5 100644 --- a/tabcmd/commands/user/user_data.py +++ b/tabcmd/commands/user/user_data.py @@ -45,7 +45,13 @@ def to_tsc_user(self) -> TSC.UserItem: site_role = UserCommand.evaluate_site_role(self.license_level, self.admin_level, self.publisher) if not site_role: raise AttributeError(_("tabcmd.user.error.site_role_required")) - user = TSC.UserItem(self.name, site_role, self.auth) + # tabcmd Classic accepts "Local" as an auth type; TSC's UserItem.Auth enum + # has no Local value and rejects it. Map Classic's "Local" to ServerDefault + # so CSVs authored for Classic import without crashing. + auth = self.auth + if isinstance(auth, str) and auth.lower() == "local": + auth = TSC.UserItem.Auth.ServerDefault + user = TSC.UserItem(self.name, site_role, auth) user.email = self.email user.fullname = self.fullname return user diff --git a/tests/commands/test_user_utils.py b/tests/commands/test_user_utils.py index 67de8d56..87512a19 100644 --- a/tests/commands/test_user_utils.py +++ b/tests/commands/test_user_utils.py @@ -150,3 +150,25 @@ def test_parse_line_preserves_role(self): user = UserCommand._parse_line("username, pword, fname, creator, none, yes, email") assert user is not None assert user.site_role == "Creator", f"Expected Creator, got {user.site_role}" + + def test_local_auth_maps_to_server_default(self): + # tabcmd Classic accepts "Local" as an auth type; TSC's Auth enum has no + # Local value, so passing it through raises ValueError on server.users.add. + # to_tsc_user should map Classic's "Local" -> ServerDefault for parity. + data = Userdata() + data.populate(["username", "pword", "fname", "creator", "none", "yes", "email", "Local"]) + user = data.to_tsc_user() + assert user.auth_setting == TSC.UserItem.Auth.ServerDefault, user.auth_setting + + def test_local_auth_case_insensitive(self): + data = Userdata() + data.populate(["username", "pword", "fname", "creator", "none", "yes", "email", "local"]) + user = data.to_tsc_user() + assert user.auth_setting == TSC.UserItem.Auth.ServerDefault, user.auth_setting + + def test_non_local_auth_passes_through(self): + # SAML/OpenID/etc. are not remapped. + data = Userdata() + data.populate(["username", "pword", "fname", "creator", "none", "yes", "email", TSC.UserItem.Auth.SAML]) + user = data.to_tsc_user() + assert user.auth_setting == TSC.UserItem.Auth.SAML, user.auth_setting From 83ed29c2a1a13694b8d0d845ce7ce8279e81da2c Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Sat, 8 Aug 2026 01:13:44 -0700 Subject: [PATCH 2/6] Log a warning when Local auth is remapped to ServerDefault A silent remap is surprising: an operator who typed Local (or imported a Classic CSV that uses it) previously got a user with ServerDefault auth and no signal that anything changed. Emit a WARNING that names the user and states both the input and remapped auth so the operator can spot unexpected remaps in a large batch. Adds a matching assertion in the existing test. Co-Authored-By: Claude Opus 4.7 (1M context) --- tabcmd/commands/user/user_data.py | 3 +++ tabcmd/locales/en/tabcmd_messages_en.properties | 1 + tests/commands/test_user_utils.py | 12 +++++++++--- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tabcmd/commands/user/user_data.py b/tabcmd/commands/user/user_data.py index 5c5d10d5..b7e88656 100644 --- a/tabcmd/commands/user/user_data.py +++ b/tabcmd/commands/user/user_data.py @@ -50,6 +50,9 @@ def to_tsc_user(self) -> TSC.UserItem: # so CSVs authored for Classic import without crashing. auth = self.auth if isinstance(auth, str) and auth.lower() == "local": + logging.getLogger(__name__).warning( + _("tabcmd.user.warning.local_auth_remapped").format(self.name) + ) auth = TSC.UserItem.Auth.ServerDefault user = TSC.UserItem(self.name, site_role, auth) user.email = self.email diff --git a/tabcmd/locales/en/tabcmd_messages_en.properties b/tabcmd/locales/en/tabcmd_messages_en.properties index 7eda4bff..296bdd05 100644 --- a/tabcmd/locales/en/tabcmd_messages_en.properties +++ b/tabcmd/locales/en/tabcmd_messages_en.properties @@ -214,6 +214,7 @@ tabcmd.status.job_completed=Job completed tabcmd.status.waiting_for_refresh_job=Waiting for refresh job to begin tabcmd.user.error.site_role_required=Site role is required tabcmd.user.help.auth_type=Assigns the authentication type for all users in the CSV file. Possible values: +tabcmd.user.warning.local_auth_remapped=User "{0}": auth_setting "Local" is not a Tableau Server auth type; mapped to ServerDefault. The site's default auth method will be used. tabcmd.user.help.site_role=Specifies a site role for all users in the .csv file. Possible roles: tabcmd.warning.calculations_not_supported=Adding or removing Calculations tasks are not supported tabcmdparser.global.behaviors=Global behaviors: diff --git a/tests/commands/test_user_utils.py b/tests/commands/test_user_utils.py index 87512a19..9c584c60 100644 --- a/tests/commands/test_user_utils.py +++ b/tests/commands/test_user_utils.py @@ -154,16 +154,22 @@ def test_parse_line_preserves_role(self): def test_local_auth_maps_to_server_default(self): # tabcmd Classic accepts "Local" as an auth type; TSC's Auth enum has no # Local value, so passing it through raises ValueError on server.users.add. - # to_tsc_user should map Classic's "Local" -> ServerDefault for parity. + # to_tsc_user should map Classic's "Local" -> ServerDefault for parity + # and log a warning so the operator sees the remap happened. data = Userdata() data.populate(["username", "pword", "fname", "creator", "none", "yes", "email", "Local"]) - user = data.to_tsc_user() + with self.assertLogs("tabcmd.commands.user.user_data", level="WARNING") as logs: + user = data.to_tsc_user() assert user.auth_setting == TSC.UserItem.Auth.ServerDefault, user.auth_setting + # In tests the gettext catalog isn't loaded, so `_()` returns the raw key; + # we just assert the localize key made it to the log record. + assert any("local_auth_remapped" in msg for msg in logs.output), logs.output def test_local_auth_case_insensitive(self): data = Userdata() data.populate(["username", "pword", "fname", "creator", "none", "yes", "email", "local"]) - user = data.to_tsc_user() + with self.assertLogs("tabcmd.commands.user.user_data", level="WARNING"): + user = data.to_tsc_user() assert user.auth_setting == TSC.UserItem.Auth.ServerDefault, user.auth_setting def test_non_local_auth_passes_through(self): From d35890fa4a65ce841144b3da47116c26183422d9 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Sat, 19 Sep 2026 19:29:10 -0700 Subject: [PATCH 3/6] refactor: extract normalize_auth_setting; apply CLI + fix nits Addresses Copilot review on PR #446: - Hoist "Local"->ServerDefault remap into UserCommand.normalize_auth_setting and reuse it for CLI --auth-type in create_site_users / create_users_command so CSV and CLI paths behave the same. - Fix NameError in dodo.py uniquify_file success log. - Single-quote the {0} placeholder in the local_auth_remapped warning so the properties->po converter doesn't emit malformed .mo entries; recompile .mo catalogs. - Assert warning names the affected user instead of coupling to the localize key. --- dodo.py | 2 +- tabcmd/commands/user/create_site_users.py | 2 +- tabcmd/commands/user/create_users_command.py | 2 +- tabcmd/commands/user/user_data.py | 24 +++++++++++------- tabcmd/locales/de/LC_MESSAGES/tabcmd.mo | Bin 19443 -> 19591 bytes tabcmd/locales/en/LC_MESSAGES/tabcmd.mo | Bin 20732 -> 22752 bytes .../locales/en/tabcmd_messages_en.properties | 2 +- tabcmd/locales/es/LC_MESSAGES/tabcmd.mo | Bin 19175 -> 19346 bytes tabcmd/locales/fr/LC_MESSAGES/tabcmd.mo | Bin 19599 -> 19776 bytes tabcmd/locales/ga/LC_MESSAGES/tabcmd.mo | Bin 19517 -> 19659 bytes tabcmd/locales/it/LC_MESSAGES/tabcmd.mo | Bin 19329 -> 19510 bytes tabcmd/locales/ja/LC_MESSAGES/tabcmd.mo | Bin 21856 -> 22037 bytes tabcmd/locales/ko/LC_MESSAGES/tabcmd.mo | Bin 19611 -> 19810 bytes tabcmd/locales/pt/LC_MESSAGES/tabcmd.mo | Bin 18559 -> 18733 bytes tabcmd/locales/sv/LC_MESSAGES/tabcmd.mo | Bin 17966 -> 18161 bytes tabcmd/locales/zh/LC_MESSAGES/tabcmd.mo | Bin 16694 -> 16916 bytes tests/commands/test_user_utils.py | 7 ++--- 17 files changed, 23 insertions(+), 16 deletions(-) diff --git a/dodo.py b/dodo.py index 9d193c9b..82ac894c 100644 --- a/dodo.py +++ b/dodo.py @@ -455,7 +455,7 @@ def uniquify_file(filename): ) ) else: - print("Saved {} sorted unique lines to {}".format(len(uniques), filename)) + print("Saved {} sorted unique lines to {}".format(len(unique_lines), filename)) def task_clean_all(): diff --git a/tabcmd/commands/user/create_site_users.py b/tabcmd/commands/user/create_site_users.py index c63436b0..7338c623 100644 --- a/tabcmd/commands/user/create_site_users.py +++ b/tabcmd/commands/user/create_site_users.py @@ -49,7 +49,7 @@ def run_command(cls, args): if args.role: user_obj.site_role = args.role # tsc is case sensitive if args.auth_type: - user_obj.auth_setting = args.auth_type + user_obj.auth_setting = UserCommand.normalize_auth_setting(args.auth_type, user_obj.name) number_of_users_listed += 1 result = server.users.add(user_obj) logger.info(_("common.output.succeeded").format(user_obj.name)) diff --git a/tabcmd/commands/user/create_users_command.py b/tabcmd/commands/user/create_users_command.py index 9dafcb3f..7790f4ec 100644 --- a/tabcmd/commands/user/create_users_command.py +++ b/tabcmd/commands/user/create_users_command.py @@ -57,7 +57,7 @@ def run_command(cls, args): if args.role: user_obj.site_role = args.role if args.auth_type: - user_obj.auth_setting = args.auth_type + user_obj.auth_setting = UserCommand.normalize_auth_setting(args.auth_type, user_obj.name) server.users.add(user_obj) logger.info(_("common.output.succeeded").format(user_obj.name)) number_of_users_added += 1 diff --git a/tabcmd/commands/user/user_data.py b/tabcmd/commands/user/user_data.py index b7e88656..cd7ec38e 100644 --- a/tabcmd/commands/user/user_data.py +++ b/tabcmd/commands/user/user_data.py @@ -45,15 +45,7 @@ def to_tsc_user(self) -> TSC.UserItem: site_role = UserCommand.evaluate_site_role(self.license_level, self.admin_level, self.publisher) if not site_role: raise AttributeError(_("tabcmd.user.error.site_role_required")) - # tabcmd Classic accepts "Local" as an auth type; TSC's UserItem.Auth enum - # has no Local value and rejects it. Map Classic's "Local" to ServerDefault - # so CSVs authored for Classic import without crashing. - auth = self.auth - if isinstance(auth, str) and auth.lower() == "local": - logging.getLogger(__name__).warning( - _("tabcmd.user.warning.local_auth_remapped").format(self.name) - ) - auth = TSC.UserItem.Auth.ServerDefault + auth = UserCommand.normalize_auth_setting(self.auth, self.name) user = TSC.UserItem(self.name, site_role, auth) user.email = self.email user.fullname = self.fullname @@ -113,6 +105,20 @@ class UserCommand(Server): This class acts as a base class for user related group of commands """ + # tabcmd Classic accepts "Local" as an auth type; TSC's UserItem.Auth enum + # has no Local value and rejects it. Map Classic's "Local" to ServerDefault + # (both for CSV values and for the CLI --auth-type override) so inputs + # authored for Classic import without crashing, and log a warning so the + # operator sees the remap happened. + @staticmethod + def normalize_auth_setting(auth, username: Optional[str] = None): + if isinstance(auth, str) and auth.lower() == "local": + logging.getLogger(__name__).warning( + _("tabcmd.user.warning.local_auth_remapped").format(username) + ) + return TSC.UserItem.Auth.ServerDefault + return auth + @staticmethod def set_role_arg(parser): parser.add_argument( diff --git a/tabcmd/locales/de/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/de/LC_MESSAGES/tabcmd.mo index cac49e59979b37c6f1ff8c94d05485f8d51a94ca..bb3aab6b0d2c871bf7163f386c2a8b79917a78a7 100644 GIT binary patch delta 3701 zcmZwIeQ;FO8OQOn2__)#0l@?UCt&2oxFH3hCh-M=L@^CAwxCi>H@i2xtDC*+-Wvq% zuoXpt+Od+UiWvnnI7MX~aP9ODij_{LlvW07)oI2y(mED86&tmGFurv9{q0_3?Uaf8 zIp^Ga&v~BboO}J`@z6tiLb;)eQ9m*KJHmf6`R`vBsQ1;s*Tx!CMfW65#Ism~=kX$} z8fVNbT!a?a;B?Fc?!c+^_u*vx8S1&?SZz$s{F#Pj;4{1oXO1sUcsU==E$l~{ur zoQAtm3q6MyjLKZc5I2({oa>VXOd z=VL8eY)5U0iz@Z5px=+IZH}UzKZy$XpID1!Og0zmQ0v`45;~N7rSjqVPs4aO4RiT$q75W`gWHX4(cyph|cq=$G@brC*CWGpkSuB~kM~j+*B{&>ui0{?TpdQOe zkKoKwRLNJP&Vq;hnWy;CL@x)vj+XuhNGxVNvnud8s7fqHEwmxfLredMs6-E{{yb}r zqxNP9l|cznsZ_P7JzatpSEBB7kUz7NAMIs-F#gNH_ppZXa-y)f09CowfeB=HP3}P& ztZDWH1BX$O4WPw$Q4?vfkQr((m!kq)g9_+&ROPmz0(uO2)Z_zCA~BgWfhr>bwxIUTY|c;6Ztbg;71GW3&xM2 z0vQa(hf(v5wZ*Nf#atf)SJKeLZ{fB0DYjt~_0@M^2kPxOg#4M){HQ`DoK!gh%dskO z4yuw1QD4wyxEP~Y8sc?CCD1pQ`aeYD1Se<>t|bbpZl1w9d|9b9ooU*`iH1PI)fP+?KXegqC_yC^8t=P>|+N%%n zYAmCUI&9aWCcFg|@cpQOpGO7uKI;AoI(nKv>2g`%6JcvRVrXBUcxfi+B>_r9md$jlwvZkrv#8qGm>cg}SRk1rz zfj^4hWRGv6792-MhxaN}C2vDr z-_B1n9>u%yQ@jVW-{fP*`sN&so3ZIiWA4I7P^Ue_#r z9{&xW!_k~9i!Y%L?H^DHoI_P^#x=$3D^O=5ia*BJuA%7c<7~wFoeWr4`F=Ma| zsjgXv)%YV+hA-fqcnWo9nkb7BxgE=J56;GYScR`5yKLS;JwK7M>dY-}$?^TBaSHoml$HBPx3_go}*xJhfcL@V8_f<3~E9-LY`-?wFHud?y^wx;>e%U+Ib@Q$1NH?D-MD z$Lk$aeZ};+jhJP{VGwVov6=T|8qUzq%z^C+ua>W$Nt}?nBzsW$&8*HnRr2A-NKWpWctpL@vnxx zj1x_EC8PPD&Ta_hOKJ|4<_9j>Tbe&Oe?w`0Mcwx=$ba1Mn=$!m%@yUn@7lS&<#kPE zt0;mK>z&uq-1|zyl6vq|17piSXvbj0o-fg=b z&x^zzo8qQqIzDpMlCUk-&bo#CygKVAoV0Z|Cw;TZ&D!pqn{}-6w4Ou5_cLB&eLdAr zIgy@lJn1KTI>Sj!I0oC>89@hxJ&*F}R3A)@%w1VIr95tJw)ibSJSqm7C2`{UO5kFh!KGqX24^UO2P zE^E7deH(qruX6`HW%%30zheHK$Wrg$zi0f$jH0^*FTh<`iJ#y^?8g!;8EVW(ti>^S z2U>V9UWi?uZ(=_EUL0Xe(tJ%rH~x&Hap17@jioq={JSd1A0&k9YgKlENTaSqEh4*D!`e1Y2gOc z_;xJAhta}UQ41VErT&c9_c55*Ofl;I$(U5+^)xO=8!yIg9FKcZ3!g?!l#xd^a1?5% zb5S)AL(S8T3^i|g{SQ$`d&0YZ&NGW$UBvameDbdyUdESFc0DR(i;+_>5xg4jM+MM> zB*h#;O?(Qq(|%Nqa(eFZHHa$riiu@odB|jk_lg%p9 zLJO}%?eq@R#BtOStwU|#byUUl*0L_$QqF~ z&3&FvqauI9vlkV}x5%lTzeyP>;AW+?92Hm%>dfy%-QR&a+7v3V7jcB1|Bq-8oH>S? zsPFs_M9M0`%f(rOSw6GhMq1~wa4>irNwE$|4cinn@xjGE{t)C5D>UIk9TQe22C;ttgB+fbQ(AG`4=YMf*7R>X~% z97y9B4HeNzyb&|VkDluWRF$tpKIUbEq3b z3b2WTUyGgN$$tZlLc${JrUfVC8sw#A-tydsy74FmaR6_r#n^&u%Jd@TV9ub5D~q)0 zG0aEJHw%fyv|=@`M~&~DkW8oOG#B`oe!i|ki|s40yKpLQN9HzPp(f1aVZRJ3aV`e2 z9(SOM@K4mZ9HLOs%|yL1>%IOeum4z*hKlHIRAdM6VI0Ef)%ZB-EQgcNSy+ZDt|h1m zJ5YhYgbIAW=O9*B05yE+z9{Nxc@0&B`@HMP<1{vLVHEk@i!SzJg38hYvnhaOxD>C& zW2lsmsZ2WyFQ>lZf4~E=1MD9<=oQpQE7+BPa^HCePhW^Pbcvqm!mloWDD((ZQjGje4rr=8c zb7Or|Ph%yn!w$@>GUg^+jkn@{Y)6X&=5)*kti|_`dNczloEn^r^Dv5cVGrJb1y}R3 z!o{c!y@`2v1d~eD85-JI(aiK?Q-{U$Bgh(N9lnMiVG$;%C_g@dLvRaf!fmMH+KoE% zFYp!o4J~|jcDk6~Lv7^C+2mg-%)TZ)U<#^87GNj7iyLs>oOFO+A;)Y6vfaU0g=E{* z;%MwZ?Qj#e;%*#^Wu$8q&PDwmMAg>XTJpbw#ttq7aC}{Q=WDRYM;+kJT;D_K>-QWA zL>X~#B)*CqyZHd0!+z|>?(2=2gEQu)16zeh=x@YNaT({r17@mca9}E~r8ovRazG}Jc{_gMd z4UI0>RI!RkV43Y69~Q~*|8phbw7FF|hkX_QG2aq9VmskrW5{l^<94IMTOMn)9VhCz zS-GVddG@`m6H|~x+}Nz>-O~KN0jYz9*?wj=j@|6ov6fWlup;-8i^gWAb`+Ppe-tmx zaQ#-LZ{T%AZHH1<)s(u`C2wSos3$xtWXG%pPIRf=8jra@ly1$OP-i<C*?#oIYubJ)!rhYQG=znHN lJy@FW*FTjuW+9Ln} diff --git a/tabcmd/locales/en/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/en/LC_MESSAGES/tabcmd.mo index a067f324e46517169c6aa3b84575f9fb3f72e1a8..0bede2bad054efd3b10870845904ae3d57094e02 100644 GIT binary patch delta 7336 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 C_G*^^ delta 5434 zcmb`|dvKK18OQOH5FikNa4{qx_yPffkPU>W5bkmfV7Q}JC4IB|CRx}^cz0LG0NtX9 zwOp*e-m0AzXc@-~1zfc^wY6xaWh^=^I2KeuvExX~AhqHJr{CYcE2Ax)=^tg5{har* z=RD^*&pA1^H}CW9dFi)$6x?U{cZ~m+>3>JQzVh#~uEzA?x*vAKF*p)u;ZSVD0k|D6 z#eLWx-#`n`VQ(zs$8r?*!aD43Oxi4?p&zzlKir5G{sf2POQ;Ud;6N-c_MeYNy}uZH zpoD6V#{{^uvCPZzj^vgPV|#Y2!;X+JqWl7ixe5I1JxH1y;yS zy;p-JxD+)|6ie|z?1Fny{rnm&d=nMu7rJJAQ`Xaep%%Gku1C#a1!@MHPyud3#x~EP z20n;-{|r`O0WVuvi5j3DmGW@zdOeav^8?iHpTM*t|09hn@gxq$zO2U}oPrwIL3MO1 zR^mgbneIo`z*$s3efd&A)u`*~eDTQKl)LZdydBH9|6mFEXH4@XHq$;0BZSS8O=ge$3fIg+fX%fFZRU@4#9({)c+He(OwLq>l(ChVJZ1nQ6;#cnca^X z;6+qNhjQ0vkUmU4Vbe5yunI>b**8Jd60Abi$TlQq^9NKv$8(-Xi|aC?*2HVmG!*$f za zT%(w~G))Z-!1-vQi%OZ7b1!O*526A+k-PpB6<`-$wy*-V1a+wXT2T|d2Nlp>WU15U z5Di8CPVT{nsFarQHhr2aP??#T(?-o?P0o9fpO{^!nmB;U&~a3X&!GbCLIJ1-2J@xg zPsd{I|7A23VH~x+?ncez$Ec2;M`h*^Dv&cdi!8t6!N|u<;7iY!A!C}=sD2+n_4^A{ zzX$LVd=HD$G|tkXPgB6#nn5|LgIZLiH=us#q5|218tBp7^}d{M=ll%yyWSLr-W!R^ z!0g<0BdWi(sQNPx&``>sL<@h1n)zR`2){se(3yo(#{*Fvk3$8%BzK)ci|e~kHL?fQ z|31_by@l%cbJRq7mXm+IP*v`CI6G$ub^lwr>+hoi$e@bqK+X?Q9hY%xDWIz{fD4dE z=G&-ky%Y7`pHKmxLG|B#2>I885%j9aW}_b1s1DbnGIKvZjE|v;Y6i!iPQV+n18zi; zWVT=}?m;GLKEM&!jl-ccj>k?o6P4KoX&N1A+>8b2U?DC?#xe=i3?9M*xE~$7ms2K+ zpW@ZnLNp3&8!|cbG%D~Tr~~aZ5=-_k*L0}hFW53n$7qbD@d}Q_j)cp;G-FUHoPidu zKxJk#5`%dJwZ^Za26`v={9~-4 z5kG~R!BN!F`Zw%?y*Yo?(O}f}nu~nQGQMubt=NR!8C^xY8mHmiScHes(*8d|Lo;hP z%8$H1Y7G~l2H1=mID=jB&77xDGyDvbSj)6G;cmPW$Bp)D;3b^N_4_ypN7eYXwNf&^ z*+kf6ZVYHzwmzRPpp15*}>8 zVtg1?Tu-0^`7IXWDa^-@umI1Y0yvNDu-zcUu%CQ0`qL!)|yWuU^0XJeN?f)$_ zl=2614|bpec@&4>FHuW#90%cPR3>`X`F;}za=i=t|J`g;j? z;JbJ;-Z9TVF;C)n?dy+esECT@`;iVp9WV{Zjx-*s2wy>^_5>O`6!OmWDD?`9i?46A zgDaZdcq$fh+(0az3^#>?b}}4~1)3u9Rc;FHa3tkAfke_yrV^&n4mG&W%9N8x20~7f zhkh@Oz+~ed6&dn)m0)8v!hPtv+l9{qA%~H+(_nzUVqLD zT%^BdC+4`}U_;1>I7z2L;k70j9M_GznZYIH`8}Q0jFV81MBLD%=rhww&lY&6FDopf zXX83eu9Il-_H^%?sp~hUowwau?#&s{>U}(Ly!Y&&%1nKEX?}sC@^%fm&$EYa^g30X z^tue2;;kHZm)ADDYbHGWM|mA)Fg+*aHP@AA3MVP5;H6Fut*!HPF! zAGW&j zupOZy{_PjuYa@FVhGWgynRy4RF3)rx^?aVUdBKFt{_0J6r58^alV4O*Ul#bUPq)|f znYPGDYOYhJF0NZTJ)44HGJK1z1ZGDYp)~Nqq9u#wPp_XgeVQ&3sYI(23*}x3cpufQ z^&YPqmsxq`oC2?KZcXOdu_bxcb+KS16>_o@zsS-><2-AkcgGI4WXB3*zNnp(Kft=N zzRLcO<-gfn7k5`Q#^WohyrmPyw+pYVYH?y#W#xq-Dtl{u?MBN#9<+H9 zPBP#{CiVG$#+IF?%Kv{4t+|IzpRdQ2X+ODFJ~iIg`{=4+nbK?CXy19w$aPwYHKW#f zeP^vM(mwL%)8wt6(9io|);jN@+0EX;Ib$Wh^pO)3$-|rRxOIv zl1#QHYOAV}{`nec4pSwKfpA=Rg3(ZwnZdryzRtnLLEslvkW@vZ+HK^=8tT7mjnCbj OXjQfh4S0L5ANenm91bD? diff --git a/tabcmd/locales/en/tabcmd_messages_en.properties b/tabcmd/locales/en/tabcmd_messages_en.properties index 296bdd05..605e229f 100644 --- a/tabcmd/locales/en/tabcmd_messages_en.properties +++ b/tabcmd/locales/en/tabcmd_messages_en.properties @@ -214,7 +214,7 @@ tabcmd.status.job_completed=Job completed tabcmd.status.waiting_for_refresh_job=Waiting for refresh job to begin tabcmd.user.error.site_role_required=Site role is required tabcmd.user.help.auth_type=Assigns the authentication type for all users in the CSV file. Possible values: -tabcmd.user.warning.local_auth_remapped=User "{0}": auth_setting "Local" is not a Tableau Server auth type; mapped to ServerDefault. The site's default auth method will be used. +tabcmd.user.warning.local_auth_remapped=User '{0}': auth_setting 'Local' is not a Tableau Server auth type; mapped to ServerDefault. The site's default auth method will be used. tabcmd.user.help.site_role=Specifies a site role for all users in the .csv file. Possible roles: tabcmd.warning.calculations_not_supported=Adding or removing Calculations tasks are not supported tabcmdparser.global.behaviors=Global behaviors: diff --git a/tabcmd/locales/es/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/es/LC_MESSAGES/tabcmd.mo index db1cd9b874478be8c6aee87ea29b4edfa754f199..971f2d80edd2c1a4d424761741a12dd87085b2b4 100644 GIT binary patch delta 3705 zcmZwJdvKK18OQOH1cS&m7YqqRPJnP{Ng~FYq-w(bQZB{TQfuQT`(}5?&A!{cAnA-< zE5a0nQr)7cb;f3D!BPLPuMRUhN-vCvH644wF||f)r^QZ1p~a4)Ez|FB-`7|>WtRQC z=e*}V=XsuU_JuPCOYXhDBtKF)=AhxvbNrpb-wT(h_358i#v4;da|kEnJJ^69;#91g zV9XWRgcdhp4Wk_ehpcVRq89oXE!Ix)ZA10ngqnW<75F}^W_|Mn7b^YFP?3$G7CeV~ppwq{ z*oYQ4qPE0Em3nW`9z@nQ&!e6nLIwOjHexxG&Bn#3^)_Q(6ZCVj1b3k_^Kc%Hpe87< z@&lwebgsrH|HlPkw z6sO~EoQs2~Ql3PW@J!Gi%fps-BkIhoLM0SO&3`{?o+Cl~I4bc|Q>niqJWIz?EGIpJ zGp(qSuS1=M4DvA#^QDQN4ty0Y?RSw_%tU5Y;ImMb=tM1aQ(y)y?YmKlKB4;atT}<& zn-Nq7r9`DtHKO)(8CqO{8s{J%vxhJ3t{xu($wKxpjdtWOq$|Hy5mF z9tb*~LPd5QExv`CNS(#ZPl3UCuDps%AUcPA>K`;bSC7dV8(WX=Stj0BW7wOmkr zb2X|GUqF>If(mQ^wb%EcCU^?9r!S)ddjmP(=HJNNW-_a5o`tA0(1{AfMTVF?sPT_t zoqqpExlm@mLEb^;Jo0ig7f}IRMwnW77HXn)wD=`d3Advb-h+J1kN8pmKSzxp4*Umd z+(Z^r|9q@rebdE-PWM*>zm1GFM^FP_#1NjwIXIbeD6?g#aXrY#+`*R?cqHh54i(67 z(0>j!-+1e9RU_sfqT?DaRGL%xIXsVRaUu29-eyt1jzg%!_!3@xYnmh2 zghQx_F5*VKlGPOGov8Q4_fd(wfQ#|B!F}D-{Yt)6@s2zfD*0FNcDx52oJ_hpbUAFp z@1jb75*cEKQ5lr+FO&kDhYB!?*Wf-JhcBYez^iyIp20qxOH@x{em@r)@BKNs5jKVv&iU}HOQ4R+x!)PgU`5@Y^`+h|W}_0LKSHSRIgLhqs~av9kuz~!hd zyB)Q)hcSv18MKAW?7j&)n6O=fH6B3>`YH3zqvYnu9o{gzRt) z{l8`(iid`}vrDEQRjD7N0)HM4;5pohcdYV%mSa{2|A)jk=ucq*H!!Ixb2`igzZ_Gu z)<3PQagcT&-i+hc`7f3zR?!~78r+X;pLrCO+1tqRFjF~57B^u7rq&l893S!;Cj7m` z3r*@S^^QzAUg8Z_&M5U>tA1~cce=K@>}yUs?WRLL;YfGd`9{vkWJB@f_HZH|>F#xt zStpt8&JLs;lMVOu_D4eg=!lbWvQ8+Pc5|uE-sua+6S=e#%4EaYT&6It{;Ha&lMOjL zm?;x?lcA)W?e24P$%xllpDxY&)271djFS#U6K+p95$bVb;q7rZoe8G?>|LTtI=xxu z`k(tTCy@&Ey8Zp(WaR(dia43xbUdXeM<-rTT(|gSBA&c;wExrYOv>qv_r-g?!!vD( zci!$OE4{1`(anvrTNvx{Oo9cZ6@gXubkS@vK?prfq-TO*>Y(+Kj_RHk-<{EnUhEB%E+A6pd$N zxt>tmRS#RewD?eQxSOb*j-BU^wH-l}*V-kqc_HH@Y|`!TVY#GhQ#mK% MSdLcV*>y|)1F|A91ONa4 delta 3472 zcmaLZeQZ=!8prW7#WvLzXaTX5)}HD}1!M$7SVW-|ib4q$kSHcPOlNLkmf=phGvzII zXAxr{20=$utfC+&z7St7M$)Je4I=&lipJoIY=kB1nspZ%R@r3L?~l1=|Jm5I^Ev0f zob#OLIrmN07ulj3encqXt}#SK#L0ypOv6 zGaP_tQGpisGo}tLYNAFQfeTOz{4-j73Tv6)yc3)_jznb6qgGVLay7vy)C4U!9PdU2 zwjOoeUL1^{qbB+XR$*0zF~nrXpvIYk7Vkm@`X}|7-)s#o=s`_%47GyqQ7b4TDn)Kl z0p7%iCT>MtzZ|c|`_SSZ)C7l7ssAqMhq##7ObzP(YcQwC=g_zw9lQ#=@k-o}n)ozo zprV0f1FKOhy#=)gGN^I7kxR|%LH_`%w8w+vX9J5_)ew$X4I=+q;aEPDvYSyUTZB}> zxOgLWp#tbZl46dc20n#a>3P&18N|V%cpVPIR#fU&pfdVM(BF%e{?SXxzcy7b2eh)m zEJG7aM-8+f=r2bFa4#y*C-7SIkwuwPs1p2$+9Q^WX_{H6abkg8Xz6c7Vm3WF8jAb~ zDkVQ4zowK~q(zH=K&^BkYTzuYL=T`A@CqtpM^QEY8ZDkdT{nQiX`1nPp6czcafrn6moJ6X2;U;BdFgGiuqfmi0p=y37>i#vT(&kZt?Ziqw z{~ypGICBg&Q168kM9M5WUW2@w%mn0RXQrbjxD_?<5>&uz(PB3$LvNw(KZ5+4zw@E- zOG$^WuanGgCeqM_^MeyEYJ&BsUHnYoLDWD$pav*sc@Z3jVO)UP#A{IJccU`<9(LpB zNYadF*-!W&=1OQBq@hi87-!-c)aIB(p0vxOs7b_&B z081JCXPkgq=y(R-h;y$b|1C8B$^q4GB+Jla)QE~aiM+f_7wUzx37fG8qo}u)UOILO3v9%G%r*yGQ2}p5y$4=JE$lCN4gNDYU&BeAkL37NP3}O|B#T|R2OS*AvbD(` z!W-~KR4Gm)mzZ;?mDKT#bRkgGb?Z<8zk?I;6kd$AM4?UCfRiydnMON}F5H8E!*lpB zJ4v-$&qo`+iML`kg;gq-25!LX=|7Ki@hj9qt|d(tm!KB70hi(~ybdeJ1kZmnjXDk_ z12>>*{{m_SZ{q|!iFzuk#}*umdj4mj#YM>eGHX#Q-iaFb1S)`^k!qQ0X6FGiO?ct^ zKSqOnVRASRci~F>2`RtnpwN@>F}x8!#U`v^*_v>^4Dls{3+ZniU)U49sO!cNy)w2E zm6KxHGhteX8amwVFOX$ zjvh|Mk5GY>vkGm>5tvhrr_f-r#>FAH18eXt)Ta6ZU&0|b6*kWS)Bwk^Kb{E;v7=Oy zO02~Od>5Os3QHJFB2|h+;X)EwqrHkAN02)`_Sxf%JJU|jk6rk?rfh{c+niiijXn?#alSu zkGyJc+>Q$1Mbu+-3KiIYa1)N2K{@bkWDREO&4vFJ*W#D-_h243FsK$@J7H$wRe2j; z&Vl=I2R@5;;S36CaVOT}0TniuNaR21SC#*^tm2NgSbXVXFWu=T953Rgv&r^kJeEzS z-AH>Xz0&K%STfb=Igv~@mhH@#wpb$SImZ>T)op`kJnH@3|2zwGN4mHl?4(@XgiD?STF{?B-e9oO-a@o2(HIaw#F@H#S4 z$Me#jUpydOG|*YaJQ)Q^#L;#|?}rEVUX(vlRa(Z##&g;|C$l8Kx$jVa_>j6j`Il=( z`9IbCvB)p8b)k|OMC~N08L=Q;6CFO#;}Sjw)p@$|B^jd9M2JJxY+DrOhVn{Ap?$z@45KQuhk zzxV3XMR|YfB!BV9`NjFsP2>EY(I+qNAI$vQ`1$=;Toz8-6-ko)yF>fkH=HZUpJTj4 MQ=ig&Ys<7>0ZyIg@&Et; diff --git a/tabcmd/locales/fr/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/fr/LC_MESSAGES/tabcmd.mo index 70813c3563967e07d984ae432b60a78401fee473..7fb39edf7958bbfde5e383f8ae663230d7ec43b2 100644 GIT binary patch delta 3681 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 R=L{cT{zZrQmFW{b=5HlF(8T}% diff --git a/tabcmd/locales/ga/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/ga/LC_MESSAGES/tabcmd.mo index ecc45de222bfa7f4ac973c88497140198c7e605d..be01907c551b0281acad4361fcf4566c9606fddc 100644 GIT binary patch delta 3662 zcmZwIe{dA#9mnw}0g_4tghVk3ktc)*Shzrh2uZ30k3b_9E!LR;l3sGl-AymI=j|RC z{yBl-0PSe&jmnRqWl~$|ly=mUu`?O8bZDtRm{O}%2Bpqai>2a^!qkr1AMelIPOP0W zmwW9#yZe2<-|zF>a&%AG3wz6QXQzyP$nbB3ziIqk8mF_*{=HabOdY2uaT30Rjrb3| z66-EE<{F%j7FXj`Ob7178qN>mWITd;?gTa%lQVB{U>W!XzlhT&lmf2A8Jx#Z*LNci zn4jPjd=97M>!=An#p`hL#8M%hXgTjiJ+~zo--E?Ou#x%AiQtC6paNG?<{Yd;B^1U+ zOygDfAZnr?SbIgucGUPfRQw@S;@`#^<~N5qQ0tGOGCPZ!@B`EX zQy83$&1mr}s3mbxt==7+4!1Rz8Pn;kn?vl7}tl&8R)ojVdUCivI{I&Y|G^II8ehuB89U@B#xZ zSV4ItXWCFL??LSa5BZn_d@0aVfv3=N{w|V>nLtz}J_FT>F4RQp0zI^xe+yOUVfCM9 z%?Z@noJCboPF8AFGips2qQx-kJ_q@jU3_URhlBBF1OJ4Lj8~F{#W|?X^#sO|)it^M zIABh*HyC&VmDzE$_(xPA4Hhdytz{P~!PTgQHlRAU4VBQt$fKqZcoNCUoC{PNNhoJ( zIiUOI22>|*MYS@DN^A(V*55@1cmlPi&!ZAMgKTi~Pb9XP#O#W59cmABp%QVCTg)!h z{XfJy{r;ckK$X3Oyo1b#$jiDZ6PQfnvvDf(n`InmcdrfHiri}sp>F&&hVV_CiIZrDDqD!UuOIoCd->7?2ZQku zR3fK?@efe(s%&YgnlZPZfh8QMm9O9p_z|wg76#{I4wvBLsE(XR1ukc&sx!5yC2K{s zb_Hrldr|RRyb&M6*YMA%f}d@o|64S`4hmz0@`!DIgl+g6RE7V>#n`}W>`uHBSK&`l zZ?=#5qBvuTstzp0>v0{bQxBkCXh(4ozKy!SeparumK|Kx4eR;33BQTzz;XN%*6=b= zLSIEyvT)(@|tBeO?QriP7WO0jKjDV>-e8X zm1R+h>_@fwuebom&MhU}hDvw?s^agV5`77k&}Eb_n^6gO;SRhP_h7Dqouz~Cqt<$H zi!pa#0$1TPNViNC7iBA|g}rzSK7dN#3@*faveWzEHta(mRmjVz4u6a+znRfmOe|-X zbD+#4r~nV4Zg?KG7cSs*oWxCZ&$OTd^`bWARwSkwL5mlVCYp%^Q=BEJ1lA*=%r@-C zBe+w)|4%tkMLYP?+KnJF%%#BD^pmce_4qpO#0WYpPR?nnK6 zU&RDgaC02fxQ6-7X%243dF+%J=1`TM!!Ddm6a`*`44ENZhEJiE=p+0xhS^A3qWiHP zk05iJ-v#62*~#ozvmkIS=J+$)`FaG8p(g5Or_^B%r{ccg{7KZ3oJ5w}ypQ|P-c(9> z1o@?!w^5sL{L<2H??M$cg#Gvfbg`m~{@>5RCdyQc#!(jCGz;-6Ork2@j!NtqR0VHg zFHX3*w8k;ieY>#&e}&WWMN}d0qL#Fh-)a+fpbF><=SqRT!@ybw&LbP%EMLz18?(3) z7qeq-#jQvz^IM#W)hkLZUX0r~zZ*B>CDhU-d6-|K`58WqXOJmPp7m0Ps=wY-ny?42 zVPFdm<6gWAJNb=G#(h|ghfz!N3uM{NYpBX<*a&PRvl6vL+wfX^G{3j1rOXt5*^CqN{D`0R@|QI%ni_NbkaG`_ zyo8$yrCh&nqnk}d3vCUV@?0rwI+F36OemIg`y2>to1WR-FTd}962 z^|+HvhX&li!AL6l|1L!xZy=LM>⪙bBgm8pG+oF-xwYLY}iXX1Bs1^fx=_co68D+ zx2MJw&dn?zTeyDCfiZ=ud2Qng+1941%KYzbQ~uQ4j*4!Y;6(Gi;m&+l>!QNL z;SH6!6~)@@Xl?B#82PVy+Rdtj=UaE9y`$J|J6g?}xVza7I-VDaIX1~niBxR#s-<9C z>Ya6q^?7rxA9qsLxhLV9ZZ~5Gm$(_ns!r=U9Qc0PYj0_x`$;E~4aE|EJlh{ixEf)Z kTZ#`AZ+G(t!!`9b>p6B->7wln(z?;ki_Z$>FW%DgA8K7A?f?J) delta 3419 zcmaLYd2EzL7{~Exp-@>sE}=lNZ)v#%mb;wNN|(D(4M&8i>$ZJMSC(D6M_U_pg{WyX zB4N2J;sF5??j;e47)3=JxkN;w#3)2X3`8kWtCINrv9IwTW7FJ4O1{^^xbODvYPpAxBM5V|rRDk#Lp@mCP0{+IFb*QY&xoBk0QHZf;b7ELBhz0I12k?DXR1}sEV$2`iIb>fA%iwuS3<$1!dNqG_-&RHPHg6UyTZ2 zB`VO(I1H0WQsy#h3w}eL5sSez%`DVBKF24~qQ41=*&K<}P~@jkCAp6LnoL%a7FswC zmFXhX#8K21y?{z!AF5(!QG5I?TKEfUTo)#%X$nwVT8g?J4>*n0s7McCE}lUPZ=g!p zmDS}4)E<|j0xWa-^~kSzkq--ZqULErCHNC+YumAi0?k4Kh?@a4RKnS)$h^p!<_X93 zsL1y_HlYH!gzVbwhg6a7JgkxqMFlnmwdadb&)1{2Hh~Ik8+O+B|2PeTGZ#=3HQ&BL zq^zRr*~q)ej745{#)Deme$>QOsDM|Xg^j2Ry^VVQH1cb%@}c=NDTl`ANY*#wX=va) z=SC2cj9(Ogs!$%H7@ftB3_iY^Dko z_-fQbXHXSzk-h>iLXuO1VcthY`aNo3Iv<*-0Cj&U z>a4879Ndpo)to`i^9SnC_T?egG7q7JtB|6aeW-c9#D4nz-=e`_TnfWz;%%aB(6|LO1K1IvRf5fmPU< z4V;Jd_yC^5C1~-KGHt;7@E~g9o2WhS#Q~av3sBE(L~YqsoQY#O$=bRV*c*3ZoS@7H z&W&5hK{olE3^@n6WNL5&9zrcxQkXi$VeCQwMW??VwKeY|$G}{|&Dgal75HA{n`|y1 zMKiaGs6SORqe(}Z1+Wa)V+dPt75Yg_Z@S-6;|CO{UPNA0<~69m4xj@60vDmalR2;$ zdtp81;W|`+Z+YUWfmgY(m72fhtFlq;*-st1Y?c{H+=Ct;q<(ewu4*Hha$*(#D z(_Q~uiG>5n@tw}4x&LFnC3esb`^&u*cEFC>UWKKXAe#Op8Z&Q(q-kRwV6iLc15DH zI6pZo_e`sHWg|w78+zrnofGCJa;Ik9v`Rc*JTdvokb;avThEx}=Og;I%drbakNlr`5=-6g~8n^;*g^Kaef-S_|i diff --git a/tabcmd/locales/it/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/it/LC_MESSAGES/tabcmd.mo index 50a2e9f7482bc20238163ed7a34ee46fb1377735..81bf77442ad08df8ef4e9919fbe0bd999d22b69f 100644 GIT binary patch delta 3678 zcmZwI3vg7`9mnyL015ABASNbq6A}YTHiSpO5P?8Q1cF$_nTo}9lfB7qyV<+$y-Pu7 zx-2vv$;_ z>cJav2i}bRs8qg&O5x?8UdUjJdNu0Kw4yc?L(RVjHP4}-ej2s$ccznnML0x59Tu=X zf-?=Ml&?kI1sD04eSB%6qk(UtMg31mEM@|;D)35FCYn(T-5=FcP#E+7}&{1chmOk#D-a|`MYG@}A>kS=Bys{cVO z)${*T3fkE@=XJawzo0SxFyEg@HNBWvWsE)5-2rpq3P9h!J*+NvmPUK_0#g`V?AGDu91#&)U zzlxe~oRvGOYRvqAhTAA8HSb^(Uc)sQBENbAZpFoT0G0ZSI2MOc8T%AR<0x*f{`;7S z%1jYzTn(sJc(NH8ZN+mUSloTfNSw#WNq_G z;1yIrV@anLC=OhN%EUdm2)~9T)9k-FlS{=(8Wz&<7Ouq6y#DUSb$B~Ik4pJPY{ap= z9PYr?*pA;tU8)PHj18flh7w*L4OokMj3YQ7JygbD$WT~8;S}z`k-UE+_#~>sRn(D; zXI5om4U!DA35nHYa0UJlbw@7aQk*e2cZu&t1@Ztc!xW})0FPj%if#Rl!f%nW<_NQF z#z9nyn|LiMm5&D=M5XW)uE)Ql&bVz}?oF9N1$Y>v_#5OwGK)D+UCxcDBX)4Ip8v-v zXuxjN4o(Ce{)l=HC|}yabkvJx4(jaIV;jbii*1gg9@DpRISwHUm|8B5&VCt^WOFZW zzzlv{&;J_~6u?F)0w*QLF*n>+r*jC(+TJSO|kQ&mVyV8bC zZPKXmhw(u?7ud4Im@d{g$0_L2mEOk9#LcL)eHGRI5t0lupPNT4CW3e3Uepm?L1khA z(d*7Uj3u}Sb$13(fqjU2S|-xSJvQ?&Lonvc6b|6`a0V{o%1^^~EXJ?mB;1M`xDzY! zC-^eHg*W1EZde%}LEWV{0vXQ=r1S#hbYWnksELUb-47q#PuuoRy|9mO$JpdTRj(u`V_VK)>SR?!(xq84my zAx(M4bmNz(r&s6B{0!2?)N$}d=%Vh*PW%r347Xz|=b_BLidsl7i(f!J?!}K$?N5c* zy*g9mxXZIL$iL&K^gui8+Bv^(R<~)7tx<8yE5` z#($jWhbDI9`-di<&hz`HmgW2J7JWF5{}0r z9bHbsvlCv2w=HQKFWlMH8wurlN9?%m*`c14lTLo|m+o*Zo=({z*9&`Tw{J}OqT(Lg z3)x$l$&ERQP{Q##x}9_);y0A1@-w+sxD^Vrv zE|0nX=l7@`Plmdj-rjH`^8bE{*lt%Umek1MiRWb3&5n%65?>!~|Ge2v+Fh~kSeO68 z%&I*96YJCn|MjYUBmC!Q-#5aaKliRt{_*;OG5*n|wS|4>tXX}3yLD+nD@m{;eV0}? z^*vI*&@Wq6H9FIl-J3PMw?-4R{MUXOO?%AsET`Mrn9a5|yqkNX&Nr-H+jYY|wiTyS zEYUOktHp+`T<$CibY1g)Da(%6)Aj)M{ZL}uTw|w2o{{WnnDO>;m delta 3402 zcmZwI3v5$W9LMp~!Ejg}gN<2WvJ2D+!YWUByE%q{C{vL{h&a3TZWKy8Zab9)tPp}k zgA(h5IYFaFXM$1eK!PFy5k-yYKx1SvfyE#$lF3xW_xES7F)=P(Kj+-X`Tx)VoO`=x zn`ix#p5#{pdp>UX+sl6;|Lxi8{rmT{*O(!6w_|_Yi{tQpyb>?sa2(#pm_axjhv5>m z@Ls$On_PEcKK-LOz?h`@iiU3N!ppH&-^`7E97BH&YQWVv0=K&7)2QoD;6OZ&N;JEl zF-2&h0+r!axEM9T`_aPfcscRS+wO^WBqMVXH6t(6Re%yyfCV@j??ffG8FgJN7U1Wo zK;NMceYwVvlNpa1rxYz*hD!84^@(qq+za-h0)2s+K__YkUb0f=7AnD6d?|1x>iX3< z3OAyKub={aj4J(Ex9{O%ax;af`^REZnJ=U<8Ew1*n{gx_L=yy4!yjwY2T-@$;_P%xW;lefiX1Gn~MeN;Vf&vb&H~Fj1U|>re^oLyBVB zPy=_MW_l5|NAfv16enUaR-#IOH>#qKx&2nO=(i1`{@PUC9MH@Pn1%vWpaxp(_E)14 z*nmp(DZCof$fQgMY6&i&_K3yBG|dgDae}Vv(4yaj4K(n1TT zqGq}THE;s8L=T}R@G7ceZKyRqg%*B~x^5tY(=^whmb4OeJQ;BtTTz*|;&A)~Ej*7Z zVII-tc+?tKq7tlj`;ExQY~jno7SuRLQ4{P$E$yWQQKCL1fuy;ThDx{qm01-M)2wxU z0+sn|u18UcoJLmd@0(PS0&Z4GOHhfGqt<*W>i$O5(xy;}y?_Js{2!q~a^?%vK;3_z zAXB2~cp>s`GEDUZ~4;rIg~@!7fIrqX*6`< zBKJfT6<{-J7eDLz9%`U-r~&#gy#N-Y9~YxGaU<${3#zh*uo*u`ie?-u^He>I$zB}z zkcKu<8_vfcQE#|X>NEvcqDuEXUW%=#iX6Zk{0Q~4@fm97CsFsE!#P;Y;D2HSHGv2V zuY?{PN&OYDnDn)l^*A0kAulbn7ZvyeoQNl}0{iopx&d!PVwh)K526z5Kn3b@?ME3^ z`ms0}XCp;4Yl@SZ5^dwaBo6Gvay*5%qMz5>EW8I*+TA!EJ8&Kj>+nY0fl9m+ zRl(m;OE8+3g07o|dOQ<20XHUTXieV0GCYo(FrW8p2%AyoHQG!lL{(%7QVg>K$<;i9 zWw-~G;04s)m@w9u85l$_Zbv2bGG2?x{WKgJzv1f`W13&$2<~PK^E)oX(Y)?d@&{0* zZFB8Hm9*bDV{XC<)Y?Ca%*A|wO7sU@j)PfWE#(H}K}(uFG_?DVqxQf#%*CG6M>h^a z&15?2d>HkdCs8wb4D}{!L9OXWI2SvS4R0nAO^@j!EX4?F{GC{!=YKy9WqJZD@ekaH z3)v7#;5e#;KO>Vhlb9}Jn-H?R=1Kes-@!V3gN42xiztHv)}j)55w(YoAR){TIEwgY z)Kq@M@fO#^SVMpEw9IB~!WHz}QENSy1=c2wBE>ShklaiME<_*M-iURmC2K`h#kduf}^E(5C9fRXCuGi!g~T_y_8`m#)ifw&U2Fes9X4<9W!V zZc5QY8<*lHEW%Sb1U<7ei4-FH(M+A4*zhzElZGS;7 zG1V+^KE8hbY9q0GoCXYr zBMpunh$n)HhPbH?hN>KUb%PyG1VVO#lYwyb?qDPws;Y@a6LvIFl~_}6r!RQBJ$+*J z8Y&)$23OhXlYOIE-hYlXIFaM{OruQx&o!cEYYwdiC)t+i_w}I(wi$ zE6=`{IB_LO##Ob-KJCx%?wLB~%keU@aqL>hjxSGb?K?C*dT>#X)Xu_^^v{JWveI5_ zq^H;QWNn91`Q`p}VEEx40~W?s*%r~PdM8$AClaxAmw!i(Ve=zaRA*{}(O5KG6SL}V z$BG0kCmfGC@znkjUwX@^r9Dzp%V(s|jJY{GwYz*$ddm1+y>tJ2XR3YN(5#r1+EY@L VJ~KUVNve@s&X)UgQa>)Z?l031)`b87 diff --git a/tabcmd/locales/ja/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/ja/LC_MESSAGES/tabcmd.mo index ce6b79faf1cfce2aa07564598b25344c194db3db..8551145560074a86171d8b8a577d3379b00d78b9 100644 GIT binary patch delta 3730 zcmZwJ4{X%s9mnyfth6A-QVNt-3cnUhTPar@SVw6QYAGEQTd{18iruyMqj$5tJMXSc zaND)Oj8%+-!%bqyNM#DxfOuORFrpYDbF;>6V#YQ*Vn@mnaX3db>d4-o{(jcEtSR?; zp6B=c`99z0xnH?(IOU0fl=#rp)F%u-C;2yvfBzb<+7~~sPckNt>L6y~B`m;?aXRK@ z7&99eqJ@o^gJIW)aT@jgn1#PUjXR6^#>CA#6f7D(#rZgEvNK^l7Ez$2Mp+)^$sBv4}_9t;P5G-JQbJp$fchtn`Y_k;ePyy9q0fun~ z?nW(i2xsD3$lB&IYN1cj!rUp2m8kX>)cjjef$zj=tZxP=DD|gNkqx01yn-4qmBtb* zLJRLe9Z3k4>POsqKeDztg&IGI3iuybglSAR7t2uVHDO#6bWvD;iN!dCnjkIP z2_zp?Ux+%xRc`x6*D&VNz8!TW`%oEr0hOWOB1tyqu?jC`lYcg8is-EgD^NRYKwYX1 zya6A=dDxFiElCZ+3&W$t#4e>MU2F0&GMDbT2A%+fV`h7#V7ku7gNSX2ex#B%rv-r9k%0 zt*A__MWxb*3TzANtaqU%IEFgY-=G5fGjhSrzmd63CaY_nn^1RP1uBpb(#7(2_Msm?LG8Gm zw-vFOAD{x+k1H^V6?hdFVj17*2Hc2x(H$;{J7;^224&!VT#MO!>D$nY>+lR#VGg6T zqh{2O_Mig4h`PkJ{J~J*8&HA&6j$R(RA6akPGGsnX_%Hc1ugI(ZpSz9Wn4#oG+{Oe zAHY)71PNS<&!7T+7Zq?iZ?9@xk9S}KHU3>xzmIVl=97k_cn@B}c*f1VbSPZKTkx%~ z8nXkhTSS`haU^(i*)?~u({Ckip#4GAop~F-hVz+(Y#SeL!JVk1N#h*UUV_A8nvlB_ zH$4>CwAq7|cp9hSXQ;Eyy4Cr*l_9a2wWt?P4-%6(fC})BsGX(~g)&r%v(ZMi@4zK^ z2z6w`_`1ITHMAB*lAh~Z$0ZL=Ig^s?pE_$?^6hEwrzS7t-u#t&>_{XO@w9n;tr7eFZaW(D z1cIBrodI8Kdng#QgR$1wmauJN-nRBGpU3I#vpemW?dgbwy2D?7vdJ6h?2g!;Xv`by zjwUAN-;&c|$2|4}%oGiTf}UU~*19Rw9rPtD@+0Ho&a`1~Bx*-I9i5>zZ>Oit_Io!6 zLXoID^_QO#RnTsaG1vb*_uHLePkX4V%Nz9lzeheh+8zmnHF9j?($RHCM|K8+_l>oG z(Hsri?SV~!_T;m3R;DCp6kJM8=FNLPH8H+AD>1leLZZ5;I@vUTW@_^3@?&Yqhbu}l z5=WQLO*9tGN-nwm)d`6Yt+@$*`O>sH5@Y)k*Va@enkyEMAL%=O@kru>y4>X54FeP6 zt44QjjqSYBxGn#++e*_Ah{mkYCaZb$EUdA;-sKN{*XpvPQE!KBb==7$kq*nr ztQ8vFeYDJq`R$-(KM;tSx=_UGiiaY$r7T!cn?fuWj#ieJb2go}x7*VZi21wQJb{o} zxCG^+gGRfDf|l3%JVl=`X>~_!tJvvlRk=|vvljS^J?7$%9=q80*zmU9BZp3m>^(Ky P``mEv-r+>v`nvxB8uU<2 delta 3425 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 TJH|Q}<~t7-?&}!Koj&R>VHW0% diff --git a/tabcmd/locales/pt/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/pt/LC_MESSAGES/tabcmd.mo index fa709d8ac9072a1809f9e6a84bcb9f9820df907a..6bfc41ef43a06090a8ccecfb9f466e6bdc52a870 100644 GIT binary patch delta 3682 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 diff --git a/tabcmd/locales/sv/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/sv/LC_MESSAGES/tabcmd.mo index e329a6a88f01698761fa98826e41d1f6b652ae22..0fa1d20a14bff8ec143200625ac794e0373ba9d3 100644 GIT binary patch delta 3712 zcmZwI4Qy5A9mnyfEo(Js_oQ2822XQ*(r*R6tjOup`D~(B;H@LA>ypK2Fywd!DYj8g09@O*QNC)#A zPQ~MR4gL-_!N0Kvr%cWV(u9`s7f}7~4(h*+xrJbe`OUGQ!JknBmk{PctUxWO6+@WB znfM55qVMBud>xtFyp5XZeY7~IJZ}T4elu$P?Wl!6gwvVdJkO0%{|ahlqo@hrL3Nl) z<@H#F7H>ywiHl10?w~w~%x#XK`kzEC`0rSS#SFFp7op}`hiMJa$IW8A9~D^^t8o-H zK=HKvLMl<^I@BKC64c)rn8Z2M-;3Ikr%)L>gv!vbkR+QCY{K)?$Ui}vDq3s6WvIy7 zQHQDr=i&o+9S)*Wc^s9(b3wU;&X#f&>ddsE0*ax=e+)Iw!Js^Z3Ve75`PT~HrlJ;$ z36I5@WvG~_XhAK+MVgph zsP+f2Lht{NxKU)Mk#CT>fPCD{ho}WiV40eDK5C#ww0H+9g#)OGcOk!KKNl_FXQ=ig zf&W0YD`hg(Uyrkx-`vEFPWKlB??Kv{gQ$kTz%agv*J2szP-OL}cAdzt*}+8mE9N)wBxP<)b^xud2F1&yfa0K;l;;o?k7c8NC z5exAH9FIE5`tVG^I;_JtFoMeH>ILL~D>s{%y%A5L_I?s!D$)=a?Nu8pLz}Q2)A%_& zfr@Y@vydFK7)P&!`3j8r@LBpv2<5%bVRjnrf8fXm_>u^7+!$mxb z<)7hI#51T5PZ@dE0=HlpK8iXc`|&}14F_=3P5Hfl4K?0GPTUXhI^2n`qxyaR%dPno z?Zm68ID~`v8@v_#+H7djy9L?s z&`VP_j7 zc`-K;PPl$Yx7(lS$}X!+6{hpUCL<}&NrijjZf7JO?sR%112H${1w()GDXU62QJ=B? z=XtLaPllszUtc8A_5U7qIbJjsORDGCzzcKp=6c3siLZ>+e_ZV)ooK8(7R^3;O?5%` zvYkQj`PS{W8*`brW07s>b+_6+$MYgR zj*ZhOmgpIKY6;%v^Jv{%tllE)_c{sdY>WA(%}v?9w3~9QVz!>cjqfMDhT2+oBJM=` z!#y#-x4$zSb5+BZ*XBCqn!5=bu^%PmqbY5_=h*6eTiX<@ve_=~tqz++q%Yz4w)^J? SQZ~k6vICJ+#AZs{LjM8YkTYrk delta 3411 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 delta 3412 zcmZwIdr*{B7{~Dg32C4ppb-+h24PyPqA3Dh&`O;&DT_+G&?+yoMZ3DYq?xu>WX_Nc z)21e5lVy`NUC6am>}nS;O<81(O^!_&X;P-5Q~uG^_s5>5nI>o5&pGdV-t#=?IcHy< zXi9kV!Gy?xj6N$3|F-Zym;d{d)cg0}Qz^#e&|QmZxCKYyP8^1(aVQQ=HD(ZAfq6I+ z9bAZK;~MMhm_>gV4m2iW4$#np$M9TC?jL`!5HF;E6>7rycpkRc`!Uq`y_kU~P>CiD zFeV=zRG>;6fz_x5E=32|;<>~(@7Nprk&Mh~)QVDAt^yRJ0!+o>csnYw<*0F+a4>$2 z3iK;xV|KbRiLT>qRgk$7>h2Rk8N0h@1p{Dp(aYqq#BrmTIm$j z83>`~X+wsZjkf<0YHRn~>nE&9tm+)DXJ=7=t#CA7D%oUI$?iaQ!T9kKT!KoV11XB> zL`{4UwbIk5Gm^!{Avgw$unJZBdr%d9!uB_zL%(wn_1B^5;eu8+m}Mxym8glTZGS#0 zfd^5Ew&F;PA&W8xQCsjk>WnxHrfIH5&EvH$L5KbtBxlnRp`pz8ph|KS`Ix>$kq$aI z9<|b$sENaEMf>f60!6GcgYSbZKgu1^0RoU&>hF_y97IfmT@Z}gu=E7DQ zIz;bbB_2c#ETm4vGUcd4H52=x&l*BKzr@;PuQ#K91uJnhzKgHp3DkV+308+6oJak~ z&=|o;iaxx6jn@Q^BgvXKQKkG5_17=8 zByK6bMSnISJ)Rk|SR z!ELBgA4F9ypZYskfx3Sy62mm17PJ}1<8d5>Blzj0VkPSQnQLgM0*$B%TdXf52iWYy zMfe+Pf;&s&Ki8$GL)C%aXKEr zo3NPl{rE8I{z=sM^m1d)#4=QZ6Hsr)dQ8J*sMEa)dFh$2Fb7Xz2BufUx1yke`fJao zaN!bMiP_kRA7VH1G40$`DStujVbP`WKvPi_yafaJ5YE6J9E+34Lh~)g<+u{>!@SGm z6=*G`!Fe<9VICgCXECjkGT}z-&6wAoEYR!ETU#6bVnBBEa7y~kv%Pcfni~u>`fJ^w z#~%p$>U?v&VPC-SscQ%<2sWbE*U%VrJ)y8S+!!*my|pz#cYdQA3VUkZFgHCu|2^IY zUv15tfIsZ|!!_Z1=ee=pQ+g6o1M@Uh$m93k?Z&?C?@vtm=SpL+Ay$^YH^K8i^UZbr zZqPTUrq*q6!)}ezn-{8agTX*BmXuMLnCUJgPDn|TaZR1Fj}>P1^oj1t?wi8QCg|1$ z-B5kBrT>uF@N@Fdh`yX#9Qz~p&cs-XGcX}}5?Q;o(US7QSnkmF#I&E+Z9UZX>Y>)A zu9g*tUTBYfQqY|^yz7yULyxt0J+;B9o-xh&amy>*Km111PaQ34ywG&`(TD8tH;T3= zMyty!Vhe_s_K9|uUmSb2c)|b3PdD2KGsos9MOT+sJeORN96dUr F=x-1P;p+eZ diff --git a/tests/commands/test_user_utils.py b/tests/commands/test_user_utils.py index 9c584c60..d765dc03 100644 --- a/tests/commands/test_user_utils.py +++ b/tests/commands/test_user_utils.py @@ -161,9 +161,10 @@ def test_local_auth_maps_to_server_default(self): with self.assertLogs("tabcmd.commands.user.user_data", level="WARNING") as logs: user = data.to_tsc_user() assert user.auth_setting == TSC.UserItem.Auth.ServerDefault, user.auth_setting - # In tests the gettext catalog isn't loaded, so `_()` returns the raw key; - # we just assert the localize key made it to the log record. - assert any("local_auth_remapped" in msg for msg in logs.output), logs.output + # Assert the warning names the affected user so the operator can act on + # it. Avoid coupling to the localize key -- once the .mo catalog carries + # this msgid, `_()` returns the translated text instead of the key. + assert any("username" in msg for msg in logs.output), logs.output def test_local_auth_case_insensitive(self): data = Userdata() From a1d2f48005ab5ff030c7abc6731711304cdbe8ab Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Thu, 15 Oct 2026 16:56:27 -0700 Subject: [PATCH 4/6] Route Local auth remap warning through the command logger The warning was emitted on the module logger, so it only reached tabcmd.log and not the console. normalize_auth_setting, to_tsc_user, _parse_line and get_users_from_file now take an optional logger; both create commands pass theirs. Callers that pass none keep the module logger. Co-Authored-By: Claude Code --- tabcmd/commands/user/create_site_users.py | 2 +- tabcmd/commands/user/create_users_command.py | 2 +- tabcmd/commands/user/user_data.py | 16 +++++++++------- tests/commands/test_user_utils.py | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/tabcmd/commands/user/create_site_users.py b/tabcmd/commands/user/create_site_users.py index 7338c623..1bfda61f 100644 --- a/tabcmd/commands/user/create_site_users.py +++ b/tabcmd/commands/user/create_site_users.py @@ -49,7 +49,7 @@ def run_command(cls, args): if args.role: user_obj.site_role = args.role # tsc is case sensitive if args.auth_type: - user_obj.auth_setting = UserCommand.normalize_auth_setting(args.auth_type, user_obj.name) + user_obj.auth_setting = UserCommand.normalize_auth_setting(args.auth_type, user_obj.name, logger) number_of_users_listed += 1 result = server.users.add(user_obj) logger.info(_("common.output.succeeded").format(user_obj.name)) diff --git a/tabcmd/commands/user/create_users_command.py b/tabcmd/commands/user/create_users_command.py index 7790f4ec..1e825dfe 100644 --- a/tabcmd/commands/user/create_users_command.py +++ b/tabcmd/commands/user/create_users_command.py @@ -57,7 +57,7 @@ def run_command(cls, args): if args.role: user_obj.site_role = args.role if args.auth_type: - user_obj.auth_setting = UserCommand.normalize_auth_setting(args.auth_type, user_obj.name) + user_obj.auth_setting = UserCommand.normalize_auth_setting(args.auth_type, user_obj.name, logger) server.users.add(user_obj) logger.info(_("common.output.succeeded").format(user_obj.name)) number_of_users_added += 1 diff --git a/tabcmd/commands/user/user_data.py b/tabcmd/commands/user/user_data.py index cd7ec38e..99cbbe79 100644 --- a/tabcmd/commands/user/user_data.py +++ b/tabcmd/commands/user/user_data.py @@ -41,11 +41,11 @@ def populate(self, values: List[str]) -> None: if n_values >= 8: self.auth = values[7] - def to_tsc_user(self) -> TSC.UserItem: + def to_tsc_user(self, logger: Optional[logging.Logger] = None) -> TSC.UserItem: site_role = UserCommand.evaluate_site_role(self.license_level, self.admin_level, self.publisher) if not site_role: raise AttributeError(_("tabcmd.user.error.site_role_required")) - auth = UserCommand.normalize_auth_setting(self.auth, self.name) + auth = UserCommand.normalize_auth_setting(self.auth, self.name, logger) user = TSC.UserItem(self.name, site_role, auth) user.email = self.email user.fullname = self.fullname @@ -111,9 +111,11 @@ class UserCommand(Server): # authored for Classic import without crashing, and log a warning so the # operator sees the remap happened. @staticmethod - def normalize_auth_setting(auth, username: Optional[str] = None): + def normalize_auth_setting(auth, username: Optional[str] = None, logger: Optional[logging.Logger] = None): if isinstance(auth, str) and auth.lower() == "local": - logging.getLogger(__name__).warning( + # Use the command's logger when given: it carries the console handlers, + # so the operator sees the warning on stderr, not only in tabcmd.log. + (logger or logging.getLogger(__name__)).warning( _("tabcmd.user.warning.local_auth_remapped").format(username) ) return TSC.UserItem.Auth.ServerDefault @@ -217,14 +219,14 @@ def get_users_from_file(csv_file: io.TextIOWrapper, logger=None) -> List[TSC.Use if logger: logger.debug("> {}".format(line)) while line: - user: Optional[TSC.UserItem] = UserCommand._parse_line(line) + user: Optional[TSC.UserItem] = UserCommand._parse_line(line, logger) if user: user_list.append(user) line = csv_file.readline() return user_list @staticmethod - def _parse_line(line: str) -> Optional[TSC.UserItem]: + def _parse_line(line: str, logger: Optional[logging.Logger] = None) -> Optional[TSC.UserItem]: if line is None or line is False or line == "\n" or line == "": return None line = line.strip() @@ -232,7 +234,7 @@ def _parse_line(line: str) -> Optional[TSC.UserItem]: data = Userdata() values: List[str] = list(map(str.strip, line_parts)) data.populate(values) - return data.to_tsc_user() + return data.to_tsc_user(logger) # https://help.tableau.com/current/server/en-us/csvguidelines.htm#settings_and_site_roles @staticmethod diff --git a/tests/commands/test_user_utils.py b/tests/commands/test_user_utils.py index d765dc03..ee4b769f 100644 --- a/tests/commands/test_user_utils.py +++ b/tests/commands/test_user_utils.py @@ -1,3 +1,4 @@ +import logging import unittest from unittest.mock import * from tabcmd.commands.user.user_data import UserCommand, Userdata @@ -179,3 +180,16 @@ def test_non_local_auth_passes_through(self): data.populate(["username", "pword", "fname", "creator", "none", "yes", "email", TSC.UserItem.Auth.SAML]) user = data.to_tsc_user() assert user.auth_setting == TSC.UserItem.Auth.SAML, user.auth_setting + + def test_local_auth_warning_uses_supplied_logger(self): + # The command logger carries the console handlers; the warning must go there. + data = Userdata() + data.populate(["username", "pword", "fname", "creator", "none", "yes", "email", "Local"]) + with self.assertLogs("tabcmd.test.command_logger", level="WARNING") as logs: + data.to_tsc_user(logging.getLogger("tabcmd.test.command_logger")) + assert any("username" in msg for msg in logs.output), logs.output + + def test_normalize_auth_setting_cli_value(self): + with self.assertLogs("tabcmd.test.command_logger", level="WARNING"): + result = UserCommand.normalize_auth_setting("Local", "bob", logging.getLogger("tabcmd.test.command_logger")) + assert result == TSC.UserItem.Auth.ServerDefault From 9889ae4ebd85595d14f526494d7b8cd5bb03106f Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Thu, 15 Oct 2026 17:05:44 -0700 Subject: [PATCH 5/6] Regenerate English catalog for the Local auth warning; unstub tests Recompiled the English .mo so it carries tabcmd.user.warning.local_auth_remapped, and restored the warning tests to assert on the real translated text instead of a stubbed translator. Co-Authored-By: Claude Code --- tabcmd/locales/en/LC_MESSAGES/tabcmd.mo | Bin 22763 -> 24529 bytes tests/commands/test_user_utils.py | 17 +++++++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/tabcmd/locales/en/LC_MESSAGES/tabcmd.mo b/tabcmd/locales/en/LC_MESSAGES/tabcmd.mo index 7238ce249e974c5f16f0720ce49c643d155464fa..5f5645a204d7848d5cbc3130de3456955981f331 100644 GIT binary patch delta 5880 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 delta 4168 zcmYM$3v|zA9LMqRM4Qz#|6Oe^ziIv>A*nXoq{WcSERD2KW~m&hB$tL?bXN-FbTM*t zx(w+s;)r6DYiC*3IZhYWg;iz%o$8}~YbWb23i;6W%!(>drY;28%=;7m-g!7yqVLbh<*c|In z_Z`JnX1-mb;W3cPw;eGTHQ^)J7GFXSS0FdoUJT(eY=ak33$$eL2JDJTWCVIR4Rzl; zZhRwhi`8Ko>)TOxLDRTk;tcG-1=*;8N-+)RVIri2~@z> z@uwYz@p>GHTJH(;HNjjOo$!5BX5V54UPe9^<{|~s1Jy4?=C+63_zdSfY{B?@NDj6I zRiRo`g&MFKp2u8_N~Zp7zvXk4CLD&!Y&_}=yo9Ov0fw;}RpMi)5=OF``Wfh9KI+Vj zMJ4nKYW@wVd3L$}uSg7brUmt9E{kE+E*M6((}tl+J`Qyj%2AnbK}~c3nZr)Hel&v~ z{V*!?zNiF7psp)JEx5?}F?#gB^l31M9YHM+!NzEhl2MuDqDuAvYHuf?hci*vy^Bg< zJMys~-1vECE6S6`cpiE<234`=o&Itf+S{$D2)}jx22@~|(Zd#`qb7>P&1#Ri+kI+GD6dFC!mI z^n&|uL+$loRDchno~GHT>lUICT8Emi22=I?AEBX$8gZ8{Xo=~Vi5ee)T5vLY__`ba z1a-)ERylK0~c{FoXK5fiO=dlDIO>eNh1&Zy7=t@dm90e& z&tQt4|HQ7rW6~9MLouonkD*F83vb2>yd7(hd#xFR3aBS4qlb`O>@`&2o3I)FjS4iP zTM&2$)K+C<4C`Ad4NWi+o8#L^QnnJu;rBQQ``i*_wh*-yAEQeBJ9>B;6>!3>LBKbm z<{g7ta1JV4^ zr0o>y{`BraW&7a}`uAfpuI^6#74c3667XkKgy&F?QzU7sbYax@1sIQ|7{Y0&H{ENP zf|aO4xdS~sg>e|l37(2DYM$Px0E%;{zn=f67+_6Xgk@NRJjym8H~2r`BvdJv;d0#R z`r~>8TQeW)7(awXxSldlM2n_eIxBgo1t+4;%&W-xwlzKt1#lP#Vxv0(2jiVa>9IE+ z!eUI%3o`bR?Y0Wk#QTv;>^RQGj@-hWPi%o1y@Cqop(^Va($E)EF%=g$SEC}| zgLmNtoP+s9qXoY~hU^&X@#@4gsP{k)s^15-RYiC`K8ka3F1EzfK3CqLzGczS=`6$> z@EOzv@8eLc#u7~C1kS=TOv6j4>)Z4TUOfF#^Aw>Lo{6on9F^D_RE59BNK7HCCVKwc z($Jx6j~-^CNud=tsRHlhyWzo>xX z25=Tw-@4GyV>1+0s)gv`M&z8?L2QSKgfDYZ3zuLD&PHAlwgfY9KT;e!k9ybV4GLa7 z!%?UIQS5*|`cX70X*9yM*cjJi6WoMX;Z{`UJ5Uv=M^)qks&eTC!GBJ>p(@Y|n__>A z#v(WV0LIcEi(#BnK>cHAEM-8MtU^^_Cl1C2Bniu*BVR_9z7F+%IEM->sW2$*Ex3Vx zKjaBEyE}L)a#4@t3{1jxScLoTrv55%8m}cyI0`%B415|ZaU8a}CzxOks`T5CwXGf% zKv!m)hy|$9ZbQx2fL$<}ldOI=F30WVY zAWxDFxHtI6WK5drZQ=P?d-r9@q<&u#fQ=yP1aebU)hY($ez!xa6vHu?0=a uzf0^;zAb5Bc~xAfsy4ZALRDVp4N>K9WIkDbB&(vTcTQ#7swtz2BmM*G1=U3W diff --git a/tests/commands/test_user_utils.py b/tests/commands/test_user_utils.py index 90405f74..ee4b769f 100644 --- a/tests/commands/test_user_utils.py +++ b/tests/commands/test_user_utils.py @@ -1,5 +1,4 @@ import logging -from unittest import mock import unittest from unittest.mock import * from tabcmd.commands.user.user_data import UserCommand, Userdata @@ -160,13 +159,12 @@ def test_local_auth_maps_to_server_default(self): # and log a warning so the operator sees the remap happened. data = Userdata() data.populate(["username", "pword", "fname", "creator", "none", "yes", "email", "Local"]) - # Stub the translator so the assertion does not depend on whether the - # compiled .mo catalog carries this msgid yet. - with mock.patch("tabcmd.commands.user.user_data._", side_effect=lambda key: key + " {0}"): - with self.assertLogs("tabcmd.commands.user.user_data", level="WARNING") as logs: - user = data.to_tsc_user() + with self.assertLogs("tabcmd.commands.user.user_data", level="WARNING") as logs: + user = data.to_tsc_user() assert user.auth_setting == TSC.UserItem.Auth.ServerDefault, user.auth_setting - # The warning must name the affected user so the operator can act on it. + # Assert the warning names the affected user so the operator can act on + # it. Avoid coupling to the localize key -- once the .mo catalog carries + # this msgid, `_()` returns the translated text instead of the key. assert any("username" in msg for msg in logs.output), logs.output def test_local_auth_case_insensitive(self): @@ -187,9 +185,8 @@ def test_local_auth_warning_uses_supplied_logger(self): # The command logger carries the console handlers; the warning must go there. data = Userdata() data.populate(["username", "pword", "fname", "creator", "none", "yes", "email", "Local"]) - with mock.patch("tabcmd.commands.user.user_data._", side_effect=lambda key: key + " {0}"): - with self.assertLogs("tabcmd.test.command_logger", level="WARNING") as logs: - data.to_tsc_user(logging.getLogger("tabcmd.test.command_logger")) + with self.assertLogs("tabcmd.test.command_logger", level="WARNING") as logs: + data.to_tsc_user(logging.getLogger("tabcmd.test.command_logger")) assert any("username" in msg for msg in logs.output), logs.output def test_normalize_auth_setting_cli_value(self): From 4bffe72cfb9b675b15f62c7e8142e8f03135b282 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Tue, 20 Oct 2026 16:29:19 -0700 Subject: [PATCH 6/6] Suppress CSV-time Local-auth warning when a CLI --auth-type override wins normalize_auth_setting/to_tsc_user/_parse_line/get_users_from_file now take a warn flag; create_users_command and create_site_users pass warn_on_local_remap=not args.auth_type when loading the CSV so the warning only fires for the value that actually takes effect, instead of describing a CSV-supplied "Local" that a --auth-type override is about to replace. Addresses Copilot finding on PR #446 (id 4300828647). Co-Authored-By: Claude Code --- tabcmd/commands/user/create_site_users.py | 7 +++- tabcmd/commands/user/create_users_command.py | 7 +++- tabcmd/commands/user/user_data.py | 34 +++++++++++++------- tests/commands/test_user_utils.py | 21 ++++++++++++ 4 files changed, 55 insertions(+), 14 deletions(-) diff --git a/tabcmd/commands/user/create_site_users.py b/tabcmd/commands/user/create_site_users.py index 1bfda61f..3c64e6b8 100644 --- a/tabcmd/commands/user/create_site_users.py +++ b/tabcmd/commands/user/create_site_users.py @@ -41,7 +41,12 @@ def run_command(cls, args): UserCommand.validate_file_for_import(args.filename, logger, detailed=True, strict=args.require_all_valid) logger.info(_("addusers.status").format(args.filename.name, creation_site)) - user_obj_list = UserCommand.get_users_from_file(args.filename, logger) + # A --auth-type override replaces any CSV-supplied auth value, so suppress + # the CSV-time "Local" remap warning when one is given -- it would describe + # a value that's about to be discarded. + user_obj_list = UserCommand.get_users_from_file( + args.filename, logger, warn_on_local_remap=not getattr(args, "auth_type", None) + ) logger.info(_("session.monitorjob.percent_complete").format(0)) error_list = [] for user_obj in user_obj_list: diff --git a/tabcmd/commands/user/create_users_command.py b/tabcmd/commands/user/create_users_command.py index 1e825dfe..1bce4528 100644 --- a/tabcmd/commands/user/create_users_command.py +++ b/tabcmd/commands/user/create_users_command.py @@ -48,7 +48,12 @@ def run_command(cls, args): UserCommand.validate_file_for_import(args.filename, logger, detailed=True, strict=args.require_all_valid) logger.info(_("createusers.status").format(args.filename.name)) - user_obj_list = UserCommand.get_users_from_file(args.filename, logger) + # A --auth-type override replaces any CSV-supplied auth value, so suppress + # the CSV-time "Local" remap warning when one is given -- it would describe + # a value that's about to be discarded. + user_obj_list = UserCommand.get_users_from_file( + args.filename, logger, warn_on_local_remap=not getattr(args, "auth_type", None) + ) logger.info(_("session.monitorjob.percent_complete").format(0)) error_list = [] for user_obj in user_obj_list: diff --git a/tabcmd/commands/user/user_data.py b/tabcmd/commands/user/user_data.py index 99cbbe79..63656921 100644 --- a/tabcmd/commands/user/user_data.py +++ b/tabcmd/commands/user/user_data.py @@ -41,11 +41,11 @@ def populate(self, values: List[str]) -> None: if n_values >= 8: self.auth = values[7] - def to_tsc_user(self, logger: Optional[logging.Logger] = None) -> TSC.UserItem: + def to_tsc_user(self, logger: Optional[logging.Logger] = None, warn_on_local_remap: bool = True) -> TSC.UserItem: site_role = UserCommand.evaluate_site_role(self.license_level, self.admin_level, self.publisher) if not site_role: raise AttributeError(_("tabcmd.user.error.site_role_required")) - auth = UserCommand.normalize_auth_setting(self.auth, self.name, logger) + auth = UserCommand.normalize_auth_setting(self.auth, self.name, logger, warn=warn_on_local_remap) user = TSC.UserItem(self.name, site_role, auth) user.email = self.email user.fullname = self.fullname @@ -110,14 +110,20 @@ class UserCommand(Server): # (both for CSV values and for the CLI --auth-type override) so inputs # authored for Classic import without crashing, and log a warning so the # operator sees the remap happened. + # warn=False suppresses the warning for a remap whose result won't be the + # effective value -- e.g. a CSV row's "Local" that a CLI --auth-type + # override is about to replace. @staticmethod - def normalize_auth_setting(auth, username: Optional[str] = None, logger: Optional[logging.Logger] = None): + def normalize_auth_setting( + auth, username: Optional[str] = None, logger: Optional[logging.Logger] = None, warn: bool = True + ): if isinstance(auth, str) and auth.lower() == "local": - # Use the command's logger when given: it carries the console handlers, - # so the operator sees the warning on stderr, not only in tabcmd.log. - (logger or logging.getLogger(__name__)).warning( - _("tabcmd.user.warning.local_auth_remapped").format(username) - ) + if warn: + # Use the command's logger when given: it carries the console handlers, + # so the operator sees the warning on stderr, not only in tabcmd.log. + (logger or logging.getLogger(__name__)).warning( + _("tabcmd.user.warning.local_auth_remapped").format(username) + ) return TSC.UserItem.Auth.ServerDefault return auth @@ -210,7 +216,9 @@ def _validate_item(item: str, possible_values: List[str], column_type) -> None: raise AttributeError(_("commandlineutils.errors.bad_value").format(column_type, item, possible_values)) @staticmethod - def get_users_from_file(csv_file: io.TextIOWrapper, logger=None) -> List[TSC.UserItem]: + def get_users_from_file( + csv_file: io.TextIOWrapper, logger=None, warn_on_local_remap: bool = True + ) -> List[TSC.UserItem]: csv_file.seek(0) # set to start of file in case it has been read earlier if logger: logger.debug("Reading from file {}".format(csv_file.name)) @@ -219,14 +227,16 @@ def get_users_from_file(csv_file: io.TextIOWrapper, logger=None) -> List[TSC.Use if logger: logger.debug("> {}".format(line)) while line: - user: Optional[TSC.UserItem] = UserCommand._parse_line(line, logger) + user: Optional[TSC.UserItem] = UserCommand._parse_line(line, logger, warn_on_local_remap) if user: user_list.append(user) line = csv_file.readline() return user_list @staticmethod - def _parse_line(line: str, logger: Optional[logging.Logger] = None) -> Optional[TSC.UserItem]: + def _parse_line( + line: str, logger: Optional[logging.Logger] = None, warn_on_local_remap: bool = True + ) -> Optional[TSC.UserItem]: if line is None or line is False or line == "\n" or line == "": return None line = line.strip() @@ -234,7 +244,7 @@ def _parse_line(line: str, logger: Optional[logging.Logger] = None) -> Optional[ data = Userdata() values: List[str] = list(map(str.strip, line_parts)) data.populate(values) - return data.to_tsc_user(logger) + return data.to_tsc_user(logger, warn_on_local_remap) # https://help.tableau.com/current/server/en-us/csvguidelines.htm#settings_and_site_roles @staticmethod diff --git a/tests/commands/test_user_utils.py b/tests/commands/test_user_utils.py index ee4b769f..7a41e67b 100644 --- a/tests/commands/test_user_utils.py +++ b/tests/commands/test_user_utils.py @@ -193,3 +193,24 @@ def test_normalize_auth_setting_cli_value(self): with self.assertLogs("tabcmd.test.command_logger", level="WARNING"): result = UserCommand.normalize_auth_setting("Local", "bob", logging.getLogger("tabcmd.test.command_logger")) assert result == TSC.UserItem.Auth.ServerDefault + + def test_local_auth_csv_warning_suppressed_when_cli_overrides(self): + # Regression: a CSV row of "Local" logged the remap warning during parsing + # even when a --auth-type CLI override was about to replace that value, + # describing a remap that never took effect. When a CLI override is + # coming, the CSV-time warning must be suppressed. + data = Userdata() + data.populate(["username", "pword", "fname", "creator", "none", "yes", "email", "Local"]) + with self.assertNoLogs("tabcmd.commands.user.user_data", level="WARNING"): + user = data.to_tsc_user(warn_on_local_remap=False) + assert user.auth_setting == TSC.UserItem.Auth.ServerDefault, user.auth_setting + + def test_local_auth_csv_warning_still_fires_without_override(self): + # Without a CLI override, the CSV's own "Local" value is effective, so the + # warning should still fire. + data = Userdata() + data.populate(["username", "pword", "fname", "creator", "none", "yes", "email", "Local"]) + with self.assertLogs("tabcmd.commands.user.user_data", level="WARNING") as logs: + user = data.to_tsc_user(warn_on_local_remap=True) + assert user.auth_setting == TSC.UserItem.Auth.ServerDefault, user.auth_setting + assert any("username" in msg for msg in logs.output), logs.output