diff --git a/docs/ko/docs/tutorial/sql-databases.md b/docs/ko/docs/tutorial/sql-databases.md new file mode 100644 index 000000000..23c24c5ea --- /dev/null +++ b/docs/ko/docs/tutorial/sql-databases.md @@ -0,0 +1,679 @@ +# SQL (관계형) 데이터베이스 + +**FastAPI**에서 반드시 SQL(관계형) 데이터베이스를 사용해야하는 것은 아닙니다. + +하지만 원한다면 어떤 관계형 데이터베이스도 사용할 수 있습니다. + +이 문서에서는 SQLAlchemy를 사용한 예시를 보게 될 것입니다. + +SQLAlchemy가 지원하는 어느 데이터베이스든 쉽게 적용할 수 있습니다: + +* PostgreSQL +* MySQL +* SQLite +* Oracle +* Microsoft SQL Server 등. + +하나의 파일을 사용하고 파이썬이 통합된 지원을 제공하기 때문에, 이 예시에서는 **SQLite**를 사용할 것입니다. 따라서 동 예시를 복사해서 그대로 실행할 수 있습니다. + +추후 프로덕션용 응용 프로그램에는 **PostgreSQL**과 같은 데이터베이스 서버를 사용할 수 있습니다. + +!!! tip "팁" + **도커**를 기반으로 하고, 프론트엔드와 많은 도구들을 포함한 **FastAPI** 및 **PostgreSQL**을 사용하는 공식적인 프로젝트 생성기가 있습니다: https://github.com/tiangolo/full-stack-fastapi-postgresql + +!!! note "참고" + 대부분의 코드는 모든 프레임워크에서 사용하는 표준 `SQLAlchemy` 코드입니다. + + **FastAPI** 특정 코드는 언제나 그렇듯 많지 않습니다. + +## ORM(객체 관계 매핑) + +**FastAPI**는 모든 데이터베이스 및 모든 스타일의 라이브러리에서 동작하며 데이터베이스와 통신합니다. + +흔한 패턴 중 하나는 "객체 관계 매핑(ORM, object-relational mapping 라이브러리)"을 사용하는 것입니다. + +ORM에는 코드의 *객체*(*object*)와 데이터베이스 테이블(*"관계: relation"*)간 변환(*"매핑: mapping"*)을 하는 도구들이 있습니다. + +ORM을 사용하여, SQL 데이터베이스의 테이블을 나타내는 클래스와, 이름과 형을 가진 열을 나타내는 해당 클래스의 각 속성들을 생성합니다. + +예를들어 클래스 `Pet` 은 SQL 테이블 `pets` 을 나타낼 수 있습니다. + +그리고 해당 클래스의 각 인스턴스 객체는 데이터베이스의 행을 나타냅니다. + +예를들어 `Pet` 의 인스턴스인 `orion_cat` 객체는 `type` 열에 대해 `orion_cat.type` 속성을 가질 수 있습니다. 그리고 속성의 값은 일례로, `"cat"` 이 될 수 있습니다. + +이러한 ORM에는 테이블 또는 엔티티 사이의 연결이나 관계를 생성하는 도구들이 있습니다. + +이를 통해 `orion_cat.owner` 속성을 가질 수 있으며, owner(주인)은 *owners* 테이블로부터 가져온 pet(반려동물)의 owner(주인)에 대한 데이터를 포함합니다. + +따라서, `orion_cat.owner.name` 은 `owners` 테이블의 `name` 에서 가져온 반려동물 주인의 이름이 될 수 있습니다. + +이는 `"Arquilian"` 와 같은 값이 될 것입니다. + +그리고 ORM은 반려동물(pet) 객체에서 *주인(owners)* 테이블로 접근하려고 할 때 해당 테이블로부터 정보를 얻기 위한 모든 작업을 수행합니다. + +많이 사용되는 ORM에는 다음의 것들이 있습니다: Django-ORM (Django 프레임워크의 일부), SQLAlchemy ORM(프레임워크와 독립적인, SQLAlchemy의 일부), Peewee(프레임워크로부터 독립적). + +여기서 우리는 **SQLAlchemy ORM**을 사용해 작업하는 방법을 살펴볼 것입니다. + +비슷한 방법으로 다른 모든 ORM을 사용할 수 있습니다. + +!!! tip "팁" + Peewee를 사용하는 문서도 제공되고 있습니다. + +## 파일 구조 + +이 예시에서, 다음과 같은 구조를 가진 `sql_app`을 하위 디렉터리로 갖는 `my_super_project` 라는 디렉터리가 있다고 가정해보겠습니다: + +``` +. +└── sql_app + ├── __init__.py + ├── crud.py + ├── database.py + ├── main.py + ├── models.py + └── schemas.py +``` + +`__init__.py` 는 빈 파일이지만, 파이썬에게 `sql_app`과 이것의 모든 모듈들(파이썬 파일들)이 패키지라는 것을 알려줍니다. + +이제 각각의 파일/모듈이 어떤 일을 하는지 봅시다. + +## SQLAlchemy 부분 생성 + +`sql_app/database.py` 파일을 봅시다. + +### SQLAlchemy 부분 임포트 + +```Python hl_lines="1-3" +{!../../../docs_src/sql_databases/sql_app/database.py!} +``` + +### SQLAlchemy을 위한 데이터베이스 URL 생성 + +```Python hl_lines="5-6" +{!../../../docs_src/sql_databases/sql_app/database.py!} +``` + +이 예시에서, SQLite 데이터베이스와 "연결"합니다(SQLite 데이터베이스를 사용해 파일 열기). + +해당 파일은 `sql_app.db` 파일과 동일한 디렉토리에 위치할 것입니다. + +마지막 부분이 `./sql_app.db` 인 이유입니다. + +**PostgreSQL**을 사용하는 경우, 하기 행의 주석 처리를 제거하면 됩니다. + +```Python +SQLALCHEMY_DATABASE_URL = "postgresql://user:password@postgresserver/db" +``` + +그리고 데이터베이스 데이터 및 자격증명(credentials)을 조정하세요(MySQL, MariaDB, 기타 다른 데이터베이스도 동일). + +!!! tip "팁" + + 이것은 다른 데이터베이스를 사용하고자 할 때 수정해야하는 주된 행입니다. + +### SQLAlchemy `engine` 생성 + +첫번째로 해야할 일은 SQLAlchemy "엔진(engine)"을 생성하는 것입니다. + +추후 해당 `engine` 을 다른 곳에서도 사용할 것입니다. + +```Python hl_lines="8-10" +{!../../../docs_src/sql_databases/sql_app/database.py!} +``` + +#### 참고 + +다음 인수: + +```Python +connect_args={"check_same_thread": False} +``` + +는 `SQLite` 를 사용할 때만 필요합니다. 다른 데이터베이스에서는 필요하지 않습니다. + +!!! note "기술적 세부사항" + + 기본적으로 SQLite는 각 스레드가 독립적인 요청을 처리한다고 가정하고, 하나의 스레드만 통신하도록 허용합니다. + + 이것은 서로 다른 작업(서로 다른 요청)에 대해 뜻하지 않게 동일한 연결을 공유하는 것을 방지하기 위함입니다. + + 하지만 FastAPI에서, 일반적인 함수(`def`)를 사용하면 둘 이상의 스레드가 동일한 요청에 대해 데이터베이스와 상호작용하는 것이 가능하고, 따라서 `connect_args={"check_same_thread": False}` 를 사용해 SQLite가 이것을 허용하도록 설정할 필요가 있습니다. + + 또한, 각각의 요청은 의존성에서 자체적인 데이터베이스 연결 세션을 가지므로 기본 메커니즘이 필요하지 않습니다. + +### `SessionLocal` 클래스 생성 + +`SessionLocal` 클래스의 각 인스턴스는 데이터베이스 세션이 될 것입니다. 클래스 자체는 아직 데이터베이스 세션이 아닙니다. + +하지만 `SessionLocal` 클래스에 인스턴스를 생성하면, 해당 인스턴스는 실질적인 데이터베이스 세션이 됩니다. + +SQLAlchemy로부터 임포트하는 `Session` 과 구분하기 위해 `SessionLocal` 이라고 이름을 붙였습니다. + +SQLAlchemy로부터 임포트하는 `Session` 은 추후 사용할 것입니다. + +`SessionLocal` 클래스를 생성하기 위해, `sessionmaker` 함수를 사용합니다: + +```Python hl_lines="11" +{!../../../docs_src/sql_databases/sql_app/database.py!} +``` + +### `Base` 클래스 생성 + +이제 클래스를 반환하기 위해 `declarative_base()` 함수를 사용합니다. + +이후 각각의 데이터베이스 모델 또는 클래스(ORM 모델)를 생성하기 위해 해당 클래스로부터 상속받을 것입니다: + +```Python hl_lines="13" +{!../../../docs_src/sql_databases/sql_app/database.py!} +``` + +## 데이터베이스 모델 생성 + +`sql_app/models.py` 파일을 봅시다. + +### `Base` 클래스로부터 SQLAlchemy 모델 생성 + +SQLAlchemy 모델을 만들기 전에 우리가 생성한 `Base` 클래스를 사용합니다. + +!!! tip "팁" + SQLAlchemy에서는 데이터베이스와 상호작용하는 클래스 및 인스턴스를 "**모델**"이라고 지칭합니다. + + 하지만 Pydantic 또한 다른 것들을 지칭하기 위해 "**모델**"이라는 용어를 사용하는데, 여기서는 데이터 유효성 검사, 변환, 그리고 클래스 및 인스턴스의 문서화를 의미합니다. + +`database` (상기 `database.py` 파일)로부터 `Base` 를 임포트하십시오. + +이것을 상속받는 클래스들을 생성합니다. + +이 클래스들은 SQLAlchemy 모델들입니다. + +```Python hl_lines="4 7-8 18-19" +{!../../../docs_src/sql_databases/sql_app/models.py!} +``` + +`__tablename__` 속성은 각각의 모델들에 대한 데이터베이스 테이블 이름을 SQLAlchemy에게 알려줍니다. + +### 모델 어트리뷰트/열 생성 + +이제 모든 모델 (클래스) 어트리뷰트를 생성합니다. + +각 속성들은 해당 데이터베이스 테이블의 열을 나타냅니다. + +SQLAlchemy의 `Column` 을 기본값으로 사용합니다. + +그리고 `Integer` , `String` , `Boolean` 과 같이 데이터베이스에서 유형을 정의하는 SQLAlchemy 클래스 "형(type)"을 매개변수로 전달합니다. + +```Python hl_lines="1 10-13 21-24" +{!../../../docs_src/sql_databases/sql_app/models.py!} +``` + +### 관계 생성 + +이제 관계를 생성합니다. + +이를 위해 SQLAlchemy ORM에서 제공하는 `relationship` 을 사용합니다. + +이것은 이와 관계가 있는 다른 테이블의 값을 포함하는 "마법"과도 같은 속성이 될 것입니다. + +```Python hl_lines="2 15 26" +{!../../../docs_src/sql_databases/sql_app/models.py!} +``` + +`my_user.items` 로 `User` 의 `items` 속성에 접근하면, 이것은 `users` 테이블의 해당 레코드를 가리키는 외래키를 갖는 SQLAlchemy 모델인 (`items` 테이블의) `Item` 의 리스트를 갖게 됩니다. + +`my_user.items` 에 접근하면, SQLAlchemy는 실제로 데이터베이스로부터 `items` 테이블의 항목을 가져와 이 리스트에 그들을 넣습니다. + +그리고 `Item` 의 `owner` 속성에 접근하면, 이것은 `users` 테이블의 `User` SQLAlchemy 모델을 포함하게 됩니다. 이것은 `users` 테이블에서 어느 레코드를 가져올지 판단하기 위해 `owner_id` 어트리뷰트/열과 그것의 외래키를 사용합니다. + +## Pydantic 모델 생성 + +이제 `sql_app/schemas.py` 파일을 봅니다. + +!!! tip "팁" + SQLAlchemy의 *모델*과 Pydantic의 *모델* 사이의 혼란을 방지하기 위해, `models.py` 파일에서 SQLAlchemy 모델을, `schemas.py` 에서 Pydantic 모델을 관리합니다. + + 이 Pydantic 모델들은 유효한 데이터 형태인 "스키마(schema)"를 정의합니다. + + 따라서 이는 이는 이 둘을 사용할 때 오는 혼란을 방지할 수 있습니다. + +### Pydantic 초기 *모델* / 스키마 생성 + +데이터를 생성하거나 읽을 때 공통적인 속성을 갖도록 `ItemBase` 과 `UserBase` Pydantic *모델*(또는 "스키마")을 생성합니다. + +그리고 그들과 같은 속성을 갖도록 그들로부터 상속받은 `ItemCreate` 과 `UserCreate` 을 만들고, 생성을 위해 필요한 추가적인 데이터 (어트리뷰트)를 추가합니다. + +사용자는 생성될 때 `password` 도 갖게 될 것입니다. + +하지만 보안상의 문제로, `password` 는 다른 Pydantic *모델*에는 존재하지 않을 것입니다. 예를들어, 사용자 데이터를 읽는 API로부터 전송되지는 않습니다. + +```Python hl_lines="3 6-8 11-12 23-24 27-28" +{!../../../docs_src/sql_databases/sql_app/schemas.py!} +``` + +#### SQLAlchemy 스타일과 Pydantic 스타일 + +SQLAlchemy *모델*은 `=` 을 사용해서 속성을 정의하고, `Column` 매개변수에 형을 전달합니다: + +```Python +name = Column(String) +``` + +한편 Pydantic *모델*은 새로운 형 어노테이션 구문/형식 힌트인 `:`을 사용해 형을 선언합니다: + +```Python +name: str +``` + +`=` 과 `:` 을 헷갈리지 않기 위해 이것을 기억하십시오. + +### 읽고 반환하기 위한 Pydantic *모델* / 스키마 생성 + +이제 데이터를 읽고 API를 통해 반환할 때 사용되는 Pydantic *모델*(스키마)를 생성합니다. + +예를 들어, 항목을 생성하기 전에는 어떤 ID를 할당해야할지 모르지만, 해당 항목을 읽을 때(API를 통해 반환할 때) 우리는 그것의 ID를 알고 있을 것입니다. + +같은 방식으로, 사용자를 읽을 때, 우리는 해당 사용자에 속한 아이템을 포함하는 `items`를 선언할 수 있습니다. + +해당 아이템들의 ID뿐 아니라 아이템을 읽기 위해 정의한 모든 Pydantic *모델*의 데이터를 선언할 수 있습니다: `Item` . + +```Python hl_lines="15-17 31-34" +{!../../../docs_src/sql_databases/sql_app/schemas.py!} +``` + +!!! tip "팁" + API로부터 반환되어 사용자를 읽기 위해 사용되는 Pydantic *모델* `User` 은 `password` 를 포함하지 않는다는 것을 주의하세요. + +### Pydantic의 `orm_mode` 사용 + +이제, 읽기위한 Pydantic *모델*들인 `Item` 과 `User` 에 내부적인 `Config` 클래스를 추가합니다. + +이 `Config` 클래스는 Pydantic 환경 설정을 위해 사용됩니다. + +`Config` 에서, `orm_mode = True` 로 설정합니다. + +```Python hl_lines="15 19-20 31 36-37" +{!../../../docs_src/sql_databases/sql_app/schemas.py!} +``` + +!!! tip "팁" + `=` 를 사용해 다음과 같이 값을 할당한다는 것에 주의하십시오: + + `orm_mode = True` + + 형 선언 때처럼 `:`를 사용하지 않습니다. + + 이것은 설정값을 할당하는 것이지, 형을 선언하는 것이 아닙니다. + +Pydantic의 `orm_mode` 는 Pydantic *모델*에게 `dict` 가 아닌 ORM 모델(또는 다른 속성이 있는 임의의 객체)이어도 데이터를 읽을 것을 지시합니다. + +이 방법으로, `dict` 에서 `id` 값을 가져오기 위해 다음을 사용하는 대신: + +```Python +id = data["id"] +``` + +다음과 같은 방법으로 속성으로부터 값을 가져올 수 있습니다: + +```Python +id = data.id +``` + +이로써 Pydantic *모델*이 ORM과 호환되며, *경로 작동*의 `response_model` 인자로 선언할 수 있습니다. + +데이터베이스 모델을 반환받고 이로부터 데이터를 읽을 수 있습니다. + +#### ORM 모드에 대한 기술적 세부사항 + +SQLAlchemy와 많은 다른 ORM들은 자동적으로 "지연 로딩(lazy loading)"을 합니다. + +이는 당신이 해당 데이터가 포함된 어트리뷰트에 접근하지 않는 한 데이터베이스에서 관계에 관한 데이터를 가져오지 않는다는 것을 의미합니다. + +예를 들어, `items` 속성에 접근하면: + +```Python +current_user.items +``` + +SQLAlchemy는 그제서야 `items` 테이블에 가서 해당 유저에 속한 아이템들을 가져옵니다. + +`orm_mode`가 없다면, *경로 작동*으로부터 SQLAlchemy 모델을 반환할 때 관계에 대한 정보는 포함하지 않습니다. + +Pydantic 모델에서 해당 관계들을 선언한 경우라도 그러합니다. + +그러나 ORM 모드를 사용하면 Pydantic이 `dict` 로 가정하는 대신 어트리뷰트에서 필요한 데이터에 접근하려고 하기 때문에, 반환하고자 하는 특정한 데이터를 선언할 수 있으며 ORM에서도 데이터들을 가져올 수 있습니다. + +## CRUD 유틸리티 + +`sql_app/crud.py` 파일을 참고하십시오. + +이 파일에서는 데이터베이스의 데이터와 상호작용 하기 위한 재사용 가능한 함수들을 정의할 것입니다. + +**CRUD**는 다음의 것들을 의미합니다: 생성(**C**reate), 읽기(**R**ead), 업데이트(**U**pdate), 삭제(**D**elete). + +하지만 예시에서는 생성과 읽기만 다룹니다. + +### 데이터 읽기 + +`sqlalchemy.orm` 에서 `Session` 을 임포트하십시오. 이로써 `db` 매개변수의 형을 선언할 수 있으며 더 나은 형 체크 및 함수의 완성도를 기대할 수 있습니다. + +`models`(SQLAlchemy 모델)와 `schemas`(Pydantic *모델* / 스키마)를 임포트 하십시오. + +다음을 위한 유틸리티 함수들을 생성합니다: + +* ID와 이메일을 이용해 하나의 사용자 읽기 +* 다수의 사용자 읽기 +* 다수의 아이템 읽기 + +```Python hl_lines="1 3 6-7 10-11 14-15 27-28" +{!../../../docs_src/sql_databases/sql_app/crud.py!} +``` + +!!! tip "팁" + *경로 작동 함수*와는 별개로 데이터베이스와 상호 작용하는 데에만 사용되는 함수들(사용자 또는 아이템을 가져오는 함수)을 생성함으로써 여러 부분에서 이들을 쉽게 재사용할 수 있으며 단위 테스트를 추가할 수 있습니다. + +### 데이터 생성 + +데이터를 생성하는 유틸리티 함수를 생성합니다. + +단계는 다음과 같습니다: + +* 데이터와 함께 SQLAlchemy 모델 *인스턴스* 생성 +* 해당 인스턴스 객체를 데이터베이스 세션에 추가(`add`) +* 변경 사항이 저장될 수 있도록 데이터베이스에 커밋(`commit`) +* 생성된 ID와 같은, 데이터베이스의 새로운 데이터를 포함할 수 있도록 인스턴스 새로고침(`refresh`) + +```Python hl_lines="18-24 31-36" +{!../../../docs_src/sql_databases/sql_app/crud.py!} +``` + +!!! tip "팁" + `User` 을 위한 SQLAlchemy 모델은 안전하게 암호화된 비밀번호인 `hashed_password` 를 포함해야 합니다. + + 그러나 API 클라이언트가 제공하는 것은 암호화되지 않은 비밀번호이기 때문에, 그것을 추출한 다음 응용 프로그램에서 암호화된 비밀번호를 생성해야합니다. + + 그 다음, 해당 값을 `hashed_password` 인자에 전달하고 저장합니다. + +!!! warning "경고" + 비밀번호가 암호화되지 않았기 때문에 이 예시는 안전하지 않습니다. + + 실제 응용 프로그램에서는 비밀번호를 암호화하고 절대 플레인 텍스트로 저장하지 마십시오. + + 더 많은 정보가 필요하다면, 자습서의 보안(Security) 항목을 참고하십시오. + + 여기서는 데이터베이스의 도구와 역학에만 초점을 맞출 것입니다. + +!!! tip "팁" + 각각의 키워드 인자들을 `Item` 에 전달하고 이들 각각을 Pydantic *모델*로 읽는 대신, 다음과 같이 Pydantic *모델*의 데이터로 `dict` 를 생성할 수 있습니다: + + `item.dict()` + + 그다음 `dict`의 키-값 쌍들을 SQLAlchemy `Item`의 키워드 인자들로 전달합니다: + + `Item(**item.dict())` + + 이후 Pydantic *모델*이 제공하지 않는 여분의 키워드 인자인 `owner_id`를 전달합니다: + + `Item(**item.dict(), owner_id=user_id)` + +## Main **FastAPI** 응용 프로그램 + +이제 `sql_app/main.py` 파일에서 지금까지 작성한 모든 부분들을 통합하고 사용합시다. + +### 데이터베이스 테이블 생성 + +데이터베이스 테이블을 생성하는 매우 간단한 방법은 다음과 같습니다: + +```Python hl_lines="9" +{!../../../docs_src/sql_databases/sql_app/main.py!} +``` + +#### Alembic 참고사항 + +일반적으로 Alembic을 사용하여 데이터베이스를 초기화(테이블 생성 등)할 수 있습니다. + +또한 Alembic을 사용하여 "마이그레이션"을 할 수도 있습니다 (이것이 Alembic의 주요 역할이기도 합니다). + +"마이그레이션"이란 SQLAlchemy 모델 구조에 변화가 생기거나, 새로운 속성을 추가하는 등의 작업이 이루어질 때마다 그러한 변경사항들을 데이터베이스에 복제하거나, 새로운 컬럼 및 테이블을 추가하기 위해 필요한 단계의 집합입니다. + +FastAPI 프로젝트에서의 Alembic에 대한 예시를 [Project Generation - Template](../../../en/docs/project-generation.md){.internal-link target=_blank}의 템플릿들에서 찾을 수 있습니다. 소스코드의 `alembic` 디렉터리를 참고하십시오. + +### 의존성 생성 + +!!! info "정보" + 이 작업을 수행하기 위해, **파이썬 3.7** 이상의 버전을 사용하거나 **파이썬 3.6** 버전 사용시 "backports"를 설치하여야 합니다: + + ```console + $ pip install async-exit-stack async-generator + ``` + + 이것은 async-exit-stack과 async-generator를 설치합니다. + + 마지막에 설명할 "미들웨어"와 함께 다른 메서드들을 사용할 수도 있습니다. + +이제 의존성을 생성하기 위해 `sql_app/databases.py` 파일의 `SessionLocal` 클래스를 사용하십시오. + +각 요청마다 독립적인 데이터베이스 세션/연결 (`SessionLocal` )이 있고, 모든 요청에 대해 동일한 세션을 사용한 후 요청이 완료되면 종료해야 합니다. + +이후 다음 요청에 대해서는 새로운 세션이 생성될 것입니다. + +이를 위해, [`yield`를 사용한 의존성](../../../en/docs/tutorial/dependencies/dependencies-with-yield.md)에서 설명한 것과 같이 `yield` 를 사용해 새로운 의존성을 생성합니다. + +이 의존성은 하나의 요청에 대해서만 사용된 후 요청이 완료되면 종료되는 새로운 SQLAlchemy `SessionLocal` 을 생성할 것입니다. + +```Python hl_lines="15-20" +{!../../../docs_src/sql_databases/sql_app/main.py!} +``` + +!!! info "정보" + `SessionLocal()` 의 생성과 요청의 처리를 `try` 블록에 배치합니다. + + 그리고 `finally` 블록에서 이를 종료합니다. + + 이를 통해 요청 이후 데이터베이스 세션이 언제나 닫혀있다는 사실을 확실히 할 수 있습니다. 요청 처리 도중 예외가 발생한 경우라도 그러합니다. + + 그러나 종료 코드(`yield` 이후)에서 다른 예외를 발생시킬 수는 없습니다. 자세한 사항은 [`yield` 및 `HTTPException`을 사용한 의존성](../../../en/docs/tutorial/dependencies/dependencies-with-yield.md#dependencies-with-yield-and-httpexception)을 참고하십시오. + +그런 다음, *경로 작동 함수*에서 의존성을 사용할 때, SQLAlchemy로부터 직접 임포트한 `Session` 형으로 이것을 선언합니다. + +이를 통해 에디터가 `db` 매개변수가 `Session` 형임을 알 수 있으므로 경로 작동 함수 내부에서 더 나은 에디터 지원을 제공할 수 있게 됩니다: + +```Python hl_lines="24 32 38 47 53" +{!../../../docs_src/sql_databases/sql_app/main.py!} +``` + +!!! info "기술적 세부사항" + `db` 매개변수는 사실 `SessionLocal` 형이지만, ( `sessionmaker()` 로부터 만들어진) 해당 클래스가 SQLAlchemy `Session` 의 "프록시"이므로, 에디터는 어떤 메서드들이 제공되는지는 알지 못합니다. + + 하지만 `Session` 형으로 선언함으로써, 에디터는 사용 가능한 메서드들(`.add()`, `.query()`, `.commit()` 등)을 알 수 있고 (자동완성과 같은) 더 나은 지원을 제공할 수 있습니다. 형 선언이 실제 객체에 영향을 미치지는 않습니다. + +### **FastAPI** *경로 동작* 생성 + +드디어, 여기 표준 **FastAPI** 경로 작동 코드가 있습니다. + +```Python hl_lines="23-28 31-34 37-42 45-49 52-55" +{!../../../docs_src/sql_databases/sql_app/main.py!} +``` + +의존성 내부에서 `yield`를 사용해 각 요청 이전에 데이터베이스 세션을 생성하고 이후 종료합니다. + +그다음 해당 세션을 직접 가져오기 위해 *경로 작동 함수* 내부에서 필요한 의존성을 생성할 수 있습니다. + +이를 통해, *경로 작동 함수* 내부에서 `crud.get_user`를 직접 호출해서 해당 세션을 사용할 수 있습니다. + +!!! tip "팁" + 반환되는 값은 SQLAlchemy 모델이거나 SQLAlchemy 모델들의 목록이라는 점을 주의하세요. + + 하지만 모든 *경로 작동*이 'orm_mode'을 사용하여 Pydantic *모델* /스키마를 사용한 'response_model'을 가지고 있으므로 Pydantic 모델에 선언된 데이터는 이로부터 추출된 후 모든 일반 필터링 및 유효성 검사를 통해 클라이언트에게 반환됩니다. + +!!! tip "팁" + `List[schemas.Item]`과 같은 표준 파이썬 형을 가진 `response_models`가 있다는 것도 주의하세요. + + 그러나 `List`의 요쇼 / 매개변수는 'orm_mode'가 있는 Pydantic *모델*이기 때문에, 데이터는 문제 없이 클라이언트에게 반환됩니다. + +### `def` vs `async def` + +여기서 우리는 경로 작동 함수 내부와 의존성에서 SQLAlchemy 코드를 사용하고, 결과적으로 외부 데이터베이스와 통신합니다. + +이는 잠재적으로 "대기"를 필요로 할 수 있습니다. + +그러나 SQLAlchemy에서는 다음과 같은 직접적인 `await`의 사용이 불가합니다: + +```Python +user = await db.query(User).first() +``` + +대신 이렇게 사용합니다: + +```Python +user = db.query(User).first() +``` + +이후 `async def`가 아닌 일반적인 `def`를 사용해서 *경로 작동 함수*와 의존성을 다음과 같이 선언합니다: + +```Python hl_lines="2" +@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 "정보" + 관계형 데이터베이스에 비동기적으로 연결해야할 경우, [Async SQL (Relational) Databases](../../../en/docs/advanced/async-sql-databases.md){.internal-link target=_blank}를 참고하십시오. + +!!! note "매우 세부적인 기술적 사항" + 만약 당신이 호기심이 있고 깊은 기술적 지식을 갖고있을 경우, `async def` vs `def`가 어떻게 처리되는지에 대해 [Async](../../../en/docs/async.md#very-technical-details) 문서의 매우 세부적인 기술적 사항들을 확인해 보십시오. + +## 마이그레이션 + +우리는 SQLAlchemy를 직접적으로 사용하고 있으며 이것이 **FastAPI**와 동작하기 위해 어떠한 종류의 플러그인도 필요로하지 않기 때문에, 데이터베이스 마이그레이션을 Alembic과 직접 통합할 수 있습니다. + +그리고 SQLAlchemy와 관련된 코드와 SQLAlchemy 모델들이 분리된 독립적인 파일들에 존재하기 때문에, Alembic을 사용한 마이그레이션을 FastAPI, Pydantic 기타 어느 것도 설치하지 않고 수행할 수 있습니다. + +같은 방식으로, 동일한 SQLAlchemy 모델 및 유틸리티를 **FastAPI**와 관련이 없는 다른 부분의 코드에서도 사용할 수 있습니다. + +예를 들어, Celery, RQ, 또는 ARQ와 함께 백그라운드 작업자에서의 사용이 가능합니다. + +## 모든 파일 검토 + +`sql_app`을 하위 디렉토리로 갖는`my_super_project`디렉토리가 있어야 합니다. + +`sql_app`에는 다음의 파일들이 있습니다: + +- `sql_app/__init__.py`: 빈 파일 + +- `sql_app/database.py`: + +```Python +{!../../../docs_src/sql_databases/sql_app/database.py!} +``` + +* `sql_app/models.py`: + +```Python +{!../../../docs_src/sql_databases/sql_app/models.py!} +``` + +* `sql_app/schemas.py`: + +```Python +{!../../../docs_src/sql_databases/sql_app/schemas.py!} +``` + +* `sql_app/crud.py`: + +```Python +{!../../../docs_src/sql_databases/sql_app/crud.py!} +``` + +* `sql_app/main.py`: + +```Python +{!../../../docs_src/sql_databases/sql_app/main.py!} +``` + +## 확인하십시오 + +이 코드를 복사해서 그대로 사용할 수 있습니다. + +!!! info "정보" + + 사실 이 문서들의 대부분의 코드와 마찬가지로 여기에 표시된 코드는 테스트의 일부입니다. + +Uvicorn을 통해 실행합니다: + + +
+
+## 데이터베이스와 직접적인 상호작용
+
+FastAPI와 관계 없이 디버깅을 하거나, 테이블, 컬럼, 레코드를 추가하거나, 데이터를 수정하는 등의 작업을 위해 SQLite 데이터베이스(파일)를 직접 실행하고 싶다면, DB Browser for SQLite를 사용할 수 있습니다.
+
+그것은 다음과 같습니다:
+
+
+
+SQLite Viewer나 ExtendsClass와 같은 온라인 SQLite 브라우저를 사용할 수도 있습니다.
+
+## 미들웨어가 있는 대체 DB 세션
+
+**파이썬 3.7**을 사용하고 있지 않거나 **파이썬 3.6**에서 상기 언급한 “backports”를 설치할 수 없어 `yield`를 사용한 의존성을 사용할 수 없다면 유사한 방식으로 “미들웨어”에 세션을 설정할 수 있습니다.
+
+“미들웨어”는 요청이 있을 때마다 실행되는 함수로, 일부는 사전에, 일부는 함수의 종점 이후에 실행됩니다.
+
+### 미들웨어 생성
+
+우리가 추가할 미들웨어(단순한 함수)는 각 요청마다 새로운 SQLAlchemy `SessionLocal`을 생성, 요청에 이를 추가하고 요청이 완료되면 이를 종료할 것입니다.
+
+```Python hl_lines="14-22"
+{!../../../docs_src/sql_databases/sql_app/alt_main.py!}
+```
+
+!!! info "정보"
+ `SessionLocal()` 의 생성과 요청의 처리를 `try` 블록에 배치합니다.
+
+ 그리고 `finally` 블록에서 이를 종료합니다.
+
+ 이를 통해 요청 이후 데이터베이스 세션이 언제나 닫혀있다는 사실을 확실히 할 수 있습니다. 요청 처리 도중 예외가 발생한 경우라도 그러합니다.
+
+### `request.state`에 대해
+
+`request.state`는 각 `Request` 객체에 대한 프로퍼티입니다. 이 경우에서의 데이터베이스 세션과 같이 요청 자체와 연결된 임의의 객체를 저장하기 위해 존재합니다. `Request` 상태에 관한 Starlette 문서에서 더 자세한 사항을 확인할 수 있습니다.
+
+이 경우에서, 이것은 하나의 데이터베이스 세션이 모든 요청에 대해 사용되고 이후 (미들웨어에서) 종료되는 것을 보장합니다.
+
+### `yield`를 사용하는 의존성 또는 미들웨어와
+
+**미들웨어**를 추가하는 것은 `yield`를 사용하는 의존성이 하는 것과 유사하지만, 몇 가지 차이점이 존재합니다:
+
+* 더 많은 코드를 필요로하고 조금 더 복잡합니다.
+* 미들웨어는 `async` 함수여야 합니다.
+ * 내부에 네트워크를 “대기”하는 코드가 있다면, 이는 응용 프로그램을 "차단"하고 성능을 약간 저하시킬 수 있습니다.
+ * `SQLAlchemy`가 작동하는 방식에 문제가 되지 않을 수도 있습니다.
+ * 하지만 I/O 대기를 해야하는 코드를 미들웨어에 다수 추가하면 문제가 될 수 있습니다.
+* 미들웨어는 *모든* 요청시에 실행됩니다.
+ * 따라서 매 요청시 연결이 생성됩니다.
+ * *경로 작동*이 데이터베이스와 관련 없는 요청을 처리하는 경우에도 그렇습니다.
+
+!!! tip "팁"
+ `yield`를 사용하는 의존성만으로도 충분하다면, 이를 사용하는 것이 더 나을 수 있습니다.
+
+!!! info "정보"
+ `yield`를 사용하는 의존성은 **FastAPI**에 최근에 추가되었습니다.
+
+ 이전 버전의 자습서에서는 미들웨어에 대한 예시만 있었고 데이터베이스 세션 관리를 위해 미들웨어를 사용하는 여러 응용 프로그램이 있을 수 있습니다.
diff --git a/docs/ko/mkdocs.yml b/docs/ko/mkdocs.yml
index 0bf1a9713..6b3085f98 100644
--- a/docs/ko/mkdocs.yml
+++ b/docs/ko/mkdocs.yml
@@ -61,6 +61,7 @@ nav:
- tutorial/path-params.md
- tutorial/query-params.md
- tutorial/header-params.md
+ - tutorial/sql-databases.md
markdown_extensions:
- toc:
permalink: true