Structuring Your Python Project

While there is no one correct way to structure a python project, it is better to consider how to leverage pythons feature set to create clean, DRY and effective codebase, with clear logic and dependancies in the planning stages of your development rather that having to worry about restructuring at a later date.

Consider the structure first and plan up front for a more streamlined and targeted approach to your coding sessions.

A Basic Sample Repository

Repository structure is critical to your project architecture.

README.rst
LICENSE
setup.py
requirements.txt
sample/__init__.py
sample/core.py
sample/helpers.py
docs/conf.py
docs/index.rst
tests/test_basic.py
tests/test_advanced.py

NOTE:

  • Your library does not belong in an ambiguous src directory
  • Likewise, documentation should be in the main repo. There is no reason for it to be hidden away.

Test Modules

Test modules must import your packaged module to test it. Use a simple (but explicit) path modification to resolve the package properly. Requiring a developer to run setup.py develop to test an actively changing codebase also requires them to have an isolated environment setup for each instance of the codebase.

To give the individual tests import context, create a tests/context.py file:

Signs of a poorly structured project include:

Multiple and messy circular dependencies: if your classes Table and Chair in furn.py need to import Carpenter from workers.py to answer a question such as table.isdoneby(), and if conversely the class Carpenter needs to import Table and Chair to answer the question carpenter.whatdo(), then you have a circular dependency. In this case you will have to resort to fragile hacks such as using import statements inside methods or functions.

Hidden coupling: each and every change in Table’s implementation breaks 20 tests in unrelated test cases because it breaks Carpenter’s code, which requires very careful surgery to adapt the change. This means you have too many assumptions about Table in Carpenter’s code or the reverse.

Heavy usage of global state or context: instead of explicitly passing (height, width, type, wood) to each other, Table and Carpenter rely on global variables that can be modified and are modified on the fly by different agents. You need to scrutinize all access to these global variables to understand why a rectangular table became a square, and discover that remote template code is also modifying this context, messing with table dimensions.

Spaghetti code: multiple pages of nested if clauses and for loops with a lot of copy-pasted procedural code and no proper segmentation are known as spaghetti code. Python’s meaningful indentation make it very hard to maintain this kind of code. So the good news is that you might not see too much of it.

Ravioli code is more likely in Python: it consists of hundreds of similar little pieces of logic, often classes or objects, without proper structure. If you never can remember if you have to use FurnitureTable, AssetTable or Table, or even TableNew for your task at hand, you might be swimming in ravioli code.