Python Dev Dependencies vs Production Dependencies
In Python packaging, managing external libraries effectively requires separating production dependencies from development dependencies. Production dependencies are the runtime packages essential for your application or library to execute its primary functions in a live environment, whereas development dependencies are auxiliary tools used exclusively for testing, formatting, linting, and documentation during the software lifecycle. Separating these dependencies minimizes security vulnerabilities, reduces container image sizes, prevents version conflicts, and streamlines deployment pipelines.
What Are Production Dependencies?
Production dependencies—often called runtime dependencies—are the packages your code imports directly to perform its core tasks. Without these libraries, the application will encounter import errors or crash at runtime.
- Examples:
fastapi,requests,sqlalchemy,pydantic. - Purpose: Executing application logic, interacting with databases, serving web endpoints, or performing data analysis.
- Target Audience: The end-users of your application or developers installing your published library from the Python Package Index (PyPI).
In standard pyproject.toml configurations (PEP 621),
production dependencies are defined under the main
[project] table:
[project]
name = "my-package"
version = "0.1.0"
dependencies = [
"requests>=2.28.0",
"pydantic>=2.0.0",
]What Are Development Dependencies?
Development dependencies are libraries required only by developers working on the codebase or by automated Continuous Integration (CI) pipelines. They are never required to execute the application in a production environment.
- Examples:
pytest(testing),black(formatting),rufforflake8(linting),mypy(static type checking), andsphinx(documentation). - Purpose: Ensuring code quality, verifying functionality, generating documentation, and packaging the software.
- Target Audience: Core maintainers, open-source contributors, and automated build servers.
In traditional setups, these were isolated in a
requirements-dev.txt file. In modern
pyproject.toml configurations, they are categorized under
optional dependencies or specific dependency groups:
# Using PEP 621 optional dependencies
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"ruff>=0.1.0",
"mypy>=1.0.0",
]
# Or using Poetry dependency groups
[tool.poetry.group.dev.dependencies]
pytest = "^7.0.0"
ruff = "^0.1.0"Key Differences
| Feature | Production Dependencies | Development Dependencies |
|---|---|---|
| Execution Context | Live server, cloud instance, client runtime | Local machine, CI/CD runners, build agents |
| PyPI Distribution | Automatically installed when running
pip install your-package |
Omitted by default; must be explicitly requested |
| Impact on Security | High (directly exposed to attack vectors in production) | Low (isolated from production environments) |
| Image Size Impact | Necessary footprint | Unnecessary bloat if bundled into release artifacts |
Why the Distinction Matters
1. Security and Attack Surface
Every package introduced into a production environment adds potential security vulnerabilities. Developer tools like test runners or documentation generators frequently require extensive dependency trees of their own. Excluding them from production significantly lowers the risk of supply chain attacks and Common Vulnerabilities and Exposures (CVEs).
2. Deployment Speed and Artifact Size
Container images (such as Docker) and deployment bundles should be as lightweight as possible. Installing unnecessary development tools inflates image sizes, increases memory consumption, and prolongs continuous deployment build steps.
3. Avoiding Dependency Conflicts
Development tools often have strict dependency requirements that can clash with production libraries. By decoupling them, developers can upgrade application-critical libraries without being blocked by pinned versions in testing frameworks or linters.
Best Practices for Management
- Separate Requirement Files: If using standard
pip, maintain distinct files such asrequirements.txtfor runtime andrequirements-dev.txt(which often includes-r requirements.txt) for developers. - Adopt Dependency Groups: When using modern
packaging managers like Poetry, PDM, or Hatch, organize dependencies
into granular groups (e.g.,
dev,test,docs) rather than combining all non-production packages into a single list. - Use Multi-Stage Docker Builds: Install both production and development dependencies in early build stages for running tests, but copy only the production virtual environment into the final deployment image.