typer integration and header for PAD AUF

This commit is contained in:
Alexander Domene
2025-10-27 09:44:07 +01:00
parent 8650bd09a3
commit 7c07d80747
48 changed files with 15010 additions and 145 deletions

641
TYPER_MIGRATION_PLAN.md Normal file
View 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?