Creating a Python App#

This tutorial will explain how to set up a new Python app.

The following sections describe how you can use autopilot-utils to create your own custom Python app, with the help of Poetry.

Setting up Poetry#

Poetry is a popular tool for dependency management and packaging in Python. We will use it to simplify the Python project setup.

Install Poetry and set up a project#

For installing Poetry and for setting up your project, you’ll find the latest information in the Poetry documentation.

Below, you can find one way of installing Poetry and setting up a project, but feel encouraged to check out the Poetry website, as there are other (and possible newer or more recommended) ways to start with Poetry.

  1. Install pipx using this documentation.

  2. Install poetry by running the following command from a shell (check out the docs for more details):

    pipx install poetry
    
  3. Set up a new project:

    poetry new my-app
    
  4. Switch to the new directory:

    cd my-app
    

Add autopilot-utils to Poetry#

Add the autopilot-utils package to your Poetry project:

poetry add autopilot-utils

Add pex to Poetry#

You should also install the pex utility if you want to bundle your app later into a single-file pex bundle:

poetry add --group=dev pex@2.2.1

Note

Currently, the tested and supported version of pex is 2.2.1.

Note

If poetry complains about incompatible Python versions for pex and the autopilot-utils package, read the error message closely and adapt the python version property inside the pyproject.toml file accordingly.

Writing the Python app#

Now that we have set up the Poetry project and added the autopilot-utils package, we can continue with creating our Python app.

Create a version file#

Poetry has already created a package folder my_app/ for us, and put a file __init__.py into the folder.

Now, create a version file my_app/_version.txt with the content 0.1.0:

echo "0.1.0" > my_app/_version.txt

This version number will later be used to display the version of your app with the --version command line argument.

Create the main Python file#

Then, create a file my_app/cli.py with the following content:

my_app/cli.py#
import click
from loguru import logger

from grow.autopilot_utils.cli_base import make_autopilot_app, read_version_from_package
from grow.autopilot_utils.results import DEFAULT_EVALUATOR, RESULTS, Result

class CLI:
    click_name = "my-app"
    click_help_text = "My first app!"
    click_evaluator_callback = DEFAULT_EVALUATOR
    click_setup = [
        click.option("-n", "--name", help="Your name."),
    ]

    def click_command(name):
        logger.debug("We are inside click_command now!")

        if name:
            logger.info(f"Hello {name}!")
        else:
            logger.info("Hello World!")

        RESULTS.append(Result(criterion="App must work.", fulfilled=True, justification="Executes correctly!"))


main = make_autopilot_app(
    provider=CLI,
    version_callback=read_version_from_package(__package__),
)

if __name__ == "__main__":
    main()

Running the app#

To run and test your Python app, execute:

poetry run python -m my_app.cli

This should result in the following output:

INFO  | Hello World!
{"status": "GREEN", "reason": "Executes correctly!"}
{"result": {"criterion": "App must work.", "fulfilled": true, "justification": "Executes correctly!", "metadata": {}}}

Running the app with arguments#

If you run your app with a --debug flag and with a --name argument, the output on the console will be different:

$ poetry run python -m my_app.cli --debug --name Tom
20:15:42 | DEBUG | We are inside click_command now!
20:15:42 | INFO  | Hello Tom!
{"status": "GREEN", "reason": "Executes correctly!"}
{"result": {"criterion": "App must work.", "fulfilled": true, "justification": "Executes correctly!", "metadata": {}}}

Bundling your app with pex#

Pex is a tool to bundle your Python app into a single file. This file includes all third-party dependencies along your own code, so that you can execute it on any computer which has a matching Python version installed.

Note

PEX files are self-contained Python virtual environments. While they can support multiple platforms and Python interpreters, they are still targeted at a certain target platform.

This means that you usually cannot run the PEX file on a largely different system. For example, a PEX file created on Mac can usually not be used on Linux. Windows is currently not supported at all.

See the explanation of what pex files are for more details.

As Yaku runs on a Linux 64bit platform, you should be fine if you are using one of the popular Linux distributions to build your app.

If you haven’t installed pex yet into your Poetry environment, see the chapter on adding pex to your poetry environment.

To bundle your app into a pex file, run:

poetry run pex . --use-pip-config --output-file my-app.pex --entry-point my_app.cli

Note

The --use-pip-config flag is required here, as pex doesn’t know about our extra package source yet, so without this flag, it will not be able to download the autopilot-utils package.

You should now have a single file my-app.pex in your working directory. This file can be called like a normal executable, e.g.:

./my-app.pex --help

or like in the example above:

./my-app.pex --debug --name Tom

You can now upload this app file to your repository and use it in your workflows.

Note

For more information on how to write such a command line app for a Yaku fetcher or evaluator, see also Writing a simple fetcher app or Writing a simple evaluator app.

Other ways for bundling an app#

Besides pex, there are other ways to create a single-file application out of a Python package.

However some of them include the Python runtime into the package file, which means that those files will be much larger than the .pex files.

Nevertheless, they can still be used to package a Python app.

Most popular tools are at the moment:

  • PyInstaller (https://pyinstaller.org/) — can even cross-build packages, but files can become quite large.

  • Nuitka (https://nuitka.net/) — uses a C compiler to compile a Python program into a binary executable. Runs fast, and seems easy to use. However involves compiling a C program with might lead to other issues.

  • PyOxidizer (indygreg/PyOxidizer) — Similar to Nuitka, but more complicated to use.

  • cx_Freeze (https://cx-freeze.readthedocs.io/) — similar to PyInstaller, but more complicated to use.