Zum Inhalt springen

Tabelle mit SQLModel erstellen - Engine verwenden

Jetzt kommen wir zum Code. 👩‍💻

Stellen Sie sicher, dass Sie sich in Ihrem Projektverzeichnis befinden und Ihre virtuelle Umgebung aktiviert haben, wie in Virtuelle Umgebungen erklärt.

Wir werden

  • Eine Tabelle mit SQLModel definieren
  • Dieselbe SQLite-Datenbank und Tabelle mit SQLModel erstellen
  • DB Browser for SQLite verwenden, um die Operationen zu bestätigen

Hier ist eine Erinnerung an die Tabellenstruktur, die wir wollen

idnamesecret_nameage
1DeadpondDive Wilsonnull
2Spider-BoyPedro Parqueadornull
3Rusty-ManTommy Sharp48

Modellklasse für die Tabelle erstellen

Das Erste, was wir tun müssen, ist eine Klasse zu erstellen, die die Daten in der Tabelle repräsentiert.

Eine Klasse wie diese, die Daten repräsentiert, wird üblicherweise als Modell bezeichnet.

Tipp

Deshalb heißt dieses Paket SQLModel. Weil es hauptsächlich zum Erstellen von SQL-Modellen verwendet wird.

Dazu importieren wir SQLModel (sowie andere Dinge, die wir ebenfalls verwenden werden) und erstellen eine Klasse Hero, die von SQLModel erbt und das Tabellenmodell für unsere Helden repräsentiert.

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None

# Code below omitted 👇
👀 Vollständige Dateivorschau
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 Andere Versionen und Varianten
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

Diese Klasse Hero repräsentiert die Tabelle für unsere Helden. Und jede Instanz, die wir später erstellen, wird eine Zeile in der Tabelle repräsentieren.

Wir verwenden die Konfiguration table=True, um SQLModel mitzuteilen, dass dies ein Tabellenmodell ist, das eine Tabelle repräsentiert.

Info

Es ist auch möglich, Modelle ohne table=True zu haben, das wären nur Datenmodelle, ohne eine Tabelle in der Datenbank, sie wären keine Tabellenmodelle.

Diese Datenmodelle werden später sehr nützlich sein, aber vorerst behalten wir die Konfiguration table=True bei.

Felder, Spalten definieren

Der nächste Schritt ist die Definition der Felder oder Spalten der Klasse unter Verwendung von Standard-Python-Typ-Annotationen.

Der Name jeder dieser Variablen wird der Name der Spalte in der Tabelle sein.

Und der Typ jeder einzelnen wird ebenfalls der Typ der Tabellenspalte sein.

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None

# Code below omitted 👇
👀 Vollständige Dateivorschau
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 Andere Versionen und Varianten
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

Betrachten wir nun diese Feld-/Spaltendefinitionen genauer.

None Felder, Nullable Spalten

Beginnen wir mit age, beachten Sie, dass es den Typ int | None hat.

Das ist die Standardmethode, um in Python zu deklarieren, dass etwas "int oder None sein könnte".

Und wir setzen auch den Standardwert von age auf None.

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None

# Code below omitted 👇
👀 Vollständige Dateivorschau
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 Andere Versionen und Varianten
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

Tipp

Wir definieren id auch mit int | None. Aber wir werden id unten besprechen.

Da der Typ int | None ist

  • Bei der Datenvalidierung wird None ein zulässiger Wert für age sein.
  • In der Datenbank darf die Spalte für age NULL (das SQL-Äquivalent zu Pythons None) enthalten.

Und da ein Standardwert = None vorhanden ist

  • Bei der Datenvalidierung ist dieses Feld age nicht erforderlich, es wird standardmäßig None sein.
  • Beim Speichern in der Datenbank hat die Spalte age standardmäßig einen NULL-Wert.

Tipp

Der Standardwert könnte auch etwas anderes gewesen sein, z. B. = 42.

Primärschlüssel id

Betrachten wir nun das Feld id. Dies ist der Primärschlüssel der Tabelle.

Daher müssen wir id als Primärschlüssel markieren.

Dazu verwenden wir die spezielle Funktion Field von sqlmodel und setzen das Argument primary_key=True.

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None

# Code below omitted 👇
👀 Vollständige Dateivorschau
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 Andere Versionen und Varianten
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

