auf dem Raspberry Pi eine MySQL kommunikation

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
raspido
User
Beiträge: 31
Registriert: Montag 10. November 2014, 19:42
Kontaktdaten:

Hey Leute,

ich habe zur Zeit ein Script auf dem Raspberry Pi laufen und möchte dort nun Funktionen ergänzen, die zum Programm gehören. Nur leider komme ich da nicht vorran.

MySQL Habe ich an sich schon benutzt aber noch nicht im Zusammenhang mit Python.

Es geht um folgende Funktion:

Code: Alles auswählen

def einlagern(scan):

        abfrage = cursor.execute("SELECT * FROM warenlager WHERE barcode = '%s'" % scan)

        if abfrage == 0:
                print "Barcode unbekannt"
                print ""

        rows = cursor.fetchall()
        for row in rows:
                inmenge = input("Bitte Menge eingeben: ")
                neuemenge = (int(row[2]) + int(inmenge))
                cursor.execute("UPDATE warenlager SET menge=? WHERE barcode=?",(neuemenge, scan))
                return
Dieser Code soll als erstes über die Abfrage sicherstellen, dass das Produkt überhaupt vorhanden ist. Wenn dieses vorhanden ist, soll er die Menge über den UPDATE Befehl abändern. Die Abfragefunktion klappt, ich habe dies bereits vorher getestet und nutze diese in einer anderen Funktion schon.

Der Codeteil erzeugt folgende Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "mysql.py", line 60, in <module>
    einlagern(scan)
  File "mysql.py", line 33, in einlagern
    cursor.execute("UPDATE warenlager SET menge=? WHERE barcode=?",(neuemenge, scan))
  File "/usr/lib/python2.7/dist-packages/MySQLdb/cursors.py", line 159, in execute
    query = query % db.literal(args)
TypeError: not all arguments converted during string formatting


Kann mir jemand verraten wodran dies liegt? Wenn zusätzliche Informationen benötigt werden, bitte fragen.



Michael

PS: Wenn wir gerade schon dabei sind, kann man mir vielleicht noch ein Tipp geben, wie man folgende MySQL Befehle in Python nutzt:

INSERT
DELET
COUNT (Habe ich zwar an sich indirekt genutzt, aber nur ob ein Datensatz vorhanden ist, ggf. benötigt man noch mal die COUNT Funktion.)

Dadrüber hinaus, gibt es die Möglichkeit, die Connectdaten aus zu lagern? Also ähnlich wie bei PHP per include - Befehl, dass man die so einbinden kann aus einer zweiten Datei?
Hobby-Programmierung - Also KEIN Profi-Progger
BlackJack

@raspido: Beim ersten `execute()` machst Du etwas was man niemals machen solle: Werte in eine SQL-Zeichenkette formatieren. Das ist zum einen ein Sicherheitsrisiko (SQL-Injektion) und zum anderen kann die Datenbank dann den übersetzten SQL-Ausdruck nicht mehr Cachen weil der durch den Wert ja jedes mal anders aussieht. Beim zweiten Aufruf von `execute()` machst Du es (fast) richtig: Platzhalter für Werte im SQL-Ausdruck und die Werte selber als zweites Argument übergeben. Der Fehler, der dann auch zur Ausnahme führt, ist das Du hier sehr wahrscheinlich die falschen Platzhalter verwendest. Das '?' ist SQL-Standard, aber die Python DB-API v2 erlaubt den Autoren von DB-Anbindungen leider auch andere zu verwenden. Da musst Du in der Dokumentation zu dem Modul das Du verwendest mal schauen welches das benutzt.

Das ist einer der Gründe warum ich gerne SQLAlchemy statt SQL-Ausdrücke per Hand zu schreiben verwende: solche Unterschiede werden damit ausgebügelt.

Wo kommt `cursor` her? Solche Werte sollten Funktionen als Argumente betreten und nicht einfach so auf magische Weise bestehen. Am besten hat man auf Modulebene gar keine Variablen sondern definiert dort nur Konstanten, Funktionen, und Klassen. Das Hauptprogramm steht üblicherweise in einer `main()`-Funktion.

Wenn die Datenbank Transaktionen unterstützt sind die bei der DB-API v2 voreingestellt *an*, das heisst man sollte besser keine Transaktionen verwenden in denen der Benutzer Eingaben führ mehrere Datensätze machen kann. Der kann ja beliebig ”bummeln”, und aus Sicht von anderen Programmen ist eigentlich alles was so ein Mensch bei einer Eingabe machen kann irre laaaangsaaaam. Solange sollte man normalerweise keine Datensätze blockieren.

