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 request: tell fire to stop parsing argument and pass the remaining unparsed args to function #403

Open
link89 opened this issue Sep 14, 2022 · 2 comments

Comments

@link89
Copy link

link89 commented Sep 14, 2022

Feature Description

I am working on a tool to wrap some existed command line tools to make them easier to use (like auto install, inject argument automatically, etc). And I hope that I can have a way to tell Fire to stop parsing after encount some specific command.

Example
Here are some real world examples like poetry run python3 ./my-scripts.py or conda run -n my-env python3 ./my-scripts.py

Without this feature I can only put everything else as string, just like what -c option of bash does, e.g. bash -c "echo 'hello world'" , which could still work, but the user have to type extra string quote and dealing with escaping in some cases.

I am not sure if it is possbile to provide some decorator to archive this, for example

class Conda:
  def __init__(verbose=False):
    ...

  @fire.decorators.SkipParse
  def run(self, *argv):
    print(argv)

fire.Fire(Conda)

And when running with python fake-conda.py --verbose run which -a python3, the output should be ('which', '-a', 'python3')

@melsabagh
Copy link

melsabagh commented Oct 23, 2022

I think part of the problem here is that -a requires disambiguation so that Fire can tell whether it's supposed to be treated as a positional argument or an option. In POSIX, this is typically done by passing -- to indicate the end of options. However, Fire has its own custom flags that use the same syntax (e.g., -- --interactive) which limits what you can do with -- here.

A hacky workaround I have came up with in the past was to disable Fire's custom flags altogether and force -- to behave like POSIX. Something like:

#!/usr/bin/env python3

import fire


def _patch_fire():
    # Disable Fire's custom flags.
    def _SeparateFlagArgs(args):
        return args, []
    fire.core.parser.SeparateFlagArgs = _SeparateFlagArgs

    # Make -- behave like POSIX, i.e., separate positional args from options.
    orig__ParseKeywordArgs = fire.core._ParseKeywordArgs
    def _ParseKeywordArgs(*args, **kwargs):
        kwargs, remaining_kwargs, remaining_args = orig__ParseKeywordArgs(*args, **kwargs)
        if remaining_kwargs:
            try:
                ddash_index = remaining_kwargs.index('--')
                remaining_args.extend(remaining_kwargs[ddash_index + 1:])
                remaining_kwargs = remaining_kwargs[:ddash_index]
            except ValueError:
                pass
        return kwargs, remaining_kwargs, remaining_args
    fire.core._ParseKeywordArgs = _ParseKeywordArgs


_patch_fire()


class Conda:
    def __init__(self, verbose=False):
        ...

    def run(self, *argv):
        print(argv)


if __name__ == '__main__':
    fire.Fire(Conda)

Now you can get the output you expect:

$ python3 fake-conda.py --verbose - run -- which -a python3
('which', '-a', 'python3')

And you can pass options too as long as you remember to end the options with --. For example, if you change the Conda class above to:

class Conda:
    def __init__(self, verbose=False):
        ...

    def run(self, *argv, foo=None, bar=None):
        print(argv, foo, bar)

You can still do things like:

$ python3 fake-conda.py --verbose - run --foo --bar=10 -- which -a pytthon3
('which', '-a', 'python3') True 10

Of course this hack is too intrusive and will likely brick the moment Fire changes anything this code touches.. but it might be enough for your needs until a proper feature for this lands in Fire.

@link89
Copy link
Author

link89 commented Oct 23, 2022

Hi @melsabagh-kw Thank you for the sample code. I think it's a good idea to use -- to indicate the end of options, but that's not what I intended. I don't need to have fire to parse partial arguments and pass the remaining to the target function like the one in your sample code, but just stop parsing anything and pass all of the remining args to the target function.

def run(self, *argv, foo=None, bar=None):  # That would be too complicated to have fire handle partial of args
  pass
 
@fire.decorators.SkipParse
def run(self, *argv):  # that's the simple form I want to solve, by just stop paring anything
  pass

I have already make a PR for it and it works well in my use case to delegate arguments to the downstream commands. And this is the reason why I want this feature to create a tool to turn this

java -jar jenkins-cli.jar -s http://localhost:9090/ -webSocket list-jobs

into this

jenkins-fire-cli run list-jobs  

by wrapping the original command line to reduce some noise for the end user.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants