Skip to content

Python Tooling for Node.js Developers

npm installs packages, locks versions, runs scripts, and publishes modules. Python splits those responsibilities differently, but uv consolidates most of them into a single command. This guide maps Node.js concepts to their Python equivalents, with emphasis on npm scripts and the daily runtime workflow. For tsc-style type-checking comparisons, see the companion TypeScript developers guide.

Map Node.js tools to Python

Most Node.js workflows have a recognizable Python counterpart:

Node.js Python Notes
nvm / fnm / Volta uv python install + uv python pin Python version management
npm / pnpm / yarn uv Dependency management
package.json pyproject.toml Project metadata and dependencies
package-lock.json / pnpm-lock.yaml uv.lock Pinned dependency graph
node_modules .venv (virtual environment) Per-project isolation
npx uvx Run a CLI tool without permanent installation
ESLint Ruff (linting)
Prettier ruff format
Jest / Mocha / Vitest pytest
npm run <script> No direct equivalent See Replacing npm scripts
npm publish uv build + uv publish Build step is separate
nodemon Framework-specific reloaders No universal --watch

Important

These analogies are orientation aids, not exact equivalences. Each pairing hides real differences in scope and behavior.

A node_modules folder holds downloaded packages for one project. A Python virtual environment also contains a Python interpreter (or symlink to one). Multiple environments can coexist for the same project, each pinned to a different Python version.

Python has no built-in equivalent to package.json scripts. Teams use uv run with explicit commands or Makefiles.

Separate the interpreter, environment, and project

Node.js collapses several concerns into one tool: npm manages dependencies and runs scripts while using whatever Node version is on PATH. Python separates these into three layers.

Layer 1: The interpreter. uv python install 3.12 installs a Python runtime, similar to nvm install 20 or fnm install 20. A .python-version file pins the project’s Python version, serving the same role as .nvmrc or .node-version.

Layer 2: The virtual environment. It attaches to an existing interpreter and holds the project’s installed packages. When you first run uv sync or uv run, uv creates and manages a .venv directory automatically.

Layer 3: The project definition. pyproject.toml declares dependencies and project metadata. Tool configuration lives in [tool.*] sections (e.g., [tool.ruff], [tool.pytest.ini_options]) rather than in separate dotfiles, consolidating what Node.js spreads across .eslintrc and .prettierrc.

Develop with uv

Add and manage dependencies

Adding a dependency mirrors the npm workflow:

# Python (uv)               # Node.js (npm)
uv add requests              # npm install axios
uv add --dev pytest          # npm install --save-dev jest

uv add updates pyproject.toml and writes a lockfile (uv.lock). To install from an existing lockfile on a fresh clone:

uv sync                      # like npm ci

uv sync is closer to npm ci than npm install: it installs exactly what the lockfile specifies without modifying it. Running uv add is the equivalent of npm install <package>, which both installs and updates package.json.

Python supports optional extras and dependency groups for separating dev and test dependencies. These serve the same purpose as splitting dependencies from devDependencies in package.json, with more flexibility for additional groups.

Run Python code

Running a script through uv:

uv run python main.py
uv run main.py               # also works for .py files

uv run ensures the virtual environment exists and dependencies are installed before executing. It fills the role that node execution and node_modules resolution handle together.

Python has two execution modes that affect import resolution. python file.py runs a single file. python -m package runs a package as a module, which adjusts how imports resolve. Node.js developers familiar with the CommonJS require() and ESM import split will recognize the shape of this problem: how you invoke code affects what it can find. When in doubt, prefer uv run python -m mypackage.

Console scripts (entry points defined in pyproject.toml) serve a similar role to the "bin" field in package.json. They create named commands installed into the environment’s bin/ directory:

[project.scripts]
myapp = "mypackage.cli:main"

For library development, the src/ layout keeps source code separate from configuration and tests. Combined with an editable install (which uv sync handles automatically), code changes take effect without reinstalling.

Replace npm scripts

A package.json can define development commands:

{
  "scripts": {
    "dev": "nodemon server.js",
    "test": "jest --coverage",
    "lint": "eslint . && prettier --check .",
    "start": "node server.js"
  }
}