Dadurch teilen wir SQLModel mit, dass dieses Feld/diese Spalte id der Primärschlüssel der Tabelle ist.

Aber innerhalb der SQL-Datenbank ist er immer erforderlich und kann nicht NULL sein. Warum sollten wir ihn mit int | None deklarieren?

Die id wird in der Datenbank erforderlich sein, aber sie wird von der Datenbank generiert, nicht von unserem Code.

Wenn wir also eine Instanz dieser Klasse erstellen (in den nächsten Kapiteln), werden wir die id nicht setzen. Und der Wert von id wird None sein, bis wir sie in der Datenbank speichern, und dann wird sie endlich einen Wert haben.

my_hero = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")

do_something(my_hero.id)  # Oh no! my_hero.id is None! 😱🚨

# Imagine this saves it to the database
somehow_save_in_db(my_hero)

do_something(my_hero.id)  # Now my_hero.id has a value generated in DB 🎉

Da also in unserem Code (nicht in der Datenbank) der Wert von id None sein könnte, verwenden wir int | None. So wird uns der Editor helfen können, zum Beispiel, wenn wir versuchen, auf die id eines Objekts zuzugreifen, das wir noch nicht in der Datenbank gespeichert haben und das immer noch None wäre.

Da wir nun den Platz des Standardwerts mit unserer Field()-Funktion einnehmen, setzen wir mit dem Argument default=None in Field() den tatsächlichen Standardwert von id auf None.

Field(default=None)

Wenn wir den default-Wert nicht setzen würden, würde er, sobald wir dieses Modell später für die Datenvalidierung (gestützt durch Pydantic) verwenden, neben einem int auch einen Wert von None *akzeptieren*, aber die Übergabe dieses None-Werts erfordern. Und das wäre verwirrend für jeden, der dieses Modell später benutzt (wahrscheinlich wir selbst), also ist es besser, den Standardwert hier zu setzen.

Engine erstellen

Jetzt müssen wir die SQLAlchemy Engine erstellen.

Es ist ein Objekt, das die Kommunikation mit der Datenbank handhabt.

Wenn Sie eine Server-Datenbank haben (z. B. PostgreSQL oder MySQL), hält die Engine die Netzwerkverbindungen zu dieser Datenbank.

Das Erstellen der Engine ist sehr einfach, rufen Sie einfach create_engine() mit einer URL für die zu verwendende Datenbank auf.

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
👀 Vollständige Dateivorschau
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 Andere Versionen und Varianten
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

Sie sollten normalerweise ein einziges Engine-Objekt für Ihre gesamte Anwendung haben und es überall wiederverwenden.

Tipp

Es gibt noch etwas anderes, das als Session bezeichnet wird und normalerweise kein einzelnes Objekt pro Anwendung sein sollte.

Aber dazu werden wir später sprechen.

Engine Datenbank-URL

Jede unterstützte Datenbank hat ihren eigenen URL-Typ. Für SQLite ist das zum Beispiel sqlite:/// gefolgt vom Dateipfad. Zum Beispiel

  • sqlite:///database.db
  • sqlite:///databases/local/application.db
  • sqlite:///db.sqlite

