Whetstone.
All courses
pythonBeginner

Modern Python for a TypeScript Developer

A fast, opinionated floor under every Python codebase you are about to read. It assumes total fluency in programming and near-zero recall of Python's surface, so nothing here explains what a for loop is, and everything here tells you how to spell one. Four modules: the syntax refresher pitched at someone who forgot the keywords rather than the concepts; the 2026 toolchain, which is uv and almost nothing else, plus the virtualenv story that explains why uv had to exist; typing, honestly, including how much weaker the guarantees are than TypeScript's and why Pydantic is the part that actually runs; and the four idioms you will meet on your first day in a LangChain or LangGraph repo. Every example is biased toward code you will actually hit, so the decorator lesson looks like a tool definition and the typing lessons look like state schemas.

Start the course 13 lessons 194 min
MPCrash course
Curriculum

13 lessons across 4 modules

01The Spelling4 lessons · 59 min
02The 2026 Toolchain3 lessons · 44 min
03Types, Honestly3 lessons · 46 min
04The Idioms You Will Meet3 lessons · 45 min
Capstone

Port something you already own into Python, and defend every spelling choice

You have the spelling, the toolchain, the type system and the four protocols. The last thing worth doing is the thing that actually converts recognition into recall: writing some.

The task

Pick something you already own in TypeScript. Small. A CLI you wrote, a scraper, a webhook handler, a bit of glue that talks to one API and writes one file. Not a framework, not a rewrite of anything with a UI. Something you could finish in an evening and whose correct behaviour you can already check, because knowing what the right answer looks like is what makes the port a language exercise instead of a debugging exercise.

Port it.

The constraints, which are the whole point

  1. Start it with uv init and never type activate. Every command goes through uv run. If you find yourself sourcing an activate script, you have reached for a 2015 habit and you should stop and ask what you were trying to do.
  2. Every dependency arrives via uv add. No pip install. Commit the uv.lock.
  3. Every function has type hints, and pyright or mypy runs clean over the project. Add it as a dependency group and run it through uv run.
  4. Anything crossing a boundary is a Pydantic model. API responses, config file, CLI arguments. Anything shaped like data that came from outside the program and could be wrong.
  5. Anything internal and trusted is a dataclass or a TypedDict, and you can say in one sentence why it is not a Pydantic model.
  6. If the original did any concurrent IO, the port does it with asyncio.TaskGroup or asyncio.gather, not by awaiting in a loop.

The write-up, which is the part that sticks

Three short answers in a scratch file. Not an essay.

  • Three places where the obvious TypeScript translation was wrong. Not “the syntax was different”. A place where you wrote something that ran, and did something other than what you meant. Mutable defaults, truthiness on an empty collection, a dict you assumed was ordered by key, an await in a loop, a type hint that was quietly a lie at runtime.
  • One place where Python was genuinely better, and why. There is at least one. Comprehensions, keyword arguments, context managers, and slicing are the usual candidates.
  • The single sentence you would tell yourself six months ago, before opening a Python repo for the first time.

The bar

The port runs. The typechecker is clean. The lockfile is committed. And every Pydantic model in it is there because data crosses a boundary at that point, rather than because Pydantic was the shape you happened to remember.

If you can hand this to someone and they cannot tell from the code that you learned Python last week, you are done. The tell is never the algorithm. It is activate in the README, a dict used where a dataclass belonged, and a bare except: swallowing something that should have crashed.