Lewati ke isi

Database

Starlette tidak sepenuhnya terkait dengan implementasi basis data tertentu.

Anda dapat menggunakannya dengan ORM asinkron seperti GINO, atau menggunakan titik akhir non-asinkron biasa dan berintegrasi dengan SQLAlchemy.

Dalam dokumen ini, kami akan menunjukkan cara menargetkan databases Integrasi paket, yang menyediakan dukungan inti SQLAlchemy untuk berbagai driver database yang berbeda.

Berikut adalah contoh lengkap yang mencakup definisi tabel, mengkonfigurasi instance database.Database, dan beberapa titik akhir yang berinteraksi dengan database.

**. Env **

DATABASE _ URL = sqlite: / / test.db

app.py

Kontekstual impor

Basis data impor
meter persegi import
Starlette.application diimpor dari Starlette
Konfigurasi impor dari starlette.config
Dari pemula. Tanggapan impor JSONResponse
Dari starlette. rute impor routing


# Konfigurasi dari variabel lingkungan atau file '.env'.
Konfigurasi = Konfigurasi ('.env')
DATABASE _ URL = konfigurasi ('DATABASE _ URL')


# Definisi tabel database.
Metadata = sqlalchemy. MetaData ()

Catatan = sqlalchemy. Tabel (
    "Catatan,"
    Metadata,
    Sqlalchemy. Kolom ("id," sqlalchemy. Integer, primary _ key = Benar),
    Sqlalchemy. Kolom ("teks," sqlalchemy. String),
    Sqlalchemy. Kolom ("selesai," sqlalchemy. Boolean),
)

Database = database. Database (DATABASE _ URL)

@ contextlib.asynccontextmanager
Async def umur (aplikasi):
    Tunggu koneksi database ()
    Hasil
    Tunggu database .putuskan sambungan ()

# Kode aplikasi utama.
Async def list _ notes (permintaan):
    Pertanyaan = notes.select ()
    Hasil = menunggu database. ambil _ semua (pertanyaan)
    Konten = [
        {
            "Teks": hasil ["teks"],
            "Selesai": hasil ["selesai"]
        }
        Untuk hasil dalam hasil
    ]
    Mengembalikan JSONResponse (konten)

Sinkronisasi def add _ note (permintaan):
    Data = menunggu request.json ()
    Query = notes.insert ().value (
       Teks = data ["teks"],
       Selesai = data ["selesai"]
    )
    Menunggu database .eksekusi (pertanyaan)
    Mengembalikan JSONResponse ({
        "Teks": data ["teks"],
        "Selesai": data ["selesai"]
    })

Rute = [
    Rute ("/ catatan," endpoint = list _ notes, metode = ["GET"]),
    Rute ("/ catatan," endpoint = add _ note, metode = ["POST"]),
]

Aplikasi = Starlette (
    Rute = rute,
    Umur = umur,
)

Terakhir, Anda perlu membuat database & tabel data. Alembic direkomendasikan dan kami menutupinya secara singkat dalam migrasi.

permintaan

Pertanyaan dapat dilakukan dengan menggunakan query SQLAlchemy Core.

Metode berikut didukung:

  • rows = await database.fetch_all(query)
  • row = await database.fetch_one(query)
  • async for row in database.iterate(query)
  • await database.execute(query)
  • await database.execute_many(query)

Transaksi; bisnis; transaksi (kata ini memiliki banyak arti, dan terjemahan spesifik tergantung pada konteksnya). Jika hanya kata "Transaksi" yang diberikan diterjemahkan secara harfiah, itu dapat diterjemahkan sebagai "transaksi; transaksi." Berikut adalah beberapa definisi umum untuk Anda pilih sesuai dengan konteks tertentu. Sesuai dengan persyaratan judul, output langsung diterjemahkan sebagai: transaksi


Transaksi database dapat digunakan sebagai dekorator, pengelola konteks, atau API tingkat rendah.

Gunakan dekorator pada titik akhir:

@ Database.transaksi ()
Async def mengisi _ note (permintaan):
    # Sisipan database ini terjadi dalam transaksi.
    # Ini akan digulung kembali oleh `RuntimeError`.
    Query = notes.insert ().value (text = "you won 't see me," selesai = Benar)
    Menunggu database .eksekusi (pertanyaan)
    Meningkatkan RuntimeError ()

Menggunakan manajer konteks:

Async def mengisi _ note (permintaan):
    Sinkronisasi dengan transaksi database ():
        # Sisipan database ini terjadi dalam transaksi.
        # Ini akan digulung kembali oleh `RuntimeError`.
        Query = notes.insert ().value (text = "you won 't see me," selesai = Benar)
        Menunggu permintaan.database .eksekusi (permintaan)
        Meningkatkan RuntimeError ()

Menggunakan API tingkat rendah:

Async def mengisi _ note (permintaan):
    Transaksi = menunggu Database.transaksi ()
    Coba:
        # Sisipan database ini terjadi dalam transaksi.
        # Ini akan digulung kembali oleh `RuntimeError`.
        Query = notes.insert ().value (text = "you won 't see me," selesai = Benar)
        Menunggu database .eksekusi (pertanyaan)
        Meningkatkan RuntimeError ()
    Kecuali:
        Menunggu transaksi.rollback ()
        Naikkan
    Lainnya:
        Menunggu transaksi.commit ()

uji isolasi

Saat menguji layanan yang menggunakan database, ada beberapa hal yang ingin kami pastikan. Persyaratan kami harus:

  • Uji dengan database terpisah.
  • Setiap kali tes di jalankan, database tes baru dibuat.
  • Pastikan status database antara setiap kasus tes terisolasi satu sama lain.

Untuk memenuhi persyaratan ini, kami perlu membangun aplikasi dan pengujian kami seperti ini:

Starlette.application diimpor dari Starlette
Konfigurasi impor dari starlette.config
Basis data impor

Konfigurasi = Konfigurasi (. "env")

TESTING = konfigurasi ('TESTING', cast = bool, default = Salah)
DATABASE _ URL = konfigurasi ('DATABASE _ URL', cast = database. Database seURL)
TEST _ DATABASE _ URL = DATABASE _ URL (database = 'test _' + DATABASE _ database URL)

# Gunakan 'force _ rollback' selama pengujian, untuk memastikan kami tidak melanjutkan perubahan database
# antara setiap kasus tes.
JIKA MENGUJI:
    Database = database. Database (TEST _ DATABASE _ URL, force _ rollback = Benar)
Lainnya:
    Database = database. Database (DATABASE _ URL)

Selama latihan berjalan, kita masih perlu mengatur TESTING dan mengatur database pengujian. Dengan asumsi kita menggunakan py.test, inilah tampilan conftest.py kita:

uji coba impor
Dari starlette.config import environ
Dari starlette.testclient impor TestClient
Dari sqlalchemy import create _ engine
Dari sqlalchemy _ memanfaatkan database impor _ ada, buat _ database, drop _ database

# Ini menetapkan `os.environ`, tetapi memberikan beberapa perlindungan tambahan.
# Jika kami menempatkannya di bawah impor aplikasi, itu akan meningkatkan kesalahan
# memberi tahu kami bahwa 'PENGUJIAN' telah dibaca dari lingkungan.
Environ ['PENGUJIAN'] = 'Benar'

Aplikasi impor


@ Pytest.fixture (scope = "sesi," autouse = Benar)
Def membuat _ test _ database ():
  """
  Buat database yang bersih pada setiap kasus pengujian.
  Demi keamanan, kita harus batalkan jika sudah ada database.

  Kami menggunakan paket `sqlalchemy_utils` di sini untuk beberapa pembantu secara konsisten
  Membuat dan menjatuhkan database.
  """
  Url = str (aplikasi. UJI _ DATABASE _ URL)
  Mesin = buat _ mesin (url)
  Tegaskan tidak database _ ada (url), 'Uji database sudah ada. Aborting tes.'
  buat _ database (url) # Buat database pengujian.
  Metadata. buat _ semua (mesin) # Buat tabel.
  Hasil # Jalankan tes.
  drop _ database (url) # Jatuhkan database tes.


