Created
July 7, 2022 01:16
-
-
Save rino0601/3c6fc43fa87d0d41fdf5840a007af0e5 to your computer and use it in GitHub Desktop.
Typer 에서 FastAPI 의 Depends 비슷한거 해보는 방법
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import Any, Optional, Union | |
import click | |
import pydantic | |
import typer | |
VERSION = "0.0.0" | |
# PATCH === https://github.com/tiangolo/typer/issues/111 === BEGIN | |
_get_click_type = typer.main.get_click_type | |
def supersede_get_click_type( | |
*, annotation: Any, parameter_info: typer.main.ParameterInfo | |
) -> click.ParamType: | |
if not hasattr(annotation, "parse_raw"): | |
return _get_click_type(annotation=annotation, parameter_info=parameter_info) | |
class CustomParamType(click.ParamType): | |
name = annotation.__name__ | |
def convert(self, value, param, ctx): | |
if isinstance(value, annotation): | |
return value | |
return annotation.parse_raw(value) | |
return CustomParamType() | |
typer.main.get_click_type = supersede_get_click_type | |
# PATCH === https://github.com/tiangolo/typer/issues/111 === END | |
class User(pydantic.BaseModel): | |
id: int | |
name: str = "Jane Doe" | |
@staticmethod | |
def from_context(ctx: typer.Context) -> 'User': | |
if ctx is None: # 이게 최상위다. 못 찾은 셈. | |
raise typer.BadParameter("암튼 잘못 입력함") | |
for param in ctx.params.values(): # 모든 params 를 다 뒤져서 | |
if isinstance(param, User): # 내가 원하던 타입이면 반환 | |
return param | |
# 못 찾았으면 부모쪽에서 한번 더 찾아보기 | |
return User.from_context(ctx.parent) | |
@staticmethod | |
def typer_callback(ctx: typer.Context, value: Union[str, 'User']) -> Optional['User']: | |
if ctx.resilient_parsing: | |
# 이게 필요한 이유는 아래 가이드를 참고하세요. | |
# https://typer.tiangolo.com/tutorial/options/callback-and-context/#handling-completion | |
return None | |
if isinstance(value, User): | |
# 사용자가 명시적으로 넘긴 경우 | |
# e.g) --user '{"id": "2"}' | |
return value | |
try: | |
# parent? command 쪽에 혹시 있는지 확인. (옵션 이름이 같아야 함.) | |
return User.from_context(ctx) | |
except typer.BadParameter: | |
# 새로 초기화. 할 필요가 있는 경우. | |
# 먼저 선언된 params 를 가져 올 수 있음. | |
_id = ctx.params['id'] | |
# 모자란 부분을 사용자에게 물어 볼 수 있음. | |
person_name = typer.prompt("What's your name?") | |
# 형 변환은 pydantic 이 적절히 해줄 것임 | |
return User.parse_obj(dict(id=_id, name=person_name)) | |
@staticmethod | |
def option(default): | |
return typer.Option(default, callback=User.typer_callback) | |
app = typer.Typer() | |
def version_callback(value: bool): | |
if value: | |
typer.echo(f"{VERSION}") | |
raise typer.Exit() | |
@app.callback() | |
def init( | |
version: Optional[bool] = typer.Option(None, "--version", callback=version_callback) | |
): | |
_ = version # pylint 를 속이기 위함 | |
@app.command(help="이해를 돕기 위한 사례 1") | |
def foo( | |
ctx: typer.Context, | |
id: int = typer.Option(None), | |
user: User = typer.Option(None, callback=User.typer_callback), # ... 을 default 로 넘기면 Option 이지만 Required 하게 할 수 있음 | |
): | |
_ = ctx, id # pylint 를 속이기 위함 | |
typer.echo(f"{user}") | |
sub = typer.Typer() | |
@sub.callback() | |
def bar( | |
user: User = User.option(User(id=999, name="provided_default_user")) | |
): | |
"""어거지 예시지만, parent 에서 뽑아온다는걸 보여주기 위한""" | |
_ = user | |
@sub.command() | |
def foo( | |
user: User = User.option(None), | |
): | |
typer.echo(f"{user}") | |
if __name__ == '__main__': | |
app.add_typer(sub) | |
app() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
각각의 차이를 관찰해보세요