Poetry makes packaging a Python application easy. Click makes it easy to create command line applications in Python. How can these two tools be used together?
kindlenotes2mdI’ve used this pattern in the tool kindlenotes2md
if you want to see a working example. Here I’m just going to talk about the most important points. I assume that you’ve already got a poetry project up and running, either by running poetry new
to create a new project, or by poetry init
in an existing one.
Now create a command line application from your code.
Make an entry point in your pyproject.toml
Add a section in your pyproject.toml
file.
[tool.poetry.scripts]
kindlenotes2md = "kindlenotes2md.notes:cli"
There are 4 important parts to this line.
kindlenotes2md
for the first time is the name of the executable script, so what you’ll be typing in on the command line.
kindlenotes2md
for the second time is the name of the module. They don’t need to be called the same thing.
notes
is the name of the python file in the module that contains the function that you’ll use as the entry point to your application.
cli
is the name of the function that you’ll call when typing the command line.
Make the function for the command line application
If I run poetry shell
first to set a virtual environment up, the function cli
will be run when I run kindlenotes2md
in my command line. To make this useful, use click decorators to pass parameters into the function from the command line.
import click
@click.command()
@click.argument("inputfilename")
@click.option("-o", "--outfilename", default=None)
def cli(inputfilename, outfilename):
checkfile(inputfilename)
contents = read_file(inputfilename)
book_notes = parse_contents(contents)
markdown = md_output(book=book_notes)
if outfilename is None:
print(markdown)
else:
save_to_file(markdown, outfilename)
How to unit test?
Click provides the CliRunner
, which lets you run commands as command line scripts. You use this in your pytest
unit tests. This test tests what happens if the file isn’t found (exit code 1), and when it runs successfully.
from click.testing import CliRunner
from kindlenotes2md import notes
def test_cli():
runner = CliRunner()
result = runner.invoke(notes.cli, "notexist.html")
assert result.exit_code == 1
result = runner.invoke(notes.cli, "tests/test_data.html")
assert result.exit_code == 0