Repository navigation
Conversation
…#301) When `tabcmd login -u myuser` is run with no `-p` flag, the session creation now correctly prompts for a password interactively (or exits with an error if `--no-prompt` is set), rather than falling through to the saved-session path and failing with a missing-arguments error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a user supplies -f output.csv with a URL that has a different extension (e.g. view.png), the file type is now determined from the user-supplied filename extension. If the filename has no recognized extension, or no filename is given, the URL extension is used as the fallback. Adds three tests covering the three cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace filename="tabcmd.log" in logging.basicConfig with a RotatingFileHandler (max 1 MB per file, 5 backups) so the log file no longer grows indefinitely. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
c:\dev\tabcmd>mypy tests tests\commands\test_projects_utils.py:22: error: Argument 1 to "_parse_project_path_to_list" of "Server" has incompatible type "None"; expected "str" [arg-type] tests\commands\test_user_utils.py:76: error: Incompatible types in assignment (expression has type "UserItem | None", variable has type "UserItem") [assignment] tests\commands\test_user_utils.py:84: error: Incompatible types in assignment (expression has type "UserItem | None", variable has type "UserItem") [assignment] tests\e2e\language_tests.py:208: error: "OnlineCommandTest" has no attribute "_get_workbook" [attr-defined] tests\e2e\tests_integration.py:68: error: Name "logger" is not defined [name-defined] tests\e2e\tests_integration.py:99: error: Name "logger" is not defined [name-defined] tests\e2e\tests_integration.py:133: error: Name "__class__" is not defined [name-defined] tests\commands\test_session.py:206: error: Argument 1 to "_allow_prompt" has incompatible type "Namespace"; expected "Session" [arg-type] tests\commands\test_session.py:211: error: Argument 1 to "_allow_prompt" has incompatible type "Namespace"; expected "Session" [arg-type] tests\commands\test_session.py:216: error: Argument 1 to "_allow_prompt" has incompatible type "Namespace"; expected "Session" [arg-type] Found 10 errors in 5 files (checked 51 source files)
output with fixes > mypy tests Success: no issues found in 51 source files
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Replace basicConfig (no-op if root already has handlers) with an explicit setLevel on the root logger - Guard RotatingFileHandler and StreamHandler creation so repeated configure_log calls don't accumulate duplicate handlers - Add tests for no-duplicate and level-set behaviour Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add test covering the case where neither filename nor URL has a recognized extension, which should exit with an error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The elif branch now only fires when there is no active session. If a session already exists (self.tableau_server is set), fall through to the existing-session reuse path instead of unconditionally signing out and prompting for a password. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Run black across all modified files - Convert PublishCommand.run_command from @staticmethod to @classmethod - Fix exit_with_error call passing list instead of str Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Satisfies mypy check_untyped_defs — local stub class doesn't structurally match the expected ProjectItem type, cast() avoids changing production code. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The committed .mo files were out of sync with their .properties sources: ~20 keys per locale were either orphan translations for removed code or missing translations for currently-used code. Regenerated via the check-mo-freshness workflow on Ubuntu and downloaded from the workflow artifact so the bytes match what CI produces.
.po files are gitignored across the repo, so the workflow's `git diff --exit-code -- '**/*.po'` was always a no-op and the `.po` path filter never triggered a run. Trim both. No behavior change; the .mo drift check is what has always been doing the actual work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…entials (#467) * Fix publish failures when using embedded db credentials (#458) * Require --db-server for embedded credentials/oauth workbook publishing, fix connections payload * Translations for publish.options.db-server message * Simplify changes * Adjust tests * Require --db-server when publishing with embedded credentials PR #458 fixed the two publish crashes when using --db-username / --db-password / --save-db-password, but callers who omit --db-server still hit a raw ValueError from tableauserverclient's _add_connections_element. Fail fast in run_command with a clear, localizable message before we build the ConnectionItem so users get an actionable error instead of an internal stack trace. Update the existing no-db-server test to assert the exit path and add the parallel coverage for the --oauth-username branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add e2e coverage for --db-server requirement Extend the online publish suite with: - A positive assertion for the happy path: test_wb_publish_embedded now passes --db-server matching the connection host baked into EmbeddedCredentials.twb (see-internal-slack). Without a matching server_address, tableauserverclient silently drops the embedded credentials at publish time, so covering the match is what actually proves the fix. - A negative assertion: test_wb_publish_embedded_missing_db_server_fails runs publish with --db-username but no --db-server and expects a non-zero exit. The check now fires in run_command before any network work, so hardcoded placeholder creds are sufficient. Threads a db_server keyword through the _publish_creds_args helper and records the workbook's connection host as a class constant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address code-review findings on the --db-server guard Scope the guard to workbook publishes only. Datasource publishes go through _add_credentials_element on the TSC side, which never requires server_address, so the previous unconditional check was a regression for `tabcmd publish live_mysql.tds --db-username ...` and similar. The guard now checks the target filename extension early (before auth, for fast failure) and repeats per-file inside the workbook branch of the loop for the folder-publish case. Rename the `credentials` parameter of `publish_workbook_file` to `connection` — it holds a single ConnectionItem, not a list. Tests: - Add test_publish_with_oauth_creds covering the oauth branch's server_address assignment (previously untested). - Add test_publish_datasource_with_db_username_no_db_server verifying datasource publishes are not blocked when --db-server is omitted. - Strengthen the e2e negative test to run tabcmd via subprocess.run, capture stdout/stderr, and assert on the localized guard message (or the raw key when .mo has not been regenerated). Any earlier unrelated failure — bad auth, missing asset, session expiry — now fails the test rather than passing it. - Trim the misleading class-constant comment on TWB_FILE_EMBEDDED_ CONNECTION_SERVER; reword the "before we touch the database" comment to reference the guard's contract instead of a specific location. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Revert "Address code-review findings on the --db-server guard" This reverts commit cfe52c1. * Revert "Add e2e coverage for --db-server requirement" This reverts commit 9581329. * Revert "Require --db-server when publishing with embedded credentials" This reverts commit d59ee65. --------- Co-authored-by: Mauro Fujihara <fujihara@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix publish failures when using embedded db credentials (#458) * Require --db-server for embedded credentials/oauth workbook publishing, fix connections payload * Translations for publish.options.db-server message * Simplify changes * Adjust tests * Require --db-server when publishing with embedded credentials PR #458 fixed the two publish crashes when using --db-username / --db-password / --save-db-password, but callers who omit --db-server still hit a raw ValueError from tableauserverclient's _add_connections_element. Fail fast in run_command with a clear, localizable message before we build the ConnectionItem so users get an actionable error instead of an internal stack trace. Update the existing no-db-server test to assert the exit path and add the parallel coverage for the --oauth-username branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add e2e coverage for --db-server requirement Extend the online publish suite with: - A positive assertion for the happy path: test_wb_publish_embedded now passes --db-server matching the connection host baked into EmbeddedCredentials.twb (see-internal-slack). Without a matching server_address, tableauserverclient silently drops the embedded credentials at publish time, so covering the match is what actually proves the fix. - A negative assertion: test_wb_publish_embedded_missing_db_server_fails runs publish with --db-username but no --db-server and expects a non-zero exit. The check now fires in run_command before any network work, so hardcoded placeholder creds are sufficient. Threads a db_server keyword through the _publish_creds_args helper and records the workbook's connection host as a class constant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address code-review findings on the --db-server guard Scope the guard to workbook publishes only. Datasource publishes go through _add_credentials_element on the TSC side, which never requires server_address, so the previous unconditional check was a regression for `tabcmd publish live_mysql.tds --db-username ...` and similar. The guard now checks the target filename extension early (before auth, for fast failure) and repeats per-file inside the workbook branch of the loop for the folder-publish case. Rename the `credentials` parameter of `publish_workbook_file` to `connection` — it holds a single ConnectionItem, not a list. Tests: - Add test_publish_with_oauth_creds covering the oauth branch's server_address assignment (previously untested). - Add test_publish_datasource_with_db_username_no_db_server verifying datasource publishes are not blocked when --db-server is omitted. - Strengthen the e2e negative test to run tabcmd via subprocess.run, capture stdout/stderr, and assert on the localized guard message (or the raw key when .mo has not been regenerated). Any earlier unrelated failure — bad auth, missing asset, session expiry — now fails the test rather than passing it. - Trim the misleading class-constant comment on TWB_FILE_EMBEDDED_ CONNECTION_SERVER; reword the "before we touch the database" comment to reference the guard's contract instead of a specific location. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Revert "Address code-review findings on the --db-server guard" This reverts commit cfe52c1. * Revert "Add e2e coverage for --db-server requirement" This reverts commit 9581329. * Revert "Require --db-server when publishing with embedded credentials" This reverts commit d59ee65. * Fix NameError in uniquify_file when no lines are discarded The success-print branch referenced uniques instead of unique_lines, breaking doit properties when input files contained no non-'=' non- codestring lines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Regenerate localized .mo files after doit localize Compiles the new `publish.options.db-server` help-text key added by #458 (backported in PR A) into the runtime gettext catalogs. Without this, English users see the raw msgid key in `tabcmd publish --help`. Non-English locales fall back to English until translators supply their versions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Mauro Fujihara <fujihara@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #458 fixed the two publish crashes when using --db-username / --db-password / --save-db-password, but callers who omit --db-server still hit a raw ValueError from tableauserverclient's _add_connections_element. Fail fast in run_command with a clear, localizable message before we build the ConnectionItem, so users get an actionable error instead of an internal stack trace. - Restore the --db-server required-when-embedded guard on the workbook publish path. - Extract _require_db_server_for_workbook(args, filename, logger) helper so the duplicated guard cannot drift between the early pre-auth check and the in-loop per-file check. - Extend the English publish.options.db-server help text to warn about the silent-drop-on-mismatch footgun. - Fix Portuguese grammar: "banco de dado" -> "banco de dados" (plural); "associado a credenciais" -> "associado as credenciais" (crase). - Regenerate .mo catalogs to pick up the new keys. - Unit tests: db-username and oauth-username exit paths, plus datasource-not-blocked coverage. - E2E test: friendly exit when --db-server is missing. Comment on test_wb_publish_embedded clarifies it only proves not-crashing (a mismatched --db-server would still exit 0 while silently dropping the creds; tracked internally). Re-cut from the closed PR #470 stack. Previously PR C in the #467/#469/#470 backport chain; PR B's base branch was deleted after merge, orphaning PR #470. This lands the same content on a fresh branch off development. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ll locales Copilot flagged the new key as English-only, so users in de/es/fr/ga/ it/ja/ko/pt/sv/zh would see the raw msgid (`publish.errors.db_server_required`) instead of any actual message. Adds the English placeholder text to each of the 10 non-EN properties catalogs and regenerates the .mo bundles via `doit localize` so the rendered guidance appears in every locale until real translations arrive. Tightens the e2e assertion to require the rendered English text (dropping the raw-msgid fallback path) so localization regressions fail loudly instead of silently.
) The two Tableau-owned Python packages (tabcmd and tableauserverclient) were on divergent dev-tool versions: tabcmd TSC black >=22,<23 ==26.5.1 mypy unpinned ==2.3.0 Anyone working on both repos (e.g. porting fixes back and forth) has to constantly swap black versions in their local env. Match TSC's pins on the tabcmd side; older repos of the two are the outlier here. Consequential changes from the version bumps: - black 26 reformats 9 files (whitespace around decorators, blank-line policy after imports). No behavior change. - black 26 also requires bumping `target-version` -- switched to py310-py314 to match tabcmd's `requires-python` and `classifiers` list (was py310-py311 only). - Dropped `required-version = 22` from [tool.black]; TSC doesn't use one and the versioned pin in [test] is enough. - mypy 2.3 caught one pre-existing issue: tabcmd/version.py re-imported `version` under the same name inside a fallback branch (`from tabcmd._version import version`), which shadows the `version: str = "unknown"` annotation. Renamed the fallback import to `_pyinstaller_version` and assigned. Verified: - black . --check passes - mypy tabcmd tests passes with 0 errors (was 1 with mypy 2.3) - Full unit-test suite: 337 passed, 2 skipped
* Fix #433: accept Classic `delete --workbook Name` syntax tabcmd Classic invokes delete as `tabcmd delete --workbook "Name"` where --workbook takes the target name as its value. tabcmd 2 shipped `tabcmd delete "Name" --workbook` (name positional, flag is a type selector). This is a breaking CLI difference for anyone running Classic scripts unchanged. Accept both forms: - Positional name stays optional (nargs="?"). - --workbook/--datasource take an optional value (nargs="?"). Bare form still means True (tabcmd 2 syntax); with a value, the value is the target name (Classic syntax). - In run_command, resolve which form supplied the name and normalize args.workbook/args.datasource to True. Emit the same "requires workbook or datasource" error when neither form supplied a name. Mutual exclusion between --workbook and --datasource still holds. Fixes #433. * Add missing parser tests for delete syntax variations Covers gaps flagged in fresh-eyes review: - `-w` / `-d` short flags on Classic form - Ambiguous "positional Name + --workbook FlagValue" -- parser accepts both; run_command normalization discards flag value - Bare `--workbook` with no value (parses because nargs="?" const=True) - Classic form + -r project combined The run_command normalization itself (`isinstance(args.workbook, str) -> args.name = args.workbook; args.workbook = True`) is not covered here; tracked in the follow-up run_command coverage issue. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches the pin declared in tableauserverclient (our direct dependency), so any Python environment installing tabcmd resolves the same urllib3 range TSC was tested with. Previously urllib3 was unpinned; pip would then pick a version compatible with TSC's constraint by transitive resolution, but the tabcmd distribution itself didn't declare compatibility bounds. No functional change — the resolved version doesn't move.
* Fix #437: add 10 editsite flags for tabcmd Classic parity tabcmd Classic exposes 12 site-configuration flags on editsite that tabcmd 2 was missing. This adds 10 of them (the 2 skipped items are --metrics-level, which is Tableau-internal VizQL telemetry likely Cloud-N/A pending VizQL team confirmation, and --cache-warmup-threshold, which is server-only). Flags added, with the SiteItem attribute each wires: - --guest-access-enabled / -g -> guest_access_enabled - --cache-warmup / --no-cache-warmup -> cache_warmup_enabled - --subscription-email / -e -> custom_subscription_email + _enabled - --subscription-footer / -f -> custom_subscription_footer + _enabled - --web-extraction-enabled -> web_extraction_enabled - --allow-subscriptions / --no-* -> disable_subscriptions (inverted) - --allow-web-authoring / --no-* -> authoring_enabled - --allow-mobile-snapshots / --no-* -> sheet_image_enabled - --time-zone STRING -> time_zone - --use-default-time-zone -> use_default_time_zone Design notes: - Paired positive/negative boolean flags default to None so an unpassed flag never touches the server setting (critical: a naive default=False would flip cache-warmup / subscriptions / etc. off on every editsite). - --time-zone and --use-default-time-zone are argparse-enforced mutually exclusive. - Flags live in a new set_edit_site_only_args helper (not set_common_site_args) because create_site_command doesn't wire these attributes; adding to the shared helper would advertise unwired flags in `createsite --help`. - --allow-mobile-snapshots maps onto sheet_image_enabled, NOT mobile_biometrics_enabled (that's a separate biometric-auth feature). - --allow-subscriptions is inverted onto disable_subscriptions because that's how the REST API expresses the setting. Two upstream TSC bugs surfaced during implementation but not fixed here: RequestFactory.py lowercases both customSubscriptionEmail and customSubscriptionFooter on the wire, so `user@Domain.com` will be sent as `user@domain.com`. To be filed separately. Tests: 30 new parser tests in tests/parsers/test_parser_edit_site.py cover each flag's arg form, mutual exclusion, and default None behavior. Full parsers suite: 127 passed, 1 skipped (pre-existing). Fixes #437. * style: apply black to test_parser_edit_site.py The initial commit predated a black run; CI's black check failed on this file (repo pins black 22). * Fill parser test gaps on new editsite flags Fresh-eyes review flagged that the new-flag test set only checked default=None for two of ten flags, and only exercised "true" (not "false") on the string-value flags. Adds: - default=None coverage for guest_access_enabled, web_extraction_enabled, allow_web_authoring, allow_mobile_snapshots, subscription_email, subscription_footer, and time_zone (previously only cache_warmup and allow_subscriptions were tested). - "false" argument on --guest-access-enabled long form and --web-extraction-enabled (previously only "true" was tested). - Empty-string acceptance on --subscription-email and --subscription-footer; the paired _enabled flip happens in run_command and is tracked in the run_command coverage issue. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: clear use_default_time_zone when explicit --time-zone is given A site currently using the server default returns from GET with use_default_time_zone=True. TSC serializes both attributes on update and the default flag wins server-side, so the explicit zone was silently ignored. Clearing the flag when --time-zone is provided lets the explicit value take effect. Addresses Copilot review finding on PR #452. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: bcantoni <58372+bcantoni@users.noreply.github.com>
Co-authored-by: bcantoni <58372+bcantoni@users.noreply.github.com>
All conflicts were .mo binaries; regenerated the full set via doit localize from the merged .properties sources rather than picking a side. Also folds in the Windows console UTF-8 fix for check_mo's charmap errors on ja/ko/zh sample output. Co-Authored-By: Claude Code <noreply@anthropic.com>
Brings development up to date with main before the next release. Conflicts resolved in favor of development, which already carries the same fixes in newer form (release-on-merge.yml, check-strings.yml, publish --db-server handling, logger stdout/stderr split, version fallback, string keys). Taken from main: the saved auth_token reuse fix (#463) and its tests, macos-15-large for the x86 Mac build, setuptools 83.0.0, and the CodeQL bump. The .mo files are regenerated with doit localize. Co-Authored-By: Claude Code <noreply@anthropic.com>
…strust (#483) Frozen PyInstaller builds embed certifi at build time, so the floor makes the next release carry the updated trust bundle. Co-authored-by: Claude Code <noreply@anthropic.com>
) * Stop committing generated .mo files; build them with doit localize The .mo bundles are generated from the .properties files, and every PR that changed a string regenerated them, so they conflicted constantly. Untrack them, ignore them, and run doit localize before building in CI and packaging. Remove check-mo-freshness, which has nothing left to check. * Keep LC_MESSAGES directories so doit localize can write into them Untracking the .mo files left the LC_MESSAGES directories empty, and git does not store empty directories, so doit localize failed on a fresh checkout. Co-Authored-By: Claude Code <noreply@anthropic.com>
…es (#489) Drop the hidden imports PyInstaller 6 no longer needs (requests.packages.urllib3, pkg_resources), which removes the reason for the setuptools==81 pin. Add a Package-and-Upload step that runs -v and a failing login against a closed port, to catch missing bundled modules (requests, urllib3, certifi). Co-authored-by: Claude Code <noreply@anthropic.com>
Unblocks #455 (createsiteusers --nowait/--silent-progress), which depends on JobItem.status_notes (tableau/server-client-python#1852), shipped in TSC v0.42 (released 2026-10-15). Co-authored-by: Claude Code <noreply@anthropic.com>
* Fix: accept yes/no on --encrypt for Classic parity tabcmd Classic's `--encrypt` on `createextracts` takes an explicit yes/no value; tabcmd 2 treated it as a bare boolean flag, silently ignoring any following value. Scripts that pass `--encrypt no` on Classic ended up encrypting on tabcmd 2 because argparse consumed the "no" as a positional or bailed. Make --encrypt accept an optional yes|no|true|false argument (case-insensitive). Bare `--encrypt` still means True; omitted means False; explicit `--encrypt no` means False. This preserves current tabcmd 2 default behavior while matching Classic syntax. * style: apply black to satisfy CI * Accept Classic's y/n shortcuts on --encrypt in addition to yes/no Classic tabcmd's CreateExtracts.java:79-101 accepts exactly `yes|y|no|n` (case-insensitive) on --encrypt and rejects anything else. This PR was dropping the `y`/`n` shortcuts, which breaks any ported Classic script that used them. Keeping Classic's set intact is the standing rule — tabcmd 2 may be more forgiving than Classic, but must not narrow. Add `y` and `n` to the accepted set (both directions). tabcmd 2 now accepts the union `yes|y|no|n|true|false|1|0`, case-insensitive. Rewrite the source comment to cite the Classic source-of-truth path in the monolith rather than the incorrect earlier claim that Classic accepted "yes/no/true/false". Add two tests pinning `y`→True and `n`→False so a future refactor can't quietly drop them again. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: expand --encrypt metavar to advertise full accepted alias set Addresses Copilot review feedback on PR #450: the help metavar showed only "yes|no" while _parse_yes_no accepts yes/y/no/n/true/false/1/0. Updates metavar to reflect the full forgiving superset and adds two tests locking in 1/0 numeric-alias acceptance so the docs contract and the parser stay in sync. * style: reformat global_options.py with black 22 Co-Authored-By: Claude Code <noreply@anthropic.com>
Resolve pyproject.toml: keep pyinstaller==6.22.3 from development (#489). Co-Authored-By: Claude Code <noreply@anthropic.com>
* Fix #35: --nowait / --silent-progress on createsiteusers (needs TSC release) tabcmd Classic accepts --nowait and --silent-progress on createsiteusers; tabcmd 2 was missing both flags. Adding them meaningfully requires more than argparse: Classic sends the CSV as a single bulk-import job and polls the server-side JobItem, while tabcmd 2 has been walking the CSV row by row and calling users.add per user. With no server-side job to poll, --nowait had nothing to skip. This PR switches createsiteusers to server.users.bulk_add + a single JobItem, and wires the two flags. Behavior changes: - Default: submit bulk_add, wait for the JobItem to complete via server.jobs.wait_for_job, print a summary derived from the completed job's status_notes (CountOfUsersAddedToSite / CountOfUsersSkipped / CountOfUsersProcessed) plus any per-row error notes. Same shape as Classic's default output. - --nowait: submit bulk_add and return immediately. Job ID is printed before return so callers can query it via a separate call. - --silent-progress: still waits for the job, but suppresses the status header, the queued-job line, and the per-row summary. Framework logging (errors, debug) is unaffected. - --complete / --no-complete: Classic used a ClientXML with_transaction param that the REST endpoint doesn't expose, so we approximate with client-side pre-flight validation: - --complete (default): validate_file_for_import(strict=True), any malformed row aborts the whole import before submission. - --no-complete: validate leniently, submit whatever parses. Doesn't cover server-side row failures mid-import; those fall through to job.status_notes on the completed job. Flag spelling: Classic uses --nowait (one word). The existing set_no_wait_option helper defined --no-wait but had zero callers, so switched the spelling to Classic's without a compatibility alias. Depends on TSC >= (next release), which will ship JobItem.status_notes (tableau/server-client-python#1852, merged). Draft while awaiting a tagged TSC release + tabcmd's TSC pin bump. Fixes #35. * fix: import JobFailedException from its module TSC top-level doesn't re-export JobFailedException; import directly from tableauserverclient.server.endpoint.exceptions. Fixes the mypy attr-defined error in CI. * style: apply black 22 to new test files * Fail loudly on older TSC; document --continue-if-exists is a no-op The status_notes fallback previously used getattr(..., None) or [] which meant an older TSC would print added=0, skipped=0, processed=len(input) with no indication anything was wrong. Detect the attribute missing and exit with a message pointing at the pinned TSC version. --continue-if-exists is a global flag used across the create-* commands to downgrade 409 conflicts to INFO. bulk_add is inherently tolerant of duplicate users (they get counted under CountOfUsersSkipped) so the flag is a no-op here. Keep the flag on the parser for consistency, log the no-op at DEBUG, and cover with a test so a future refactor doesn't accidentally rely on it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Drop redundant --continue-if-exists no-op test test_continue_if_exists_is_a_noop_documented_in_debug just re-ran the default happy path with the flag set. bulk_add is called and wait_for_job is called either way, so this asserts nothing that the base test doesn't. The behavioral comment lives on the code path in create_site_users.py already. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Bump tableauserverclient to 0.42 and drop pre-status_notes guard in createsiteusers 0.42 exposes JobItem.status_notes, so the hasattr guard, its needs_newer_tsc string and the test for it are dead code. --continue-if-exists is silently ignored by createsiteusers (bulk_add tolerates existing users), so the debug-only no-op message is removed too. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix: createsiteusers numeric timeout, failed-job diagnostics - pass --timeout to wait_for_job as an int (it arrives as a string and TSC compares it with elapsed seconds) - on JobFailedException, log status_notes/notes before exiting nonzero - tests for both Co-Authored-By: Claude Code <noreply@anthropic.com> * localize: regenerate English catalog for createsiteusers messages Co-Authored-By: Claude Code <noreply@anthropic.com> * style: format test_create_site_users.py with black 26.5.1 Co-Authored-By: Claude Code <noreply@anthropic.com> * fix: createsiteusers timeout message, job id under --nowait, bulk_add errors - on wait timeout, say the import is still running and print the job id - always print the job id under --nowait, even with --silent-progress - route any bulk_add failure (not just ServerResponseError) through exit_with_error - tests for the first two Co-Authored-By: Claude Code <noreply@anthropic.com> * createsiteusers: save per-row import errors to a file (--errors-file, default per job id) Under --silent-progress rejected rows are written to createsiteusers-errors-<jobid>.txt instead of being dropped; --errors-file overrides the path and also enables the file without --silent-progress. Co-Authored-By: Claude Code <noreply@anthropic.com> * createsiteusers: fix rejected-row detail to use job.notes, not merged status_notes The server does not emit one statusNote per rejected row for a UserImport job. RestApiHelper.getStatusNotesErrors merges each error field (line/username/message) across ALL failed rows into a single statusNote per field, joined by ";", and omits the CountOf* aggregates entirely once there are errors. The actual one-line-per-row detail is the legacy <notes> text built by UserManagementService.getHumanReadableCSVImportSummary, which TSC surfaces as job.notes. _job_detail_lines now prefers job.notes and only falls back to non-CountOf* status_notes if notes is empty. Added tests/assets/createsiteusers_job_with_errors.xml as a fixture matching the real response shape, parsed via TSC.JobItem.from_response in _rejected_job() instead of a hand-rolled status_notes mock. Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
prop2po wrote values into the .po unescaped, so a value containing " ended the
msgstr early. msgfmt.py's literal_eval then failed (ERROR (skipped)) and the .mo
stored the wrong text: editsite.options.subscription_email kept stray wrapping
quotes, and session.options.site lost its ("") in every locale. Escape bare
quotes in prop2po and regenerate the catalogs.
Co-authored-by: Claude Code <noreply@anthropic.com>
… 1 (#432) * fix: correct output messages in listsites, deletegroup, export, createsiteusers, removeusers - listsites: show site content_url instead of internal UUID for SITEID - deletegroup: log group name instead of UUID in delete status message - export/save_to_file: pass filename as content name to fix double-space in "Saved to '...'" message - createsiteusers: append str(e) to error_list instead of e.__class__.__name__ so error details show readable messages - removeusers: use new tabcmd.removeusers.group string key that includes the group name in the status message Fixes #429 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: add ===== prefix to all INFO output, fix delete path, suppress debug noise - logger_config.py: add ===== prefix to INFO format so all action lines match tabcmd 1 style in one place - session.py: remove hardcoded ===== from _print_server_info lines (now added by formatter); demote "Fetched user details from server" to debug - constants.py: demote tabcmd.debug.error_message log call from info to debug so it no longer appears in normal output - delete_command.py: default content_type to "workbook" in status message to fix double-space when type is undetected - tabcmd_messages_en.properties: remove hardcoded ===== from tabcmd.listing.header (would double-prefix with formatter) Closes remaining items from #429 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: print version/help output directly instead of via logger.info Avoids the ===== prefix being applied to the version/help display. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add TABCMD_CLASSIC_OUTPUT env var to enable ===== prefix on output Plain output is the default. Set TABCMD_CLASSIC_OUTPUT=true (or 1/yes) to restore the ===== prefix on all INFO-level messages for backward compatibility with tabcmd 1 output style. Also moves the version banner in tabcmd_controller to print() so it is never prefixed regardless of mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: remove spurious 'logging: INFO' printed on every command The condition compared logging_level string 'INFO' to the integer logging.INFO (20), which was always True, causing the print to fire on every invocation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: add 'Signing in...' message to match tabcmd 1 login output session.login string already existed; add logger.info call at the start of _sign_in() to surface it as a distinct step after connecting. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Thread content name through save_to_file, default ===== prefix on Two follow-up fixes for parity with tabcmd Classic output: - save_to_file/save_to_data_file previously passed filename twice into the "Saved {0} to '{1}'" template, so both slots rendered the same path. Classic output was "Saved <content-name> to '<destination>'" where the two are distinct. Add a content_name parameter and thread export_item.name / get_url_item.name from all four callers. - TABCMD_CLASSIC_OUTPUT default flipped to true. The stated goal of this PR is parity with tabcmd 1 output, and issue #429 item 8 lists the missing "=====" prefix as a bug. Users who prefer the plainer tabcmd 2 style can opt out with TABCMD_CLASSIC_OUTPUT=false. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: address Copilot review findings in PR #432 - delete_command.py: initialize item_to_delete before conditional; move delete.status log after item resolution so content_type/name are populated. - dodo.py: fix NameError in uniquify_file - reference unique_lines (the sorted list actually printed) instead of undefined uniques. - en properties: strip stray leading space in tabcmd.listing.header format string. - locales: regenerated .mo/.properties for all languages. * Apply remaining changes Co-authored-by: bcantoni <58372+bcantoni@users.noreply.github.com> * fix: stop configure_log from overwriting the INFO format at non-INFO levels Keeps the TABCMD_CLASSIC_OUTPUT-derived prefix for INFO lines under --logging-level DEBUG. Adds tests for the DEBUG case and the opt-out. Co-Authored-By: Claude Code <noreply@anthropic.com> * fix: keep classic INFO prefix on the stdout handler at DEBUG level The stdout handler used the configured level's format for every record, so INFO lines at --logging-level DEBUG rendered with the DEBUG format. Format each record by its own level instead. Co-Authored-By: Claude Code <noreply@anthropic.com> * chore: remove unused item_name in DeleteCommand Co-Authored-By: Claude Code <noreply@anthropic.com> * localize: regenerate compiled message catalogs Co-Authored-By: Claude Code <noreply@anthropic.com> * Print listing data lines without the classic status prefix The ===== prefix is for status lines. Data rows from listsites and list, including list --machine output, now go through a PLAIN extra marker and print unprefixed. The per-level formatter builds its formatters once instead of rewriting the private _style._fmt on each record. Co-Authored-By: Claude Code <noreply@anthropic.com> * Keep the version banner on the logger so --logging-level and tabcmd.log apply Routes the banner through logger.info with the PLAIN marker, so it stays unprefixed on the console and still goes through the root handler. Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: bcantoni <58372+bcantoni@users.noreply.github.com>
* fix: preserve '=' inside --filter values
Previously `apply_filter_value` called `value.split("=")`, so a filter
like `Notes=x=y` silently truncated to name=Notes, value=x. Split on the
first '=' only (maxsplit=1) so multi-'=' values round-trip intact, and
raise a clear error when the input isn't in name=value form.
Verified end-to-end against a real workbook: `Product Name=x=y Config Kit`
now filters correctly instead of returning empty results.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: add filter_test_data.csv fixture for e2e filter tests
Dataset with product names containing '=', '&', ',', backslashes, and
metacharacter samples for exercising --filter parsing edge cases against
a real server. Also includes columns whose names contain '&', '#', and
'\' so future tests can exercise special characters on both sides of
the name=value pair.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: don't hard-error on unparseable URL filter fragments (Classic parity)
The earlier tightening in this PR made apply_filter_value raise a clean
error when a clause isn't in name=value form. That's the right behavior
for the --filter flag, but URL-syntax exports inherited from tabcmd
Classic scripts often contain literal '&' inside filter values (e.g.
`?Product Name=AT&T 841000 Phone`), which get split into fragments the
parser can't understand.
Classic silently skipped such fragments. Verified end-to-end against a
Tableau Server today: with the strict path, tabcmd 2 errored out
("Filter clause 'T 841000 Phone' must be in name=value form") where
Classic silently continued and let the server return whatever the well-
formed portion matched.
Add strict: bool = True to apply_filter_value. --filter callers stay
strict; apply_encoded_filter_value (the URL path) passes strict=False,
which logs a WARNING and skips the fragment instead of exiting.
Adds three tests: strict path still exits, non-strict path skips
silently, and end-to-end apply_values_from_url_params tolerates a value
with '&'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style: apply black to satisfy CI
Black 22 (pinned in pyproject.toml) reformats these two files;
the CI 'Check formatting with black' step was failing.
* Rejoin unencoded '&' inside URL-embedded filter values
The prior fix for `?Product Name=AT&T 841000 Phone` matched Classic by
skipping the "T 841000 Phone" fragment, but it also applied the truncated
first fragment (`Product Name=AT`) as a real filter. On any dataset with
a matching "AT" row that produces silent wrong data with only a WARNING
log the user might miss.
Detect the pattern: if a fragment after '&' has no '=' and isn't a
tabcmd options key (":..."), assume it's a continuation of the previous
value and rejoin. Warns loudly that the URL should encode '&' as '%26'
so the ambiguity is visible.
Tests cover the AT&T case, multiple '&' in a value, options mixed with
filters, and multiple legitimate filters.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Log unparseable URL filter fragments at debug level
Classic skips these silently, and a literal '&' inside a URL filter value is a normal case, so a warning per fragment is noisy. The strict --filter path still errors on malformed clauses.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: don't absorb bare fragments into preceding option in URL filter parsing
Copilot review on #442 flagged that a bare fragment (no '=') following an
options key (':...') was being rejoined onto the option itself, corrupting
it -- e.g. '?:refresh=yes&' became ':refresh=yes&' and 'yes&' failed to
enable refresh. Require the fragment being extended to itself be a filter
(not an option) and ignore empty trailing fragments before rejoining.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* Reject empty filter names and skip empty --filter fragments
- apply_filter_value treats an empty field name ("=x") like a missing '=':
exit for --filter, skip for URL-embedded filters.
- apply_filters_from_args skips the empty fragment left by a trailing or
doubled '&', so --filter "Region=West&" no longer exits.
- Add tests for the --filter path end to end, Parameters. stripping with
rejoin, the rejoin warning, skipped URL fragments, and the known
AT&T x=y ambiguity.
Co-Authored-By: Claude Code <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: allow repeated --filter flags for exports Previously `--filter` accepted only one value, and multi-pair filtering required joining pairs with '&' (e.g. `--filter "A=1&B=2"`). That parsing scheme conflicts with values that legitimately contain '&' (e.g. `Product Name=AT&T ...`). Make `--filter` repeatable via argparse action="append". Each flag now carries exactly one COLUMN=VALUE pair — no '&' splitting — so literal '&' in a value passes through untouched. Back-compat: when only one --filter flag is present, the value is still split on '&' so existing scripts keep working. Users who need '&' in a value should switch to the repeated-flag form. Adds parser tests for the new syntax and unit tests for apply_filters_from_args covering both forms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Remove dead default=None on logger param All call sites pass logger explicitly, so the default was unreachable. Tightens the type from Optional[Logger] to Logger, drops any fallback branch inside. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: address Copilot findings on filter parsing for repeated --filter - apply_filter_value now splits on the first '=' only, so values containing '=' (e.g. Notes=x=y) are no longer truncated. - apply_filters_from_args only treats a literal '&' in a single --filter value as a back-compat pair delimiter when every resulting segment actually looks like COLUMN=VALUE; otherwise the '&' is treated as literal (e.g. Company=AT&T), avoiding the previous IndexError risk on ambiguous splits. - Add regression tests for both cases. Co-Authored-By: Claude Code <noreply@anthropic.com> * Regenerate en tabcmd.mo after merging development Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(e2e): round-trip CSVImport bug coverage for TSC #1811 (#472) * test(e2e): round-trip CSVImport bug coverage for #1811 Adds a two-row CSV fixture and one createsiteusers e2e test exercising every UserItem.CSVImport bug fixed in TSC #1811 (issue #1809): AUTH column readable at 8 columns, mixed-case usernames preserved, TableauIDWithMFA in the auth allowlist, case-insensitive validation of role/admin/publisher tokens, and the @property_is_enum guard active on the parsed auth_setting. Test runs under the same server-admin / site-admin gate as test_users_create_site_users and shares its ordering slot; skips cleanly when the caller lacks admin scope or a credentials file. Related: #297 (license-name case sensitivity still surfacing after partial fixes), #434 (Local auth accepted by the CLI but crashes on TSC call). * test(e2e): pass --no-complete + drop --role Publisher on CSVImport 1811 test `createsiteusers` runs `--complete` by default, which invokes tabcmd's duplicate strict validator (`UserCommand._validate_user_or_throw`). That validator rejects `len(line) > 7` and validates license values case-sensitively -- both of which the CSVImport #1811 fixture is specifically meant to exercise. Under `--complete`, the CSV never reached TSC's `UserItem.CSVImport` and the test fell through tabcmd-side. `--no-complete` skips tabcmd's validator so the fixture reaches TSC's parser. Removing `--role Publisher` lets each row's own license field be validated -- restoring coverage of the case-insensitive-license fix. Also corrected the docstring: five of the six bugs, since the sixth (unknown-AUTH rejection) is a negative-path check that doesn't fit a single happy-path fixture and is already covered by TSC's unit tests. * docs: correct test docstring -- this test doesn't reach TSC's CSVImport yet createsiteusers parses the CSV itself via UserCommand.get_users_from_file() / _parse_line() and builds TSC.UserItem directly; it doesn't delegate to TSC's UserItem.CSVImport. The docstring claimed the opposite. Co-Authored-By: Claude Code <noreply@anthropic.com> * test(e2e): use a valid TSC auth value in the CSVImport fixture TableauID is not a member of TSC.UserItem.Auth in the pinned tableauserverclient (0.42), so the first row raised ValueError before the server call. Use ServerDefault for that row. Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Code <noreply@anthropic.com> * Bump github/codeql-action from 4.39.1 to 4.39.3 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.39.1 to 4.39.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@v4.39.1...v4.39.3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.39.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Jac <jacalata@users.noreply.github.com> Co-authored-by: Claude Code <noreply@anthropic.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ases (#459) * fix: Package-and-Upload workflow attaches binaries to UI-created releases Two bugs found while investigating why v2.0.21 shipped without executables attached: 1. The "Upload binaries to release" step was gated on `github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')`. Releases created via the GitHub UI create a tag but do not fire the push:tags: event that this workflow depends on, so v2.0.21 (and v2.0.19) ended up as releases with no assets. The only successful run for v2.0.21 was a workflow_dispatch afterward that built the binaries as artifacts but skipped the upload-to-release step. 2. Both macOS build jobs used `name: tabcmd-macos` on `actions/upload-artifact`, so the two same-named artifacts collided in the artifact store and downloads clobbered each other. Fixes: - New `release_tag` workflow_dispatch input. Set it when dispatching from a branch to attach binaries to a UI-created release. Falls back to `github.ref_name` when the workflow runs on a tag ref (push or dispatch). - Fix mac artifact collision: use `matrix.UPLOAD_FILE_NAME` (unique per platform) as the artifact name instead of `tabcmd-${{ matrix.TARGET }}`. - Split the upload into a separate `upload_to_release` job that depends on `buildexe`, gated on `environment: release`. The `release` environment needs to be created in the repo's Settings -> Environments with required-reviewer protection (mirroring the existing `pypi` environment). Anyone with dispatch access can trigger a build, but only an approved reviewer can attach binaries to a public release. - Upload gate: `if: startsWith(github.ref, 'refs/tags/') || inputs.release_tag != ''` so both the push:tags path and the workflow_dispatch path work. Live-verified by using `gh release upload` today to fix v2.0.21 retroactively with the artifacts from the last workflow_dispatch run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Harden Package-and-Upload workflow per review - Switch to release: types: [published] as the trigger (matches publish-pypi.yml pattern). Removes the manual-dispatch-with-input footgun; release payload provides the tag directly. - Add explicit permissions: contents: write on the upload job so it survives future org-default hardening. - Add concurrency group keyed on the release tag; prevents overlapping runs from stomping each other's approved uploads. - Pin download-artifact back to v7 to match upload-artifact@v7 until v7/v8 interop is verified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(package): restore workflow_dispatch + resolve release tag across event paths Copilot flagged that removing workflow_dispatch broke the release-on-merge.yml caller and the documented manual re-attach path, and that github.event.release.tag_name is empty for anything other than release:published events. Restores workflow_dispatch alongside release:published, adds a "Resolve release tag" step that falls back to github.ref_name, and switches all four uploaders to the resolved output. * docs(contributing): document release-publish reviewer gate + manual re-attach Adds a Publishing-a-release subsection covering the release:published path fired by publishing the draft release in the UI, the new `environment: release` reviewer-approval gate that pauses upload_to_release until a maintainer clicks "Review deployments", and the manual re-attach command `gh workflow run package.yml --ref <tag>` for existing releases. Requested during Copilot review of #459. * fix(workflow): add release_tag dispatch input and fail fast on branch dispatch without one Address Copilot review on #459: define the release_tag workflow_dispatch input, include it in the concurrency group, and resolve the upload tag from the release event, then release_tag, then ref_name only for tag refs. A branch dispatch with no release_tag now errors instead of using the branch name. Correct contributing.md to say publish-pypi.yml is gated on the pypi environment, and document the branch-dispatch form. Co-Authored-By: Claude Code <noreply@anthropic.com> * Only build release binaries once a release is published Drop the package.yml dispatch from release-on-merge.yml. The merge-time run targeted a draft release, which upload-release-action cannot find by tag, and each release needed two `release` approvals. Merging to main now only pushes the tag and creates the draft; publishing it starts package.yml and publish-pypi.yml. Also rewrite the legacy release-process section of contributing.md and correct the trigger and download-artifact comments in package.yml. Co-Authored-By: Claude Code <noreply@anthropic.com> * Limit buildexe job to contents: read The buildexe job only checks out code and uploads artifacts, so it does not need the default token permissions. Only upload_to_release needs write access. Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 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. * 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) <noreply@anthropic.com> * 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. * 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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> * Pass logger to get_users_from_file in act_on_users addusers and removeusers fell back to the module logger when normalizing auth settings, so warnings ignored the command's logging level. This matches the createusers and create_site_users paths. Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Releases tabcmd 2.0.34 from
development.Security updates
Changes in this release
Classic-parity and CLI fixes
--encryptfor Classic paritydelete --workbook Namesyntax #451 accept Classicdelete --workbook Namesyntax--nowait/--silent-progressto createsiteusers=inside--filtervalues--filterflags for exports--usernameis given without credentialsget_file_type_from_filenamepython -m tabcmdwork reliably--password-fileor--token-fileis missing:size=W,Hargument order in URL paramsPublishing
--db-serverwhen publishing with embedded credentialsLocalization
doit localizePackaging, dependencies and CI
test-output/, tighten.gitignore🤖 Generated with Claude Code