From 19faf0d798fb50a2007c4216d9954da240a5fb8b Mon Sep 17 00:00:00 2001 From: Marco Schmid Date: Tue, 12 May 2026 08:48:10 +0200 Subject: [PATCH] Task_7: Logging --- TASK.md | 24 +++----------- src/overpass/main.py | 15 ++++++--- src/overpass/models.py | 16 ++++++++++ src/overpass/overpass.py | 68 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 94 insertions(+), 29 deletions(-) create mode 100644 src/overpass/models.py diff --git a/TASK.md b/TASK.md index d11e47f..ae4a203 100644 --- a/TASK.md +++ b/TASK.md @@ -1,21 +1,5 @@ -# TASK 6: +# TASK 7: -Wir arbeiten nun intern direkt mit den Daten von Overpass, will heissen `results` hat die gleiche Struktur wie die Daten, -welche von Overpass kommen. Macht das Sinn? Warum vielleicht nicht? - -* Wir versuchen einen Adapter zu bauen. Intern wollen wir mit einer eigenen Dataclass `POI` arbeiten. Wir bauen also dazu eine - Funktion `load_pois`, welche einerseits die Daten fetched und andererseits auch parsed. Den Fetching-Teil haben wir - bereits (`fetch_overpass`), den Pasing-Teil haben wir noch nicht. - Schreibt bitte eine eigene Dataclass`Poi` in welche die gefetchten Daten 'abgefüllt' werden können. -* Die Datenklasse `POI` könnt ihr in einem neuen Modul `models.py` ablegen. -* Die Struktur von `POI` sieht z.B. wie folgt aus: - -``` -class POI: - id: str - type: str - poi_type: str - lat: float - lon: float - tags: dict -``` \ No newline at end of file +* Jetzt wäre doch ein guter Zeitpunkt um anstelle der print-Statements das Logging einzubauen. Verwendet das + Logging-Modul in Python und ersetzt alle print-statements! Ab nun verwenden wir nur noch das Logging-Module im + Miniprojekt. \ No newline at end of file diff --git a/src/overpass/main.py b/src/overpass/main.py index 916c22b..5e0e79f 100644 --- a/src/overpass/main.py +++ b/src/overpass/main.py @@ -1,4 +1,5 @@ -from overpass import fetch_overpass, OverpassApiError +from overpass import load_pois, OverpassApiError +from models import POI # --------------------------------------------------------------------------- # Konfiguration @@ -9,6 +10,8 @@ BBOXEN = { "schweiz": (45.8, 5.9, 47.8, 10.5), } +poi_type = "bergbahn" + QUERY = """ [out:json][timeout:2][maxsize:500000]; ( @@ -28,13 +31,15 @@ QUERY = """ def main() -> None: for name, bbox in BBOXEN.items(): try: - result = fetch_overpass(overpass_query=QUERY, bbox=bbox) + pois: list[POI] = load_pois(overpass_query=QUERY, bbox=bbox, poi_type=poi_type) except OverpassApiError as exc: - print(f" Fehler : {exc}") + print(f"Fehler bei '{name}': {exc}") continue - elements = result.get("elements", []) - print(elements) + print(f"\n{name}: {len(pois)} POIs gefunden") + for poi in pois: + print(f" {poi.id}: ({poi.lat}, {poi.lon})") + if __name__ == "__main__": main() \ No newline at end of file diff --git a/src/overpass/models.py b/src/overpass/models.py new file mode 100644 index 0000000..af727e3 --- /dev/null +++ b/src/overpass/models.py @@ -0,0 +1,16 @@ +from dataclasses import dataclass, field + +@dataclass +class POI: + id: str + type: str + poi_type: str + lat: float + lon: float + tags: dict = field(default_factory=dict) # weil mutable defaults in Dataclasses eine bekannte Python-Falle sind + # (alle Instanzen würden dasselbe Dict teilen...) + + +# REMARK: +# Wann eine eigene Dataclass für tags? +# Nur wenn die tags strukturiert und vorhersehbar sind, was bei OSM-Daten nicht der Fall ist... \ No newline at end of file diff --git a/src/overpass/overpass.py b/src/overpass/overpass.py index 803d0e2..58b24d7 100644 --- a/src/overpass/overpass.py +++ b/src/overpass/overpass.py @@ -1,15 +1,31 @@ import requests from pprint import pprint +from models import POI +import logging + +logger = logging.getLogger(__name__) OVERPASS_URL = "https://overpass-api.de/api/interpreter" +# REMARK: +# zwei Strategien: +# Fail-fast: Ein Fehler bricht alles ab → sinnvoll, wenn jedes Element kritisch ist +# Best-effort: Fehlerhafte Elemente überspringen, Rest verarbeiten → sinnvoll bei OSM-Daten, wo einzelne Einträge unvollständig sein können + + class OverpassApiError(Exception): pass -def fetch_overpass(overpass_query: str, bbox: tuple) -> dict: +def load_pois(overpass_query: str, bbox: tuple, poi_type: str) -> list[POI]: + """Führt Fetch und Parse zusammen aus.""" + raw = _fetch_overpass(overpass_query=overpass_query, bbox=bbox) + return _parse_pois(raw, poi_type) + + +def _fetch_overpass(overpass_query: str, bbox: tuple) -> dict: """ Fragt die Overpass API nach Bergbahnen in der angegebenen Bounding Box ab. Sendet einen HTTP-POST-Request an die Overpass API und gibt die geparste @@ -59,7 +75,7 @@ def fetch_overpass(overpass_query: str, bbox: tuple) -> dict: response = requests.post( OVERPASS_URL, data={"data": query}, - timeout=5, + timeout=15, headers={"User-Agent": "CDS Exercise"}, ) response.raise_for_status() # prüft den HTTP-Statuscode der Antwort und wirft eine Exception, wenn es ein Fehler war (requests.HTTPError) @@ -67,7 +83,51 @@ def fetch_overpass(overpass_query: str, bbox: tuple) -> dict: raise OverpassApiError("Overpass-API Timeout") from exc except requests.RequestException as exc: raise OverpassApiError("Overpass-API Request fehlgeschlagen") from exc - return response.json() + + data = response.json() + + # zusätzliche Fehlermöglichkeit -> Status ist zwar 200, aber Liste mit Ergebnissen ist leer... + if "remark" in data: + raise OverpassApiError(f"Overpass Query-Fehler: {data['remark']}") + + return data + + +def _parse_poi(data: dict, poi_type: str) -> POI: + """ Wandelt ein einzelnes Overpass-Element in ein POI-Objekt um. + + :param data: dictionary mit Daten für ein POI-Objekt + :param poi_type: Bezeichnung für die geladene POI-Gruppe (z.B. 'restaurant', 'bergbahn', ...) + :return: POI-Objekt + """ + try: + return POI( + id=data['id'], + poi_type=poi_type, + type=data.get('type', ''), + lat=float(data.get("lat") or data["center"]["lat"]), + lon=float(data.get("lon") or data["center"]["lon"]), + tags=data.get('tags', {}), + ) + except KeyError as exc: + raise OverpassApiError("Feld in API - Antwort fehlt") from exc + except (TypeError, ValueError) as exc: + raise OverpassApiError("API - Antwort hat falsches Format ") from exc + + + +def _parse_pois(raw: dict, poi_type: str) -> list[POI]: + """Extrahiert alle Elemente aus der API-Antwort und parst sie. + Fehlerhafte Elemente werden übersprungen und geloggt. + """ + pois = [] + for element in raw.get("elements", []): + try: + pois.append(_parse_poi(data=element, poi_type=poi_type)) + except OverpassApiError as exc: + logger.warning(f"POI übersprungen (id={element.get('id', '?')}): {exc}") + return pois + if __name__ == "__main__": @@ -86,5 +146,5 @@ if __name__ == "__main__": bbox = (46.72, 9.70, 46.92, 10.00) # bbox = (45.8, 5.9, 47.8, 10.5) - result = fetch_overpass(overpass_query=BERGBAHN_QUERY, bbox=bbox) + result = load_pois(overpass_query=BERGBAHN_QUERY, bbox=bbox) pprint(result) \ No newline at end of file