Skip to content

Python vs. JavaScript: A Guide to Dependency Management

Dependency management is a cornerstone of modern software development, ensuring that projects are reproducible, maintainable, and easy to collaborate on. This document provides an in-depth comparison of the core philosophies, toolchains, and evolutionary paths of dependency management in Python and JavaScript.

References:


Core Philosophies and Concepts

  • JavaScript: Early on, JavaScript established a centralized, de facto standard workflow with npm and package.json. Its core philosophy is project-local dependencies, where each project has its own independent copy of dependencies in a node_modules directory, fundamentally solving the "dependency hell" problem.

  • Python: The dependency management ecosystem has undergone a longer evolution. It is moving from the traditional pip + requirements.txt model to modern, all-in-one tools driven by a pyproject.toml file (like Poetry, PDM, and Rye), catching up with the modern front-end development experience.

ConceptPythonJavaScript
Manifest Filepyproject.toml (Modern) / requirements.txt (Legacy)package.json
Lock Filepoetry.lock, pdm.lock, rye.lock, etc.package-lock.json, yarn.lock, pnpm-lock.yaml
Package RegistryPyPI (Python Package Index)npm registry
CLI Toolpip (basic) / poetry, pdm, hatch, rye (all-in-one)npm, yarn, pnpm
Env. IsolationManual creation required (venv)Built into the mechanism (node_modules dir)

PyPI: Python's Equivalent of npmjs.com

Yes, Python has an official, centralized package repository that is the direct equivalent of npmjs.com. It is called PyPI (The Python Package Index), and you can browse it at pypi.org.

  • Role and Function: PyPI is the "official supermarket" for the Python community. It hosts hundreds of thousands of third-party libraries and applications created and shared by developers worldwide. When you need a package for a specific functionality, this is the first place you should look.

  • Interaction with Tools:

    • When you run pip install requests, pip, by default, searches for, downloads, and installs the requests package from PyPI's servers.
    • Modern tools like Poetry and PDM also use PyPI as their primary or default package source when resolving and downloading dependencies.
    • When developers have created their own Python package and want to share it with the world, they use tools like twine or poetry publish to upload it to PyPI.

Therefore, in terms of function and ecological niche, PyPI is to Python what npmjs.com is to JavaScript—they are the cornerstones of their respective ecosystems.


1. The JavaScript Model: npm and package.json

JavaScript's dependency management model is mature and highly standardized.

  • package.json: The project's "identity card." This JSON file defines:

    • Metadata: Project name, version, author, etc.
    • dependencies: Packages required for the application to run in production.
    • devDependencies: Packages only needed for development and testing (e.g., linters, testing frameworks).
    • scripts: Aliases for runnable command-line scripts (e.g., npm run dev).
  • node_modules Directory:

    • When you run npm install, all dependencies listed in package.json (and their sub-dependencies) are downloaded into the node_modules folder in the project's root.
    • This localized installation is a key advantage of JS dependency management, ensuring that each project has its own isolated environment, free from interference.
  • package-lock.json:

    • To guarantee reproducible builds, npm install automatically generates or updates package-lock.json.
    • This file locks the exact version numbers of the entire dependency tree (including all sub-dependencies). This means any team member running npm install at any time, on any machine, will get the exact same node_modules structure.
  • Workflow Example:

    bash
    # Initialize a new project
    npm init -y
    
    # Add a production dependency (e.g., express)
    npm install express
    
    # Add a development dependency (e.g., jest)
    npm install --save-dev jest
    
    # Install all dependencies from the lock file
    npm install

2. The Python Model: From Fragmentation to Unity

The Traditional Way: pip + requirements.txt

This was the standard practice in Python for a long time, but it comes with fundamental problems.

  • Workflow:

    1. pip install requests: Install a package.
    2. pip freeze > requirements.txt: "Freeze" all currently installed packages in the environment (including sub-dependencies) and their versions into a text file.
    3. pip install -r requirements.txt: Install all listed packages on another machine.
  • Flaws:

    • Can't distinguish direct vs. transitive dependencies: requirements.txt is a flat list. You can't tell which packages are direct requirements for your project and which were installed as dependencies of those requirements.
    • Doesn't guarantee sub-dependency versions: It only locks the top-level dependency versions. If a sub-dependency releases a new, incompatible version, it can break the build on different machines or at different times, destroying reproducibility.
    • No hash validation: requirements.txt does not contain file hashes, which poses a security risk.

The Need for Isolation: venv

Since Python defaults to installing packages in a global environment, creating a virtual environment is an essential first step in Python development to avoid dependency conflicts between projects.

bash
# Create a virtual environment named .venv
python -m venv .venv

# Activate the virtual environment (macOS/Linux)
source .venv/bin/activate

# From now on, all pip installs are confined to the .venv directory
pip install ...

This stands in stark contrast to the automatic, project-level isolation provided by JS's node_modules.

How venv Solves Dependency Conflicts: The Art of Isolation

The reason venv effectively prevents dependency conflicts is that it creates an independent, self-contained Python runtime environment. It's like giving each project its own exclusive, isolated "toolbox" instead of having all projects share one messy, public toolbox.

Here's how it works:

  1. Creating an Isolated Directory: When you run python -m venv .venv, it creates a .venv directory in your project containing:

    • A copy or symbolic link of the Python interpreter.
    • An independent site-packages directory to store third-party libraries for this project only.
    • Activation scripts (like activate).
  2. The Magic of Activation: The activate script is the key. When you run source .venv/bin/activate, it cleverly modifies your current terminal session's PATH environment variable, placing the .venv/bin directory at the very front.

  3. Redirecting Commands:

    • With the PATH modified, when you type python or pip, the system uses the versions inside .venv/bin instead of the global system ones.
    • This means the pip you are using is the one that belongs exclusively to this virtual environment.
  4. Achieving Dependency Isolation:

    • Since you're using the environment's pip, the pip install <package> command installs packages into the local .venv/lib/pythonX.X/site-packages/ directory.
    • It never touches your system's global site-packages directory or any other project's environment.

Conclusion: This way, Project A can have requests==2.20.0 installed, while Project B has requests==2.28.0 in its own venv. Both have their own independent copies, do not interfere with each other, and thus dependency conflicts are perfectly resolved.

One Project, One Environment: A Best Practice

Creating a new, dedicated virtual environment for each individual project is a core best practice in Python development.

If you have Project A, Project B, and Project C on your machine, you should have three separate virtual environments for them. This practice is the fundamental solution to "dependency hell."

Why is this necessary?

Consider this scenario:

  • Project A is an old legacy project that requires Django==3.2.
  • Project B is a new project where you want to use the latest Django==4.1.

Without virtual environments, both projects would share the global Python packages. You cannot have two different versions of Django installed simultaneously. Every time you switched projects, you'd have to uninstall one version and install the other—a complete disaster.

By creating separate venvs, Project A's venv has Django 3.2, and Project B's has Django 4.1. They are isolated in their respective site-packages directories and coexist peacefully.

How to Manage Multiple Virtual Environments?

There are two popular strategies:

  1. Create in the Project Directory (Recommended) This is the most common and straightforward method. You create the venv directory, usually named .venv, directly in your project's root.

    /path/to/my/projects/
    ├── project_A/
    │   ├── .venv/         <-- venv for Project A
    │   └── ... (Code for Project A)
    └── project_B/
        ├── .venv/         <-- venv for Project B
        └── ... (Code for Project B)

    Advantages:

    • Self-contained: The project and its environment are bundled together, making them easy to manage.
    • Easy to exclude: Just add .venv/ to your .gitignore file.
    • IDE-friendly: IDEs like VS Code and PyCharm automatically detect the .venv directory and set it as the interpreter.
  2. Centralized Management Another strategy is to store all virtual environments in a single, central directory (e.g., ~/.virtualenvs/). Helper tools can then be used to create, delete, and switch between them.

    • virtualenvwrapper is a classic tool that provides handy commands like mkvirtualenv myproject (create) and workon myproject (switch).

    Advantage:

    • Cleaner project directory: The project's source code directory remains free of a .venv folder.

