Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Python REST API Dengan Flask, Connexion, dan SQLAlchemy – Bagian 2

Di Bagian 1 seri ini, Anda menggunakan Flask dan Connexion untuk membuat REST API yang menyediakan operasi CRUD ke struktur dalam memori sederhana yang disebut PEOPLE . Itu berhasil untuk mendemonstrasikan bagaimana modul Connexion membantu Anda membangun REST API yang bagus bersama dengan dokumentasi interaktif.

Seperti yang dicatat beberapa orang dalam komentar untuk Bagian 1, PEOPLE struktur diinisialisasi ulang setiap kali aplikasi dimulai ulang. Dalam artikel ini, Anda akan mempelajari cara menyimpan PEOPLE struktur, dan tindakan yang disediakan API, ke database menggunakan SQLAlchemy dan Marshmallow.

SQLAlchemy menyediakan Object Relational Model (ORM), yang menyimpan objek Python ke representasi database dari data objek. Itu dapat membantu Anda terus berpikir dengan cara Pythonic dan tidak peduli dengan bagaimana data objek akan direpresentasikan dalam database.

Marshmallow menyediakan fungsionalitas untuk membuat serial dan deserialize objek Python saat mereka mengalir keluar dan masuk ke REST API berbasis JSON kami. Marshmallow mengonversi instance kelas Python menjadi objek yang dapat dikonversi ke JSON.

Anda dapat menemukan kode Python untuk artikel ini di sini.

Bonus Gratis: Klik di sini untuk mengunduh salinan Panduan "Contoh REST API" dan mendapatkan pengenalan langsung tentang prinsip-prinsip Python + REST API dengan contoh yang dapat ditindaklanjuti.


Untuk Siapa Artikel Ini

Jika Anda menikmati Bagian 1 dari seri ini, artikel ini akan memperluas sabuk alat Anda lebih jauh. Anda akan menggunakan SQLAlchemy untuk mengakses database dengan cara yang lebih Pythonic daripada SQL langsung. Anda juga akan menggunakan Marshmallow untuk membuat serial dan deserialize data yang dikelola oleh REST API. Untuk melakukan ini, Anda akan memanfaatkan fitur Pemrograman Berorientasi Objek dasar yang tersedia di Python.

Anda juga akan menggunakan SQLAlchemy untuk membuat database serta berinteraksi dengannya. Ini diperlukan untuk mengaktifkan dan menjalankan REST API dengan PEOPLE data yang digunakan di Bagian 1.

Aplikasi web yang disajikan di Bagian 1 akan memiliki file HTML dan JavaScript yang dimodifikasi dengan cara kecil untuk mendukung perubahan juga. Anda dapat meninjau versi final kode dari Bagian 1 di sini.



Ketergantungan Tambahan

Sebelum Anda mulai membangun fungsionalitas baru ini, Anda harus memperbarui virtualenv yang Anda buat untuk menjalankan kode Bagian 1, atau membuat yang baru untuk proyek ini. Cara termudah untuk melakukannya setelah Anda mengaktifkan virtualenv Anda adalah dengan menjalankan perintah ini:

$ pip install Flask-SQLAlchemy flask-marshmallow marshmallow-sqlalchemy marshmallow

Ini menambahkan lebih banyak fungsionalitas ke virtualenv Anda:

  1. Flask-SQLAlchemy menambahkan SQLAlchemy, bersama dengan beberapa ikatan ke Flask, memungkinkan program untuk mengakses database.

  2. flask-marshmallow menambahkan bagian Flask dari Marshmallow, yang memungkinkan program mengonversi objek Python ke dan dari struktur serial.

  3. marshmallow-sqlalchemy menambahkan beberapa pengait Marshmallow ke SQLAlchemy untuk memungkinkan program membuat serial dan deserialize objek Python yang dihasilkan oleh SQLAlchemy.

  4. marshmallow menambahkan sebagian besar fungsi Marshmallow.



Data Orang

Seperti disebutkan di atas, PEOPLE struktur data pada artikel sebelumnya adalah kamus Python di dalam memori. Dalam kamus itu, Anda menggunakan nama belakang orang tersebut sebagai kunci pencarian. Struktur data terlihat seperti ini dalam kode:

# Data to serve with our API
PEOPLE = {
    "Farrell": {
        "fname": "Doug",
        "lname": "Farrell",
        "timestamp": get_timestamp()
    },
    "Brockman": {
        "fname": "Kent",
        "lname": "Brockman",
        "timestamp": get_timestamp()
    },
    "Easter": {
        "fname": "Bunny",
        "lname": "Easter",
        "timestamp": get_timestamp()
    }
}

Modifikasi yang Anda buat pada program akan memindahkan semua data ke tabel database. Ini berarti data akan disimpan ke disk Anda dan akan ada di antara menjalankan server.py program.

Karena nama belakang adalah kunci kamus, kode tersebut membatasi pengubahan nama belakang seseorang:hanya nama depan yang dapat diubah. Selain itu, pindah ke database akan memungkinkan Anda untuk mengubah nama belakang karena tidak lagi digunakan sebagai kunci pencarian untuk seseorang.

Secara konseptual, tabel database dapat dianggap sebagai array dua dimensi di mana baris adalah catatan, dan kolom adalah bidang dalam catatan tersebut.

Tabel database biasanya memiliki nilai integer yang bertambah secara otomatis sebagai kunci pencarian untuk baris. Ini disebut kunci utama. Setiap record dalam tabel akan memiliki kunci utama yang nilainya unik di seluruh tabel. Memiliki kunci utama yang independen dari data yang disimpan dalam tabel membebaskan Anda untuk mengubah bidang lain di baris.

Catatan:

Kunci utama yang bertambah secara otomatis berarti bahwa database menangani:

  • Menambahkan bidang kunci utama terbesar yang ada setiap kali catatan baru dimasukkan ke dalam tabel
  • Menggunakan nilai tersebut sebagai kunci utama untuk data yang baru dimasukkan

