FastAPI 3

태그: ,

카테고리: ,


출처 : FastAPI

🚫 아래 내용은 주관적인 생각이므로 사실과 다를 수 있습니다.



Request Body

  • 클라이언트에서 API로 데이터를 전송할 때, request body로 보낸다
  • API에서 클라이언트로 데이터를 전송할 때는 response body로 보낸다
  • API는 대부분의 경우 response body를 보내야하지만, 클라이언트는 그렇지 않다
  • request body를 선언하려면 Pydantic 모델들을 쓰면 된다
  • HTTP GET 메소드로는 데이터를 보내지 않는것을 권장한다

Import Pydantic’s BaseModel

  • 일단 pydantic에서 BaseModel을 임포트한다
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item

Create your data model

  • 그 후엔 임포트한 BaseModel을 상속받아 데이터 모델을 선언한다
  • 필드 타입들은 파이썬 기본 타입들을 사용한다
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item

  • 쿼리 파라미터를 선언했을때와 같이,
    모델 필드가 기본값을 가지면 비필수 항목이 된다
  • 옵셔널로 선언하고 싶다면 기본값을 None으로 하면 된다
  • 위의 모델은 JSON object나 파이썬의 dict 처럼 선언된다
      {
          "name": "Foo",
          "description": "An optional description",
          "price": 45.2,
          "tax": 3.5
      }
    
  • “description”과 “tax”는 기본값이 있으므로 아래처럼 생략해도 된다
      {
          "name": "Foo",
          "price": 45.2
      }
    

Declare it as a parameter

  • 선언한 데이터 모델은 패스/쿼리 파라미터와 같이 사용하면 된다 ```py from typing import Optional

from fastapi import FastAPI from pydantic import BaseModel

class Item(BaseModel): name: str description: Optional[str] = None price: float tax: Optional[float] = None

app = FastAPI()

@app.post(“/items/”) async def create_item(item: Item): return item


### Results
- 이 정도 만으로도 FastAPI는 아래의 효과를 낸다
  - request body를 JSON처럼 읽음
  - 자동 형변환(필요시)
  - 데이터 검증
    - 데이터가 유효하지 않을 경우, 깔끔-명확한 에러를 반환
    - 정확히 어디서 뭐가 잘못된건지 알려줌
  - 파라미터로 받은 값을 보여줌
    - 선언한 그대로의 모든 필드와 타입들을 에디터에서 지원함
  - 모델의 JSON 스키마 생성, 프로젝트 어디에서든 사용 가능
  - 그렇게 생성된 스키마들은 자동생성 API문서의 일부분으로 사용된다

### Automatic docs
- 모델 스키마들을 이용해 API문서를 자동으로 생성해줌

### Editor support
- Pydantic 모델 대신 dict 타입으로 받지만 않으면,  
  에디터 어디에서든 타입 자동완성 추천 목록으로 작동한다
- 추가로 타입별 연산자 오류 알림 또한 작동함
- 우연히 발생한게 아니고, 프레임워크 전반에 걸쳐 이런 디자인이 반영됨

### User the model
- 함수 안에서, 모델 객체의 모든 필드에 직접적인 접근이 가능함
  ```py
    from typing import Optional

    from fastapi import FastAPI
    from pydantic import BaseModel


    class Item(BaseModel):
        name: str
        description: Optional[str] = None
        price: float
        tax: Optional[float] = None


    app = FastAPI()


    @app.post("/items/")
    async def create_item(item: Item):
        item_dict = item.dict()
        if item.tax:
            price_with_tax = item.price + item.tax
            item_dict.update({"price_with_tax": price_with_tax})
        return item_dict

Request body + path parameters

  • 패스 파라미터와 request body는 동시 사용이 가능하다
  • FastAPI는 패스 파라미터의 값은 경로에서 읽어오고,
  • Pydantic 모델들은 request body에서 읽어온다 ```py from typing import Optional

from fastapi import FastAPI from pydantic import BaseModel

class Item(BaseModel): name: str description: Optional[str] = None price: float tax: Optional[float] = None

app = FastAPI()

@app.put(“/items/{item_id}”) async def create_item(item_id: int, item: Item): return {“item_id”: item_id, **item.dict()}


### Request body + path + query parameters
- 물론 request body, 패스/쿼리 파라미터 전부 동시 사용도 가능
- FastAPI가 적절한 곳에서 알아서 잘 읽어온다
```py
from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None


app = FastAPI()


@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Item, q: Optional[str] = None):
    result = {"item_id": item_id, **item.dict()}
    if q:
        result.update({"q": q})
    return result
  • 함수 파리미터는 다음과 같이 인식합니다
    • 파라미터가 경로에도 선언되어 있는지 - 있다면 패스 파라미터
    • 파라미터가 단수형 타입인지(int, float, str, bool, …) - 쿼리 파라미터
    • 파라미터가 Pydantic 모델 타입으로 선언되어 있는지 - request body

Without Pydantic



Query Parameters and String Validations

  • FastAPI는 파라미터의 추가적인 정보/타입 검증의 선언이 가능함 ```py from typing import Optional

from fastapi import FastAPI

app = FastAPI()

@app.get(“/items/”) async def read_items(q: Optional[str] = None): results = {“items”: [{“item_id”: “Foo”}, {“item_id”: “Bar”}]} if q: results.update({“q”: q}) return results

- 쿼리 파라미터 'q'는 Optional[str] 타입이다
- 'q'는 str 타입일 수도, None일 수도 있다
- 기본값은 None이다
- FastAPI는 'q'를 비필수로 처리한다

### Additional validation
- 'q'가 비필수 파라미터라도 입력이 된다면 길이를 50으로 제한할거다

#### Import Query
- 제한하려면 우선 fastapi의 Query를 임포트한다
```py
from typing import Optional

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Optional[str] = Query(None, max_length=50)):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

Use Query as the default value

  • 위와 같이 파라미터의 검증값을 설정할 때 Query를 사용하면,
  • 기본값 또한 함께 설정이 가능하다(위의 경우 기본값 None)
    q: Optional[str] = Query(None)
    # equals to
    -> q: Optional[str] = None
    

Add more validations

  • 여러개의 검증값을 동시에 사용하는 것도 가능하다
    @app.get("/items/")
    async def read_items(q: Optional[str] = Query(None, min_length=3, max_length=50)):
    

Add regular expressions

  • 정규표현식도 가능
    q: Optional[str] = Query(None, min_length=3, max_length=50, regex="^fixedquery$")
    

Default values

  • None 외의 값도 기본값으로 설정 가능하다
    q: str = Query("fixedquery", min_length=3)
    
  • 기본값을 가진다는 것은 비필수 파라미터가 된다는 뜻이기도 하다

Make it required

  • 필수 파라미터로 설정하려면 기본값을 설정하지 않으면 된다
  • Query의 경우는 ‘…‘를 사용한다
    q: str = Query(..., min_length=3)
    

Query parameter list / multiple values

  • 아래와 같이 기재하면 쿼리 파라미터값을 명시적으로 다중/리스트로 지정할 수 있다
    @app.get("/items/")
    async def read_items(q: Optional[List[str]] = Query(None)):
        query_items = {"q": q}
        return query_items
    
  • 위 경우의 URL은 아래와 같다
    http://localhost:8000/items/?q=foo&q=bar
    
  • 함수 내부에서는 ‘q’가 리스트로 처리 되기 때문에 response는 아래와 같다
      {
          "q": [
              "foo",
              "bar"
          ]
      }
    
  • 위와 같이 리스트 타입의 쿼리 파라미터를 받고 싶다면
  • 명시적으로 Query를 사용해줘야만 한다
  • 그렇지 않으면 request body로 처리될 것이다

Query parameter list / multiple values with defaults

  • 아래처럼 리스트로 받는 파라미터에도 기본값 설정이 가능하다
    async def read_items(q: List[str] = Query(["foo", "bar"])):
    
Using list
  • List[타입] 대신 list를 써도 된다
    q: list = Query([])
    
  • 그러나 달랑 list만 쓴다면 리스트 내 항목들의 타입 검증은 하지 않는다

