Warn on invalid argument ordering, or empty positional argument #1079

Closed
hangtwenty opened this Issue Feb 25, 2014 · 1 comment

Comments

Projects
None yet
2 participants

I love Fabric, and I love the scheme it's invented for specifying Python arguments and keyword arguments to various tasks from the command-line.

I was scripting around Fabric for some QA testing. A bash script constructs and executes Fabric calls like so:

FOO="foo"
CUSTOMER="test"
BAR_KWARG="bar=daffy"
BAZ_KWARG="baz=duck"

fab frobnicate:$FOO,customer=$CUSTOMER,$BAR_KWARG,$BAZ_KWARG

And the frobnicate task is something like...

@task(task_class=MyTaskClass)
def frobnicate(name, bar="donald", baz="duck", customer=None):
    pass

Later, to test a different case, I changed my script to be like this:

FOO="foo"
CUSTOMER="test"
BAR_KWARG=""  # !!! CHANGED THIS
BAZ_KWARG="baz=duck"

fab frobnicate:$FOO,customer=$CUSTOMER,$BAR_KWARG,$BAZ_KWARG

When I ran this script, it executed this command: fab frobnicate:foo,customer=test,,baz=duck. Fabric interpreted the values for this call like so:

argument value
name 'foo'
bar ''
baz 'duck'
customer 'test'

Why does bar equal ''? Slightly confusing but bear with me. In our call, fab frobnicate:foo,customer=test,,baz=duck, there are two arguments given with keywords, and two without. It seems Fabric partitions these and therefore, due to the little ,, chunk, there's an empty string positional argument... the 2nd positional argument received. In the Python function definition, bar is a keyword argument, but it is indeed the 2nd argument defined (see Python snippet above).

In a way this makes sense. It's doing what I say. But in another way, this is very confusing as a Python developer. In Python, yes, you can give call a function with positional arguments, even when the arguments you are providing are defined as keyword arguments. For example this is valid:

>>> def foo(a, b=None, c=None): pass
>>> foo(1, 2, 3)   # no error

BUT! This is the astonishment. At least in Python 2.7.x (what I am using), you cannot provide a positional argument AFTER a keyword argument.

>>> def foo(a, b=None, c=None): pass
>>> foo(1, 2, 3)
>>> foo(1, b=2, 3)
  File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg

I knew this property of Python. So I guess I thought, since Fabric wasn't complaining, that maybe it was stripping out my empty argument or something. Not so.

The effects of this confusion caused me to identify a false bug in our application code, and lead to a day or two of lost work. That's my fault. But I want to save someone else the trouble. There has to way to make this clearer.

What would be better? Could Fabric log a warning in this case? The warning would tell you that you've got positional arguments after keyword arguments. I think that could resolve the confusion for anyone else trying to call Fabric tasks from scripts, without changing behavior in a breaking way.

Owner

bitprophet commented Dec 7, 2015

Closing for now - v2 will be using a much more UNIX-y CLI functionality where the hacky comma-based posarg junk is no longer valid - meaning that it'll be impossible to accidentally submit empty posargs like this.

@bitprophet bitprophet closed this Dec 7, 2015

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