Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Support for common pydantic types #181

Open
haizaar opened this issue Oct 22, 2020 · 7 comments · May be fixed by #723
Open

[FEATURE] Support for common pydantic types #181

haizaar opened this issue Oct 22, 2020 · 7 comments · May be fixed by #723
Labels
feature New feature, enhancement or request

Comments

@haizaar
Copy link

haizaar commented Oct 22, 2020

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 haizaar added the feature New feature, enhancement or request label Oct 22, 2020
@haizaar
Copy link
Author

haizaar commented Oct 22, 2020

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

@gvoysey
Copy link

gvoysey commented Jan 13, 2022

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

@lachaib
Copy link

lachaib commented Dec 1, 2022

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:

  • Currently Typer doesn't depend on pydantic, and supporting pydantic native types would add an extra dependency that not everyone may want
  • Besides Pydantic, users may want to use other kinds of data types that may just be subclasses of str, int or whatever plain data type.
  • Pydantic types are not consistent in the way you may do conversion to them. For instance, EmailStr.validate takes a simple value argument, and will return a regular str, while AnyHttpUrl will require FieldInfo and BaseConfig arguments on top.

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
Copy link

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
Copy link

lachaib commented Sep 19, 2023

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
Copy link

lachaib commented Dec 20, 2023

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.

lachaib added a commit to lachaib/typer that referenced this issue Dec 21, 2023
lachaib added a commit to lachaib/typer that referenced this issue Dec 21, 2023
@lachaib lachaib linked a pull request Dec 21, 2023 that will close this issue
lachaib added a commit to lachaib/typer that referenced this issue Dec 21, 2023
lachaib added a commit to lachaib/typer that referenced this issue Mar 26, 2024
lachaib added a commit to lachaib/typer that referenced this issue Mar 28, 2024
lachaib added a commit to lachaib/typer that referenced this issue Mar 28, 2024
lachaib added a commit to lachaib/typer that referenced this issue Mar 28, 2024
lachaib added a commit to lachaib/typer that referenced this issue May 12, 2024
lachaib added a commit to lachaib/typer that referenced this issue May 12, 2024
lachaib added a commit to lachaib/typer that referenced this issue May 17, 2024
lachaib added a commit to lachaib/typer that referenced this issue Jul 25, 2024
@pypae
Copy link

pypae commented Aug 21, 2024

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:

  • cyclopts uses the Annotation syntax exclusively, allowing most cyclopts functions to be callable from regular Python code as well. In contrast, typer also allows defining Options and Arguments using proxy default values for function parameters.
  • cyclopts doesn’t use click but implements its own parser.

lachaib added a commit to lachaib/typer that referenced this issue Oct 15, 2024
lachaib added a commit to lachaib/typer that referenced this issue Oct 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature, enhancement or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants