SQLAlchemy: Das verfluchte NOT NULL

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Benutzeravatar
Sophus
User
Beiträge: 1085
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Freitag 4. August 2017, 17:29

Hallo Leute,
um euch den Wind aus den Segeln zu nehmen, eines gleich vorweg: Ich weiß, dass der try-except-Block ziemlich groß ausgefallen ist. In der Regel sollte man diesen Block sehr gering halten, damit man auch am Ende an die tatsächlichen Ausnahmen gelangt. In meinem Fall können mehrere und unterschiedliche Fehler auftauchen. Das ist mir bewusst. Daher möchte ich mich für meine unsaubere Art entschuldigen. Des Weiteren wundern sich einige von euch, warum ich in Zeile 14 bei einem Feld, welches als Primärschlüssel deklariert wird, auch noch unique auf True setze. Nun, da ich zur Zeit erst einmal mit MySQL arbeite, und mir beim Zugriff auf die Datenbank aufgefallen ist, dass das Feld mit dem Primärschlüssel nicht auf unique gesetzt wurde, habe ich es händisch noch zur Sicherheit hinzugefügt. Damit bin ich wohl nun der leidigen Diskussionen aus dem Weg gegangen :)

Das eigentliche Problem
Kommen wir zum eigentlichen Problem. Ihr seht in Zeile 15, dass nullable auf False gesetzt wurde. Was es mit NOT NULL und NULL auf sich hat, ist mir klar. In meinem Fall möchte ich einen Eintrag erzwingen. Der Benutzer soll also zu einem Eintrag gezwungen werden. Vor diesem Hintergrund habe ich gehofft, dass SQLAlchemy beim Versuch, einen leeren Datensatz anzulegen, eine Ausnahme wirft, und ich somit den Benutzer informieren kann, dass er einen Wert vergessen hat. Wenn ich also den nachfolgenden ausführbaren Quelltext ausführe, meine Anmelde-Daten korrekt eingebe, und dann an der Stelle, wo ich irgendeinen Text eingeben soll einfach nur Enter betätigt, wirft SQLAlchemy keine Ausnahme. Er tut so, als sei nichts geschehen und führt den commit() getrost aus (da ich echo auf True gesetzt habe, kann ich das verfolgen).

Meine Frage an euch. Habe ich was übersehen? Ist das ein normales Verhalten von seitens SQLAlchemy?

Warum ich euch das frage? Ich habe zum Test mit "MySQL Workbench" den gleichen versuch gestartet, indem ich versuche einen leeren Datensatz anzulegen, und dort bekomme ich folgende Meldung:
ERROR 1364: Field 'some_text' doesn't have a default value

SQL Statement:

INSERT INTO `xarphus`.`some_text` (`id`) VALUES ('3')
Und sowas in der Art habe ich bei SQLAlchemy auch gehofft. Aber nichts passiert. Keinerlei Fehlermeldung.

Code: Alles auswählen

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

''' setting up root class for declarative declaration '''
Base = declarative_base()

class SOME_TEXT(Base):

    __tablename__ = "some_text"

    id = Column(Integer, primary_key=True, unique=True)
    some_text = Column(String(50), nullable=False, unique=True)

    def __init__(self, Some_text):

        self.some_text = Some_text

def main():
    try:
        dbms = raw_input('Enter database type: ')
        dbdriver = raw_input('Enter database driver: ')
        dbuser = raw_input('Enter user name: ')
        dbuser_pwd = raw_input('Enter user password: ')
        db_server_host = raw_input('Enter server host: ')
        dbport = raw_input('Enter port: ')
        db_name = raw_input('Enter database name: ')

        url = '{}+{}://{}:{}@{}:{}/{}'.format(
               dbms, dbdriver, dbuser, dbuser_pwd, db_server_host, dbport, db_name)
        
        ''' Create the engine '''
        engine = create_engine(url, echo=True)

        ''' Create the schema and tables '''
        Base.metadata.create_all(engine)

        ''' Create the Session class '''
        Session = sessionmaker(bind=engine)

        ''' Create a new Session '''
        session = Session()

        ''' Populate the database '''
        enter_text = raw_input('Enter text: ')
        some_text_table = SOME_TEXT(enter_text)
        session.add(some_text_table)

        ''' Commit the changes: '''
        session.commit()

    except SQLAlchemyError as err:
        print "err", err


if __name__ == "__main__":
    main()

    

BlackJack

Freitag 4. August 2017, 21:05

