Published
- 3 min read
Python app structure
Recently I was starting a Python application project from scratch and I had some issues understanding the correct project structure. Here’s what I have learnt.
Project structure
Here you can find the project structure of a python application implementing a cli tool named examplecli
. You can find that app here: https://github.com/adriangalera/examplecli
├── Makefile
├── README.md
├── examplecli
│ ├── __init__.py
│ ├── commands
│ │ ├── __init__.py
│ │ ├── bye.py
│ │ ├── hello.py
│ │ └── options.py
│ ├── common
│ │ ├── __init__.py
│ │ └── logging.py
│ └── entrypoint.py
├── requirements.txt
├── requirements_test.txt
├── scripts
│ ├── local-build.sh
│ └── set-module-in-path.sh
├── setup.py
└── tests
├── __init__.py
└── commands
├── __init__.py
├── test_bye.py
├── test_hello.py
└── test_options.py
Let’s list the most important stuff:
- Makefile: contains a series of instructions on how to perform common tasks: clean, test, lint, coverage and building locally
- examplecli: main module of the application. Contains all the application code organized as well in sub-modules.
- README.md: Readme file that contains some documentation and help materials
- requirements.txt: the pip libraries the app needs to be able to execute
- requirements_test.txt: the pip libraries to run the app tests
- scripts: useful tools for local development
- setup.py: we’ll discuss in a following section about this file
- tests: folder that contains the tests for the app
Virtual environment and dependencies
In order to have a clean environment, it is very recommended to use virtual environments. This fancy feature will isolate the dependencies needed for every application.
In order to boostrap the virtual environment, you should create it (if not created) and activate it:
python -m venv .venv
source .venv/bin/activate
Once the virtual environemnt is setup, you can install the dependencies in:
pip install -r requirements.txt
pip install -r requirements_test.txt
This will store the dependencies under the .venv
folder
Python modules
You should organise the python source code around the idea of modules. Modules are basically a folder with an empty file named __init__.py
and the source code.
it is very important to add the .py extension, otherwise it’s not recognized as a module
Different parts of the application can import the modules, e.g.:
import click
from examplecli.common.logging import debug, info
from examplecli.commands.options import verbose_option, user_option_required
@click.group()
def hello_source():
pass
@hello_source.command()
@verbose_option
@user_option_required
def hello(**opts):
user_name = opts["user"]
say_hello(user_name)
def say_hello(user_name):
debug(f"Saying hello to {user_name}")
info(f"Hello {user_name}")
This file is importing the methods debug
and info
from logging
file in module examplecli.common
setup.py
This file is only needed if we want to package the application. Without it, we are still able to run the application by calling the entrypoint:
python3 examplecli/entrypoint.py
Usage: entrypoint.py [OPTIONS] COMMAND [ARGS]...
Welcome to Example CLI!
Options:
--help Show this message and exit.
Commands:
bye
hello
However, for this application, we want to package it into a wheel file and install it with pipx
. In order to do that, we need the setup.py file. Let’s see what’s inside of this file:
#!/usr/bin/env python
from setuptools import setup, find_packages
from os import environ
# reads the requirements and stores them into a variable
with open('requirements.txt') as fp:
install_requires = fp.read()
# reads the test requirements and stores them into a variable
with open('requirements_test.txt') as fp:
tests_require = fp.read()
setup(name='example-cli', # name of the package
version=environ.get('EXAMPLE_CLI_VERSION', '0.0.1'), # reads the variable from a environment variable
description='Example CLI', # provides a description of the package
author='Adrian Galera', # provides the author of the package
author_email='',
python_requires='>=3.6.*', # details the version compatibility.
packages=find_packages(), # the find_packages method scans the folder for modules and sub-modules
install_requires=install_requires,
tests_require=tests_require,
entry_points={
'console_scripts': [
'example-cli = examplecli.entrypoint:start', # required for click framework to find the starting point
]
}
)
In order to generate the wheel file, the user should run the following command:
EXAMPLE_CLI_VERSION=$VERSION python3 setup.py sdist bdist_wheel