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 engine 1, 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 from sqlalchemy import * 2 3 #driver://login:haslo@host:port/nazwa bazy 4 db = create engine( mysql://root:@localhost/przyklad ) 5 db.execute("set @@storage engine = InnoDB ", ) 6 # przypisanie tabel do MetaData 7 metadata = BoundMetaData(db) 8 9 # ustawiamy opcję echo na True żeby widzieć generowany kod SQL 10 metadata.engine.echo = True 11 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 users = Table( users, metadata, 13 Column( user id, Integer, primary key=true), 14 Column( user name, String(40)), 15 Column( password, String(10)), 16 Column( age, Integer)) 17 18 users.create() 19 20 emails = Table( emails, metadata, 21 Column( email id, Integer, primary key=true), 22 Column( address, String), 23 Column( user id, Integer, ForeignKey( users.user id ))) 24 25 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 insert 3. 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 2 http://www.sqlalchemy.org/docs/tutorial.myt#tutorial schemasql table creating 3 http://www.sqlalchemy.org/docs/tutorial.myt#tutorial schemasql inserting 2
26 users = Table( users, metadata, autoload=true) 27 emails = Table( emails, metadata, autoload=true) 28 29 i = users.insert() 30 i.execute(user name = kasia, password = kasiap, age = 12) 31 i.execute(user name = zosia, password = zosiap,age = 34, 32 user name = rysia, password = rysiap,age = 38, 33 user name = misia, password = misiap,age = 22, 34 user name = misia, password = misiap,age = 22) 35 36 i = emails.insert() 37 i.execute(address = kasia@xxx.com, user id = 1, 38 address = zosia@xxx.com, user id = 2, 39 address = zosia@xxx.net, user id = 2, 40 address = misia@xxx.com, user id = 4) 4 Zapytania SQLAlchemy umożliwia nam wykonywanie różnego rodzaju zapytań na bazie danych. Sluży do tego obiekt select 4. 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 s = users.select() 42 rs = s.execute() 43 row = rs.fetchone() 44 print id:, row[0] 45 print name:, row[ user name ] 46 print password:, row[ password ] Żeby pobrać wszystkie wiersze użyjemy fetchall(), a następnie przeglądamy je w pętli. 47 s = users.select() 48 rs = s.execute() 49 a = rs.fetchall() 50 for row in a: 51 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 schemasql selecting 3
56 s = users.select(users.c.user name == misia ) 57 run(s) 58 59 s = users.select(users.c.user id < 4) 60 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 s = users.select(users.c.user name.startswith( m )) 79 run(s) 80 81 s = users.select(users.c.user name.like( %o% )) 82 run(s) 83 84 s = users.select(users.c.user name.endswith( a )) 85 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 s = users.select(users.c.password.between( m, z )) 87 run(s) 88 89 s = users.select(users.c.user name.in ( zosia, kasia )) 90 run(s) Aby wywołać funkcje SQLowe (np. substr, count, max, min używamy func w sposób następujący 4
91 s = users.select(func.substr(users.c.user name, 2, 1) == o ) 92 run(s) 93 94 s = select([func.count(users.c.user id)]) 95 run(s) 96 97 s = select([func.count("*")], from obj=[users]) 98 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 join 5, 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 s = select([users, emails]) 106 run(s) 107 108 s = select([users, emails], emails.c.user id == users.c.user id) 109 run(s) 110 111 s = select([users.c.name, emails.c.address], 112 emails.c.user id == users.c.user id) 113 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 schemasql table relationships 5
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 s = outerjoin(users, emails).select() 117 run(s) 118 119 s = outerjoin(emails, users).select() 120 run(s) 6 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 q = session.query(user) 144 rys = q.get by(users.c.user name== rysia ) 145 rys.age += 10 146 rys.wypisz() 147 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 u2 = User("ala","alap",12) 149 session.save(u2) 150 151 session.save(user("misia","misiap",34)) 152 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 mis = q.get by(users.c.user name== misia ) 154 session.delete(mis) 155 mis.wypisz() 156 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 class UE(object): 158 pass 159 def wypisz(self): 160 print self.user name 161 print self.password 162 print self.age 163 print self.address 164 165 j=outerjoin(users,emails) 166 167 uemap = mapper(ue,j) 168 q=session.query(ue) 169 mis=q.selectfirst(users.c.user name== misia ) 170 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 transakcji 7. 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 transaction = session.create transaction() 172 transaction.autoflush=false 173 174 kas.age += 10 175 session.flush() 176 mis.age += 10 177 transaction.rollback() 178 session.flush() Zmieniliśmy wiek mis, wiek kas pozostal bez zmian. 179 kas.age += 10 180 session.flush() 181 mis.age += 10 182 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 orm transactions 8