SQLAlchemy - Portale Internetowe

Transkrypt

SQLAlchemy - Portale Internetowe
SQLAlchemy - Portale Internetowe
Kamila Polewczyk
7 listopada 2006
1
Wprowadzenie
SQLAlchemy to ORM (mapper obiektowo-relacyjny) baz danych dla pythona.
Dzięki niemu mozna przesłonić bazę danych i do jej obsługi nie korzystać z samego SQLa, a ze specjalnych procedur. Bez znajomości języka SQL możemy w
skryptach dokonywać zapytań na tabelach, tworzyć tablele i modyfikować dane
w nich zamieszczone. Kolejną zaletą takiego mappera jest to, że możemy zmapowac tabele bazy danych na obiekty klas, a następnie wprowadzać zmiany w
bazie operując na tych właśnie obiektach.
Wersja SQLAlchemy 0.2.8 wspiera następujące bazy danych pakietami podanymi obok:
• Postgres - psycopg2
• SQLite - pysqlite
• MySQL - MySQLDB
• Oracle - cx Oracle
• MS-SQL - adodbapi pymssql
• Firebird - kinterbasdb
Obecnie z tego mappera korzysta wiele frameworków, m.in
• Django
• TurboGears
• Zope
Szczegółowy tutorial na temat SQLAlchemy można znaleźć pod adresem:
http://www.sqlalchemy.org/docs/tutorial.myt
2
Instalacja
Ze strony http://www.sqlalchemy.org/download.myt sciągamy jedną z wersji
SQLAlchemy (treść tego dokumentu testowana byla na SQLAlchemy-0.2.8),
rozpakowujemy archiwum, a następnie, będąc w odpowiednim katalogu, instalujemy wykonując polecenie: python setup.py install. Teraz mozemy używać
pakietów alchemii pisząc skrypty w pythonie. Trzeba pamiętać o zaimportowaniu alchemii: from sqlalchemy import *.
1
3
Połączenie z bazą i tworzenie tabel
Utwórzmy sobie przykładową bazę danych (MySQL: create database przyklad)
Na początek należy połączyć się z naszą bazą za pomocą create engine1 , a następnie przypisać ją do obiektu MetaData. Różnica w działaniu na odmiennych
bazach danych ogranicza się jedynie do linii definiowania połączenia z bazą gdzie
podajemy rodzaj silnika, nazwę użytkownika, jego hasło oraz nazwę konkretnej
bazy, której będziemy używać. Równie dobrze może być to
create engine(’mysql://root:@localhost/przyklad’) czy
create engine(’postgres://root:@localhost/przyklad’)
Reszta poleceń wygląda tak samo dla każdej bazy.
Ustawiając opcje echo dla silnika zobaczymy kod SQL generowany przez alchemię, co pozwoli nam upewnić się, że działa ona poprawnie (typ tabel musimy
ustawic na InnoDB, w przeciwnym razie MySQL nie obsłuży kluczy obcych).
1
2
3
4
5
6
7
8
9
10
11
from sqlalchemy import *
#driver://login:haslo@host:port/nazwa bazy
db = create engine(’mysql://root:@localhost/przyklad’)
db.execute("SET @@storage engine = ’InnoDB’", )
# przypisanie tabel do MetaData
metadata = BoundMetaData(db)
# ustawiamy opcję echo na True żeby widzieć generowany kod SQL
metadata.engine.echo = True
Teraz można już operować na tabelach. W kolejnych liniach definiujemy dwie
tabele (users i emails) i tworzymy je za pomocą metody create()2 .
12
13
14
15
16
17
18
19
20
21
22
23
24
25
users = Table(’users’, metadata,
Column(’user id’, Integer, primary key=True),
Column(’user name’, String(40)),
Column(’password’, String(10)),
Column(’age’, Integer))
users.create()
emails = Table(’emails’, metadata,
Column(’email id’, Integer, primary key=True),
Column(’address’, String),
Column(’user id’, Integer, ForeignKey(’users.user id’)))
emails.create()
Aby używać tabel w programie definiujemy obiekty users i emails, ktore bedą
reprezentowały nasze tabelki.
Definiujemy i, które bedzie odpowiadac operacji insert3 . Następnie na i wykonujemy wstawianie. Możemy wstawiać dane pojedynczymi wierszami, jak i
podać całą tablicę rekordów.
1 http://www.sqlalchemy.org/docs/tutorial.myt#tutorial
gettingstarted connecting
schemasql table creating
3 http://www.sqlalchemy.org/docs/tutorial.myt#tutorial schemasql inserting
2 http://www.sqlalchemy.org/docs/tutorial.myt#tutorial
2
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
4
users = Table(’users’, metadata, autoload=True)
emails = Table(’emails’, metadata, autoload=True)
i = users.insert()
i.execute(user name
i.execute(user name
user name =
user name =
user name =
= ’kasia’, password
= ’zosia’, password
’rysia’, password =
’misia’, password =
’misia’, password =
= ’kasiap’, age = 12)
= ’zosiap’,age = 34,
’rysiap’,age = 38,
’misiap’,age = 22,
’misiap’,age = 22)
i = emails.insert()
i.execute(address = ’[email protected]’, user id
address = ’[email protected]’, user id =
address = ’[email protected]’, user id =
address = ’[email protected]’, user id =
= 1,
2,
2,
4)
Zapytania
SQLAlchemy umożliwia nam wykonywanie różnego rodzaju zapytań na bazie
danych. Sluży do tego obiekt select4 .
Definiujemy s jako select dla tabeli users, wykonujemy na nim zapytanie
(execute), a następnie wybieramy z wyników wiersze.
Aby pobrać jeden wiersz z tabeli możemy użyc metody fetchone()
41
42
43
44
45
46
s = users.select()
rs = s.execute()
row = rs.fetchone()
print ’id:’, row[0]
print ’name:’, row[’user name’]
print ’password:’, row[’password’]
Żeby pobrać wszystkie wiersze użyjemy fetchall(), a następnie przeglądamy
je w pętli.
47
48
49
50
51
s = users.select()
rs = s.execute()
a = rs.fetchall()
for row in a:
print row.user name + ’ - ’ + row.password;
Dla ułatwienia dalszej pracy wprowadzamy sobie funkcje run(), która będzie
wypisywała wiersze zwrócone na podstawie zapytania podanego w argumencie.
52 def run(stmt):
53
rs = stmt.execute()
54
for row in rs:
55
print row
Wybór wierszy możemy ograniczyć różnymi kryteriami podając warunki w metodzie select(). Do kolumny odwołujemy się w sposób obiekt tabeli.c.nazwa kolumny
4 http://www.sqlalchemy.org/docs/tutorial.myt#tutorial
3
schemasql selecting
56
57
58
59
60
s = users.select(users.c.user name == ’misia’)
run(s)
s = users.select(users.c.user id < 4)
run(s)
Dostępne są również funkcje and , or i not oraz odpowiadające im &, | i ∼
61 s = users.select(and (users.c.user id < 4, users.c.user name !=
’kasia’))
62 run(s)
63
64 s = users.select(or (users.c.user id < 4, users.c.user name !=
’misia’))
65 run(s)
66
67 s = users.select(not (users.c.user name == ’zosia’))
68 run(s)
69
70 s = users.select((users.c.user id < 4) & (users.c.user name !=
’zosia’))
71 run(s)
72
73 s = users.select((users.c.user id < 4) | (users.c.user name !=
’misia’))
74 run(s)
75
76 s = users.select(∼(users.c.user name == ’rysia’))
77 run(s)
Do dyspozycji mamy również funkcje takie jak like, startswith, endswith.
Należy pamietać, że oznacza jeden dowolny znak, % to dowolny ciąg znaków.
78
79
80
81
82
83
84
85
s = users.select(users.c.user name.startswith(’m’))
run(s)
s = users.select(users.c.user name.like(’%o%’))
run(s)
s = users.select(users.c.user name.endswith(’a’))
run(s)
Także between i in , która ze względu na konflikt ze składnią pythona nie może
nazywac się in.
86
87
88
89
90
s = users.select(users.c.password.between(’m’,’z’))
run(s)
s = users.select(users.c.user name.in (’zosia’, ’kasia’))
run(s)
Aby wywołać funkcje SQLowe (np. substr, count, max, min używamy func
w sposób następujący
4
91
92
93
94
95
96
97
98
s = users.select(func.substr(users.c.user name, 2, 1) == ’o’)
run(s)
s = select([func.count(users.c.user id)])
run(s)
s = select([func.count("*")], from obj=[users])
run(s)
Do sortowania możemy użyc metody order by i uporzadkować elementy np. po
kolumnie name
99 s = users.select(order by=[users.c.name])
100 run(s)
Sortowanie na wielu kolumnach malejąco względem user id (desc) i rosnąco
względem user name (asc)
101 s = users.select(users.c.user name>’m’, order by=[desc(users.c.user id),
asc(users.c.user name)])
102 run(s)
Pobieranie danych z określonych pól
103 s = select([users.c.user id, users.c.user name], users.c.user name
!= ’kasia’)
104 run(s)
5
Łączenie tabel
Pełen join5 , pozbawiony warunków zwraca za dużo wyników. Aby otrzymać
poprawne wyświetlenie należy odpowiednio połączyć tabele wykorzystując odpowiednie krytria.
105
106
107
108
109
110
111
112
113
s = select([users, emails])
run(s)
s = select([users, emails], emails.c.user id == users.c.user id)
run(s)
s = select([users.c.name, emails.c.address],
emails.c.user id == users.c.user id)
run(s)
Do dyspozycji mamy obiekt join, który sam określa pole łączenia po kluczu
obcym.
114 s = join(users, emails).select()
115 run(s)
5 http://www.sqlalchemy.org/docs/tutorial.myt#tutorial
5
schemasql table relationships
Przykładowo gdy któryś z ’userów’ nie posiada adresu email używamy outerjoin,
by wyświetlić wszystkich ’userów’. Domyślnie łączenie jest ’left outerjoin’ tzn.
rekordy z tabeli po lewej stronie oraz odpowiadające im wpisy w tabeli po prawej.
116
117
118
119
120
6
s = outerjoin(users, emails).select()
run(s)
s = outerjoin(emails, users).select()
run(s)
Porządkowanie
Podobnie jak w czystym SQLu możemy stosować funkcje sortujące takie jak
order by, która porządkuje dane w kolejności asc - malejącej, desc - rosnącej.
Opcja distinct użyta przy select umożliwia wybranie niepowtarzających się
wierszy. Natomiast parametry offset i limit zawężaja obszar zwracanych wyników odpowiednio przez przesunięcie o ileś pozycji oraz ograniczenie ilości kolejnych rekordów.
121 s = users.select(order by=[users.c.user name])
122 run(s)
123
124 s= users.select(users.c.user name>’m’, order by=[desc(users.c.user id),
asc(users.c.user name)])
125 run(s)
126
127 s = select([users.c.user name], distinct=True)
128 run(s)
129
130 s = users.select(offset=2, limit=2)
131 run(s)
7
Mapowanie
Tabele bazy danych możemy ’zmapować’6 na klasy utworzone w pythonie. Dzięki
temu możemy tworzyć obiekty odpowiadające konkretnym rekordom w tabeli.
Utworzmy klasę odpowiadającą tabeli users:
132 class User(object):
133
def init (self, name, password,age):
134
self.user name = name
135
self.password = password
136
self.age = age
137
def wypisz(self):
138
print self.user name
139
print self.password
140
print self.age
6 http://www.sqlalchemy.org/docs/datamapping.myt
6
Nastepnie wykonujemy mapowanie. Funkcja mapper przyjmuje dwa parametry
- klasę do mapowania i obiekt tabeli. W ten sposób uzyskamy dostęp do tabeli
w bazie poprzez obiekt klasy User.
141 usermapper = mapper(User, users)
Aby dokonywać zmian w tabelach potrzebny jest obiekt session:
142 session = create session()
Teraz mozemy utworzyc nowy obiekt klasy User z odpowiednio wczytanymi
z bazy danymi. Na takim obiekcie możemy operować jak na każdym innym.
Przykladowo możemy wczytac dane ’rysi’ i zmienic jej wiek.
143
144
145
146
147
q = session.query(User)
rys = q.get by(users.c.user name==’rysia’)
rys.age += 10
rys.wypisz()
session.flush()
Należy pamiętać o metodzie flush(), która powoduje, że nowe dane zostaną zatwierdzone w bazie.
Podobnie możemy wpisać do bazy zupelnie nowe obiekty korzystając z konstruktora.
148
149
150
151
152
u2 = User("ala","alap",12)
session.save(u2)
session.save(User("misia","misiap",34))
session.flush()
Metoda save() powiąże nam obiekt gosia z bazą danych, następnie flush() dokona
zmian.
Podobnie możemy usunąć wybrany obiekt z bazy:
153
154
155
156
mis = q.get by(users.c.user name==’misia’)
session.delete(mis)
mis.wypisz()
session.flush()
Możliwe jest zmapowanie kilku tabel na jeden obiekt. W tym celu wykorzystamy obiekt join albo outerjoin. ORM połączy nam tabele po kluczu obcym.
Następnie mapujemy obiekt join na specjalnie utworzoną, pustą klasę UE.
157
158
159
160
161
162
163
164
165
166
167
168
169
170
class UE(object):
pass
def wypisz(self):
print self.user name
print self.password
print self.age
print self.address
j=outerjoin(users,emails)
uemap = mapper(UE,j)
q=session.query(UE)
mis=q.selectfirst(users.c.user name==’misia’)
mis.wypisz()
7
8
Transakcje
W przypadku kiedy dokonujemy bardziej złożonych zmian w bazie i potrzebna
nam możliwość wycofania operacji, która przykładowo nie powiodła się tak jak
byśmy oczekiwali, mamy do dyspozycji obiekt transakcji7 .
Transakcję tworzymy w obrębie sesji. Rozpoczynamy ją
transaction = session.create transaction(). Gdy ustawimy
transaction.autoflush=False metoda flush() nie będzie automatycznie wprowadzała zmian do bazy, a zaczeka z tym do momentu gdy transakcja zakończy
się. W tym wypadku mogą zajść dwie sytuacje: transaction.commit() zatwierdzi wszystkie zmiany w obrębie transakcji, transaction.rollback() wycofa to
co próbowaliśmy zatwierdzić za pomocą flush()
171
172
173
174
175
176
177
178
transaction = session.create transaction()
transaction.autoflush=False
kas.age += 10
session.flush()
mis.age += 10
transaction.rollback()
session.flush()
Zmieniliśmy wiek mis, wiek kas pozostal bez zmian.
179
180
181
182
kas.age += 10
session.flush()
mis.age += 10
transaction.commit()
Wszystkie zmiany zostaly zatwierdzone a transakcja zakończona
9
Podsumowanie
Nie widzę powodów, przemawiających za tym by nie korzystać z SQLAlchemy
przy pracy z bazami danych, które wspiera. Jak wiadomo implementacja każdej relacyjnej bazy danych rózni się w jakimś stopniu od pozostałych. Mapper
zapobiega występowaniu błędów i różnic związanych ze zmianami w składni kodu SQL ujednolicając obsługę do jednego zestawu metod, co jest podstawową
zaletą tego systemu.
7 http://www.sqlalchemy.org/docs/tutorial.myt#tutorial
8
orm transactions

Podobne dokumenty