Falls ein Fehler auftritt sollte man die Transaktion auch zurückrollen. Passiert hier zum Beispiel sehr leicht wenn der Benutzer etwas eingibt was nicht in eine ganze Zahl konvertiert werden kann.

Der Code verlässt sich blind auf die Reihenfolge der beim ``SELECT *`` ausgewählten Spaltenwerte. Das ist fehleranfällig.

Das ``return`` an der Stelle sieht sehr falsch aus. Und eingerückt wird konventionell mit vier Leerzeichen pro Ebene.

``INSERT`` und ``DELETE`` benutzt man genau wie ``SELECT`` und ``UPDATE``. Und das hat nichts mit Python zu tun, denn in vielen anderen Sprachen benutzt man die genau so. Das ist ja eine eigene Sprache. Das gleiche gilt für SQL-Funktionen wie ``COUNT``.

Ein ``include`` wie in PHP gibt es $GOTT sei Dank nicht. Du kannst die Daten in ein Python-Modul auslagern und das importieren oder in eine Konfigurationsdatei schreiben. Das JSON-Format bietet sich da an. In der Python-Standardbibliothek gibt es ein Modul dafür.
raspido
User
Beiträge: 31
Registriert: Montag 10. November 2014, 19:42
Kontaktdaten:

@BlackJack: wäre nur dann nett, evtl. ein Tipp zu zeigen, wie man es stattdessen macht. Nur meckern bringt mich nicht wirklich vorran.

Und in PHP konnte man die SQL Befehle "unterbrechen" und man musste so keine Platzhalter und ähnliches verwenden. Daher ist es schon anders als in PHP.

Also in PHP sieht das etwa so aus:

Code: Alles auswählen

mysql_query("SELECT * FROM login WHERE username LIKE '$username'");
Da ist einfach $username rein gesetzt, was die Variable aus PHP angeht.

Und ich habe einfach nur die Funktion hier aufgelistet, ich habe vor die verschiedenen Funktionen so aufgeteilt, dass jede Funktion eine Aufgabe abdeckt. Und die Variable "cursor" beinhaltet die MySQL Verbindung. Und damit ich nicht bei jedem Funktionsaufruf die Verbindung neu aufbauen muss habe ich das eigentlich so gemacht. Den sonst müsste ich ja bei jedem Aufruf Funktion jedes mal eine neue Verbindung aufbauen.

Und soweit ich eigentlich dachte wäre main Funktion primär nötig, bei oop Programmierung. Und an sich hab ich es einfach gehalten, in dem ich ein paar Funktionen erzeuge und diese aus dem Programm her bei gewisser Aktion aufrufe. Ich hoffe man versteht was ich meine.

Ich könnte aber den Code auch mal im ganzen Posten und dann sieht man eher was ich genau mache.

Und wegen der Geschwindigkeit muss ich mir keine Gedanken machen. Die Datenbank steht NUR dem Programm zur Verfügung und es gibt nur 1 Instanz des Programms und somit kann es keine Probleme geben dass Datensätze länger belegt sind.

Und zu dem "Select *", wenn ich eh alle Spalten haben will, ist es doch die einfachste Lösung. Gab eigentlich auch noch nie Probleme, wenn ich lediglich ein Teil der Spalten will, macht das * kein Sinn.



Michael
Hobby-Programmierung - Also KEIN Profi-Progger
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

raspido hat geschrieben: Also in PHP sieht das etwa so aus:

Code: Alles auswählen

mysql_query("SELECT * FROM login WHERE username LIKE '$username'");
Da ist einfach $username rein gesetzt, was die Variable aus PHP angeht.
Was, wenn:

Code: Alles auswählen

$username = "'joe'; insert into login (username, password) values ('datathief', 'i pwnd u!!'); --";
?

Siehe auch: http://xkcd.com/327/
In specifications, Murphy's Law supersedes Ohm's.
raspido
User
Beiträge: 31
Registriert: Montag 10. November 2014, 19:42
Kontaktdaten:

@pillmuncher: Was willst damit sagen?!

Das bringt mich doch der Lösung nicht wirklich näher ran.



Michael
Hobby-Programmierung - Also KEIN Profi-Progger
BlackJack