SQLite unterstützt eine spezielle Datenbank, die komplett *im Speicher* lebt. Daher ist sie sehr schnell, aber seien Sie vorsichtig, die Datenbank wird nach Beendigung des Programms gelöscht. Sie können diese In-Memory-Datenbank angeben, indem Sie nur zwei Schrägstriche (//) und keinen Dateinamen verwenden.

  • sqlite://
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
👀 Vollständige Dateivorschau
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 Andere Versionen und Varianten
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

Sie können viel mehr über alle von SQLAlchemy unterstützten Datenbanken (und damit von SQLModel unterstützten) in der SQLAlchemy-Dokumentation lesen.

Engine Echo

In diesem Beispiel verwenden wir auch das Argument echo=True.

Dadurch gibt die Engine alle SQL-Anweisungen aus, die sie ausführt, was Ihnen helfen kann zu verstehen, was passiert.

Es ist besonders nützlich zum Lernen und Debuggen.

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
👀 Vollständige Dateivorschau
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 Andere Versionen und Varianten
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

Aber in der Produktion würden Sie wahrscheinlich echo=True entfernen wollen.

engine = create_engine(sqlite_url)

Engine Technische Details

Tipp

Wenn Sie vorher nichts über SQLAlchemy wussten und nur SQLModel lernen, können Sie diesen Abschnitt wahrscheinlich überspringen und nach unten scrollen.

Sie können viel mehr über die Engine in der SQLAlchemy-Dokumentation lesen.

SQLModel definiert seine eigene Funktion create_engine(). Sie ist dieselbe wie die create_engine() von SQLAlchemy, mit dem Unterschied, dass sie standardmäßig future=True verwendet (was bedeutet, dass sie den Stil des neuesten SQLAlchemy, 1.4, und des zukünftigen 2.0 verwendet).

Und die Version von create_engine() von SQLModel ist intern typisiert, sodass Ihr Editor Ihnen mit Autovervollständigung und Inline-Fehlern helfen kann.

Datenbank und Tabelle erstellen

Jetzt ist alles vorbereitet, um endlich die Datenbank und die Tabelle zu erstellen.

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 Andere Versionen und Varianten
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

Tipp

Das Erstellen der Engine erstellt noch nicht die Datei database.db.

Aber sobald wir SQLModel.metadata.create_all(engine) ausführen, erstellt es die Datei database.db und erstellt die Tabelle hero in dieser Datenbank.

Beides geschieht in diesem einzigen Schritt.

Lassen Sie uns das aufschlüsseln.

SQLModel.metadata.create_all(engine)

SQLModel MetaData

Die Klasse SQLModel hat ein Attribut metadata. Es ist eine Instanz der Klasse MetaData.

Immer wenn Sie eine Klasse erstellen, die von SQLModel erbt und mit table = True konfiguriert ist, wird sie in diesem metadata-Attribut registriert.

Daher hat SQLModel.metadata durch die letzte Zeile die Registrierung von Hero bereits abgeschlossen.

create_all() aufrufen

Dieses Objekt MetaData unter SQLModel.metadata hat eine Methode create_all().

Es nimmt eine Engine und verwendet diese, um die Datenbank und alle in diesem MetaData-Objekt registrierten Tabellen zu erstellen.

SQLModel MetaData Reihenfolge ist wichtig

Das bedeutet auch, dass Sie SQLModel.metadata.create_all() nach dem Code aufrufen müssen, der neue Modellklassen erstellt, die von SQLModel erben.

Stellen Sie sich zum Beispiel vor, Sie tun Folgendes

  • Erstellen Sie die Modelle in einer Python-Datei models.py.
  • Erstellen Sie das Engine-Objekt in einer Datei db.py.
  • Erstellen Sie Ihre Hauptanwendung und rufen Sie SQLModel.metadata.create_all() in app.py auf.

Wenn Sie nur SQLModel importieren und versuchen würden, SQLModel.metadata.create_all() in app.py aufzurufen, würden Ihre Tabellen nicht erstellt werden.

# This wouldn't work! 🚨
from sqlmodel import SQLModel

from .db import engine

SQLModel.metadata.create_all(engine)

Es würde nicht funktionieren, weil, wenn Sie nur SQLModel importieren, Python nicht den gesamten Code ausführt, der die Klassen erstellt, die davon erben (in unserem Beispiel die Klasse Hero), daher ist SQLModel.metadata noch leer.

Aber wenn Sie die Modelle bevor Sie SQLModel.metadata.create_all() aufrufen, importieren, wird es funktionieren.

from sqlmodel import SQLModel

from . import models
from .db import engine

SQLModel.metadata.create_all(engine)

Dies würde funktionieren, weil durch den Import der Modelle Python den gesamten Code ausführt, der die Klassen erstellt, die von SQLModel erben, und sie im SQLModel.metadata registriert.

Alternativ könnten Sie SQLModel und Ihre Modelle in db.py importieren.

# db.py
from sqlmodel import SQLModel, create_engine
from . import models


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url)

Und dann SQLModel *von* db.py in app.py importieren und dort SQLModel.metadata.create_all() aufrufen.

# app.py
from .db import engine, SQLModel

SQLModel.metadata.create_all(engine)

Der Import von SQLModel aus db.py würde funktionieren, weil SQLModel auch in db.py importiert wird.

Und dieser Trick würde korrekt funktionieren und die Tabellen in der Datenbank erstellen, weil durch den Import von SQLModel aus db.py Python den gesamten Code ausführt, der die Klassen erstellt, die von SQLModel in dieser db.py-Datei erben, z. B. die Klasse Hero.

Migrationen

Für dieses einfache Beispiel und für den größten Teil des Tutorials - Benutzerhandbuch reicht die Verwendung von SQLModel.metadata.create_all() aus.

Aber für ein Produktionssystem würden Sie wahrscheinlich ein System zur Migration der Datenbank verwenden wollen.

Dies wäre nützlich und wichtig, zum Beispiel, wenn Sie eine Spalte hinzufügen oder entfernen, eine neue Tabelle hinzufügen, einen Typ ändern usw.

Aber über Migrationen werden Sie später im Fortgeschrittenen Benutzerhandbuch erfahren.

Das Programm ausführen

Lassen Sie uns das Programm ausführen, um alles funktionsfähig zu sehen.

Speichern Sie den Code, falls noch nicht geschehen, in einer Datei app.py.

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 Andere Versionen und Varianten
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

Tipp

Denken Sie daran, die virtuelle Umgebung zu aktivieren, bevor Sie sie ausführen.

Führen Sie nun das Programm mit Python aus.

// We set echo=True, so this will show the SQL code
$ python app.py

// First, some boilerplate SQL that we are not that interested in

INFO Engine BEGIN (implicit)
INFO Engine PRAGMA main.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine PRAGMA temp.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine

// Finally, the glorious SQL to create the table ✨

CREATE TABLE hero (
        id INTEGER,
        name VARCHAR NOT NULL,
        secret_name VARCHAR NOT NULL,
        age INTEGER,
        PRIMARY KEY (id)
)

// More SQL boilerplate

INFO Engine [no key 0.00020s] ()
INFO Engine COMMIT

Info

Ich habe die Ausgabe oben etwas vereinfacht, um sie leichter lesbar zu machen.

Aber in Wirklichkeit würde es anstelle der Anzeige von

INFO Engine BEGIN (implicit)

etwas wie dies anzeigen

2021-07-25 21:37:39,175 INFO sqlalchemy.engine.Engine BEGIN (implicit)

TEXT oder VARCHAR

Im Beispiel des vorherigen Kapitels haben wir die Tabelle mit TEXT für einige Spalten erstellt.

Aber in dieser Ausgabe verwendet SQLAlchemy stattdessen VARCHAR. Lassen Sie uns sehen, was los ist.

Erinnern Sie sich, dass jede SQL-Datenbank Unterschiede in dem hat, was sie unterstützt?

Dies ist einer der Unterschiede. Jede Datenbank unterstützt bestimmte Datentypen wie INTEGER und TEXT.

Einige Datenbanken haben bestimmte Typen, die für bestimmte Dinge besonders sind. Zum Beispiel unterstützen PostgreSQL und MySQL BOOLEAN für die Werte True und False. SQLite akzeptiert SQL mit Booleans, auch beim Definieren von Tabellenspalten, aber intern verwendet es INTEGERs, wobei 1 für True und 0 für False steht.

Auf die gleiche Weise gibt es verschiedene mögliche Typen zur Speicherung von Zeichenketten. SQLite verwendet den Typ TEXT. Andere Datenbanken wie PostgreSQL und MySQL verwenden jedoch standardmäßig den Typ VARCHAR, und VARCHAR ist einer der gebräuchlichsten Datentypen.

VARCHAR kommt von variable length character (variable Längen Zeichen).

SQLAlchemy generiert die SQL-Anweisungen zum Erstellen von Tabellen mit VARCHAR, und dann empfängt SQLite diese und wandelt sie intern in TEXTs um.

Zusätzlich zu den Unterschieden zwischen diesen beiden Datentypen verlangen einige Datenbanken wie MySQL die Angabe einer maximalen Länge für die VARCHAR-Typen, z. B. setzt VARCHAR(255) die maximale Anzahl von Zeichen auf 255.