Ini menjamin kunci utama yang unik saat tabel berkembang.

Anda akan mengikuti konvensi database penamaan tabel sebagai singular, sehingga tabel akan disebut person . Menerjemahkan PEOPLE our kami struktur di atas menjadi tabel database bernama person memberi Anda ini:

person_id lnama namanama stempel waktu
1 Farrell Doug 08-08-2018 21:16:01.888444
2 Brockman Kent 08-08-2018 21:16:01.889060
3 Paskah Kelinci 08-08-2018 21:16:01.886834

Setiap kolom dalam tabel memiliki nama field sebagai berikut:

  • person_id : bidang kunci utama untuk setiap orang
  • lname : nama belakang orang tersebut
  • fname : nama depan orang tersebut
  • timestamp : stempel waktu yang terkait dengan tindakan penyisipan/pembaruan


Interaksi Basis Data

Anda akan menggunakan SQLite sebagai mesin database untuk menyimpan PEOPLE data. SQLite adalah database yang paling banyak didistribusikan di dunia, dan dilengkapi dengan Python secara gratis. Cepat, melakukan semua pekerjaannya menggunakan file, dan cocok untuk banyak proyek. Ini adalah RDBMS (Sistem Manajemen Basis Data Relasional) lengkap yang mencakup SQL, bahasa dari banyak sistem basis data.

Untuk saat ini, bayangkan person tabel sudah ada dalam database SQLite. Jika Anda pernah memiliki pengalaman dengan RDBMS, Anda mungkin mengetahui SQL, Bahasa Kueri Terstruktur yang digunakan sebagian besar RDBMS untuk berinteraksi dengan database.

Tidak seperti bahasa pemrograman seperti Python, SQL tidak mendefinisikan bagaimana untuk mendapatkan data:ini menjelaskan apa data yang diinginkan, meninggalkan bagaimana hingga mesin database.

Kueri SQL yang mendapatkan semua data di person our kami tabel, diurutkan berdasarkan nama belakang, akan terlihat seperti ini:

SELECT * FROM person ORDER BY 'lname';

Kueri ini memberi tahu mesin database untuk mendapatkan semua bidang dari tabel orang dan mengurutkannya secara default, urutan menaik menggunakan lname bidang.

Jika Anda menjalankan kueri ini terhadap database SQLite yang berisi person tabel, hasilnya akan menjadi satu set catatan yang berisi semua baris dalam tabel, dengan setiap baris berisi data dari semua bidang yang membentuk satu baris. Di bawah ini adalah contoh menggunakan alat baris perintah SQLite yang menjalankan kueri di atas terhadap person tabel basis data:

sqlite> SELECT * FROM person ORDER BY lname;
2|Brockman|Kent|2018-08-08 21:16:01.888444
3|Easter|Bunny|2018-08-08 21:16:01.889060
1|Farrell|Doug|2018-08-08 21:16:01.886834

Output di atas adalah daftar semua baris di person tabel database dengan karakter pipa (‘|’) memisahkan bidang dalam baris, yang dilakukan untuk tujuan tampilan oleh SQLite.

Python sepenuhnya mampu berinteraksi dengan banyak mesin basis data dan mengeksekusi kueri SQL di atas. Hasilnya kemungkinan besar adalah daftar tupel. Daftar luar berisi semua catatan di person meja. Setiap tupel dalam individu akan berisi semua data yang mewakili setiap bidang yang ditentukan untuk baris tabel.

Mendapatkan data dengan cara ini tidak terlalu Pythonic. Daftar catatan boleh saja, tetapi setiap catatan individu hanyalah kumpulan data. Terserah program untuk mengetahui indeks setiap bidang untuk mengambil bidang tertentu. Kode Python berikut menggunakan SQLite untuk mendemonstrasikan cara menjalankan kueri di atas dan menampilkan datanya:

 1import sqlite3
 2
 3conn = sqlite3.connect('people.db')
 4cur = conn.cursor()
 5cur.execute('SELECT * FROM person ORDER BY lname')
 6people = cur.fetchall()
 7for person in people:
 8    print(f'{person[2]} {person[1]}')

Program di atas melakukan hal berikut:

  • Baris 1 mengimpor sqlite3 modul.

  • Baris 3 membuat koneksi ke file database.

  • Baris 4 membuat kursor dari koneksi.

  • Baris 5 menggunakan kursor untuk menjalankan SQL kueri yang dinyatakan sebagai string.

  • Baris 6 mendapatkan semua catatan yang dikembalikan oleh SQL kueri dan menetapkannya ke person variabel.

  • Baris 7 &8 ulangi person daftar variabel dan cetak nama depan dan belakang setiap orang.

person variabel dari Baris 6 di atas akan terlihat seperti ini dengan Python:

people = [
    (2, 'Brockman', 'Kent', '2018-08-08 21:16:01.888444'), 
    (3, 'Easter', 'Bunny', '2018-08-08 21:16:01.889060'), 
    (1, 'Farrell', 'Doug', '2018-08-08 21:16:01.886834')
]

Output dari program di atas terlihat seperti ini:

Kent Brockman
Bunny Easter
Doug Farrell

Dalam program di atas, Anda harus tahu bahwa nama depan seseorang ada di indeks 2 , dan nama belakang seseorang ada di indeks 1 . Lebih buruk lagi, struktur internal person juga harus diketahui setiap kali Anda melewatkan variabel iterasi person sebagai parameter untuk fungsi atau metode.

Akan jauh lebih baik jika apa yang Anda dapatkan kembali untuk person adalah objek Python, di mana masing-masing bidang adalah atribut objek. Ini adalah salah satu hal yang dilakukan SQLAlchemy.


Meja Bobby Kecil

Dalam program di atas, pernyataan SQL adalah string sederhana yang diteruskan langsung ke database untuk dieksekusi. Dalam hal ini, itu bukan masalah karena SQL adalah string literal sepenuhnya di bawah kendali program. Namun, kasus penggunaan untuk REST API Anda akan mengambil input pengguna dari aplikasi web dan menggunakannya untuk membuat kueri SQL. Ini dapat membuka aplikasi Anda untuk menyerang.

Anda akan ingat dari Bagian 1 bahwa REST API untuk mendapatkan satu person dari PEOPLE datanya seperti ini:

GET /api/people/{lname}

Ini berarti API Anda mengharapkan sebuah variabel, lname , di jalur titik akhir URL, yang digunakannya untuk menemukan satu person . Memodifikasi kode Python SQLite dari atas untuk melakukan ini akan terlihat seperti ini:

 1lname = 'Farrell'
 2cur.execute('SELECT * FROM person WHERE lname = \'{}\''.format(lname))

Cuplikan kode di atas melakukan hal berikut:

  • Baris 1 menyetel lname variabel ke 'Farrell' . Ini akan berasal dari jalur titik akhir URL REST API.

  • Baris 2 menggunakan pemformatan string Python untuk membuat string SQL dan menjalankannya.

Untuk mempermudah, kode di atas menyetel lname variabel ke konstanta, tetapi sebenarnya itu akan berasal dari jalur titik akhir URL API dan bisa berupa apa saja yang disediakan oleh pengguna. SQL yang dihasilkan oleh pemformatan string terlihat seperti ini:

SELECT * FROM person WHERE lname = 'Farrell'

Ketika SQL ini dieksekusi oleh database, ia mencari person tabel untuk catatan di mana nama belakang sama dengan 'Farrell' . Inilah yang dimaksudkan, tetapi program apa pun yang menerima input pengguna juga terbuka untuk pengguna jahat. Pada program di atas, di mana lname variabel diatur oleh input yang disediakan pengguna, ini membuka program Anda untuk apa yang disebut serangan injeksi SQL. Inilah yang dikenal sebagai Little Bobby Tables:

Misalnya, bayangkan pengguna jahat memanggil REST API Anda dengan cara ini:

GET /api/people/Farrell');DROP TABLE person;

Permintaan REST API di atas menyetel lname variabel ke 'Farrell');DROP TABLE person;' , yang dalam kode di atas akan menghasilkan pernyataan SQL ini:

SELECT * FROM person WHERE lname = 'Farrell');DROP TABLE person;

Pernyataan SQL di atas valid, dan ketika dijalankan oleh database akan menemukan satu record di mana lname cocok dengan 'Farrell' . Kemudian, ia akan menemukan karakter pembatas pernyataan SQL ; dan akan pergi ke depan dan menjatuhkan seluruh meja. Ini pada dasarnya akan merusak aplikasi Anda.

Anda dapat melindungi program Anda dengan membersihkan semua data yang Anda dapatkan dari pengguna aplikasi Anda. Membersihkan data dalam konteks ini berarti meminta program Anda memeriksa data yang diberikan pengguna dan memastikannya tidak mengandung sesuatu yang berbahaya bagi program. Ini mungkin sulit dilakukan dengan benar dan harus dilakukan di mana pun data pengguna berinteraksi dengan database.

Ada cara lain yang jauh lebih mudah:gunakan SQLAlchemy. Ini akan membersihkan data pengguna untuk Anda sebelum membuat pernyataan SQL. Ini adalah keuntungan dan alasan besar lainnya untuk menggunakan SQLAlchemy saat bekerja dengan database.



Memodelkan Data Dengan SQLAlchemy

SQLAlchemy adalah proyek besar dan menyediakan banyak fungsi untuk bekerja dengan database menggunakan Python. Salah satu hal yang disediakannya adalah ORM, atau Object Relational Mapper, dan inilah yang akan Anda gunakan untuk membuat dan bekerja dengan person tabel basis data. Ini memungkinkan Anda untuk memetakan deretan bidang dari tabel database ke objek Python.

Pemrograman Berorientasi Objek memungkinkan Anda untuk menghubungkan data bersama dengan perilaku, fungsi yang beroperasi pada data tersebut. Dengan membuat kelas SQLAlchemy, Anda dapat menghubungkan bidang dari baris tabel database ke perilaku, memungkinkan Anda untuk berinteraksi dengan data. Berikut definisi kelas SQLAlchemy untuk data di person tabel basis data:

class Person(db.Model):
    __tablename__ = 'person'
    person_id = db.Column(db.Integer, 
                          primary_key=True)
    lname = db.Column(db.String)
    fname = db.Column(db.String)
    timestamp = db.Column(db.DateTime, 
                          default=datetime.utcnow, 
                          onupdate=datetime.utcnow)

Kelas Person mewarisi dari db.Model , yang akan Anda dapatkan saat mulai membuat kode program. Untuk saat ini, itu berarti Anda mewarisi dari kelas dasar yang disebut Model , menyediakan atribut dan fungsionalitas umum untuk semua kelas yang diturunkan darinya.

Definisi lainnya adalah atribut tingkat kelas yang didefinisikan sebagai berikut:

  • __tablename__ = 'person' menghubungkan definisi kelas ke person tabel basis data.

  • person_id = db.Column(db.Integer, primary_key=True) membuat kolom database yang berisi bilangan bulat yang bertindak sebagai kunci utama untuk tabel. Ini juga memberi tahu database bahwa person_id akan menjadi nilai Integer otomatis.

  • lname = db.Column(db.String) membuat bidang nama belakang, kolom database yang berisi nilai string.

  • fname = db.Column(db.String) membuat bidang nama depan, kolom database yang berisi nilai string.

  • timestamp = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) membuat bidang stempel waktu, kolom basis data yang berisi nilai tanggal/waktu. default=datetime.utcnow parameter default nilai cap waktu ke utcnow saat ini nilai saat record dibuat. onupdate=datetime.utcnow parameter memperbarui stempel waktu dengan utcnow saat ini nilai saat catatan diperbarui.

