refact: apply consistent formatting

This commit is contained in:
sebthom
2025-04-27 23:54:22 +02:00
parent fe33a0e461
commit ef923a8337
21 changed files with 1020 additions and 709 deletions

View File

@@ -96,7 +96,7 @@ def save_dict(filepath:str, content:dict[str, Any]) -> None:
yaml.indent(mapping = 2, sequence = 4, offset = 2)
yaml.representer.add_representer(str, # use YAML | block style for multi-line strings
lambda dumper, data:
dumper.represent_scalar('tag:yaml.org,2002:str', data, style = '|' if '\n' in data else None)
dumper.represent_scalar("tag:yaml.org,2002:str", data, style = "|" if "\n" in data else None)
)
yaml.allow_duplicate_keys = False
yaml.explicit_start = False

View File

@@ -3,14 +3,14 @@
# SPDX-ArtifactOfProjectHomePage: https://github.com/Second-Hand-Friends/kleinanzeigen-bot/
import sys, traceback # isort: skip
from types import FrameType, TracebackType
from typing import Any, Final
from typing import Final
from . import loggers
LOG:Final[loggers.Logger] = loggers.get_logger(__name__)
def on_exception(ex_type: type[BaseException] | None, ex_value: BaseException | None, ex_traceback: TracebackType | None) -> None:
def on_exception(ex_type:type[BaseException] | None, ex_value:BaseException | None, ex_traceback:TracebackType | None) -> None:
if ex_type is None or ex_value is None:
LOG.error("Unknown exception occurred (missing exception info): ex_type=%s, ex_value=%s", ex_type, ex_value)
return

View File

@@ -11,6 +11,6 @@ class KleinanzeigenBotError(RuntimeError):
class CaptchaEncountered(KleinanzeigenBotError):
"""Raised when a Captcha was detected and auto-restart is enabled."""
def __init__(self, restart_delay: timedelta) -> None:
def __init__(self, restart_delay:timedelta) -> None:
super().__init__()
self.restart_delay = restart_delay

View File

