Objektfragen: Class und Scope von MySQL-Datenbank

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Benutzeravatar
kajarno
User
Beiträge: 26
Registriert: Samstag 16. Januar 2010, 12:41
Wohnort: München
Kontaktdaten:

Gegeben:
- Eine Datenbank in MySQL, "kajtajm"
- Python-Scripts ohne "class", die mit MySQLdb die Datenbank lesen und schreiben
- Umgebung: Mac command line interface, .py -Dateien

Bedarf:
- Funktionierende, am liebsten auch schöne Class-Schnittstelle zur gleichen Datenbank
- das Lernen der richtigen Objektprogrammierung mit Python (vielleicht wäre Django gut, aber ich möchte erstmal die Grundsteine selbst verstehen)

Diagnose:
- die unten definierte Datei ktclasses.py startet in Mac die X-Umgebung und gibt nach zweimal Verlassen von X diese Fehlermeldung
kaj@Birger[kajtajm]$ ktclasses.py
X connection to /tmp/launch-8TKCAP/:0 broken (explicit kill or server shutdown).
X connection to /tmp/launch-8TKCAP/:0 broken (explicit kill or server shutdown).
./ktclasses.py: line 6: class: command not found
./ktclasses.py: line 7: syntax error near unexpected token `('
./ktclasses.py: line 7: ` db = MySQLdb.connect(db="kajtajm",read_default_file="~/.my.cnf")'
Wenn ich dahingegen genau die gleiche Befehle an der Python-Commandzeile angebe, funktioniert es wie gewünscht:
kaj@Birger[kajtajm]$ python
Python 2.5.1 (r251:54863, Feb 6 2009, 19:02:12)
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> # Classes for kajtajm
...
>>> import sys
>>> import MySQLdb
>>>
>>> class KtDbRow:
... db = MySQLdb.connect(db="kajtajm",read_default_file="~/.my.cnf")
... # myKtRow = KtRow("2010-01-01",1000,1015,"Python","Some long text")
...blabla die ganzen Zeilen...
>>> myKtDbRow = KtDbRow("2010-01-25",1100,1015,"Python","Some long text")
>>> print myKtDbRow.val()
2010-01-25 1100-1015 Python -0.75 (Some) Some long text
>>> print myKtDbRow.shortval()
1100-1015 Python -0.75 (Some) Some long text
>>> print myKtDbRow.wherestring()
WHERE kajdate="2010-01-25" AND timefrom=1100
>>> print myKtDbRow.insertstring()
INSERT INTO kajtimerow (kajdate, timefrom, timeto, project, kajtext, hours, keyword) VALUES ("2010-01-25",1100,1015,"Python","Some long text",-0.75,"Some")
>>> print myKtDbRow.updatestring()
UPDATE kajtimerow SET timeto=1015, project="Python", kajtext="Some long text", hours=-0.75, keyword="Some" WHERE kajdate="2010-01-25" AND timefrom=1100
>>> print myKtDbRow.deletestring()
DELETE from kajtimerow WHERE kajdate="2010-01-25" AND timefrom=1100
>>> print myKtDbRow.exists()
(datetime.date(2010, 1, 25), 1100, 1115, 'kajtajm', 'ktrpt F\xc3\xb6rb\xc3\xa4ttringar (urval, rubriker', Decimal("0.25"), 'ktrpt')
False
>>>
Probleme

1. Wie vermeide ich es, dass X aus einem mir unerklärlichen Grund gestartet wird? (Die Scripts ohne "class" starten kein X sondern geben ihr Output am Command Line)

2. Wo definiere ich Scope-mäßig (Gültigkeitsbereich) die Verbindung zur Datenbank so, dass ich ein "Thread Pool" (oder ähnliches) bekomme? Ich möchte ja nur ein Cursor neu starten und schließen, ungern aber eine ganze Verbindung zur Datenbank.

Inhalt ktclasses.py:

Code: Alles auswählen

# Classes for kajtajm

import sys
import MySQLdb

