Project Anatomy — Packages, Imports, and pyproject.toml
Files, modules, and packages
Section titled “Files, modules, and packages”In Node.js every .js/.ts file is a module. You import from it with import. Python works the same way, but the vocabulary is slightly different:
- Module — any
.pyfile.math.pyis a module namedmath. - Package — a directory containing an
__init__.pyfile. It groups related modules. - Namespace package — a directory without
__init__.py(Python 3.3+). Rarely needed.
// Node/TypeScript module layoutsrc/ utils/ string.ts ← module number.ts ← module api/ index.ts ← entry point index.ts ← root entry
// import from a moduleimport { capitalize } from "./utils/string";# Python package layoutsrc/ utils/ __init__.py ← marks this as a package string.py ← module number.py ← module api/ __init__.py routes.py main.py ← entry point
# import from a modulefrom utils.string import capitalizeThe __init__.py file
Section titled “The __init__.py file”__init__.py is the Python equivalent of index.ts in a directory — it marks the directory as a package and optionally re-exports symbols. An empty __init__.py is valid and is the minimum required.
// TypeScript: src/utils/index.tsexport { capitalize } from "./string";export { clamp } from "./number";
// Caller imports from the directory:import { capitalize, clamp } from "./utils";# Python: src/utils/__init__.pyfrom .string import capitalizefrom .number import clamp
# Caller imports from the package:from utils import capitalize, clampImport syntax
Section titled “Import syntax”Python has two import forms. Both are common; which you use depends on context.
// TypeScript import stylesimport path from "path"; // default importimport { join, resolve } from "path"; // named importsimport * as fs from "fs"; // namespace importimport type { Dirent } from "fs"; // type-only import# Python import stylesimport math # import the whole modulefrom math import sqrt, pi # import specific namesfrom math import sqrt as sq # aliasimport os.path # sub-module importfrom . import sibling # relative: same packagefrom ..utils import helper # relative: parent packagepyproject.toml — the package.json equivalent
Section titled “pyproject.toml — the package.json equivalent”Modern Python projects use pyproject.toml as their single project manifest. It replaces setup.py, setup.cfg, and partly requirements.txt.
// package.json (Node){ "name": "my-api", "version": "1.0.0", "description": "A REST API", "main": "dist/index.js", "scripts": { "dev": "ts-node src/index.ts", "build": "tsc", "test": "jest" }, "dependencies": { "express": "^4.18.2" }, "devDependencies": { "typescript": "^5.0.0", "jest": "^29.0.0" }}# pyproject.toml (Python — PEP 517/518/621)[project]name = "my-api"version = "1.0.0"description = "A REST API"requires-python = ">=3.11"dependencies = [ "fastapi>=0.110.0", "uvicorn>=0.27.0",]
[project.optional-dependencies]dev = ["pytest>=8.0", "mypy>=1.8"]
[project.scripts]start = "my_api.main:main" # like "main" in package.json
[build-system]requires = ["hatchling"]build-backend = "hatchling.build"requirements.txt — the package-lock equivalent
Section titled “requirements.txt — the package-lock equivalent”requirements.txt is a flat list of pinned package versions. It is simpler than package-lock.json but fulfils the same purpose: reproducible installs for teammates and CI.
# Nodenpm install # reads package.json + package-lock.jsonnpm ci # clean install, fails if lock is missing
# Add a packagenpm install express # auto-updates package.json + lock# Python (classic)pip install -r requirements.txt # install from snapshot
# Generate the snapshotpip freeze > requirements.txt
# Add a packagepip install fastapipip freeze > requirements.txt # update snapshot manually
# Modern: use poetry or uvpoetry add fastapi # adds to pyproject.toml + lockTypical project layout
Section titled “Typical project layout”Here is a realistic Python API project layout, annotated with Node equivalents:
# Node/TypeScript projectmy-api/ src/ index.ts ← entry point routes/ users.ts services/ user.service.ts package.json ← manifest package-lock.json ← lockfile tsconfig.json ← compiler config node_modules/ ← dependencies (gitignored) .env ← secrets (gitignored)# Python projectmy-api/ src/ my_api/ __init__.py main.py ← entry point routes/ __init__.py users.py services/ __init__.py user.py pyproject.toml ← manifest requirements.txt ← lockfile (or poetry.lock) venv/ ← dependencies (gitignored) .env ← secrets (gitignored)# Demonstrating Python imports and stdlib modulesimport mathimport os.path
# Using a stdlib module (like a built-in npm package)radius = 5area = math.pi * radius ** 2print(f"Circle area (r={radius}): {area:.4f}")
# Path manipulation — like Node's path.joinparts = ["src", "my_api", "main.py"]joined = os.path.join(*parts)print(f"Joined path: {joined}")
# __name__ guard — entry point detectiondef main() -> None: print("main() called from entry point")
if __name__ == "__main__": main()Loading Python runtime (first run only)…