Declare more metadata

  • 파라미터에 대해 더 많은 정보를 기재할 수도 있다
  • 해당 정보는 자동생성된 API 문서와 기타 외부 도구에서 사용된다
  • OpenAPI 지원 레벨에 따라서 기재한 정보가 보이지 않을 수 있다
    @app.get("/items/")
    async def read_items(
      q: Optional[str] = Query(
          None,
          title="Query string",                     # 파라미터 타이틀
          description="Query string for the...",    # 파라미터 설명
          min_length=3,
      )
    ):
      results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
      if q:
          results.update({"q": q})
      return results
    

Alias parameters

  • 파라미터의 이름을 바꿔서 받을 수도 있다
    @app.get("/items/")
    async def read_items(q: Optional[str] = Query(None, alias="item-query")):
      results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
      if q:
          results.update({"q": q})
      return results
    
  • ‘alias’를 이용하면, ‘q’의 이름을 ‘item-query’로 바꿔 받을 수 있다

Deprecating parameters

  • Query의 옵션에서 deprecated=True를 이용하면,
  • 더 이상 사용하지 않는 파라미터를 표기할 수 있다
    q: Optional[str] = Query(None, deprecated=True)
    
  • 위와 같이 작성시 자동생성된 API 문서에서 deprecated 표식을 확인할 수 있다


Path Parameters and Numeric Validations

  • 쿼리 파라미터에서 검증과 추가 정보를 선언했던것처럼
  • 패스 파라미터에서도 가능하다

Import Path

  • 우선 fastapi에서 Path를 임포트한다 ```py from typing import Optional

from fastapi import FastAPI, Path, Query

app = FastAPI()

@app.get(“/items/{item_id}”) async def read_items( item_id: int = Path(…, title=”The ID of the item to get”), q: Optional[str] = Query(None, alias=”item-query”), ): results = {“item_id”: item_id} if q: results.update({“q”: q}) return results


### Declare metadata
- 추가 정보는 Query와 같은 방식으로 사용하면 된다
  ```py
  item_id: int = Path(..., title="The ID of the item to get")
  • 패스 파라미터는 경로의 일부분이기 때문에 항상 필요하다
  • 기본값 자리에 ‘…’ 대신 None을 넣더라도, 바뀌는건 없다
  • 패스 파라미터는 언제나 필수다

Order the parameters as you need

  • 기본값이 있는 파라미터와 없는 파라미터를 동시에 써야한다면
  • 아래와 같이 기본값이 없는 파라미터를 먼저 선언하도록 한다
    from fastapi import FastAPI, Path
      
    app = FastAPI()
      
      
    @app.get("/items/{item_id}")
    async def read_items(
        q: str, item_id: int = Path(..., title="The ID of the item to get")
    ):
        results = {"item_id": item_id}
        if q:
            results.update({"q": q})
        return results
    

Order the parameters as you need, tricks

  • 원하는 순서대로 선언하고 싶다면 ‘*‘를 사용하자 ```py from fastapi import FastAPI, Path

app = FastAPI()

@app.get(“/items/{item_id}”) async def read_items( *, item_id: int = Path(…, title=”The ID of the item to get”), q: str ): results = {“item_id”: item_id} if q: results.update({“q”: q}) return results

- 위 처럼 '*'를 첫 파라미터로 준다면 기본값 유무와 순서가 상관없어진다

### Number validations
- Query와 Path 모두 아래와 같은 방식으로 숫자 검증이 가능하다
```py
from fastapi import FastAPI, Path

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    *,
    item_id: int = Path(..., title="The ID of the item to get", gt=0, le=1000),
    q: str,
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

Number validations: greater than or equal

  • ‘ge’는 ~ 이상, greater than or equal의 약자이다
  • ‘gt’는 ~ 초과, greater than의 약자이다

Number validations: greater than and less than or equal

  • ‘le’는 ~ 이하, less than or equal의 약자이다
  • ‘lt’는 ~ 미만, less than의 약자이다

Number validations: floats, greater than and less than

  • 아래와 같이 소수도 가능하다 ```py from fastapi import FastAPI, Path, Query

app = FastAPI()

@app.get(“/items/{item_id}”) async def read_items( *, item_id: int = Path(…, title=”The ID of the item to get”, ge=0, le=1000), q: str, size: float = Query(…, gt=0, lt=10.5) ): results = {“item_id”: item_id} if q: results.update({“q”: q}) return results ```

댓글남기기