Hacker News new | past | comments | ask | show | jobs | submit login
Easy Python CLI with Click (codingwithricky.com)
56 points by ribab 64 days ago | hide | past | web | favorite | 61 comments

The thing that bugs me about click is that now your python script has a python dependency that you have to install every time you need to run the script. This is fine sometimes, but a lot of the time I want a script that will be usable with minimal setup, which is why I always use argparse, which is in the standard library, and gets the job done despite its quirky API.

This is more of a packaging problem. There are various ways to package python code with it's dependencies into a single executable.

In any case, comparing a stdlib library to a 3rd party one is a bit apples to oranges. Most people first decide whether or not they want to use pypi packages, and then start evaluating which ones are appropriate.

> Most people first decide whether or not they want to use pypi packages, and then start evaluating which ones are appropriate.

I don't think it's as binary as this. There should be, at least intuitively, a mild negative bias to adding a marginal dependency, even once you're dependent on pypi. We're lucky enough to not be dealing with a dumpster fire ecosystem like nodejs, but it's still a good habit.

For me, writing your interface with click doesn't seem too much better than writing it with argparse, certainly not enough to get over the (small) activation energy of a non-stdlib dependency

Click is multiple order of magnitude better than argparse. I don't even start a CLI app with argparse anymore, because even with very simple interfaces you will hit it's limitations. You should try click.

Interesting, thanks, I had just skimmed the posted article and it looked substantially similar to argparse, but I guess I'll take a closer look.

It is a bit clunky but haven’t hit a limit since Py 3.4 or so.

sure, if you're building a CLI to do management commands for an application that already has dependency management in place, that's all fine

but if you just have some basic script to scrape some logs or zip up files, its nice to have it be self contained

if it's a simple basic script, i wouldn't even go through the pain of using argparse. i would just manually check sys.argv.

You ALWAYS need a CLI framework at least for generating help automatically, otherwise I hate you when I want to use your frugal tool and I have to look into it to find out how it works.

In which case you would be using click. If I have any expectation that someone else is going to use my script I give it a REAME.md, a requirements.txt, a setup.py and I use click. It literally takes about 30's to do and you now have a cli interface indistinguishable from any other cli on your system.

I've generally found argparse to be worth it the minute that you need anything other than two args, whose usage implies an intuitive ordering (eg with mv)

Perhaps a non-starter for you if you don't want to involve Docker but I have had good experiences wrapping a docker invocation in a shell script shim with the same name as the program and forwarding everything I need into the container. Then folks just grab the shell script and they're off... added bonus it's really easy to add update functionality to your tools.

So you're replacing a single executable Python script with a dependency that requires four additional separate artefacts (Dockerfile, image, container and the shell script) to be executed...

It's like you conveniently missed the first sentence.

1. Nothing stays a simple as time happens. New shit gets added.

2. Eventually people do want to use libraries so you're back to square 1.

3. It's "cross-platform" runnable now so those annoying macOS and Windows users can suddenly use it. Though depending on how you do the shim it might be tricky. I've taken to writing the shim in Go lately so I can poop out a static binary that does the rest of the work.

4. It's really easy to keep stuff up to date if your users are non-technical... simply have the shim docker pull a new version on startup.

To the end user it requires just a single artifact. For the developer there's a bit more stuff to manage but it's not exactly like any of this is hard stuff to figure out.

As well as Docker being installed, and configured, which implies admin access to the hardware you're running it on

It's easy to make self-contained scripts with dependencies using Pex: https://github.com/pantsbuild/pex/

I don't think it makes sense to bundle multiple python interpreters into a 100MB executable so that my ftp_send.py script can parse command line arguments

Pex doesn't bundle any interpreters.

This is a solved problem, you should not worry about.

My favorite library for this is docopt[0], which parses the docstring at the top of your script. It’s a lot easier for people reading your code to see the usage up front rather than scrolling to the bottom and finding your main() and reading the argparse calls:

  #!/usr/bin/env python3
  Usage: ./myscript.py <arg> [<optionalarg>]
  from docopt import docopt

  if __name__ == ‘__main__’:
      args = docopt(__doc__)
      print(args[‘<arg>’], args[‘<optionalarg>’] or ‘foo’)
