Browse Source

linting with ruff

main
Holger Frey 2 months ago
parent
commit
48527fee63
  1. 4
      Makefile
  2. 16
      pyproject.toml
  3. 12
      superx_budget/__init__.py
  4. 21
      superx_budget/budget.py
  5. 2
      superx_budget/exceptions.py
  6. 32
      superx_budget/helpers.py
  7. 6
      superx_budget/overview.py
  8. 24
      superx_budget/pyramid/__init__.py
  9. 28
      superx_budget/pyramid/overview.py
  10. 13
      superx_budget/pyramid/security.py
  11. 16
      superx_budget/pyramid/templates.py
  12. 22
      superx_budget/superx.py
  13. 10
      tests/conftest.py
  14. 20
      tests/test_helpers.py
  15. 12
      tests/test_overview.py
  16. 12
      tests/test_superx_parser.py

4
Makefile

@ -51,8 +51,8 @@ clean-test: ## remove test and coverage artifacts
rm -fr htmlcov/ rm -fr htmlcov/
lint: ## reformat with black and check style with flake8 lint: ## reformat with black and check style with flake8
.venv/bin/ruff format src tests noxfile.py .venv/bin/ruff format superx_budget tests
.venv/bin/ruff check src tests noxfile.py .venv/bin/ruff check superx_budget tests
test: lint ## run tests quickly with the default Python test: lint ## run tests quickly with the default Python
.venv/bin/pytest tests -x --disable-warnings -m "not fun" .venv/bin/pytest tests -x --disable-warnings -m "not fun"

16
pyproject.toml

