feat: clean up console output, add manage subcommand#23
Conversation
- Remove RichHandler, logging.basicConfig, log = getLogger, log.setLevel
from core.py, inspector.py, and cli.py (dead code — never produced output)
- Keep import logging + urllib3 suppression lines in utils.py (needed to
silence noisy Snowflake connector connection warnings)
- Fix broken `from typing import T` → TypeVar("T") in utils.py
- Replace all console.log() → console.print() across all 4 files
(console.log adds [HH:MM:SS] timestamps and file:line refs — not suitable
for user-facing CLI output)
- Update inspector warning to use [yellow]Warning:[/yellow] prefix
- Add .worktrees/ to .gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename section header 'Drop/create Snowflake objects' → 'Manage Snowflake objects'
- Consolidate dry-run banner: remove per-section log_dry_run_info() calls from
drop_create() and permifrost(), add single call in main() before load_env_var()
- Update log_dry_run_info() to styled '⚠ DRY RUN — no changes will be applied' banner
- Condense load_env_var() output to a single summary line
('Loaded N environment variables from ...' or 'No .env file found, ...')
- Fix unclosed Rich markup tag in Permifrost completed message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hting - Add _count_operations() helper for single-pass counting of CREATE/DROP/ALTER ops - Add _format_summary() to format counts into readable string - Add build_summary_line() public function (returns None for empty list) - Update print_ddl_statements() to show summary line before statements, with red highlighting for DROP count and DROP statements - Add 5 tests for build_summary_line (mixed, empty, only creates, only SET, only UNSET) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename drop_create_objects() → manage_objects() in core.py - Add 'manage' subcommand in cli.py via shared add_manage_args() helper - Keep 'drop_create' as backwards-compatible alias pointing to same handler - Move 'if len(sys.argv) == 1: parser.print_help()' to after subparsers are registered (ensures help is always available) - Update argparse descriptions to use 'Manage' terminology - Update CLAUDE.md: add 'manage' subcommand, fix module table and execution flow descriptions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The message about skipping users without DESCRIBE privileges is informational, not a warning — it's expected behaviour for admin users. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
🚀 Package published to TestPyPI! You can test the installation with: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ tundri==1.3.5.dev23View package: https://test.pypi.org/project/tundri/1.3.5.dev23/ |
| parser_drop_create = subparsers.add_parser("permifrost", help="Run Permifrost") | ||
| parser_drop_create.add_argument( | ||
| parser_permifrost = subparsers.add_parser("permifrost", help="Run Permifrost") | ||
| parser_permifrost.add_argument( |
There was a problem hiding this comment.
[Suggestion] parser_run manually duplicates the args that add_manage_args already encapsulates (-p, --dry, --users-to-skip). If the manage/drop_create args change in the future, run will silently drift out of sync.
Consider reusing the helper:
| parser_permifrost.add_argument( | |
| parser_run = subparsers.add_parser("run", help="Run manage and then permifrost") | |
| add_manage_args(parser_run) | |
| parser_run.set_defaults(func=run) |
add_manage_args already sets func=manage, so you'd need to override it after the call: parser_run.set_defaults(func=run) (as shown above, set_defaults called twice merges; the second call wins).
| create += 1 | ||
| elif s.startswith("DROP"): | ||
| drop += 1 | ||
| elif s.startswith("ALTER"): |
There was a problem hiding this comment.
[Suggestion] " UNSET " in s requires a space after UNSET, which holds for all current Snowflake ALTER UNSET DDL (e.g. ALTER USER foo UNSET rsa_public_key). However it would also match the substring if a SET statement's value happens to contain UNSET (e.g. ALTER USER foo SET comment = 'Please UNSET this'), silently misclassifying it.
A slightly more robust check would inspect only the SQL keyword portion:
elif s.startswith("ALTER"):
keyword = s.split("=")[0].upper() # strip values/assignments
if " UNSET " in keyword or keyword.endswith(" UNSET"):
alter_unset += 1
else:
alter_set += 1Low risk in practice, but worth noting for correctness.
| summary = _format_summary( | ||
| create_count, drop_count, alter_set_count, alter_unset_count | ||
| ) | ||
| if drop_count > 0: |
There was a problem hiding this comment.
[Suggestion] Injecting Rich markup via .replace() is fragile: if drop_count happens to equal another count (e.g. create=2, drop=2), and the format string ever changes to put counts adjacent, the replacement could match unintended substrings. Since _format_summary already owns the string structure, consider passing an highlight_drops=True flag to it so the markup is built at construction time rather than patched afterwards.
Review SummaryOverall this is a clean, well-structured PR. The rename from Three suggestions (no blockers):
Notable positives:
|
|
Claude review cost: $0.7149 · 13 turns · 408s · |
msenyonyi
left a comment
There was a problem hiding this comment.
Don't really have the context to review this, tbh - will set to approve cause it looks fine but I'd need to dedicate a bit more time to understanding Tundri
Summary
console.log()withconsole.print()and remove dead Python logging framework — eliminates timestamps andfile:linereferences from every output line0 CREATE, 23 ALTER (0 SET, 23 UNSET), 0 DROP) with red highlighting for DROP operationsLoaded N environment variables from <path>(also removes plaintext secret printing)⚠ DRY RUNbanner at the top instead of two per-section bannersmanagesubcommand: Primary command for object management;drop_createkept as deprecated aliastyping.Timport, privilege-skip message changed from Warning → InfoBefore / After
Before:
After:
Guardrails
This is a clean reimplementation from
main(replaces #21 which had rebase artifacts):params_to_unset_if_absentfor users: only["rsa_public_key", "rsa_public_key_2"]✓build_summary_linetests (19 total) ✓tundri/constants.py,tundri/parser.py,tundri/objects.py— no changes ✓Test plan
uv run pytest tests/ --ignore=tests/integration_tests -v— 19 passeduv run ruff check . && uv run ruff format --check .— cleantundri manage --dry -p spec.ymlworks (new subcommand)tundri drop_create --dry -p spec.ymlstill works (backwards compat)tundriwith no subcommand shows help instead of crashing🤖 Generated with Claude Code