Catatan:Stempel Waktu UTC

Anda mungkin bertanya-tanya mengapa stempel waktu di kelas di atas default dan diperbarui oleh datetime.utcnow() metode, yang mengembalikan UTC, atau Waktu Universal Terkoordinasi. Ini adalah cara menstandardisasi sumber stempel waktu Anda.

Sumber, atau waktu nol, adalah garis yang membentang ke utara dan selatan dari kutub utara bumi ke selatan melalui Inggris. Ini adalah zona waktu nol dari mana semua zona waktu lainnya diimbangi. Dengan menggunakan ini sebagai sumber waktu nol, stempel waktu Anda diimbangi dari titik referensi standar ini.

Jika aplikasi Anda diakses dari zona waktu yang berbeda, Anda memiliki cara untuk melakukan perhitungan tanggal/waktu. Yang Anda butuhkan hanyalah stempel waktu UTC dan zona waktu tujuan.

Jika Anda menggunakan zona waktu lokal sebagai sumber stempel waktu Anda, maka Anda tidak dapat melakukan penghitungan tanggal/waktu tanpa informasi tentang zona waktu lokal yang diimbangi dari waktu nol. Tanpa informasi sumber cap waktu, Anda tidak dapat melakukan perbandingan tanggal/waktu atau matematika sama sekali.

Bekerja dengan stempel waktu berdasarkan UTC adalah standar yang baik untuk diikuti. Berikut adalah situs toolkit untuk bekerja dengan dan lebih memahami mereka.

Ke mana tujuan Anda dengan Person ini definisi kelas? Tujuan akhirnya adalah untuk dapat menjalankan kueri menggunakan SQLAlchemy dan mendapatkan kembali daftar instance dari Person kelas. Sebagai contoh, mari kita lihat pernyataan SQL sebelumnya:

SELECT * FROM people ORDER BY lname;

Tampilkan program contoh kecil yang sama dari atas, tetapi sekarang menggunakan SQLAlchemy:

 1from models import Person
 2
 3people = Person.query.order_by(Person.lname).all()
 4for person in people:
 5    print(f'{person.fname} {person.lname}')

Mengabaikan baris 1 untuk saat ini, yang Anda inginkan hanyalah person catatan diurutkan dalam urutan menaik berdasarkan lname bidang. Apa yang Anda dapatkan kembali dari pernyataan SQLAlchemy Person.query.order_by(Person.lname).all() adalah daftar Person objek untuk semua record dalam person tabel database dalam urutan itu. Pada program di atas, people variabel berisi daftar Person objek.

Program berulang pada person variabel, mengambil setiap person pada gilirannya dan mencetak nama depan dan belakang orang tersebut dari database. Perhatikan bahwa program tidak harus menggunakan indeks untuk mendapatkan fname atau lname values:ia menggunakan atribut yang ditentukan pada Person objek.

Menggunakan SQLAlchemy memungkinkan Anda untuk berpikir dalam kerangka objek dengan perilaku daripada SQL mentah . Ini menjadi lebih bermanfaat ketika tabel database Anda menjadi lebih besar dan interaksi lebih kompleks.



Serializing/Deserializing Data Model

Bekerja dengan data model SQLAlchemy di dalam program Anda sangat nyaman. Ini sangat nyaman dalam program yang memanipulasi data, mungkin membuat perhitungan atau menggunakannya untuk membuat presentasi di layar. Aplikasi Anda adalah REST API yang pada dasarnya menyediakan operasi CRUD pada data, dan karena itu tidak melakukan banyak manipulasi data.

REST API bekerja dengan data JSON, dan di sini Anda dapat mengalami masalah dengan model SQLAlchemy. Karena data yang dikembalikan oleh SQLAlchemy adalah instance kelas Python, Connexion tidak dapat membuat serial instance kelas ini ke data berformat JSON. Ingat dari Bagian 1 bahwa Connexion adalah alat yang Anda gunakan untuk merancang dan mengonfigurasi REST API menggunakan file YAML, dan menghubungkan metode Python ke sana.

Dalam konteks ini, serialisasi berarti mengubah objek Python, yang dapat berisi objek Python lain dan tipe data kompleks, menjadi struktur data yang lebih sederhana yang dapat diuraikan menjadi tipe data JSON, yang tercantum di sini:

  • string : jenis string
  • number : angka yang didukung oleh Python (bilangan bulat, float, long)
  • object : objek JSON, yang kira-kira setara dengan kamus Python
  • array : kira-kira setara dengan Daftar Python
  • boolean : direpresentasikan dalam JSON sebagai true atau false , tetapi dengan Python sebagai True atau False
  • null : dasarnya adalah None dengan Python

Sebagai contoh, Person . Anda kelas berisi stempel waktu, yang merupakan DateTime Python . Tidak ada definisi tanggal/waktu di JSON, jadi stempel waktu harus diubah menjadi string agar ada dalam struktur JSON.

Person . Anda class cukup sederhana sehingga mendapatkan atribut data darinya dan membuat kamus secara manual untuk kembali dari titik akhir URL REST kami tidak akan terlalu sulit. Dalam aplikasi yang lebih kompleks dengan banyak model SQLAlchemy yang lebih besar, ini tidak akan terjadi. Solusi yang lebih baik adalah dengan menggunakan modul bernama Marshmallow untuk melakukan pekerjaan untuk Anda.

Marshmallow membantu Anda membuat PersonSchema kelas, yang seperti SQLAlchemy Person kelas yang kita buat. Namun di sini, alih-alih memetakan tabel database dan nama bidang ke kelas dan atributnya, PersonSchema class mendefinisikan bagaimana atribut kelas akan diubah menjadi format ramah-JSON. Berikut definisi kelas Marshmallow untuk data di person kami tabel:

class PersonSchema(ma.ModelSchema):
    class Meta:
        model = Person
        sqla_session = db.session