@raspido: Was soll hier „meckern” heissen? Ich habe doch geschrieben wie man es macht und zwar so wie *Du* es im zweiten Aufruf ja schon gemacht hast, nur das die Fragezeichen nicht die richtigen Platzhalter sind. Welche die richtigen Platzhalter sind sollte in der Dokumentation des Moduls stehen welches Du verwendest. Ich weiss nicht welches das ist, es gibt für MySQL eine ganze handvoll Verschiedener.

Das was Du da in PHP machst ist keine Lösung. Das würde man in PHP so *nicht* lösen. Selbst wenn das eine Lösung wäre wüsstest Du ja wie man das in Python macht, denn auch das machst Du in Deinem Code bereits.

Statt bei jedem Aufruf eine neue Verbindung aufzubauen kannst Du eine bestehende Verbindung als Argument an die Funktion übergeben. `cursor` ist hoffentlich nicht wirklich die Verbindung, denn es gibt neben Verbindungen ja auch Cursor-Objekte und da ist das sehr verwirrend wenn `cursor` nicht für so ein Cursor-Objekt steht sondern für eine Verbindung.

Eine `main()`-Funktion ist immer nötig, weil man eben nie Variablen auf Modulebene haben möchte. Das wird mit der Zeit sonst sehr unübersichtlich solange bis der Code nicht mehr verständlich und damit schwer wart- und erweiterbar ist.

Wenn die Geschwindigkeit keine Rolle spielt und nur ein Programm darauf zugreift, sollte man vielleicht auch SQLite statt MySQL ins Auge fassen. Dann spart man sich einen vergleichsweise fetten Serverprozess.

Das Du noch nie Probleme mit dem ``*`` hattest, heisst nicht das es sie nicht gibt. Es gibt halt gewisse Sachen die man bei sauberer, robuster, und auf Zukunft ausgerichteter Programmierung nicht macht. Zudem ist es auch lesbarer wenn man dort die Spalten und die Ergebnisreihenfolge explizit auflistet.

Was pillmuncher Dir sagen will ist das man das so auch in PHP nicht macht, weil es gefährlich ist Werte einfach so in eine SQL-Zeichenkette einzufügen ohne besondere Schutzmassnahmen zu ergreifen. Du solltest echt mal versuchen zu verstehen was da passiert. Sonst solltest Du die Finger von Datenbankprogrammierung lassen. Und komm dann nicht mit der Begründung so etwas kann bei Dir nicht passieren weil… — das ist egal! Solchen Code *nicht* zu schreiben ist eine prinzipielle Sache. Man sollte den *immer* sicher schreiben. Insbesondere weil das ja auch gar nicht weiter schwer ist die Werte da nicht selber hinein zu formatieren sondern das dem Datenbankmodul zu überlassen.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Setze doch zunächst mal die Hinweise von BlackJack um, die stehen nicht ohne Grund da. Und pillmuncher hat dich auf SQL-Injections in deinem PHP-Code hingewiesen. So macht man das in PHP nämlich nicht. Außer, man möchte sich ganz sicher in den Fuß schießen. Dann ist das perfekt ;-)
Das Leben ist wie ein Tennisball.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

raspido hat geschrieben:@pillmuncher: Was willst damit sagen?!
Das hier: http://de.wikipedia.org/wiki/SQL_Injection

Außerdem:

Code: Alles auswählen

>>> x = input('? ')
? __import__('subprocess').call(['ls'],stdout=open('/dev/null','w'),stderr=open('/dev/null','w'),shell=True) or 123
>>> x
123
Und jetzt stellen wir uns vor, statt ['ls'] stünde da ['rm', '-rf', '~']
Deswegen: lies dir die Dokumentation zu raw_input() durch.
In specifications, Murphy's Law supersedes Ohm's.
raspido
User
Beiträge: 31
Registriert: Montag 10. November 2014, 19:42
Kontaktdaten:

@BlackJack: Meckern war evtl. nicht das richtige Wort. Sry wenn es evtl. falsch rüber gekommen ist.

Und als Funktionsumfang nutze ich dieses Modul "MySQLdb".

Ich wollte an sich bei MySQL bleiben, da evtl. in Zukunft noch eine externe Abfrage Möglichkeit hinzu kommen soll um Daten aus der Tabelle zu holen. Aber nur um zu holen.

