Intro
I've been writing TypeScript and Next.js for years. Python? I used it in college for LeetCode problems, but that was about it. Fast forward to now, and I needed to build a real Python project—a Slack bot using Flask and Google Cloud.
The Python ecosystem has changed a lot since my college days. Modern Python tooling now resembles the JavaScript world I'm familiar with, especially with tools like uv
that bring the simplicity of npm
and pnpm
to Python development.
Here's what I learned setting up a modern Python development environment, with constant comparisons to TypeScript/Next.js to make the transition easier.
The Old Way vs The New Way
Traditional Python Setup (What I Remembered)
Back in college, setting up Python meant juggling multiple tools:
1# Install pyenv (like nvm)2brew install pyenv34# Set up virtual environment5python -m venv .venv6source .venv/bin/activate # Don't forget this!78# Install packages9pip install flask10pip freeze > requirements.txt
This worked, but it was clunky. You had to remember to activate the virtual environment every time. Forgetting meant installing packages globally or getting weird import errors.
The Modern Approach: uv
Enter uv - an all-in-one tool that replaces pyenv
, pip
, venv
, and poetry
. It's written in Rust, blazingly fast (10-100x faster than pip), and works like modern JavaScript tools.
Think of it as:
uv
≈pnpm
orbun
(all-in-one package manager)pyproject.toml
≈package.json
uv.lock
≈package-lock.json
Setting Up Python with uv
Installation
1# Install uv (macOS/Linux)2curl -LsSf https://astral.sh/uv/install.sh | sh34# Or with Homebrew5brew install uv67# Verify8uv --version
Creating a Project
1# Create new project2mkdir my-python-app3cd my-python-app4uv init56# This creates:7# - .python-version (like .nvmrc)8# - pyproject.toml (like package.json)9# - .venv/ (virtual environment, like node_modules)
Installing Python Versions
1# Install specific Python version2uv python install 3.1234# Pin it to your project5uv python pin 3.12
This is like using nvm install 18
and creating a .nvmrc
file - but all in one command.
Managing Dependencies
1# Add a package (like npm install express)2uv add flask34# Add dev dependency (like npm install -D jest)5uv add --dev pytest black mypy67# Install all dependencies (like npm install)8uv sync910# Run Python with the right environment (like npx)11uv run python app/main.py
The best part? No need to activate the virtual environment. uv run
automatically uses your project's isolated Python environment.
The pyproject.toml File
Here's what a typical pyproject.toml
looks like:
1[project]2name = "my-python-app"3version = "0.1.0"4requires-python = ">=3.12"5dependencies = [6 "flask>=3.1.2",7 "slack-bolt>=1.26.0",8]910[dependency-groups]11dev = [12 "black>=25.9.0",13 "pytest>=8.4.2",14 "mypy>=1.18.2",15]
Comparison to package.json:
1{2 "name": "my-nextjs-app",3 "version": "0.1.0",4 "dependencies": {5 "next": "^14.0.0",6 "react": "^18.0.0"7 },8 "devDependencies": {9 "prettier": "^3.0.0",10 "eslint": "^8.0.0"11 }12}
Pretty similar, right?
Python Development Tools
Coming from TypeScript, I wanted the same developer experience: formatting, linting, and type checking. Here's the Python equivalent of your JavaScript toolchain.
Code Formatting (Like Prettier)
Black is Python's Prettier - opinionated, automatic formatting:
1# Add black2uv add --dev black34# Format all files5uv run black .67# Check without formatting8uv run black --check .
Configuration in pyproject.toml
:
1[tool.black]2line-length = 88 # Like prettier's printWidth3target-version = ['py312']
Linting (Like ESLint)
Flake8 catches code issues:
1# Add flake82uv add --dev flake834# Lint your code5uv run flake8 app/
Configuration in .flake8
:
1[flake8]2max-line-length = 883exclude = .venv,__pycache__
Type Checking (Like tsc)
Mypy is Python's TypeScript compiler (kind of):
1# Add mypy2uv add --dev mypy34# Type check5uv run mypy app/
Python's type hints look like this:
1# Python with type hints2def greet(name: str) -> str:3 return f"Hello, {name}!"45# TypeScript6function greet(name: string): string {7 return `Hello, ${name}!`8}
Very similar syntax!
Automating Tasks with Makefile
In Next.js, you'd use package.json
scripts:
1{2 "scripts": {3 "dev": "next dev",4 "test": "jest",5 "format": "prettier --write ."6 }7}
In Python, I use a Makefile (common practice in Python projects):
1.PHONY: dev format lint test check23dev:4 uv run python app/main.py56format:7 uv run black .89lint:10 uv run flake8 app/1112test:13 uv run pytest1415check: format lint test16 @echo "✅ All checks passed!"
Usage:
1make dev # Start dev server2make format # Format code3make test # Run tests4make check # Run all checks before committing
Why Makefile instead of npm scripts?
- Familiar to infrastructure/DevOps folks
- Can chain commands with dependencies
- Standard practice in Python ecosystem
- Works great with any language (not just Python)
VSCode Configuration
Getting VSCode to work properly with Python requires some setup. Without it, you'll see import errors even though your code runs fine.
Create .vscode/settings.json
:
1{2 "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",3 "python.analysis.extraPaths": [4 "${workspaceFolder}/.venv/lib/python3.12/site-packages"5 ],6 "python.languageServer": "Pylance",7 "editor.formatOnSave": true,8 "[python]": {9 "editor.defaultFormatter": "ms-python.black-formatter"10 },11 "python.terminal.activateEnvironment": false12}
Required VSCode Extensions:
- Python (ms-python.python)
- Pylance (ms-python.vscode-pylance) - Like TypeScript's language server
- Black Formatter (ms-python.black-formatter)
- Mypy Type Checker (ms-python.mypy-type-checker)
After installing, select your interpreter:
- Open command palette (
Cmd+Shift+P
) - Type "Python: Select Interpreter"
- Choose
./.venv/bin/python
Now imports will autocomplete, and you'll get inline type checking—just like in TypeScript.
Common Gotchas Coming from TypeScript
1. "Command not found" Even Though Package is Installed
Problem:
1uv add --dev black2black . # ❌ command not found
Solution:
Always use uvx
to run binaries from your virtual environment:
1uvx black . # ✅ Works
This is like using npx
in JavaScript - it ensures you're using the project-local version, not a global one.
2. Forgetting to Run uv sync
After Pulling
Problem:
1git pull2uv run pytest # ❌ ImportError: module not found
Solution:
1git pull2uv sync # Like npm install3uvx pytest # ✅ Works
I created an alias to avoid this:
1# In ~/.zshrc2alias gup='git pull && uv sync'
3. Import Errors in IDE But Code Runs Fine
Problem: VSCode shows red squiggles on imports, but uv run python app.py
works.
Solution: Your IDE is using the wrong Python interpreter. Follow the VSCode setup steps above to point it to .venv/bin/python
.
4. Module Files (__init__.py
)
In Python, folders need __init__.py
files to be treated as packages:
1app/2├── __init__.py # Makes 'app' a package3├── main.py4└── utils/5 ├── __init__.py # Makes 'utils' a package6 └── helpers.py
Think of __init__.py
like index.ts
in TypeScript - it marks the folder as a module.
Quick Command Reference
Here's a cheat sheet mapping JavaScript commands to Python/uv:
Task | JavaScript/npm | Python/uv |
---|---|---|
Install tool | npm install | curl -LsSf https://astral.sh/uv/install.sh | sh |
Init project | npm init | uv init |
Add dependency | npm install express | uv add flask |
Add dev dependency | npm install -D jest | uv add --dev pytest |
Install all deps | npm install | uv sync |
Run script | npm run dev | uv run python app.py |
Run local binary | npx prettier | uv run black |
Format code | npx prettier --write . | uv run black . |
Run tests | npm test | uv run pytest |
Type check | tsc --noEmit | uv run mypy app/ |
Why This Setup is Better
Coming from TypeScript, this modern Python setup feels familiar:
- One tool (
uv
) instead of three (pyenv
+pip
+venv
) - Fast - Rust-based, 10-100x faster than pip
- No manual venv activation -
uv run
handles it - Lock file -
uv.lock
ensures reproducible builds (likepackage-lock.json
) - Familiar workflow - Works like npm/pnpm
The old Python workflow felt archaic. This feels like modern JavaScript tooling.
Project Structure
Here's the complete structure for a Python project:
1my-python-app/2├── .venv/ # Virtual environment (like node_modules)3├── .vscode/4│ └── settings.json # VSCode config5├── app/6│ ├── __init__.py7│ └── main.py8├── tests/9│ ├── __init__.py10│ └── test_main.py11├── .gitignore12├── .python-version # Like .nvmrc13├── pyproject.toml # Like package.json14├── uv.lock # Like package-lock.json15├── Makefile # Task automation16└── README.md
.gitignore:
1# Python2__pycache__/3*.py[cod]4.venv/56# Environment7.env89# IDE10.vscode/11.idea/1213# Testing14.coverage15.pytest_cache/
Wrapping Up
Transitioning from TypeScript to Python felt daunting at first, but modern tooling bridges the gap. The key is finding the right tools - uv
makes Python development feel as smooth as JavaScript.
If you're a TypeScript developer dipping your toes into Python:
- Use
uv
instead of the traditionalpyenv + pip + venv
stack - Set up VSCode properly (or you'll fight import errors)
- Use a Makefile for task automation
- Think of Python's tooling as similar to JavaScript - just different names
In the next article, I'll cover building my first Flask application and how it compares to Next.js API routes.