FastAPI doesn't require you to use a SQL (relational) database.
But you can use any relational database that you want.
Here we'll see an example using SQLAlchemy.
.
└── sql_app
├── init.py
├── crud.py
├── database.py
├── main.py
├── models.py
└── schemas.py
init.py는 빈 file이다, 그러나 이것은 모든 module들이 있는 sql_app이 패키지임을 Python에게 말해준다.
pip install sqlalchemy
Let's refer to the file sql_app/database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
// Create a database URL for SQLAlchemy
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
이 예시에서, SQLite database랑 연결한다.
(opening a file with the SQLite database).
이 파일은 sql_app.db 파일의 같은 directory 안에 위치하게 될 것이다.
이것이 마지막 부분이 ./sql_app.db인 이유이다.
만약 PostgreSQL database를 사용한다면, 아래와 같게 된다:
SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
The first step is to create a SQLAlchemy "engine".
We will later use this engine in other places.
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
The argument:
connect_args={"check_same_thread": False}
...is needed only for SQLite. It's not needed for other databases.
SessionLocal callse의 각 instance는 database session이 된다. class 자체로는 database session이 아니다.
하지만 SessionLocal class의 instance를 만들고 나면, 이 instance는 실제 database session이 된다.
SQLAlchemy로부터 importing 하는 Session으로부터 구별하기 위해 SessionLocal이라고 이름을 지었다.
나중에 Session을 사용하게 될 것이다.
SessionLocal class를 만들기 위해, sessionmaker 함수를 사용한다:
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
// 이 부분
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class를 return 하는 declare_base() function을 사용할 것이다.
후에 database models or classes의 각각을 만들기 위해 이 class로부터 상속받을 것이다:
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
// 이 부분
Base = declarative_base()
이제 sql_app/models.py을 보자
SQLAlchemy mdoel을 만들기 전에 만들어둔 Base class를 사용할 것이다.
Tip
Import Base from database (the file database.py from above).
Create classes that inherit from it.
These classes are the SQLAlchemy models.
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
items = relationship("Item", back_populates="owner")
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User", back_populates="items")
__tablename__
attribute SQLAlchemy에게 각 모델에 대한 database에서 사용할 table 이름에 대해 알려준다.
Now create all the model (class) attributes.
이러한 속성의 각각은 연관된 database table의 column을 나타낸다.
기본값으로 SQLAlchemy로부터 column을 사용한다.
그리고 argument로 database에 type을 정의해 Integer, String, and Boolean으로서 SQLAlchemy에 class type을 전달한다.
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = "users"
// 이 부분
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
items = relationship("Item", back_populates="owner")
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User", back_populates="items")
Now create the relationships.
For this, we use relationship provided by SQLAlchemy ORM.
This will become, more or less, a "magic" attribute that will contain the values from other tables related to this one.
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
// 이 부분
items = relationship("Item", back_populates="owner")
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
// 이 부분
owner = relationship("User", back_populates="items")
my_user.item로 User의 items 속성에 접근할 때, 이것은 users table의 이 record로 pointing 하는 foreign key를 가진 Item SQLAlchemy models의 list를 갖게 된다.
my_user.items에 접근할 때, SQLAlchemy는 실제로 가고, items table의 database로부터 items를 fetch하고, 여기에 채운다.
그리고 Item의 owner 속성에 접근할 때, 이것은 usesr table로부터 User SQLAlchemy model을 포함할 것이다. 이것은 users table로부터 record를 가져오기 위해 이것의 foriegn key를 알려주는 attribute/column인 owner_id를 사용할 것이다.
Now let's check the file sql_app/schemas.py.
Tip
data를 만들고 읽는 동안 common attributes(or let's say "schemas")를 가지기 위해 ItemBase and UserBase Pydantic models을 만든다.
그리고 이것들로부터 상속하는 ItemCreate와 UserCreate를 만들고, 생성에 필요한 추가적인 data(속성)를 더한다.
그래서, user는 만들 때 password를 가지고 있다.
하지만 보안을 위해, password는 다른 Pydantic models에 들어가지 않는다. 예를 들어, 이것은 API로부터 user를 읽어올 때는 보내지지 않는다.
from pydantic import BaseModel
class ItemBase(BaseModel):
title: str
description: str | None = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: list[Item] = []
class Config:
orm_mode = True
SQLAlchemy models는 =를 사용하여 attributes를 정의한다, 그리고 Column에 parameter로 type을 전달한다:
name = Column(String)
하지만 Pydantic models는 :을 사용하여 type을 선언한다:
name: str
data reading, API로부터 returning할 때 사용될 Pydantic moedels를 만들어 보자.
예를 들어, item을 만들기 전에, 우리는 그것에 할당된 ID가 무엇인지 모른다, 하지만 읽을 때 (API로부터 returning 했을 때) 이미 ID를 알게 된다.
같은 방식으로, user를 읽을 때, 이 user에 속하는 item을 포함하는 items를 선언할 수 있다.
이러한 items의 ID들 뿐만 아니라, items를 읽기 위한 Pydantic models 안에서 정의된 모든 data이다.
from pydantic import BaseModel
class ItemBase(BaseModel):
title: str
description: str | None = None
class ItemCreate(ItemBase):
pass
// 이 부분
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
// 이 부분
class User(UserBase):
id: int
is_active: bool
items: list[Item] = []
class Config:
orm_mode = True
Tip:
Now, Pydantic models에서 Item and User을 읽기 위해, 내부 Config class를 추가한다.
이 Config class는 Pydantic에 configuration을 제공하기 위해 사용된다.
Config class에서, attribute orm_mode True로 set 한다.
from pydantic import BaseModel
class ItemBase(BaseModel):
title: str
description: str | None = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
// 이 부분
class Config:
orm_mode = True
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: list[Item] = []
// 이 부분
class Config:
orm_mode = True
Tip:
이 방식으로, dict로부터 id value 만을 가져오기 위해 쓰인다:
id = data["id]
이것은 또한 속성으로부터 가져올 때 사용된다:
id = data.id
그리고 이것과 함께, Pydantic model은 ORMs와 호환되며, 그리고 내 Path operations에서 response_model argument에서 사용될 수 있다.
database model을 return 할 수 있고 이것으로부터 data를 읽을 수 있을 것이다.
SQLAlchemy and many others are by default "lazy loading"
이것은 예를 들어, data를 포함한 속성에 접근하려고 시도하지 않는 한 database로부터 relationships에 대한 data를 fetch 해오지 않는다는 것이다.
예를 들어, items 속성에 접근할 때:
current_user.items
would make SQLAlchemy go to the items table and get the items for this user, but not before.
orm_mode 없이, path operation으로부터 만약 SQLAlchemy model을 return한다면, relationship data를 포함하고 있지 않을 것이다.
Pydantic models 안에 그것의 relationship들을 선언해놓았더라도 말이다.
하지만 ORM mode로는, Pydantic 그 자체로 속성으로부터 필요한 data에 접근할 수 있다(dict로 가정하는 대신), return 하고 싶은 특정 data를 선언할 수 있고 이것은 갈 수 있고, ORM으로부터 얻을 수 있다.
file sql_app/crud.py를 보자
이 파일에서는 database 안에서 data와 상호작용하기 위해 재사용할 수 있는 함수를 가질 수 있다.
CRUD: Create, Read, Update, and Delete
Import Session from sqlalchemy.orm, 이것은 db parameter의 type을 선언하는 것을 허용하게 하고 더 나은 type check와 함수의 completion을 제공한다.
Import medels (the SQLAlchemy models) and schemas (the Pydantic models / schemas).
Create utility functions to:
from sqlalchemy.orm import Session
from . import models, schemas
// 이 부분
def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.id == user_id).first()
// 이 부분
def get_user_by_email(db: Session, email: str):
return db.query(models.User).filter(models.User.email == email).first()
// 이 부분
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all()
def create_user(db: Session, user: schemas.UserCreate):
fake_hashed_password = user.password + "notreallyhashed"
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
// 이 부분
def get_items(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Item).offset(skip).limit(limit).all()
def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
db_item = models.Item(**item.dict(), owner_id=user_id)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
Tip:
Now create utility functions to create data.
The steps are:
from sqlalchemy.orm import Session
from . import models, schemas
def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.id == user_id).first()
def get_user_by_email(db: Session, email: str):
return db.query(models.User).filter(models.User.email == email).first()
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all()
// 이 부분
def create_user(db: Session, user: schemas.UserCreate):
fake_hashed_password = user.password + "notreallyhashed"
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
def get_items(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Item).offset(skip).limit(limit).all()
// 이 부분
def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
db_item = models.Item(**item.dict(), owner_id=user_id)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
Info:
Tip:
Warning:
Tip:
**
item.dict())**
item.dict(), owner_id=user_id)이제 file sql_app/main.py
전에 만든 다른 부분을 포함하고 사용해보자
매우 간단한 방식:
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine
// 이 부분
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=list[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
Normally you would probably initailize your database (create tables, etc) with Alembic.
And you would also use Alembic for "migrations" (that's its main job).
A "migration" is the set of steps needed whenever you change the structure of your SQLAlchemy models, add a new attribute, etc. to replicate those changes in the database, add a new column, a new table, etc.
You can find an example of Alembic in a
FastAPI project in the templates from Project Generation - Template. Specifically in the alembic directory in the source code.
이제 dependency를 만들기 위해 sql_app/database.py 파일에서 만든 class인 SessionLocal class를 사용한다.
요청마다 독립적인 database session/connection (SessionLocal)을 가져야만 하고, 모든 요청과 요청을 통해 같은 session을 사용하고 요청들이 끝난 후에 close 해야만 한다.
이러기 위해, 우리는 이전에 Dependencies with yield에 대한 section에서 설명된 yield라는 new dependency를 만들어야 할 것이다.
우리의 dependency는 single request에서 사용될 new SQLAlchemy SessionLocal을 만들 것이다, 그리고 request가 끝나고 나면 close한다.
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
// 이 부분
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=list[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
Info:
그리고, path operation function의 dependency를 사용할 때, 우리는 SQLAlchemy로부터 직접 import한 Session type을 정의한다.
이것은 우리에게 path operation function 내부의 더 나은 editor를 지원한다, 왜냐하면 editor는 dp parameter가 type Session인 것을 알게 하기 때문이다:
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=schemas.User)
// 이 부분
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=list[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
이제, 최종적으로, 기준 FastAPI path operations code가 있다.
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=list[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
우리는 yield dependency 안에서 각각의 request 전에 database session을 만든다, 그리고 후에 이것을 닫는다.
그리고 우리는 session을 directly하게 얻어오기 위해 paht operaton function 안에서 요청된 dependency를 만들 수 있다.
이것과 함꼐, 우리는 path operation function의 내부로부터 직접적으로 crud.get_user를 호출할 수 있고 session을 사용할 수 있다.
Tip:
Tip:
[
schemas.Item]
같은 기준 Python type을 가진 response_models가 있다.여기엔 path operation function의 내부와 dependency의 안에서 SQLAlchemy를 사용하고 결국엔 이것을 보내고 외부의 database와 communicate 할 것이다.
이것은 잠재적으로 "waiting"을 요구할 수 있다.
그러나 SQLAlchemy는 직접적으로 waiting을 사용할 수 있는 호환성이 없다:
user = await db.query(User).first()
대신 이렇게 사용한다:
user = db.query(User).first()
그리고 우리는 async def 없이 path operation function과 dependency를 선언해야만 한다, 그저 normal def만 사용해서:
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
...
Info:
Very Technical Details:
SQLAlchemy를 직접적으로 사용하고 FastAPI와 함께 작동하기 위해 어떠한 plug-in도 필요하지 않기 때문에, we could integrate database migrations with Alembic directly.
And as the code related to SQLAlchemy and the SQLAlchemy models lives in separates independent files, you would even be able to perform the migrations with Alembiv without having to install FastAPI, Pydantic, or anything else.
The same way, you would be able to use the same SQLAlchemy models and utilities in other parts of your code that are not related to FastAPI.
For example, in a background task worker with Celery, RQ, or ARQ
Remember you should have a directory named my_super_project that contains a sub-directory called sql_app.
sql_app should have the following files:
__
init__.py: is an empty file.from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
# SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from .database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
items = relationship("Item", back_populates="owner")
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True)
title = Column(String, index=True)
description = Column(String, index=True)
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User", back_populates="items")
from pydantic import BaseModel
class ItemBase(BaseModel):
title: str
description: str | None = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: list[Item] = []
class Config:
orm_mode = True
from sqlalchemy.orm import Session
from . import models, schemas
def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.id == user_id).first()
def get_user_by_email(db: Session, email: str):
return db.query(models.User).filter(models.User.email == email).first()
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all()
def create_user(db: Session, user: schemas.UserCreate):
fake_hashed_password = user.password + "notreallyhashed"
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
def get_items(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Item).offset(skip).limit(limit).all()
def create_user_item(db: Session, item: schemas.ItemCreate, user_id: int):
db_item = models.Item(**item.dict(), owner_id=user_id)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=list[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post("/users/{user_id}/items/", response_model=schemas.Item)
def create_item_for_user(
user_id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)
):
return crud.create_user_item(db=db, item=item, user_id=user_id)
@app.get("/items/", response_model=list[schemas.Item])
def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = crud.get_items(db, skip=skip, limit=limit)
return items
You can copy this code and use it as is.
Then you can run it with Uvicorn:
uvicorn sql_app.main:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
And then, you can open your browser at http://127.0.0.1:8000/docs.
And you will be able to interact with your FastAPI application, reading data from a real database:
...