Kelas PersonSchema mewarisi dari ma.ModelSchema , yang akan Anda dapatkan saat mulai membuat kode program. Untuk saat ini, ini berarti PersonSchema mewarisi dari kelas dasar Marshmallow yang disebut ModelSchema , menyediakan atribut dan fungsionalitas umum untuk semua kelas yang diturunkan darinya.

Definisi selanjutnya adalah sebagai berikut:

  • class Meta mendefinisikan kelas bernama Meta dalam kelas Anda. ModelSchema kelas yang PersonSchema class mewarisi dari pencarian Meta internal ini class dan menggunakannya untuk menemukan model SQLAlchemy Person dan db.session . Beginilah cara Marshmallow menemukan atribut di Person class dan jenis atribut tersebut sehingga ia tahu cara membuat serial/deserialisasi mereka.

  • model memberi tahu kelas model SQLAlchemy apa yang akan digunakan untuk membuat serial/deserialisasi data ke dan dari.

  • db.session memberi tahu kelas sesi database apa yang digunakan untuk mengintrospeksi dan menentukan tipe data atribut.

Ke mana tujuan Anda dengan definisi kelas ini? Anda ingin dapat membuat serial sebuah instance dari Person kelas menjadi data JSON, dan untuk deserialize data JSON dan membuat Person instance kelas darinya.




Buat Basis Data yang Diinisialisasi

SQLAlchemy menangani banyak interaksi khusus untuk database tertentu dan memungkinkan Anda fokus pada model data serta cara menggunakannya.

Sekarang Anda benar-benar akan membuat database, seperti yang disebutkan sebelumnya, Anda akan menggunakan SQLite. Anda melakukan ini karena beberapa alasan. Itu datang dengan Python dan tidak harus diinstal sebagai modul terpisah. Ini menyimpan semua informasi database dalam satu file dan karena itu mudah diatur dan digunakan.

Menginstal server database terpisah seperti MySQL atau PostgreSQL akan berfungsi dengan baik tetapi akan memerlukan menginstal sistem tersebut dan menjalankannya, yang berada di luar cakupan artikel ini.

Karena SQLAlchemy menangani database, dalam banyak hal tidak masalah apa database yang mendasarinya.

Anda akan membuat program utilitas baru bernama build_database.py untuk membuat dan menginisialisasi people.db SQLite file database yang berisi person . Anda tabel basis data. Sepanjang jalan, Anda akan membuat dua modul Python, config.py dan models.py , yang akan digunakan oleh build_database.py dan server.py . yang dimodifikasi dari Bagian 1.

Di sinilah Anda dapat menemukan kode sumber untuk modul yang akan Anda buat, yang diperkenalkan di sini:

  • config.py mendapatkan modul yang diperlukan diimpor ke dalam program dan dikonfigurasi. Ini termasuk Flask, Connexion, SQLAlchemy, dan Marshmallow. Karena akan digunakan oleh build_database.py dan server.py , beberapa bagian dari konfigurasi hanya akan berlaku untuk server.py aplikasi.

  • models.py adalah modul tempat Anda akan membuat Person SQLAlchemy dan PersonSchema Definisi kelas Marshmallow dijelaskan di atas. Modul ini bergantung pada config.py untuk beberapa objek yang dibuat dan dikonfigurasi di sana.


Modul Konfigurasi

config.py modul, seperti namanya, adalah tempat semua informasi konfigurasi dibuat dan diinisialisasi. Kami akan menggunakan modul ini untuk build_database.py kami file program dan server.py yang akan segera diperbarui file dari artikel Bagian 1. Ini berarti kita akan mengkonfigurasi Flask, Connexion, SQLAlchemy, dan Marshmallow di sini.

Meskipun build_database.py Program tidak menggunakan Flask, Connexion, atau Marshmallow, tetapi menggunakan SQLAlchemy untuk membuat koneksi kita ke database SQLite. Berikut adalah kode untuk config.py modul:

 1import os
 2import connexion
 3from flask_sqlalchemy import SQLAlchemy
 4from flask_marshmallow import Marshmallow
 5
 6basedir = os.path.abspath(os.path.dirname(__file__))
 7
 8# Create the Connexion application instance
 9connex_app = connexion.App(__name__, specification_dir=basedir)
10
11# Get the underlying Flask app instance
12app = connex_app.app
13
14# Configure the SQLAlchemy part of the app instance
15app.config['SQLALCHEMY_ECHO'] = True
16app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////' + os.path.join(basedir, 'people.db')
17app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
18
19# Create the SQLAlchemy db instance
20db = SQLAlchemy(app)
21
22# Initialize Marshmallow
23ma = Marshmallow(app)

