mirror of
https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git
synced 2026-03-12 10:31:50 +01:00
feat: add type safe Ad model
This commit is contained in:
committed by
Sebastian Thomschke
parent
1369da1c34
commit
6ede14596d
@@ -8,15 +8,18 @@ from gettext import gettext as _
|
||||
from importlib.resources import read_text as get_resource_as_string
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Any, Final
|
||||
from typing import Any, Final, TypeVar
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
from . import files, loggers # pylint: disable=cyclic-import
|
||||
from .misc import K, V
|
||||
|
||||
LOG:Final[loggers.Logger] = loggers.get_logger(__name__)
|
||||
|
||||
# https://mypy.readthedocs.io/en/stable/generics.html#generic-functions
|
||||
K = TypeVar("K")
|
||||
V = TypeVar("V")
|
||||
|
||||
|
||||
def apply_defaults(
|
||||
target:dict[Any, Any],
|
||||
|
||||
@@ -5,14 +5,12 @@ import asyncio, decimal, re, sys, time # isort: skip
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from gettext import gettext as _
|
||||
from typing import Any, TypeVar
|
||||
from typing import Any, Mapping, TypeVar
|
||||
|
||||
from . import i18n
|
||||
|
||||
# https://mypy.readthedocs.io/en/stable/generics.html#generic-functions
|
||||
T = TypeVar("T")
|
||||
K = TypeVar("K")
|
||||
V = TypeVar("V")
|
||||
|
||||
|
||||
def ensure(
|
||||
@@ -44,6 +42,63 @@ def ensure(
|
||||
time.sleep(poll_requency)
|
||||
|
||||
|
||||
def get_attr(obj:Mapping[str, Any] | Any, key:str, default:Any | None = None) -> Any:
|
||||
"""
|
||||
Unified getter for attribute or key access on objects or dicts.
|
||||
Supports dot-separated paths for nested access.
|
||||
|
||||
Args:
|
||||
obj: The object or dictionary to get the value from.
|
||||
key: The attribute or key name, possibly nested via dot notation (e.g. 'contact.email').
|
||||
default: A default value to return if the key/attribute path is not found.
|
||||
|
||||
Returns:
|
||||
The found value or the default.
|
||||
|
||||
Examples:
|
||||
>>> class User:
|
||||
... def __init__(self, contact): self.contact = contact
|
||||
|
||||
# [object] normal nested access:
|
||||
>>> get_attr(User({'email': 'user@example.com'}), 'contact.email')
|
||||
'user@example.com'
|
||||
|
||||
# [object] missing key at depth:
|
||||
>>> get_attr(User({'email': 'user@example.com'}), 'contact.foo') is None
|
||||
True
|
||||
|
||||
# [object] explicit None treated as missing:
|
||||
>>> get_attr(User({'email': None}), 'contact.email', default='n/a')
|
||||
'n/a'
|
||||
|
||||
# [object] parent in path is None:
|
||||
>>> get_attr(User(None), 'contact.email', default='n/a')
|
||||
'n/a'
|
||||
|
||||
# [dict] normal nested access:
|
||||
>>> get_attr({'contact': {'email': 'data@example.com'}}, 'contact.email')
|
||||
'data@example.com'
|
||||
|
||||
# [dict] missing key at depth:
|
||||
>>> get_attr({'contact': {'email': 'user@example.com'}}, 'contact.foo') is None
|
||||
True
|
||||
|
||||
# [dict] explicit None treated as missing:
|
||||
>>> get_attr({'contact': {'email': None}}, 'contact.email', default='n/a')
|
||||
'n/a'
|
||||
|
||||
# [dict] parent in path is None:
|
||||
>>> get_attr({}, 'contact.email', default='none')
|
||||
'none'
|
||||
"""
|
||||
for part in key.split("."):
|
||||
obj = obj.get(part) if isinstance(obj, Mapping) else getattr(obj, part, None)
|
||||
if obj is None:
|
||||
return default
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user