Und dann noch bezüglich Sicherheit, danke für den Hinweis. Nur ich habe es in Vergangenheit immer nur so gemacht, weil es mir nie anders gezeigt wurde und auch in den Büchern und Co. so formuliert wurde, wie ich es bei dem PHP Code gezeigt habe.

Bezüglich der main müsste ich mir dann noch mal Gedanken machen, wie ich das vernünftig dann umsetzen kann.



Aber nun zu meinem größten Problem, bzw. Nachfrage ist, ist den der Select Teil den Sicherheitstechnisch gesehen ein guter Anfang außer das mit dem *? Also zumindest das ich mal auf die richtige Richtung nun komme. Wäre von daher nett, wenn man mich evtl. da ein wenig an die Hand nehmen würde. Ich bin kein Profiprogrammierer und mache es eher nur fürs Hobby oder um Lösungen für Probleme zu finden, die ich habe. Ob es immer die "Optimale" Lösung ist, sei mal dahin gestellt. Von daher wäre ich über jede Hilfe Dankbar.

Und bezüglilch der Platzhalter muss ich mal in die Doku gucken. Ich hoffe ich find was ich suche um da hin zu kommen, habe mich bislang da noch nicht groß dran gesetzt.



Michael



--------------------------------------------------------------------------
Änderung / Ergänzung:

@pillmuncher: Sry, aber bezüglich "raw_input" blicke ich leider nicht durch, was du mir damit sagen willst. Meine Situtation ist, ich habe ein Barcodescanner und dieser soll einfach über Input überwacht werden. Und dieser übergibt automatisch den Code mit einem "Enter", so dass die Eingabe direkt weiter verarbeitet wird. Es soll an dem Raspberry Pi lediglich ein paar Taster, der Barcodescanner und ein Textdisplay hängen. Also Keine Tastertur, Monitor oder ähnliches. Und die Datenbank selbst befindet sich auch nicht bei mir zuhause, sondern bei einem Anbieter wo ich auch extern per MySQL Befehle auf die Datenbank zugreifen kann.
Hobby-Programmierung - Also KEIN Profi-Progger
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

input() fragt eine Benutzereingabe ab und interpretiert diese dann als Python Code. Da kann der Benutzer allerhand Schabernack treiben, zB., wie ich gezeigt habe, alles im Benutzerverzeichnis löschen, ohne dass das Programm das mitbekommt. Statt dessen sollte man in Python 2.x raw_input() verwenden. Das liefert die Benutzereingabe als String zurück, den man dann programmatisch in geeignete Datenstrukturen parsen muss. Im einfachsten Fall belässt man die Eingabe als String, und im zweiteinfachsten wandelt man sie mittels int() oder float() in eine Zahl um:

Code: Alles auswählen

>>> x = raw_input('? ')
? 123
>>> x
'123'
>>> type(x)
<type 'str'>
>>> y = int(x)
>>> y
123
>>> type(y)
<type 'int'>
Die Eingabe wird dabei übrigens ebenso wie bei input() mit einem <Enter> abgeschlossen.

Soweit ich sehen kann ist das Platzhalterzeichen bei MySqlDb dasselbe wie bei der %-Stringformatierung in Python, sodass man wohl einfach schreiben kann:

Code: Alles auswählen

results = cursor.execute('select foo from bar where my_field = %s', ['hallo'])
Und analog dazu bei update:

Code: Alles auswählen

cursor.execute('update bar set foo = %d where my_field = %s', [123, 'hallo'])
In specifications, Murphy's Law supersedes Ohm's.
raspido
User
Beiträge: 31
Registriert: Montag 10. November 2014, 19:42
Kontaktdaten:

Danke @pillmuncher, natürlich auch die anderen.

Aber mit der letzten Hilfe kann ich als Anfänger am besten leben. Weil dieses Fachgequatsche hilft einem Anfänger wenig. Werde ich im Profil vermerken, dass ich kein Profi - Progger bin und es nur Hobbymäßig mache. Mein Täglich Brot verdiene ich mit anderen Dingen daher weniger Erfahrung.

Werde es morgen testen.



Michael
Hobby-Programmierung - Also KEIN Profi-Progger
BlackJack

@raspido: Noch mal zu PHP: Da wird die Zeichenkette nicht ”unterbrochen” sondern der Variablenname wird als Platzhalter für ihren Wert in die Zeichenkette geschrieben. Also doch Platzhalter.

