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:
Das sieht alles recht zusammengestoppelt aus. Wenn Du das SqlAlchemy-Tutorial ernsthaft durchgearbeitet hättest, kämst Du eher bei sowas raus:

Code: Alles auswählen

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

# default db engine
DB_URI = 'sqlite:///:memory:'

Base = declarative_base()

class MyModel(Base):
    __tablename__ = 'mymodel'
    id = Column(Integer, primary_key=True)
    some_string = Column(String(63))
    some_number = Column(Integer)

    def __repr__(self):
        return "<MyModel(some_string='%s', some_number='%s')>" % (
            self.some_string, self.some_number)


def get_db_session(uri=DB_URI):
    engine = create_engine(uri, echo=True)
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    return Session()


if __name__ == '__main__':
    session = get_db_session()

    # create object and write it to database
    obj = MyModel(some_string='abc', some_number=123)
    session.add(obj)
    session.commit()

    # query MyModel in database
    for obj in session.query(MyModel):
        print obj
Solange Du Dich auf die Standardfeldtypen beschränkt, baut SqlAlchemy das wunderbar DBMS-unabhängig zusammen und Du kannst durch Angabe eines anderen Datenbank-URIs das Backend wechseln. Wenn Du aber von Anfang an auf die Dialekte gehst, wirst Du massive Probleme mit anderen Backends bekommen und musst dann händisch patchen oder Workarounds implementieren. Warum machst Du Dir die Sache so schwer?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Danke. Ich muss mir das mal verinnerlichen. Jedoch käme es nicht dabei raus, wenn ich das SQLAlchemy ernsthaft befolgt hätte. Mir geht es zunächst um eine ganz normale Anmeldung am MySQL-Server. NUR eine Anmeldung. Du hier hier quasi einen Schritt weiter gegangen. Frage: Wo werden die Anmelde-Daten angegeben? Du gehst hierbei nur auf SQLite ein, richtig? Was ich zunächst einmal versuche, ist, dass ich eine Anmelde-Maske erstelle. Der Benutzer soll sich also erst einmal anmelden, und nicht gleich Datensätze in die Datenbank hinterlegen. Der Benutzer kann sich also "nur" erst einmal anmelden, und später, sagen wir mal 1 Stunde später, Datensätze hinterlegen. Meine Idee ist also, dass die Anmeldedaten, sobald sie die Anmeldung bei der Datenbank erfolgreich war, in einem Wörterbuch gespeichert werden. Und beim Manipulieren der Datensätze werden dann die Anmeldedaten aus dem Wörterbuch gelesen, ind die Session gepackt und dann durchgeführt. Ich werde mal versuchen dein Skript zu verstehen, und auf meine Bedürfnisse anzupassen.

Und inwiefern kriege ich Probleme, wenn ich auf Dialekte eingehe? Das ich am Ende für mehrere Datenbanksysteme mehre LogIn-Funktionen schreiben müsste?
Zuletzt geändert von Sophus am Sonntag 19. Juli 2015, 17:57, insgesamt 1-mal geändert.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

So würde meine "Vorab-Version" aussehen:

Code: Alles auswählen

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

Base = declarative_base()
# a function from login-form
def log_in(self):
    server_host = str(self.ui_login.lineEdit_sevrer_host.text())
    username = str(self.ui_login.lineEdit_username.text())
    password = str(self.ui_login.lineEdit_password.text())
    database_name = str(self.ui_login.lineEdit_database_name.text())
    port = str(self.ui_login.lineEdit_port.text())
    mysql_connection_result = manage_mysql.connect_to_mysql(server_host, username, password, database_name, port)

# a function in a core module
def manage_dict(db_host, db_user, db_passwd, db_name, db_port):
    # empty dictionary
    dict = {'server_host': '',
            'username': '',
            'password': '',
            'database_name': '',
            'port': ''};
    # updating the dictionary
    dict['server_host'] = db_host
    dict['username'] = db_user
    dict['password'] = db_passwd
    dict['database_name'] = db_name
    dict['port'] = db_port

def connect_to_mysql(db_host, db_user, db_passwd, db_name, db_port):
    try:
        DB_URI = "mysql+pyodbc://{user}:{password}@{host}:{port}/{db}"
        engine = create_engine(DB_URI.format(
              user=db_user,
              password=db_passwd,
              host=db_host,
              port=db_port,
              db=db_name,
              connect_args = {'time_zone': '+00:00'},
              encoding='latin1',
              echo=True)) # Die Anmeldung funktioniert, nur wird über die Konsole kein Echo zurückgegeben.
        
        connection = engine.connect() # for testing - connect to database
        connection.execute('USE ' + db_name) # is there a datasebase?
        Base.metadata.create_all(engine) # 
        Session = sessionmaker(bind=engine) # create a session
        connection.close() # close connection to database
        
        manage_dict(db_host, db_user, db_passwd, db_name, db_port) # write in dictonairy
        
        return True
    except exception.SQLAlchemyError as AlErr:
        return AlErr[0]
 
Bedenke, dass es mir zunächst um eine Anmeldung geht, nicht darum gleich irgendwelche Datensätze in die Datenbank zu hinterlegen. Es ist durchaus möglich, und dies soll in meinem Programm auch gegeben werde, dass der Benutzer sich zunächst über die Anmelde-Maske beim MSQL-Server anmeldet. Der Benutzer muss aber nicht gleich mit der Arbeit beginnen. Die Anmelde-Maske ist von der eigentlichen Arbeitsmaske (Filme, Bücher, Adressbuch, Musik) losgelöst. Nicht mehr, und nicht weniger.
Zuletzt geändert von Sophus am Sonntag 19. Juli 2015, 18:25, insgesamt 1-mal geändert.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus: Meh, ich bin raus. Du wirst das schon machen.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Ich wollte dich nicht verjagen. Mir ging es nur darum, dass ich mich zuerst auf die Anmeldung konzentriere, nicht auf die Daten-Manipulation. Bei deinem Skript würde es heißen, dass man gleich zur Datenverarbeitung übergeht. Ich möchte erst einmal eine Anmeldung basteln. Wenn die Anmeldung steht, dann kann man mit den Daten immer auf den Server zugreifen. So war das meine Idee. Wenn du also Kritik an meinem Vorab-Skript hast, denn immer her damit. Ich will ja schließlich was lernen. Mir fehlt nämlich der Schritt, wie ich mich über das ORM-Konzept auf eine Datenbank anmelde, denn in meinem Beispiel stütze ich mich noch zu sehr auf die Dialekte.
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: 17711
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: 6731
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