From TypeScript to Python: Setting Up a Modern Development Environment

8 min read
Python

Table of Contents

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 pyenv
3
4# Set up virtual environment
5python -m venv .venv
6source .venv/bin/activate # Don't forget this!
7
8# Install packages
9pip install flask
10pip 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:

  • uvpnpm or bun (all-in-one package manager)
  • pyproject.tomlpackage.json
  • uv.lockpackage-lock.json

Setting Up Python with uv

Installation

1# Install uv (macOS/Linux)
2curl -LsSf https://astral.sh/uv/install.sh | sh
3
4# Or with Homebrew
5brew install uv
6
7# Verify
8uv --version

Creating a Project

1# Create new project
2mkdir my-python-app
3cd my-python-app
4uv init
5
6# 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 version
2uv python install 3.12
3
4# Pin it to your project
5uv 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 flask
3
4# Add dev dependency (like npm install -D jest)
5uv add --dev pytest black mypy
6
7# Install all dependencies (like npm install)
8uv sync
9
10# 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]
9
10[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 black
2uv add --dev black
3
4# Format all files
5uv run black .
6
7# Check without formatting
8uv run black --check .

Configuration in pyproject.toml:

1[tool.black]
2line-length = 88 # Like prettier's printWidth
3target-version = ['py312']

Linting (Like ESLint)

Flake8 catches code issues:

1# Add flake8
2uv add --dev flake8
3
4# Lint your code
5uv run flake8 app/

Configuration in .flake8:

1[flake8]
2max-line-length = 88
3exclude = .venv,__pycache__

Type Checking (Like tsc)

Mypy is Python's TypeScript compiler (kind of):

1# Add mypy
2uv add --dev mypy
3
4# Type check
5uv run mypy app/

Python's type hints look like this:

1# Python with type hints
2def greet(name: str) -> str:
3 return f"Hello, {name}!"
4
5# TypeScript
6function 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 check
2
3dev:
4 uv run python app/main.py
5
6format:
7 uv run black .
8
9lint:
10 uv run flake8 app/
11
12test:
13 uv run pytest
14
15check: format lint test
16 @echo "✅ All checks passed!"

Usage:

1make dev # Start dev server
2make format # Format code
3make test # Run tests
4make 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": false
12}

Required VSCode Extensions:

  1. Python (ms-python.python)
  2. Pylance (ms-python.vscode-pylance) - Like TypeScript's language server
  3. Black Formatter (ms-python.black-formatter)
  4. Mypy Type Checker (ms-python.mypy-type-checker)

After installing, select your interpreter:

  1. Open command palette (Cmd+Shift+P)
  2. Type "Python: Select Interpreter"
  3. 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 black
2black . # ❌ 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 pull
2uv run pytest # ❌ ImportError: module not found

Solution:

1git pull
2uv sync # Like npm install
3uvx pytest # ✅ Works

I created an alias to avoid this:

1# In ~/.zshrc
2alias 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 package
3├── main.py
4└── utils/
5 ├── __init__.py # Makes 'utils' a package
6 └── 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:

TaskJavaScript/npmPython/uv
Install toolnpm installcurl -LsSf https://astral.sh/uv/install.sh | sh
Init projectnpm inituv init
Add dependencynpm install expressuv add flask
Add dev dependencynpm install -D jestuv add --dev pytest
Install all depsnpm installuv sync
Run scriptnpm run devuv run python app.py
Run local binarynpx prettieruv run black
Format codenpx prettier --write .uv run black .
Run testsnpm testuv run pytest
Type checktsc --noEmituv 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 (like package-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 config
5├── app/
6│ ├── __init__.py
7│ └── main.py
8├── tests/
9│ ├── __init__.py
10│ └── test_main.py
11├── .gitignore
12├── .python-version # Like .nvmrc
13├── pyproject.toml # Like package.json
14├── uv.lock # Like package-lock.json
15├── Makefile # Task automation
16└── README.md

.gitignore:

1# Python
2__pycache__/
3*.py[cod]
4.venv/
5
6# Environment
7.env
8
9# IDE
10.vscode/
11.idea/
12
13# Testing
14.coverage
15.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:

  1. Use uv instead of the traditional pyenv + pip + venv stack
  2. Set up VSCode properly (or you'll fight import errors)
  3. Use a Makefile for task automation
  4. 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.

Related Articles