fastapi / typer

Typer, build great CLIs. Easy to code. Based on Python type hints.
https://typer.tiangolo.com/
MIT License
15.44k stars 656 forks source link

[FEATURE] Support for common pydantic types #181

Open haizaar opened 3 years ago

haizaar commented 3 years ago

It will be great to add support for common pydantic types, namely EmailStr and HttpUrl. Possible?

The obvious use case is to pass and emails and HTTP URLs strings as command line parameters and being validated automagically - similar to what FastAPI does.

haizaar commented 3 years ago

P.S. @tiangolo many thanks for creating another gem!

gvoysey commented 2 years ago

I have immediate usecases for pydantic.FilePath, pydantic.DirectoryPath, and the pydantic.con- types (conint, confloat, ...)

lachaib commented 1 year ago

Hello, I encountered this issue while trying to use email, IP addresses and urls as parameters.

I did some experiments so I'm sharing thoughts:

My experiment was therefore not opinionated on supporting pydantic but rather supporting "any kind of str or int subclass". I changed get_click_type's

if annotated == str:
    return click.STRING

to a later

elif issubclass(annotated, str):
    return click.STRING

It is important to defer it as one of the latest cases, because it is not uncommon to have subclasses of Enum to be also subclasses of int or str see StrEnum coming in 3.11, which we want to still fall into click.CHOICE.

With this change in mind, we've got half of the original request: we can annotate our code with the actual expected data. What's missing is the automagical conversion to the expected type when the callback gets executed. To me, it's not a big deal to wrap it with @pydantic.validate_arguments decorator to get it done, and it avoids an unnecessary dependency.

All that being said, I would like to thank you @tiangolo for the great and inspirational work you do with your libraries ❤️ , and I'd really be eager to contribute if you accept my proposal of simplified implementation of this ticket.

Minipada commented 1 year ago

Hi I am trying to pass a URL as a parameter, hence check it is valid so this feature would be great. Any update on it? Thanks!

lachaib commented 1 year ago

Hello,

I've made a recent attempt to progress on the topic in order to propose a pull request. Unfortunately, I've discovered that some pydantic types, such as EmailStr or HttpUrl, which were previously subclasses of str are no longer so in pydantic 2. However, I've therefore dug a little bit further and realised that you may specify click_type in the parameter_info

Consider the following snippet:

app = Typer()

EmailArg = Annotated[EmailStr, Argument(click_type=click.STRING)]

@app.command("something_with_email")
@validate_arguments
def something(user_email: EmailArg):
    print(user_email)

if __name__=='__main__':
    app()

It's enough IMO to do the job, and if you want to go even further, it's possible to even plug a callback function to the argument to catch a pydantic error and return a typer Exception

def validate_email(ctx: Context, value: str):
    if ctx.resilient_parsing: # handling autocompletion
        return 
    try:
        return EmailStr._validate(value, None) # this one is a bit hacky but just for the sake of demo
    except ValueError as exc:
        raise BadParameter(str(exc)) from exc 

EmailArg = Annotated[EmailStr, Argument(click_type=click.STRING, callback=validate_email)]

@app.command("something_with_email")
def something(user_email: EmailArg):
    print(user_email)

however there are hacky parts here and there in code above, which I'd be delighted to not have to put in my production code, but I think we'd need a strong statement from @tiangolo or core team of this repository whether we want to go or not into adding pydantic as strong dependency for typer.

lachaib commented 9 months ago

Update on this ticket, I've found some free time to work a bit on a tryout for pydantic types integration while still keeping it an optional dependency. I'll write some more tests, and polish a bit the code before I submit something.

pypae commented 1 month ago

Note for people coming here from Google who really want pydantic support: There are extensions/alternatives to typer that enable pydantic types for Python CLIs. Here's a quick overview from most to least similar to typer:

pydantic-typer


feud

Feud is essentially a wrapper around Click that takes classes and functions with type hints and intelligently 'compiles' them into a ready-to-use Click generated CLI.

The author compares it to typer here:

Typer is a more complete library for building CLIs overall, but currently lacks support for more complex types, such as those offered by Pydantic.


cyclopts

From the author:

Cyclopts is what you thought Typer was. Cyclopts includes information from docstrings, supports more complex types (even Unions and Literals!), and includes proper validation support.

There is a detailed comparison between typer and cyclopts. Two main differences are: