Programmabsturz trotz Exception handling

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
hwm
User
Beiträge: 39
Registriert: Mittwoch 20. April 2005, 23:33

@BlackJack: nahezu genauso sah mein Script nach den vorgeschlagenen Änderungen aus, mit der einen Ausnahme, Vermeidung von 'INSERT OR REPLACE'

@Snafu: In meinem Job habe ich leider mit 'Moderne datenbankbasierte Anwendungen' nichts zu tun, ich entwickle auf SAP Systemen.

Euch allen einen guten Rutsch nach 2015.
BlackJack

@hwm: Ich weiss das Programm sieht inzwischen etwas anders aus aber im ersten gezeigten Programm ist übrigens ein Fehler: Du verwendest `sys.exit()` ohne `sys` importiert zu haben.

Die Fehlermeldung wenn eine Verbindung zur Datenbank fehlschlägt ist irreführend. Wenn die Datenbankdatei nicht existiert, dann wird sie automatisch angelegt, also kann der Grund für eine Ausnahme nicht sein das die Datei nicht gefunden wurde.

Falls die Datei nicht existierte und angelegt werden musste, dann ist die Datenank leer aber das Programm läuft weiter und dann natürlich bei der ersten Datenankanfrage in eine Ausnahme weil die Tabellen nicht existieren. Dieser Fall wird vom Programm aber nirgends behandelt. Ich würde da einfach auch die ”Fehlerbehandlung” beim `connect()` weglassen. Das Programm bricht sowieso ab und man hat mehr Informationen, zum Beispiel den Traceback und die genaue Ausnahme plus potentiell weitere Informationen.

Nochmal völlig ungetestet wie das dann mit SQLAlchemy ohne SQL in Zeichenketten aussehen könnte:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function

from datetime import datetime as DateTime
from time import sleep
from urllib2 import URLError

import lnetatmo
from sqlalchemy import (
    and_, Column, create_engine, Date as SADate, Float, ForeignKey, Integer,
    MetaData, select, Table, Text, DateTime as SADateTime, UniqueConstraint
)

metadata = MetaData()


Device = Table('device', metadata,
    Column('id', Integer, primary_key=True, autoincrement=True),
    Column('name', Text, unique=True),
)


Measurement = Table('measurement', metadata,
    Column('id', Integer, primary_key=True, autoincrement=True),
    Column('device_id', ForeignKey(Device.c.id), nullable=False),
    Column('timestamp', SADateTime, nullable=False),
    Column('temperature', Float),
    Column('pressure', Float),
    Column('noise', Float),
    Column('co_2', Float),
    Column('humidity', Float),
    Column('battery_vp', Float),
)


Extrema = Table('extrema', metadata,
    Column('id', Integer, primary_key=True, autoincrement=True),
    Column('device_id', ForeignKey(Device.c.id), nullable=False),
    Column('date', SADate, nullable=False),
    Column('min_value', Float, nullable=False),
    Column('max_value', Float, nullable=False),
    UniqueConstraint('device_id', 'date'),
)


def main():
    engine = create_engine('sqlite:///netatmo.db3', echo=True)
    connection = engine.connect()
    metadata.create_all(engine)

    while True:
        try:
            devices = lnetatmo.DeviceList(lnetatmo.ClientAuth())
        except URLError:
            print('Problem with Netatmo server, will retry in 60 seconds')
            sleep(60)
        else:
            for name, values in devices.lastData().items():
                device_id = connection.execute(
                    select([Device.c.id]).where(Device.c.name == name)
                ).fetchone()
                if device_id is None:
                    device_id = connection.execute(
                        Device.insert().values(name=name)
                    ).inserted_primary_key

                timestamp = DateTime.now()
                connection.execute(
                    Measurement.insert().values(
                        device_id=device_id,
                        timestamp=timestamp,
                        temperature=values['Temperature'],
                        pressure=values.get('Pressure', 0),
                        noise=values.get('Noise', 0),
                        co_2=values.get('CO2', 0),
                        humidity=values.get('Humidity', 0),
                        battery_vp=values.get('battery_vp', 0)
                    )
                )

                date = timestamp.date()
                extrema = connection.execute(
                    Extrema.select().where(
                        and_(
                            Extrema.c.device_id == device_id,
                            Extrema.c.date == date
                        )
                    )
                ).fetchone()
                min_value = values.get('min_temp', 0)
                max_value = values.get('max_temp', 0)
                if extrema is None:
                    connection.execute(
                        Extrema.insert().values(
                            device_id=device_id,
                            date=date,
                            min_value=min_value,
                            max_value=min_value,
                        )
                    )
                else:
                    connection.execute(
                        Extrema.update().values(
                            min_value=min_value, max_value=max_value
                        ).where(Extrema.c.id == extrema[Extrema.c.id])
                    )

                connection.commit()

            sleep(600)


if __name__ == '__main__':
    main()
Man hat hier zwar jetzt die zusätzliche Arbeit die Tabellenstruktur als Objekte zu definieren (obwohl man sich die auch von der Datenbank abfragen lassen könnte!), aber das ermöglicht es dann auch mit der Zeile nach der `connection` die Tabellen anzulegen sofern sie noch nicht existieren. Und bei den `values()`-Methodenaufrufen sieht man viel leichter welcher Wert welcher Spaltezugeordnet wird im Gegensatz zum SQL wo man Spaltennamen und Werte getrennt angibt, und erst die Positionen abzählen muss wenn man wissen will welcher Wert zu einer bestimmten Spalte gehört oder zu welcher Spalte ein bestimmter Wert gehört.