[0]: https://github.com/docopt/docopt

For really, really basic things docopt is good. But you tend to re-invent the wheel when you need to do something other than the basic parsing it supports. The click docs have a good section[1] on this:

> On top of that docopt is restricted to basic parsing. It does not handle argument dispatching and callback invocation or types. This means there is a lot of code that needs to be written in addition to the basic help page to handle the parsing results.

1. http://click.palletsprojects.com/en/7.x/why/#why-not-docopt-...

Docopt is a killer arg parser for build scripts.

I drop a doc-string like this one in every build script and get the command line interfaces for free:

      pipenv install --dev
      make.py [<command>] [options]
      build    Build wheel.
      push     Push wheel to pypi.
      test     Run tests.
      bump     Run interacitve bump sequence.
      git      Run interactive git sequence.
      -h, --help  Show this screen.

That's what I came in here to mention. I mostly write small, personal utilities with a max of 5-10 commands and a few parameters, and I think click's architecture is too strange and complex. Docopt is simple and clear, doesn't do anything more than I need, and leaves me free to architect the rest of the script however seems best. It also works the same in a bunch of languages.

I do concede that click may be better for CLI apps that grow to tens of thousands of LOC and involve multiple full-time developers. I've never built one that big though.

I also worry a bit that docopt doesn't seem to have gotten much attention in a while. It's basically complete, so shouldn't matter, but still.

docopt seems like a good idea at first, but you will hit a limitation sooner or later. Not with Click.

I’ve only ever found click to be a waste of time compared to just using argparse. The extra concision you get from decorator syntax just doesn’t matter and you introduce another dependency and need to go into the relatively poor click documentation (especially if you end up with a longterm dependency on old versions of click). Just not worth it.

I'm not sure why click is often recommended. I've used argh for years and find it easier:

  def hello(name):
      return "hello {}".format(name)
  def ping():
      return "pong"

  if __name__ == "__main__":
      import argh
      parser = argh.ArghParser()
      parser.add_commands([hello, ping])

Yep, this is my first time seeing Click and it looks very similar to argh.

Personally I'll probably stick to argh, but good to see some competition in the space.

One nice thing about argh is that it's mostly just a fancy wrapper around argparse. So you have the full functionality of argparse if you need it, while still getting the lack of boilerplate and nice defaults of argh.

It also means it works with other third party things on top of argparse like argcomplete for tab completion.

Does anyone know if Click is similar, or if it implements its own parser?

EDIT: Answered my own question. Looks like it uses optparse internally. So yeah, should see similar benefits.

If people in your organization tend to write Flask apps, then you might find click in standalone scrips as well.

I'm not sure why Flask is often recommended either. It seems to me that it's somewhere between Bottle and Django, playing the strange role of a not-so-lightweight, not-so-unopiniated framework. But I should probably reserve this rant for another thread.

Wow argh looks great! I'll definitely try swapping one of my tools to argh

No stacked decorators, nice.

Why format()?

I like it. Maybe because it's explicit. I never liked the old % style. I may switch to f-strings one day. So much for "There should be one-- and preferably only one --obvious way to do it."

The key word in that line is "obvious".

So you're comfortable using something because you like it?

Of course.

Do you work with others? Do you code professionally?


When you do things you like, how does it impact others?

You mean things as trivial as string format methods ? No impact. This is not subject to discussion either, we avoid bikeshedding.

So there are topics that are "not subject to discussion" and they're also the decisions you get to unilaterally make based on your preference, with no explanation needed.

Sounds fun, great coworking environment.

Not sure what's going on, but you've posted so many low-quality comments and broken the site guidelines so many times in the last several hours that I'm rate-limiting your account again, until we get a commitment that this will stop and not recur.

I have 3 live projects using click. I like it.

Caveat: Click's docs were out of date and had open bugs for what feels like years until it finally updated to 7. version 6 was "unstable" https://github.com/pallets/click/issues/503. Hopefully it's over now.

Good things:

- More concise than standard library argparse

