For JavaScript Developers: Comparing Python Package Management to npm and package.json

Understanding the similarities and differences between Python and JavaScript dependency management

Bridging Two Worlds: Python vs JavaScript Dependencies

If you're coming from a JavaScript background, you're likely familiar with npm (or yarn) and package.json. Let's explore how Python's approach to dependency management compares, highlighting both the conceptual similarities and important differences that will help you transition smoothly between these ecosystems.

Core Concepts Comparison

The Package Manager

JavaScript: npm (Node Package Manager) or yarn serve as the central tools for managing JavaScript dependencies.

Python: pip (Pip Installs Packages) is Python's primary package manager. Like npm, it handles downloading, installing, and managing third-party libraries.

Analogy: If npm is like a department store where you shop for components, pip is like a specialized marketplace. Both help you acquire what you need, but with slightly different shopping experiences.

Package Registries

JavaScript: npmjs.com hosts most JavaScript packages.

Python: PyPI (Python Package Index) at pypi.org is the main registry for Python packages.

Both ecosystems have massive libraries of open-source packages available. PyPI hosts over 400,000 packages, while npm has over 1.3 million packages. The difference in numbers partly reflects the more focused nature of Python packages versus the granular component-based approach common in JavaScript.

Dependency Declaration

package.json vs requirements.txt

The most fundamental difference is how dependencies are declared:

JavaScript: package.json

{
  "name": "my-awesome-app",
  "version": "1.0.0",
  "description": "An awesome application",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "test": "jest"
  },
  "dependencies": {
    "express": "^4.17.1",
    "axios": "^0.21.1",
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "jest": "^27.0.6",
    "nodemon": "^2.0.12"
  }
}

Python: requirements.txt

# Basic format
requests==2.26.0
flask==2.0.1
pandas==1.3.2
pytest==6.2.5

Key differences:

Real-world implication: When porting a JavaScript project to Python, you'll need to distribute information across multiple files rather than centralizing everything in a single package.json.

Version Specifications

Version Range Syntax

JavaScript (npm)

"dependencies": {
  "express": "^4.17.1",     // Compatible with 4.17.1 up to (not including) 5.0.0
  "react": "~16.13.1",      // Compatible with 16.13.1 up to (not including) 16.14.0
  "moment": "2.29.1",       // Exactly version 2.29.1
  "lodash": ">4.0.0",       // Greater than 4.0.0
  "axios": ">=0.21.0 <0.22.0"  // Between versions (inclusive/exclusive)
}

Python (pip)

# Requirements.txt
requests==2.26.0          # Exactly version 2.26.0
flask>=2.0.0,<3.0.0       # Between 2.0.0 (inclusive) and 3.0.0 (exclusive)
pandas~=1.3.2             # Compatible release (>= 1.3.2, < 1.4.0)
pytest>6.0.0              # Greater than 6.0.0
django<=3.2.6             # Less than or equal to 3.2.6

Both systems allow for specifying version constraints, but with different syntax:

Real-world approach: Many Python projects use exact versions in production to ensure reproducibility, while JavaScript projects often use ranges to automatically get compatible updates. This reflects different community philosophies about dependency management.

Lock Files: Ensuring Reproducibility

JavaScript: package-lock.json or yarn.lock

{
  "name": "my-project",
  "lockfileVersion": 2,
  "requires": true,
  "packages": {
    "": {
      "dependencies": {
        "express": "^4.17.1"
      }
    },
    "node_modules/express": {
      "version": "4.17.1",
      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
      "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
      "dependencies": {
        "accepts": "~1.3.7",
        "array-flatten": "1.1.1",
        // ... more nested dependencies
      }
    },
    // ... many more packages
  }
}

Python: Pipfile.lock (with pipenv)

{
    "_meta": {
        "hash": {
            "sha256": "a82b674d67d29678775ff6b773de1686a9593749ec14483b0d8e05131b761769"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.9"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "requests": {
            "hashes": [
                "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
                "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
            ],
            "index": "pypi",
            "version": "==2.26.0"
        },
        // ... more dependencies
    },
    "develop": {
        "pytest": {
            "hashes": [
                "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b",
                "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"
            ],
            "index": "pypi",
            "version": "==6.2.4"
        },
        // ... more dev dependencies
    }
}

Key similarities:

Key differences:

Real-world implication: For production Python applications, consider using pipenv or poetry to get the benefits of lock files that JavaScript developers take for granted with npm.

Environment Isolation

node_modules vs. Virtual Environments

Aspect JavaScript (npm) Python
Isolation Method Project-local node_modules directory Virtual environments (venv, virtualenv)
Scope Installed dependencies live inside the project Virtual environments can be inside or outside the project
Activation Automatic (Node.js finds modules in node_modules) Explicit activation required (source venv/bin/activate)
Global vs Local Clear distinction with -g flag System Python vs virtualenv Python
Storage Nested dependency tree in node_modules Flat structure in site-packages directory

JavaScript approach:

# Installing dependencies locally (default)
npm install express 

# Installing a global tool
npm install -g create-react-app

Python approach:

# Create a virtual environment
python -m venv myproject_env

# Activate the environment
# On Windows:
myproject_env\Scripts\activate
# On macOS/Linux:
source myproject_env/bin/activate

# Install dependencies in the active environment
pip install requests

# Deactivate when done
deactivate