Der `create_all()`-Aufruf setzt übrigens folgendes SQL ab:

Code: Alles auswählen

CREATE TABLE device (
        id INTEGER NOT NULL, 
        name TEXT, 
        PRIMARY KEY (id), 
        UNIQUE (name)
)

CREATE TABLE extrema (
        id INTEGER NOT NULL, 
        device_id INTEGER NOT NULL, 
        date DATE NOT NULL, 
        min_value FLOAT NOT NULL, 
        max_value FLOAT NOT NULL, 
        PRIMARY KEY (id), 
        UNIQUE (device_id, date), 
        FOREIGN KEY(device_id) REFERENCES device (id)
)

CREATE TABLE measurement (
        id INTEGER NOT NULL, 
        device_id INTEGER NOT NULL, 
        timestamp DATETIME NOT NULL, 
        temperature FLOAT, 
        pressure FLOAT, 
        noise FLOAT, 
        co_2 FLOAT, 
        humidity FLOAT, 
        battery_vp FLOAT, 
        PRIMARY KEY (id), 
        FOREIGN KEY(device_id) REFERENCES device (id)
)
hwm
User
Beiträge: 39
Registriert: Mittwoch 20. April 2005, 23:33

@BlackJack: stimmt, fehlgeschlagene Verbindungen zur DB gibt es bei Sqlite nicht, das Coding hatte ich aus meinen Postgres Anwendungen kopiert. Das ist das erste mal, dass ich Sqlite verwende, ist doch ziemlich anders als z. B.Postgres., mit Vor- und eben auch Nachteilen. Die Exception habe ich entfernt, somit erübrigt sich auch das fehlende import sys (was in diesem Fall nicht tragisch wäre, da die Exception ja sowieso nie auftreten kann; bei Postgres würde das Programm einfach abstürzen, was auch nicht tragisch wäre, denn ohne Datenbankverbindung muss sich das Programm sowieso verabschieden).

SQLAlchemy muss ich mir dann doch mal näher ansehen.
BlackJack

Und noch mal etwas ungetestetes — diesmal mit dem ORM:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function

from datetime import datetime as DateTime
from time import sleep
from urllib2 import URLError

#import lnetatmo
from sqlalchemy import (
    and_, Column, create_engine, Date as SADate, Float, ForeignKey, Integer,
    select, Text, DateTime as SADateTime, UniqueConstraint
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()


class Device(Base):

    __tablename__ = 'device'

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(Text, unique=True)

    @classmethod
    def get_or_create_by_name(cls, session, name):
        result = session.query(cls).filter_by(name=name).scalar()
        if result is None:
            result = cls(name=name)
            session.add(result)
        return result


class Measurement(Base):

    __tablename__ = 'measurement'
    
    id = Column(Integer, primary_key=True, autoincrement=True)
    device_id = Column(ForeignKey(Device.id), nullable=False)
    timestamp = Column(SADateTime, nullable=False)
    temperature = Column(Float)
    pressure = Column(Float)
    noise = Column(Float)
    co_2 = Column(Float)
    humidity = Column(Float)
    battery_vp = Column(Float)


class Extrema(Base):

    __tablename__ = 'extrema'
    
    id = Column(Integer, primary_key=True, autoincrement=True)
    device_id = Column(ForeignKey(Device.id), nullable=False)
    date = Column(SADate, nullable=False)
    min_value = Column(Float, nullable=False)
    max_value = Column(Float, nullable=False)

    __table_args__ = (UniqueConstraint(device_id, date),)

    @classmethod
    def update_values(cls, session, device, date, min_value, max_value):
        extrema = (
            session.query(cls)
                .filter_by(device_id=device.id, date=date)
                .scalar()
        )
        if extrema is None:
            session.add(
                Extrema(
                    device_id=device.id,
                    date=date,
                    min_value=min_value,
                    max_value=max_value
                )
            )
        else:
            extrema.min_value = min_value
            extrema.max_value = max_value


def main():
    engine = create_engine('sqlite:///netatmo.db3', echo=True)
    Base.metadata.create_all(engine)
    session = sessionmaker(engine)()

    while True:
        try:
            devices = lnetatmo.DeviceList(lnetatmo.ClientAuth())
        except URLError:
            print('Problem with Netatmo server, will retry in 60 seconds')
            sleep(60)
        else:
            for name, values in devices.lastData().items():
                device = Device.get_or_create_by_name(session, name)
                timestamp = DateTime.now()
                session.add(
                    Measurement(
                        device_id=device.id,
                        timestamp=timestamp,
                        temperature=values['Temperature'],
                        pressure=values.get('Pressure', 0),
                        noise=values.get('Noise', 0),
                        co_2=values.get('CO2', 0),
                        humidity=values.get('Humidity', 0),
                        battery_vp=values.get('battery_vp', 0)
                    )
                )
                Extrema.update_values(
                    session,
                    device,
                    timestamp.date(),
                    values.get('min_temp', 0),
                    values.get('max_temp', 0)
                )
                session.commit()

            sleep(600)


if __name__ == '__main__':
    main()
Antworten