- Testing via CliRunner (http://click.palletsprojects.com/en/7.x/testing/), example: https://github.com/tmux-python/tmuxp/blob/v1.5.3/tests/test_...

Feel free to study / copy from mine if you like (license MIT):

- https://cihai-cli.git-pull.com/: https://github.com/cihai/cihai-cli/blob/v0.5.0/cihai_cli/cli...

- https://tmuxp.git-pull.com/: https://github.com/tmux-python/tmuxp/blob/v1.5.3/tmuxp/cli.p...

- https://vcspull-git-pull.com/: https://github.com/vcs-python/vcspull/blob/v1.2.0/vcspull/cl...

Does this do anything that the builtin argparse library https://docs.python.org/3/library/argparse.html doesn't? Seems it just turns it into decorator syntax.

It really comes handy IMHO when you have a lot of commands with nesting and context to share. I've used it on a CLI tool with many sub-commands implemented in imported modules and it made things really easy while I don't think argpase would have "scaled" without becoming a mess.

Plus it comes with some useful utilities you might need in a cli to show a progressbar, display color, open test in a pager/an editor etc.

The official doc is really nice and gives it more justice than this blog post: https://click.palletsprojects.com/en/7.x.

Ah - makes a lot more sense when they describe Git, with its nested interfaces and argument parsers (and reused logic therein) as a motivating example!

For those of you using argparse I recommend looking at configargparse. A drop in replacement which adds environment variable support (similar to click) and config file support

I've used Fire[0] for a few projects and have been meaning to give Click a try, but I'm not sure I like the syntax after reading through the article (holy moly decorators). Seems unwieldy to me, but does anyone have experience with both?

[0] https://github.com/google/python-fire

Based on the examples in the article, explicitly writing a line for each CLI option seems like excessive boilerplate and verbosity that Fire manages to sidestep completely. To me, this is the principal point of using a Python library to create CLIs. Am I missing something?

Fire works with bare args, automatically supports --arg=value for constructor's arguments and provides more features like tracing and bash completion generator.

I liked fire a lot, gets kinda goofy sometimes (and it looks abnormal in exceptions). If you can use python 3 - defopt is simpler and less visually noisy on errors https://defopt.readthedocs.io/en/stable/

Click unfortunately doesn't pass the smell test of a non-awkward implementation of 'sudo'. It gets some partial credit because an implementation is possible but only because you can subclass the click.Command object and change the parsing behavior arbitrarily.

So many of these parsing libraries seem to forget that any positional parameter can stop options parsing not just '--' and that that parameter might appear arbitrarily deep in subcommands.

This is not only needed for commands like sudo and ssh but BSD style CLIs as well which all OSX users are probably intimately familiar and annoyed with.

Which Python library fills this requirement well?

Click and argparse end up with a lot of boilerplate. `defopt` (https://defopt.readthedocs.io/en/stable/) reduces it to almost nothing (granted only in Python 3), is only a single file and even turns your function docstring into command help text.

With every other tool I’ve tried, I end up writing a python API and a CLI and documenting it twice.

Defopt eliminates that almost completely (but with *args still allows for elegant interfaces)

The decorators aren't the main thing, you dont need to use them. The overall interface is just so much better than argparse. It's way more featureful.

i'm sorry but this is not how decorators are supposed to be used. I also doubt that click does decorators correctly too. I couldn't get them working on a instance method for example. Just stick to the standard library on this. A dependency is not worth it here.

Decorators can be used in any way you want. This is actually an absolutely fine use of decorators to add metadata/annotations to a given function.

Critiques of any library are great, but you need to expand on vague points: "I also doubt that click does decorators correctly too", or how you would ever expect them (or even want them??) to work on an instance method. It might be that you're misunderstanding the library and how it should be used rather than it being not worth using.

I find it absolutely perfect for a lot of situations.

You have no taste and you don't know what you are talking about. Click is very useful, elegant and flexible, the decorators make developing CLI apps way faster and more readable with it.

I use click for https://github.com/rcarmo/piku (in fact it’s the only dependency) and often wish something like it was part of the standard library...

Here is a fairly detailed overview in the form of slides what is Click and what it can do: https://slides.com/kissgyorgy/deck-10#/

I wish python had something similar to OCaml's Cmdliner[0]

[0] https://erratique.ch/software/cmdliner/doc/Cmdliner

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact