Fehler über Messagebox ausgeben lassen [SQLArchemy]

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus: Verjagen kannst Du mich nicht so leicht - allerdings ist meine Hilfsbereitschaft Dir gegenüber weit abgesunken. Mal diplomatisch formuliert. Das ist alles.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

solche Grundlegenden Sachen sollte es doch in irgendeinem sqlalchemy tutorial beschrieben sein...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Ich weiß deine Hilfe zu schätzen, aber diesmal kam ich mir nicht verstanden vor. Es wäre damit zu vergleichen, ich frage dich als Autofahrer, wie man zu Edeka kommt, und du erklärst mir wie man die Kirche erreicht, die man wunderbar bestaunen kann. Die Kirche mag vielleicht toll sein, aber mich interessiert der Edeka. Konkret. Ich versuche eine Anmeldung zu kreieren, und keine Daten-Manipulation. Ich möchte also über das ORM-Konzept eine Anmeldung erstellen. Wenn das erledigt ist, dann käme der nächste Schritt. Ich wollte einen Schritt nach dem anderen machen.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jens: Wäre dann mein oben genanntes Beispiel denn nicht korrekt?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ich kenne mich mit SQLAlchemy nicht aus. Aber einen DB connect machen und irgendwas nachschlagen, sind ja wirklich Grundlagen. Das sollte IMHO in jedem Einsteiger Tutorial besprochen werden.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jens: Im Grunde hast du auch Recht. Die Verbindung besteht ja auch. Aber Sirius3 hat etwas angesprochen, was mir nun keine Ruhe lässt, und zwar ODBC. Ich möchte, das mein Programm ohne Probleme weitere Datenbanken ansprechen kann, wie zu Beispiel PostgreSQL und MS SQL sowie MySQL. Ich ging davon aus, dass die Bibliothek pymsql ausreichend wäre. Aber damit hätte ich bestimmt keine Allgemeinheit geschaffen, denke ich.

Hier mein derzeit lauffähiges Programm:

Code: Alles auswählen

#!/usr/bin/env python
#-*- coding:utf-8 -*-

FILE_NAME = "manage_mysql.py"

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
from sqlalchemy.exc import SQLAlchemyError

import pymysql
import pyodbc
import sys

from PyQt4 import QtGui, QtCore

Base = declarative_base()

def manage_dict(db_sess):
    # create an empty dictionary
    dict = {'sess': ''};
    
    # updating the dictionary
    dict['sess'] = db_sess
    
    print "dict['Session']: ", dict['sess'];            
    
def connect_to_mysql(db_host, db_user, db_passwd, db_name, db_port):
    print "STATUS [OK]  (", FILE_NAME, "):  Calling the function (connect_to_mysql)"
    try:
        print "STATUS [OK]  (", FILE_NAME, "):  Creating an engine-instance"
        engine = create_engine(
            'mysql+pymysql://' + db_user + ':' + db_passwd + '@' + db_host + ':' + db_port + '/' + db_name,
             encoding='latin1', echo=False)
        
        Base.metadata.create_all(engine)
        Session = sessionmaker(bind=engine)
        
        print "SESSION:", Session
        print "STATUS [OK]  (", FILE_NAME, "):  Engine-instance was created"    

        print "STATUS [OK]  (", FILE_NAME, "):  Successfully connected to the database."
        connection = engine.connect()
        print "STATUS [OK]  (", FILE_NAME, "):  Successfully connected to the database."
        connection.execute('USE ' + db_name)
        print "STATUS [OK]  (", FILE_NAME, "):  Using the chosen database"
        connection.close()
        print "STATUS [OK]  (", FILE_NAME, "):  Successfully closed to the database."
        print "STATUS [OK]  (", FILE_NAME, "):  Returning a value"
        
        manage_dict(Session) # write in dictonairy
        
        return True
    except SQLAlchemyError as AlErr:
        print "STATUS [FAILED]  (", FILE_NAME, "):", AlErr #, sys.exc_info()[0]
        return AlErr[0]
        
class MyExampleWindow(QtGui.QDialog):    # any super class is okay
    def __init__(self, parent=None):
        super(MyExampleWindow, self).__init__(parent)
        
        self.button_login_in = QtGui.QPushButton('LogIn')
        self.button_close = QtGui.QPushButton('Close window')

        vlayout = QtGui.QVBoxLayout()

        self.lineEdit_sevrer_host = QtGui.QLineEdit()
        self.lineEdit_sevrer_host.setText("localhost")
        self.lineEdit_username = QtGui.QLineEdit()
        self.lineEdit_username.setText("username")
        self.lineEdit_password = QtGui.QLineEdit()
        self.lineEdit_password.setText("password")
        self.lineEdit_database_name = QtGui.QLineEdit()
        self.lineEdit_database_name.setText("database name")
        self.lineEdit_port = QtGui.QLineEdit()
        self.lineEdit_port.setText("3306")

        vlayout.addWidget(self.lineEdit_sevrer_host)
        vlayout.addWidget(self.lineEdit_username)
        vlayout.addWidget(self.lineEdit_password)
        vlayout.addWidget(self.lineEdit_database_name)
        vlayout.addWidget(self.lineEdit_port)

        vlayout.addWidget(self.button_login_in)
        vlayout.addWidget(self.button_close)

        self.setLayout(vlayout)
        
        self.button_login_in.clicked.connect(self.login_in_db)
        self.button_close.clicked.connect(self.close)
        
    def login_in_db(self):
        server_host = str(self.lineEdit_sevrer_host.text())
        username = str(self.lineEdit_username.text())
        password = str(self.lineEdit_password.text())
        database_name = str(self.lineEdit_database_name.text())
        port = str(self.lineEdit_port.text())
        
        mysql_connection_result = connect_to_mysql(server_host, username, password, database_name, port)
        print mysql_connection_result 

if __name__ == '__main__':
    # QApplication created only here.
    app = QtGui.QApplication([])
    window = MyExampleWindow()
    window.show()
    app.exec_()
Die Idee ist zunächst, die Session in einem Wörterbuch zu speichern. Wir stellen uns vor, dass sich der Benutzer zunächst über die Anmelde-Maske anmeldet. Noch werden keine Daten manipuliert. Damit sich der Benutzer nicht jedesmal erneut anmelden muss, sobald er in einem späteren Zeitraum einen Datensatz manipulieren möchte, werden diese eben in einem Wörterbuch gespeichert. Nun frage ich mich, ob dies notwenig sei oder ob SQLAlchemy die Session selbst "speichert". Also dass mein Weg unnötig sei? Und dann lässt mich das mit dem ODBC keine wirkliche Ruhe.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: dieses Herumgerate von Dir geht einem solangsam auf die Mütze. Du gehst von irgendwas aus? Was meinst Du, was das mysql bedeutet? Dass man damit eine PostgreSQL-Datenbank ansprechen kann!? Oder dass mysql nur eine andere Abkürzung für ODBC ist? Sinnloses importieren von pymysql und pyodbc hilft da auch nichts. Wenigstens die Dokumentation von SQLAlchemy zu den Funktionen, die Du benutzt, hättest Du lesen können.

Dann zu manage_dict: man erzeugt keine Wörterbücher mit leeren Einträgen (der Kommentar ist falsch) um sie danach mit "sinnvollen" Werten zu füllen. Sinnvoll in Anführungszeichen, weil die Benutzung eines Wörterbuchs hier keinen Sinn ergibt und auch die ganze Funktion nicht das hält, was sie verspricht.

Die Probleme beim create_engine bist Du jetzt ja in einem weiteren Thread angegangen. Dort hast Du wenigstens ein besseres Encoding. Die ganzen Statusmeldungen stören nur, "USE" ist nicht nur überflüssig, sondern wie jens schon angesprochen hat, Datenbankabhängig und hat damit hier nichts verloren, zumal das SQLAlchemy alles schon automatisch macht. Welchen Nutzen haben Kommentare wie "Returning a value"!? Funktionen sollten nur einen Datentyp zurückliefern und im Fehlerfall eine Exception werfen.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Sophus: Python bietet mit dem `logging`-Modul eine äußerst hilfreiche Möglichkeit für's Logging an. Aus all deinen `print`-Zeilen werden dann ``logging.debug()``-Zeilen. Alternativ kann man auch einen eigenen Logger via ``logging.getLogger('dein_loggername')`` erzeugen und auf diesem die `debug()`-Methode aufrufen.

`print`-Anweisungen in Funktionen sind fast immer ein Zeichen für schlechten Code. Ausnahmen bilden Funktionen, die explizit für das `print`en ausgelegt sind (z.B. kleine Hilfsfunktionen) oder ggf Funktionen, die man schnell mal zum Testen in die Python-Shell getippt hat. Im Code des eigentlichen Programms haben sie jedoch in den meisten Fällen (abgesehen von `main()`) nichts zu suchen.

Übrigens kann "richtiges" Loggen auch das automatisierte Testen vereinfachen. Denn anstatt die Debug-Informationen immer mit drin zu haben, weil `print` verwendet wird, kann man stattdessen durch das Nutzen von `logging.debug()` dem Anwender die Wahl lassen, ob er die Debug-Infos überhaupt angezeigt bekommen möchte. Wenn man dies ausschaltet, dann stören die Ausgaben auch bei den Tests nicht.

Auch sollten Log-Meldungen hilfreich für den Leser sein. "Using the chosen database" ist da viel zu allgemein. Welche Datenbank wird benutzt? Was genau wird gerade gemacht? Du solltest hier mehr Details angeben. Zum Beispiel darüber, welches Statement gerade ausgeführt wird und welche Datenbankinstanz gerade erzeugt bzw verwendet wird. Also weg von allgemeinem Bla-Bla und hin zu konkreten Informationen.