@@ -42,7 +42,7 @@ class Locale(NamedTuple):
return f"{self.language}{region_part}{encoding_part}"
@staticmethod
def of(locale_string: str) -> 'Locale':
def of(locale_string:str) -> "Locale":
"""
>>> Locale.of("en_US.UTF-8")
Locale(language='en', region='US', encoding='UTF-8')
@@ -86,11 +86,11 @@ def _detect_locale() -> Locale:
return Locale.of(lang) if lang else Locale("en", "US", "UTF-8")
_CURRENT_LOCALE: Locale = _detect_locale()
_TRANSLATIONS: dict[str, Any] | None = None
_CURRENT_LOCALE:Locale = _detect_locale()
_TRANSLATIONS:dict[str, Any] | None = None
def translate(text:object, caller: inspect.FrameInfo | None) -> str:
def translate(text:object, caller:inspect.FrameInfo | None) -> str:
text = str(text)
if not caller:
return text
@@ -105,7 +105,7 @@ def translate(text:object, caller: inspect.FrameInfo | None) -> str:
if not _TRANSLATIONS:
return text
module_name = caller.frame.f_globals.get('__name__') # pylint: disable=redefined-outer-name
module_name = caller.frame.f_globals.get("__name__") # pylint: disable=redefined-outer-name
file_basename = os.path.splitext(os.path.basename(caller.filename))[0]
if module_name and module_name.endswith(f".{file_basename}"):
module_name = module_name[:-(len(file_basename) + 1)]
@@ -124,9 +124,9 @@ gettext.gettext = lambda message: translate(_original_gettext(message), reflect.
for module_name, module in sys.modules.items():
if module is None or module_name in sys.builtin_module_names:
continue
if hasattr(module, '_') and module._ is _original_gettext:
if hasattr(module, "_") and module._ is _original_gettext:
module._ = gettext.gettext # type: ignore[attr-defined]
if hasattr(module, 'gettext') and module.gettext is _original_gettext:
if hasattr(module, "gettext") and module.gettext is _original_gettext:
module.gettext = gettext.gettext # type: ignore[attr-defined]
@@ -190,8 +190,8 @@ def pluralize(noun:str, count:int | Sized, *, prefix_with_count:bool = True) ->
# English
if len(noun) < 2: # noqa: PLR2004 Magic value used in comparison
return f"{prefix}{noun}s"
if noun.endswith(('s', 'sh', 'ch', 'x', 'z')):
if noun.endswith(("s", "sh", "ch", "x", "z")):
return f"{prefix}{noun}es"
if noun.endswith('y') and noun[-2].lower() not in "aeiou":
if noun.endswith("y") and noun[-2].lower() not in "aeiou":
return f"{prefix}{noun[:-1]}ies"
return f"{prefix}{noun}s"

View File

@@ -28,11 +28,11 @@ LOG_ROOT:Final[logging.Logger] = logging.getLogger()
class _MaxLevelFilter(logging.Filter):
def __init__(self, level: int) -> None:
def __init__(self, level:int) -> None:
super().__init__()
self.level = level
def filter(self, record: logging.LogRecord) -> bool:
def filter(self, record:logging.LogRecord) -> bool:
return record.levelno <= self.level
@@ -104,7 +104,7 @@ def configure_console_logging() -> None:
class LogFileHandle:
"""Encapsulates a log file handler with close and status methods."""
def __init__(self, file_path: str, handler: RotatingFileHandler, logger: logging.Logger) -> None:
def __init__(self, file_path:str, handler:RotatingFileHandler, logger:logging.Logger) -> None:
self.file_path = file_path
self._handler:RotatingFileHandler | None = handler
self._logger = logger
@@ -146,14 +146,14 @@ def flush_all_handlers() -> None:
handler.flush()
def get_logger(name: str | None = None) -> logging.Logger:
def get_logger(name:str | None = None) -> logging.Logger:
"""
Returns a localized logger
"""
class TranslatingLogger(logging.Logger):
def _log(self, level: int, msg: object, *args: Any, **kwargs: Any) -> None:
def _log(self, level:int, msg:object, *args:Any, **kwargs:Any) -> None:
if level != DEBUG: # debug messages should not be translated
msg = i18n.translate(msg, reflect.get_caller(2))
super()._log(level, msg, *args, **kwargs)

View File

@@ -10,7 +10,7 @@ from typing import Any, TypeVar
from . import i18n
# https://mypy.readthedocs.io/en/stable/generics.html#generic-functions
T = TypeVar('T')
T = TypeVar("T")
def ensure(condition:Any | bool | Callable[[], bool], error_message:str, timeout:float = 5, poll_requency:float = 0.5) -> None:
@@ -49,7 +49,7 @@ def is_frozen() -> bool:
return getattr(sys, "frozen", False)
async def ainput(prompt: str) -> str:
async def ainput(prompt:str) -> str:
return await asyncio.to_thread(input, f'{prompt} ')
@@ -84,10 +84,10 @@ def parse_decimal(number:float | int | str) -> decimal.Decimal:
def parse_datetime(
date: datetime | str | None,
date:datetime | str | None,
*,
add_timezone_if_missing: bool = True,
use_local_timezone: bool = True
add_timezone_if_missing:bool = True,
use_local_timezone:bool = True
) -> datetime | None:
"""
Parses a datetime object or ISO-formatted string.
@@ -152,22 +152,22 @@ def parse_duration(text:str) -> timedelta:
>>> parse_duration("invalid input")
datetime.timedelta(0)
"""
pattern = re.compile(r'(\d+)\s*([dhms])')
pattern = re.compile(r"(\d+)\s*([dhms])")
parts = pattern.findall(text.lower())
kwargs: dict[str, int] = {}
kwargs:dict[str, int] = {}
for value, unit in parts:
if unit == 'd':
kwargs['days'] = kwargs.get('days', 0) + int(value)
elif unit == 'h':
kwargs['hours'] = kwargs.get('hours', 0) + int(value)
elif unit == 'm':
kwargs['minutes'] = kwargs.get('minutes', 0) + int(value)
elif unit == 's':
kwargs['seconds'] = kwargs.get('seconds', 0) + int(value)
if unit == "d":
kwargs["days"] = kwargs.get("days", 0) + int(value)
elif unit == "h":
kwargs["hours"] = kwargs.get("hours", 0) + int(value)
elif unit == "m":
kwargs["minutes"] = kwargs.get("minutes", 0) + int(value)
elif unit == "s":
kwargs["seconds"] = kwargs.get("seconds", 0) + int(value)
return timedelta(**kwargs)
def format_timedelta(td: timedelta) -> str:
def format_timedelta(td:timedelta) -> str:
"""
Formats a timedelta into a human-readable string using the pluralize utility.

View File

@@ -5,7 +5,7 @@ import inspect
from typing import Any
def get_caller(depth: int = 1) -> inspect.FrameInfo | None:
def get_caller(depth:int = 1) -> inspect.FrameInfo | None:
stack = inspect.stack()
try:
for frame in stack[depth + 1:]:

View File

@@ -165,7 +165,7 @@ class WebScrapingMixin:
prefs_file = os.path.join(profile_dir, "Preferences")
if not os.path.exists(prefs_file):
LOG.info(" -> Setting chrome prefs [%s]...", prefs_file)
with open(prefs_file, "w", encoding = 'UTF-8') as fd:
with open(prefs_file, "w", encoding = "UTF-8") as fd:
json.dump({
"credentials_enable_service": False,
"enable_do_not_track": True,
@@ -234,16 +234,16 @@ class WebScrapingMixin:
case "Windows":
browser_paths = [
os.environ.get("PROGRAMFILES", "C:\\Program Files") + r'\Microsoft\Edge\Application\msedge.exe',
os.environ.get("PROGRAMFILES(X86)", "C:\\Program Files (x86)") + r'\Microsoft\Edge\Application\msedge.exe',
os.environ.get("PROGRAMFILES", "C:\\Program Files") + r"\Microsoft\Edge\Application\msedge.exe",
os.environ.get("PROGRAMFILES(X86)", "C:\\Program Files (x86)") + r"\Microsoft\Edge\Application\msedge.exe",
os.environ["PROGRAMFILES"] + r'\Chromium\Application\chrome.exe',
os.environ["PROGRAMFILES(X86)"] + r'\Chromium\Application\chrome.exe',
os.environ["LOCALAPPDATA"] + r'\Chromium\Application\chrome.exe',
os.environ["PROGRAMFILES"] + r"\Chromium\Application\chrome.exe",
os.environ["PROGRAMFILES(X86)"] + r"\Chromium\Application\chrome.exe",
os.environ["LOCALAPPDATA"] + r"\Chromium\Application\chrome.exe",
os.environ["PROGRAMFILES"] + r'\Chrome\Application\chrome.exe',
os.environ["PROGRAMFILES(X86)"] + r'\Chrome\Application\chrome.exe',
os.environ["LOCALAPPDATA"] + r'\Chrome\Application\chrome.exe',
os.environ["PROGRAMFILES"] + r"\Chrome\Application\chrome.exe",
os.environ["PROGRAMFILES(X86)"] + r"\Chrome\Application\chrome.exe",
os.environ["LOCALAPPDATA"] + r"\Chrome\Application\chrome.exe",
shutil.which("msedge.exe"),
shutil.which("chromium.exe"),
@@ -259,8 +259,8 @@ class WebScrapingMixin:
raise AssertionError(_("Installed browser could not be detected"))
async def web_await(self, condition: Callable[[], T | Never | Coroutine[Any, Any, T | Never]], *,
timeout:int | float = 5, timeout_error_message: str = "") -> T:
async def web_await(self, condition:Callable[[], T | Never | Coroutine[Any, Any, T | Never]], *,
timeout:int | float = 5, timeout_error_message:str = "") -> T:
"""
Blocks/waits until the given condition is met.
@@ -523,7 +523,7 @@ class WebScrapingMixin:
return response
# pylint: enable=dangerous-default-value
async def web_scroll_page_down(self, scroll_length: int = 10, scroll_speed: int = 10_000, *, scroll_back_top: bool = False) -> None:
async def web_scroll_page_down(self, scroll_length:int = 10, scroll_speed:int = 10_000, *, scroll_back_top:bool = False) -> None:
"""
Smoothly scrolls the current web page down.
@@ -532,7 +532,7 @@ class WebScrapingMixin:
:param scroll_back_top: whether to scroll the page back to the top after scrolling to the bottom
"""
current_y_pos = 0
bottom_y_pos: int = await self.web_execute('document.body.scrollHeight') # get bottom position
bottom_y_pos:int = await self.web_execute("document.body.scrollHeight") # get bottom position
while current_y_pos < bottom_y_pos: # scroll in steps until bottom reached
current_y_pos += scroll_length
await self.web_execute(f'window.scrollTo(0, {current_y_pos})') # scroll one step