diff --git a/Makefile b/Makefile index b08e03c..824a1f7 100644 --- a/Makefile +++ b/Makefile @@ -51,8 +51,8 @@ clean-test: ## remove test and coverage artifacts rm -fr htmlcov/ lint: ## reformat with black and check style with flake8 - .venv/bin/ruff format src tests noxfile.py - .venv/bin/ruff check src tests noxfile.py + .venv/bin/ruff format superx_budget tests + .venv/bin/ruff check superx_budget tests test: lint ## run tests quickly with the default Python .venv/bin/pytest tests -x --disable-warnings -m "not fun" diff --git a/pyproject.toml b/pyproject.toml index 2d56c72..b2be9ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ name = "superx_budget" readme = "README.md" description = "Creating a budget overview from a SuperX export." license = { file = "LICENSE" } -requires-python = ">=3.7" +requires-python = ">=3.10" dynamic = ["version"] authors = [ @@ -20,8 +20,8 @@ authors = [ classifiers = [ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3 :: Only", "License :: Freely Distributable", ] @@ -96,6 +96,16 @@ ignore = [ # ignored, due to Windows / WSL2 setup # flake8-executable "EXE", + + # ignored, not really working with datetimes + # just retrieving the year + "DTZ", + + # TODO + # checkis for moving project to rust + "ANN", + "TID252", + "PYI024", ] [tool.ruff.lint.pydocstyle] diff --git a/superx_budget/__init__.py b/superx_budget/__init__.py index dcf75e4..60286b5 100644 --- a/superx_budget/__init__.py +++ b/superx_budget/__init__.py @@ -1,4 +1,4 @@ -""" superx_budget +"""superx_budget Creating a budget overview from a SuperX export """ @@ -6,14 +6,14 @@ Creating a budget overview from a SuperX export __version__ = "0.0.1" from .budget import parse_budget_file # noqa: F401 -from .superx import parse_exported_file # noqa: F401 +from .exceptions import BudgetParserError, SuperXParserError # noqa: F401 from .helpers import ( # noqa: F401 - find_recipients, find_budget_file, + find_recipients, get_sheet_of_file, - list_budget_files, is_budget_file_name, + list_budget_files, ) -from .pyramid import main # noqa: F401 from .overview import create_overview # noqa: F401 -from .exceptions import BudgetParserError, SuperXParserError # noqa: F401 +from .pyramid import main # noqa: F401 +from .superx import parse_exported_file # noqa: F401 diff --git a/superx_budget/budget.py b/superx_budget/budget.py index df2543d..6cbe193 100644 --- a/superx_budget/budget.py +++ b/superx_budget/budget.py @@ -1,9 +1,13 @@ -""" Budget Parser """ +"""Budget Parser""" +import typing from collections import namedtuple -from .helpers import get_sheet_of_file, strip_excel_value, is_empty_excel_value from .exceptions import BudgetParserError +from .helpers import get_sheet_of_file, is_empty_excel_value, strip_excel_value + +ExcelStuff = typing.Any +T = typing.TypeVar("T") EXPECTED_TABLE_HEADERS = [ "Nr.", @@ -36,17 +40,18 @@ BudgetData = namedtuple( ) -def _check_table_header(xl_row): +def _check_table_header(xl_row: ExcelStuff) -> None: fields_ignore_none = ( ("" if c is None else c) for c in xl_row.data[:NUM_EXPECTED_HEADERS] ) fields_str = (str(c) for c in fields_ignore_none) fields = [c.strip() for c in fields_str] if fields != EXPECTED_TABLE_HEADERS: - raise BudgetParserError(f"unexpected headers: '{xl_row.data}'") + msg = f"unexpected headers: '{xl_row.data}'" + raise BudgetParserError(msg) -def _skip_empty_lines(rows): +def _skip_empty_lines(rows: ExcelStuff) -> typing.Iterable[T]: for xl_row in rows: first_cell = xl_row.data[0] if is_empty_excel_value(first_cell): @@ -54,7 +59,7 @@ def _skip_empty_lines(rows): yield xl_row -def _parse_data_table(rows): +def _parse_data_table(rows: ExcelStuff) -> BudgetData: for xl_row in _skip_empty_lines(rows): data = [ strip_excel_value(value) @@ -63,14 +68,14 @@ def _parse_data_table(rows): yield BudgetData(xl_row.row, *data) -def parse_budget_data(xls_sheet): +def parse_budget_data(xls_sheet: ExcelStuff) -> list[BudgetData]: """parses the budget data""" rows = (ExcelRow(i, v) for i, v in enumerate(xls_sheet.values, start=1)) _check_table_header(next(rows)) return list(_parse_data_table(rows)) -def parse_budget_file(file_path): +def parse_budget_file(file_path: str) -> list[BudgetData]: """parses the budget file""" sheet = get_sheet_of_file(file_path, sheet=None) return parse_budget_data(sheet) diff --git a/superx_budget/exceptions.py b/superx_budget/exceptions.py index 0d880ea..d66ac25 100644 --- a/superx_budget/exceptions.py +++ b/superx_budget/exceptions.py @@ -1,4 +1,4 @@ -""" Exceptions used in the Project """ +"""Exceptions used in the Project""" class SuperXBudgetError(ValueError): diff --git a/superx_budget/helpers.py b/superx_budget/helpers.py index eceaca1..f70425a 100644 --- a/superx_budget/helpers.py +++ b/superx_budget/helpers.py @@ -1,13 +1,16 @@ -""" some helper functions """ +"""some helper functions""" from pathlib import Path +from typing import TypeVar import openpyxl +ExcelItem = TypeVar("ExcelItem") + DEFAULT_RECIPIENTS = ["frey@imtek.de"] -def excel_value_as_number(value): +def excel_value_as_number(value: ExcelItem) -> float | int: if value is None: return 0 if isinstance(value, str): @@ -18,7 +21,7 @@ def excel_value_as_number(value): return value -def get_sheet_of_file(excel_file, sheet=None): +def get_sheet_of_file(excel_file: str, sheet: str | None = None) -> ExcelItem: """returns a sheet from an excel FileCache if name is set to None, the function returns the first sheet @@ -30,51 +33,52 @@ def get_sheet_of_file(excel_file, sheet=None): return workbook[sheet] -def is_empty_excel_value(value): +def is_empty_excel_value(value: ExcelItem) -> bool: """is the cell value considered empty""" if value is None: return True - if isinstance(value, str) and value.strip() == "": - return True - return False + return isinstance(value, str) and value.strip() == "" -def strip_excel_value(value): +def strip_excel_value(value: ExcelItem) -> str | ExcelItem: """remove whitespace from an excel value if it is a string""" if isinstance(value, str): return value.strip() return value -def is_budget_file_name(path_or_name): +def is_budget_file_name(path_or_name: str | Path) -> bool: """checks if a filename has the format "budget[...]-.xlsx""" path = Path(path_or_name) - if not path.suffix.lower() == ".xlsx": + if path.suffix.lower() != ".xlsx": return False if not path.name.lower().startswith("budget"): return False try: - return int(path.stem[-5:]) <= -2019 + return int(path.stem[-5:]) <= -2019 # noqa: PLR2004 except ValueError: pass return False -def list_budget_files(folder): +def list_budget_files(folder: str | Path) -> list[Path]: """lists all files with the name "budget[...]-.xlsx""" files = (i for i in Path(folder).iterdir() if i.is_file()) visible = (i for i in files if not i.name.startswith(".")) return [i for i in visible if is_budget_file_name(i)] -def find_budget_file(folder, year): +def find_budget_file(folder: str | Path, year: str | int) -> Path | None: """searches for a file with the name "budget[...]-.xlsx""" for path in list_budget_files(folder): if path.stem.endswith(f"-{year}"): return path + return None -def find_recipients(folder, filename="recipients.txt"): +def find_recipients( + folder: str | Path, filename: str = "recipients.txt" +) -> list[str]: """finds the recipients of the budget list""" file_path = folder / filename if file_path.is_file(): diff --git a/superx_budget/overview.py b/superx_budget/overview.py index a6b8952..e9e3c32 100644 --- a/superx_budget/overview.py +++ b/superx_budget/overview.py @@ -66,11 +66,11 @@ class ProjectOverview: def _create_overview_map(budget_list): """returns a dictonary with project as key and overview as value""" - map = {} + mapping = {} for budget_data in budget_list: overview = ProjectOverview(budget_data) - map[str(overview.project)] = overview - return map + mapping[str(overview.project)] = overview + return mapping def _filter_superx_material_expenses(superx_export): diff --git a/superx_budget/pyramid/__init__.py b/superx_budget/pyramid/__init__.py index cf6f81c..936f726 100644 --- a/superx_budget/pyramid/__init__.py +++ b/superx_budget/pyramid/__init__.py @@ -1,31 +1,29 @@ -""" Superx Budget GUI """ +"""Superx Budget GUI""" from pathlib import Path -from pyramid.view import notfound_view_config +from pyramid.authorization import Allow, Authenticated, Everyone from pyramid.config import Configurator -from pyramid.session import JSONSerializer, SignedCookieSessionFactory -from pyramid.authorization import Allow, Everyone, Authenticated from pyramid.httpexceptions import HTTPFound +from pyramid.session import JSONSerializer, SignedCookieSessionFactory +from pyramid.view import notfound_view_config -from ..overview import create_overview # noqa: F401 from ..exceptions import BudgetParserError, SuperXParserError # noqa: F401 +from ..overview import create_overview # noqa: F401 XLSX_CONTENT_TYPE = "application/octet-stream" class Root: - - __acl__ = [(Allow, Everyone, "login"), (Allow, Authenticated, "view")] + __acl__ = [(Allow, Everyone, "login"), (Allow, Authenticated, "view")] # noqa: RUF012 def __init__(self, request): pass -def main(global_config, **settings): +def main(global_config, **settings): # noqa: ARG001 """This function returns a Pyramid WSGI application.""" with Configurator(settings=settings) as config: - session_factory = SignedCookieSessionFactory( settings["session.secret"], serializer=JSONSerializer() ) @@ -34,7 +32,11 @@ def main(global_config, **settings): config.set_root_factory(Root) config.add_request_method( - lambda r: Path(settings["budgets.dir"]), "budgets_dir", reify=True + lambda r: Path( # noqa: ARG005 + settings["budgets.dir"] + ), + "budgets_dir", + reify=True, ) age = int(settings.get("static_views.cache_max_age", 0)) @@ -49,5 +51,5 @@ def main(global_config, **settings): @notfound_view_config() -def not_found(context, request): +def not_found(context, request): # noqa: ARG001 return HTTPFound("/") diff --git a/superx_budget/pyramid/overview.py b/superx_budget/pyramid/overview.py index 347340a..1fe2db1 100644 --- a/superx_budget/pyramid/overview.py +++ b/superx_budget/pyramid/overview.py @@ -1,17 +1,17 @@ -""" Views for the create overview part """ +"""Views for the create overview part""" from tempfile import NamedTemporaryFile -from pyramid.view import view_config from pyramid.httpexceptions import HTTPFound -from pyramid_mailer.message import Message, Attachment +from pyramid.view import view_config +from pyramid_mailer.message import Attachment, Message -from . import XLSX_CONTENT_TYPE, Root from ..budget import parse_budget_file +from ..exceptions import BudgetParserError, SuperXParserError +from ..helpers import find_budget_file, find_recipients, get_sheet_of_file +from ..overview import create_overview from ..superx import parse_exported_file -from ..helpers import find_recipients, find_budget_file, get_sheet_of_file -from ..overview import create_overview # noqa: F401 -from ..exceptions import BudgetParserError, SuperXParserError # noqa: F401 +from . import XLSX_CONTENT_TYPE, Root MAIL_MESSAGE_BODY = """Hallo an Alle, @@ -35,10 +35,10 @@ def _map_from_form_data(request, prefix): row_str = key.split("-")[-1] row = int(row_str) try: - value = float(value) + as_number = float(value) except ValueError: - value = 0 - result[row] = value + as_number = 0 + result[row] = as_number return result @@ -48,7 +48,7 @@ def _map_from_form_data(request, prefix): renderer="superx_budget:pyramid/templates/start.jinja2", permission="view", ) -def index(context, request): +def index(context, request): # noqa: ARG001 return {} @@ -58,7 +58,7 @@ def index(context, request): renderer="superx_budget:pyramid/templates/overview.jinja2", permission="view", ) -def superx_upload(context, request): +def superx_upload(context, request): # noqa: ARG001 upload = request.POST.get("superx") if upload == b"" or not upload.filename.endswith(".xlsx"): @@ -122,7 +122,7 @@ def superx_upload(context, request): renderer="superx_budget:pyramid/templates/sent.jinja2", permission="view", ) -def send_overview(context, request): +def send_overview(context, request): # noqa: ARG001 export_date = request.POST.get("export_date").strip() tmp_recipients = request.POST.get("recipients").strip() recipients = tmp_recipients.splitlines() @@ -165,7 +165,7 @@ def send_overview(context, request): xls_name = f"{export_date}-Budget-Overview-{budget_year}.xlsx" with NamedTemporaryFile() as tmp: - sheet._parent.save(tmp.name) + sheet._parent.save(tmp.name) # noqa: SLF001 tmp.seek(0) attachment = Attachment(xls_name, XLSX_CONTENT_TYPE, tmp) message.attach(attachment) diff --git a/superx_budget/pyramid/security.py b/superx_budget/pyramid/security.py index 6d2c640..b546367 100644 --- a/superx_budget/pyramid/security.py +++ b/superx_budget/pyramid/security.py @@ -1,9 +1,9 @@ from passlib.hash import argon2 -from pyramid.view import view_config, forbidden_view_config -from pyramid.security import forget, remember -from pyramid.authorization import ACLAuthorizationPolicy from pyramid.authentication import AuthTktAuthenticationPolicy +from pyramid.authorization import ACLAuthorizationPolicy from pyramid.httpexceptions import HTTPFound +from pyramid.security import forget, remember +from pyramid.view import forbidden_view_config, view_config from . import Root @@ -15,6 +15,7 @@ class MyAuthenticationPolicy(AuthTktAuthenticationPolicy): user = request.user if user is not None: return AUTHENTICATED_USER_ID + return None def get_user(request): @@ -22,7 +23,7 @@ def get_user(request): @forbidden_view_config(renderer="superx_budget:pyramid/templates/login.jinja2") -def forbidden_view(request): +def forbidden_view(request): # noqa: ARG001 return {"error": False} @@ -54,11 +55,11 @@ def includeme(config): config.set_authentication_policy(authn_policy) config.set_authorization_policy(ACLAuthorizationPolicy()) - hashes = [hash for hash in settings["pwd.db"].splitlines() if hash] + hashes = [hashed for hashed in settings["pwd.db"].splitlines() if hashed] def check_password(request): password = request.POST.get("password", "") - return any(argon2.verify(password, hash) for hash in hashes) + return any(argon2.verify(password, hashed) for hashed in hashes) config.add_request_method(check_password, "check_password") config.add_request_method(get_user, "user", reify=True) diff --git a/superx_budget/pyramid/templates.py b/superx_budget/pyramid/templates.py index 248f998..b611cb4 100644 --- a/superx_budget/pyramid/templates.py +++ b/superx_budget/pyramid/templates.py @@ -1,16 +1,16 @@ -""" Views for the templates part """ +"""Views for the templates part""" from pathlib import Path -from pyramid.view import view_config -from pyramid.response import FileResponse from pyramid.httpexceptions import HTTPFound +from pyramid.response import FileResponse +from pyramid.view import view_config -from . import XLSX_CONTENT_TYPE, Root from ..budget import parse_budget_file -from ..helpers import list_budget_files, is_budget_file_name -from ..overview import create_overview # noqa: F401 from ..exceptions import BudgetParserError, SuperXParserError # noqa: F401 +from ..helpers import is_budget_file_name, list_budget_files +from ..overview import create_overview # noqa: F401 +from . import XLSX_CONTENT_TYPE, Root @view_config( @@ -20,7 +20,7 @@ from ..exceptions import BudgetParserError, SuperXParserError # noqa: F401 renderer="superx_budget:pyramid/templates/templates.jinja2", permission="view", ) -def templates(context, request): +def templates(context, request): # noqa: ARG001 if "f" in request.GET: file_name = request.GET["f"] file_path = request.budgets_dir / file_name @@ -50,7 +50,7 @@ def templates(context, request): renderer="superx_budget:pyramid/templates/templates.jinja2", permission="view", ) -def templates_update(context, request): +def templates_update(context, request): # noqa: ARG001 upload = request.POST.get("budget") if upload == b"" or not is_budget_file_name(upload.filename): diff --git a/superx_budget/superx.py b/superx_budget/superx.py index acc63fa..5566639 100644 --- a/superx_budget/superx.py +++ b/superx_budget/superx.py @@ -1,10 +1,10 @@ -""" SuperX Parser """ +"""SuperX Parser""" -from datetime import datetime from collections import namedtuple +from datetime import datetime -from .helpers import get_sheet_of_file, strip_excel_value from .exceptions import SuperXParserError +from .helpers import get_sheet_of_file, strip_excel_value EXPECTED_HEADLINE = "Verwendungsnachweis und Kassenstand SAP" EXPECTED_METADATA_KEYS = {"Haushaltsjahr", "Stand", "Gruppierung"} @@ -36,7 +36,8 @@ def _check_export_headline(row): """checks the first line of the excel data if it's what we'd expect""" headline = row[0] if headline != EXPECTED_HEADLINE: - raise SuperXParserError(f"unexpected headline: '{headline}'") + msg = f"unexpected headline: '{headline}'" + raise SuperXParserError(msg) def _get_export_metadata(row): @@ -46,11 +47,11 @@ def _get_export_metadata(row): parts = [entry.split(":", 1) for entry in entries] metadata = {key.strip(): value.strip() for key, value in parts} if EXPECTED_METADATA_KEYS - set(metadata.keys()): - raise SuperXParserError(f"unexpected metadata: '{data}'") + msg = f"unexpected metadata: '{data}'" + raise SuperXParserError(msg) if metadata["Gruppierung"] != EXPECTED_EXPORT_GROUPING: - raise SuperXParserError( - f"unexpected grouping: {metadata['Gruppierung']}" - ) + msg = f"unexpected grouping: {metadata['Gruppierung']}" + raise SuperXParserError(msg) date_part, *rest = metadata["Stand"].split(",") return SuperXResult( metadata["Haushaltsjahr"], @@ -66,7 +67,8 @@ def _skip_export_data_until_table_header(rows): if first_cell == EXPECTED_DATA_TABLE_HEADER: break else: - raise SuperXParserError("could not find table header") + msg = "could not find table header" + raise SuperXParserError(msg) def _parse_data_table(rows): @@ -80,7 +82,7 @@ def _parse_data_table(rows): def parse_export_data(xls_sheet): """parses the exported superx data""" - rows = xls_sheet.values + rows = xls_sheet.values # noqa: PD011 _check_export_headline(next(rows)) metadata = _get_export_metadata(next(rows)) _skip_export_data_until_table_header(rows) diff --git a/tests/conftest.py b/tests/conftest.py index 5b809f3..5b6328b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,4 @@ -""" Global test fixtures and Mocks """ +"""Global test fixtures and Mocks""" from pathlib import Path @@ -17,7 +17,7 @@ class MockWorkbookSheet: @pytest.fixture def example_root(request): root_dir = Path(request.config.rootdir) - yield root_dir / "test_data" + return root_dir / "test_data" @pytest.fixture @@ -29,14 +29,14 @@ def budget_example_file(example_root): def budget_example_workbook(budget_example_file): import openpyxl - yield openpyxl.open(budget_example_file) + return openpyxl.open(budget_example_file) @pytest.fixture def budget_example_sheet(budget_example_workbook): sheets = budget_example_workbook.sheetnames first = sheets[0] - yield budget_example_workbook[first] + return budget_example_workbook[first] @pytest.fixture @@ -48,4 +48,4 @@ def superx_example_file(example_root): def superx_example_workbook(superx_example_file): import openpyxl - yield openpyxl.open(superx_example_file) + return openpyxl.open(superx_example_file) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index bdbd509..d4db790 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -2,7 +2,7 @@ import pytest @pytest.mark.parametrize( - "input,expected", + ("given", "expected"), [ ("a", False), ("", True), @@ -13,16 +13,16 @@ import pytest (2.2, False), ], ) -def test_is_empty_excel_value(input, expected): +def test_is_empty_excel_value(given, expected): from superx_budget.helpers import is_empty_excel_value - result = is_empty_excel_value(input) + result = is_empty_excel_value(given) assert result == expected @pytest.mark.parametrize( - "input,expected", + ("given", "expected"), [ ("a", "a"), ("", ""), @@ -33,10 +33,10 @@ def test_is_empty_excel_value(input, expected): (2.2, 2.2), ], ) -def test_strip_excel_value(input, expected): +def test_strip_excel_value(given, expected): from superx_budget.helpers import strip_excel_value - result = strip_excel_value(input) + result = strip_excel_value(given) assert result == expected @@ -62,7 +62,7 @@ def test_get_sheet_of_file_named(budget_example_file): @pytest.mark.parametrize( - "input,expected", + ("given", "expected"), [ (None, 0), ("", 0), @@ -73,16 +73,16 @@ def test_get_sheet_of_file_named(budget_example_file): (2.4, 2.4), ], ) -def test_excel_value_as_number(input, expected): +def test_excel_value_as_number(given, expected): from superx_budget.helpers import excel_value_as_number - result = excel_value_as_number(input) + result = excel_value_as_number(given) assert result == expected @pytest.mark.parametrize( - "name,expected", + ("name", "expected"), [ ("budget-2019.xlsx", True), ("budget-template-2020.xlsx", True), diff --git a/tests/test_overview.py b/tests/test_overview.py index 0fbc1f0..4ae1a5b 100644 --- a/tests/test_overview.py +++ b/tests/test_overview.py @@ -17,7 +17,7 @@ def example_budget_data(): 0, ) - yield example + return example # noqa: RET504 def test_project_overview_init(example_budget_data): @@ -46,7 +46,7 @@ def test_project_overview_row_property(example_budget_data): def test_project_overview_add(example_budget_data): - from superx_budget.overview import ProjectOverview, OverviewBudgetEntry + from superx_budget.overview import OverviewBudgetEntry, ProjectOverview over = ProjectOverview(example_budget_data) over.add("Sachmittel", "Obligo", -100) @@ -94,11 +94,11 @@ def test_create_overview_map(budget_example_file): def test_create_entries_from_export(budget_example_file, superx_example_file): from superx_budget.budget import parse_budget_file - from superx_budget.superx import parse_exported_file from superx_budget.overview import ( - _create_overview_map, _create_entries_from_superx, + _create_overview_map, ) + from superx_budget.superx import parse_exported_file superx_data = parse_exported_file(superx_example_file) budget_data = parse_budget_file(budget_example_file) @@ -111,11 +111,11 @@ def test_create_entries_from_export(budget_example_file, superx_example_file): def test_filter_superx_material_expenses(superx_example_file): - from superx_budget.superx import parse_exported_file from superx_budget.overview import ( VALID_MATERIAL_IDS, _filter_superx_material_expenses, ) + from superx_budget.superx import parse_exported_file superx_data = parse_exported_file(superx_example_file) result = list(_filter_superx_material_expenses(superx_data)) @@ -127,8 +127,8 @@ def test_filter_superx_material_expenses(superx_example_file): def test_create_overview(budget_example_file, superx_example_file): from superx_budget.budget import parse_budget_file - from superx_budget.superx import parse_exported_file from superx_budget.overview import create_overview + from superx_budget.superx import parse_exported_file superx_data = parse_exported_file(superx_example_file) budget_data = parse_budget_file(budget_example_file) diff --git a/tests/test_superx_parser.py b/tests/test_superx_parser.py index 3ae28a5..5fe31dc 100644 --- a/tests/test_superx_parser.py +++ b/tests/test_superx_parser.py @@ -1,11 +1,11 @@ -""" Stub file for testing the project """ +"""Stub file for testing the project""" import pytest def test_check_export_headline(): - from superx_budget.superx import _check_export_headline from superx_budget.exceptions import SuperXParserError + from superx_budget.superx import _check_export_headline row = ["nomatching header"] @@ -42,9 +42,7 @@ def test_get_export_metadata_raises_error(faulty_data): row = [faulty_data] - with pytest.raises( - ValueError - ): # SuperXParserError is a subclass of ValueError + with pytest.raises(ValueError): # noqa: PT011 _get_export_metadata(row) @@ -60,8 +58,8 @@ def test_skip_export_data_until_table_header_ok(): def test_skip_export_data_until_table_header_raises_error(): - from superx_budget.superx import _skip_export_data_until_table_header from superx_budget.exceptions import SuperXParserError + from superx_budget.superx import _skip_export_data_until_table_header rows = [[""], [""], ["Keine Kostenstelle"], ["Daten"]] iterator = iter(rows) @@ -73,7 +71,7 @@ def test_parse_data_table(): from superx_budget.superx import _parse_data_table rows = [ - ["A "] + list("BCDEFGHIJ"), + ["A ", *list("BCDEFGHIJ")], ["" for i in range(10)], list("qrstuvwxyzX"), # one column more ]