class KtDbRow:
    db = MySQLdb.connect(db="kajtajm",read_default_file="~/.my.cnf")
    # Wann wird je die obige Zeile ausgeführt?
    # myKtRow = KtRow("2010-01-01",1000,1015,"Python","Some long text")
    def __init__(self, kajdate, timefrom, timeto, project, kajtext):
        self.kajdate = str(kajdate) # in MySQL format i.e. 2001-01-01
        self.timefrom = int(timefrom)
        self.timeto = int(timeto)
        self.project = project # 10 chars
        self.kajtext = kajtext # 50 chars
        self.hours = (60*(timeto/100-timefrom/100) + timeto%100 - timefrom%100)/60.0
        self.keyword = kajtext.split()[0][0:10] # First 10 chars of first word in kajtext
    def info(self):
        return (self)
    #
    # Contents in user readable string form
    def shortval(self):
        return str(self.timefrom) + "-" + str(self.timeto) + " " + self.project \
            + " " + str(self.hours) + " (" + self.keyword + ") " + self.kajtext
    def val(self):
        return self.kajdate + " " + self.shortval()
    #
    # SQL string representations
    def wherestring(self):
        return 'WHERE kajdate="' + self.kajdate + '" AND timefrom=' + str(self.timefrom)
    def insertstring(self):
        return "INSERT INTO kajtimerow (kajdate, timefrom, timeto, project, " + \
            'kajtext, hours, keyword) VALUES ("'  + self.kajdate + '",' + str(self.timefrom) + \
            ',' + str(self.timeto) + ',"' + self.project + '","' + self.kajtext + '",' + \
            str(self.hours) + ',"' + self.keyword + '")'
    def updatestring(self):
        return "UPDATE kajtimerow SET timeto=" + str(self.timeto)+ ', project="' \
            + self.project + '", kajtext="' + self.kajtext + '", hours=' + \
            str(self.hours) + ', keyword="' + self.keyword + '" ' + self.wherestring()
    def deletestring(self):
        return "DELETE from kajtimerow " + self.wherestring()
    #
    # Database communication
    def opendb(self):
      # Check the MySQLdb module is available, if not error and exit
      try:
        import MySQLdb
      except ImportError:
        print "\nktopendb.py error: MySQLdb module required for interactive usage\n"
        sys.exit(0)
      # Open the connection to the database
      try:
        return MySQLdb.connect(db="kajtajm",read_default_file="~/.my.cnf")
      # If there is an exception, print the error and exit
      except MySQLdb.Error, e:
        print "ktopendb.py: Couldn't open kajtajm database"
        print "Error %d: %s" % (e.args[0], e.args[1])
        sys.exit (1)
    def exists(self):
        db = self.opendb()
        c = db.cursor()
        c.execute("SELECT * FROM kajtajmrow " + self.wherestring())
        row = c.fetchone()
        print row
        return row[0]==1
    def insert(self):
        return true
    def update(self):
        return true
    def delete(self):
        return true

myKtDbRow = KtDbRow("2010-01-25",1100,1015,"Python","Some long text")
print myKtDbRow.val()
print myKtDbRow.shortval()
print myKtDbRow.wherestring()
print myKtDbRow.insertstring()
print myKtDbRow.updatestring()
print myKtDbRow.deletestring()
print myKtDbRow.exists()
Kaj Arnö, Sun VP MySQL Community Relations
Vormals (1979-99) Programmierer, möchte 2010 alte Künste wiederbeleben, und zwar mit PyObjC und MySQLdb unter Mac. Aus Finnland, Muttersprache Schwedisch. Bevorzugt Deutsch über Englisch. In München seit 2006.
lunar

Muss man denn unter Mac OS X keinen Shebang angeben?

Was denn Quelltext angeht:
- Prepared Statements nutzen statt SQL-Ausdrücke mit Zeichenkettenverkettung zusammenzusetzen
- Eine Datenbankverbindung auf Klassenebene ist eine schlechte Idee. Zum einen lässt sich die Klasse dann nur aus einem Thread heraus nutzen, zum anderen kann die Datenbank während der Lebenszeit der Klasse nicht geschlossen werden.
- sys.exit(1) in einer Klasse ist nicht sehr nett gegenüber dem Benutzer dieser Klasse :)

Davon abgesehen: ORMs gibt es bereits in großer Anzahl und vielen Varianten. Zur Übung ist nichts gegen einen eigenen Versuch zu sagen, in produktiver Umgebung aber ist man mit SQLAlchemy, Storm u.ä. eigentlich besser bedient.
Benutzeravatar
kajarno
User
Beiträge: 26
Registriert: Samstag 16. Januar 2010, 12:41
Wohnort: München
Kontaktdaten:

