Open jd-solanki opened 3 years ago
Hey, not a Python nor Typer expert, but after searching for a few hours for my own needs, I came up with this type of solution https://gist.github.com/thibaud-opal/84eceafd67bf67361f5b194aafdd75bc
Not class methods per-se, but still allows you to declare commands that can access other instance methods. The drawback to this approach is that methods declared as commands are not accessible in the rest of the code, only as commands. But if yuu work in an object approach, that should not be an issue as your commands' role is only to parse input, delegate to services/classes and generate the output
EDIT: While redacting this, I also came to realize that it does not really make sense to declare instance methods as commands, as they are single entry-points into your app logic.
The static method mentioned in your comment is there because otherwise the function would have self
argument, that would be registered as required and you would need to pass something, when invoking commands. Here are more examples: https://github.com/captainCapitalism/typer-oo-example
I believe you could also play around with creating class that inherits from typer.Typer
, but self
argument will simply be registered as typer.Argument
. Other thing than using staticmethod
would be modifying arguments of the given command by popping self
from argument list and modifying the signature, but that is IMO much more hacky than staticmethod
. Turns out the way typer works is a curse here rather than a blessing.
And could you explain maybe why having staticmethods
does not work for you?
@captainCapitalism I didn't try using static methods. Also, I prefer using instance methods (which are usual also) rather than static methods.
Regards.
I have this problem where I have a program that queries a db, make some calculations and returns different answers based on the command issue. There are 3 main calculations, all of them need to connect to the db. In order to avoid repeating code I was thinking on a way to pass the same connection object to the 3 functions without having to explicitly connect in each of them. A class that initiates with the connection code will be an alternative if there were an easy way to expose methods as commands. Any ideas?
Currently, I think there are two straightforward methods for accomplishing this. Note that there are more complex solutions involving custom Typer and wrapper functions to trick Typer into ignoring self
.
The first method involves using static methods:
import typer
class MyKlass:
app = typer.Typer()
@app.command()
@staticmethod
def run():
print("Running")
if __name__ == "__main__":
MyKlass().app()
The second method registers commands in __init__
:
import typer
class MyKlass:
def __init__(self):
self.app = typer.Typer()
self.app.command()(self.run)
def run(self):
print("Running")
if __name__ == "__main__":
MyKlass().app()
The latter has the benefit of keeping MyKlass .run usable as a standalone function.
You can execute both scripts as follows:
$ python3 test.py myklass run
Running
Currently, I think there are two straightforward methods for accomplishing this. Note that there are more complex solutions involving custom Typer and wrapper functions to trick Typer into ignoring
self
. ...
tks @skycaptain, work's fine!
Follow an example for a pattern that can be implement to use in command class to "auto" get all commands methods. This way, if u have a lot of commands u don't need to add them manually(only if u have a specific value to inject :X)
I was need to implement it to inject values in my commands.
import inspect
import typer
class MyKlass:
some_default_value: str
def __init__(self, some_default_value: str):
self.some_default_value = some_default_value
self.app = typer.Typer()
for method, _ in inspect.getmembers(self, predicate=inspect.ismethod):
if not method.startswith('cmd'):
continue
cmd_name = method.strip('cmd_')
self.app.command(
name=cmd_name,
help=self.some_default_value
)(eval(f'self.{method}'))
def cmd_run(self):
print("Running")
def cmd_sleep(self):
print("sleep")
if __name__ == "__main__":
MyKlass(some_default_value="it's a command").app()
Usage: python -m legalops_commons.utils.foo [OPTIONS] COMMAND [ARGS]...
Options:
--install-completion Install completion for the current shell.
--show-completion Show completion for the current shell, to copy it or
customize the installation.
--help Show this message and exit.
Commands:
run it's a command
sleep it's a command
@danbailo That's a nice addition, but I would recommend two changes. First, avoid using eval. You could use the value returned by inspect.getmembers instead. Second, str.strip
trims characters, not a substring. So, cmd_build
would be trimmed down to buil
instead of build
. I think you were looking for str.removeprefix
. You can also use Typer's get_commandname function to obtain the same default formatting as Typer (for example, replacing `with
-`).
import typer
from typer.main import get_command_name
class MyKlass:
def __init__(self):
self.app = typer.Typer()
for method, func in inspect.getmembers(self, predicate=inspect.ismethod):
if not method.startswith("cmd_"):
continue
# Generate the command name from the method name
# e.g. cmd_build -> build
# e.g. cmd_pre_commit -> pre-commit
command_name = get_command_name(method.removeprefix("cmd_"))
self.app.command(name=command_name)(func)
def cmd_run(self):
print("Running")
First check
Description
How can I use Classes app in Typer and specific instance methods as commands?
The more easy question can be how can I implement the below code using class. Please note not all instance methods shall be a command.
Additional context
I checked https://github.com/tiangolo/typer/issues/306#issuecomment-889374055 but it has a static method.
Regards.