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