15 KiB
Typer Migration Plan
Executive Summary
Migrate the FHIR to PADneXt converter from argparse to typer for improved CLI experience, better type safety, and enhanced usability.
Estimated Time: 1-2 hours Risk Level: LOW (backward compatible) Breaking Changes: NONE (command-line interface remains identical)
Why Migrate to Typer?
Current State (argparse)
p = argparse.ArgumentParser(description="FHIR JSON -> PAD XML converter")
p.add_argument("--input-json", required=True, help="Path to FHIR Bundle JSON")
p.add_argument("--output-dir", default=".", help="Directory to save output files")
p.add_argument("--verbose", action="store_true", help="Show detailed output")
args = p.parse_args()
Benefits of Typer
-
Better Type Safety
- Uses Python type hints natively
- Automatic type validation
- IDE autocomplete support
-
Cleaner Code
- Less boilerplate
- More Pythonic
- Function parameters = CLI arguments
-
Better UX
- Beautiful help messages (with Rich integration)
- Color-coded output
- Better error messages
- Auto-generated documentation
-
Modern Stack
- Built on top of Click (industry standard)
- Active development
- Large community
-
Enhanced Features
- Progress bars
- Prompts/confirmations
- Shell completion (bash, zsh, fish)
- Subcommands support (future extensibility)
Current CLI Interface
Arguments (9 total)
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
--input-json |
str | Yes | - | Path to FHIR Bundle JSON |
--output-dir |
str | No | "." | Directory to save output files |
--verbose |
bool | No | False | Show detailed output |
--fhir-json-schema |
str | No | None | Path to FHIR JSON Schema |
--pad-xsd |
str | No | None | Path to PAD XML XSD |
--header-cfg |
str | No | None | Path to header config JSON |
--placeholder-cfg |
str | No | None | Path to placeholder config JSON |
--mapping-config |
str | No | "mapping_config.json" | Path to mapping config JSON |
--concept-maps |
str | No | None | Path to ConceptMap directory/file |
Current Usage
python3 fhir_to_pad_converter.py \
--input-json samples/fhir/sample_1/data.json \
--output-dir . \
--pad-xsd specs/padnext/padx_adl_v2.12.xsd \
--placeholder-cfg placeholder_config.json \
--verbose
Proposed Typer Implementation
New Signature
import typer
from typing import Optional
from pathlib import Path
app = typer.Typer(
name="fhir2pad",
help="FHIR JSON to PADneXt XML converter with validation & statistics",
add_completion=True
)
@app.command()
def convert(
input_json: Path = typer.Option(
...,
"--input-json",
"-i",
exists=True,
file_okay=True,
dir_okay=False,
readable=True,
resolve_path=True,
help="Path to FHIR Bundle JSON file"
),
output_dir: Path = typer.Option(
".",
"--output-dir",
"-o",
file_okay=False,
dir_okay=True,
writable=True,
resolve_path=True,
help="Directory to save output files"
),
verbose: bool = typer.Option(
False,
"--verbose",
"-v",
help="Show detailed output on console"
),
fhir_json_schema: Optional[Path] = typer.Option(
None,
"--fhir-json-schema",
exists=True,
file_okay=True,
readable=True,
help="Optional path to FHIR JSON Schema"
),
pad_xsd: Optional[Path] = typer.Option(
None,
"--pad-xsd",
exists=True,
file_okay=True,
readable=True,
help="Optional path to PAD XML XSD schema"
),
header_cfg: Optional[Path] = typer.Option(
None,
"--header-cfg",
exists=True,
file_okay=True,
readable=True,
help="Optional header config JSON (fills static fields)"
),
placeholder_cfg: Optional[Path] = typer.Option(
None,
"--placeholder-cfg",
exists=True,
file_okay=True,
readable=True,
help="Optional placeholder config JSON (fills missing fields)"
),
mapping_config: Path = typer.Option(
"mapping_config.json",
"--mapping-config",
"-m",
help="Path to mapping config JSON"
),
concept_maps: Optional[Path] = typer.Option(
None,
"--concept-maps",
help="Path to ConceptMap directory or file"
),
):
"""
Convert FHIR Bundle JSON to PADneXt 2.12 XML format.
Validates both input FHIR data and output PAD XML, producing detailed
diagnostic reports.
Example:
fhir2pad convert --input-json input.json --output-dir ./results
"""
# Implementation stays the same
pass
def main():
app()
if __name__ == "__main__":
main()
Key Improvements
-
Path Type with Built-in Validation
Pathobjects instead of stringsexists=Truevalidates file existsreadable=Truechecks permissionsresolve_path=Trueconverts to absolute paths- Removes need for our custom
validate_file_path()in main()
-
Short Aliases
-ifor--input-json-ofor--output-dir-vfor--verbose-mfor--mapping-config
-
Type Safety
Pathtype automatically converts stringsOptional[Path]for nullable pathsboolfor flags
-
Better Help
- Auto-generated from docstring
- Parameter descriptions from
help= - Type information displayed
- Default values shown
Enhanced Features (Optional Additions)
1. Rich Output (Optional)
from rich.console import Console
from rich.progress import Progress
console = Console()
# Colorful success messages
console.print("[green]✓ Conversion completed successfully![/green]")
# Progress bars for large files
with Progress() as progress:
task = progress.add_task("[cyan]Converting...", total=100)
# ... conversion logic
progress.update(task, advance=10)
2. Confirmation Prompts (Optional)
# Warn before overwriting
if output_file.exists():
if not typer.confirm(f"Output file exists. Overwrite?"):
raise typer.Abort()
3. Shell Completion (Automatic)
# Users can install completion
python3 fhir_to_pad_converter.py --install-completion
# Then use tab completion
python3 fhir_to_pad_converter.py --input-<TAB>
# Shows: --input-json
4. Future Subcommands (Extensibility)
@app.command()
def convert(...):
"""Convert FHIR to PAD XML"""
pass
@app.command()
def validate(...):
"""Validate PAD XML only"""
pass
@app.command()
def batch(...):
"""Batch convert multiple files"""
pass
Implementation Plan
Phase 1: Dependencies (5 min)
Update requirements.txt:
# Existing
lxml==4.9.4
jsonschema==4.19.2
# New
typer[all]==0.9.0 # Includes rich for beautiful output
Update requirements-dev.txt:
-r requirements.txt
pytest==7.4.3
pytest-cov==4.1.0
Phase 2: Code Changes (30 min)
File: fhir_to_pad_converter.py
-
Add imports (top of file):
import typer from typing import Optional from pathlib import Path -
Create Typer app (before main()):
app = typer.Typer( name="fhir2pad", help="FHIR JSON to PADneXt XML converter", add_completion=True ) -
Replace main() function:
- Change signature to use typer decorators
- Remove argparse code
- Remove manual path validation (typer does it)
- Keep all business logic
-
Update main:
if __name__ == "__main__": app()
Phase 3: Simplifications (15 min)
Remove redundant validation:
Since Typer validates paths automatically:
# REMOVE these blocks (typer handles it)
try:
input_json = validate_file_path(args.input_json, must_exist=True)
except FileNotFoundError as e:
logger.error(f"File not found: {e}")
return 1
# REPLACE with simple conversion
input_json = str(input_json) # Path to string if needed
Keep custom validation for:
- Config file schema validation (our custom logic)
- Output directory creation (custom logic)
Phase 4: Testing (30 min)
Update test_fhir_to_pad_converter.py:
Add CLI tests using typer's testing utilities:
from typer.testing import CliRunner
from fhir_to_pad_converter import app
runner = CliRunner()
def test_cli_help():
"""Test --help output."""
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "FHIR JSON to PADneXt XML" in result.output
def test_cli_missing_required_arg():
"""Test error when required argument missing."""
result = runner.invoke(app, [])
assert result.exit_code != 0
assert "Missing option '--input-json'" in result.output
def test_cli_invalid_file():
"""Test error with nonexistent file."""
result = runner.invoke(app, ["--input-json", "nonexistent.json"])
assert result.exit_code != 0
assert "does not exist" in result.output
def test_cli_full_conversion(tmp_path):
"""Test complete conversion workflow."""
# Create test input file
input_file = tmp_path / "input.json"
input_file.write_text('{"resourceType": "Bundle", ...}')
result = runner.invoke(app, [
"--input-json", str(input_file),
"--output-dir", str(tmp_path)
])
assert result.exit_code == 0
assert "SUCCESS" in result.output
Phase 5: Documentation (15 min)
Update CLAUDE.md:
## Common Commands
### Convert FHIR to PAD XML
Basic conversion:
```bash
python3 fhir_to_pad_converter.py --input-json Input.json --output-dir .
# Or with short aliases:
python3 fhir_to_pad_converter.py -i Input.json -o .
With validation and verbose output:
python3 fhir_to_pad_converter.py \
-i samples/fhir/sample_1/data.json \
-o . \
--pad-xsd specs/padnext/padx_adl_v2.12.xsd \
--verbose
Shell Completion (Optional)
Install for your shell:
# Bash
python3 fhir_to_pad_converter.py --install-completion bash
# Zsh
python3 fhir_to_pad_converter.py --install-completion zsh
# Fish
python3 fhir_to_pad_converter.py --install-completion fish
---
## Backward Compatibility
### ✅ Fully Backward Compatible
**Same command-line interface**:
```bash
# This still works exactly the same
python3 fhir_to_pad_converter.py --input-json input.json --output-dir .
Same arguments: All existing arguments preserved Same defaults: All default values unchanged Same behavior: Identical functionality
🆕 New Features (Bonus)
Short aliases (optional to use):
# New short form (optional)
python3 fhir_to_pad_converter.py -i input.json -o .
# Old long form (still works)
python3 fhir_to_pad_converter.py --input-json input.json --output-dir .
Better error messages:
# Before (argparse)
error: the following arguments are required: --input-json
# After (typer)
Error: Missing option '--input-json' / '-i'.
Colored output (if rich installed):
- Green for success
- Red for errors
- Yellow for warnings
- Cyan for info
Migration Steps Summary
- ✅ Add
typer[all]to requirements.txt - ✅ Import typer and Path at top of file
- ✅ Create typer app instance
- ✅ Replace main() with @app.command() decorated function
- ✅ Convert argparse arguments to typer parameters
- ✅ Remove redundant path validation (typer handles it)
- ✅ Update main to call app()
- ✅ Add CLI tests
- ✅ Update documentation
- ✅ Run full test suite
Code Size Comparison
Before (argparse)
# ~150 lines in main() function
def main():
p = argparse.ArgumentParser(...)
p.add_argument("--input-json", required=True, ...)
p.add_argument("--output-dir", default=".", ...)
# ... 7 more arguments
args = p.parse_args()
# Manual validation (~50 lines)
try:
input_json = validate_file_path(args.input_json, ...)
# ... more validation
except FileNotFoundError as e:
logger.error(...)
return 1
# ... rest of function
After (typer)
# ~100 lines (33% reduction)
@app.command()
def convert(
input_json: Path = typer.Option(..., exists=True, readable=True, ...),
output_dir: Path = typer.Option(".", ...),
# ... 7 more parameters
):
"""Convert FHIR to PAD XML."""
# No manual validation needed - typer does it!
# input_json is already validated, absolute path
# Business logic starts immediately
# ... rest of function
Reduction: ~50 lines of boilerplate removed
Risks & Mitigation
Risk 1: New Dependency
Impact: LOW Mitigation:
- Typer is stable (v0.9.0)
- Widely used (182k+ downloads/month)
- Maintained by FastAPI author (Sebastian Ramirez)
Risk 2: Breaking Changes
Impact: NONE Mitigation:
- All existing arguments preserved
- Same CLI interface
- Fully backward compatible
Risk 3: Learning Curve
Impact: LOW Mitigation:
- Simpler than argparse
- Better documentation
- Type hints make it self-documenting
Risk 4: Test Coverage
Impact: LOW Mitigation:
- Add comprehensive CLI tests
- Test both new and old usage patterns
Success Criteria
✅ All existing commands work unchanged ✅ All tests pass ✅ Better error messages ✅ Shorter, cleaner code ✅ Shell completion available ✅ Documentation updated
Recommendation
APPROVE: This migration provides significant benefits with minimal risk:
Benefits
- ✅ Cleaner, more maintainable code (-33% boilerplate)
- ✅ Better type safety (Path types, IDE support)
- ✅ Better UX (colored output, better errors)
- ✅ Modern CLI standard (typer/click)
- ✅ Future extensibility (subcommands)
Minimal Risk
- ✅ 100% backward compatible
- ✅ No breaking changes
- ✅ Stable, popular library
- ✅ Easy to test
Effort
- ⏱️ 1-2 hours total implementation
- ⏱️ 30 minutes testing
- ⏱️ 15 minutes documentation
Optional Enhancements (Future)
Once typer is integrated, these become easy to add:
- Progress bars for large files
- Interactive mode with prompts
- Batch processing subcommand
- Validation-only subcommand
- Statistics subcommand
- Config wizard for generating configs
- Beautiful tables for reports (Rich)
Questions?
-
Should we add short aliases (-i, -o, -v)? Recommended: YES (convenience, no breaking change)
-
Should we add Rich for colored output? Recommended: YES (included in typer[all], better UX)
-
Should we add shell completion? Recommended: YES (automatic with typer)
-
Should we add CLI tests? Recommended: YES (ensure reliability)
Approval Needed
Please approve or provide feedback:
- Approve migration to typer
- Add short aliases (-i, -o, -v)
- Install Rich for colored output
- Add shell completion support
- Add comprehensive CLI tests
- Any changes to the plan?