Python for Node.js Developers
Python is the lingua franca of AI engineering. From PyTorch and TensorFlow to LangChain and LlamaIndex, every major framework in the AI ecosystem is built in Python. If you come from a Node.js and TypeScript background, you already have the mental models you need β you just need to learn the new vocabulary and tooling.
This guide bridges that gap. By the end, you'll understand how your existing knowledge maps to the Python world and be ready to contribute to our AI engineering projects.
Why Python for AI Engineeringβ
The AI/ML ecosystem is overwhelmingly Python-native. Here is why:
- Framework dominance. PyTorch, TensorFlow, JAX, LangChain, LlamaIndex, Hugging Face Transformers β all first-class Python APIs.
- Data science stack. NumPy, Pandas, Polars, scikit-learn form the foundation of data manipulation and analysis.
- Rapid prototyping. Python's dynamic typing and REPL-friendly nature make it ideal for experimentation.
- Community and documentation. When you encounter an error in an AI library, the top search results will almost always be Python-centric.
This does not mean Python replaces TypeScript in your toolkit. Many of our services use a hybrid approach: Python for AI/ML workloads, TypeScript for APIs and frontend. But you need to be fluent in both.
The Mental Model: Node.js β Python Mappingβ
The fastest way to learn Python is to understand what corresponds to the tools you already know. The following table maps your existing ecosystem to the Python equivalents we use on this team.
| Node.js / JavaScript Concept | Python Equivalent | Notes |
|---|---|---|
npm | uv pip | uv is our package manager β orders of magnitude faster than pip |
package.json | pyproject.toml | Single file for metadata, dependencies, and tool configuration |
node_modules/ | .venv/ | Isolated virtual environment for project dependencies |
tsconfig.json + TypeScript | Type hints + ty | Python has optional static typing; ty checks it at 10β100Γ the speed of mypy |
| ESLint + Prettier | ruff | One tool replaces both linting and formatting, 10β100Γ faster than Flake8/Black |
| Jest | pytest | The de facto Python test runner |
npx | uvx | Run a tool without installing it globally |
npm init | uv init | Bootstrap a new project with sensible defaults |
package-lock.json | uv.lock | Deterministic lockfile for reproducible installs |
.eslintrc / .prettierrc | pyproject.toml (inline) | Tool config lives alongside project metadata |
ts-node | uv run | Execute Python scripts with dependencies resolved |
The biggest conceptual shift is not syntax β it is dependency management. In Node.js, npm install writes to node_modules/. In Python, uv sync writes to .venv/. The virtual environment is your project's isolated runtime, and uv manages it automatically.
Setting Up Your Environment with uvβ
What is uv?β
uv is an extremely fast Python package and project manager written in Rust by Astral. It replaces pip, virtualenv, pip-tools, and more β all while being orders of magnitude faster. It is the single most important tool in the Python workflow for our team.
Installing uvβ
- macOS / Linux (standalone)
- macOS (Homebrew)
- Windows
curl -LsSf https://astral.sh/uv/install.sh | sh
brew install uv
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Verify the installation:
$ uv --version
uv 0.x.x
Enable autocompletion for a better experience:
# zsh (default on macOS)
echo 'eval "$(uv generate-shell-completion zsh)"' >> ~/.zshrc
# bash
echo 'eval "$(uv generate-shell-completion bash)"' >> ~/.bashrc
Your First Projectβ
Here is the complete workflow side by side with its npm equivalent:
- npm workflow
- uv workflow
# 1. Create project directory
mkdir my-project && cd my-project
# 2. Initialize package.json
npm init -y
# 3. Install dependencies
npm install express
npm install --save-dev typescript eslint prettier
# 4. Install and lock
npm ci # uses package-lock.json
# 5. Run a script
npm run dev
# 1. Create project directory
mkdir my_project && cd my_project
# 2. Initialize pyproject.toml + .python-version
uv init
# 3. Add dependencies
uv add fastapi
uv add --dev ruff ty pytest
# 4. Sync environment (creates .venv/ and uv.lock)
uv sync
# 5. Run a script
uv run main.py
Understanding the Project Filesβ
After running uv init, your project looks like this:
my_project/
βββ .git/
βββ .gitignore
βββ .python-version # Pinned Python version (e.g., "3.12")
βββ .venv/ # Virtual environment (created on first uv sync/run)
β βββ bin/
β βββ lib/
β βββ pyvenv.cfg
βββ main.py # Entry point
βββ pyproject.toml # Project metadata + dependencies
βββ uv.lock # Lockfile (like package-lock.json)
The pyproject.toml is the heart of your project configuration:
[project]
name = "my_project"
version = "0.1.0"
description = "My AI engineering project"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi",
"pydantic",
]
[tool.uv.dev-dependencies]
ruff = ">=0.4.0"
ty = ">=0.0.1"
pytest = ">=8.0.0"
| npm command | uv equivalent |
|---|---|
npm install <pkg> | uv add <pkg> |
npm uninstall <pkg> | uv remove <pkg> |
npm install (from lockfile) | uv sync |
npm run <script> | uv run <command> |
npx <tool> | uvx <tool> |
npm outdated | uv lock --upgrade-package <pkg> |
npm ls | uv tree |
Python Syntax Crash Courseβ
This section highlights the key differences between JavaScript/TypeScript and Python. It is not a complete Python tutorial β for that, see the official Python tutorial. Instead, it focuses on what will trip you up coming from JavaScript.
Variables and Type Hintsβ
Python is dynamically typed, but you can (and should) add type hints. Think of type hints as TypeScript types written inline.
- TypeScript
- Python
let name: string = "Alice";
let age: number = 30;
let isActive: boolean = true;
let tags: string[] = ["ai", "python"];
let config: Record<string, unknown> = { key: "value" };
// Optional types
let middleName: string | undefined;
name: str = "Alice"
age: int = 30
is_active: bool = True
tags: list[str] = ["ai", "python"]
config: dict[str, object] = {"key": "value"}
# Optional types
from typing import Optional
middle_name: Optional[str] = None
Key differences:
- No
let/const/var. All variables are mutable by default. If you need an immutable constant, by convention, useUPPER_SNAKE_CASE. nullβNone. Python has a single null value:None.true/falseβTrue/False. Note the capitalization.undefineddoes not exist. Uninitialized variables raise aNameError.- Type hints are optional at runtime. They exist for static analysis (checked by
ty), not for runtime enforcement.
Functions and Type Annotationsβ
- TypeScript
- Python
function greet(name: string, times: number = 1): string {
return Array(times).fill(`Hello, ${name}!`).join("\n");
}
// Async function
async function fetchData(url: string): Promise<string> {
const response = await fetch(url);
return response.text();
}
def greet(name: str, times: int = 1) -> str:
return "\n".join([f"Hello, {name}!" for _ in range(times)])
# Async function
import asyncio
async def fetch_data(url: str) -> str:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
Key differences:
functionβdef. Python uses thedefkeyword.- Parameter syntax. Default values use
=just like JavaScript, but the type annotation goes before the=. - Return types. The arrow
->specifies the return type, placed after the parameter list. - No
Promise. Python usesasync/awaitwith coroutines. Theasync defsyntax is similar, but there is noPromisetype β the return type hint is the unwrapped value. - Indentation matters. Python uses whitespace (4 spaces) for block structure, not curly braces.
Classes and Dataclassesβ
Python has classes, but for data-carrying structures, dataclasses are the Python equivalent of TypeScript interfaces or DTOs.
- TypeScript
- Python
interface User {
id: number;
name: string;
email: string;
role: "admin" | "user" | "moderator";
}
class UserService {
private users: User[] = [];
add(user: User): void {
this.users.push(user);
}
findById(id: number): User | undefined {
return this.users.find(u => u.id === id);
}
}
from dataclasses import dataclass
from typing import Literal
@dataclass
class User:
id: int
name: str
email: str
role: Literal["admin", "user", "moderator"]
class UserService:
def __init__(self) -> None:
self._users: list[User] = []
def add(self, user: User) -> None:
self._users.append(user)
def find_by_id(self, id: int) -> User | None:
for user in self._users:
if user.id == id:
return user
return None
Key differences:
interfaceβ@dataclass. Dataclasses auto-generate__init__,__repr__, and__eq__methods from type annotations.classfields. Python usesselfexplicitly as the first parameter of every instance method.- Private fields. Python uses a leading underscore
_nameas a convention for private (not enforced by the language). |union syntax. Python 3.10+ supportsX | Yfor union types, just like TypeScript.Literaltype. For enumerated string values, useLiteral["a", "b"]instead of a union of string literals.
List and Dict Comprehensionsβ
Python comprehensions are the equivalent of map/filter/reduce chains β but more concise.
- TypeScript
- Python
// Filter and transform
const ids = users
.filter(u => u.role === "admin")
.map(u => u.id);
// Build an object from an array
const userMap = users.reduce((acc, u) => {
acc[u.id] = u.name;
return acc;
}, {} as Record<number, string>);
# Filter and transform (list comprehension)
ids = [user.id for user in users if user.role == "admin"]
# Build a dict from a list (dict comprehension)
user_map = {user.id: user.name for user in users}
The pattern is always: [expression for item in iterable if condition]. Read it left to right: "for each item in iterable, if condition, collect expression."
f-Stringsβ
Python's f-strings are the equivalent of template literals, with a slightly different syntax.
- TypeScript
- Python
const name = "Alice";
const score = 95;
console.log(`User ${name} scored ${score} points`);
console.log(`Result: ${score > 90 ? "pass" : "fail"}`);
name = "Alice"
score = 95
print(f"User {name} scored {score} points")
print(f"Result: {'pass' if score > 90 else 'fail'}")
Async/Await in Pythonβ
Python's async model is similar to JavaScript's but has some important differences.
- TypeScript
- Python
async function main() {
const [data1, data2] = await Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json()),
]);
console.log(data1, data2);
}
import asyncio
import aiohttp
async def main() -> None:
async with aiohttp.ClientSession() as session:
users_task = session.get("/api/users")
posts_task = session.get("/api/posts")
users_resp, posts_resp = await asyncio.gather(
users_task, posts_task
)
data1 = await users_resp.json()
data2 = await posts_resp.json()
print(data1, data2)
asyncio.run(main())
Key differences:
- No global event loop. Python requires an explicit
asyncio.run()to start the event loop (unlike Node.js which has one built in). Promise.allβasyncio.gather. The equivalent for running coroutines concurrently.async withβ context managers. Python'sasync withis the equivalent of wrapping I/O in a context that handles cleanup (liketry/finally).
Project Structureβ
Python projects follow a different convention than Node.js projects. Understanding the layout is critical for knowing where to put code and how imports work.
- Typical Node.js Project
- Typical Python Project
my-project/
βββ package.json
βββ package-lock.json
βββ tsconfig.json
βββ .eslintrc.json
βββ .prettierrc
βββ src/
β βββ index.ts # Entry point
β βββ routes/
β β βββ users.ts
β βββ services/
β β βββ ai-service.ts
β βββ utils/
β βββ helpers.ts
βββ tests/
β βββ users.test.ts
βββ dist/ # Compiled output
my_project/
βββ pyproject.toml # Dependencies + tool config
βββ uv.lock # Lockfile
βββ .python-version # Pinned Python version
βββ .venv/ # Virtual environment (gitignored)
βββ src/
β βββ my_project/ # Package directory
β βββ __init__.py # Makes it a package
β βββ main.py # Entry point
β βββ routes/
β β βββ __init__.py
β β βββ users.py
β βββ services/
β β βββ __init__.py
β β βββ ai_service.py
β βββ utils/
β βββ __init__.py
β βββ helpers.py
βββ tests/
βββ __init__.py
βββ test_users.py
__init__.py FilesEvery directory that contains Python code needs an __init__.py file (which can be empty). This is Python's way of saying "this directory is a package." It is the equivalent of how Node.js treats any folder as importable by default.
Import Pathsβ
In Node.js, you import using file paths:
import { getUser } from "../services/ai-service";
In Python, you import using package names:
from my_project.services.ai_service import get_user
The import path starts from the package root (the directory containing __init__.py), not from the current file. This is one of the most common sources of confusion for Node.js developers.
Our Tooling Stackβ
Our team uses three tools from the Astral ecosystem. Together, they replace what would be six or more separate tools in the JavaScript world.
ruff β Linting and Formattingβ
ruff is an extremely fast Python linter and code formatter written in Rust. It replaces Flake8, isort, Black, pydocstyle, pyupgrade, autoflake, and dozens of plugins β all while running 10β100Γ faster.
Key commands:
# Lint the project
uvx ruff check .
# Auto-fix fixable issues
uvx ruff check --fix .
# Format the project
uvx ruff format .
# Lint and format in one go
uvx ruff check --fix . && uvx ruff format .
Configuration lives in pyproject.toml:
[tool.ruff]
target-version = "py312"
line-length = 88
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"D100", # Missing docstring on module
"D104", # Missing docstring on public package
]
[tool.ruff.lint.pydocstyle]
convention = "google"
In the JavaScript world, you configure ESLint for linting, Prettier for formatting, and often add plugins (eslint-plugin-import, eslint-plugin-unused-imports, etc.) to bridge gaps. With ruff, one tool does everything with over 900 built-in rules.
ty β Type Checkingβ
ty is an extremely fast Python type checker and language server written in Rust. It is 10β100Γ faster than mypy and Pyright, making it practical to run on every edit.
Key commands:
# Type check the project
uvx ty check
# Type check a single file
uvx ty check src/my_project/main.py
Configuration in pyproject.toml:
[tool.ty]
python-version = "3.12"
ty is production-ready for most use cases but is still in beta. Its type system supports Python 3.10+ and covers the vast majority of typing features including generics, protocols, and type narrowing.
Pre-commit Hooksβ
We wire ruff and ty into pre-commit hooks so that quality checks run automatically before every commit. A typical .pre-commit-config.yaml:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/astral-sh/ty
rev: v0.0.33
hooks:
- id: ty
Install the hooks:
uv add --dev pre-commit
uv run pre-commit install
After installation, ruff and ty run automatically on staged files whenever you run git commit.
Your First Day Checklistβ
Here is a practical checklist to get you productive on day one:
- Install uv β
curl -LsSf https://astral.sh/uv/install.sh | sh - Clone the repository β
git clone <repo-url> && cd <repo-name> - Sync dependencies β
uv sync - Run the linter β
uvx ruff check . - Run the type checker β
uvx ty check - Run the tests β
uv run pytest - Install pre-commit hooks β
uv run pre-commit install - Make your first change β write code, commit, and let the hooks validate it
Key Takeawaysβ
- uv is your new npm. It manages packages, virtual environments, and lockfiles β all in one extremely fast tool.
- pyproject.toml is your new package.json. It holds project metadata, dependencies, and tool configuration in a single file.
- Type hints + ty replace TypeScript. Python type hints give you static analysis without sacrificing dynamic flexibility.
- ruff replaces ESLint + Prettier. One tool handles linting, formatting, and import sorting with 900+ rules.
.venv/replacesnode_modules/. Each project gets an isolated virtual environment.uvxreplacesnpx. Run tools without global installation.- Comprehensions replace
map/filterchains. Python's list and dict comprehensions are concise and expressive. asyncio.run()starts the event loop. Unlike Node.js, Python requires an explicit entry point for async code.
Further Readingβ
- uv Documentation β Official uv guides and reference
- ruff Documentation β Official ruff guides and rule catalog
- ty Documentation β Official ty documentation
- Python 3 Documentation β Official Python language reference and tutorial
- Static Typing with Python β Comprehensive guide to Python type hints