2

I have multiple Python scripts which use docopt.

My issue is that the available options for the two scripts differ slightly - one option is present in one script, but not the other.

I've included a minimum working example below.

If I run:

python main.py --num=7 --name=John

the script does not run, as --name=John is also passed to module1.py, where it is not a valid option.

With my actual script, I have several imports after docopt parses the arguments, and so I cannot simply move the docopt call to the bottom of the script (if __name__ == '__main__':). If I do this, the imports in the imported script never get called, and I get undefined name errors.

I have found a workaround, but I don't think it is good practice at all.

What I am doing is adding:

if __name__ == '__main__':
    arguments = docopt.docopt(__doc__, version=0.1)

just after the import docopt.

However, I believe that having two of these statements in a script is bad practice. I cannot think of any other workarounds at this time though.

Can someone suggest a better solution? Thanks in advance.

main.py

"""
main.py

Usage:
    main.py [--num=<num>] [--name=<name>] [--lib=<lib-dir>]
    main.py -h | --help
    main.py --version

Options:
    --num=<num>  A number
    --name=<name>  A name
    --lib=<lib-dir>  Path to the directory containing lib
    --version
"""
import docopt

arguments = docopt.docopt(__doc__, version=0.1)
library_path = os.path.abspath(arguments['--lib'])
sys.path.insert(1, library_path)
NUM = arguments['--num']

from other_file import x, y


from module1 import function


def main():
    print 'In main()'
    function()
    print NUM


if __name__ == '__main__':
    print '{} being executed directly'.format(__name__)
    main()

module1.py:

"""
module1.py

Usage:
    module1.py [--num=<num>] [--lib=<lib-dir>]
    module1.py -h | --help
    module1.py --version

Options:
    --num=<num>  A number
    --lib=<lib-dir>  Path to the directory containing lib
    --version
"""
import docopt

arguments = docopt.docopt(__doc__, version=0.1)
library_path = os.path.abspath(arguments['--lib'])
sys.path.insert(1, library_path)

NUM = arguments['--num']

from other_file import z


def main():
    print 'In main()'
    print NUM


def function():
    print 'In function in {}'.format(__name__)
    # print NUM


if __name__ == '__main__':
    print '{} being executed directly'.format(__name__)
    main()

EDIT:

I forgot to mention that the other_file module has many different versions. Because of this, one of the docopt options is the path to the file. This is then added to sys.path as follows:

library_path = os.path.abspath(arguments['--lib'])
sys.path.insert(1, library_path)

For this reason, the import of docopt in the global scope is needed to add the path to the other_file module to my system path.

The global variable (NUM below, DEBUG in my actual file) I can live without.

Matthew
  • 1,179
  • 2
  • 12
  • 16
  • Why can't all your contingent stuff be inside `main()` ? – khelwood Nov 27 '15 at 11:46
  • 1
    Arguments should be processed in the script that is invoked from the command line. A module should not read or process command line arguments. Change your modules to accept parameters that were parsed by the main script. – Andomar Nov 27 '15 at 11:51
  • I've updated my question as I forgot a key point in my minimum working example. I need to add the path to `other_file` to my system path. If I do this in `if __name__ == '__main__'`, then the import of that module also has to be here. If the import of `other_file` is in this section in both modules, then it will never be imported in one, leading to my undefined name error. – Matthew Nov 27 '15 at 12:36
  • @Andomar I agree. I don't wish for any other modules to process the command line arguments, however, by importing docopt and having `arguments = docopt.docopt(__doc__, version=0.1)` in both modules, both modules will attempt to parse the arguments. The reason for this line in the global scope has been outlined in my updated question. – Matthew Nov 27 '15 at 12:39

1 Answers1

2

The clean solution is to refactor your code so it doesn't rely on a global, neither in main.py nor module1.py:

"""
main.py

Usage:
    main.py [--num=<num>] [--name=<name>]
    main.py -h | --help
    main.py --version

Options:
    --num=<num>  A number
    --name=<name>  A name
    --version
"""
from other_file import x, y
from module1 import function


def main(num):
    print 'In main()'
    function(num)
    print num


if __name__ == '__main__':
    import docopt

    arguments = docopt.docopt(__doc__, version=0.1)
    NUM = arguments['--num']

    print '{} being executed directly'.format(__name__)
    main(NUM)

And:

"""
module1.py

Usage:
    module1.py [--num=<num>]
    module1.py -h | --help
    module1.py --version

Options:
    --num=<num>  A number
    --version
"""
from other_file import z


def main(num):
    print 'In main()'
    print num


def function(num):
    print 'In function in {}'.format(__name__)
    print num


if __name__ == '__main__':
    import docopt

    arguments = docopt.docopt(__doc__, version=0.1)
    NUM = arguments['--num']

    print '{} being executed directly'.format(__name__)
    main(NUM)
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118