This project is a state-of-the-art template for creating an installable Python module. It incorporates community best practices, including:
pyproject.toml: As the single configuration file (PEP 621).srclayout: To prevent import errors and ensure robust tests.pytest: For simple and powerful testing.ruffandblack: For automated code quality and formatting.Makefile: To simplify common tasks.pre-commit: To ensure code quality before every commit.
my-project/
├── src/
│ └── my_package/ <-- Your package's source code lives here
│ ├── __init__.py
│ └── main.py
├── tests/ <-- Tests for your package
│ └── test_main.py
├── .gitignore
├── LICENSE
├── Makefile <-- Shortcuts for common tasks
├── pyproject.toml <-- The only configuration file you need!
├── .pre-commit-config.yaml <-- Configuration for pre-commit hooks
└── README.md
The advantage of the src layout is that it forces you to install the package to test it, ensuring that tests run under the same conditions as they will for the end user.
The src/my_package/__init__.py file not only defines the directory as a Python package but also allows you to create a clean public API for your module.
For instance, instead of having users import functions from deep within your project's structure, like this:
# Less ideal, couples the user to your internal structure
from my_package.main import greetYou can expose the greet function directly in the __init__.py:
# src/my_package/__init__.py
"""Top-level package for my_package."""
from .main import greet
__all__ = ["greet"]This way, the end-user can import greet in a much cleaner and more intuitive way:
# Ideal, the API is cleaner and decoupled from the structure
from my_package import greetThis template is already configured with this pattern for the example greet function. Take advantage of it to provide a great experience for your module's users!
The source code is located in src/module_name. You should rename it to match your package's name.
mv src/module_name src/my_packageThis file is your project's control center. Open it and edit the [project] section:
[project]
name = "my_package" # <-- Very important! Must match the folder name.
version = "0.0.1"
authors = [
{ name="Your Name", email="your_email@domain.com" },
]
description = "A great description of my package."This file also manages your project's and development dependencies.
Use the Makefile to set up your environment:
make installThis command creates a virtual environment, installs pytest, ruff, pre-commit, etc., and your package in editable mode (-e). It also installs the pre-commit hooks automatically.
pytest is the de facto standard for testing in Python due to its simplicity and power.
Writing Tests:
Simply create functions that start with test_ and use assert to check the results.
# tests/test_main.py
from my_package.main import greet # Be sure to use your package name
def test_greet_with_name():
"""Tests that the function greets by name."""
assert greet("Javi") == "Hello, Javi"Running Tests:
Use the Makefile shortcut:
make testOr run pytest directly:
pytestUse make help to see all available commands.
make install: Installs the development environment.make test: Runs tests withpytest.make lint: Checks code for style issues and errors withruff.make format: Formats all project code withruffandblack.make build: Builds the package for distribution.make publish-test: Publishes the package to TestPyPI.make publish: Publishes the package to PyPI.make clean: Removes all generated files.
pre-commit is a tool to install and manage Git hooks that run automatically before each commit.
How it works:
Once installed (with make install), every time you try to git commit, pre-commit will automatically run the configured code quality tools (ruff for linting and black for formatting).
- If
rufffinds linting errors, it will notify you, and the commit will fail until you fix them. - If
blackreformats your code, the commit will fail, but your code will already be formatted. You just need togit add .the formatted changes and try to commit again.
This ensures that all code in your repository always meets the defined quality and style standards.
Configuration:
The hook configuration is located in the .pre-commit-config.yaml file.
Versioning (MAJOR.MINOR.PATCH) is managed with bump-my-version, the modern successor to bumpversion. It is configured to update the version directly in pyproject.toml and VERSION.txt.
Good practice: Before bumping the version, make sure all your changes are committed to git.
# Bump a patch (e.g., from 0.0.1 to 0.0.2)
bump-my-version patch
# Bump a minor version (e.g., from 0.0.2 to 0.1.0)
bump-my-version minorThe modern workflow uses the build package.
Use the Makefile command:
make buildOr the direct command:
python -m buildThis generates the distribution files (.whl and .tar.gz) in the dist/ folder.
If you want to install your package locally to test the final artifact before publishing, you can use the generated .whl file. This is useful for testing the actual package installation in a clean environment.
# Make sure to activate your virtual environment if you are using one.
pip install dist/your_package-0.0.1-py3-none-any.whlNote: Replace your_package-0.0.1-py3-none-any.whl with the exact name of the .whl file generated in your dist/ folder.
The Makefile also simplifies publishing.
Good practice: Publish to the test environment first.
make publish-testWhen everything is correct, publish to the official repository:
make publishThese commands use twine to securely upload the artifacts from the dist/ folder.
Once you've configured and tested the template, it's time to make it your own. Here are some steps to clean up the example content and start with your own code:
-
Remove example code (
src/my_package/main.py):- The
greet()function and themain()entry point are just examples. Delete them and start writing your module's real functionality.
- The
-
Delete example tests (
tests/test_main.py):- This file contains tests for the
greet()function. Delete this file or clear its content to start writing your own tests.
- This file contains tests for the
-
Update your changelog (
CHANGELOG.md):- The
CHANGELOG.mdfile is empty, ready for you to add your first entry. - You can start with something like:
0.0.1 - YYYY-MM-DD: Initial project setup from template.
- The
-
Review the license (
LICENSE):- Although already mentioned, make sure the
LICENSEfile contains the final license you wish to use for your project.
- Although already mentioned, make sure the