5.2. System ORM Peewee

Używanie systemów ORM, takich jak Peewee, w prostych projektach sprowadza się do schematu, który poglądowo można opisać w trzech krokach:

  1. deklaracja modelu opisującego bazę

  2. utworzenie na podstawie modelu tabel w bazie,

  3. wykonywanie operacji CRUD.

Przez model (zob. też: model bazy danych) rozumiemy tutaj deklaracje klas i ich właściwości (atrybutów) opisujące obiekty, które będą przechowywane w bazie. Systemy ORM na podstawie klas tworzą odpowiednie tabele i pola, uwzględniając ich typy i powiązania. Odwzorowanie klas i ich właściwości na tabele, kolumny i relacje w bazie stanowi istotę mapowania relacyjno-obiektowego.

Poniżej spróbujemy pokazać, jak wykonywać typowe operacje na bazie z wykorzystaniem biblioteki Peewee.

Informacja

Wyjaśnienia podanego niżej kodu są uproszczone ze względu na przejrzystość i poglądowość instrukcji. Do używania systemów ORM wystarczające jest poznanie ich interfejsu API.

5.2.1. Środowisko pracy

Informacja

Do kodowania i uruchamiania skryptu możesz użyć dowolnych narzędzi, np. ulubionego edytora kodu i terminala. Sugerujemy jednak wykorzystanie środowiska typu PyCharm lub innego, ponieważ ułatwiają przygotowania i pracę nad projektami w języku Python.