Benutzerausgaben im Fehlerfall, wie z.B. bei dir die Behandlung eines `SQLAlchemyError`s, sind oftmals an der Stelle, wo die Funktionalität aufgerufen wird, *nicht* so gut aufgehoben. Besser ist es, Funktionen welche die konkrete Schritte ausführen, durch eine übergeordnete Funktion aufzurufen und die möglicherweise aufgetretenen Fehler in dieser übergeordneten Funktion sinnvoll zu behandeln. Was ich damit sagen will: Noch handelt es sich bei dir um die Schritte zu Anmeldung an der Datenbank. Aber später wirst du ja auch diverse Veränderungen des Datenbestands an die Datenbank übermitteln. Du willst doch wohl kaum überall im Code, wo Interaktion mit der Datenbank betrieben wird, `try`-`except`-Blöcke für Benutzerausgaben einbauen. Man kann doch viel besser an einer zentralen Stelle im Programmcode alle auftretenden `SQLAlchemyError`s abfangen und diese dann in Benutzerausgaben (Logging-Einträge, Fehlerdialoge in der GUI und ähnliches) umwandeln.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: In einem anderen Thread habe ich meine Anmelde-Funktion bereits veröffentlicht, aber ich tue dies hier nochmal.

Code: Alles auswählen

def connect_to_mysql(dbm_system, dbm_driver, db_user, db_passwd, db_host, db_port, db_name):
    
    try:
        engine = create_engine('{db_sys}+{db_driver}://{user}:{password}@{host}:{port}/{db}'.format(
                                                                     db_sys=dbm_system,
                                                                     db_driver=dbm_driver,
                                                                     user=db_user,
                                                                     password=db_passwd,
                                                                     host=db_host,
                                                                     port=db_port,
                                                                     db=db_name,
                                                                     connect_args = {'time_zone': '+00:00'},
                                                                     encoding='utf8',
                                                                     echo=True))

        Base.metadata.create_all(engine)
        Session = sessionmaker(bind=engine)
        connection = engine.connect()
        connection.close()       
        return True
Ich habe meine Funktion etwas erweitert. Zwei neue Argumente sind hinzugekommen. Der Funktion wird dann einmal das Datenbanksystem (MySQl, PostgreSQL, MS SQL etc.) und dann einmal den dazugehörigen Treiber (pymssql, pymysql etc.) übergeben. Ich habe mich entschieden nicht auf ODBC zu fahren. Meine Entscheidung beruht darauf, weil es vom ODBC Treiber vom MySQL gleich drei Versionen gibt und ich nicht erraten möchte welchen davon der Anwender auf seinem Rechner hat, und weil ich dem Anwender nicht noch zusätzlich Arbeit aufhalsen möchte, dass er sich gefälligst einen ODBC-Treiber installieren soll, und das auch nur diesen, den ich will. Und überhaupt sähe dann der Code für die ODBC-Anwendung (für mich) sehr hässlich aus:

Code: Alles auswählen

 cndBase = pyodbc.connect("DRIVER={MySQL ODBC 3.51 Driver}; SERVER="+db_host+"; PORT="+db_port+";DATABASE="+db_name+"; UID="+db_user+"; PASSWORD="+db_port+";")
In der Geschweiften Klammer, in welcher der Treiber (Driver) mit angegeben werden muss ist wirklich hässlich, und man müsste sich hier auf eine Version einigen. Und dann betrifft das nur MySQL. Ich verfahre also so, dass ich mich vom ODBC-Treiber fernhalte, und für jedes Datenbanksystem eine Treiber-Bibliothek lade. Daher habe ich die Funktion erweitert. Ich habe ein zentrales Wörterbuch angelegt, die dann von den Einstellungen aus einer INI-Datei befüllt wird. Darin wird dann auch bekannt gegeben, für welches Datenbanksystem der Anwender entschieden hat. Und daraus wird dann die entsprechende DBAPI-Bibliothek geladen. Es ist vielleicht nicht der Königsweg, aber so erspare ich mir diese Version-Angaben und dieses Version-WirrWarr, und dem Anwender bleibt ein weiterer Installations-Schritt erspart.

Aber zum Thema Wörterbuch. Wieso erzeugt man kein leeres Wörterbuch, um es dann hinterher zu befüllen? Aber ich lese aus deiner Anmerkung heraus, dass ich die Session nicht in einem Wörterbuch schreiben brauche, da SQLAlchemy sie für sich speichert, richtig? Ich bin mir nur nicht sicher, ob SQLAlchemy die Session "lange" behält? Wie gesagt, ich stelle es mir immer vor, dass man sich erst einmal anmeldet, und sagen wir mal 3 Stunden nichts macht, und später Daten manipulieren will, und das Programm natürlich offen bleibt. Oder ob SQLAlcemy sie später für "ungültig" erklärt?

Ach, und der Wert True, der bei Erfolg zurückgegeben wird, ist in sofern wichtig, dass beim Erfolg das Anmeldefenster geschlossen wird. Im View-Modul wird also darauf geprüft, ob die Funktion True zurück liefert, wenn nicht, dann wird die Fehlermeldung in Form eines QMessageBox ausgegeben.

@snafu: Deine Anmerkung mit den Try-Blöcken klingt sehr plausibel und logisch. Danke. Ich muss mir mal deine Anmerkung durch den Kopf gehen lassen, und versuche mal das Ganze in einem Quelltext zu schreiben, denn momentan habe ich keine Ahnung wie das alles aussehen sollte.
Antworten