From a098ba91b47000b7cfe811b7811a39f3b2c0c90a Mon Sep 17 00:00:00 2001 From: bensteUEM Date: Wed, 15 Jan 2025 21:17:54 +0100 Subject: [PATCH] chore(#31): resolve some ruff linting issues --- church_web_helper/app.py | 19 +++---- church_web_helper/helper.py | 73 +++++++++++---------------- generate_pyproj.py | 1 + pyproject.toml | 1 + tests/test_helper.py | 99 +++++++++++++++++++++---------------- version.py | 7 ++- 6 files changed, 103 insertions(+), 97 deletions(-) diff --git a/church_web_helper/app.py b/church_web_helper/app.py index ec43335..f774310 100644 --- a/church_web_helper/app.py +++ b/church_web_helper/app.py @@ -6,14 +6,13 @@ import logging import logging.config import os -from datetime import datetime, time import re import urllib -from datetime import datetime, time, timedelta +from datetime import datetime, time from pathlib import Path -import pytz import pandas as pd +import pytz import toml import vobject from churchtools_api.churchtools_api import ChurchToolsApi as CTAPI @@ -43,11 +42,11 @@ from church_web_helper.helper import ( deduplicate_df_index_with_lists, extract_relevant_calendar_appointment_shortname, + get_group_name_services, get_plan_months_docx, get_plan_months_xlsx, get_primary_resource, get_special_day_name, - get_group_name_services, get_title_name_services, ) @@ -244,8 +243,8 @@ def download_events(): event_choices=event_choices, service_groups=session["serviceGroups"], ) - elif request.method == "POST": - if "event_id" not in request.form.keys(): + if request.method == "POST": + if "event_id" not in request.form: return redirect(url_for("download_events")) event_id = int(request.form["event_id"]) if "submit_docx" in request.form: @@ -396,7 +395,7 @@ def download_plan_months(): from_date=from_date, to_date=to_date, ) - elif request.method == "POST": + if request.method == "POST": logger.info("Responding to POST request") selected_calendars = [ @@ -661,7 +660,7 @@ def download_plan_months(): to_date=to_date, ) - elif action == "DOCx Document Download": + if action == "DOCx Document Download": logger.debug("Preparing Download as DOCx") document = get_plan_months_docx(df_data, from_date=from_date) filename = f"Monatsplan_{from_date.strftime('%Y_%B')}.docx" @@ -672,7 +671,7 @@ def download_plan_months(): os.remove(filename) return response - elif action == "Excel Download": + if action == "Excel Download": logger.debug("Preparing Download as Excel") filename = f"Monatsplan_{from_date.strftime('%Y_%B')}.xlsx" workbook = get_plan_months_xlsx( @@ -684,6 +683,8 @@ def download_plan_months(): ) os.remove(filename) return response + return None + return None @app.route("/ct/calendar_appointments") diff --git a/church_web_helper/helper.py b/church_web_helper/helper.py index 026dd17..3c60d43 100644 --- a/church_web_helper/helper.py +++ b/church_web_helper/helper.py @@ -3,20 +3,19 @@ It is used to outsource parts which don't need to be part of app.py """ -from collections import OrderedDict import logging -from churchtools_api.churchtools_api import ChurchToolsApi as CTAPI -from dateutil.relativedelta import relativedelta +from collections import OrderedDict +from datetime import datetime -from datetime import datetime, timedelta import docx -from docx.shared import Pt, RGBColor, Cm import docx.table import pandas as pd +import xlsxwriter +from churchtools_api.churchtools_api import ChurchToolsApi as CTAPI +from dateutil.relativedelta import relativedelta from docx.oxml import OxmlElement, ns from docx.oxml.ns import qn - -import xlsxwriter +from docx.shared import Cm, Pt, RGBColor logger = logging.getLogger(__name__) @@ -43,15 +42,14 @@ def get_special_day_name( from_=trunc_date, to_=trunc_date + relativedelta(days=1) - relativedelta(seconds=1), ) - if special_names: - if len(special_names) > 0: - return special_names[0]["caption"] + if special_names and len(special_names) > 0: + return special_names[0]["caption"] return "" def extract_relevant_calendar_appointment_shortname(longname: str) -> str: - """Tries to extract a shortname for "special ocasion events" based on a mapping + """Tries to extract a shortname for "special ocasion events" based on a mapping. Excecution order is relevant! @@ -100,7 +98,7 @@ def extract_relevant_calendar_appointment_shortname(longname: str) -> str: def get_plan_months_docx(data: pd.DataFrame, from_date: datetime) -> docx.Document: - """Function which converts a Dataframe into a DOCx document used for final print modifications + """Function which converts a Dataframe into a DOCx document used for final print modifications. Args: data: pre-formatted data to be used as base @@ -109,7 +107,6 @@ def get_plan_months_docx(data: pd.DataFrame, from_date: datetime) -> docx.Docume Returns: document reference """ - document = docx.Document() padding_left = 1.5 padding_right = -0.25 @@ -130,7 +127,7 @@ def get_plan_months_docx(data: pd.DataFrame, from_date: datetime) -> docx.Docume run.font.size = Pt(32) run.font.color.rgb = RGBColor.from_string("000000") - locations = set([item[0] for item in data.columns[2:]]) + locations = {item[0] for item in data.columns[2:]} table = document.add_table(rows=1, cols=len(locations) + 1) hdr_cells = table.rows[0].cells @@ -141,7 +138,7 @@ def get_plan_months_docx(data: pd.DataFrame, from_date: datetime) -> docx.Docume for run in paragraph.runs: run.bold = True - for index, df_row in data.iterrows(): + for _index, df_row in data.iterrows(): row_cells = table.add_row().cells para = row_cells[0].paragraphs[0] para.add_run(df_row["shortDay"].iloc[0]).add_break() @@ -172,7 +169,7 @@ def get_plan_months_docx(data: pd.DataFrame, from_date: datetime) -> docx.Docume def get_plan_months_xlsx( data: pd.DataFrame, from_date: datetime, filename: str ) -> xlsxwriter.Workbook: - """Function which converts a Dataframe into a XLXs used as admin overview printout + """Function which converts a Dataframe into a XLXs used as admin overview printout. Args: data: pre-formatted data to be used as base @@ -182,13 +179,12 @@ def get_plan_months_xlsx( Returns: workbook reference """ - workbook = xlsxwriter.Workbook(filename) heading = f"{from_date.strftime('%B %Y')}" worksheet = workbook.add_worksheet(name=heading) - locations = set([item[0] for item in data.columns[2:]]) + locations = {item[0] for item in data.columns[2:]} row = 0 @@ -228,7 +224,7 @@ def get_plan_months_xlsx( TIME | Predigt | ABM | Organist | | Taufe | Musik - + """ column_offsets = [0, 1, 2, 3, 0, 1, 2, 3] @@ -246,7 +242,7 @@ def get_plan_months_xlsx( # Iterate all data rows location_column_offset = 0 - for index, df_row in data.iterrows(): + for _index, df_row in data.iterrows(): format_content = workbook.add_format({"bold": True, "border": 1}) format_content_b = workbook.add_format({"bold": True, "border": 1, "bottom": 2}) @@ -255,12 +251,12 @@ def get_plan_months_xlsx( max_events_per_date = max([len(i) for i in df_row[slice(None), "shortTime"]]) for row_offset, column_offset, column_value in zip( - row_offsets, column_offsets, column_references + row_offsets, column_offsets, column_references, strict=False ): for location_index, location in enumerate(locations): location_column_offset = location_index * NUMBER_OF_COLUMNS_PER_LOCATION - for event_per_day_offset in range(0, max_events_per_date): + for event_per_day_offset in range(max_events_per_date): value = "" if column_value in df_row[location].index: if len(df_row[location, column_value]) > event_per_day_offset: @@ -285,7 +281,7 @@ def get_plan_months_xlsx( def deduplicate_df_index_with_lists(df_input: pd.DataFrame) -> pd.DataFrame: - """Flattens a df with multiple same index entries to list entries + """Flattens a df with multiple same index entries to list entries. Args: df_input: the original dataframe which contains multiple entries for "shortDay" index per column @@ -293,7 +289,6 @@ def deduplicate_df_index_with_lists(df_input: pd.DataFrame) -> pd.DataFrame: Returns: flattened df which has unique shortDay and combined lists in cells """ - shortDays = list(OrderedDict.fromkeys(df_input["shortDay"]).keys()) df_output = pd.DataFrame(columns=df_input.columns) for shortDay in shortDays: @@ -332,7 +327,7 @@ def deduplicate_df_index_with_lists(df_input: pd.DataFrame) -> pd.DataFrame: def generate_event_paragraph( target_cell: docx.table._Cell, relevant_entry: pd.Series ) -> None: - """function which generates the content of one table cell. + """Function which generates the content of one table cell. Used with get_plan_months_docx Iterates through all items in relevant row and using the columns to generate the text. @@ -372,12 +367,11 @@ def generate_event_paragraph( def change_table_format(table: docx.table) -> None: - """Inplace overwrite of styles + """Inplace overwrite of styles. Args: table: the table to modify """ - # Access the XML element of the table and move ident because by default it's 1,9cm off tbl_pr = table._element.xpath("w:tblPr")[0] tbl_indent = OxmlElement("w:tblInd") @@ -401,8 +395,8 @@ def change_table_format(table: docx.table) -> None: def set_page_margins( doc: docx.Document, top: float, bottom: float, left: float, right: float -): - """Helper to set document page borders in cm +) -> None: + """Helper to set document page borders in cm. Args: doc: the document to change @@ -420,7 +414,7 @@ def set_page_margins( section.right_margin = Cm(right) -def set_cell_border(cell): +def set_cell_border(cell) -> None: """Function to add borders to a cell. Args: @@ -443,7 +437,7 @@ def set_cell_border(cell): tcPr.append(tcBorders) -def set_cell_margins(cell, top=0, start=0, bottom=0, end=0): +def set_cell_margins(cell, top=0, start=0, bottom=0, end=0) -> None: """Function to set cell margins (padding). Args: @@ -486,7 +480,7 @@ def get_primary_resource( api: CTAPI, considered_resource_ids: list[int], ) -> str: - """Helper which is used to get the primary resource allocation of an event + """Helper which is used to get the primary resource allocation of an event. Args: appointment_id: id of calendar entry @@ -502,9 +496,7 @@ def get_primary_resource( to_=relevant_date, appointment_id=appointment_id, ) - locations = {booking["base"]["resource"]["name"] for booking in bookings} - - return locations + return {booking["base"]["resource"]["name"] for booking in bookings} def get_title_name_services( @@ -515,8 +507,7 @@ def get_title_name_services( considered_program_services: list[int], considered_groups: list[int], ) -> str: - """ - Helper function which retrieves a text representation of a service including the persons title based on considered groups. + """Helper function which retrieves a text representation of a service including the persons title based on considered groups. 1. Lookup relevant services 2. Lookup the prefix of the person to be used based on group assignemnts @@ -562,7 +553,7 @@ def get_title_name_services( def get_group_title_of_person( person_id: int, relevant_groups: list[int], api: CTAPI ) -> str: - """Retrieve name of first group for specified person and gender if possible + """Retrieve name of first group for specified person and gender if possible. Args: person_id: CT id of the user @@ -630,7 +621,6 @@ def get_group_name_services( Returns: text which can be used as suffix - empty in case no special service """ - relevant_event = api.get_event_by_calendar_appointment( appointment_id=appointment_id, start_date=relevant_date ) @@ -661,7 +651,4 @@ def get_group_name_services( for group_id in relevant_group_ids: group = api.get_groups(group_id=group_id)[0] result_groups.append(group["name"]) - result_string = ( - "mit " + " und ".join(result_groups) if len(result_groups) > 0 else "" - ) - return result_string + return "mit " + " und ".join(result_groups) if len(result_groups) > 0 else "" diff --git a/generate_pyproj.py b/generate_pyproj.py index e6b210f..5267a5b 100644 --- a/generate_pyproj.py +++ b/generate_pyproj.py @@ -41,6 +41,7 @@ "toml": "^0.10.2", "vobject": "^0.9.8", "xlsxwriter": "^3.2.0", + "tzlocal" : "^5.2", }, "group": { "dev": { diff --git a/pyproject.toml b/pyproject.toml index 23c87be..3a187ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ pandas = "^2.2.2" toml = "^0.10.2" vobject = "^0.9.8" xlsxwriter = "^3.2.0" +tzlocal = "^5.2" [tool.poetry.dependencies.churchtools-api] git = "https://github.com/bensteUEM/ChurchToolsAPI.git" diff --git a/tests/test_helper.py b/tests/test_helper.py index 4a8a790..7ac1ba1 100644 --- a/tests/test_helper.py +++ b/tests/test_helper.py @@ -1,24 +1,29 @@ -from datetime import datetime +"""All tests in regards to helper.py.""" + import json -import logging, logging.config +import logging +import logging.config import os +from datetime import datetime from pathlib import Path -from typing import Tuple + +import docx +import docx.table import pandas as pd import pytest -import docx import pytz +from churchtools_api.churchtools_api import ChurchToolsApi +from tzlocal import get_localzone from church_web_helper.helper import ( extract_relevant_calendar_appointment_shortname, + get_group_name_services, get_group_title_of_person, get_plan_months_docx, get_primary_resource, get_special_day_name, - get_group_name_services, get_title_name_services, ) -from churchtools_api.churchtools_api import ChurchToolsApi as CTAPI logger = logging.getLogger(__name__) @@ -32,14 +37,16 @@ class Test_Helper: + """Combined tests""" def setup_method(self) -> None: - self.ct_api = CTAPI( + """Init API connection used for all tests""" + self.ct_api = ChurchToolsApi( domain=os.getenv("CT_DOMAIN"), ct_token=os.getenv("CT_TOKEN") ) # Parametrized pytest function @pytest.mark.parametrize( - "input, expected_output", + ("sample_input", "expected_output"), [ ("Abendmahl", "mit Abendmahl"), ("Familien", "für Familien"), @@ -57,15 +64,16 @@ def setup_method(self) -> None: ], ) def test_extract_relevant_calendar_appointment_shortname( - self, input: str, expected_output: str - ): + self, sample_input: str, expected_output: str + ) -> None: + """CHeck shortname can be extracted.""" assert ( - extract_relevant_calendar_appointment_shortname(longname=input) + extract_relevant_calendar_appointment_shortname(longname=sample_input) == expected_output ) @pytest.mark.parametrize( - ("date, expected_output"), + ("date", "expected_output"), [ ( datetime(year=2024, month=12, day=23, hour=23).astimezone( @@ -77,7 +85,7 @@ def test_extract_relevant_calendar_appointment_shortname( datetime(year=2024, month=12, day=24).astimezone( pytz.timezone("Europe/Berlin") ), - "Christvesper", + "Christnacht", ), ( datetime(year=2024, month=12, day=25).astimezone( @@ -99,7 +107,8 @@ def test_extract_relevant_calendar_appointment_shortname( ), ], ) - def test_get_special_day_name(self, date: datetime, expected_output: str): + def test_get_special_day_name(self, date: datetime, expected_output: str) -> None: + """Check that special day names can be identified.""" special_name_calendar_ids = [52, 72] assert ( @@ -111,7 +120,8 @@ def test_get_special_day_name(self, date: datetime, expected_output: str): == expected_output ) - def test_get_plan_months_docx(self): + def test_get_plan_months_docx(self) -> None: + """Check that plan months can be created as docx.""" df_sample = pd.DataFrame( { "shortDay": ["3.2", "23.1", "23.1", "3.2"], @@ -156,12 +166,14 @@ def test_get_plan_months_docx(self): expected_sample = docx.Document(FILENAME) result = get_plan_months_docx( - df_data, from_date=datetime(year=2024, month=1, day=1) + df_data, + from_date=datetime(year=2024, month=1, day=1).astimezone(get_localzone()), ) assert compare_docx_files(result, expected_sample) - def test_get_primary_resource(self): + def test_get_primary_resource(self) -> None: + """Check if primary resource can be identified.""" SAMPLE_EVENT_ID = 330754 SAMPLE_DATE = datetime(year=2024, month=9, day=29).astimezone( pytz.timezone("Europe/Berlin") @@ -176,13 +188,14 @@ def test_get_primary_resource(self): considered_resource_ids=RESOURCE_IDS, ) - assert EXPECTED_RESULT == result + assert result == EXPECTED_RESULT - def test_get_title_name_services(self): + def test_get_title_name_services(self) -> None: """Check respective function with real sample. IMPORTANT - This test method and the parameters used depend on target system! - The sample event needs to be less than 3 months old otherwise it will not be available + The sample event needs to be less than 3 months old + otherwise it will not be available On ELKW1610.KRZ.TOOLS event ID 331144 is an existing event """ SAMPLE_CALENDAR_IDS = [2] @@ -203,10 +216,10 @@ def test_get_title_name_services(self): ) EXPECTED_RESULT = "Pfarrer Raiser" - assert EXPECTED_RESULT == result + assert result == EXPECTED_RESULT @pytest.mark.parametrize( - ("person_id, relevant_groups, expected_result"), + ("person_id", "relevant_groups", "expected_result"), [ (51, [367, 89, 355, 358], "Pfarrer"), (51, [], ""), @@ -221,9 +234,9 @@ def test_get_title_name_services(self): ], ) def test_get_group_title_of_person( - self, person_id, relevant_groups, expected_result - ): - """Check that titles by group can be retrieved + self, person_id: int, relevant_groups: list[int], expected_result: str + ) -> None: + """Check that titles by group can be retrieved. ELKW1610 specific IDs - """ @@ -240,7 +253,7 @@ def test_get_group_title_of_person( # 331153 - InJoyChor 14.12 - 10:00 # 331153 - PChor - 4.5.25 @pytest.mark.parametrize( - ("appointment_id, relevant_date, considered_services, expected_result"), + ("appointment_id", "relevant_date", "considered_services", "expected_result"), [ ( 331510, @@ -287,9 +300,13 @@ def test_get_group_title_of_person( ], ) def test_get_group_name_services( - self, appointment_id, relevant_date, considered_services, expected_result - ): - """Check that special service group names can be retrieved + self, + appointment_id: int, + relevant_date: datetime, + considered_services: list[int], + expected_result: str, + ) -> None: + """Check that special service group names can be retrieved. ELKW1610 specific IDs - """ @@ -308,7 +325,8 @@ def test_get_group_name_services( assert expected_result == result -def test_compare_docx_files(): +def test_compare_docx_files() -> None: + """Check that two docx documents can be compared.""" FILENAME = "tests/samples/test_get_plan_months.docx" FILENAME2 = "tests/samples/test_get_plan_months_other.docx" @@ -322,7 +340,7 @@ def test_compare_docx_files(): def compare_docx_files( document1: docx.Document, document2: docx.Document -) -> Tuple[bool, str]: +) -> tuple[bool, str]: """Compare both text and table content of two docx files. Args: @@ -333,7 +351,6 @@ def compare_docx_files( bool - if is equal text - description of difference """ - # Compare text text1 = get_docx_text(document1) text2 = get_docx_text(document2) @@ -351,40 +368,36 @@ def compare_docx_files( return True, "Files are identical" -def get_docx_text(document): +def get_docx_text(document: docx.Document) -> str: """Extract text content from the docx file.""" - full_text = [] - for para in document.paragraphs: - full_text.append(para.text) + full_text = [para.text for para in document.paragraphs] return "\n".join(full_text) -def get_docx_tables(document): +def get_docx_tables(document: docx.Document) -> str: """Extract tables content from the docx file.""" tables = [] for table in document.tables: table_content = [] for row in table.rows: - row_content = [] - for cell in row.cells: - row_content.append(cell.text.strip()) + row_content = [cell.text.strip() for cell in row.cells] table_content.append(row_content) tables.append(table_content) return tables -def compare_tables(tables1, tables2): +def compare_tables(tables1: docx.table, tables2: docx.table) -> bool: """Compare two sets of tables.""" if len(tables1) != len(tables2): return False - for table1, table2 in zip(tables1, tables2): + for table1, table2 in zip(tables1, tables2, strict=False): if len(table1) != len(table2): return False - for row1, row2 in zip(table1, table2): + for row1, row2 in zip(table1, table2, strict=False): if row1 != row2: return False diff --git a/version.py b/version.py index ce1dbd9..3da6ca1 100644 --- a/version.py +++ b/version.py @@ -1,8 +1,11 @@ +"""Helper script which aids github actions to access version number.""" + import os +from pathlib import Path import toml -with open("pyproject.toml") as f: +with Path("pyproject.toml").open() as f: pyproject_data = toml.load(f) VERSION = pyproject_data["tool"]["poetry"]["version"] @@ -10,4 +13,4 @@ if __name__ == "__main__": os.environ["VERSION"] = VERSION - print(VERSION) + print(VERSION) # noqa:T201