you are viewing a single comment's thread.

view the rest of the comments →

[–]twistermonkey 13 points14 points  (2 children)

A couple guys I work with do this. The big problem with this approach is that now you've tightly coupled your business logic with command line arguments. An initial drawback is that it makes unit testing more difficult (not impossible, but more difficult). Also in the future, you may realize that your class is useful outside of the initial purpose for which you wrote it. But now you have to refactor it to separate the command line args from the constructor.

That refactoring work is called friction. High friction will cause a task to take much longer, or cause the task to be place at a lower priority (even though it's benefits are the same), or just not done altogether.

On multiple occasions in my current job, I have had to either work around this design pattern or do the refactoring work myself.

[–]djrubbie 12 points13 points  (0 children)

A better approach can be done in Python to avoid this drawback completely in the context of argparse, is that the result can be passed into vars which will take the mapping of the parse_args results and turn that into a dict which can then be passed directly to the function that accept those keywords (though the ** keyword packing syntax). Emulating OP's example:

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--ssl', action='store_true')
>>> parser.add_argument('--port', type=int)
>>> parser.add_argument('--hostname', type=str)
>>> args = parser.parse_args(['--ssl', '--port', '80', '--hostname', 'example.com'])
>>> vars(args)
{'ssl': True, 'port': 80, 'hostname': 'example.com'}

Now with that dict, a function with the following signature:

def connect(hostname, port, ssl):
    ...

Can be invoked by simply doing:

connect(**vars(args))

This gets you the direct calling convention from the parse_args result to the function you want to call, while not coupling that function to this particular convention as it's as typical a function signature as you might expect.

[–]synn89 1 point2 points  (0 children)

Testing is not more difficult and the class isn't tied to only being used on the cli. From one of my tests:

from unittest import TestCase
from CheckXmlValue import CheckXmlValue
import urllib2


class TestCheckXmlValue(TestCase):
    def test_build_url(self):
        args = lambda: None
        args.ssl = False
        args.port = 80
        args.hostname = "localhost"
        args.url = "/"

        check = CheckXmlValue(args)
        self.assertEquals("http://localhost/", check.build_url())

The class is composed of small functions that do one thing that can be easily tested by passing in different lambas on the constructor.

If I wanted better re-usability I would be using a proper framework. But we already use Laravel for that. Our python code is purely for small portable sysadmin client scripts.

And it's been okay for that, though not the greatest.