Inilah yang dilakukan kode di atas:

  • Baris 2 – 4 impor Connexion seperti yang Anda lakukan di server.py program dari Bagian 1. Ini juga mengimpor SQLAlchemy dari flask_sqlalchemy modul. Ini memberikan akses database program Anda. Terakhir, ia mengimpor Marshmallow dari flask_marshamllow modul.

  • Baris 6 membuat variabel basedir menunjuk ke direktori tempat program dijalankan.

  • Baris 9 menggunakan basedir variabel untuk membuat instance aplikasi Connexion dan memberikannya jalur ke swagger.yml berkas.

  • Baris 12 membuat variabel app , yang merupakan instance Flask yang diinisialisasi oleh Connexion.

  • Baris 15 menggunakan app variabel untuk mengonfigurasi nilai yang digunakan oleh SQLAlchemy. Pertama set SQLALCHEMY_ECHO ke True . Ini menyebabkan SQLAlchemy menggemakan pernyataan SQL yang dijalankannya ke konsol. Ini sangat berguna untuk men-debug masalah saat membangun program database. Setel ini ke False untuk lingkungan produksi.

  • Baris 16 set SQLALCHEMY_DATABASE_URI ke sqlite:////' + os.path.join(basedir, 'people.db') . Ini memberitahu SQLAlchemy untuk menggunakan SQLite sebagai database, dan sebuah file bernama people.db di direktori saat ini sebagai file database. Mesin database yang berbeda, seperti MySQL dan PostgreSQL, akan memiliki SQLALCHEMY_DATABASE_URI yang berbeda string untuk mengonfigurasinya.

  • Baris 17 set SQLALCHEMY_TRACK_MODIFICATIONS menjadi False , mematikan sistem peristiwa SQLAlchemy, yang diaktifkan secara default. Sistem peristiwa menghasilkan peristiwa yang berguna dalam program yang digerakkan oleh peristiwa tetapi menambahkan overhead yang signifikan. Karena Anda tidak membuat program berdasarkan peristiwa, nonaktifkan fitur ini.

  • Baris 19 membuat db variabel dengan memanggil SQLAlchemy(app) . Ini menginisialisasi SQLAlchemy dengan meneruskan app informasi konfigurasi baru saja diatur. db variabel adalah apa yang diimpor ke build_database.py program untuk memberikan akses ke SQLAlchemy dan database. Ini akan melayani tujuan yang sama di server.py program dan people.py modul.

  • Baris 23 membuat ma variabel dengan memanggil Marshmallow(app) . Ini menginisialisasi Marshmallow dan memungkinkannya untuk mengintrospeksi komponen SQLAlchemy yang dilampirkan ke aplikasi. Inilah mengapa Marshmallow diinisialisasi setelah SQLAlchemy.



Modul Model

models.py modul dibuat untuk menyediakan Person dan PersonSchema kelas persis seperti yang dijelaskan pada bagian di atas tentang pemodelan dan serialisasi data. Berikut adalah kode untuk modul tersebut:

 1from datetime import datetime
 2from config import db, ma
 3
 4class Person(db.Model):
 5    __tablename__ = 'person'
 6    person_id = db.Column(db.Integer, primary_key=True)
 7    lname = db.Column(db.String(32), index=True)
 8    fname = db.Column(db.String(32))
 9    timestamp = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
10
11class PersonSchema(ma.ModelSchema):
12    class Meta:
13        model = Person
14        sqla_session = db.session    

Inilah yang dilakukan kode di atas:

  • Baris 1 mengimpor datetime objek dari datetime modul yang disertakan dengan Python. Ini memberi Anda cara untuk membuat stempel waktu di Person kelas.

  • Baris 2 mengimpor db dan ma variabel instan yang didefinisikan dalam config.py modul. Ini memberi modul akses ke atribut dan metode SQLAlchemy yang dilampirkan ke db variabel, dan atribut dan metode Marshmallow yang dilampirkan ke ma variabel.

  • Baris 4 – 9 tentukan Person kelas seperti yang dibahas di bagian pemodelan data di atas, tetapi sekarang Anda tahu di mana db.Model bahwa kelas mewarisi dari berasal. Ini memberikan Person fitur kelas SQLAlchemy, seperti koneksi ke database dan akses ke tabelnya.

  • Baris 11 – 14 tentukan PersonSchema kelas seperti yang telah dibahas pada bagian serialisasi data di atas. Kelas ini mewarisi dari ma.ModelSchema dan memberikan PersonSchema class Marshmallow features, like introspecting the Person class to help serialize/deserialize instances of that class.



Creating the Database

You’ve seen how database tables can be mapped to SQLAlchemy classes. Now use what you’ve learned to create the database and populate it with data. You’re going to build a small utility program to create and build the database with the People data. Here’s the build_database.py program:

 1import os
 2from config import db
 3from models import Person
 4
 5# Data to initialize database with
 6PEOPLE = [
 7    {'fname': 'Doug', 'lname': 'Farrell'},
 8    {'fname': 'Kent', 'lname': 'Brockman'},
 9    {'fname': 'Bunny','lname': 'Easter'}
10]
11
12# Delete database file if it exists currently
13if os.path.exists('people.db'):
14    os.remove('people.db')
15
16# Create the database
17db.create_all()
18
19# Iterate over the PEOPLE structure and populate the database
20for person in PEOPLE:
21    p = Person(lname=person['lname'], fname=person['fname'])
22    db.session.add(p)
23
24db.session.commit()

Here’s what the above code is doing:

  • Line 2 imports the db instance from the config.py modul.

  • Line 3 imports the Person class definition from the models.py modul.

  • Lines 6 – 10 create the PEOPLE data structure, which is a list of dictionaries containing your data. The structure has been condensed to save presentation space.

  • Lines 13 &14 perform some simple housekeeping to delete the people.db file, if it exists. This file is where the SQLite database is maintained. If you ever have to re-initialize the database to get a clean start, this makes sure you’re starting from scratch when you build the database.

  • Line 17 creates the database with the db.create_all() panggilan. This creates the database by using the db instance imported from the config modul. db instance is our connection to the database.

  • Lines 20 – 22 iterate over the PEOPLE list and use the dictionaries within to instantiate a Person kelas. After it is instantiated, you call the db.session.add(p) fungsi. This uses the database connection instance db to access the session obyek. The session is what manages the database actions, which are recorded in the session. In this case, you are executing the add(p) method to add the new Person instance to the session objek.

  • Line 24 calls db.session.commit() to actually save all the person objects created to the database.

Catatan: At Line 22, no data has been added to the database. Everything is being saved within the session obyek. Only when you execute the db.session.commit() call at Line 24 does the session interact with the database and commit the actions to it.

In SQLAlchemy, the session is an important object. It acts as the conduit between the database and the SQLAlchemy Python objects created in a program. The session helps maintain the consistency between data in the program and the same data as it exists in the database. It saves all database actions and will update the underlying database accordingly by both explicit and implicit actions taken by the program.

Now you’re ready to run the build_database.py program to create and initialize the new database. You do so with the following command, with your Python virtual environment active:

python build_database.py

When the program runs, it will print SQLAlchemy log messages to the console. These are the result of setting SQLALCHEMY_ECHO to True in the config.py mengajukan. Much of what’s being logged by SQLAlchemy is the SQL commands it’s generating to create and build the people.db SQLite database file. Here’s an example of what’s printed out when the program is run:

2018-09-11 22:20:29,951 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
2018-09-11 22:20:29,951 INFO sqlalchemy.engine.base.Engine ()
2018-09-11 22:20:29,952 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
2018-09-11 22:20:29,952 INFO sqlalchemy.engine.base.Engine ()
2018-09-11 22:20:29,956 INFO sqlalchemy.engine.base.Engine PRAGMA table_info("person")
2018-09-11 22:20:29,956 INFO sqlalchemy.engine.base.Engine ()
2018-09-11 22:20:29,959 INFO sqlalchemy.engine.base.Engine 
CREATE TABLE person (
    person_id INTEGER NOT NULL, 
    lname VARCHAR, 
    fname VARCHAR, 
    timestamp DATETIME, 
    PRIMARY KEY (person_id)
)
2018-09-11 22:20:29,959 INFO sqlalchemy.engine.base.Engine ()
2018-09-11 22:20:29,975 INFO sqlalchemy.engine.base.Engine COMMIT
2018-09-11 22:20:29,980 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-09-11 22:20:29,983 INFO sqlalchemy.engine.base.Engine INSERT INTO person (lname, fname, timestamp) VALUES (?, ?, ?)
2018-09-11 22:20:29,983 INFO sqlalchemy.engine.base.Engine ('Farrell', 'Doug', '2018-09-12 02:20:29.983143')
2018-09-11 22:20:29,984 INFO sqlalchemy.engine.base.Engine INSERT INTO person (lname, fname, timestamp) VALUES (?, ?, ?)
2018-09-11 22:20:29,985 INFO sqlalchemy.engine.base.Engine ('Brockman', 'Kent', '2018-09-12 02:20:29.984821')
2018-09-11 22:20:29,985 INFO sqlalchemy.engine.base.Engine INSERT INTO person (lname, fname, timestamp) VALUES (?, ?, ?)
2018-09-11 22:20:29,985 INFO sqlalchemy.engine.base.Engine ('Easter', 'Bunny', '2018-09-12 02:20:29.985462')
2018-09-11 22:20:29,986 INFO sqlalchemy.engine.base.Engine COMMIT



Using the Database

Once the database has been created, you can modify the existing code from Part 1 to make use of it. All of the modifications necessary are due to creating the person_id primary key value in our database as the unique identifier rather than the lname nilai.


Update the REST API

None of the changes are very dramatic, and you’ll start by re-defining the REST API. The list below shows the API definition from Part 1 but is updated to use the person_id variable in the URL path:

Action HTTP Verb URL Path Deskripsi
Create POST /api/people Defines a unique URL to create a new person
Read GET /api/people Defines a unique URL to read a collection of people
Read GET /api/people/{person_id} Defines a unique URL to read a particular person by person_id
Update PUT /api/people/{person_id} Defines a unique URL to update an existing person by person_id
Delete DELETE /api/orders/{person_id} Defines a unique URL to delete an existing person by person_id

Where the URL definitions required an lname value, they now require the person_id (primary key) for the person record in the people meja. This allows you to remove the code in the previous app that artificially restricted users from editing a person’s last name.

In order for you to implement these changes, the swagger.yml file from Part 1 will have to be edited. For the most part, any lname parameter value will be changed to person_id , and person_id will be added to the POST and PUT responses. You can check out the updated swagger.yml file.



Update the REST API Handlers

With the swagger.yml file updated to support the use of the person_id identifier, you’ll also need to update the handlers in the people.py file to support these changes. In the same way that the swagger.yml file was updated, you need to change the people.py file to use the person_id value rather than lname .

Here’s part of the updated person.py module showing the handler for the REST URL endpoint GET /api/people :

 1from flask import (
 2    make_response,
 3    abort,
 4)
 5from config import db
 6from models import (
 7    Person,
 8    PersonSchema,
 9)
10
11def read_all():
12    """
13    This function responds to a request for /api/people
14    with the complete lists of people
15
16    :return:        json string of list of people
17    """
18    # Create the list of people from our data
19    people = Person.query \
20        .order_by(Person.lname) \
21        .all()
22
23    # Serialize the data for the response
24    person_schema = PersonSchema(many=True)
25    return person_schema.dump(people).data

Here’s what the above code is doing:

  • Lines 1 – 9 import some Flask modules to create the REST API responses, as well as importing the db instance from the config.py modul. In addition, it imports the SQLAlchemy Person and Marshmallow PersonSchema classes to access the person database table and serialize the results.

  • Line 11 starts the definition of read_all() that responds to the REST API URL endpoint GET /api/people and returns all the records in the person database table sorted in ascending order by last name.

  • Lines 19 – 22 tell SQLAlchemy to query the person database table for all the records, sort them in ascending order (the default sorting order), and return a list of Person Python objects as the variable people .

  • Line 24 is where the Marshmallow PersonSchema class definition becomes valuable. You create an instance of the PersonSchema , passing it the parameter many=True . This tells PersonSchema to expect an interable to serialize, which is what the people variable is.

  • Line 25 uses the PersonSchema instance variable (person_schema ), calling its dump() method with the people daftar. The result is an object having a data attribute, an object containing a people list that can be converted to JSON. This is returned and converted by Connexion to JSON as the response to the REST API call.

Catatan: The people list variable created on Line 24 above can’t be returned directly because Connexion won’t know how to convert the timestamp field into JSON. Returning the list of people without processing it with Marshmallow results in a long error traceback and finally this Exception:

TypeError: Object of type Person is not JSON serializable

Here’s another part of the person.py module that makes a request for a single person from the person basis data. Here, read_one(person_id) function receives a person_id from the REST URL path, indicating the user is looking for a specific person. Here’s part of the updated person.py module showing the handler for the REST URL endpoint GET /api/people/{person_id} :

 1def read_one(person_id):
 2    """
 3    This function responds to a request for /api/people/{person_id}
 4    with one matching person from people
 5
 6    :param person_id:   ID of person to find
 7    :return:            person matching ID
 8    """
 9    # Get the person requested
10    person = Person.query \
11        .filter(Person.person_id == person_id) \
12        .one_or_none()
13
14    # Did we find a person?
15    if person is not None:
16
17        # Serialize the data for the response
18        person_schema = PersonSchema()
19        return person_schema.dump(person).data
20
21    # Otherwise, nope, didn't find that person
22    else:
23        abort(404, 'Person not found for Id: {person_id}'.format(person_id=person_id))

Here’s what the above code is doing:

  • Lines 10 – 12 use the person_id parameter in a SQLAlchemy query using the filter method of the query object to search for a person with a person_id attribute matching the passed-in person_id . Rather than using the all() query method, use the one_or_none() method to get one person, or return None if no match is found.

  • Line 15 determines whether a person was found or not.

  • Line 17 shows that, if person was not None (a matching person was found), then serializing the data is a little different. You don’t pass the many=True parameter to the creation of the PersonSchema() contoh. Instead, you pass many=False because only a single object is passed in to serialize.

  • Line 18 is where the dump method of person_schema is called, and the data attribute of the resulting object is returned.

  • Line 23 shows that, if person was None (a matching person wasn’t found), then the Flask abort() method is called to return an error.

Another modification to person.py is creating a new person in the database. This gives you an opportunity to use the Marshmallow PersonSchema to deserialize a JSON structure sent with the HTTP request to create a SQLAlchemy Person obyek. Here’s part of the updated person.py module showing the handler for the REST URL endpoint POST /api/people :

 1def create(person):
 2    """
 3    This function creates a new person in the people structure
 4    based on the passed-in person data
 5
 6    :param person:  person to create in people structure
 7    :return:        201 on success, 406 on person exists
 8    """
 9    fname = person.get('fname')
10    lname = person.get('lname')
11
12    existing_person = Person.query \
13        .filter(Person.fname == fname) \
14        .filter(Person.lname == lname) \
15        .one_or_none()
16
17    # Can we insert this person?
18    if existing_person is None:
19
20        # Create a person instance using the schema and the passed-in person
21        schema = PersonSchema()
22        new_person = schema.load(person, session=db.session).data
23
24        # Add the person to the database
25        db.session.add(new_person)
26        db.session.commit()
27
28        # Serialize and return the newly created person in the response
29        return schema.dump(new_person).data, 201
30
31    # Otherwise, nope, person exists already
32    else:
33        abort(409, f'Person {fname} {lname} exists already')

Here’s what the above code is doing:

  • Line 9 &10 set the fname and lname variables based on the Person data structure sent as the POST body of the HTTP request.

  • Lines 12 – 15 use the SQLAlchemy Person class to query the database for the existence of a person with the same fname and lname as the passed-in person .

  • Line 18 addresses whether existing_person is None . (existing_person was not found.)

  • Line 21 creates a PersonSchema() instance called schema .

  • Line 22 uses the schema variable to load the data contained in the person parameter variable and create a new SQLAlchemy Person instance variable called new_person .

  • Line 25 adds the new_person instance to the db.session .

  • Line 26 commits the new_person instance to the database, which also assigns it a new primary key value (based on the auto-incrementing integer) and a UTC-based timestamp.

  • Line 33 shows that, if existing_person is not None (a matching person was found), then the Flask abort() method is called to return an error.



Update the Swagger UI

With the above changes in place, your REST API is now functional. The changes you’ve made are also reflected in an updated swagger UI interface and can be interacted with in the same manner. Below is a screenshot of the updated swagger UI opened to the GET /people/{person_id} section. This section of the UI gets a single person from the database and looks like this:

As shown in the above screenshot, the path parameter lname has been replaced by person_id , which is the primary key for a person in the REST API. The changes to the UI are a combined result of changing the swagger.yml file and the code changes made to support that.



Update the Web Application

The REST API is running, and CRUD operations are being persisted to the database. So that it is possible to view the demonstration web application, the JavaScript code has to be updated.

The updates are again related to using person_id instead of lname as the primary key for person data. In addition, the person_id is attached to the rows of the display table as HTML data attributes named data-person-id , so the value can be retrieved and used by the JavaScript code.

This article focused on the database and making your REST API use it, which is why there’s just a link to the updated JavaScript source and not much discussion of what it does.




Example Code

All of the example code for this article is available here. There’s one version of the code containing all the files, including the build_database.py utility program and the server.py modified example program from Part 1.



Kesimpulan

Congratulations, you’ve covered a lot of new material in this article and added useful tools to your arsenal!

You’ve learned how to save Python objects to a database using SQLAlchemy. You’ve also learned how to use Marshmallow to serialize and deserialize SQLAlchemy objects and use them with a JSON REST API. The things you’ve learned have certainly been a step up in complexity from the simple REST API of Part 1, but that step has given you two very powerful tools to use when creating more complex applications.

SQLAlchemy and Marshmallow are amazing tools in their own right. Using them together gives you a great leg up to create your own web applications backed by a database.

In Part 3 of this series, you’ll focus on the R part of RDBMS :relationships, which provide even more power when you are using a database.

« Part 1:REST APIs With Flask + ConnexionPart 2:Database PersistencePart 3:Database Relationships »

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Kinerja sys.partitions

  2. Cara Menghitung Nilai Berbeda dalam SQL

  3. SQL Alias ​​Dijelaskan

  4. Bagaimana Menulis Pernyataan KASUS dalam SQL

  5. Analisis Operasional Real-Time dan Indeks Toko Kolom Non-Clustered