| name | python-toolsmith |
| version | 1.0.0 |
| description | Production-ready patterns for building Python CLI tools with Click, SQLite, and Rich. Golden scaffolds, anti-patterns, and guardrails for durable command-line applications. Use this skill when building a Python CLI tool, adding Click commands, working with SQLite persistence, or structuring a Python package. Also trigger for "Python CLI", "Click", "command line tool", "Python package", or "SQLite persistence."
|
| author | G-HunterAi |
| license | MIT |
| tags | ["python","cli","click","sqlite","tooling","patterns"] |
| platforms | ["all"] |
| category | tooling |
| metadata | {"clawdbot":{"emoji":"🐍","requires":{"bins":["python3","pip"]}}} |
python-toolsmith
Purpose
Production-ready patterns for building and extending Python CLI tools with Click, SQLite, and Rich. Provides golden scaffolds, guardrails, and anti-patterns to prevent common mistakes and ensure durable, maintainable command-line applications.
When To Use
Use this skill when:
- Building a new Python CLI tool from scratch
- Adding Click commands to an existing tool
- Implementing SQLite persistence in a Python package
- Structuring a Python package with modular source layout
- Setting up logging, config, and error handling guardrails
- Validating user inputs and managing subprocess calls safely
Do NOT use this skill for:
- Web applications (use Flask/FastAPI)
- Pure data processing scripts (write plain Python)
- ML/data science pipelines (use dedicated frameworks)
- System administration tools (evaluate shell scripts first)
Quick Start
1. Generate the Golden Scaffold
mkdir my-tool && cd my-tool
Then use the canonical structure from this skill:
pyproject.toml with Click, Rich, PyYAML dependencies
src/my_tool/ module layout
cli.py with Click command groups and version/help options
db.py with SQLite wrapper and transaction semantics
config.py with XDG config fallback chain
errors.py with stable exit codes (1, 2, 130)
logging_.py with handler deduplication
utils.py with atomic writes and path traversal protection
tests/test_smoke.py with pytest smoke tests
CHANGELOG.md for release tracking
See references/golden-scaffold.md for complete template files.
2. Run Your CLI
pip install -e .
my-tool --help
my-tool --version
my-tool health
3. Add Your Feature
Implement your feature in a new function/module, wire it into a Click command, add a test, then run the Pre-Delivery Checklist (see below).
Non-Negotiables
Your CLI must have:
--help and -h option (use Click's built-in)
--version flag showing semantic version
health command for readiness checks
- Input validation at command boundary (Click validators or Choice)
- Atomic writes for important files (config, state, artifacts)
- Parameterized SQL only (no string interpolation)
- Safe subprocesses (list args, no
shell=True)
Canonical Structure
your-repo/
pyproject.toml
src/your_tool/
__init__.py
__version__.py
cli.py
config.py
logging_.py
errors.py
db.py
utils.py
tests/
test_smoke.py
CHANGELOG.md
Core Concepts
Click Groups & Commands
Click groups organize commands hierarchically. Use @click.group() for the root, then @group.command() for subcommands.
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
@click.version_option(__version__, prog_name="your-tool")
def cli():
"""Your tool description."""
pass
@cli.command()
def health():
"""Check tool readiness."""
click.echo("✓ OK")
Input Validation
Use Click validators and Choice types to validate at the boundary:
def validate_priority(ctx, param, value):
if value < 1 or value > 3:
raise click.BadParameter("Priority must be 1-3")
return value
@cli.command()
@click.option("--priority", type=int, callback=validate_priority, default=2)
@click.option("--status", type=click.Choice(["active", "blocked"]), required=True)
def configure(priority, status):
"""Configure the tool."""
pass
Error Handling
Stable exit codes:
1 — Expected tool error (user mistake, missing config, etc.)
2 — Unexpected bug (assertion, unhandled exception)
130 — Interrupted by Ctrl+C
Use errors.ToolError for expected errors, let exceptions bubble for bugs.
from your_tool.errors import ToolError, handle_error
try:
except ToolError as e:
logger.error(str(e))
sys.exit(1)
except Exception as e:
logger.error(f"Unexpected: {e}")
traceback.print_exc()
sys.exit(2)
SQLite Patterns
Use the golden scaffold db.py as your canonical SQLite wrapper:
- Explicit transaction semantics (commit on success, rollback on exception)
- WAL mode for concurrent access
- Foreign keys enabled
- Busy timeout to reduce lock contention
- Row factory for dict-like access
with Database(db_path) as db:
db.create_schema()
db.execute("INSERT INTO items (name) VALUES (?)", ("x",))
rows = db.fetchall("SELECT * FROM items")
Config & Paths
Follow XDG Base Directory spec with fallback:
- Explicit path (if provided)
./config.json (current directory)
$XDG_CONFIG_HOME/your_tool/config.json (default: ~/.config)
- Defaults (hardcoded)
Environment overrides: YOUR_TOOL_DATA_DIR, YOUR_TOOL_LOG_LEVEL.
Use safe_user_path() to prevent path traversal attacks.
Atomic Writes
For important files (config, state, artifacts), always use atomic writes:
- Write to temp file in same directory (same filesystem)
- Flush and fsync
- Atomic swap with
os.replace()
Reduces corruption risk on crash or disk error.
from your_tool.utils import atomic_write
atomic_write(Path("config.json"), json.dumps(config))
Guardrails
Forbidden Patterns
❌ subprocess.run("string", shell=True) — allows shell injection
❌ SQL via string concatenation — allows SQL injection
❌ Writing without atomic_write() — risks corruption
❌ User paths without safe_user_path() — allows traversal
Required Patterns
✓ subprocess.run([cmd, arg1, arg2], text=True, capture_output=True)
✓ Parameterized SQL: execute("SELECT ? FROM t", params)
✓ atomic_write(path, content) for important files
✓ safe_user_path(base, user_input) for user-provided paths
Testing
Minimal smoke test template:
import shutil
import tempfile
from pathlib import Path
import pytest
@pytest.fixture
def temp_dir():
tmp = Path(tempfile.mkdtemp())
yield tmp
shutil.rmtree(tmp)
def test_db_roundtrip(temp_dir: Path):
from your_tool.db import Database
db_path = temp_dir / "t.db"
with Database(db_path) as db:
db.create_schema()
db.execute("INSERT INTO items (name) VALUES (?)", ("test",))
rows = db.fetchall("SELECT name FROM items")
assert rows[0]["name"] == "test"
def test_atomic_write(temp_dir: Path):
from your_tool.utils import atomic_write
p = temp_dir / "x.txt"
atomic_write(p, "content")
assert p.read_text(encoding="utf-8") == "content"
Pre-Delivery Checklist (Core)
Before releasing a CLI or feature:
Verification
After implementing a feature, verify manually:
pip install -e .
my-tool --help
my-tool --version
my-tool health
my-tool my-command --option value
my-tool --debug my-command
pytest tests/ -v
Works Well With
- postgres-advanced — Use as production DB backend instead of SQLite
- git-github — Integrate CLI tool into version control workflows
- workflow-orchestrator — Use CLI tool as reusable pipeline step
Dependency Policy
- Prefer stdlib
- Use
>= for version constraints (allow patch/minor updates)
- Avoid
== unless freezing is required
sqlite3 is stdlib; do not list in requirements
- Keep optional dependencies under
[project.optional-dependencies]
Recommended Dependencies
dependencies = [
"click>=8.1.7",
"rich>=13.7.0",
"pyyaml>=6.0.1",
"python-dotenv>=1.0.0",
]
[project.optional-dependencies]
embeddings = [
"sentence-transformers>=2.2.2",
"numpy>=1.24.0",
]
test = [
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
]
Roadmap
- v1.0.0 (current): Golden scaffold, Click/SQLite patterns, guardrails
- v1.1.0: Rich table formatting guide, logging best practices
- v2.0.0: Async Click support, connection pooling for persistent DBs, integration templates
For Complete Templates
See the references/ folder:
- golden-scaffold.md — Verbatim copy-paste files for pyproject.toml, cli.py, db.py, config.py, errors.py, logging_.py, utils.py, tests, CHANGELOG
- anti-patterns.md — What NOT to do
- sqlite-patterns.md — Advanced SQLite patterns (transactions, indexes, migrations)