포스트

[FastAPI] <2 : 데이터 검증>

참고 문서: 공식문서

0. 이 글에서 설명할 것들

  • Pydantic
  • BaseModel
    • Optional
    • Union
    • Field

1. Pydantic

Pydantic 은 FastAPI에서 가장 널리 사용되는 검증 라이브러리이다. 공식 문서의 설명에 따르면 Pydantic은 빠르고 확장성이 좋으며, 여러 IDE에서 사용 가능하고 Python 3.8 버전 이상부터 사용 가능하다고 한다.

그 이외에도 왜 Pydantic을 사용해야 하는지 여러 이유를 대고 있는데 주목해볼만한 것은 type hint를 적극적으로 사용한다는 것이다.

1.1 Type hint

The schema that Pydantic validates against is generally defined by Python type hints.

Type hints are great for this since, if you’re writing modern Python, you already know how to use them. Using type hints also means that Pydantic integrates well with static typing tools like mypy and pyright and IDEs like pycharm and vscode.

Java와 달리 동적 언어인 Python은 변수의 선언 시 Type을 지정해 줄 필요가 없다. Python에서 변수는 컴파일 하는 시점이 아닌 실행시(런타임)에 Type이 정해진다. 그렇기 때문에 굉장히 편하다! 하지만 코드에 유지 보수가 필요할 정도로 사이즈가 거대해지면 이 장점은 타입 에러를 쉽게 발생시킬 수 있는 단점으로 변한다. 이 단점을 막기 위한 방법이 Type hint 이다.

1
2
3
id: int = 1
name: str = "jesus"
email: str = "abcde@naver.com"

Python에선 이와 같이 정적 언어처럼 변수 선언 시에 Type을 지정해주면 된다.

그렇다면 본격적으로 Pydantic을 사용해보자.

2. BaseModel

Pydantic에서 데이터의 검증은 BaseModel을 상속시켜서 진행한다. 예시를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pydantic import BaseModel
from typing import Dict, Any

class User(BaseModel):
	id: int
	name: str
	password: str
	sex: str = "default"
	email: str
	etc: Dict[str, Any]
	
data = {
	'id' : 3,
	'name' : 'kim minsu',
	'password' : 'abcddefg',
	'email' : 'abcd@gmail.com',
	'etc':{
		'a': 'AAA',
		'b': 'BBB',
		'c': 'CCC'
    }
}

user = User(**data)

print(user)

User class 안에 들어올 데이터들의 자료형을 type hint로 명시해주면 된다. sex 필드 처럼 값을 미리 지정해주는 것도 가능하다.

그렇다면 id의 값을 int가 아닌 str로 입력해보자.

1
2
3
4
5
6
7
8
9
10
11
data = {
	'id' : '3',
	'name' : 'kim minsu',
	'password' : 'abcddefg',
	'email' : 'abcd@gmail.com',
	'etc':{
		'a': 'AAA',
		'b': 'BBB',
		'c': 'CCC'
    }
}

문자열 ‘3’ 을 입력했을 경우엔 알아서 변환된다. int의 경우정수로 변환이 가능한 값이면 자동으로 변환해준다.

그렇다면 정수로 변환 불가능한 문자를 넣어보자.

1
2
3
4
5
6
7
8
9
10
11
data = {
	'id' : 'hi',
	'name' : 'kim minsu',
	'password' : 'abcddefg',
	'email' : 'abcd@gmail.com',
	'etc':{
		'a': 'AAA',
		'b': 'BBB',
		'c': 'CCC'
    }
}

정수 변환이 불가능한 문자의 경우 오류를 내준다. 개인적으로 pydantic의 에러 메시지가 오류를 찾기 편하게 해주는 것 같다.

Pydantic에서는 Python 표준 라이브러리에 있는 거의 모든 공통 타입들을 지원하기 때문에 사용에 아무 불편함이 없다. 이하의 토글을 확인해 지원하는 타입을 확인해보자.

지원 타입
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- bool
- int
- float
- str
- bytes
- list
- tuple
- dict
- set
- frozenset
- datetime.date
- datetime.time
- datetime.datetime
- datetime.timedelta
- typing.Any
- typing.TypeVar
- typing.Union
- typing.Optional
- typing.List
- typing.Tuple
- typing.Dict
- typing.Set
- typing.FrozenSet
- typing.Sequence
- typing.Iterable
- typing.Type
- typing.Callable
- typing.Pattern
- ipaddress.IPv4Address
- ipaddress.IPv4Interface
- ipaddress.IPv4Network
- ipaddress.IPv6Address
- ipaddress.IPv6Interface
- ipaddress.IPv6Network
- enum.Enum
- enum.IntEnum
- decimal.Decimal
- pathlib.Path
- uuid.UUID
- ByteSize

