Structuring a Python application
In this short article I will explain all the different ways of structuring a Python application, from a quick script to a more complex web application.
Single python file containing all the code
That’s okay for code without dependencies or using a virtual environment. This is generally used for a quick script that doesn’t need complexity in its file structure.
my-single-file-project/
├── .gitignore
├── helloworld.py
├── test.py
├── pyproject.toml
├── LICENSE └── README.md
Example of the helloworld.py
:
#!/usr/bin/env python
# helloworld.py
from datetime import datetime
def do_hello():
print(f"Hello, today is {datetime.now().date()}")
if __name__ == '__main__':
do_hello()
Installable Single Package
When your app get more complex you generally wants to group your different files and helpers into a module.
You also need to improve your tests so they cover all the files → test coverage. You can also add the use of a linter.
my-single-package-app/
├── helloworld/
│ ├── __init__.py
│ ├── helloworld.py
│ └── utils.py
├── tests/
│ ├── __init__.py
│ ├── test_helloworld.py
│ └── test_utils.py
├── runtest.sh
├── .gitignore
├── pyproject.toml
├── LICENSE └── README.md
Now we grouped the files from our app in two folders, the
helloworld
app folder and the tests
folder.
We match the test_****.py
files name with the files in
the app module folder.
The __init__.py
files are mandatory for python to know
it is package and can be empty.
#!/usr/bin/env python
# helloworld.py
from datetime import datetime
from helloworld.utils import show_message
def do_hello():
f"Hello, today is {datetime.now().date()}")
show_message(
if __name__ == '__main__':
# pragma: no cover do_hello()
runtests.sh
file is a script to run all the test with the coverage
dependency that you can install with
poetry add coverage -D
:
#!/bin/bash
find . -name "*.pyc" -exec rm {} \;
coverage run -p --source=tests,helloworld -m unittest # this is where you add the
# folders you want to test
if [ "$?" = "0" ]; then
coverage combine
echo -e "\n\n================================================"
echo "Test Coverage"
coverage report
echo -e "\nrun \"coverage html\" for full report"
echo -e "\n"
# pyflakes or its like should go here
fi
Application with internal packages
In bigger apps you will probably have multiple modules, particularly if you do a GUI.
Possible additions to your application are :
- bin directory, wrapper for your entry point.
- data directory, always separate your data from your code
app/
├── helloworld/
│ ├── __init__.py
│ ├── hello/
│ │ ├── __init__.py
│ │ ├── hello.py
│ │ └── utils.py
│ ├── world/
│ │ ├── __init__.py
│ │ └── world.py
├── tests/
│ ├── __init__.py
│ ├── hello/
│ │ ├── __init__.py
│ │ ├── test_hello.py
│ │ └── test_utils.py
│ ├── world/
│ │ ├── __init__.py
│ │ └── test_world.py
├── bin/
│ └── helloworld*
├── data/
│ └── translate.csv
├── docs/
│ ├── conf.py
│ ├── index.rst
│ ├── hello.rst
│ ├── world.rst
│ └── Makefile
├── runtest.sh
├── .gitignore
├── pyproject.toml
├── LICENSE └── README.md
bin/
This new folder holds any executable files the users will use. The
script drop the .py
ending. It should have very little code
logic, it’s just a wrapper for the main module’s entry point.
You can configure setup.py
to package this if you build
a wheel, the script will be put on the path.
data/
If there are files that are not code that is usually where they should end up. It is also useful for test data.
docs/
The documentation is very important if you want to share your app with users.
A very useful library for that is sphinx
, it uses
pydoc
comments to build the doc automatically.
poetry add sphinx -D
poetry add sphinx-rtd-theme -D
cd docs
sphinx-quickstarts make html
The .rst
files are generated by
sphinx-quickstart
.
Django web application
After you’ve installed Django
you can use this included
command to generate the architecture of the project as Django expects it :
django-admin startproject django_world
django-admin startapp hello
You will end up with this basic folder tree where just a few things has been manually added :
django_world/
├── docs/
├── static/
│ └── style.css
├── templates/
│ └── base.html
├── resetdb.sh
├── runserver.sh
├── hello/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── django_world/
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py └── manage.py
The hello/
folder is where all the logic, model, views
and so on live.
The static
folder contains all the static contents (css, js,…).
The templates
folder has all the html templates.
runserver.sh
is a script that has only one line :
python manage.py runserver
resetdb.sh
is a script for wiping all the database and
start from scratch, it is just a helper tool :
#!/bin/bash
find . -name "*.pyc" -exec rm {} \;
rm db.sqlite3
python manage.py migrate
# python manage.py create_test_data
# comment that last line to use premade test data
Flask
Flask is proud to show a working web app with 10 lines of code, but in practice you’ll never do that. Here is a complete folder organization :
flaskr/
│
├── flaskr/
│ ├── ___init__.py
│ ├── db.py
│ ├── schema.sql
│ ├── auth.py
│ ├── blog.py
│ ├── templates/
│ │ ├── base.html
│ │ ├── auth/
│ │ │ ├── login.html
│ │ │ └── register.html
│ │ │
│ │ └── blog/
│ │ ├── create.html
│ │ ├── index.html
│ │ └── update.html
│ │
│ └── static/
│ └── style.css
│
├── tests/
│ ├── conftest.py
│ ├── data.sql
│ ├── test_factory.py
│ ├── test_db.py
│ ├── test_auth.py
│ └── test_blog.py
│
├── .gitignore └── MANIFEST.in
We can see that flask really uses python package way of working.
Comments
Comments powered by Disqus