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

running typer cli command from python code #106

Closed
blnprasad opened this issue May 23, 2020 · 28 comments
Closed

running typer cli command from python code #106

blnprasad opened this issue May 23, 2020 · 28 comments
Labels
answered question Question or problem

Comments

@blnprasad
Copy link

How can i trigger/run typer cli specific command from python code. I guess i need to emulate command line arguments some where but not sure. Can you please help on this?

Cli:
python main.py goodbye --formal Camila

From Code:
i want to run specific command based on my need. how do i pass command line arguments, some thing like:
typer_app( "goodbye --formal Camila" )

@blnprasad blnprasad added the question Question or problem label May 23, 2020
@tiangolo
Copy link
Member

tiangolo commented Jun 6, 2020

A function in a Typer app is not modified by Typer, it's still a normal function. So, you should be able to call it directly as you normally would, just make sure you pass all the parameters to it.

@github-actions
Copy link

Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.

@justinpinkney
Copy link

What about in the case I'm using the typer.Option("default") syntax, now my function doesn't receive "default" as the default value, but instead a typer.models.OptionInfo object

Is there a way to deal with this?

@mirestrepo
Copy link

mirestrepo commented Sep 21, 2020

@justinpinkney Did you ever figure out a way to get the defaults correctly? Thanks in advance! My use case is to call the functions directly from the tests, but for every option that has a default I get something like <typer.models.OptionInfo object at 0x119787650> instead of the default value. I suppose if I don't use the typer.Option as a default it should work, but then I can take advantage of the --help etc

@justinpinkney
Copy link

@justinpinkney Did you ever figure out a way to get the defaults correctly? Thanks in advance! My use case is to call the suctions directly from the tests, by for every option that has a default I get something like <typer.models.OptionInfo object at 0x119787650> instead of the default value. I suppose if I don't use the typer.Option as a default it should work, but then I can take advantage of the --help etc

My problem exactly, didn't find a good way around this.

@ojomio
Copy link

ojomio commented Aug 10, 2021

Can this issue be re-opened? It seems to be a relevant one.