lunar hat geschrieben:Muss man denn unter Mac OS X keinen Shebang angeben?
Natürlich muss man. Und dies war auch der Fehler, wieso X gelaunched wurde. Vielen Dank lunar! Dass der Fehler so blöd war ... :)
lunar hat geschrieben:Was denn Quelltext angeht:
- Prepared Statements nutzen statt SQL-Ausdrücke mit Zeichenkettenverkettung zusammenzusetzen
Verstehe ich. Dein Vorschlag erscheint mir sinnvoll aus Effizienzgründen. Jedoch werde ich wohl nicht allzu viele Probleme mit Antwortzeiten haben, da wenig Transaktionen (und kein UI-Overhead).
lunar hat geschrieben:- Eine Datenbankverbindung auf Klassenebene ist eine schlechte Idee. Zum einen lässt sich die Klasse dann nur aus einem Thread heraus nutzen, zum anderen kann die Datenbank während der Lebenszeit der Klasse nicht geschlossen werden.
Das glaube ich. Dies war aber auch meine Hauptfrage von Anfang an: Wo soll die Datenbankverbindung denn definiert sein, so dass die Klasse es ausnutzen kann? Ich hätte ja so gerne eine Methode KtDbRow.Update. Muss die db-Verbindung als Parameter bei __init__ von einer KtDbRow mitgegeben werden?
lunar hat geschrieben:- sys.exit(1) in einer Klasse ist nicht sehr nett gegenüber dem Benutzer dieser Klasse :)
Neulingsfehler! :)
Nicht desto trotz: Wie sich die Klasse benehmen soll wenn es keine Db-Verbindung gibt, weiß ich auch nicht.
lunar hat geschrieben: Davon abgesehen: ORMs gibt es bereits in großer Anzahl und vielen Varianten. Zur Übung ist nichts gegen einen eigenen Versuch zu sagen, in produktiver Umgebung aber ist man mit SQLAlchemy, Storm u.ä. eigentlich besser bedient.
Das bestreite ich nicht. Dies hier ist eine Kombination von Wiederbelebung alter Programmierkünste vom vergangenen Jahrhundert und von einer super-kleinen Produktivumgebung im eigenen Rechner (localhost).

Nochmals: Danke Lunar für die einleuchtenden Antworten!
Kaj Arnö, Sun VP MySQL Community Relations
Vormals (1979-99) Programmierer, möchte 2010 alte Künste wiederbeleben, und zwar mit PyObjC und MySQLdb unter Mac. Aus Finnland, Muttersprache Schwedisch. Bevorzugt Deutsch über Englisch. In München seit 2006.
lunar

Der Vorschlag, Prepared Statements zu nutzen, hatte weniger mit Effizienz zu tun, als vielmehr mit Sicherheit und Robustheit. Ohne Prepared Statements musst Du die Eingabeparameter validieren und maskieren, um SQL-Injections und Syntaxfehlern (falls SQL-Sonderzeichen oder Schlüsselwörter in den Parametern enthalten sind) vorzubeugen. Prepared Statements nehmen Dir diese Arbeit ab. Zudem sehen sie im Quelltext übersichtlicher aus als Zeichenketten-Konkatenation.

Für die Verwaltung der Datenbankverbindung würde ich bei diesem einfachen Beispiel das Kontextmanager-Protokoll implementieren. In __enter__ wird die Verbindung geöffnet, in __exit__ geschlossen. Innerhalb des Blocks kann man dann Datenbankoperationen ausführen.

Falls beim Öffnen Fehler auftreten, so würde ich sie gar nicht behandeln, denn im Allgemeinen gilt, dass man Ausnahmen nach oben weiterreichen sollte, wenn man sie an einer Stelle nicht sinnvoll behandeln kann. Ausnahmen sind als Fehlerindikator stark genug, da sie die Ausführung an der aktuellen Stelle abbrechen. sys.exit() dagegen ist zu stark, denn es bricht den Prozess ab (streng genommen kann man sys.exit() auch abfangen, aber das ist nicht Sinn der Übung).
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Du solltest dir auch klar machen was du genau repräsentieren willst. Schaue dir mal folgende Fragen und versuche sie zu beantworten ohne auf deinen Code zu schauen oder an ihn zu Denken.

Ist die Zahl von Spalten in einer Zeile veränderbar oder nicht? Sind die Elemente in einer Zeile sortiert oder haben sie eine Ordnung und welchen Einfluss hat dies?

Was kann man mit einer Zeile machen? Kann man sie verändern und wenn ja wie?

Welche Tätigkeiten stehen im Zusammenhang mit einer Zeile in der Datenbank? In welchen davon agiert die Zeile selbst, in welchen agieren andere mit der Zeile und wie?

Ist die Bezeichnung Zeile und/oder seine Repräsentation in der Datenbank wichtig/relevant für die API bzw. lässt sich dieser Umstand akzeptabel abstrahieren?

Sind die Fragen beantwortet betrachte deinen Code, unter Berücksichtungen der Fragen bzw. den Antworten zu diesen. Entspricht deine Implementation deiner eigenen Vorstellung?

Wenn dir klar ist was eine Zeile ist, was sie macht und womit, sollte der Code kein Problem mehr sein.
Benutzeravatar
kajarno
User
Beiträge: 26
Registriert: Samstag 16. Januar 2010, 12:41
Wohnort: München
Kontaktdaten:

Danke Lunar!
lunar hat geschrieben:Der Vorschlag, Prepared Statements zu nutzen, hatte weniger mit Effizienz zu tun, als vielmehr mit Sicherheit und Robustheit. Ohne Prepared Statements musst Du die Eingabeparameter validieren und maskieren, um SQL-Injections und Syntaxfehlern (falls SQL-Sonderzeichen oder Schlüsselwörter in den Parametern enthalten sind) vorzubeugen. Prepared Statements nehmen Dir diese Arbeit ab. Zudem sehen sie im Quelltext übersichtlicher aus als Zeichenketten-Konkatenation.
OK! Meine Anwendung ist, sehe und staune, eine nicht-Web-Applikation. Nur von meinem eigenen Command Line zu betätigen.
http://blogs.mysql.com/kaj/2010/01/15/d ... ith-mysql/
http://blogs.mysql.com/kaj/2010/01/16/r ... nd-pyobjc/
http://blogs.mysql.com/kaj/2010/01/17/k ... b-project/
Streng genommen sagen diese drei Blogeinträge nur, dass ich eine Single-User-Anwendung erstellen möchte, wo der Benutzer ich selbst bin. Ursprünglich wollte ich eine Cocoa-GUI, aber mittlerweile ist es mir aufgefallen, dass ich es schneller von der Command Line (und mit sequentiellen Dateien) mache.
lunar hat geschrieben:Für die Verwaltung der Datenbankverbindung würde ich bei diesem einfachen Beispiel das Kontextmanager-Protokoll implementieren. In __enter__ wird die Verbindung geöffnet, in __exit__ geschlossen. Innerhalb des Blocks kann man dann Datenbankoperationen ausführen.
Werde ich probieren!
lunar hat geschrieben:Falls beim Öffnen Fehler auftreten, so würde ich sie gar nicht behandeln, denn im Allgemeinen gilt, dass man Ausnahmen nach oben weiterreichen sollte, wenn man sie an einer Stelle nicht sinnvoll behandeln kann. Ausnahmen sind als Fehlerindikator stark genug, da sie die Ausführung an der aktuellen Stelle abbrechen. sys.exit() dagegen ist zu stark, denn es bricht den Prozess ab (streng genommen kann man sys.exit() auch abfangen, aber das ist nicht Sinn der Übung).
OK, erscheint mir sinnvoll!
Kaj Arnö, Sun VP MySQL Community Relations
Vormals (1979-99) Programmierer, möchte 2010 alte Künste wiederbeleben, und zwar mit PyObjC und MySQLdb unter Mac. Aus Finnland, Muttersprache Schwedisch. Bevorzugt Deutsch über Englisch. In München seit 2006.
Benutzeravatar
kajarno
User
Beiträge: 26
Registriert: Samstag 16. Januar 2010, 12:41
Wohnort: München
Kontaktdaten:

Danke DasIch! Diese sind gute Fragen. Ich glaube sie auch beantworten zu können. Meine Fragen lagen im Bereich Syntax- und Scopebeherrschung von Python. Dies glaube ich dank Lunar mittlerweile besser verstanden zu haben. Jetzt kommt also wieder der Spaßfaktor, und zwar das Konzentrieren auf Deine unten genannten Fragen. Ich bin ja beim Wiederentdecken vom Spaß des Programmierens, nach über zehn Jahren Pause.
DasIch hat geschrieben:Du solltest dir auch klar machen was du genau repräsentieren willst. Schaue dir mal folgende Fragen und versuche sie zu beantworten ohne auf deinen Code zu schauen oder an ihn zu Denken.

