Clize 3.0b1: An argument parser that draws a CLI from your function sigature
kaiser.yann at gmail.com
Wed Apr 29 03:55:42 CEST 2015
On Mon, 27 Apr 2015 at 20:28 Chris Angelico <rosuav at gmail.com> wrote:
> On Tue, Apr 28, 2015 at 12:45 PM, Yann Kaiser <kaiser.yann at gmail.com>
> > On Mon, 27 Apr 2015 at 17:04 Chris Angelico <rosuav at gmail.com> wrote:
> >> Interesting. I've also been working on a simpler arg handling module;
> >> maybe we can work together. The goals for mine are:
> >> * Each function should be as independent as possible.
> > In Clize, commands are also just regular functions which have the desired
> > amount of keyword-only parameters and annotations. They can still be run
> > individually, be tested, and so forth.
> To implement something like the examples I gave, though, you'd need to
> enumerate the subcommand functions at the end. I'd like to be able to
> not do that.
> My inspiration came partly from Flask. I can build up a web site with
> several separate files, where one file might define routes for all the
> human-accessible content (the stuff that's designed for a web browser)
> and a separate file could define the API endpoints (emitting JSON or
> something). There's no need to have a single gathering point that
> lists every function that needs to be called - they get squirreled
> away by the decorator.
I'm aware of the pattern, and I don't really like it, especially because it
gets weird when multiple modules are involved. You'd have to import modules
because they have a side-effect of adding stuff to a list rather than
import things so that you can put them in a list. In addition, if Clize
manages that list, it will be a global mutable object to take care of in
tests and so forth. That'll be rather yucky.
I like the straightforwardness of "names are imported or defined" -> "names
are passed to run" (what is passed to run *is* what you get) rather than
names are collected as side effect of this and that import then run looks
at them (well, I could explore what all those imports are doing...).
>> * Minimize repetition (of names, descriptions, etc)
> > I try to minimize repetition, but the reality of things is that a
> > name could be repeated up to 4 times in Clize:
> > * In the function's parameter list
> > * In the function's docstring
> > * If named explicitly, in a @-kwoargs decorator (not needed with Py3)
> > * In an annotate decorator (again, not needed with Py2 compatibility
> > desired)
> > Two of these wouldn't be relevant in a Python 3-only world (if it's just
> > shell script replacement, you can probably pretend to be in one for a
> > moment), the remainder being normal things in a function. So I'm not
> > too badly there. Descriptions are of course only written in the
> > :-)
> A lot of my projects are Py3-only. I have no problem with that.
> >> * Keep everything in the function signature
> > I started taking this at heart with 3.0, after seeing the rather
> > constructs that had to be used in Clize 2.x for specifying aliases and
> > converters. Now everything is annotations. Nothing in the docstring
> > specifies behavior, and it isn't even read at all until --help is
> > I intend to keep docstrings behavior-free because, well, they're strictly
> > for documentation IMO.
> > What I mean to say, is that I am definitely committed to keeping
> > in the function signature. Probably even more than you :-)
> There's a spectrum of convenience:
> 1) The actual function signature - information that already exists.
> Function and parameter names, basically.
> 2) Annotations, directly attached to the parameters.
> 3) Docstrings and decorators, adjacent to the 'def' statement.
> 4) Code elsewhere in the file.
> 5) Code or directives in a separate file.
> Some of Clize stretches as far as level 4, and that's what I'd like to
> pull up a bit. With docstringargs, the only things at level 4 are
> generic setup - importing the module (unavoidable) and "if name is
> main, do stuff" (also unavoidable unless you want to put in some major
> MAJOR magic).
> (I took this spectrum from the discussions surrounding PEP 484,
> incidentally. I'm sure you won't be even *considering* having crucial
> command-line parsing out in a separate file, but that's precisely what
> type-hint stub files are.)
> >> There's a demo file in the source repo, plus here are a couple of
> >> actual usage examples:
> >> https://github.com/Rosuav/LetMeKnow/blob/master/letmeknow.py
> >> https://github.com/MikeiLL/appension/blob/master/fore/database.py
> >> The latter is an existing module in an existing project, and it grew a
> >> command-line interface with minimal changes, eg:
> >> https://github.com/MikeiLL/appension/commit/566f195
> >> Can we merge our plans and make a single module that's more likely to
> >> be maintained long-term? No point over-duplicating!
> > Agreed. I'm open to have more maintainers and to take input, but I have
> > admit that at this stage of development, I'm quite attached to Clize's
> > existing codebase and features (although I'm always open to refactoring,
> > the test suite helps with that).
> If Clize can implement something comparably simple to what I'm doing
> in the above examples, I'd be happy to drop docstringargs in favour of
> it. Basically, I didn't like how argparse code looked:
> (That file actually has a deliberate bug in it, which I intended to
> use as a demo of 'git bisect', but so far, haven't had a chance to do
> that with my students yet.)
> Taking the 'put' subcommand as an example, we have these lines of code
> to handle the CLI:
> 2: generic boilerplate (import)
> 10: function signature
> 11-15: docstring
> 33-35: generic boilerplate (setup)
> 39: repetition of function name and purpose
> 40-41: repetition of function arguments, now with their purposes
> 48-51: generic boilerplate (execution)
> 53: repetition of function name
> 54: repetition of function name
> 60-61: generic boilerplate (execution)
> That's a lot of separate pieces. Here's the docstringargs equivalent:
> 1-2: generic boilerplate (import/setup)
> 10: simple decorator
> 11: function signature
> 12-16: docstring
> 30-31: generic boilerplate (execution)
> All the put-specific code is in one place - the signature, plus one
> marker line above it, plus the docstring. It's not scattered all up
> and down the entire program. Adding another command is simply a matter
> of creating a new function. THAT is an important part of what I'm
> looking for; I don't want to have to go dig through five different
> places when I want to add or remove a subcommand.
> Can you put together a Clize equivalent? I'm pretty sure it's going to
> be remarkably close to what I want.
It is pretty much the same save for the result formatting (clize prints
non-None results on its own then exits, although for your example I
understand the extra format step is necessary)
Annotated line numbers for "put": (PEP8 double-spacing increased the
1-6: Boilerplate: imports
16: Function signature
17-22: Documentation (parameter descriptions need to be in their own
paragraph in Clize's default helper)
37-39: Defining a wrapper just for reformatting the result
42-43: defining the execution point and adding the result format decorator
The result formatting could be better. Maybe run could be made not to exit
upon success (but still upon failure) and return the evaluated value rather
than print it, or apply decorators en masse. I definitely see the appeal in
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the Python-list