Skip to content

Commit

Permalink
feat: Add CLI Command xml2json (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
hf-kklein authored Dec 5, 2024
1 parent b4882fc commit c285a28
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 3 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ jobs:
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13"]
pydantic: [true, false]
pydantic: [install_pydantic, skip_pydantic]
cli: [install_typer, skip_typer]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
Expand All @@ -23,8 +24,11 @@ jobs:
python -m pip install --upgrade pip
pip install tox
- name: install pydantic if requested
if: matrix.run_step == 'true'
if: matrix.run_step == 'install_pydantic'
run: pip install .[pydantic]
- name: install typer if requested
if: matrix.run_step == 'install_typer'
run: pip install .[cli]
- name: Run the Unit Tests via Tox
run: |
tox -e tests
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,20 @@ Das Ergebnis sieht dann so aus:
"codes": []
},
```
### CLI Tool für XML➡️JSON Konvertierung
Mit
```bash
pip install fundamend[cli]
```
Kann ein CLI-Tool in der entsprechenden venv installiert werden, das einzelne MIG- und AHB-XML-Dateien in entsprechende JSONs konvertiert:
```bash
(myvenv): xml2json path/to/mig.xml
```
erzeugt `path/to/mig.json`. Und
```bash
(myvenv): xml2json path/to/my/directory
```
konvertiert alle XML-Dateien im entsprechenden Verzeichnis.

### JSON Schemas
Das fundamend Datenmodell ist auch als JSON Schema verfügbar: [`json_schemas`](json_schemas).
Expand Down
1 change: 1 addition & 0 deletions domain-specific-terms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ als
paket
beginn
referenz
alle
10 changes: 10 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ pydantic = [
"pydantic>=2"
# if you install fundamend[pydantic], the dataclasses from pydantic will be used
]
cli = [
"fundamend[pydantic]",
"typer" # if you install fundamend[cli], the cli commands are available via typer
]
spellcheck = [
"codespell==2.3.0"
]
Expand Down Expand Up @@ -65,6 +69,12 @@ profile = "black"
[tool.pylint."MESSAGES CONTROL"]
max-line-length = 120

[project.scripts]
xml2json = "fundamend.cli:main"
# fundamend is the package in the src directory
# .cli means the cli.py module inside the fundamend package
# :main means the def main() function inside the cli.py module

[mypy]
truethy-bool = true

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# pip-compile pyproject.toml
Expand Down
54 changes: 54 additions & 0 deletions src/fundamend/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""contains the entrypoint for the command line interface"""

import json
import sys
from pathlib import Path

import typer
from pydantic import RootModel
from rich.console import Console

from fundamend import AhbReader, Anwendungshandbuch, MessageImplementationGuide, MigReader

app = typer.Typer(help="Convert XML(s) by BDEW to JSON(s)")
err_console = Console(stderr=True) # https://typer.tiangolo.com/tutorial/printing/#printing-to-standard-error


def _convert_to_json_file(xml_file_path: Path) -> Path:
"""converts the given XML file to a JSON file and returns the path of the latter"""
if not xml_file_path.is_file():
raise ValueError(f"The given path {xml_file_path.absolute()} is not a file")
is_ahb = "ahb" in xml_file_path.stem.lower()
is_mig = "mig" in xml_file_path.stem.lower()
if is_ahb and is_mig:
raise ValueError(f"Cannot detect if {xml_file_path} is an AHB or MIG")
root_model: RootModel[Anwendungshandbuch] | RootModel[MessageImplementationGuide]
if is_ahb:
ahb_model = AhbReader(xml_file_path).read()
root_model = RootModel[Anwendungshandbuch](ahb_model)
elif is_mig:
mig_model = MigReader(xml_file_path).read()
root_model = RootModel[MessageImplementationGuide](mig_model)
else:
raise ValueError(f"Seems like {xml_file_path} is neither an AHB nor a MIG")
out_dict = root_model.model_dump(mode="json")
json_file_path = xml_file_path.with_suffix(".json")
with open(json_file_path, encoding="utf-8", mode="w") as outfile:
json.dump(out_dict, outfile, indent=True, ensure_ascii=False)
print(f"Successfully converted {xml_file_path} file to JSON {json_file_path}")
return json_file_path


@app.command()
def main(xml_in_path: Path) -> None:
"""
converts the xml file from xml_in_path to a json file next to the .xml
"""
if not xml_in_path.exists():
err_console.print(f"The path {xml_in_path.absolute()} does not exist")
sys.exit(1)
if xml_in_path.is_dir():
for xml_path in xml_in_path.rglob("*.xml"):
_convert_to_json_file(xml_path)
else:
_convert_to_json_file(xml_in_path)
3 changes: 3 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ commands = python -m pytest --basetemp={envtmpdir} {posargs}
deps =
{[testenv:tests]deps}
.[linting]
.[cli]
# add your fixtures like e.g. pytest_datafiles here
setenv = PYTHONPATH = {toxinidir}/src
commands =
Expand All @@ -37,6 +38,7 @@ deps =
{[testenv:tests]deps}
.[type_check]
.[pydantic]
.[cli]
commands =
mypy --show-error-codes src/fundamend --strict
mypy --show-error-codes unittests --strict
Expand All @@ -60,6 +62,7 @@ deps =
{[testenv:tests]deps}
.[coverage]
.[pydantic]
.[cli]
setenv = PYTHONPATH = {toxinidir}/src
commands =
coverage run -m pytest --basetemp={envtmpdir} {posargs}
Expand Down
54 changes: 54 additions & 0 deletions unittests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from pathlib import Path

import pytest

_SKIP_TESTS = False
try:
from typer.testing import CliRunner

from fundamend.cli import app
except ImportError:
_SKIP_TESTS = True


def _copy_xml_file(inpath: Path, outpath: Path) -> None:
with open(outpath, encoding="utf-8", mode="w") as outfile:
with open(inpath, encoding="utf-8", mode="r") as infile:
outfile.write(infile.read())


def test_cli_single_file_mig(tmp_path: Path) -> None:
if _SKIP_TESTS:
pytest.skip("Seems like typer is not installed")
original_mig_file = Path(__file__).parent / "example_files" / "UTILTS_MIG_1.1c_Lesefassung_2023_12_12.xml"
tmp_mig_path = tmp_path / "my_mig.xml"
_copy_xml_file(original_mig_file, tmp_mig_path)
runner = CliRunner()
runner.invoke(app, [str(tmp_mig_path)])
assert (tmp_path / "my_mig.json").exists()


def test_cli_single_file_ahb(tmp_path: Path) -> None:
if _SKIP_TESTS:
pytest.skip("Seems like typer is not installed")
original_ahb_file = Path(__file__).parent / "example_files" / "UTILTS_AHB_1.1d_Konsultationsfassung_2024_04_02.xml"
tmp_ahb_path = tmp_path / "my_ahb.xml"
_copy_xml_file(original_ahb_file, tmp_ahb_path)
runner = CliRunner()
runner.invoke(app, [str(tmp_ahb_path)])
assert (tmp_path / "my_ahb.json").exists()


def test_cli_directory(tmp_path: Path) -> None:
if _SKIP_TESTS:
pytest.skip("Seems like typer is not installed")
original_mig_file = Path(__file__).parent / "example_files" / "UTILTS_MIG_1.1c_Lesefassung_2023_12_12.xml"
tmp_mig_path = tmp_path / "my_mig.xml"
original_ahb_file = Path(__file__).parent / "example_files" / "UTILTS_AHB_1.1d_Konsultationsfassung_2024_04_02.xml"
tmp_ahb_path = tmp_path / "my_ahb.xml"
_copy_xml_file(original_ahb_file, tmp_ahb_path)
_copy_xml_file(original_mig_file, tmp_mig_path)
runner = CliRunner()
runner.invoke(app, [str(tmp_path)])
assert (tmp_path / "my_mig.json").exists()
assert (tmp_path / "my_ahb.json").exists()

0 comments on commit c285a28

Please sign in to comment.