I will present here the best practice when you are coding with Python. I will not speak about syntaxing but more the tools and code structure to follow.

Code Structure

Keep your code, tests, and configs separated:

my-project/
├── src/                  # all your main code here
│   ├── __init__.py
│   ├── main.py
│   ├── utils.py
│   └── dataset/
│       ├── __init__.py
│       └── dataset_creation.py
├── tests/                # all tests in a parallel folder
│   ├── test_main.py
│   └── test_utils.py
├── requirements.txt       # list of dependencies
├── README.md              # project description and usage
├── .gitignore             # files to ignore in git

Each file or folder should have one clear purpose:

  • init.py → turns a folder into a Python package This file tells Python: “Hey, this folder can be imported as a module.”

Without it, you couldn’t do this:

from src.utils import my_function
  • main.py → entry point of the app
  • utils.py → helper functions, a place for small, reusable functions like logging, connecting to APIs, etc.
  • dataset/ → logic related to dataset creation
  • tests/ → unit tests to check your code works
  • requirements.txt, list of dependencies
  • .gitignore

Virtual Environments

Each project may need different versions of libraries. A virtual environment keeps them separate, so no version conflicts.

How to create one venv and activate

python3 -m venv .venv
source .venv/bin/activate   # on macOS / Linux

Then install dependencies inside it:

pip install -r requirements.txt

Deactivate it when done:

deactivate

Best practice:

  • Always have one virtual environment per project
  • Add .venv/ to your .gitignore
  • Never commit dependencies, only requirements.txt or pyproject.toml

Dependency management and versioning

Use pinned versions Specify exact versions so builds are reproducible

boto3==1.35.3
pandas==2.2.2

Then, when you want to update safely:

pip list --outdated
pip install -U boto3
pip freeze > requirements.txt

Application versioning and pyproject.toml

pyproject.toml is a configuration file for Python projects. Think of it as a central hub where you can define:

  • Your project metadata (name, version, description, authors…)
  • Dependencies your project needs
  • Configuration for tools like linters, formatters, testing, type checking, etc.

It’s like a settings panel for your Python project.

Use Semantic Versioning (SemVer) insde this file and update after each big change: MAJOR.MINOR.PATCH → 1.2.3

Example:

1.0.0 → First stable release 1.1.0 → New features (no breaking change) 1.1.1 → Bug fix

vim pyproject.toml
[project]
name = "ai-writer"
version = "0.1.0"
description = "Python project to read ebooks, create datasets, and upload to MinIO"
authors = ["Julien <you@example.com>"]
dependencies = [
    "boto3==1.40.61",
    "botocore==1.40.61",
    "EbookLib==0.20",
    "jmespath==1.0.1",
    "lxml==6.0.2",
    "pi==0.1.2",
    "PyPDF2==3.0.1",
    "python-dateutil==2.9.0.post0",
    "s3transfer==0.14.0",
    "six==1.17.0",
    "urllib3==2.5.0"
]

[tool.black]
line-length = 88
target-version = ['py310']

[tool.ruff]
line-length = 88
select = ["E", "F", "I"]
exclude = ["tests", ".venv"]

[tool.bandit]
targets = ["src"]
exclude = [".venv", "tests"]
severity-level = "medium"
confidence-level = "medium"

[tool.pip-audit]
requirement-files = ["requirements.txt"]

[tool.isort]
profile = "black"

Here

Linting — catch errors and bad patterns

Linters check for code smells, unused imports, bad syntax, etc.

Popular tools

  • Flake8 → lightweight and standard
  • Ruff → very fast modern linter (recommended)
  • Pylint → very strict, useful for large projects

Example (Ruff)

pip install ruff
ruff check src/

Best practice: run ruff in CI before merging to main.

Security and vulnerabilities

Use pip-audit to find vulnerabilities in dependencies.

Installation:

pip install pip-audit
(venv) jusi@Juliens-MacBook-Air src % pip-audit
Found 1 known vulnerability in 1 package
Name Version ID                  Fix Versions
---- ------- ------------------- ------------
pip  25.2    GHSA-4xh5-x5gv-qwph 25.3

Automate all with pre-commit hooks

Pre-commit runs checks before commits, perfect for linting and formatting.

Installation:

pip install pre-commit

Create .pre-commit-config.yaml:

repos:
  # Formatter
  - repo: https://github.com/psf/black
    rev: 24.4.2
    hooks:
      - id: black

  # Linter
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.6.1
    hooks:
      - id: ruff

  # Dependency vulnerability
  - repo: https://github.com/pypa/pip-audit
    rev: v2.7.3
    hooks:
      - id: pip-audit