Right now if we want to call a function from code we have to write duplicate functions just for the Typer CLI with Typer defaults and counterparts with regular python defaults - very ugly (

@rec
Copy link

rec commented Sep 3, 2021

I am experiencing exactly the same issue.

The problem is hidden in this statement above: "A function in a Typer app is not modified by Typer, it's still a normal function. So, you should be able to call it directly as you normally would, just make sure you pass all the parameters to it."

(Italics mine.)

I want to call a Typer function which takes seven parameters, only one of which I want to modify.

So I need to pass in seven default arguments to get the one real argument in, and if I ever change any default, I now have to change it not just in the code but at every call site.

I can see how this would be fixed in Typer. It wouldn't be trivial, you'd have to use inspection on the function, and then decorate it based on that.

@ojomio
Copy link

ojomio commented Sep 4, 2021

Yes, so we literally write two functions - one with typer.Option and friends, which calls the second one, which only contains plain default values.

This way we get --help , type casting, enum checking at runtime and all what typer offers and still have a way to call the original function

The only problem is - you know - there are two functions

How a workaround for the problem we all have may look like- Typer.add_command automatically generates a plain version of decorated function and make it available through -say- .original attribute

@rec
Copy link

rec commented Sep 4, 2021 via email

@sherifattia
Copy link

The suggested workarounds make the code very ugly. It would be awesome if Typer can fix this

@nacitar
Copy link

nacitar commented Oct 19, 2021

Indeed, this shouldn't be closed. It's the biggest blemish this otherwise somewhat elegant library has.

sthagen added a commit to sthagen/arbejdstimer that referenced this issue Dec 29, 2021
sthagen added a commit to sthagen/arbejdstimer that referenced this issue Dec 29, 2021
@Spenhouet
Copy link

@tiangolo I agree that this issue should be reopened.
If one uses typer.Option the function also requires that and not the actual type.

@rec
Copy link

rec commented Jun 8, 2022

After thinking about the above for quite a while, I wrote a decorator that takes typer functions and automatically creates a callable dataclass with the same signature and proper defaults, which in many ways is even better, because you can easily write methods and properties.

The code is not long: dcommand.py.

Here's a test that shows how to use it.


I'm using this in production in a proprietary project and it's worked very well to reduce clutter and repetition, and to share code between typer and non-typer sides - and it lets me (more or less) unit test my typer commands, which is great.

I could easily productionize it into a separate pypi module, or prepare it for release into typer if there were any interest.

@rec
Copy link

rec commented Jun 17, 2022

I released this, with one more piece of functionality, as a PyPi module.

https://pypi.org/project/dtyper/

It's marked as beta, but I have complete test coverage and it's in use in a production project so far without issue.

@jonasjancarik
Copy link

jonasjancarik commented Aug 29, 2022

I released this, with one more piece of functionality, as a PyPi module.

https://pypi.org/project/dtyper/

It's marked as beta, but I have complete test coverage and it's in use in a production project so far without issue.

That looks great, I'll try it out.

For reference, I've been using this decorator function to get the defaults:

import inspect

def set_default_values(function):
    def wrapper(**kwargs):
        # first, check if any of the kwargs is not included in the signature of the function and raise an error if so
        for kwarg in kwargs:
            if kwarg not in inspect.signature(function).parameters:
                raise ValueError(f'{kwarg} is not a valid argument for {function.__name__}')

        new_kwargs = {}

        for default_kwarg in inspect.signature(function).parameters.values():
            if default_kwarg.name in kwargs.keys():
                new_kwargs[default_kwarg.name] = kwargs[default_kwarg.name]
            else:
                if isinstance(default_kwarg.default, typer.models.OptionInfo):
                    new_kwargs[default_kwarg.name] = default_kwarg.default.default
                else:
                    new_kwargs[default_kwarg.name] = default_kwarg

        return function(**new_kwargs)

    if __name__ == '__main__':
        return function()
    else:
        return wrapper


@set_default_values
@app.command ...

@AbeHandler
Copy link

I agree this is an issue which should be fixed

@lainisourgod
Copy link

@rec thank you! just putting @dtyper.function over my command works. Except one issue.

In typer you can require user to provide some option/argument by setting default to ellipsis (...). Usually you can put requried options in the mix with optional. E.g.

@dtyper.function
@app.command()
def main(
    city: str = Option("New York"),
    name: str = Option(...),
)

Now this code is invalid, emitting ValueError: non-default argument follows default argument emitting from lib inspect.

@tiangolo
Copy link
Member

I want to support and recommend Annotated, that will alleviate a lot of these problems.

@rec
Copy link

rec commented Nov 12, 2022

Now this code is invalid, emitting ValueError: non-default argument follows default argument emitting from lib inspect.

Arg, I am so sorry I just saw this comment from last week a few moments ago!!!

I guess it was part of this thread so I didn't look closely enough.

I filed an issue against the project rec/dtyper#4 and I will fix this perhaps tonight but otherwise tomorrow.

Sorry again!

@rec
Copy link

rec commented Nov 13, 2022

Hello again! This is fixed in dtyper 2.0.0 which I just released.

The major version number increase is because typer.Option arguments are now keyword-only, when before they were positional or keyword.

While this is technically a breaking change, it's unlikely that any real world code will be affected by this, and it fixes that bug rec/dtyper#4. Named arguments are always preferred in general anyway.

I think Arguments -> positional or keyword, Options -> keyword only is the clearest conceptual solution but open to arguments.


Thanks for finding this, I even had a unit test testing the wrong behavior!

If you file bugs right on https://github.com/rec/dtyper/issues, I'll be less likely to miss them.

@mirestrepo
Copy link

@tiangolo Do you mind elaborating more on how we can leverage Annotated in this case? Thank you!

@tiangolo
Copy link
Member

tiangolo commented Nov 17, 2022

@mirestrepo I haven't implemented it yet, but it will look more or less like this:

@app.command()
def main(
    city: Annotated[str, Option("New York")],
):
    print(city)

tilman19 added a commit to FraunhoferIVV/fastiot that referenced this issue Jan 2, 2023
Running the config command from run-tests will trigger  fastapi/typer#106 and we only have an object but not the default string.

Also using correct path to determine if new config needs to be generated before running unit tests.
@lainisourgod
Copy link

@tiangolo hi! do you intend to implement this feature in package? i mean natively running typer commands from python code as usual functions?

@rec
Copy link

rec commented Jan 27, 2023

The code to do it is here and here.

@rec
Copy link

rec commented Jan 27, 2023

If @tiangolo pointed me to the right place, I could easily emit a pull request to add this to typer.

@glass-ships
Copy link

+1 for this! any plans to implement?

@starwalker00
Copy link

+1 for this ! Still not a dead topic imo.

@trymzet
Copy link

trymzet commented Nov 28, 2023

It seems the solution proposed by Sebastián was implemented in the mean time and is reflected in the docs -- see https://typer.tiangolo.com/tutorial/options/help/

arneso-ssb added a commit to statisticsnorway/ssb-project-cli that referenced this issue Jan 10, 2024
- Solves wrong type for optional parameters
  fastapi/typer#106
- Add testcases for --no-kernel option
arneso-ssb added a commit to statisticsnorway/ssb-project-cli that referenced this issue Jan 10, 2024
- Solves wrong type for optional parameters
  fastapi/typer#106
- Add testcases for --no-kernel option
arneso-ssb added a commit to statisticsnorway/ssb-project-cli that referenced this issue Jan 10, 2024
- Solves wrong type for optional parameters
  fastapi/typer#106
- Add testcases for --no-kernel option
arneso-ssb added a commit to statisticsnorway/ssb-project-cli that referenced this issue Jan 10, 2024
- Solves wrong type for optional parameters
  fastapi/typer#106
- Add testcases for --no-kernel option
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
answered question Question or problem
Projects
None yet
Development

No branches or pull requests