]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
:memo: Add docs for SQL databases
authorSebastián Ramírez <tiangolo@gmail.com>
Sat, 15 Dec 2018 16:06:08 +0000 (20:06 +0400)
committerSebastián Ramírez <tiangolo@gmail.com>
Sat, 15 Dec 2018 16:06:08 +0000 (20:06 +0400)
docs/tutorial/sql-databases.md [new file with mode: 0644]
docs/tutorial/src/sql-databases/tutorial001.py [new file with mode: 0644]
mkdocs.yml

diff --git a/docs/tutorial/sql-databases.md b/docs/tutorial/sql-databases.md
new file mode 100644 (file)
index 0000000..5b5d528
--- /dev/null
@@ -0,0 +1,142 @@
+**FastAPI** doesn't require you to use a SQL (relational) database.
+
+But you can use relational database that you want.
+
+Here we'll see an example using <a href="https://www.sqlalchemy.org/" target="_blank">SQLAlchemy</a>.
+
+You can easily adapt it to any database supported by SQLAlchemy, like:
+
+* PostgreSQL
+* MySQL
+* SQLite
+* Oracle
+* Microsoft SQL Server, etc.
+
+In this example, we'll use **PostgreSQL**.
+
+!!! note
+    Notice that most of the code is the standard `SQLAlchemy` code you would use with any framework.
+
+    The **FastAPI** specific code is as small as always.
+
+## Import SQLAlchemy components
+
+For now, don't pay attention to the rest, only the imports:
+
+```Python hl_lines="3 4 5"
+{!./tutorial/src/sql-databases/tutorial001.py!}
+```
+
+## Define the database
+
+Define the database that SQLAlchemy should connect to:
+
+```Python hl_lines="8"
+{!./tutorial/src/sql-databases/tutorial001.py!}
+```
+
+!!! tip
+    This is the main line that you would have to modify if you wanted to use a different database than **PostgreSQL**.
+
+## Create the SQLAlchemy `engine`
+
+```Python hl_lines="10"
+{!./tutorial/src/sql-databases/tutorial001.py!}
+```
+
+## Create a `scoped_session`
+
+```Python hl_lines="11 12 13"
+{!./tutorial/src/sql-databases/tutorial001.py!}
+```
+
+!!! note "Very Technical Details"
+    Don't worry too much if you don't understand this. You can still use the code.
+
+    This `scoped_session` is a feature of SQLAlchemy.
+
+    The resulting object, the `db_session` can then be used anywhere a a normal SQLAlchemy session.
+    
+    It can be used as a global because it is implemented to work independently on each "<abbr title="A sequence of code being executed by the program, while at the same time, or at intervals, there can be others being executed too.">thread</abbr>", so the actions you perform with it in one path operation function won't affect the actions performed (possibly concurrently) by other path operation functions.
+
+## Create a `CustomBase` model
+
+This is more of a trick to facilitate your life than something required.
+
+But by creating this `CustomBase` class and inheriting from it, your models will have automatic `__tablename__` attributes (that are required by SQLAlchemy).
+
+That way you don't have to declare them explicitly.
+
+So, your models will behave very similarly to, for example, Flask-SQLAlchemy.
+
+```Python hl_lines="15 16 17 18 19"
+{!./tutorial/src/sql-databases/tutorial001.py!}
+```
+
+## Create the SQLAlchemy `Base` model
+
+```Python hl_lines="22"
+{!./tutorial/src/sql-databases/tutorial001.py!}
+```
+
+## Create your application data model
+
+Now this is finally code specific to your app.
+
+Here's a user model that will be a table in the database:
+
+```Python hl_lines="25 26 27 28 29"
+{!./tutorial/src/sql-databases/tutorial001.py!}
+```
+
+## Get a user
+
+By creating a function that is only dedicated to getting your user from a `username` (or any other parameter) independent of your path operation function, you can more easily re-use it in multiple parts and also add <abbr title="Automated test, written in code, that checks if another piece of code is working correctly.">unit tests</abbr> for it:
+
+```Python hl_lines="32 33"
+{!./tutorial/src/sql-databases/tutorial001.py!}
+```
+
+## Create your **FastAPI** code
+
+Now, finally, here's the standard **FastAPI** code.
+
+Create your app and path operation function:
+
+```Python hl_lines="37 40 41 42 43"
+{!./tutorial/src/sql-databases/tutorial001.py!}
+```
+
+As we are using SQLAlchemy's `scoped_session`, we don't even have to create a dependency with `Depends`.
+
+We can just call `get_user` directly from inside of the path operation function and use the global `db_session`.
+
+## Create the path operation function
+
+Here we are using SQLAlchemy code inside of the path operation function, and it in turn will go and communicate with an external database. 
+
+That could potentially require some "waiting".
+
+But as SQLAlchemy doesn't have compatibility for using `await`, as would be with something like:
+
+```Python
+user = await get_user(username, db_session)
+```
+
+...and instead we are using:
+
+```Python
+user = get_user(username, db_session)
+```
+
+Then we should declare the path operation without `async def`, just with a normal `def`:
+
+```Python hl_lines="41"
+{!./tutorial/src/sql-databases/tutorial001.py!}
+```
+
+## Migrations
+
+Because we are using SQLAlchemy directly and we don't require any kind of plug-in for it to work with **FastAPI**, we could integrate database <abbr title="Automatically updating the database to have any new column we define in our models.">migrations</abbr> with <a href="https://alembic.sqlalchemy.org" target="_blank">Alembic</a> directly.
+
+You would probably want to declare your database and models in a different file or set of files, this would allow Alembic to import it and use it without even needing to have **FastAPI** installed for the migrations.
diff --git a/docs/tutorial/src/sql-databases/tutorial001.py b/docs/tutorial/src/sql-databases/tutorial001.py
new file mode 100644 (file)
index 0000000..cc0c01c
--- /dev/null
@@ -0,0 +1,43 @@
+from fastapi import FastAPI
+
+from sqlalchemy import Boolean, Column, Integer, String, create_engine
+from sqlalchemy.ext.declarative import declarative_base, declared_attr
+from sqlalchemy.orm import scoped_session, sessionmaker
+
+# SQLAlchemy specific code, as with any other app
+SQLALCHEMY_DATABASE_URI = "postgresql://user:password@postgresserver/db"
+
+engine = create_engine(SQLALCHEMY_DATABASE_URI, convert_unicode=True)
+db_session = scoped_session(
+    sessionmaker(autocommit=False, autoflush=False, bind=engine)
+)
+
+class CustomBase:
+    # Generate __tablename__ automatically
+    @declared_attr
+    def __tablename__(cls):
+        return cls.__name__.lower()
+
+
+Base = declarative_base(cls=CustomBase)
+
+
+class User(Base):
+    id = Column(Integer, primary_key=True, index=True)
+    email = Column(String, unique=True, index=True)
+    hashed_password = Column(String)
+    is_active = Column(Boolean(), default=True)
+
+
+def get_user(username, db_session):
+    return db_session.query(User).filter(User.id == username).first()
+
+
+# FastAPI specific code
+app = FastAPI()
+
+
+@app.get("/users/{username}")
+def read_user(username: str):
+    user = get_user(username, db_session)
+    return user
index a5b635a8f26d5b1a4e299f67238cb8ee738122a6..a90a6c0553b333ae8a4e75ddaa00fd3c0ddf340f 100644 (file)
@@ -41,6 +41,7 @@ nav:
             - Dependencies Intro: 'tutorial/dependencies/intro.md'
             - First Steps: 'tutorial/dependencies/first-steps.md'
             - Second Steps: 'tutorial/dependencies/second-steps.md'
+        - SQL (Relational) Databases: 'tutorial/sql-databases.md'
     - Concurrency and async / await: 'async.md'
     - Deployment: 'deployment.md'