Ist die Zahl von Spalten in einer Zeile veränderbar oder nicht? Sind die Elemente in einer Zeile sortiert oder haben sie eine Ordnung und welchen Einfluss hat dies?

Was kann man mit einer Zeile machen? Kann man sie verändern und wenn ja wie?

Welche Tätigkeiten stehen im Zusammenhang mit einer Zeile in der Datenbank? In welchen davon agiert die Zeile selbst, in welchen agieren andere mit der Zeile und wie?

Ist die Bezeichnung Zeile und/oder seine Repräsentation in der Datenbank wichtig/relevant für die API bzw. lässt sich dieser Umstand akzeptabel abstrahieren?

Sind die Fragen beantwortet betrachte deinen Code, unter Berücksichtungen der Fragen bzw. den Antworten zu diesen. Entspricht deine Implementation deiner eigenen Vorstellung?

Wenn dir klar ist was eine Zeile ist, was sie macht und womit, sollte der Code kein Problem mehr sein.
Kaj Arnö, Sun VP MySQL Community Relations
Vormals (1979-99) Programmierer, möchte 2010 alte Künste wiederbeleben, und zwar mit PyObjC und MySQLdb unter Mac. Aus Finnland, Muttersprache Schwedisch. Bevorzugt Deutsch über Englisch. In München seit 2006.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

[wiki]Parametrisierte SQL-Queries[/wiki] :lol:

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
kajarno
User
Beiträge: 26
Registriert: Samstag 16. Januar 2010, 12:41
Wohnort: München
Kontaktdaten:

jens hat geschrieben:[wiki]Parametrisierte SQL-Queries[/wiki] :lol:
Sehr hilfreich! Eins meiner Script-Programme sieht jetzt so aus:

Code: Alles auswählen

#!/usr/bin/env python 
# -*- coding: latin-1 -*-
# 
# ktreplace: Replace one project with another in all of kajtajm

import sys 
from ktopendb import db
from datetime import date

print "ktreplace " + date.today().strftime("%a %d.%m.%Y")

paramcount = len(sys.argv) - 1

if paramcount < 2:
  print "Syntax: ktreplace <fromproject> <toproject> [do]"
  sys.exit(1)
paramfromproject = sys.argv[1]
paramtoproject = sys.argv[2]

if paramcount >= 3:
  paramdo = sys.argv[3] == "do"
  will = "will"
else:
  paramdo = False
  will = "would"

c=db.cursor()
c.execute("SELECT count(*) FROM kajtajmrow WHERE project=""%s""", (paramfromproject))
selectresult = c.fetchone()
fromcount = selectresult[0]

c.execute("SELECT count(*) FROM kajtajmrow WHERE project=""%s""", (paramtoproject))
selectresult = c.fetchone()
tocount = selectresult[0]

print "%d row(s) with project %s %s be converted to project %s, which until now has %d row(s)" % \
  (fromcount, paramfromproject, will, paramtoproject, tocount)

if paramdo:
  c=db.cursor()
  c.execute("UPDATE kajtajmrow SET project=""%s"" WHERE project=""%s""", (paramtoproject, paramfromproject))

db.commit()
# Close the connection once finished 
db.close ()
Kaj Arnö, Sun VP MySQL Community Relations
Vormals (1979-99) Programmierer, möchte 2010 alte Künste wiederbeleben, und zwar mit PyObjC und MySQLdb unter Mac. Aus Finnland, Muttersprache Schwedisch. Bevorzugt Deutsch über Englisch. In München seit 2006.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

kajarno hat geschrieben:

Code: Alles auswählen

...
c.execute("SELECT count(*) FROM kajtajmrow WHERE project=""%s""", (paramfromproject))
...
c.execute("SELECT count(*) FROM kajtajmrow WHERE project=""%s""", (paramtoproject))
...
Die Anführungszeichen sind IMHO falsch bzw. überflüssig. Probier es mal ohne:

Code: Alles auswählen

...
c.execute("SELECT count(*) FROM kajtajmrow WHERE project=%s", (paramfromproject))
...
c.execute("SELECT count(*) FROM kajtajmrow WHERE project=%s", (paramtoproject))
...
Auf langer Sicht gesehen, macht ein ORM jedoch Sinn und mehr Spass... ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten