Steven's Knowledge

Tooling & Packaging

The modern Python toolchain -- uv, ruff, virtual environments, pyproject.toml packaging and distribution, and the import system

Tooling & Packaging

Python's packaging ecosystem was historically painful; modern tools have transformed it. This page covers the toolchain (uv, ruff, mypy), building and distributing installable packages with pyproject.toml, and the import-system mechanics that underlie all of it.

Virtual Environments and Tooling

The Python packaging ecosystem has historically been painful. Modern tools have improved things significantly:

ToolPurposeWhen to Use
uvFast package installer and resolverNew projects -- it is dramatically faster than pip
poetryDependency management with lockfileTeams that need reproducible builds
pip + venvBuilt-in, always availableSimple projects, CI environments
ruffLinter and formatter (replaces flake8, black, isort)Every project -- it is fast and comprehensive
mypy / pyrightStatic type checkingAny project with type hints

Packaging and Distribution

A senior Python engineer is expected to ship installable, reproducible packages -- not a folder of loose scripts. Modern packaging centres on a single pyproject.toml:

[project]
name = "myservice"
version = "1.2.0"
requires-python = ">=3.11"
dependencies = ["httpx>=0.27", "pydantic>=2.0"]

[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"]

[project.scripts]
myservice = "myservice.cli:main"   # creates a console command on install

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

Conventions that prevent real bugs:

  • Use a src/ layout (src/myservice/). It forces you to install the package to import it, so your tests run against the installed package -- catching missing-data-file and packaging errors that a flat layout hides.
  • pip install -e . (editable install) for development: code changes take effect without reinstalling.
  • Pin for apps, range for libraries. Applications commit a lockfile (uv.lock, poetry.lock, or pip-compile'd requirements.txt) for byte-for-byte reproducible deploys; libraries specify loose ranges so they compose with others.
  • entry_points/[project.scripts] is how you expose CLI commands and plugin hooks -- far better than telling users to run python -m.
  • Build with python -m build (produces a wheel + sdist); publish with twine upload or uv publish. The wheel is the fast, pre-built format; the sdist is the fallback that builds from source.

The Import System and Module Mechanics

import is not magic -- it runs code. A module's top level executes exactly once, the first time it is imported; the result is cached in sys.modules and every later import returns that same object. This is why module-level state behaves like a singleton.

# config.py -- runs once; every importer shares this object
settings = load_settings()      # executed a single time, at first import

# Re-importing does not re-run it:
import config                   # cached hit, settings is the same instance

Circular imports are the classic failure. a imports b, b imports a; whichever loads first hits a half-initialised module and a name that does not exist yet:

# a.py
from b import helper           # ImportError if b imports a at its top level

# Fixes, in order of preference:
#  1. Restructure -- the cycle usually signals a missing third module.
#  2. Import inside the function that needs it (deferred, breaks the cycle).
#  3. `import b` then use `b.helper` (module object resolves lazily at call time).

Two distinctions experts hold:

  • import module vs from module import name. The first binds the module object (so reassignments in the module are seen); the second binds the current value of a name -- if the module later rebinds it, you keep the stale one.
  • Namespace packages (PEP 420) let a package span multiple directories with no __init__.py. Useful for plugins, but an accidental missing __init__.py can also turn a normal package into one and silently change import behaviour.

python -m mypackage runs a package's __main__.py with the package importable -- the correct way to launch a package, versus python script.py, which puts the script's directory on the path and breaks relative imports. if __name__ == "__main__": is what distinguishes "run directly" from "imported".

On this page