python-poetry / cleo

Cleo allows you to create beautiful and testable command-line interfaces.
https://cleo.readthedocs.io
MIT License
1.29k stars 92 forks source link

The "call" and "call_silent" methods are not working as expected #130

Open ianrodrigues opened 2 years ago

ianrodrigues commented 2 years ago

I'm using this sample app.py:

from cleo.commands.command import Command
from cleo.application import Application
from cleo.helpers import argument

class FooCommand(Command):
    name = "foo"
    arguments = [argument("baz", "The baz argument.")]

    def handle(self):
        baz = self.argument("baz")
        self.line(baz)

class BarCommand(Command):
    name = "bar"

    def handle(self):
        self.call("foo", "baz")

application = Application()
application.add(FooCommand())
application.add(BarCommand())

if __name__ == "__main__":
    application.run()

When I'm calling the foo command itself, it works:

$ python app.py foo baz
baz

But when I'm calling the bar command, I'm getting this:

$ python app.py bar

Not enough arguments (missing: "baz")

If I replace self.call by self.call_silent that's the error:

$ python app.py bar

  AttributeError

  'builtin_function_or_method' object has no attribute 'bind'

  at ~/Library/Caches/pypoetry/virtualenvs/app-wSCSi1z6-py3.9/lib/python3.9/site-packages/cleo/commands/base_command.py:103 in run
       99│     def run(self, io: IO) -> int:
      100│         self.merge_application_definition()
      101│ 
      102│         try:
    → 103│             io.input.bind(self.definition)
      104│         except CleoException:
      105│             if not self._ignore_validation_errors:
      106│                 raise
      107│
qezz commented 2 years ago

Faced the same issue today. Found out that in call() cleo parses the arguments differently, e.g.

# trying to call command "x" with a required argument "value" as "100"
return_code = self.call("x", "100")

leads to the internal representation as

self._arguments: {'command': '100'} # not  `x`!

though, when I call it from the terminal, the representation is:

# ./app.py x 100
self._arguments: {'command': 'x', 'value': '100'}

Eventually, I made it work with the following hack:

return_code = self.call("x", "x 100")
# inner:
self._arguments: {'command': 'x', 'value': '100'}

So, apparently the first word of the arguments is meaningless when calling a command.

return_code = self.call("x", "<anything> 100")
# inner:
self._arguments: {'command': '<anything>', 'value': '100'}