Przed rozpoczęciem pracy przygotuj w wybranym katalogu, np. baza_orm` wirtualne środowisko Pythona i w aktywnym środowisku zainstaluj pakiet Peewee:

(.venv) ~/baza_orm$ pip install peewee

5.2.2. Klasa bazowa

W ulubionym edytorze utwórz plik o nazwie orm_pw.py z następującym kodem:

Peewee. Kod nr
 1import os
 2from peewee import SqliteDatabase, Model
 3from peewee import CharField, ForeignKeyField
 4
 5plik_bazy = 'baza_pw.db'
 6if os.path.exists(plik_bazy):
 7    os.remove(plik_bazy)
 8
 9# tworzymy instancję klasy Database do obsługi bazy
10baza = SqliteDatabase(plik_bazy)  # ':memory:'
11
12# klasa bazowa dla modeli
13class Base(Model):
14    class Meta:
15        database = baza
16

Na początku importujemy potrzebne klasy. Dalej tworzymy zmienną plik_bazy, która będzie przechowywała nazwę pliku z bazą danych. Jeżeli plik znajduje się na dysku (if os.path.exists()), usuwamy go (os.remove()), aby zapewnić bezproblemowe działanie skryptu podczas wielokrotnego uruchamiania.

Następnie tworzymy obiekt baza do obsługi bazy SQlite3 przechowywanej w pliku baza_pw.db.

Wskazówka

Jeżeli zamiast nazwy pliku, podamy argument :memory:, baza utworzona zostanie w pamięci RAM, co może być przydatne podczas testowania.

Do utworzenia modeli danych potrzebna będzie klasa bazowa, którą tworzymy w oparciu o klasę Model, w podklasie Meta dodatkowo przypisujemy obiekt służący do komunikacji z bazą do atrybutu database.

5.2.3. Model danych

Dodajemy definicje klas opisujących dwa obiekty reprezentujące klasę i ucznia. Każda klasa ma swoją nazwę i profil, każdy uczeń ma imię, nazwisko oraz przynależy do jakiejś klasy.

Peewee. Kod nr
18# klasy Klasa i Uczen opisują rekordy tabel "klasa" i "uczen" oraz relacje między nimi
19class Klasa(Base):
20    nazwa = CharField(null=False)
21    profil = CharField(default='')
22
23
24class Uczen(Base):
25    imie = CharField(null=False)
26    nazwisko = CharField(null=False)
27    klasa = ForeignKeyField(Klasa, related_name='uczniowie')
28
29
30baza.connect()  # nawiązujemy połączenie z bazą
31baza.create_tables([Klasa, Uczen])  # tworzymy tabele
32

Deklarowanie modelu opiera się na dziedziczonej klasie podstawowej Base. Klasy o nazwach Klasa i Uczen reprezentują tabele w bazie. Właściwości tych klas odpowiadają polom. Każde pole jest instancją klasy określającej typ danych i ma ograniczenia podawane jako dodatkowe argumenty konstruktora:

  • CharFiled() – klasa definiująca pole zawierające ciąg znaków,

  • null=False – ograniczenie, pole nie może zawierać wartości NULL,

  • default='' – ograniczenie, wartość domyślna przechowywana w polu,

  • ForeignKeyField() – klasa definiująca relację, konstruktor otrzymuje nazwę klasy powiązanej, z którą tworzymy relację, oraz nazwę atrybutu określającego relację zwrotną w powiązanej klasie; dzięki temu wywołanie w postaci Klasa.uczniowie da nam dostęp do obiektów reprezentujących uczniów przypisanych do danej klasy.

Po zdefiniowaniu modelu, co jest relatywnie najtrudniejsze, trzeba go przetestować, czyli utworzyć tabele i kolumny w bazie. W Peewee łączymy się z bazą (baza.connect()) i wywołujemy metodę create_tables(), której podajemy w liście nazwy klas reprezentujących tabele.

Omówiony kod można już uruchomić, w katalogu, z którego uruchamiamy skrypt, powinien zostać utworzony plik bazy baza_pw.db.

5.2.3.1. Ćwiczenie

  1. Wykorzystaj interpreter sqlite3 i sprawdź, czy zostały utworzone tabele, czyli jak wygląda kod SQL wygenerowany przez ORM. Przykładowy zrzut poniżej.

../../_images/sqlite3_2.png

Informacja

Nazwy utworzonych tabel to nazwy klas, które je opisują, podobnie nazwy pól odpowiadają nazwom atrybutów. Warto zauważyć, że Peewee nie wymaga definiowania kluczy głównych, są tworzone automatycznie jako pola o nazwie id zawierające liczby całkowite.

5.2.4. Dodawanie danych

Dodawanie (ang. create) danych w Peewee wykonywane jest za pomocą obiektów reprezentujących rekordy zdefiniowanych tabel oraz ich metod.

Peewee. Kod nr
33# dodajemy dwie klasy
34klasa1 = Klasa.create(nazwa='1A', profil='matematyczny')
35klasa2 = Klasa(nazwa='1B', profil='humanistyczny')
36klasa2.save()
37
38# lista uczniów, których dane zapisane są w słownikach
39uczniowie = [
40    {'imie': 'Tomasz', 'nazwisko': 'Nowak', 'klasa': klasa1},
41    {'imie': 'Jan', 'nazwisko': 'Kos', 'klasa': klasa2},
42    {'imie': 'Piotr', 'nazwisko': 'Kowalski', 'klasa': klasa2}
43]
44# dodajemy dane wielu uczniów
45Uczen.insert_many(uczniowie).execute()
46

Metoda create() modelu służy do utworzenia jego instancji (obiektu) i zapisania odpowiedniego rekordu w bazie, czyli wykonania klauzuli INSERT języka SQL. Nazwane argumenty metody odpowiadają atrybutom modelu: klasa = '1A', profil = 'matematyczny').

Innym sposobem jest utworzenie instancji modelu i zapisanie obiektu w bazie jako rekordu za pomocą metody .save().

Można również dodawać wiele rekordów na raz. Tworzymy listę słowników uczniowie. Każdy słownik zawieraja dane w formacie „klucz”:”wartość”, przy czym klucze są nazwami atrybutów klasy. Wartością klucza 'klasa' jest instancja modelu Klasa.

Następnie za pomocą metody insert_many(), której jako argument podajemy przygotowaną listę słowników, dodajemy rekordy z danymi wielu uczniów do bazy.

5.2.5. Odczyt danych

Odczyt danych może być realizowany na wiele sposobów. Zacznijmy od uzupełnienia kodu skryptu:

Peewee. Kod nr
47# odczytujemy wiele rekordów
48print('Klasy:')
49klasy = Klasa.select()
50for klasa in klasy:
51    print(klasa.id, klasa.nazwa, klasa.profil)
52print()
53
54# odczytujemy jeden rekord
55klasa = Klasa.select().where(Klasa.nazwa == '1A').get()
56# klasa = Klasa.get(Klasa.nazwa == '1A')
57print('Klasa:', klasa.nazwa)
58print()
59
60def wypisz_listę_uczniow():
61    """ Odczytujemy i wypisujemy dane uczniów, w tym klasę"""
62    if Uczen.select().count():
63        print('Uczniowie:')
64        uczniowie = Uczen.select().join(Klasa)
65        for uczen in uczniowie:
66            print(uczen.id, uczen.imie, uczen.nazwisko, uczen.klasa.nazwa)
67        print()
68    else:
69        print('Brak uczniów w bazie!')
70
71wypisz_listę_uczniow()
72

Do odczytywaniu wielu rekordów służy metoda select() modelu, która zwraca listę obiektów zapisanych w bazie. Listę możemy odczytać za pomocą pętli, np.: for klasa in klasy:. Mamy również dostęp do atrybutów odczytywanych obiektów, możemy je wypisać dzięki notacji z kropką: print(klasa.id, klasa.nazwa, klasa.profil).

Do odczytania jednego rekordu (obiektu) z bazy danych na podstawie wartości któregoś z jego atrybutów, możemy użyć metody where(), która odpowiada klauzuli warunkowej WHERE języka SQL, np.: Klasa.select().where(Klasa.nazwa == '1A').get(). Dopóki interesuje nas jeden rekord z jednej tabeli, możemy też użyć skróconego zapytania: klasa = Klasa.get(Klasa.nazwa == '1A').

W funkcji wypisz_liste_uczniow() do sprawdzenia liczby obiektów zapisanych w bazie używamy metody count(). Jeżeli w bazie zapisano jakichś uczniów (if Uczen().select().count():), pobieramy ich dane używając złączenia z modelem Klasa. Używamy metody join(), która odpowiada klauzuli INNER JOIN języka SQL: Uczen.select().join(Klasa). Zwróconą listę rekordów odczytujemy za pomocą pętli for. Jeżeli w bazie nie ma żadnych uczniów, wypisujemy odpowiedni komunikat.

5.2.6. Modyfikowanie danych

Systemy ORM ułatwiają modyfikowanie danych w bazie, ponieważ operacja ta polega na zmianie wartości pól wybranego obiektu. W naszym skrypcie dopisujemy kod:

Peewee. Kod nr
73# zmiana klasy ucznia o identyfikatorze 2
74uczen = Uczen.select().join(Klasa).where(Uczen.id == 2).get()
75nowa_klasa = Klasa.select().where(Klasa.nazwa == '1A').get()
76print('Zmieniam klasę ucznia:', uczen.imie, uczen.nazwisko, uczen.klasa.nazwa)
77uczen.klasa = nowa_klasa
78uczen.save()  # zapisanie zmian w bazie
79wypisz_listę_uczniow()
80

Na początku odczytujemy obiekt klasy Uczen o podanym identyfikatorze oraz obiekt klasy Klasa o podanej nazwie. Następnie atrybutowi klasa obiektu reprezentującego ucznia przypisujemy obiekt klasy: uczen.klasa = nowa_klasa. Na koniec zapisujemy zmiany w bazie za pomocą metody save().

5.2.7. Usuwanie danych

Do skryptu dodajemy poniższy kod:

Peewee. Kod nr
87print('Usuwam uczniów z klasy 1A')
88zapytanie = Uczen.delete().where(Uczen.klasa == Klasa.get(Klasa.nazwa == '1A'))
89zapytanie.execute()
90wypisz_listę_uczniow()
91
92baza.close()

Za pomocą kwerendy warunkowej odczytujemy obiekt ucznia o identyfikatorze „3”, a następnie wywołujemy metodę delete_instance(), która usuwa obiekt z bazy.

Do usuwania wielu rekordów służy metoda delete() modelu połączona z metodą where(), która pozwala wskazać rekordy do usunięcia. Należy pamiętać, żeby po utworzeniu zapytania wykonać go za pomocą metody execute().

Po zakończeniu operacji wykonywanych na danych powinniśmy pamiętać o zamknięciu połączenia. Robimy to używając metody obiektu bazy baza.close().

5.2.8. Zadania

  1. Spróbuj dodać do bazy korzystając z systemu Peewee wiele rekordów na raz pobranych z pliku uczniowie.csv. Wykorzystaj i zmodyfikuj funkcję pobierz_dane() opisaną w materiale Dane z pliku.

  2. Dodaj do aplikacji konsolowy interfejs, który umożliwi operacje odczytu, zapisu, modyfikowania i usuwania rekordów. Dane powinny być pobierane z klawiatury od użytkownika.

  3. Przedstawione rozwiązania warto użyć w aplikacjach internetowych jako relatywnie szybki i łatwy sposób obsługi danych. Zobacz, jak to zrobić na przykładzie scenariusza aplikacji Quiz ORM.

  4. Przejrzyj scenariusz aplikacji internetowej Czat, zbudowanej z użyciem frameworku Django, korzystającego z własnego modelu ORM.


Licencja Creative Commons Materiały Python 101 udostępniane przez Centrum Edukacji Obywatelskiej na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowa.

Utworzony:

2026-05-30 o 19:12 w Sphinx 7.3.7

Autorzy:

Robert Bednarz