overpass/TASK.md

3.1 KiB

Task 17 — PostgresStorage

Rückblick Task 16: Lock und @timer-Decorator

Ihr habt CONCURRENT_LOCKED und den @timer-Decorator eingeführt. Die wichtigsten Punkte:

  • Warum Lock, obwohl der GIL schützt? Der GIL verhindert echte Parallelität auf CPU-Ebene, aber er gibt keine Garantien über die Reihenfolge von Operationen oder die Konsistenz komplexerer Datenstrukturen. Ein expliziter Lock macht die Absicht klar, ist portabel (auch ohne GIL, z.B. in PyPy oder zukünftigen Python-Versionen) und schützt bei read-modify-write-Operationen zuverlässig.
  • time.perf_counter() ist präziser als time.time() für Laufzeitmessungen, weil er eine hochauflösende Systemuhr verwendet und nicht durch Systemzeit-Anpassungen beeinflusst wird.

Aufgabe

Bisher speichern wir POIs in JSON-Dateien. Für grössere Datenmengen und spätere Abfragen ist eine Datenbank besser geeignet. Ziel ist es, PostgresStorage als zweites konkretes Backend zu implementieren — ohne main.py, pipeline.py oder fetcher.py anzufassen.

Vorbereitung:

  • Stelle sicher, dass eine PostgreSQL-Datenbank läuft und erreichbar ist.
  • Erstelle in der PostgreSQL-Datenbank eine neue Datenbank. Das kannst Du z.B. in PGAdmin oder aus dem Terminal erreichen. Gebt der neuen Datenbank einen Namen (z.B. overpass) und ordnet ihr einen Nutzer (z.B. postgres) inkl. Passwort zu.
  • Installiere psycopg2: pip install psycopg2-binary
  • Ergänze den Connection-String in config.yaml ("postgresql://user:password@localhost:5432/dbname").

Konkret:

  1. Ergänze in models.py zwei Methoden in der POI-Dataclass:
   def to_row(self) -> tuple:
       """POI-Objekt → DB-Zeile (Schreiben)"""
       ...

   @classmethod
   def from_row(cls, row: dict) -> POI:
       """DB-Zeile → POI-Objekt (Lesen)"""
       ...

Die tags in POI sollen als JSONB gespeichert werden — verwende dafür psycopg2.extras.Json.

  1. Implementiere PostgresStorage(Storage) in storage.py:

    • Nimmt connection_string: str und table: str = "pois" entgegen.
    • _ensure_table(): Erstellt die Tabelle, falls sie nicht existiert (Primary Key: (id, poi_type)).
    • store(): Schreibt alle POIs per UPSERT in die Tabelle (ON CONFLICT DO UPDATE). Verwende execute_values aus psycopg2.extras für effizientes Bulk-Insert.
    • Verwende with conn: als Context-Manager für Transaktionen (automatisches commit/rollback).
    • Für die Bildung des SQL-Statements könnt ihr gerne in den Unterrichtsunterlagen (Folien) 'spicken'.
  2. Ergänze build_storage() — der POSTGRES-Case soll nun PostgresStorage(**params) zurückgeben statt NotImplementedError.

  3. Passe config.yaml an:

   storage:
     type: postgres
     params:
       connection_string: "postgresql://user:password@localhost:5432/dbname"

Fragen zum Nachdenken:

  • Warum ON CONFLICT DO UPDATE (UPSERT) statt einem einfachen INSERT — was passiert ohne es beim zweiten Ausführen?
  • Was macht with conn: als Context-Manager — was passiert bei einem Fehler innerhalb des Blocks?