@ -9,7 +9,7 @@ name = "superx_budget"
readme = "README.md" readme = "README.md"
description = "Creating a budget overview from a SuperX export." description = "Creating a budget overview from a SuperX export."
license = { file = "LICENSE" } license = { file = "LICENSE" }
requires-python = ">=3.7" requires-python = ">=3.10"
dynamic = ["version"] dynamic = ["version"]
authors = [ authors = [
@ -20,8 +20,8 @@ authors = [
classifiers = [ classifiers = [
"Development Status :: 2 - Pre-Alpha", "Development Status :: 2 - Pre-Alpha",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3 :: Only",
"License :: Freely Distributable", "License :: Freely Distributable",
] ]
@ -96,6 +96,16 @@ ignore = [
# ignored, due to Windows / WSL2 setup # ignored, due to Windows / WSL2 setup
# flake8-executable # flake8-executable
"EXE", "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] [tool.ruff.lint.pydocstyle]

12
superx_budget/__init__.py

@ -1,4 +1,4 @@
""" superx_budget """superx_budget
Creating a budget overview from a SuperX export Creating a budget overview from a SuperX export
""" """
@ -6,14 +6,14 @@ Creating a budget overview from a SuperX export
__version__ = "0.0.1" __version__ = "0.0.1"
from .budget import parse_budget_file # noqa: F401 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 from .helpers import ( # noqa: F401
find_recipients,
find_budget_file, find_budget_file,
find_recipients,
get_sheet_of_file, get_sheet_of_file,
list_budget_files,
is_budget_file_name, is_budget_file_name,
list_budget_files,
) )
from .pyramid import main # noqa: F401
from .overview import create_overview # 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

21
superx_budget/budget.py

@ -1,9 +1,13 @@
""" Budget Parser """ """Budget Parser"""
import typing
from collections import namedtuple from collections import namedtuple
from .helpers import get_sheet_of_file, strip_excel_value, is_empty_excel_value
from .exceptions import BudgetParserError 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 = [ EXPECTED_TABLE_HEADERS = [
"Nr.", "Nr.",
@ -36,17 +40,18 @@ BudgetData = namedtuple(
) )
def _check_table_header(xl_row): def _check_table_header(xl_row: ExcelStuff) -> None:
fields_ignore_none = ( fields_ignore_none = (
("" if c is None else c) for c in xl_row.data[:NUM_EXPECTED_HEADERS] ("" 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_str = (str(c) for c in fields_ignore_none)
fields = [c.strip() for c in fields_str] fields = [c.strip() for c in fields_str]
if fields != EXPECTED_TABLE_HEADERS: 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: for xl_row in rows:
first_cell = xl_row.data[0] first_cell = xl_row.data[0]
if is_empty_excel_value(first_cell): if is_empty_excel_value(first_cell):
@ -54,7 +59,7 @@ def _skip_empty_lines(rows):
yield xl_row yield xl_row
def _parse_data_table(rows): def _parse_data_table(rows: ExcelStuff) -> BudgetData:
for xl_row in _skip_empty_lines(rows): for xl_row in _skip_empty_lines(rows):
data = [ data = [
strip_excel_value(value) strip_excel_value(value)
@ -63,14 +68,14 @@ def _parse_data_table(rows):
yield BudgetData(xl_row.row, *data) 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""" """parses the budget data"""
rows = (ExcelRow(i, v) for i, v in enumerate(xls_sheet.values, start=1)) rows = (ExcelRow(i, v) for i, v in enumerate(xls_sheet.values, start=1))
_check_table_header(next(rows)) _check_table_header(next(rows))
return list(_parse_data_table(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""" """parses the budget file"""
sheet = get_sheet_of_file(file_path, sheet=None) sheet = get_sheet_of_file(file_path, sheet=None)
return parse_budget_data(sheet) return parse_budget_data(sheet)

2
superx_budget/exceptions.py

@ -1,4 +1,4 @@
""" Exceptions used in the Project """ """Exceptions used in the Project"""
class SuperXBudgetError(ValueError): class SuperXBudgetError(ValueError):

32
superx_budget/helpers.py

@ -1,13 +1,16 @@
""" some helper functions """ """some helper functions"""
from pathlib import Path from pathlib import Path
from typing import TypeVar
import openpyxl import openpyxl
ExcelItem = TypeVar("ExcelItem")
DEFAULT_RECIPIENTS = ["frey@imtek.de"] DEFAULT_RECIPIENTS = ["frey@imtek.de"]
def excel_value_as_number(value): def excel_value_as_number(value: ExcelItem) -> float | int:
if value is None: if value is None:
return 0 return 0
if isinstance(value, str): if isinstance(value, str):
@ -18,7 +21,7 @@ def excel_value_as_number(value):
return 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 """returns a sheet from an excel FileCache
if name is set to None, the function returns the first sheet 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] return workbook[sheet]
def is_empty_excel_value(value): def is_empty_excel_value(value: ExcelItem) -> bool:
"""is the cell value considered empty""" """is the cell value considered empty"""
if value is None: if value is None:
return True return True
if isinstance(value, str) and value.strip() == "": return isinstance(value, str) and value.strip() == ""
return True
return False
def strip_excel_value(value): def strip_excel_value(value: ExcelItem) -> str | ExcelItem:
"""remove whitespace from an excel value if it is a string""" """remove whitespace from an excel value if it is a string"""
if isinstance(value, str): if isinstance(value, str):
return value.strip() return value.strip()
return value 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[...]-<year>.xlsx""" """checks if a filename has the format "budget[...]-<year>.xlsx"""
path = Path(path_or_name) path = Path(path_or_name)
if not path.suffix.lower() == ".xlsx": if path.suffix.lower() != ".xlsx":
return False return False
if not path.name.lower().startswith("budget"): if not path.name.lower().startswith("budget"):
return False return False
try: try:
return int(path.stem[-5:]) <= -2019 return int(path.stem[-5:]) <= -2019 # noqa: PLR2004
except ValueError: except ValueError:
pass pass
return False return False
def list_budget_files(folder): def list_budget_files(folder: str | Path) -> list[Path]:
"""lists all files with the name "budget[...]-<year>.xlsx""" """lists all files with the name "budget[...]-<year>.xlsx"""
files = (i for i in Path(folder).iterdir() if i.is_file()) files = (i for i in Path(folder).iterdir() if i.is_file())
visible = (i for i in files if not i.name.startswith(".")) visible = (i for i in files if not i.name.startswith("."))
return [i for i in visible if is_budget_file_name(i)] 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[...]-<year>.xlsx""" """searches for a file with the name "budget[...]-<year>.xlsx"""
for path in list_budget_files(folder): for path in list_budget_files(folder):
if path.stem.endswith(f"-{year}"): if path.stem.endswith(f"-{year}"):
return path 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""" """finds the recipients of the budget list"""
file_path = folder / filename file_path = folder / filename
if file_path.is_file(): if file_path.is_file():

6
superx_budget/overview.py

@ -66,11 +66,11 @@ class ProjectOverview:
def _create_overview_map(budget_list): def _create_overview_map(budget_list):
"""returns a dictonary with project as key and overview as value""" """returns a dictonary with project as key and overview as value"""
map = {} mapping = {}
for budget_data in budget_list: for budget_data in budget_list:
overview = ProjectOverview(budget_data) overview = ProjectOverview(budget_data)
map[str(overview.project)] = overview mapping[str(overview.project)] = overview
return map return mapping
def _filter_superx_material_expenses(superx_export): def _filter_superx_material_expenses(superx_export):

24
superx_budget/pyramid/__init__.py

@ -1,31 +1,29 @@
""" Superx Budget GUI """ """Superx Budget GUI"""
from pathlib import Path 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.config import Configurator
from pyramid.session import JSONSerializer, SignedCookieSessionFactory
from pyramid.authorization import Allow, Everyone, Authenticated
from pyramid.httpexceptions import HTTPFound 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 ..exceptions import BudgetParserError, SuperXParserError # noqa: F401
from ..overview import create_overview # noqa: F401
XLSX_CONTENT_TYPE = "application/octet-stream" XLSX_CONTENT_TYPE = "application/octet-stream"
class Root: class Root:
__acl__ = [(Allow, Everyone, "login"), (Allow, Authenticated, "view")] # noqa: RUF012
__acl__ = [(Allow, Everyone, "login"), (Allow, Authenticated, "view")]
def __init__(self, request): def __init__(self, request):
pass pass
def main(global_config, **settings): def main(global_config, **settings): # noqa: ARG001
"""This function returns a Pyramid WSGI application.""" """This function returns a Pyramid WSGI application."""
with Configurator(settings=settings) as config: with Configurator(settings=settings) as config:
session_factory = SignedCookieSessionFactory( session_factory = SignedCookieSessionFactory(
settings["session.secret"], serializer=JSONSerializer() settings["session.secret"], serializer=JSONSerializer()
) )
@ -34,7 +32,11 @@ def main(global_config, **settings):
config.set_root_factory(Root) config.set_root_factory(Root)
config.add_request_method( 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)) age = int(settings.get("static_views.cache_max_age", 0))
@ -49,5 +51,5 @@ def main(global_config, **settings):
@notfound_view_config() @notfound_view_config()
def not_found(context, request): def not_found(context, request): # noqa: ARG001
return HTTPFound("/") return HTTPFound("/")

28
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 tempfile import NamedTemporaryFile
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPFound 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 ..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 ..superx import parse_exported_file
from ..helpers import find_recipients, find_budget_file, get_sheet_of_file from . import XLSX_CONTENT_TYPE, Root
from ..overview import create_overview # noqa: F401
from ..exceptions import BudgetParserError, SuperXParserError # noqa: F401
MAIL_MESSAGE_BODY = """Hallo an Alle, MAIL_MESSAGE_BODY = """Hallo an Alle,
@ -35,10 +35,10 @@ def _map_from_form_data(request, prefix):
row_str = key.split("-")[-1] row_str = key.split("-")[-1]
row = int(row_str) row = int(row_str)
try: try:
value = float(value) as_number = float(value)
except ValueError: except ValueError:
value = 0 as_number = 0
result[row] = value result[row] = as_number
return result return result
@ -48,7 +48,7 @@ def _map_from_form_data(request, prefix):
renderer="superx_budget:pyramid/templates/start.jinja2", renderer="superx_budget:pyramid/templates/start.jinja2",
permission="view", permission="view",
) )
def index(context, request): def index(context, request): # noqa: ARG001
return {} return {}
@ -58,7 +58,7 @@ def index(context, request):
renderer="superx_budget:pyramid/templates/overview.jinja2", renderer="superx_budget:pyramid/templates/overview.jinja2",
permission="view", permission="view",
) )
def superx_upload(context, request): def superx_upload(context, request): # noqa: ARG001
upload = request.POST.get("superx") upload = request.POST.get("superx")
if upload == b"" or not upload.filename.endswith(".xlsx"): 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", renderer="superx_budget:pyramid/templates/sent.jinja2",
permission="view", permission="view",
) )
def send_overview(context, request): def send_overview(context, request): # noqa: ARG001
export_date = request.POST.get("export_date").strip() export_date = request.POST.get("export_date").strip()
tmp_recipients = request.POST.get("recipients").strip() tmp_recipients = request.POST.get("recipients").strip()
recipients = tmp_recipients.splitlines() recipients = tmp_recipients.splitlines()
@ -165,7 +165,7 @@ def send_overview(context, request):
xls_name = f"{export_date}-Budget-Overview-{budget_year}.xlsx" xls_name = f"{export_date}-Budget-Overview-{budget_year}.xlsx"
with NamedTemporaryFile() as tmp: with NamedTemporaryFile() as tmp:
sheet._parent.save(tmp.name) sheet._parent.save(tmp.name) # noqa: SLF001
tmp.seek(0) tmp.seek(0)
attachment = Attachment(xls_name, XLSX_CONTENT_TYPE, tmp) attachment = Attachment(xls_name, XLSX_CONTENT_TYPE, tmp)
message.attach(attachment) message.attach(attachment)

13
superx_budget/pyramid/security.py

@ -1,9 +1,9 @@
from passlib.hash import argon2 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.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import HTTPFound
from pyramid.security import forget, remember
from pyramid.view import forbidden_view_config, view_config
from . import Root from . import Root
@ -15,6 +15,7 @@ class MyAuthenticationPolicy(AuthTktAuthenticationPolicy):
user = request.user user = request.user
if user is not None: if user is not None:
return AUTHENTICATED_USER_ID return AUTHENTICATED_USER_ID
return None
def get_user(request): def get_user(request):
@ -22,7 +23,7 @@ def get_user(request):
@forbidden_view_config(renderer="superx_budget:pyramid/templates/login.jinja2") @forbidden_view_config(renderer="superx_budget:pyramid/templates/login.jinja2")
def forbidden_view(request): def forbidden_view(request): # noqa: ARG001
return {"error": False} return {"error": False}
@ -54,11 +55,11 @@ def includeme(config):
config.set_authentication_policy(authn_policy) config.set_authentication_policy(authn_policy)
config.set_authorization_policy(ACLAuthorizationPolicy()) 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): def check_password(request):
password = request.POST.get("password", "") 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(check_password, "check_password")
config.add_request_method(get_user, "user", reify=True) config.add_request_method(get_user, "user", reify=True)

16
superx_budget/pyramid/templates.py

@ -1,16 +1,16 @@
""" Views for the templates part """ """Views for the templates part"""
from pathlib import Path from pathlib import Path
from pyramid.view import view_config
from pyramid.response import FileResponse
from pyramid.httpexceptions import HTTPFound 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 ..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 ..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( @view_config(
@ -20,7 +20,7 @@ from ..exceptions import BudgetParserError, SuperXParserError # noqa: F401
renderer="superx_budget:pyramid/templates/templates.jinja2", renderer="superx_budget:pyramid/templates/templates.jinja2",
permission="view", permission="view",
) )
def templates(context, request): def templates(context, request): # noqa: ARG001
if "f" in request.GET: if "f" in request.GET:
file_name = request.GET["f"] file_name = request.GET["f"]
file_path = request.budgets_dir / file_name file_path = request.budgets_dir / file_name
@ -50,7 +50,7 @@ def templates(context, request):
renderer="superx_budget:pyramid/templates/templates.jinja2", renderer="superx_budget:pyramid/templates/templates.jinja2",
permission="view", permission="view",
) )
def templates_update(context, request): def templates_update(context, request): # noqa: ARG001
upload = request.POST.get("budget") upload = request.POST.get("budget")
if upload == b"" or not is_budget_file_name(upload.filename): if upload == b"" or not is_budget_file_name(upload.filename):

22
superx_budget/superx.py

@ -1,10 +1,10 @@
""" SuperX Parser """ """SuperX Parser"""
from datetime import datetime
from collections import namedtuple from collections import namedtuple
from datetime import datetime
from .helpers import get_sheet_of_file, strip_excel_value
from .exceptions import SuperXParserError from .exceptions import SuperXParserError
from .helpers import get_sheet_of_file, strip_excel_value
EXPECTED_HEADLINE = "Verwendungsnachweis und Kassenstand SAP" EXPECTED_HEADLINE = "Verwendungsnachweis und Kassenstand SAP"
EXPECTED_METADATA_KEYS = {"Haushaltsjahr", "Stand", "Gruppierung"} 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""" """checks the first line of the excel data if it's what we'd expect"""
headline = row[0] headline = row[0]
if headline != EXPECTED_HEADLINE: if headline != EXPECTED_HEADLINE:
raise SuperXParserError(f"unexpected headline: '{headline}'") msg = f"unexpected headline: '{headline}'"
raise SuperXParserError(msg)
def _get_export_metadata(row): def _get_export_metadata(row):
@ -46,11 +47,11 @@ def _get_export_metadata(row):
parts = [entry.split(":", 1) for entry in entries] parts = [entry.split(":", 1) for entry in entries]
metadata = {key.strip(): value.strip() for key, value in parts} metadata = {key.strip(): value.strip() for key, value in parts}
if EXPECTED_METADATA_KEYS - set(metadata.keys()): 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: if metadata["Gruppierung"] != EXPECTED_EXPORT_GROUPING:
raise SuperXParserError( msg = f"unexpected grouping: {metadata['Gruppierung']}"
f"unexpected grouping: {metadata['Gruppierung']}" raise SuperXParserError(msg)
)
date_part, *rest = metadata["Stand"].split(",") date_part, *rest = metadata["Stand"].split(",")
return SuperXResult( return SuperXResult(
metadata["Haushaltsjahr"], metadata["Haushaltsjahr"],
@ -66,7 +67,8 @@ def _skip_export_data_until_table_header(rows):
if first_cell == EXPECTED_DATA_TABLE_HEADER: if first_cell == EXPECTED_DATA_TABLE_HEADER:
break break
else: else:
raise SuperXParserError("could not find table header") msg = "could not find table header"
raise SuperXParserError(msg)
def _parse_data_table(rows): def _parse_data_table(rows):
@ -80,7 +82,7 @@ def _parse_data_table(rows):
def parse_export_data(xls_sheet): def parse_export_data(xls_sheet):
"""parses the exported superx data""" """parses the exported superx data"""
rows = xls_sheet.values rows = xls_sheet.values # noqa: PD011
_check_export_headline(next(rows)) _check_export_headline(next(rows))
metadata = _get_export_metadata(next(rows)) metadata = _get_export_metadata(next(rows))
_skip_export_data_until_table_header(rows) _skip_export_data_until_table_header(rows)

10
tests/conftest.py

@ -1,4 +1,4 @@
""" Global test fixtures and Mocks """ """Global test fixtures and Mocks"""
from pathlib import Path from pathlib import Path
@ -17,7 +17,7 @@ class MockWorkbookSheet:
@pytest.fixture @pytest.fixture
def example_root(request): def example_root(request):
root_dir = Path(request.config.rootdir) root_dir = Path(request.config.rootdir)
yield root_dir / "test_data" return root_dir / "test_data"
@pytest.fixture @pytest.fixture
@ -29,14 +29,14 @@ def budget_example_file(example_root):
def budget_example_workbook(budget_example_file): def budget_example_workbook(budget_example_file):
import openpyxl import openpyxl
yield openpyxl.open(budget_example_file) return openpyxl.open(budget_example_file)
@pytest.fixture @pytest.fixture
def budget_example_sheet(budget_example_workbook): def budget_example_sheet(budget_example_workbook):
sheets = budget_example_workbook.sheetnames sheets = budget_example_workbook.sheetnames
first = sheets[0] first = sheets[0]
yield budget_example_workbook[first] return budget_example_workbook[first]
@pytest.fixture @pytest.fixture
@ -48,4 +48,4 @@ def superx_example_file(example_root):
def superx_example_workbook(superx_example_file): def superx_example_workbook(superx_example_file):
import openpyxl import openpyxl
yield openpyxl.open(superx_example_file) return openpyxl.open(superx_example_file)

20
tests/test_helpers.py

@ -2,7 +2,7 @@ import pytest
@pytest.mark.parametrize( @pytest.mark.parametrize(
"input,expected", ("given", "expected"),
[ [
("a", False), ("a", False),
("", True), ("", True),
@ -13,16 +13,16 @@ import pytest
(2.2, False), (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 from superx_budget.helpers import is_empty_excel_value
result = is_empty_excel_value(input) result = is_empty_excel_value(given)
assert result == expected assert result == expected
@pytest.mark.parametrize( @pytest.mark.parametrize(
"input,expected", ("given", "expected"),
[ [
("a", "a"), ("a", "a"),
("", ""), ("", ""),
@ -33,10 +33,10 @@ def test_is_empty_excel_value(input, expected):
(2.2, 2.2), (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 from superx_budget.helpers import strip_excel_value
result = strip_excel_value(input) result = strip_excel_value(given)
assert result == expected assert result == expected
@ -62,7 +62,7 @@ def test_get_sheet_of_file_named(budget_example_file):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"input,expected", ("given", "expected"),
[ [
(None, 0), (None, 0),
("", 0), ("", 0),
@ -73,16 +73,16 @@ def test_get_sheet_of_file_named(budget_example_file):
(2.4, 2.4), (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 from superx_budget.helpers import excel_value_as_number
result = excel_value_as_number(input) result = excel_value_as_number(given)
assert result == expected assert result == expected
@pytest.mark.parametrize( @pytest.mark.parametrize(
"name,expected", ("name", "expected"),
[ [
("budget-2019.xlsx", True), ("budget-2019.xlsx", True),
("budget-template-2020.xlsx", True), ("budget-template-2020.xlsx", True),

12
tests/test_overview.py

@ -17,7 +17,7 @@ def example_budget_data():
0, 0,
) )
yield example return example # noqa: RET504
def test_project_overview_init(example_budget_data): 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): 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 = ProjectOverview(example_budget_data)
over.add("Sachmittel", "Obligo", -100) 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): def test_create_entries_from_export(budget_example_file, superx_example_file):
from superx_budget.budget import parse_budget_file from superx_budget.budget import parse_budget_file
from superx_budget.superx import parse_exported_file
from superx_budget.overview import ( from superx_budget.overview import (
_create_overview_map,
_create_entries_from_superx, _create_entries_from_superx,
_create_overview_map,
) )
from superx_budget.superx import parse_exported_file
superx_data = parse_exported_file(superx_example_file) superx_data = parse_exported_file(superx_example_file)
budget_data = parse_budget_file(budget_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): def test_filter_superx_material_expenses(superx_example_file):
from superx_budget.superx import parse_exported_file
from superx_budget.overview import ( from superx_budget.overview import (
VALID_MATERIAL_IDS, VALID_MATERIAL_IDS,
_filter_superx_material_expenses, _filter_superx_material_expenses,
) )
from superx_budget.superx import parse_exported_file
superx_data = parse_exported_file(superx_example_file) superx_data = parse_exported_file(superx_example_file)
result = list(_filter_superx_material_expenses(superx_data)) 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): def test_create_overview(budget_example_file, superx_example_file):
from superx_budget.budget import parse_budget_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.overview import create_overview
from superx_budget.superx import parse_exported_file
superx_data = parse_exported_file(superx_example_file) superx_data = parse_exported_file(superx_example_file)
budget_data = parse_budget_file(budget_example_file) budget_data = parse_budget_file(budget_example_file)

12
tests/test_superx_parser.py

@ -1,11 +1,11 @@
""" Stub file for testing the project """ """Stub file for testing the project"""
import pytest import pytest
def test_check_export_headline(): def test_check_export_headline():
from superx_budget.superx import _check_export_headline
from superx_budget.exceptions import SuperXParserError from superx_budget.exceptions import SuperXParserError
from superx_budget.superx import _check_export_headline
row = ["nomatching header"] row = ["nomatching header"]
@ -42,9 +42,7 @@ def test_get_export_metadata_raises_error(faulty_data):
row = [faulty_data] row = [faulty_data]
with pytest.raises( with pytest.raises(ValueError): # noqa: PT011
ValueError
): # SuperXParserError is a subclass of ValueError
_get_export_metadata(row) _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(): 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.exceptions import SuperXParserError
from superx_budget.superx import _skip_export_data_until_table_header
rows = [[""], [""], ["Keine Kostenstelle"], ["Daten"]] rows = [[""], [""], ["Keine Kostenstelle"], ["Daten"]]
iterator = iter(rows) iterator = iter(rows)
@ -73,7 +71,7 @@ def test_parse_data_table():
from superx_budget.superx import _parse_data_table from superx_budget.superx import _parse_data_table
rows = [ rows = [
["A "] + list("BCDEFGHIJ"), ["A ", *list("BCDEFGHIJ")],
["" for i in range(10)], ["" for i in range(10)],
list("qrstuvwxyzX"), # one column more list("qrstuvwxyzX"), # one column more
] ]

Loading…
Cancel
Save