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 jestuv add updates pyproject.toml and writes a lockfile (uv.lock). To install from an existing lockfile on a fresh clone:
uv sync # like npm ciuv 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 filesuv 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 runwith explicit commands. Install pytest-cov withuv add --dev pytest-cov, thenuv run pytest --covreplacesnpm test. Tab completion and shell history make repeated commands easier.- Makefile. A
Makefilewith targets likemake testandmake lintis the closest analog tonpm run. It works on macOS and Linux out of the box. Windows users needmakeinstalled or can use just as a cross-platform alternative. - Third-party tools. taskipy adds a
[tool.taskipy.tasks]section topyproject.toml, which is the closest structural match topackage.jsonscripts.
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 setsCheck 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 indescribe/itblocks. - Test discovery is by naming convention: files named
test_*.py(and*_test.py) containing functions namedtest_*. - Fixtures replace
beforeEach/afterEachwith a dependency-injection model.
# test_math.py
def test_addition():
assert 1 + 1 == 2Python’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 jestPackage 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 PyPInpm 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
Dockerfilethat installs dependencies withuv sync --frozenand copies the source. - Platform-managed: services like Heroku, Railway, or Render that read
pyproject.tomland 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 eslintExtend 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.