Whichever method you choose, the core principle is the same: one project, one isolated environment.

The Modern Way: pyproject.toml and All-in-One Tools

Inspired by modern front-end toolchains, the Python community has recently pushed for the modernization of dependency management, centered around the pyproject.toml file, defined in PEP 621.

  • pyproject.toml:

    • This is the new standard configuration file for Python projects, intended to unify and replace multiple files like setup.py, requirements.txt, and setup.cfg.
    • It defines project metadata, build system information, and project dependencies, making it the direct equivalent of package.json.
  • All-in-One Tools: These tools integrate virtual environment management, dependency resolution, locking, packaging, and publishing into a single, unified CLI, offering an experience similar to npm. They all use pyproject.toml as their manifest file.

    • Poetry: One of the earliest popular tools, known for its powerful dependency resolver and user-friendly interface, though sometimes slower to adopt official PEP standards.
    • PDM: Strictly follows the latest PEPs and supports PEP 582, allowing it to work without a virtual environment (using a __pypackages__ directory, similar to node_modules).
    • Hatch: Maintained by the PyPA (Python Packaging Authority), it integrates well with build backends and is especially suited for library authors.
    • Rye: A more opinionated, "all-in-one" tool created by the author of Flask, Armin Ronacher. It manages not only dependencies but also Python versions themselves, providing a highly integrated experience.
  • Modern Workflow Example (with Poetry):

    bash
    # Initialize a new project (auto-creates pyproject.toml and a virtual env)
    poetry new my_project
    
    # cd into the project directory
    cd my_project
    
    # Add a dependency (auto-updates pyproject.toml and generates poetry.lock)
    poetry add requests
    
    # Install all dependencies from the lock file
    poetry install
    
    # Run a command within the project's virtual environment
    poetry run python my_app.py

Poetry's Environment Management: Where Do Dependencies Get Installed?

This is a critical question and a key part of Poetry's design philosophy. Unlike manually running python -m venv .venv, Poetry offers two strategies for managing virtual environments:

1. Default Strategy: Centralized Management

By default, when you run poetry install or poetry add, Poetry does not create a .venv folder in your project directory. Instead, it creates a dedicated virtual environment for your project in a central directory, external to your project.

  • The path is typically located at:
    • macOS: ~/Library/Caches/pypoetry/virtualenvs
    • Linux/WSL: ~/.cache/pypoetry/virtualenvs
    • Windows: %APPDATA%\pypoetry\virtualenvs

Advantages:

  • Clean Project Directory: Your project's source code directory remains uncluttered, with no virtual environment files. This is especially friendly for library developers.
  • Unified Management: All environments managed by Poetry are in one place, making them easy to clean up and inspect.

How do you use this environment? Since the environment is not local to the project, you need to use Poetry's commands to interact with it:

  • poetry run <command>: This is the most common way. It executes the specified command within the virtual environment managed by Poetry for the current project. For example, poetry run python my_app.py or poetry run pytest.
  • poetry shell: This command directly activates the virtual environment for the current project, dropping you into a shell session where the environment is already configured. You can then run commands like python or pip directly.

2. Optional Strategy: Create .venv within the Project

If you prefer to have your virtual environment and project files together, similar to npm or a manual venv setup, Poetry fully supports this. You just need to run one configuration command:

bash
poetry config virtualenvs.in-project true

This command changes Poetry's configuration to "create virtual environments inside the project." After setting this, the next time you run poetry install for a new project, it will create a familiar .venv folder in the project's root and install all dependencies there.

Advantages:

  • Self-Contained Environment: The virtual environment is physically bundled with the project code, making it easier to understand and locate.
  • IDE Auto-detection: IDEs like VS Code and PyCharm can very easily and automatically detect the .venv directory within the project and use it as the interpreter.

Conclusion: Poetry offers flexible environment management strategies. The default centralized management keeps your project directory clean, while the optional in-project creation provides a more traditional and intuitive experience. You can choose the method that best suits your personal or team preferences.

Cleaning Up and Managing Poetry Environments

As you work on more projects, you might need to view, manage, or delete the virtual environments created by Poetry. Poetry provides a concise set of env commands to handle these tasks.