@Sophus: Primärschlüssel sind von Haus aus schon eindeutig, da braucht man kein UNIQUE. Wenn das in PRIMARY KEY nicht implizit wäre, würde es ja nicht als Primärschlüssel funktionieren.

Und ja es ist normales Verhalten das man keine Ausnahme bekommt wenn man etwas bei einer NOT NULL angibt. Es ist ja nicht so das Du nichts angibst oder NULL angibst sondern wenn Du einfach Enter drückst, dann hat die Variable ja eine leere Zeichenkette als Wert. Und eine leere Zeichenkette ist etwas anderes als NULL. Nur NULL ist NULL.
[codebox=text file=Unbenannt.txt]mysql> SELECT '' IS NULL;
+------------+
| '' IS NULL |
+------------+
| 0 |
+------------+
1 row in set (0.00 sec)

mysql> SELECT NULL IS NULL;
+--------------+
| NULL IS NULL |
+--------------+
| 1 |
+--------------+
1 row in set (0.00 sec)[/code]
Die `__init__()` würde ich übrigens nicht implementieren, insbesondere nicht ohne die der Basisklasse darin aufzurufen. Lass die einfach weg und nimm die der Basisklasse. Man muss dann halt die Argumente als Schlüsselwort-Argumente angeben, was ich aber nicht so schlimm finde als das in der `__init__()` zu machen.

Warum heisst die Klasse `SOME_TEXT` statt `SomeText`?
Benutzeravatar
Sophus
User
Beiträge: 1085
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Freitag 4. August 2017, 21:27

@BlackJack: Vielen dank. Warum ich SOME_TEXT anstatt Some_Text als Name einer Klasse genommen habe? Das ist eine verdammt gute Frage. Ich habe beim schnellen zusammenschustern vergessen daran zu denken. Ich war wohl zu übereifrig.

Und das du __init__()-Methode nicht implementieren würdest, das hast du (glaube ich) schon einmal erwähnt. Es ist irgendwie eine dumme Angewohnheit. Ich habe damals auf einer bestimmten Seite (die Adresse habe ich leider vergessen) so gelernt. Bei den Beispielen wurde stets diese Methode angeführt, unter anderem auch diese __repr__ ()-Methode. Diese habe ich allerdings schnell wieder abgelehnt.

Aktualisierung:
Ich habe die Seite gefunden, worüber ich damals gelernt habe: hier
Benutzeravatar
snafu
User
Beiträge: 5536
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Samstag 5. August 2017, 08:47

Sophus hat geschrieben:Ich weiß, dass der try-except-Block ziemlich groß ausgefallen ist. In der Regel sollte man diesen Block sehr gering halten, damit man auch am Ende an die tatsächlichen Ausnahmen gelangt. In meinem Fall können mehrere und unterschiedliche Fehler auftauchen. Das ist mir bewusst.
Es können tatsächlich unterschiedliche Ausnahmen auftreten, du fängst aber nur eine ab. Bis einschließlich zum Erzeugen von ``url`` kommt SQLAchemy noch gar nicht ins Spiel. Somit ist es besser, den Anfang bis zu dieser Stelle aus dem try-Block rauszunehmen. Den Rest würde ich dann als eigene Funktion auslagern (``create_db()`` oder so), diese aufrufen und den try-Block nur für den Aufruf verwenden. Das ändert nichts am Ergebnis, sieht aber IMHO ordentlicher aus.
shcol (Repo | Doc | PyPi)
BlackJack

Samstag 5. August 2017, 08:54

Ich würde diese Behandlung komplett rausnehmen, denn wenn eine Ausnahme auftritt, erfährt man zwar welche das war, aber nicht wo sie aufgetreten ist. Man verliert also wichtige Information die man zur Fehlersuche braucht. Durch diese Behandlung gewinnt man nichts, man verliert nur.

Edit: Die `__init__()` in dem verlinkten Tutorial ist da ja hauptsächlich weil das Objekt als normales, nicht gemapptes Objekt angefangen hat. Und die `__repr__()` führt zu sinnvolleren Ausgaben wenn man sich das in einer Python-Shell anschaut oder mal per ``print`` ausgibt. Das kann bei der Fehlersuche nützlich sein wenn man sich da wichtige Attributwerte anzeigen lassen kann.

Was die `__init__()` soll ist in den Kommentaren zum Blogartikel auch gleich eine der ersten Fragen und der Autor sagt dazu er wüsste das auch nicht, das SA-Tutorial würde das so machen. Da war SA 0.5.irgendwas, im aktuellen Tutorial steht so etwas nicht mehr.
Antworten