typer integration and header for PAD AUF
This commit is contained in:
641
TYPER_MIGRATION_PLAN.md
Normal file
641
TYPER_MIGRATION_PLAN.md
Normal file
@@ -0,0 +1,641 @@
|
||||
# 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)
|
||||
```python
|
||||
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
|
||||
|
||||
1. **Better Type Safety**
|
||||
- Uses Python type hints natively
|
||||
- Automatic type validation
|
||||
- IDE autocomplete support
|
||||
|
||||
2. **Cleaner Code**
|
||||
- Less boilerplate
|
||||
- More Pythonic
|
||||
- Function parameters = CLI arguments
|
||||
|
||||
3. **Better UX**
|
||||
- Beautiful help messages (with Rich integration)
|
||||
- Color-coded output
|
||||
- Better error messages
|
||||
- Auto-generated documentation
|
||||
|
||||
4. **Modern Stack**
|
||||
- Built on top of Click (industry standard)
|
||||
- Active development
|
||||
- Large community
|
||||
|
||||
5. **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
|
||||
```bash
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
1. **Path Type with Built-in Validation**
|
||||
- `Path` objects instead of strings
|
||||
- `exists=True` validates file exists
|
||||
- `readable=True` checks permissions
|
||||
- `resolve_path=True` converts to absolute paths
|
||||
- **Removes need for our custom `validate_file_path()` in main()**
|
||||
|
||||
2. **Short Aliases**
|
||||
- `-i` for `--input-json`
|
||||
- `-o` for `--output-dir`
|
||||
- `-v` for `--verbose`
|
||||
- `-m` for `--mapping-config`
|
||||
|
||||
3. **Type Safety**
|
||||
- `Path` type automatically converts strings
|
||||
- `Optional[Path]` for nullable paths
|
||||
- `bool` for flags
|
||||
|
||||
4. **Better Help**
|
||||
- Auto-generated from docstring
|
||||
- Parameter descriptions from `help=`
|
||||
- Type information displayed
|
||||
- Default values shown
|
||||
|
||||
---
|
||||
|
||||
## Enhanced Features (Optional Additions)
|
||||
|
||||
### 1. Rich Output (Optional)
|
||||
```python
|
||||
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)
|
||||
```python
|
||||
# Warn before overwriting
|
||||
if output_file.exists():
|
||||
if not typer.confirm(f"Output file exists. Overwrite?"):
|
||||
raise typer.Abort()
|
||||
```
|
||||
|
||||
### 3. Shell Completion (Automatic)
|
||||
```bash
|
||||
# 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)
|
||||
```python
|
||||
@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**:
|
||||
```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**:
|
||||
```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**
|
||||
|
||||
1. **Add imports** (top of file):
|
||||
```python
|
||||
import typer
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
```
|
||||
|
||||
2. **Create Typer app** (before main()):
|
||||
```python
|
||||
app = typer.Typer(
|
||||
name="fhir2pad",
|
||||
help="FHIR JSON to PADneXt XML converter",
|
||||
add_completion=True
|
||||
)
|
||||
```
|
||||
|
||||
3. **Replace main() function**:
|
||||
- Change signature to use typer decorators
|
||||
- Remove argparse code
|
||||
- Remove manual path validation (typer does it)
|
||||
- Keep all business logic
|
||||
|
||||
4. **Update __main__**:
|
||||
```python
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
```
|
||||
|
||||
### Phase 3: Simplifications (15 min)
|
||||
|
||||
**Remove redundant validation**:
|
||||
|
||||
Since Typer validates paths automatically:
|
||||
```python
|
||||
# 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:
|
||||
```python
|
||||
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**:
|
||||
```markdown
|
||||
## 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:
|
||||
```bash
|
||||
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
|
||||
# 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):
|
||||
```bash
|
||||
# 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**:
|
||||
```bash
|
||||
# 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
|
||||
|
||||
1. ✅ Add `typer[all]` to requirements.txt
|
||||
2. ✅ Import typer and Path at top of file
|
||||
3. ✅ Create typer app instance
|
||||
4. ✅ Replace main() with @app.command() decorated function
|
||||
5. ✅ Convert argparse arguments to typer parameters
|
||||
6. ✅ Remove redundant path validation (typer handles it)
|
||||
7. ✅ Update __main__ to call app()
|
||||
8. ✅ Add CLI tests
|
||||
9. ✅ Update documentation
|
||||
10. ✅ Run full test suite
|
||||
|
||||
---
|
||||
|
||||
## Code Size Comparison
|
||||
|
||||
### Before (argparse)
|
||||
```python
|
||||
# ~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)
|
||||
```python
|
||||
# ~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:
|
||||
|
||||
1. **Progress bars** for large files
|
||||
2. **Interactive mode** with prompts
|
||||
3. **Batch processing** subcommand
|
||||
4. **Validation-only** subcommand
|
||||
5. **Statistics** subcommand
|
||||
6. **Config wizard** for generating configs
|
||||
7. **Beautiful tables** for reports (Rich)
|
||||
|
||||
---
|
||||
|
||||
## Questions?
|
||||
|
||||
1. Should we add short aliases (-i, -o, -v)?
|
||||
**Recommended**: YES (convenience, no breaking change)
|
||||
|
||||
2. Should we add Rich for colored output?
|
||||
**Recommended**: YES (included in typer[all], better UX)
|
||||
|
||||
3. Should we add shell completion?
|
||||
**Recommended**: YES (automatic with typer)
|
||||
|
||||
4. 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?
|
||||
|
||||
Reference in New Issue
Block a user