In den Beispielen in der Dokumentation sieht man dann auch wie man es *richtig* macht, und zwar auch bei PHP mit Platzhaltern und Zeichenkettenformatierung und einer Funktion um die Werte entsprechend sicher zu machen, also sowohl SQL-Injektions zu verhindern als auch Programmfehler wenn der Benutzer ”komische” Zeichen im Namen hat:

Code: Alles auswählen

mysql_query(
    sprintf("SELECT * FROM login WHERE username = '%s'",
        mysql_real_escape_string($username)));
Und diese tolle Funktion mit dem so prägnanten Namen `mysql_real_escape_string()` muss man für jede Zeichenkette aufrufen die als Argument für einen Platzhalter in die SQL-Abfrage eingefügt werden soll. Argh!

Das ist einer der Gründe warum man die `mysql_*()`-Funktionen in PHP gar nicht mehr benutzen sollte. Die fliegen auch irgendwann demnächst aus PHP raus, dann *muss* man etwas anderes benutzen. Zum Beispiel `PDO`. Da sähe dass dann so aus:

Code: Alles auswählen

$statement = $database->prepare('SELECT * FROM login WHERE username = ?');
$statement->execute($username);
Also fast wie bei der DB-API V2 in Python, nur dass die `execute()`-Methode beides macht, man also kein `Statement`-Objekt als Zwischenschritt hat, und das leider das '?' nicht für alle Module gilt sondern es noch Alternativen gibt.

Darum benutze ich wie schon gesagt fast immer SQLAlchemy als Zwischenschicht und da in der Regel dann auch gleich das ORM-Modul und nicht nur die SQL-Abstraktion. Da sähe das dann so aus:

Code: Alles auswählen

login_user = session.query(Login).filter_by(username=username).one()
Das liefert dann auch gleich das Objekt für den Datensatz, was bei den PHP-Beispielen ja noch zusätzlichen Code, inklusive Fehlerbehandlung erfordern würde.

Der Code aus Deinem ersten Beitrag könnte dann so ähnlich wie das hier aussehen:

Code: Alles auswählen

# ...

class Article(Base):
    __tablename__ = 'warenlager'

    id = Column(INTEGER, primary_key=True)
    # ...
    barcode = Column(VARCHAR(30), unique=True)
    amount = Column('menge', INTEGER, nullable=False, default=0)
    # ...


def store(session, barcode, amount):
    try:
        article = session.query(Article).filter_by(barcode=barcode).one()
        article.amount += amount
    finally:
        session.commit()
Sirius3
User
Beiträge: 17738
Registriert: Sonntag 21. Oktober 2012, 17:20

@BlackJack: noch als kleine Anmerkung, normalerweise will man ja bei einem Fehler kein commit, sondern ein rollback:

Code: Alles auswählen

def store(session, barcode, amount):
    try:
        article = session.query(Article).filter_by(barcode=barcode).one()
        article.amount += amount
    except Exception:
        session.rollback()
        raise
    else:
        session.commit()
raspido
User
Beiträge: 31
Registriert: Montag 10. November 2014, 19:42
Kontaktdaten:

@BlackJack:

Zu dem Codebeispiel, sry bin da ein wenig verwirrt und blicke nicht ganz durch. Wie gesagt bin Anfänger. Aber zum Verständnis, die Klasse "Article" soll alle Information zur Datenbank enthalten. Also Spalten und somit Variablen, in denen die Informationen gespeichert werden und diese Klasse wird zur Konstruktion eines "Objektes" genutzt, welche diese Eigenschaften und Variablen der Funktion store zur Verfügung stellt. Soweit richtig verstanden?

Somit wird die Funktion "store" sicher über die main - Funktion aufgerufen und die main Funktion übergibt dann die Datenbank Verbindung (session), die Barcodeinformation (barcode) und die neue Menge (amount) an die Funktion store beim aufrufen dieser Funktion. und über das finally wird dann die rechnerei der Neuen Menge in die Datenbank übertragen. Soweit richtig?

Also verschwindet der ganze SQL Syntax komplett aus dem Quellcode. Auch richtig?

Wenn man so Schritt für Schritt weiter geht, wäre echt Top. Da lerne ich auch was und verstehe was ich mache. Den das möchte ich schon. Den man kann zwar viel durch zusammenklöppeln aus Code-Schnippseln im Netz erreichen, nur verstehen tut mans nicht und man erreicht meistens nur mit Glück das was man möchte.



Michael
Hobby-Programmierung - Also KEIN Profi-Progger
Antworten