Friday, March 29, 2013

A Mistrust for Software as a Service

Burned twice in the same month, I've suddenly developed mistrust for software as a service. If I cannot load the software on a machine that I control, I'm going to think twice before I decide to use it.

 It started with Google Reader. I've used this as my primary method for reading RSS and Atom feeds for years. I've linked it up with Yahoo Pipes and IfThisThenThat to make a wonderfully tailored experience for myself. Each morning when I sit down to my computer, I have a news feed that has automatically filtered out the crap and shows me only that which has a high probability of being interesting.

Poof, it ends. Well, that should be a lesson to me about using free services. If I'm not paying for it, what should I expect? But wait, paying for a service doesn't seem to help as another service that I use extensively is going to vanish.

TinkerCad is 3D modeling done on the Web. It's brilliant. Rather, it was brilliant. They've just announced that as of April 30, they're done. The brilliance vanishes leaving me in the dark. I spent weeks honing my skills on this Web application and I've gotten very good at it. I have a Makerbot Replicator 2 and I use it extensively. TinkerCad was my primary means of making models. With the announcement of the end of TinkerCad, all my effort to learn it has gone "poof".

I will not replace these two services with services from someone else. I will choose to not use any new product from either Google or the people behind TinkerCad unless I can download and run the software on my own hardware. You burn me, I will not extend my trust again.

I'm examining all the services that I use and assessing the feasibility of dropping them before they drop me. I cannot see how any company could attain my trust for their service software offerings. At least these were personal projects. If I ran a company, I certainly wouldn't trust my revenue stream to any software as service offering without an explicit contract that guaranteed service availability for a predetermined amount of time.

 Nope, I'm not interested in a service that can be taken away at any random time.

Tuesday, March 05, 2013

Named Arguments for Configman

I'm proposing a small modification to Configman to add named arguments to its Swiss Army Knife feature set. Neglected in the first versions of Configman, I think this proposal will be very useful. So what I'm I talking about?
    $ some_app.py  --help
    usage:
        some_app.py [OPTIONS]...  arg1 [ arg2 [ arg3 ]]

It's the arg1, arg2, arg3 that I'm interested in here. Normally, you'd access these with sys.args without configman or config.args with configman. They come to the programmer as an uninterpreted list of strings. I'm proposing treating them just like switches. We should be able to define what is expected: the position, defaults, conversion functions, actions to take, just like the command line switches. The end result to the programmer should be something that looks like this:
    config = config_manager.get_config()
    print config.arg1, config.arg2, config.arg3

In other words, from the programmers perspective, they're just values passed into the program with the same access method and priority as command line switches. In fact, with a minor change to the configman Option object, they can be used to specify both switches and arguments:
    n.add_option(
        name='filename',
        doc='the name of the file',
        default=None,
        is_argument=True
    )
    n.add_option(
        name='action',
        doc='the action to take on the file',
        default='echo',
    )

Within the program, this could be accessed as:
    config = config_manager.get_config()
    with open(config.filename) as fp:
        do_something_interesting(fp)

From the users perspective, the command line can be used like this:
    $ some_app.py --help
    usage:
        some_app.py [OPTIONS]... filename
    OPTIONS:
        --action    the action to take on the file (default: echo)
        --filename  the name of the file

    $ some_app.py my_file.txt
    contents of my file

    $ some_app.py --action=upper my_file.txt
    CONTENTS OF MY FILE

    $ some_app.py --filename=my_file.txt --action=backwards
    elif ym fo stnetnoc

Notice that the actions can be specified as either positional arguments or as switches. If you do one then the other is automatically disallowed to prevent conflicts. By treating positional arguments in the same manner as switches, we get the benefit of configman's dependency injection. Say the first positional argument is the action and that corresponds with the name of a function. Because we specified the converter for the action argument to load a matching Python object from the scope, config.action will be a callable function:
    def echo(x):
        print x
    
    def backwards(x):
        print x[::-1]
    
    def upper(x):
        print x.upper()
    
    n = Namespace()
    n.add_option(
        'action',
        default=None,
        doc='the action to take [echo, backwards, upper]',
        short_form='a',
        is_argument=True,
        from_string_converter=class_converter
    )
    n.add_option(
        'text',
        default='Socorro Forever',
        doc='the text input value',
        short_form='t',
        is_argument=True,
    )
    c = ConfigurationManager(
        n,
        app_name='demo1',
        app_description=__doc__
    )
    try:
        config = c.get_config()
        config.action(config.text)
    except AttributeError, x:
        print "%s is not a valid command"
    except TypeError:
        print "you must specify an action"

Which will yield this user experience:
    $ demo1.py --help
    usage:
        demo1.py [OPTIONS]... action [ text ]
    OPTIONS:
        --action  the action to take [echo, backwards, upper]
        --text    the text input value
                  default: "Socorro Forever"

Notice that because the action has no default, action is required, there are no brackets around it in help. However, if you specify it as a switch, the necessity to use it as an argument goes away.
    $ demo1.py backwards "Configman is pretty cool"
    looc ytterp si namgifnoC
    $ demo1.py "Socorro uses Configman" --action=upper
    SOCORRO USES CONFIGMAN

I think this is pretty cool because it makes subcommands and subcommand help simple. The list of options and additional arguments are loaded dynamically from the classes (or functions) that the user specifies on the command line.
    $ socorro.py processor --help
    usage:
        socorro.py [OPTIONS]... command
    OPTIONS:
        --command   the socorro subsystem to start (default: processor)
        -- ...   all the options for processor

    $ socorro.py monitor --help
    usage:
        socorro.py [OPTIONS]... command
    OPTIONS:
        --command   the socorro subsystem to start (default: monitor)
        -- ...   all the options for monitor