@ pytest.fixture ()
Pasti klien ():
    """
    Saat menggunakan perlengkapan 'klien' dalam kasus uji, kami akan mendapatkan database lengkap
    Pengembalian antar kasus uji:

    Tes pasti _ beranda (klien):
        Url = app.url _ path _ untuk ('beranda')
        Tanggapan = klien. Dapatkan (url)
        Tegaskan tanggapan. status _ code = = 200
    """
    Dengan TestClient (aplikasi) sebagai klien:
        Menghasilkan klien

Migrasi

Untuk mengelola perubahan tambahan pada database Anda, Anda hampir pasti perlu menggunakan migrasi database. Untuk ini, kami sangat merekomendasikan Alembic, yang ditulis oleh penulis SQLAlchemy.

$Pip instal alembic
$alembic init migrasi

Sekarang Anda perlu mengaturnya sehingga Alembic mereferensikan URL DATABASE _ yang dikonfigurasi dan menggunakan metadata tabel Anda.

Hapus baris berikut di alembic.ini:

Sqlalchemy.url = driver: / / user: pass@localhost/ dbname

Dalam migrations/env.py, Anda perlu mengatur kunci konfigurasi 'sqlalchemy.url' dan variabel target_metadata. Anda akan membutuhkan sesuatu seperti ini:

# Objek Konfigurasi Alembik.
Konteks = kontekstut.config

# Konfigurasikan Alembic untuk menggunakan URL DATABASE _ kami dan definisi tabel kami...
Aplikasi impor
Config.set _ main _ option ('sqlalchemy.url', str (app.DATABASE _ URL))
target _ metadata = app.metadata

...

Kemudian, dengan menggunakan contoh catatan kami di atas, buat revisi awal:

Revisi alembik -m "Buat tabel catatan"

Dan isi file baru (di dalam migrations/versions) dengan instruksi yang diperlukan:

Upgrade pasti ():
    Pilih. membuat _ tabel (
      'Catatan',
      Sqlalchemy. Kolom ("id," sqlalchemy. Integer, primary _ key = Benar),
      Sqlalchemy. Kolom ("teks," sqlalchemy. String),
      Sqlalchemy. Kolom ("selesai," sqlalchemy. Boolean),
    )

Benar-benar downgrade ():
    Op. drop _ table ('catatan')

Dan jalankan migrasi pertama Anda. Aplikasi pencatat kami sekarang aktif dan berjalan!

Kepala peningkatan alembik

Jalankan migrasi selama pengujian

Ini adalah praktik yang baik untuk memastikan bahwa rangkaian pengujian Anda menjalankan migrasi basis data setiap kali Anda membuat basis data pengujian. Ini akan membantu mengidentifikasi masalah apa pun dalam skrip migrasi Anda dan membantu memastikan bahwa tes di jalankan terhadap basis data yang konsisten dengan status basis data langsung Anda.

Kami dapat menyesuaikan perlengkapan create_test_database sedikit:

Dari perintah impor alembic
Konfigurasi impor dari alembic.config
Aplikasi impor

...

@ Pytest.fixture (scope = "sesi," autouse = Benar)
Def membuat _ test _ database ():
    Url = str (aplikasi. DATABASE _ URL)
    Mesin = buat _ mesin (url)
    Tegaskan tidak database _ ada (url), 'Uji database sudah ada. Aborting tes.'
    buat _ database (url) # Buat database pengujian.
    Konfigurasi = Konfigurasi ("alembic.ini") # Jalankan migrasi.
    Perintah.upgrade (konfigurasi, "kepala")
    Hasil # Jalankan tes.
    drop _ database (url) # Jatuhkan database tes.