Um den Einstieg in SQLModel unabhängig von der verwendeten Datenbank (auch mit MySQL) und ohne zusätzliche Konfigurationen zu erleichtern, werden str-Felder standardmäßig in den meisten Datenbanken als VARCHAR und in MySQL als VARCHAR(255) interpretiert. So wissen Sie, dass dieselbe Klasse ohne zusätzlichen Aufwand mit den gängigsten Datenbanken kompatibel sein wird.

Tipp

Wie Sie die maximale Länge von Zeichenketten-Spalten ändern können, erfahren Sie später im Fortgeschrittenen Tutorial - Benutzerhandbuch.

Datenbank überprüfen

Öffnen Sie nun die Datenbank mit DB Browser for SQLite, Sie werden sehen, dass das Programm die Tabelle hero genau wie zuvor erstellt hat. 🎉

Datenerstellung refaktorisieren

Lassen Sie uns nun den Code etwas umstrukturieren, um ihn später leichter wiederverwenden, teilen und testen zu können.

Verschieben wir den Code, der die Haupt-Nebeneffekte hat, der Daten verändert (erstellt eine Datei mit einer Datenbank und einer Tabelle), in eine Funktion.

In diesem Beispiel ist es nur SQLModel.metadata.create_all(engine).

Lassen Sie uns das in eine Funktion create_db_and_tables() packen.

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)

# Code below omitted 👇
👀 Vollständige Dateivorschau
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


if __name__ == "__main__":
    create_db_and_tables()
🤓 Andere Versionen und Varianten
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


if __name__ == "__main__":
    create_db_and_tables()

Wenn SQLModel.metadata.create_all(engine) nicht in einer Funktion wäre und wir versuchen würden, etwas aus diesem Modul (aus dieser Datei) in einem anderen zu importieren, würde es versuchen, die Datenbank und die Tabelle jedes Mal zu erstellen, wenn wir diese andere Datei ausführen, die dieses Modul importiert.

Das wollen wir nicht, dass es so passiert, sondern nur, wenn wir es *absichtlich* tun, deshalb packen wir es in eine Funktion, weil wir sicherstellen können, dass die Tabellen nur erstellt werden, wenn wir diese Funktion aufrufen, und nicht, wenn dieses Modul woanders importiert wird.

Jetzt könnten wir zum Beispiel die Klasse Hero in einer anderen Datei importieren, ohne diese Nebeneffekte zu haben.

Tipp

😅 Spoiler-Alarm: Die Funktion heißt create_db_and_tables(), weil wir in Zukunft mehr Tabellen mit anderen Klassen neben Hero haben werden. 🚀

Daten als Skript erstellen

Wir haben die Nebeneffekte beim Importieren von etwas aus Ihrer app.py-Datei verhindert.

Aber wir möchten immer noch, dass sie die Datenbank und die Tabelle erstellt, wenn wir sie direkt mit Python als unabhängiges Skript vom Terminal aus aufrufen, genau wie oben.

Tipp

Betrachten Sie die Wörter Skript und Programm als austauschbar.

Das Wort Skript impliziert oft, dass der Code unabhängig und einfach ausgeführt werden kann. Oder in einigen Fällen bezieht es sich auf ein relativ einfaches Programm.

Dazu können wir die spezielle Variable __name__ in einem if-Block verwenden.

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


if __name__ == "__main__":
    create_db_and_tables()
🤓 Andere Versionen und Varianten
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


if __name__ == "__main__":
    create_db_and_tables()

Über __name__ == "__main__"

Der Hauptzweck von __name__ == "__main__" ist es, Code zu haben, der ausgeführt wird, wenn Ihre Datei mit

$ python app.py

// Something happens here ✨

...aber nicht aufgerufen wird, wenn eine andere Datei sie importiert, wie in

from app import Hero

Tipp

Dieser if-Block, der if __name__ == "__main__": verwendet, wird manchmal als "Hauptblock" bezeichnet.

Der offizielle Name (in der Python-Dokumentation) ist "Top-Level-Skriptumgebung".

Mehr Details

Nehmen wir an, Ihre Datei heißt myapp.py.

Wenn Sie sie mit

$ python myapp.py

// This will call create_db_and_tables()

...dann wird die interne Variable __name__ in Ihrer Datei, die automatisch von Python erstellt wird, den Wert des Strings "__main__" haben.

So wird die Funktion in

if __name__ == "__main__":
    create_db_and_tables()