1. List Environments Associated with a Project

To see which virtual environments Poetry has created for the current project, use the env list command:

bash
poetry env list

The output might look something like this, with one marked as (Activated):

text
my-project-LNaP_o8l-py3.9 (Activated)
my-project-LNaP_o8l-py3.10

2. Get Detailed Information about an Environment

If you want to know the specific path or other details about an environment, you can use env info:

bash
poetry env info

This will display details for the currently active environment, including its Python version and path.

3. Remove a Specific Virtual Environment

This is the core cleanup operation. You can use the env remove command, followed by an identifier for the environment you want to delete. This identifier can be the full name shown in poetry env list, or more simply, the Python version.

bash
# Remove by the full environment name
poetry env remove my-project-LNaP_o8l-py3.10

# Or more conveniently, by the Python version
poetry env remove python3.10
# Or even shorter
poetry env remove 3.10

4. Remove All Associated Environments at Once

If you want to delete all virtual environments that Poetry has created for the current project, use the --all flag:

bash
poetry env remove --all

For .venv created within the project: If you have configured virtualenvs.in-project = true, then cleanup is more direct: you can either run poetry env remove python from the project directory, or you can simply delete the .venv folder in the project's root, just like with a standard venv. However, using the command is still the more canonical approach.

Tools vs. Libraries: The Roles of pip and pipx

So far, we've discussed pip and venv for managing internal project dependencies (libraries). But what about another common scenario: you want to install a command-line tool written in Python (like black, ruff, or httpie) and be able to call it from anywhere on your system?

The Problem with the Traditional Way Using pip install black to install it globally is a bad practice because it:

  1. Pollutes the global environment: black and all its dependencies go into your global Python environment.
  2. Causes dependency conflicts: If you want to install another global tool, tool_B, that depends on a library version incompatible with black's dependencies, you'll be stuck in dependency hell.

The Solution: pipxpipx is a tool designed specifically for this scenario. Its core idea is to safely install and run Python command-line applications in isolated environments, while still making them globally available.

How pipx Works: When you run pipx install black, pipx does the following in the background:

  1. It automatically creates a new, isolated virtual environment just for black, located in ~/.local/pipx/venvs/.
  2. It installs black and all its dependencies into this dedicated environment.
  3. It links the black command's executable to a universal directory on your system's PATH (~/.local/bin/).

This perfectly combines isolation with convenience:

  • Each tool lives in its own venv "bubble" and will never have dependency conflicts with other tools or your projects.
  • You can run black directly in any terminal, just like a regular system command.

A Clear Analogy:

  • pip: Is for installing libraries inside your project's venv. These are the things you import in your code. Think of them as the ingredients for a recipe.
  • pipx: Is for installing tools (applications) for your own use. These are the things you run directly in the command line. Think of them as your kitchen appliances (like a code formatter black or an HTTP client httpie). Each appliance has its own internal parts and you don't mix them.

Temporary Execution: pipx runpipx also provides a run command, which behaves much like npx. It allows you to download and run a Python application in a temporary virtual environment without permanently installing it.

bash
# Temporarily run a tool to scaffold a project, then it's gone
pipx run cookiecutter https://github.com/audreyfeldroy/cookiecutter-pypackage

This is perfect for one-off scripts or for trying out a new tool.


Summary and Core Differences

  1. Environment Isolation: Python requires developers to manually create and manage isolated environments using tools like venv (though tools like Poetry simplify this). JavaScript's node_modules mechanism is built-in and automatic.

  2. Toolchain: JavaScript has a powerful de facto standard in npm (and popular alternatives like yarn and pnpm). Python's pip is the low-level foundation, while modern workflows rely on one of several coexisting, high-level management tools like Poetry, PDM, Hatch, and Rye, each with different focuses.

  3. State of Evolution: JavaScript established a mature dependency management model early on. Python, in recent years, has begun to truly unify its modern dependency ecosystem through the standardization of pyproject.toml, providing developers with a smooth experience comparable to the front-end world. Developers can choose the best tool based on their project's needs.