2.1 원하는 필드를 선택사항으로 만들고 싶은 경우. Optional

많은 사용자 입력 폼을 봤을 때, 필수로 입력해야 하는 항목도 있지만 원하지 않는다면 입력하지 않아도 되는 항목도 있다. 그 부분은 어떻게 구현할까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pydantic import BaseModel
from typing import Dict, Any, Optional

class User(BaseModel):
	id: int
	name: str
	password: str
	sex: Optional[str] = None
	email: str
	etc: Dict[str, Any]
	
data = {
	'id' : 'hi',
	'name' : 'kim minsu',
	'password' : 'abcddefg',
	'email' : 'abcd@gmail.com',
	'etc':{
		'a': 'AAA',
		'b': 'BBB',
		'c': 'CCC'
    }
}

user = User(**data)

print(user)

typingOptional 을 import 하여 구현하면 된다. Optional 안에 들어올 수 있는 자료형을 입력한 후, 기본값을 입력해주면 된다.(여기선 None이 기본값이다.)

2.2 원하는 필드에 2개 이상의 자료형을 입력받고 싶은 경우. Union

예를 들어 tax 항목이 있다고 생각해보자.

기본 단위가 천원인 경우 1000원의 세금을 내면 1이 입력값으로 들어온다.

900원의 세금을 낸다면 0.9를 입력받아야 하는데 1은 int, 0.9는 float이다. 어떻게 해결할까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pydantic import BaseModel
from typing import Dict, Any, Union

class User(BaseModel):
	id: int
	name: str
	password: str
	tax: Union[int, float]
	email: str
	etc: Dict[str, Any]
	
data = {
	'id' : 5,
	'name' : 'kim minsu',
	'password' : 'abcdde',
	'tax' : 0.9,
	'email' : 'abcd@gmail.com',
	'etc':{
		'a': 'AAA',
		'b': 'BBB',
		'c': 'CCC'
    }
}

user = User(**data)

print(user)

오류가 나지 않고 제대로 출력된다.

2.3 사용자가 제대로 된 값을 입력했는지 확인하고싶어. Field

몇몇 입력값들은 정해진 형식이란 것이 있다.

예를 들어 email의 경우 반드시 @ 문자가 중간에 들어가야 한다.

이름의 길이는 1문자 이상, 20문자 이하로 하고싶다.

나이는 0세 이상 99세 이하로 하고싶다.

이런 검증은 Field를 import 해 수행할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pydantic import BaseModel, Field
from typing import Dict, Any

class User(BaseModel):
	id: int
	name: str = Field(min_length=1, max_length=20)
	password: str
	age: int = Field(ge = 0, le = 99)
	email: str = Field(pattern=r"^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$")
	etc: Dict[str, Any]
	
data = {
	'id' : 5,
	'name' : 'kim minsu',
	'password' : 'abcdde',
	'age': '99' ,
	'email' : 'abcd@gmail.com',
	'etc':{
		'a': 'AAA',
		'b': 'BBB',
		'c': 'CCC'
    }
}

user = User(**data)

print(user)

모든 필드를 충족시켰다.

이번엔 100세 이상의 사람이 왔다고 해보자

1
2
3
4
5
6
7
8
9
10
11
12
data = {
	'id' : 5,
	'name' : 'kim minsu',
	'password' : 'abcdde',
	'age': 100,
	'email' : 'abcd@gmail.com',
	'etc':{
		'a': 'AAA',
		'b': 'BBB',
		'c': 'CCC'
    }
}

사진처럼 오류가 난다.

Field에서 사용할 수 있는 함수들은 다음과 같다.

  • min_length: 최소 길이
  • max_length: 최대 길이
  • gt: 설정된 값보다 큰 정수
  • ge: 설정된 값보다 크거나 같은 정수
  • lt: 설정된 값보다 작은 정수
  • le: 설정된 값보다 작거나 같은 정수
  • pattern: 정규식
  • multiple_of: 설정된 값의 배수
  • allow_inf_nan: -inf, inf, nan 값만 가능


이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.