Run commands directly with uv run; add a Makefile when you want short aliases:

  • uv run with explicit commands. Install pytest-cov with uv add --dev pytest-cov, then uv run pytest --cov replaces npm test. Tab completion and shell history make repeated commands easier.
  • Makefile. A Makefile with targets like make test and make lint is the closest analog to npm run. It works on macOS and Linux out of the box. Windows users need make installed or can use just as a cross-platform alternative.
  • Third-party tools. taskipy adds a [tool.taskipy.tasks] section to pyproject.toml, which is the closest structural match to package.json scripts.

Lint and format code

Ruff handles both linting and formatting. It replaces the combination of ESLint and Prettier with one command:

uv run ruff check .          # lint (like npx eslint .)
uv run ruff format .         # format (like npx prettier --write .)

Configuration lives in pyproject.toml under [tool.ruff], not in separate config files:

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = ["E", "F", "I"]    # enable specific rule sets

Check types

Node.js developers who write plain JavaScript can skip this section and return when they want to add type annotations. Python’s type system is opt-in: code runs whether or not it has annotations.

For TypeScript developers, see the TypeScript guide’s type-checking section for a comparison with tsc.

mypy and pyright are the established type checkers. ty is a newer option from the team behind Ruff. See how they compare for help choosing.

uv run mypy .

Because typing is gradual, most teams adopt it incrementally: add annotations to new code and tighten strictness over time. Legacy code often stays untyped until it needs changes.

Run tests with pytest

pytest is Python’s standard testing framework. Its design differs from Jest and Mocha in a few ways:

  • Tests are plain functions prefixed with test_, not wrapped in describe/it blocks.
  • Test discovery is by naming convention: files named test_*.py (and *_test.py) containing functions named test_*.
  • Fixtures replace beforeEach/afterEach with a dependency-injection model.
# test_math.py
def test_addition():
    assert 1 + 1 == 2

Python’s built-in assert statement works without an assertion library. pytest rewrites it at import time to report failed expressions and their values.

uv run pytest                # like npx jest

Package and distribute Python code

Publish libraries

Publishing to PyPI (the Python Package Index) is analogous to npm publish. The package format differs: Python libraries are distributed as wheels (prebuilt) and sdists (source distributions), built by a build backend.

uv build                     # creates wheel and sdist
uv publish                   # uploads to PyPI

npm publishes whatever is in your project directory (minus .npmignore entries). Python’s separate build step exists because packages can contain compiled extensions (C, Fortran, Rust) that need platform-specific handling. For pure-Python packages, the build step adds a few seconds at most.

Deploy applications

Node.js applications deploy as source code with node_modules installed on the target. Python applications deploy the same way, with a virtual environment instead of node_modules.

The most common patterns:

  • Container-based: a Dockerfile that installs dependencies with uv sync --frozen and copies the source.
  • Platform-managed: services like Heroku, Railway, or Render that read pyproject.toml and install dependencies at deploy time.

Node.js developers using pm2 or forever for process management will find that gunicorn and uvicorn serve the same role for Python web applications: process supervision and worker management.

There is no bundling step. Python’s import system loads modules at runtime from the filesystem, so the concept of webpack or esbuild does not apply to server-side Python.

Run CLI tools

npx runs a package’s binary without installing it globally. uvx does the same:

uvx ruff check .             # like npx eslint .

For permanent installation, uv tool install creates an isolated environment per tool, avoiding the dependency conflicts that npm install -g can cause:

uv tool install ruff         # like npm install -g eslint

Extend the workflow with interactive tools

Python’s REPL and IPython support interactive development. Jupyter notebooks combine code, output, and prose for data science and machine learning. tox and nox automate testing across multiple Python versions.

Node’s --watch flag and tools like nodemon have no universal Python equivalent: web frameworks like Django and FastAPI provide their own reload mechanisms, but general-purpose file watching requires extra setup. uv workspaces exist, but monorepo tooling comparable to Turborepo or Nx has not yet matured in the Python ecosystem.

Last updated on