Analogy: If npm's approach is like having a dedicated toolbox for each project where tools automatically appear when needed, Python's virtual environments are like separate workshops that you consciously enter and exit.

Real-world implication: The explicit activation step in Python can be a source of confusion for JavaScript developers. Modern tools like pipenv try to bridge this gap with commands like pipenv shell and pipenv run.

Modern Python Dependency Tools

Several tools have emerged to make Python's dependency management more similar to npm:

Pipenv: Combining pip and virtualenv

Pipenv creates a workflow similar to npm by combining package management and virtual environment management:

# Initialize a project (like npm init)
pipenv --python 3.9

# Install a package (like npm install express)
pipenv install requests

# Install a development dependency (like npm install --save-dev jest)
pipenv install --dev pytest

# Run a command in the virtual environment (like npm run)
pipenv run python app.py

# Activate the environment (no npm equivalent)
pipenv shell

Pipenv generates two files:

Poetry: Modern Python Packaging

Poetry takes an approach even more similar to npm:

# Initialize a new project
poetry new myproject

# Add dependencies
poetry add requests

# Add development dependencies
poetry add --dev pytest

# Run commands
poetry run python app.py

# Activate the virtual environment
poetry shell

Poetry uses:

Real-world recommendation: For JavaScript developers transitioning to Python, pipenv or poetry will feel more familiar than the traditional pip+virtualenv workflow.

Scripts and Task Running

JavaScript (npm)

"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js",
  "test": "jest",
  "build": "webpack --mode production",
  "lint": "eslint src/"
}

Running: npm run dev

Python (pipenv)

[scripts]
start = "python server.py"
dev = "flask run --debug"
test = "pytest"
lint = "flake8 src/"

Running: pipenv run dev

Both ecosystems support defining reusable commands, but:

Real-world approach: Many Python projects use Makefiles or shell scripts for complex task running that would be handled by npm scripts in JavaScript projects.

Managing Dependencies in Docker Environments

Both ecosystems have best practices for containerizing applications:

JavaScript Dockerfile

# Multi-stage build
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:16-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --production
CMD ["node", "dist/server.js"]

Python Dockerfile

# Multi-stage build
FROM python:3.9 AS builder
WORKDIR /app
COPY requirements*.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python setup.py build

FROM python:3.9-slim
WORKDIR /app
COPY --from=builder /app/build ./build
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "-m", "myapp.server"]

Key similarities:

Key differences:

Real-world practice: For Python applications, consider creating requirements.prod.txt and requirements.dev.txt files to mirror npm's production/development dependency separation.

Typical Workflow Comparison

Starting a New Project

JavaScript Workflow

# Create and initialize a project
mkdir myproject && cd myproject
npm init -y

# Install dependencies
npm install express mongoose

# Install development dependencies
npm install --save-dev jest nodemon

# Add scripts to package.json
# (edit package.json)

# Start development
npm run dev

Traditional Python Workflow

# Create project and virtual environment
mkdir myproject && cd myproject
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
pip install flask pymongo

# Install development dependencies
pip install pytest pytest-cov

# Freeze requirements
pip freeze > requirements.txt

# Create setup.py or pyproject.toml for metadata
# (create these files manually)

# Start development
python app.py

Modern Python Workflow (with Poetry)

# Create and initialize a project
poetry new myproject
cd myproject

# Install dependencies
poetry add flask pymongo

# Install development dependencies
poetry add --dev pytest pytest-cov

# Edit pyproject.toml to add scripts
# (edit pyproject.toml)

# Start development
poetry run python app.py

Real-world insight: The modern Python tools like poetry create a developer experience much closer to npm, which can ease the transition for JavaScript developers.

Additional Considerations

Monorepo Support

JavaScript: Tools like Lerna, npm workspaces, or Yarn workspaces provide robust monorepo support.

Python: No standard monorepo solution exists, though tools like poetry can handle basic monorepo setups with its package management capabilities.

Peer Dependencies

JavaScript: npm has a concept of peerDependencies for plugin ecosystems.

Python: No direct equivalent exists, though "extras" in setup.py can serve a similar purpose for optional feature sets.

Security Auditing

JavaScript: npm audit checks dependencies for known vulnerabilities.

Python: Tools like safety and pip-audit provide similar functionality but aren't built into pip directly.

Caching

JavaScript: npm and yarn cache packages automatically.

Python: pip caches downloaded packages but provides fewer commands for managing the cache.

Bridging the Gap: Best Practices

If you're a JavaScript developer working with Python, consider these practices to make the transition smoother:

The JavaScript and Python ecosystems have been evolving in parallel, with Python gradually adopting some of the developer experience improvements that npm pioneered. Understanding both systems allows you to bring the best practices from each world to your development process.

Practical Exercise: Converting Between Systems

To solidify your understanding, try converting this package.json to equivalent Python dependency files:

{
  "name": "api-service",
  "version": "1.0.0",
  "description": "API service for data processing",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "jest",
    "lint": "eslint ."
  },
  "dependencies": {
    "express": "^4.17.1",
    "mongoose": "^6.0.12",
    "axios": "^0.24.0",
    "dotenv": "^10.0.0"
  },
  "devDependencies": {
    "jest": "^27.3.1",
    "nodemon": "^2.0.14",
    "eslint": "^8.1.0"
  }
}

Create both traditional Python files and a modern Poetry configuration:

This exercise will help you understand how to translate between the two ecosystems when working on cross-language teams.