From f4e55cb0f2642e660022d2daea6a4de4d7f10a8d Mon Sep 17 00:00:00 2001 From: Tim Schrader Date: Sat, 10 Jan 2026 12:03:10 +0100 Subject: [PATCH 01/10] [wip] restructuring Signed-off-by: Tim Schrader --- README.md | 89 ++++-- {.github => old/.github}/workflows/python.yml | 0 old/.pylintrc | 5 + {.vscode => old/.vscode}/settings.json | 0 Pipfile => old/Pipfile | 0 Pipfile.lock => old/Pipfile.lock | 0 old/README.md | 34 +++ requirements.txt => old/requirements.txt | 0 .../update_dsa_sheet}/__init__.py | 0 pyproject.toml | 28 ++ src/update_dsa_sheet/__init__.py | 5 + src/update_dsa_sheet/__main__.py | 4 + src/update_dsa_sheet/cli.py | 34 +++ .../update_dsa_sheet}/dsa_soup.py | 0 .../update_dsa_sheet}/hero_characteristics.py | 0 tests/conftest.py | 27 ++ .../resources}/character_sheet.html | 0 .../characteristics_and_talents.html | 0 ...characteristics_and_talents_annotated.html | 0 ...eristics_and_talents_annotated_custom.html | 0 tests/test_dsa_soup.py | 95 +++++++ .../test_hero_characteristics.py | 1 - update_dsa_sheet/__main__.py | 31 --- update_dsa_sheet/test/test_dsa_soup.py | 142 ---------- uv.lock | 255 ++++++++++++++++++ 25 files changed, 561 insertions(+), 189 deletions(-) rename {.github => old/.github}/workflows/python.yml (100%) create mode 100644 old/.pylintrc rename {.vscode => old/.vscode}/settings.json (100%) rename Pipfile => old/Pipfile (100%) rename Pipfile.lock => old/Pipfile.lock (100%) create mode 100644 old/README.md rename requirements.txt => old/requirements.txt (100%) rename {update_dsa_sheet => old/update_dsa_sheet}/__init__.py (100%) create mode 100644 pyproject.toml create mode 100644 src/update_dsa_sheet/__init__.py create mode 100644 src/update_dsa_sheet/__main__.py create mode 100644 src/update_dsa_sheet/cli.py rename {update_dsa_sheet => src/update_dsa_sheet}/dsa_soup.py (100%) rename {update_dsa_sheet => src/update_dsa_sheet}/hero_characteristics.py (100%) create mode 100644 tests/conftest.py rename {update_dsa_sheet/test/test_resources => tests/resources}/character_sheet.html (100%) rename {update_dsa_sheet/test/test_resources => tests/resources}/characteristics_and_talents.html (100%) rename {update_dsa_sheet/test/test_resources => tests/resources}/characteristics_and_talents_annotated.html (100%) rename {update_dsa_sheet/test/test_resources => tests/resources}/characteristics_and_talents_annotated_custom.html (100%) create mode 100644 tests/test_dsa_soup.py rename {update_dsa_sheet/test => tests}/test_hero_characteristics.py (99%) delete mode 100644 update_dsa_sheet/__main__.py delete mode 100644 update_dsa_sheet/test/test_dsa_soup.py create mode 100644 uv.lock diff --git a/README.md b/README.md index 254cf2b..d51f683 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,93 @@ -# DSA Helper Scripts +# update-dsa-sheet -Helper Scripts for the Pen&Paper game "Das Schwarze Auge". +A small CLI helper for **Das Schwarze Auge (DSA)** character sheets exported as HTML from **Helden Software**. -The scripts are designed to work in concert with the [Helden Software](https://www.helden-software.de/) +It annotates the talent table with the hero’s current characteristic values, so entries like: -## Update DSA Sheet +``` +Athletik | (GE/KO/KK) | BEx2 | 7 +``` + +become: + +``` +Athletik | ( GE[14] / KO[16] / KK[15] ) | BEx2 | 7 +``` + +The tool **modifies the input file in-place** and creates a timestamped backup next to it. + +## Requirements + +- Python 3.11+ (recommended) +- Works best with `uv`, but can also be installed with `pip` + +## Install and run with `uv` (recommended) + +### Run without installing (from the repo) + +```bash +uv run update-dsa-sheet path/to/character_sheet.html +``` + +### Install into a virtual environment + +```bash +uv sync +uv run update-dsa-sheet path/to/character_sheet.html +``` -Add the current characteristic values to the talent table +### Alternative module invocation -e.g. the talent table row +```bash +uv run python -m update_dsa_sheet path/to/character_sheet.html ``` -Athletik | (GE/KO/KK) | BEx2 | 7 + +## Install and run without `uv` + +### Using `pip` in a virtual environment + +```bash +python -m venv .venv +source .venv/bin/activate + +pip install -e . ``` -is converted to + +Run: + +```bash +update-dsa-sheet path/to/character_sheet.html ``` -Athletik | ( GE[14] / KO[16] / KK[15] ) | BEx2 | 7 + +Or as a module: + +```bash +python -m update_dsa_sheet path/to/character_sheet.html ``` -### Usage +## What it does +- Parses the HTML character sheet +- Extracts the hero’s characteristic values (MU/KL/IN/CH/FF/GE/KO/KK) +- Updates talent rows by expanding the characteristic shorthand with the current values +- Writes the updated HTML back to the original file and creates a `*.bak_` backup + +## Development + +Run tests: ```bash -python3 -m update_dsa_sheet {CHARACTER_SHEET} +uv run pytest ``` -or +(Without `uv`): ```bash -uv run --with-requirements requirements.txt -- python -m update_dsa_sheet +pytest ``` -# ToDo -- add table with meta-talents described in "Wege des Schwerts" +## Notes + +- Input file is changed in-place; the backup file is created automatically. +- This project is not affiliated with Helden Software. + diff --git a/.github/workflows/python.yml b/old/.github/workflows/python.yml similarity index 100% rename from .github/workflows/python.yml rename to old/.github/workflows/python.yml diff --git a/old/.pylintrc b/old/.pylintrc new file mode 100644 index 0000000..8318223 --- /dev/null +++ b/old/.pylintrc @@ -0,0 +1,5 @@ +[MASTER] +disable= + C0114, # missing-module-docstring + C0115, # missing-class-docstring + C0116, # missing-function-docstring diff --git a/.vscode/settings.json b/old/.vscode/settings.json similarity index 100% rename from .vscode/settings.json rename to old/.vscode/settings.json diff --git a/Pipfile b/old/Pipfile similarity index 100% rename from Pipfile rename to old/Pipfile diff --git a/Pipfile.lock b/old/Pipfile.lock similarity index 100% rename from Pipfile.lock rename to old/Pipfile.lock diff --git a/old/README.md b/old/README.md new file mode 100644 index 0000000..254cf2b --- /dev/null +++ b/old/README.md @@ -0,0 +1,34 @@ +# DSA Helper Scripts + +Helper Scripts for the Pen&Paper game "Das Schwarze Auge". + +The scripts are designed to work in concert with the [Helden Software](https://www.helden-software.de/) + +## Update DSA Sheet + +Add the current characteristic values to the talent table + +e.g. the talent table row +``` +Athletik | (GE/KO/KK) | BEx2 | 7 +``` +is converted to +``` +Athletik | ( GE[14] / KO[16] / KK[15] ) | BEx2 | 7 +``` + +### Usage + + +```bash +python3 -m update_dsa_sheet {CHARACTER_SHEET} +``` + +or + +```bash +uv run --with-requirements requirements.txt -- python -m update_dsa_sheet +``` + +# ToDo +- add table with meta-talents described in "Wege des Schwerts" diff --git a/requirements.txt b/old/requirements.txt similarity index 100% rename from requirements.txt rename to old/requirements.txt diff --git a/update_dsa_sheet/__init__.py b/old/update_dsa_sheet/__init__.py similarity index 100% rename from update_dsa_sheet/__init__.py rename to old/update_dsa_sheet/__init__.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5078b2a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[project] +name = "update-dsa-sheet" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +authors = [{ name = "Tim Schrader", email = "tim.schrader92@gmail.com" }] +requires-python = ">=3.13" +dependencies = [ + "beautifulsoup4>=4.12.3", +] + +[project.scripts] +update-dsa-sheet = "update_dsa_sheet:main" + +[dependency-groups] +dev = [ + "pytest>=8.3.2", + "coverage>=7.6.1", + "pylint>=3.2.6", + "isort>=5.13.2", +] + +[build-system] +requires = ["uv_build>=0.9.24,<0.10.0"] +build-backend = "uv_build" + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/src/update_dsa_sheet/__init__.py b/src/update_dsa_sheet/__init__.py new file mode 100644 index 0000000..a9b50fb --- /dev/null +++ b/src/update_dsa_sheet/__init__.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from .cli import main + +__all__ = ["main"] diff --git a/src/update_dsa_sheet/__main__.py b/src/update_dsa_sheet/__main__.py new file mode 100644 index 0000000..bfdcd0c --- /dev/null +++ b/src/update_dsa_sheet/__main__.py @@ -0,0 +1,4 @@ +from .cli import main + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/update_dsa_sheet/cli.py b/src/update_dsa_sheet/cli.py new file mode 100644 index 0000000..8cfefd5 --- /dev/null +++ b/src/update_dsa_sheet/cli.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +import argparse +import os +from datetime import datetime + +from .dsa_soup import DsaSoup + + +def back_up_input_file(file: str) -> None: + os.rename(file, f"{file}.bak_{datetime.now().isoformat()}") + + +def save_modified_soup(output_file: str, soup: DsaSoup) -> None: + with open(output_file, "w", encoding="utf8") as file: + file.write(soup.serialize()) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(prog="update-dsa-sheet") + parser.add_argument( + "character_sheet", + type=str, + help="Path to the character sheet", + ) + args = parser.parse_args(argv) + + input_file = args.character_sheet + character_sheet = DsaSoup.from_file(input_file) + character_sheet.annotate_talents_with_characteristics_values() + + back_up_input_file(input_file) + save_modified_soup(input_file, character_sheet) + return 0 diff --git a/update_dsa_sheet/dsa_soup.py b/src/update_dsa_sheet/dsa_soup.py similarity index 100% rename from update_dsa_sheet/dsa_soup.py rename to src/update_dsa_sheet/dsa_soup.py diff --git a/update_dsa_sheet/hero_characteristics.py b/src/update_dsa_sheet/hero_characteristics.py similarity index 100% rename from update_dsa_sheet/hero_characteristics.py rename to src/update_dsa_sheet/hero_characteristics.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..670c571 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest +from bs4 import BeautifulSoup + + +@pytest.fixture(scope="session") +def resources_dir() -> Path: + return Path(__file__).parent / "resources" + + +@pytest.fixture +def load_html(resources_dir: Path): + def _load(name: str) -> str: + return (resources_dir / name).read_text(encoding="utf8") + + return _load + + +@pytest.fixture +def load_soup(load_html): + def _load(name: str) -> BeautifulSoup: + return BeautifulSoup(load_html(name), "html.parser") + + return _load diff --git a/update_dsa_sheet/test/test_resources/character_sheet.html b/tests/resources/character_sheet.html similarity index 100% rename from update_dsa_sheet/test/test_resources/character_sheet.html rename to tests/resources/character_sheet.html diff --git a/update_dsa_sheet/test/test_resources/characteristics_and_talents.html b/tests/resources/characteristics_and_talents.html similarity index 100% rename from update_dsa_sheet/test/test_resources/characteristics_and_talents.html rename to tests/resources/characteristics_and_talents.html diff --git a/update_dsa_sheet/test/test_resources/characteristics_and_talents_annotated.html b/tests/resources/characteristics_and_talents_annotated.html similarity index 100% rename from update_dsa_sheet/test/test_resources/characteristics_and_talents_annotated.html rename to tests/resources/characteristics_and_talents_annotated.html diff --git a/update_dsa_sheet/test/test_resources/characteristics_and_talents_annotated_custom.html b/tests/resources/characteristics_and_talents_annotated_custom.html similarity index 100% rename from update_dsa_sheet/test/test_resources/characteristics_and_talents_annotated_custom.html rename to tests/resources/characteristics_and_talents_annotated_custom.html diff --git a/tests/test_dsa_soup.py b/tests/test_dsa_soup.py new file mode 100644 index 0000000..8f5b26a --- /dev/null +++ b/tests/test_dsa_soup.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest +from bs4 import BeautifulSoup + +from update_dsa_sheet.dsa_soup import DsaSoup +from update_dsa_sheet.hero_characteristics import HeroCharacteristics + + +@pytest.fixture +def empty_html_document() -> str: + return "" + + +@pytest.fixture +def character_sheet_file_path(resources_dir: Path) -> str: + return str(resources_dir / "character_sheet.html") + + +@pytest.fixture +def custom_characteristics() -> HeroCharacteristics: + return HeroCharacteristics( + { + "Mut": 1, + "Klugheit": 2, + "Intuition": 3, + "Charisma": 4, + "Fingerfertigkeit": 5, + "Gewandtheit": 6, + "Konstitution": 7, + "Körperkraft": 8, + } + ) + + +def test_dsa_soup_from_soup(empty_html_document: str): + soup = BeautifulSoup(empty_html_document, "html.parser") + assert isinstance(DsaSoup(soup), DsaSoup) + + +def test_dsa_soup_from_file(character_sheet_file_path: str): + assert isinstance(DsaSoup.from_file(character_sheet_file_path), DsaSoup) + + +def test_returns_the_current_soup(empty_html_document: str): + soup = BeautifulSoup(empty_html_document, "html.parser") + dsa = DsaSoup(soup) + assert dsa.soup == soup + + +def test_fetches_hero_characteristics(character_sheet_file_path: str): + dsa = DsaSoup.from_file(character_sheet_file_path) + characteristics = dsa.characteristics() + + expected = HeroCharacteristics( + { + "Mut": 15, + "Klugheit": 10, + "Intuition": 14, + "Charisma": 8, + "Fingerfertigkeit": 12, + "Gewandtheit": 14, + "Konstitution": 16, + "Körperkraft": 15, + } + ) + assert characteristics == expected + + +@pytest.mark.parametrize( + "expected_file, custom", + [ + ("characteristics_and_talents_annotated.html", None), + ("characteristics_and_talents_annotated_custom.html", "use_custom"), + ], +) +def test_annotation_of_talents_with_characteristics( + load_soup, + expected_file: str, + custom: str | None, + custom_characteristics: HeroCharacteristics, +): + dsa = DsaSoup(load_soup("characteristics_and_talents.html")) + + if custom: + dsa.annotate_talents_with_characteristics_values(custom_characteristics) + else: + dsa.annotate_talents_with_characteristics_values() + + actual = dsa.soup.prettify(formatter=None) + expected = load_soup(expected_file).prettify(formatter=None) + + assert actual == expected diff --git a/update_dsa_sheet/test/test_hero_characteristics.py b/tests/test_hero_characteristics.py similarity index 99% rename from update_dsa_sheet/test/test_hero_characteristics.py rename to tests/test_hero_characteristics.py index 7eaaed4..bd8cf76 100644 --- a/update_dsa_sheet/test/test_hero_characteristics.py +++ b/tests/test_hero_characteristics.py @@ -1,5 +1,4 @@ import pytest - from update_dsa_sheet.hero_characteristics import HeroCharacteristics diff --git a/update_dsa_sheet/__main__.py b/update_dsa_sheet/__main__.py deleted file mode 100644 index fc3c6d3..0000000 --- a/update_dsa_sheet/__main__.py +++ /dev/null @@ -1,31 +0,0 @@ -import os -from datetime import datetime -import argparse -from .dsa_soup import DsaSoup - - -def back_up_input_file(file: str): - os.rename(file, f"{file}.bak_{datetime.now().isoformat()}") - - -def save_modified_soup(output_file: str, soup: DsaSoup): - with open(output_file, "w", encoding="utf8") as file: - file.write(soup.serialize()) - - -parser = argparse.ArgumentParser() -parser.add_argument( - "character_sheet", - action="store", - type=str, - help="Path to the character sheet", -) - -args = parser.parse_args() -input_file = args.character_sheet - -character_sheet = DsaSoup.from_file(input_file) -character_sheet.annotate_talents_with_characteristics_values() - -back_up_input_file(input_file) -save_modified_soup(input_file, character_sheet) diff --git a/update_dsa_sheet/test/test_dsa_soup.py b/update_dsa_sheet/test/test_dsa_soup.py deleted file mode 100644 index 33238d1..0000000 --- a/update_dsa_sheet/test/test_dsa_soup.py +++ /dev/null @@ -1,142 +0,0 @@ -from bs4 import BeautifulSoup -import pytest -from importlib.resources import files -from update_dsa_sheet.dsa_soup import DsaSoup -from update_dsa_sheet.hero_characteristics import HeroCharacteristics - - -@pytest.fixture -def character_sheet_file_path() -> str: - return str( - files("update_dsa_sheet") - .joinpath("test") - .joinpath("test_resources") - .joinpath("character_sheet.html") - ) - - -@pytest.fixture -def empty_html_document() -> str: - return "" - - -@pytest.fixture -def characteristics_and_talents_sheet_soup() -> BeautifulSoup: - path = str( - files("update_dsa_sheet") - .joinpath("test") - .joinpath("test_resources") - .joinpath("characteristics_and_talents.html") - ) - - with open(path, "r", encoding="utf8") as file: - html_content = file.read() - return BeautifulSoup(html_content, "html.parser") - - -@pytest.fixture -def characteristics_and_talents_annotated_sheet_soup() -> BeautifulSoup: - path = str( - files("update_dsa_sheet") - .joinpath("test") - .joinpath("test_resources") - .joinpath("characteristics_and_talents_annotated.html") - ) - - with open(path, "r", encoding="utf8") as file: - html_content = file.read() - return BeautifulSoup(html_content, "html.parser") - - -@pytest.fixture -def characteristics_and_talents_custom_annotated_sheet_soup() -> BeautifulSoup: - path = str( - files("update_dsa_sheet") - .joinpath("test") - .joinpath("test_resources") - .joinpath("characteristics_and_talents_annotated_custom.html") - ) - - with open(path, "r", encoding="utf8") as file: - html_content = file.read() - return BeautifulSoup(html_content, "html.parser") - - -@pytest.fixture -def custom_characteristics() -> HeroCharacteristics: - return HeroCharacteristics( - { - "Mut": 1, - "Klugheit": 2, - "Intuition": 3, - "Charisma": 4, - "Fingerfertigkeit": 5, - "Gewandtheit": 6, - "Konstitution": 7, - "Körperkraft": 8, - } - ) - - -def test_dsa_soup_from_soup(empty_html_document): - soup = BeautifulSoup(empty_html_document, "html.parser") - assert isinstance(DsaSoup(soup), DsaSoup) - - -def test_dsa_soup_from_file(character_sheet_file_path): - assert isinstance(DsaSoup.from_file(character_sheet_file_path), DsaSoup) - - -def test_returns_the_current_soup(empty_html_document): - soup = BeautifulSoup(empty_html_document, "html.parser") - dsa = DsaSoup(soup) - - assert dsa.soup == soup - - -def test_fetches_hero_characteristics(character_sheet_file_path): - dsa = DsaSoup.from_file(character_sheet_file_path) - characteristics = dsa.characteristics() - expected = HeroCharacteristics( - { - "Mut": 15, - "Klugheit": 10, - "Intuition": 14, - "Charisma": 8, - "Fingerfertigkeit": 12, - "Gewandtheit": 14, - "Konstitution": 16, - "Körperkraft": 15, - } - ) - - assert characteristics == expected - - -def test_annotation_of_talents_with_characteristics( - characteristics_and_talents_sheet_soup, - characteristics_and_talents_annotated_sheet_soup, -): - dsa = DsaSoup(characteristics_and_talents_sheet_soup) - dsa.annotate_talents_with_characteristics_values() - dsa = dsa.soup.prettify(formatter=None) - - expected = characteristics_and_talents_annotated_sheet_soup.prettify(formatter=None) - - assert dsa == expected - - -def test_annotation_of_talents_with_custom_characteristics( - characteristics_and_talents_sheet_soup, - custom_characteristics, - characteristics_and_talents_custom_annotated_sheet_soup, -): - dsa = DsaSoup(characteristics_and_talents_sheet_soup) - dsa.annotate_talents_with_characteristics_values(custom_characteristics) - dsa = dsa.soup.prettify(formatter=None) - - expected = characteristics_and_talents_custom_annotated_sheet_soup.prettify( - formatter=None - ) - - assert dsa == expected diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..0c6c9a7 --- /dev/null +++ b/uv.lock @@ -0,0 +1,255 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "astroid" +version = "4.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/ca/c17d0f83016532a1ad87d1de96837164c99d47a3b6bbba28bd597c25b37a/astroid-4.0.3.tar.gz", hash = "sha256:08d1de40d251cc3dc4a7a12726721d475ac189e4e583d596ece7422bc176bda3", size = 406224, upload-time = "2026-01-03T22:14:26.096Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/66/686ac4fc6ef48f5bacde625adac698f41d5316a9753c2b20bb0931c9d4e2/astroid-4.0.3-py3-none-any.whl", hash = "sha256:864a0a34af1bd70e1049ba1e61cee843a7252c826d97825fcee9b2fcbd9e1b14", size = 276443, upload-time = "2026-01-03T22:14:24.412Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", size = 218951, upload-time = "2025-12-28T15:41:16.653Z" }, + { url = "https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", size = 219325, upload-time = "2025-12-28T15:41:18.609Z" }, + { url = "https://files.pythonhosted.org/packages/59/f6/efdabdb4929487baeb7cb2a9f7dac457d9356f6ad1b255be283d58b16316/coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", size = 250309, upload-time = "2025-12-28T15:41:20.629Z" }, + { url = "https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992", size = 252907, upload-time = "2025-12-28T15:41:22.257Z" }, + { url = "https://files.pythonhosted.org/packages/75/38/f1ea837e3dc1231e086db1638947e00d264e7e8c41aa8ecacf6e1e0c05f4/coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4", size = 254148, upload-time = "2025-12-28T15:41:23.87Z" }, + { url = "https://files.pythonhosted.org/packages/7f/43/f4f16b881aaa34954ba446318dea6b9ed5405dd725dd8daac2358eda869a/coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a", size = 250515, upload-time = "2025-12-28T15:41:25.437Z" }, + { url = "https://files.pythonhosted.org/packages/84/34/8cba7f00078bd468ea914134e0144263194ce849ec3baad187ffb6203d1c/coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766", size = 252292, upload-time = "2025-12-28T15:41:28.459Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/cffac66c7652d84ee4ac52d3ccb94c015687d3b513f9db04bfcac2ac800d/coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4", size = 250242, upload-time = "2025-12-28T15:41:30.02Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/9a64d462263dde416f3c0067efade7b52b52796f489b1037a95b0dc389c9/coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398", size = 250068, upload-time = "2025-12-28T15:41:32.007Z" }, + { url = "https://files.pythonhosted.org/packages/69/c8/a8994f5fece06db7c4a97c8fc1973684e178599b42e66280dded0524ef00/coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784", size = 251846, upload-time = "2025-12-28T15:41:33.946Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f7/91fa73c4b80305c86598a2d4e54ba22df6bf7d0d97500944af7ef155d9f7/coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461", size = 221512, upload-time = "2025-12-28T15:41:35.519Z" }, + { url = "https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500", size = 222321, upload-time = "2025-12-28T15:41:37.371Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b8/bdcb7253b7e85157282450262008f1366aa04663f3e3e4c30436f596c3e2/coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9", size = 220949, upload-time = "2025-12-28T15:41:39.553Z" }, + { url = "https://files.pythonhosted.org/packages/70/52/f2be52cc445ff75ea8397948c96c1b4ee14f7f9086ea62fc929c5ae7b717/coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc", size = 219643, upload-time = "2025-12-28T15:41:41.567Z" }, + { url = "https://files.pythonhosted.org/packages/47/79/c85e378eaa239e2edec0c5523f71542c7793fe3340954eafb0bc3904d32d/coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a", size = 219997, upload-time = "2025-12-28T15:41:43.418Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9b/b1ade8bfb653c0bbce2d6d6e90cc6c254cbb99b7248531cc76253cb4da6d/coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4", size = 261296, upload-time = "2025-12-28T15:41:45.207Z" }, + { url = "https://files.pythonhosted.org/packages/1f/af/ebf91e3e1a2473d523e87e87fd8581e0aa08741b96265730e2d79ce78d8d/coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6", size = 263363, upload-time = "2025-12-28T15:41:47.163Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8b/fb2423526d446596624ac7fde12ea4262e66f86f5120114c3cfd0bb2befa/coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1", size = 265783, upload-time = "2025-12-28T15:41:49.03Z" }, + { url = "https://files.pythonhosted.org/packages/9b/26/ef2adb1e22674913b89f0fe7490ecadcef4a71fa96f5ced90c60ec358789/coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd", size = 260508, upload-time = "2025-12-28T15:41:51.035Z" }, + { url = "https://files.pythonhosted.org/packages/ce/7d/f0f59b3404caf662e7b5346247883887687c074ce67ba453ea08c612b1d5/coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c", size = 263357, upload-time = "2025-12-28T15:41:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b1/29896492b0b1a047604d35d6fa804f12818fa30cdad660763a5f3159e158/coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0", size = 260978, upload-time = "2025-12-28T15:41:54.589Z" }, + { url = "https://files.pythonhosted.org/packages/48/f2/971de1238a62e6f0a4128d37adadc8bb882ee96afbe03ff1570291754629/coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e", size = 259877, upload-time = "2025-12-28T15:41:56.263Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fc/0474efcbb590ff8628830e9aaec5f1831594874360e3251f1fdec31d07a3/coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53", size = 262069, upload-time = "2025-12-28T15:41:58.093Z" }, + { url = "https://files.pythonhosted.org/packages/88/4f/3c159b7953db37a7b44c0eab8a95c37d1aa4257c47b4602c04022d5cb975/coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842", size = 222184, upload-time = "2025-12-28T15:41:59.763Z" }, + { url = "https://files.pythonhosted.org/packages/58/a5/6b57d28f81417f9335774f20679d9d13b9a8fb90cd6160957aa3b54a2379/coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2", size = 223250, upload-time = "2025-12-28T15:42:01.52Z" }, + { url = "https://files.pythonhosted.org/packages/81/7c/160796f3b035acfbb58be80e02e484548595aa67e16a6345e7910ace0a38/coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09", size = 221521, upload-time = "2025-12-28T15:42:03.275Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" }, + { url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" }, + { url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" }, + { url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" }, + { url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" }, + { url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" }, + { url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" }, + { url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" }, + { url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" }, + { url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" }, + { url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" }, + { url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" }, + { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, +] + +[[package]] +name = "dill" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "isort" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/53/4f3c058e3bace40282876f9b553343376ee687f3c35a525dc79dbd450f88/isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187", size = 805049, upload-time = "2025-10-11T13:30:59.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/ed/e3705d6d02b4f7aea715a353c8ce193efd0b5db13e204df895d38734c244/isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", size = 94672, upload-time = "2025-10-11T13:30:57.665Z" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pylint" +version = "4.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "dill" }, + { name = "isort" }, + { name = "mccabe" }, + { name = "platformdirs" }, + { name = "tomlkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d2/b081da1a8930d00e3fc06352a1d449aaf815d4982319fab5d8cdb2e9ab35/pylint-4.0.4.tar.gz", hash = "sha256:d9b71674e19b1c36d79265b5887bf8e55278cbe236c9e95d22dc82cf044fdbd2", size = 1571735, upload-time = "2025-11-30T13:29:04.315Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/92/d40f5d937517cc489ad848fc4414ecccc7592e4686b9071e09e64f5e378e/pylint-4.0.4-py3-none-any.whl", hash = "sha256:63e06a37d5922555ee2c20963eb42559918c20bd2b21244e4ef426e7c43b92e0", size = 536425, upload-time = "2025-11-30T13:29:02.53Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/23/adf3796d740536d63a6fbda113d07e60c734b6ed5d3058d1e47fc0495e47/soupsieve-2.8.1.tar.gz", hash = "sha256:4cf733bc50fa805f5df4b8ef4740fc0e0fa6218cf3006269afd3f9d6d80fd350", size = 117856, upload-time = "2025-12-18T13:50:34.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl", hash = "sha256:a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434", size = 36710, upload-time = "2025-12-18T13:50:33.267Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "update-dsa-sheet" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "beautifulsoup4" }, +] + +[package.dev-dependencies] +dev = [ + { name = "coverage" }, + { name = "isort" }, + { name = "pylint" }, + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [{ name = "beautifulsoup4", specifier = ">=4.12.3" }] + +[package.metadata.requires-dev] +dev = [ + { name = "coverage", specifier = ">=7.6.1" }, + { name = "isort", specifier = ">=5.13.2" }, + { name = "pylint", specifier = ">=3.2.6" }, + { name = "pytest", specifier = ">=8.3.2" }, +] From 0fe461d586df7816ae62f95fe5f49dd4abcf817d Mon Sep 17 00:00:00 2001 From: Tim Schrader Date: Sat, 10 Jan 2026 12:13:35 +0100 Subject: [PATCH 02/10] CI Workflow and coverage report Signed-off-by: Tim Schrader --- .github/workflows/ci.yml | 84 ++++++++++++++++++++++++++++++++++++++++ .gitignore | 1 + README.md | 16 ++++++++ 3 files changed, 101 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0e52bf3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,84 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + lint: + name: Lint (Python 3.13) + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.13" + enable-cache: true + + - name: Install dependencies + run: uv sync --locked --all-groups + + - name: isort + run: uv run isort --check-only --diff src tests + + - name: pylint + run: uv run pylint src/update_dsa_sheet tests + + - name: Minimize uv cache + run: uv cache prune --ci + + test: + name: Tests (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.11", "3.12", "3.13"] + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + python-version: ${{ matrix.python-version }} + enable-cache: true + + - name: Install dependencies + run: uv sync --locked --all-groups + + - name: Run tests with coverage + run: uv run coverage run -m pytest + + - name: Coverage summary + run: uv run coverage report -m + + - name: Generate coverage XML + JSON + HTML + run: | + uv run coverage xml + uv run coverage json + uv run coverage html + + - name: Upload coverage artifacts (only once) + if: matrix.python-version == '3.13' + uses: actions/upload-artifact@v4 + with: + name: coverage + path: | + coverage.xml + coverage.json + htmlcov/ + .coverage + + - name: Minimize uv cache + run: uv cache prune --ci diff --git a/.gitignore b/.gitignore index ea6753a..0ae3458 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ htmlcov/ .cache nosetests.xml coverage.xml +coverage.json *.cover *.py,cover .hypothesis/ diff --git a/README.md b/README.md index d51f683..d635dfe 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,22 @@ uv run pytest pytest ``` +Generate an HTML coverage report: + +```bash +uv run coverage run -m pytest +uv run coverage html +# then open htmlcov/index.html in a browser +``` + +(Without `uv`, assuming `coverage` is installed): + +```bash +coverage run -m pytest +coverage html +# then open htmlcov/index.html in a browser +``` + ## Notes - Input file is changed in-place; the backup file is created automatically. From 48a15162f4cc15de9d1438fa78e5155c533f0eb2 Mon Sep 17 00:00:00 2001 From: Tim Schrader Date: Sat, 10 Jan 2026 12:13:53 +0100 Subject: [PATCH 03/10] remove old code Signed-off-by: Tim Schrader --- old/.github/workflows/python.yml | 33 ------------------------------- old/.pylintrc | 5 ----- old/.vscode/settings.json | 7 ------- old/Pipfile | 11 ----------- old/Pipfile.lock | 20 ------------------- old/README.md | 34 -------------------------------- old/requirements.txt | 15 -------------- old/update_dsa_sheet/__init__.py | 0 8 files changed, 125 deletions(-) delete mode 100644 old/.github/workflows/python.yml delete mode 100644 old/.pylintrc delete mode 100644 old/.vscode/settings.json delete mode 100644 old/Pipfile delete mode 100644 old/Pipfile.lock delete mode 100644 old/README.md delete mode 100644 old/requirements.txt delete mode 100644 old/update_dsa_sheet/__init__.py diff --git a/old/.github/workflows/python.yml b/old/.github/workflows/python.yml deleted file mode 100644 index 4dcf0a6..0000000 --- a/old/.github/workflows/python.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Run Unit Test via Pytest - -on: [push] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.12"] - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with Pylint - run: | - pip3 install pylint - pylint $(git ls-files '*.py') - continue-on-error: true - - name: Test with pytest - run: | - python3 -m coverage run -m pytest -v -s - - name: Generate Coverage Report - run: | - coverage report -m - \ No newline at end of file diff --git a/old/.pylintrc b/old/.pylintrc deleted file mode 100644 index 8318223..0000000 --- a/old/.pylintrc +++ /dev/null @@ -1,5 +0,0 @@ -[MASTER] -disable= - C0114, # missing-module-docstring - C0115, # missing-class-docstring - C0116, # missing-function-docstring diff --git a/old/.vscode/settings.json b/old/.vscode/settings.json deleted file mode 100644 index 23f1380..0000000 --- a/old/.vscode/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "python.testing.pytestArgs": [ - "update_dsa_sheet/test" - ], - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true -} \ No newline at end of file diff --git a/old/Pipfile b/old/Pipfile deleted file mode 100644 index 645a67e..0000000 --- a/old/Pipfile +++ /dev/null @@ -1,11 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] - -[dev-packages] - -[requires] -python_version = "3.12" diff --git a/old/Pipfile.lock b/old/Pipfile.lock deleted file mode 100644 index b6df5da..0000000 --- a/old/Pipfile.lock +++ /dev/null @@ -1,20 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "702ad05de9bc9de99a4807c8dde1686f31e0041d7b5f6f6b74861195a52110f5" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.12" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": {}, - "develop": {} -} diff --git a/old/README.md b/old/README.md deleted file mode 100644 index 254cf2b..0000000 --- a/old/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# DSA Helper Scripts - -Helper Scripts for the Pen&Paper game "Das Schwarze Auge". - -The scripts are designed to work in concert with the [Helden Software](https://www.helden-software.de/) - -## Update DSA Sheet - -Add the current characteristic values to the talent table - -e.g. the talent table row -``` -Athletik | (GE/KO/KK) | BEx2 | 7 -``` -is converted to -``` -Athletik | ( GE[14] / KO[16] / KK[15] ) | BEx2 | 7 -``` - -### Usage - - -```bash -python3 -m update_dsa_sheet {CHARACTER_SHEET} -``` - -or - -```bash -uv run --with-requirements requirements.txt -- python -m update_dsa_sheet -``` - -# ToDo -- add table with meta-talents described in "Wege des Schwerts" diff --git a/old/requirements.txt b/old/requirements.txt deleted file mode 100644 index 9a7bb96..0000000 --- a/old/requirements.txt +++ /dev/null @@ -1,15 +0,0 @@ -astroid==3.2.4 -beautifulsoup4==4.12.3 -bs4==0.0.2 -coverage==7.6.1 -dill==0.3.8 -iniconfig==2.0.0 -isort==5.13.2 -mccabe==0.7.0 -packaging==24.1 -platformdirs==4.2.2 -pluggy==1.5.0 -pylint==3.2.6 -pytest==8.3.2 -soupsieve==2.6 -tomlkit==0.13.2 diff --git a/old/update_dsa_sheet/__init__.py b/old/update_dsa_sheet/__init__.py deleted file mode 100644 index e69de29..0000000 From 08110df1e8857d269d106b5d814f627ca6e5f567 Mon Sep 17 00:00:00 2001 From: Tim Schrader Date: Sat, 10 Jan 2026 12:23:40 +0100 Subject: [PATCH 04/10] update readme Signed-off-by: Tim Schrader --- README.md | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index d635dfe..da1f29d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # update-dsa-sheet -A small CLI helper for **Das Schwarze Auge (DSA)** character sheets exported as HTML from **Helden Software**. +A small CLI helper for **Das Schwarze Auge (DSA)** character sheets exported as HTML (e.g. from **Helden Software**). It annotates the talent table with the hero’s current characteristic values, so entries like: @@ -42,14 +42,35 @@ uv run update-dsa-sheet path/to/character_sheet.html uv run python -m update_dsa_sheet path/to/character_sheet.html ``` +### Install as an `uv` tool (global) + +If you want to run `update-dsa-sheet` from anywhere (without `uv run` and without activating a virtual environment), install it as a persistent uv tool. + +From a local checkout: + +```bash +# from the project root +uv tool install . + +# if the tool bin dir is not on your PATH: +uv tool update-shell +``` + +Uninstall: + +```bash +uv tool uninstall update-dsa-sheet +``` + ## Install and run without `uv` -### Using `pip` in a virtual environment +### Using `pip` in a virtual environment (recommended) ```bash python -m venv .venv source .venv/bin/activate +pip install -U pip pip install -e . ``` @@ -86,22 +107,6 @@ uv run pytest pytest ``` -Generate an HTML coverage report: - -```bash -uv run coverage run -m pytest -uv run coverage html -# then open htmlcov/index.html in a browser -``` - -(Without `uv`, assuming `coverage` is installed): - -```bash -coverage run -m pytest -coverage html -# then open htmlcov/index.html in a browser -``` - ## Notes - Input file is changed in-place; the backup file is created automatically. From 9a93a8c9dbc3467f245aca3ea0e01bbc95a0ea37 Mon Sep 17 00:00:00 2001 From: Tim Schrader Date: Sat, 10 Jan 2026 12:24:52 +0100 Subject: [PATCH 05/10] fix isort Signed-off-by: Tim Schrader --- src/update_dsa_sheet/dsa_soup.py | 2 ++ src/update_dsa_sheet/hero_characteristics.py | 1 - tests/test_hero_characteristics.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/update_dsa_sheet/dsa_soup.py b/src/update_dsa_sheet/dsa_soup.py index 52ca7ad..55c6c20 100644 --- a/src/update_dsa_sheet/dsa_soup.py +++ b/src/update_dsa_sheet/dsa_soup.py @@ -1,5 +1,7 @@ from typing import Optional + from bs4 import BeautifulSoup, ResultSet, Tag + from update_dsa_sheet.hero_characteristics import HeroCharacteristics diff --git a/src/update_dsa_sheet/hero_characteristics.py b/src/update_dsa_sheet/hero_characteristics.py index 9e33222..2c37b12 100644 --- a/src/update_dsa_sheet/hero_characteristics.py +++ b/src/update_dsa_sheet/hero_characteristics.py @@ -1,6 +1,5 @@ from dataclasses import dataclass - _shorthand_map = { "charisma": "ch", "fingerfertigkeit": "ff", diff --git a/tests/test_hero_characteristics.py b/tests/test_hero_characteristics.py index bd8cf76..7eaaed4 100644 --- a/tests/test_hero_characteristics.py +++ b/tests/test_hero_characteristics.py @@ -1,4 +1,5 @@ import pytest + from update_dsa_sheet.hero_characteristics import HeroCharacteristics From 614934838234087eced33e60ec82be15e8712736 Mon Sep 17 00:00:00 2001 From: Tim Schrader Date: Sat, 10 Jan 2026 12:28:52 +0100 Subject: [PATCH 06/10] fix linter errors Signed-off-by: Tim Schrader --- tests/conftest.py | 12 ++++++------ tests/test_dsa_soup.py | 12 ++++++------ tests/test_hero_characteristics.py | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 670c571..07054f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,21 +6,21 @@ from bs4 import BeautifulSoup -@pytest.fixture(scope="session") -def resources_dir() -> Path: +@pytest.fixture(name="resources_dir", scope="session") +def fixture_resources_dir() -> Path: return Path(__file__).parent / "resources" -@pytest.fixture -def load_html(resources_dir: Path): +@pytest.fixture(name="load_html") +def fixture_load_html(resources_dir: Path): def _load(name: str) -> str: return (resources_dir / name).read_text(encoding="utf8") return _load -@pytest.fixture -def load_soup(load_html): +@pytest.fixture(name="load_soup") +def fixture_load_soup(load_html): def _load(name: str) -> BeautifulSoup: return BeautifulSoup(load_html(name), "html.parser") diff --git a/tests/test_dsa_soup.py b/tests/test_dsa_soup.py index 8f5b26a..79ee5aa 100644 --- a/tests/test_dsa_soup.py +++ b/tests/test_dsa_soup.py @@ -9,18 +9,18 @@ from update_dsa_sheet.hero_characteristics import HeroCharacteristics -@pytest.fixture -def empty_html_document() -> str: +@pytest.fixture(name="empty_html_document") +def fixture_empty_html_document() -> str: return "" -@pytest.fixture -def character_sheet_file_path(resources_dir: Path) -> str: +@pytest.fixture(name="character_sheet_file_path") +def fixture_character_sheet_file_path(resources_dir: Path) -> str: return str(resources_dir / "character_sheet.html") -@pytest.fixture -def custom_characteristics() -> HeroCharacteristics: +@pytest.fixture(name="custom_characteristics") +def fixture_custom_characteristics() -> HeroCharacteristics: return HeroCharacteristics( { "Mut": 1, diff --git a/tests/test_hero_characteristics.py b/tests/test_hero_characteristics.py index 7eaaed4..26d69d6 100644 --- a/tests/test_hero_characteristics.py +++ b/tests/test_hero_characteristics.py @@ -3,8 +3,8 @@ from update_dsa_sheet.hero_characteristics import HeroCharacteristics -@pytest.fixture -def shorthand_characteristics(): +@pytest.fixture(name="shorthand_characteristics") +def fixture_shorthand_characteristics(): return { "cH": 1, "ff": 2, @@ -18,8 +18,8 @@ def shorthand_characteristics(): } -@pytest.fixture -def longhand_characteristics(): +@pytest.fixture(name="longhand_characteristics") +def fixture_longhand_characteristics(): return { "CHARISMA": 11, "FINgerfertigKEIT": 12, From e463c8ee227b8488a1794d13f5e431202952746f Mon Sep 17 00:00:00 2001 From: Tim Schrader Date: Sat, 10 Jan 2026 12:30:04 +0100 Subject: [PATCH 07/10] test with python 3.11 and up Signed-off-by: Tim Schrader --- pyproject.toml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5078b2a..827feb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,21 +4,14 @@ version = "0.1.0" description = "Add your description here" readme = "README.md" authors = [{ name = "Tim Schrader", email = "tim.schrader92@gmail.com" }] -requires-python = ">=3.13" -dependencies = [ - "beautifulsoup4>=4.12.3", -] +requires-python = ">=3.11" +dependencies = ["beautifulsoup4>=4.12.3"] [project.scripts] update-dsa-sheet = "update_dsa_sheet:main" [dependency-groups] -dev = [ - "pytest>=8.3.2", - "coverage>=7.6.1", - "pylint>=3.2.6", - "isort>=5.13.2", -] +dev = ["pytest>=8.3.2", "coverage>=7.6.1", "pylint>=3.2.6", "isort>=5.13.2"] [build-system] requires = ["uv_build>=0.9.24,<0.10.0"] From 9786427be04547b1fb044a32e95516ad8385d7a9 Mon Sep 17 00:00:00 2001 From: Tim Schrader Date: Sat, 10 Jan 2026 12:33:47 +0100 Subject: [PATCH 08/10] Update uv.lock Signed-off-by: Tim Schrader --- uv.lock | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 0c6c9a7..602002c 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 3 -requires-python = ">=3.13" +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version < '3.12'", +] [[package]] name = "astroid" @@ -39,6 +43,32 @@ version = "7.13.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" }, + { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" }, + { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" }, + { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" }, + { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" }, + { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" }, + { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" }, + { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" }, + { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, + { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, + { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, + { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, + { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, + { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, + { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, { url = "https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", size = 218951, upload-time = "2025-12-28T15:41:16.653Z" }, { url = "https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", size = 219325, upload-time = "2025-12-28T15:41:18.609Z" }, { url = "https://files.pythonhosted.org/packages/59/f6/efdabdb4929487baeb7cb2a9f7dac457d9356f6ad1b255be283d58b16316/coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", size = 250309, upload-time = "2025-12-28T15:41:20.629Z" }, From 9264e03a2fb2279c61de1f96892699a11b5601dc Mon Sep 17 00:00:00 2001 From: Tim Schrader Date: Sat, 10 Jan 2026 12:35:22 +0100 Subject: [PATCH 09/10] pin uv version Signed-off-by: Tim Schrader --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e52bf3..d140016 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,13 @@ jobs: - name: Checkout uses: actions/checkout@v6 + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "0.9.24" + python-version: ${{ matrix.python-version }} + enable-cache: true + - name: Install uv uses: astral-sh/setup-uv@v7 with: @@ -51,6 +58,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v7 with: + version: "0.9.24" python-version: ${{ matrix.python-version }} enable-cache: true From ebb76e4ed5cbb61e3b46d1aa965754c67912f008 Mon Sep 17 00:00:00 2001 From: Tim Schrader Date: Sat, 10 Jan 2026 12:46:12 +0100 Subject: [PATCH 10/10] vscode configuration Signed-off-by: Tim Schrader --- .vscode/launch.json | 78 +++++++++++++++++++++++++++++++++++++++++++ .vscode/settings.json | 14 ++++++++ .vscode/tasks.json | 18 ++++++++++ 3 files changed, 110 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..74636cf --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,78 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "update-dsa-sheet: CLI (file arg)", + "type": "debugpy", + "request": "launch", + "module": "update_dsa_sheet", + "args": [ + "${file}" + ], + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "update-dsa-sheet: CLI (prompt for file)", + "type": "debugpy", + "request": "launch", + "module": "update_dsa_sheet", + "args": [ + "${input:characterSheetPath}" + ], + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "pytest: all tests", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "args": [ + "-q" + ], + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "pytest: current test file", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "args": [ + "${file}", + "-q" + ], + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "pytest: test by name", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "args": [ + "${file}", + "-q", + "-k", + "${input:pytestK}" + ], + "console": "integratedTerminal", + "justMyCode": true + } + ], + "inputs": [ + { + "id": "characterSheetPath", + "type": "promptString", + "description": "Path to character_sheet.html", + "default": "tests/resources/character_sheet.html" + }, + { + "id": "pytestK", + "type": "promptString", + "description": "pytest -k expression", + "default": "" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e331fee --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "python.defaultInterpreterPath": "${workspaceFolder}/.venv", + "python.terminal.activateEnvironment": true, + "python.testing.pytestEnabled": true, + "python.testing.unittestEnabled": false, + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.cwd": "${workspaceFolder}", + "python.testing.autoTestDiscoverOnSaveEnabled": true, + "python.analysis.extraPaths": [ + "src" + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..b353617 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,18 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "uv: sync", + "type": "shell", + "command": "uv", + "args": [ + "sync", + "--all-groups" + ], + "problemMatcher": [], + "runOptions": { + "runOn": "folderOpen" + } + } + ] +} \ No newline at end of file