...ausgeführt.


Das passiert nicht, wenn Sie dieses Modul (Datei) importieren.

Wenn Sie also eine andere Datei importer.py mit

from myapp import Hero

# Some more code

...in diesem Fall hat die automatische Variable in myapp.py nicht die Variable __name__ mit dem Wert "__main__".

Daher wird die Zeile

if __name__ == "__main__":
    create_db_and_tables()

...nicht ausgeführt.

Info

Weitere Informationen finden Sie in der offiziellen Python-Dokumentation.

Letzte Überprüfung

Nach diesen Änderungen könnten Sie sie erneut ausführen, und sie würde die gleiche Ausgabe wie zuvor erzeugen.

Aber jetzt können wir Dinge aus diesem Modul in anderen Dateien importieren.

Werfen wir nun einen letzten Blick auf den Code.

from sqlmodel import Field, SQLModel, create_engine  # (2)!


class Hero(SQLModel, table=True):  # (3)!
    id: int | None = Field(default=None, primary_key=True)  # (4)!
    name: str  # (5)!
    secret_name: str  # (6)!
    age: int | None = None  # (7)!


sqlite_file_name = "database.db"  # (8)!
sqlite_url = f"sqlite:///{sqlite_file_name}"  # (9)!

engine = create_engine(sqlite_url, echo=True)  # (10)!


def create_db_and_tables():  # (11)!
    SQLModel.metadata.create_all(engine)  # (12)!


if __name__ == "__main__":  # (13)!
    create_db_and_tables()  # (14)!
  1. Importieren Sie Optional aus typing, um Felder zu deklarieren, die None sein können.
  2. Importieren Sie die Dinge, die wir von sqlmodel benötigen: Field, SQLModel, create_engine.
  3. Erstellen Sie die Modellklasse Hero, die die Tabelle hero in der Datenbank repräsentiert.

    Und markieren Sie diese Klasse auch als Tabellenmodell mit table=True.

  4. Erstellen Sie das Feld id.

    Es könnte None sein, bis die Datenbank ihm einen Wert zuweist, daher annotieren wir es mit Optional.

    Es ist ein Primärschlüssel, daher verwenden wir Field() und das Argument primary_key=True.

  5. Erstellen Sie das Feld name.

    Es ist erforderlich, daher gibt es keinen Standardwert und es ist nicht Optional.

  6. Erstellen Sie das Feld secret_name.

    Ebenfalls erforderlich.

  7. Erstellen Sie das Feld age.

    Es ist nicht erforderlich, der Standardwert ist None.

    In der Datenbank ist der Standardwert NULL, das SQL-Äquivalent zu None.

    Da dieses Feld None (und in der Datenbank NULL) sein kann, annotieren wir es mit Optional.

  8. Schreiben Sie den Namen der Datenbankdatei.

  9. Verwenden Sie den Namen der Datenbankdatei, um die Datenbank-URL zu erstellen.
  10. Erstellen Sie die Engine mit der URL.

    Dies erstellt die Datenbank noch nicht. Zu diesem Zeitpunkt wird keine Datei oder Tabelle erstellt, nur das Engine-Objekt, das die Verbindungen zu dieser spezifischen Datenbank verwaltet, und mit spezifischer Unterstützung für SQLite (basierend auf der URL).

  11. Packen Sie den Code, der Nebeneffekte verursacht, in eine Funktion.

    In diesem Fall nur eine Zeile, die die Datenbankdatei mit der Tabelle erstellt.

  12. Erstellen Sie alle Tabellen, die automatisch in SQLModel.metadata registriert wurden.

  13. Fügen Sie einen Hauptblock oder eine "Top-Level-Skriptumgebung" hinzu.

    Und fügen Sie einige Logik hinzu, die ausgeführt werden soll, wenn dies direkt mit Python aufgerufen wird, wie in

    $ python app.py
    
    // Execute all the stuff and show the output
    

    ...aber die nicht ausgeführt wird, wenn etwas aus diesem Modul importiert wird, wie

    from app import Hero
    
  14. Rufen Sie in diesem Hauptblock die Funktion auf, die die Datenbankdatei und die Tabelle erstellt.

    Auf diese Weise, wenn wir es mit

    $ python app.py
    
    // Doing stuff ✨
    

    ...wird die Datenbankdatei und die Tabelle erstellt.

from typing import Optional  # (1)!

from sqlmodel import Field, SQLModel, create_engine  # (2)!


class Hero(SQLModel, table=True):  # (3)!
    id: Optional[int] = Field(default=None, primary_key=True)  # (4)!
    name: str  # (5)!
    secret_name: str  # (6)!
    age: Optional[int] = None  # (7)!


sqlite_file_name = "database.db"  # (8)!
sqlite_url = f"sqlite:///{sqlite_file_name}"  # (9)!

engine = create_engine(sqlite_url, echo=True)  # (10)!


def create_db_and_tables():  # (11)!
    SQLModel.metadata.create_all(engine)  # (12)!


if __name__ == "__main__":  # (13)!
    create_db_and_tables()  # (14)!
  1. Importieren Sie Optional aus typing, um Felder zu deklarieren, die None sein können.
  2. Importieren Sie die Dinge, die wir von sqlmodel benötigen: Field, SQLModel, create_engine.
  3. Erstellen Sie die Modellklasse Hero, die die Tabelle hero in der Datenbank repräsentiert.

    Und markieren Sie diese Klasse auch als Tabellenmodell mit table=True.

  4. Erstellen Sie das Feld id.

    Es könnte None sein, bis die Datenbank ihm einen Wert zuweist, daher annotieren wir es mit Optional.

    Es ist ein Primärschlüssel, daher verwenden wir Field() und das Argument primary_key=True.

  5. Erstellen Sie das Feld name.

    Es ist erforderlich, daher gibt es keinen Standardwert und es ist nicht Optional.

  6. Erstellen Sie das Feld secret_name.

    Ebenfalls erforderlich.

  7. Erstellen Sie das Feld age.

    Es ist nicht erforderlich, der Standardwert ist None.

    In der Datenbank ist der Standardwert NULL, das SQL-Äquivalent zu None.

    Da dieses Feld None (und in der Datenbank NULL) sein kann, annotieren wir es mit Optional.

  8. Schreiben Sie den Namen der Datenbankdatei.

  9. Verwenden Sie den Namen der Datenbankdatei, um die Datenbank-URL zu erstellen.
  10. Erstellen Sie die Engine mit der URL.

    Dies erstellt die Datenbank noch nicht. Zu diesem Zeitpunkt wird keine Datei oder Tabelle erstellt, nur das Engine-Objekt, das die Verbindungen zu dieser spezifischen Datenbank verwaltet, und mit spezifischer Unterstützung für SQLite (basierend auf der URL).

  11. Packen Sie den Code, der Nebeneffekte verursacht, in eine Funktion.

    In diesem Fall nur eine Zeile, die die Datenbankdatei mit der Tabelle erstellt.

  12. Erstellen Sie alle Tabellen, die automatisch in SQLModel.metadata registriert wurden.

  13. Fügen Sie einen Hauptblock oder eine "Top-Level-Skriptumgebung" hinzu.

    Und fügen Sie einige Logik hinzu, die ausgeführt werden soll, wenn dies direkt mit Python aufgerufen wird, wie in

    $ python app.py
    
    // Execute all the stuff and show the output
    

    ...aber die nicht ausgeführt wird, wenn etwas aus diesem Modul importiert wird, wie

    from app import Hero
    
  14. Rufen Sie in diesem Hauptblock die Funktion auf, die die Datenbankdatei und die Tabelle erstellt.

    Auf diese Weise, wenn wir es mit

    $ python app.py
    
    // Doing stuff ✨
    

    ...wird die Datenbankdatei und die Tabelle erstellt.

Tipp

Überprüfen Sie, was jede Zeile tut, indem Sie auf jede Zahlenblase im Code klicken. 👆

Zusammenfassung

Wir haben gelernt, wie man SQLModel verwendet, um zu definieren, wie eine Tabelle in der Datenbank aussehen soll, und wir haben mit SQLModel eine Datenbank und eine Tabelle erstellt.

Wir haben auch den Code refaktorisiert, um ihn leichter wiederverwenden, teilen und später testen zu können.

In den nächsten Kapiteln werden wir sehen, wie SQLModel uns helfen wird, mit SQL-Datenbanken aus Code zu interagieren. 🤓