Ecosystem & ToolingLesson 2 of 2
Project Setup & Tooling
The critical difference from Node: Python does not isolate dependencies per project by default. npm always installs into the project's node_modules, but a bare pip install puts packages into whichever Python you are running, often shared by every project on your machine. Isolation is something you opt into with a virtual environment.
A virtual environment (venv) is a folder, conventionally .venv, with its own Python interpreter and installed packages. It plays the role of node_modules. The classic workflow uses only built-in tools: python -m venv .venv, then activate it (source .venv/bin/activate on macOS/Linux, .venv\Scripts\activate on Windows), then pip install requests. pip freeze > requirements.txt records the installed versions.
In 2026 most new projects use uv, a fast all-in-one tool that will feel familiar to npm and pnpm users. uv init creates a project with a pyproject.toml (the package.json of Python), uv add requests installs a dependency and creates the .venv for you, uv.lock pins exact versions like package-lock.json, and uv run main.py runs code inside the environment without activating anything. uv can even install Python itself (uv python install 3.13), replacing nvm-style version managers. pyproject.toml is the standard either way; setup.py is the legacy approach.
For code quality, ruff is the ESLint + Prettier of Python: ruff check . lints and ruff format . formats, both configured in pyproject.toml.
Packages are directories of modules. A directory with an __init__.py file is a regular package. Without __init__.py, Python 3 still imports it as a namespace package (PEP 420), but namespace packages are meant for splitting one package across several locations, so add __init__.py to your own packages. A common layout is src/your_package/, tests/, pyproject.toml, and README.md. To locate files next to your code, use Path(__file__).parent, Python's __dirname.
For debugging, call breakpoint() anywhere and Python drops into pdb, an interactive debugger in your terminal: n (next), s (step into), c (continue), p expr (print). VS Code and PyCharm offer graphical debuggers too.
Popular libraries: Django (batteries-included web framework, closer to Rails or NestJS than to Next.js), FastAPI and Flask (APIs, like Express or Fastify), requests and httpx (HTTP clients, like axios), SQLAlchemy (ORM, like Prisma or TypeORM), pandas and NumPy (data analysis and numerical computing, with no real JS counterpart).
Quick reference
| JavaScript | Python | Note |
|---|---|---|
| node_modules/ | .venv/ | Per-project environment |
| package.json | pyproject.toml | Metadata, deps, tool config |
| package-lock.json | uv.lock (or requirements.txt) | Pinned versions |
| npm init | uv init | Create a project |
| npm install axios | uv add requests | Or pip install inside a venv |
| npm run / npx | uv run | Run inside the environment |
| nvm install 22 | uv python install 3.13 | Manage interpreter versions |
| ESLint + Prettier | ruff check / ruff format | Lint and format |
| __dirname | Path(__file__).parent | Folder of the current file |
| node --inspect | breakpoint() / python -m pdb | Interactive debugger |
Side by side
// Node.js project setup
// npm init -y -> package.json
// npm install axios -> node_modules/ + package-lock.json
// npx eslint . && npx prettier --write .
import axios from "axios";
import path from "node:path";
const here = __dirname; // CommonJS
const res = await axios.get("https://api.example.com/data");
console.log(res.data);# Modern setup with uv
# uv init my-app -> pyproject.toml
# cd my-app
# uv add requests -> .venv/ + uv.lock
# uv run main.py -> runs inside .venv
# uvx ruff check . -> lint (like npx eslint)
# uvx ruff format . -> format (like prettier)
# Classic setup with built-in tools
# python -m venv .venv
# source .venv/bin/activate (macOS/Linux)
# .venv\Scripts\activate (Windows)
# pip install requests
# pip freeze > requirements.txt
from pathlib import Path
import requests
BASE_DIR = Path(__file__).parent # like __dirname
response = requests.get("https://api.example.com/data")
response.raise_for_status() # error on 4xx/5xx
breakpoint() # inspect in pdb
print(response.json())
# Project structure:
# my-app/
# pyproject.toml
# uv.lock
# src/my_app/
# __init__.py
# core.py
# tests/Exercise
Real Python, running in your browserSet up a project and write the code (terminal commands go in comments):
# As comments (terminal commands):
# 1. Create a project / environment (uv init, or python -m venv .venv)
# 2. Add the 'requests' package (uv add requests, or pip install requests)
# 3. Lint the code with ruff
# As Python code:
# 4. BASE_DIR = the folder of the current file (Python's __dirname)
# 5. def get_data(): GET https://api.example.com/data with requests,
# call raise_for_status(), and return the parsed JSON
# 6. print(get_data())
(The network is simulated here, so requests.get returns fake data.){'items': [1, 2, 3], 'source': 'https://api.example.com/data'}Ctrl + Enter runCtrl + Shift + Enter checkTab indentEsc then Tab to leave the editorCtrl + / comment