惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Cyber Attacks, Cyber Crime and Cyber Security
Cisco Talos Blog
Cisco Talos Blog
Scott Helme
Scott Helme
The Last Watchdog
The Last Watchdog
G
GRAHAM CLULEY
T
Tenable Blog
PCI Perspectives
PCI Perspectives
Simon Willison's Weblog
Simon Willison's Weblog
N
News and Events Feed by Topic
Know Your Adversary
Know Your Adversary
S
Schneier on Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
P
Privacy International News Feed
C
CERT Recently Published Vulnerability Notes
NISL@THU
NISL@THU
SecWiki News
SecWiki News
S
Securelist
D
Docker
阮一峰的网络日志
阮一峰的网络日志
人人都是产品经理
人人都是产品经理
T
Tailwind CSS Blog
T
Troy Hunt's Blog
The Register - Security
The Register - Security
K
Kaspersky official blog
Blog — PlanetScale
Blog — PlanetScale
云风的 BLOG
云风的 BLOG
Hacker News: Ask HN
Hacker News: Ask HN
S
Secure Thoughts
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
博客园 - 司徒正美
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
F
Fortinet All Blogs
T
Threatpost
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
小众软件
小众软件
WordPress大学
WordPress大学
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 聂微东
Attack and Defense Labs
Attack and Defense Labs
B
Blog RSS Feed
Project Zero
Project Zero
Y
Y Combinator Blog
T
The Blog of Author Tim Ferriss
博客园 - 【当耐特】
V
V2EX
Help Net Security
Help Net Security
P
Proofpoint News Feed
A
Arctic Wolf

Pierce Freeman

A browser for agents | Pierce Freeman The grey market of podcast appearances The way I travel | Pierce Freeman Fixing slow AWS uploads | Pierce Freeman Local tools should still use vaults We solved scratch content first Starting a podcast in 2025 Being late but still being early Automating our home video imports Adding my parents to tailscale A deep dive on agent sandboxes Language servers for AI | Pierce Freeman My simple home podcast studio We need centralized infrastructure | Pierce Freeman Coercing agents to follow conventions using AST validation My unified theory of social selling My personal backup strategy | Pierce Freeman July updates to the homelab How the KV Cache works httpx is the right way to do web requests in Python Reputation is becoming everything | Pierce Freeman Building a (kind of) invisible mac app Updated knowledge in language models Making an ascii animation | Pierce Freeman How speculative decoding works | Pierce Freeman Under the hood of Claude Code Doing things because they're easy, not hard Speeding up sideeffects with JIT in mountaineer Firehot for hot reloading in Python Misadventures in Python hot reloading How text diffusion works | Pierce Freeman The tenacity of modern LLMs The ergonomics of rails | Pierce Freeman How language servers work | Pierce Freeman Just add eggs | Pierce Freeman Unfortunately SEO still matters | Pierce Freeman The futility of human-only web requirements Setting up Input Leap | Pierce Freeman Checking in on Waymo | Pierce Freeman The react revolution | Pierce Freeman Speeding up many small transfers to a unifi nas Quick notes on swift libraries AI engineering is a different animal San Francisco | Pierce Freeman Debugging a mountaineer rendering segfault Local network config on macOS Building our home network | Pierce Freeman Introducing Envelope.dev | Pierce Freeman Legacy code and AI copilots Typehinting from day-zero | Pierce Freeman Generating database migrations with acyclic graphs Lofoten | Pierce Freeman Constraining LLM Outputs | Pierce Freeman Passthrough above all | Pierce Freeman Accuracy in kudos | Pierce Freeman How quick we are to adapt The curious case of LM repetition Costa Rica | Pierce Freeman Debugging chrome extensions with system-level logging Speeding up runpod | Pierce Freeman Inline footnotes with html templates Parsing Common Crawl in a day for $60 An era of rich CLI All or nothing with remote work The Next 10 Years | Pierce Freeman Adding wheels to flash-attention | Pierce Freeman LLMs as interdisciplinary agents | Pierce Freeman New Zealand | Pierce Freeman Representations in autoregressive models | Pierce Freeman Let's talk about Siri | Pierce Freeman Minimum viable public infrastructure | Pierce Freeman Reasoning vs. Memorization in LLMs Automatically migrate enums in alembic Greater sequence lengths will set us free On learning to ski | Pierce Freeman Dolomites | Pierce Freeman Using grpc with node and typescript Opportunity years | Pierce Freeman Buzzword peaks and valleys | Pierce Freeman Buenos Aires | Pierce Freeman Network routing interaction on MacOS Independent work: November recap | Pierce Freeman Debugging slow pytorch training performance The provenance of copy and paste Debugging tips for neural network training Patagonia | Pierce Freeman Santiago | Pierce Freeman My 2022 digital travel kit AWS vs GCP - GPU Availability V2 Independent work: October recap | Pierce Freeman Planning Patagonia | Pierce Freeman Relationship modeling | Pierce Freeman The power of status updates A new chapter | Pierce Freeman Give my library a coffee shop AWS vs GCP - GPU Availability V1 Switzerland | Pierce Freeman Headfull browsers beat headless | Pierce Freeman Webcrawling tradeoffs | Pierce Freeman Copenhagen | Pierce Freeman
Mountaineer v0.1: Webapps in Python and React
2024-02-27 · via Pierce Freeman

Today I'm really excited to open source a beta of Mountaineer, an integrated framework to quickly build webapps in Python and React. It's initial goals are quite humble: make it really fun to design systems with these two languages.

Mountaineer provides the following key features:

  • First-class typehints for the frontend, backend, and database. Auto-suggest all data and function signatures for ease of development in your IDE.
  • Trivially simple client↔server data binding, function calling, and error handling.
  • Server rendering of frontend components, using a bundled V8 engine.
  • Static analysis of frontend code for strong validation of types and links to other pages.
  • PostCSS support for Tailwind, CSS Polyfills, etc.

It doesn't shoot for anything too custom. You don't write Python code to be compiled into React components. It doesn't spin up a new intermediary server to route requests. It's just vanilla Python and vanilla React. The framework focuses on helping them both play to their strengths while interoperating seamlessly.

If you're itching to see code, either head over to the Github or skip down to the Walkthrough. Before that, I want to talk a little about how I got here.

Background

Another framework? We're drowning in them already!

  • Abraham Lincoln

I've built upwards of 25 separate webapps over the last 5 years. Some were in an R&D capacity at Globality, many were side projects, and some were for MonkeySee's core product. Some backends have been in Python, others in Golang, some in Node, and others in Rust. I've poked around the frontend scene as well before eventually landing on Next.js with React.1

I really liked Next. It's fast to prototype, fast to deploy, and lets you develop complex UI interactions easily. But throughout using Javascript for server-side development, I always hit walls while trying to make it do what I wanted. Classes weren't a first class primitive until ES6! Type annotations aren't interpretable at runtime! Node has always felt like a language that already had adoption client-side, so it expanded in feature-set to do more server-side. Python has felt like a language that was born on desktops, for the desktop, and expanded its feature set because people loved the primitives baked into the language design.2

Instead of server-side Node, my typical stack these days is: a Python backend, a simple Next.js gateway and frontend host, and a React codebase client-side.

I want to be clear. All this is personal preference, not some dogma about language design. But I've tried to interrogate why I feel most productive in Python and React (and why it seems like many others agree). I think most of it comes down to expectations - by users about websites and implementers about their companies.

  1. Users: As the web evolved, people's expectations of it grew. Users now expect a baseline of interactivity from webpages; they want overlay popovers, inline data validation, async data fetching, and virtualized table views with reams of data. This interaction is easier to model with a declarative virtual DOM - you write components once and let the runtime change them depending on variable state.

  2. Users: Speed isn't the most important priority. We're acclimated to having to wait for websites, because we recognize there is going to be some network latency.3 With the speed of modern hardware, combined with the pretty simple CRUD commands necessary to serialize data, an interpreted language can deliver performance that still far exceeds user expectations.

  3. Implementers: I imagine the majority of webapps are written for startups: either as their core offering or as internal tools. Fast-to-prototype languages let you hear from clients about what they want and change your tact as you learn from the market. This speed is especially worth a premium when you're competing against incumbents that need to move slowly.

Python and React facilitate these tradeoffs as part of language design:

  1. They're optionally typed. You can whip up a component really quickly without having to worry about memory management, reference passing, or correctly typehinting signatures.4
  2. Have a large standard library (in the case of Python especially) and a rich ecosystem of 3rd party packages for where the stdlib falls short.
  3. Greedily executed. You don't have to worry about other pieces of the codebase when you're just focused on one component. For webapps with large scope and a lot of different pages, you just focus on the parts you're touching and testing.

All of these have downsides. Lack of consistent typing, supply-chain vulnerabilities, and delaying errors until runtime all being key ones. But net-net, I think the juice is worth the squeeze.

Introducing Mountaineer 🏔️

Mountaineer is built on a few core ideas for how webapps tend to work.

  1. Your database tables contain the "rawest" form of your data. It's the ground truth source but also the least self-explanatory.
  2. The backend fetches data from this database and does some additional rollup to make it more accessible to clients. It then passes on this data to the frontend for the initial render.
  3. Most of this data stays the same for the lifecycle of the page, but users can manipulate some of it. They can edit values, create new objects, etc.
  4. These manipulations need to update the data in the database. The frontend communicates what it wants changed, the API validates it, then the backend makes the proper modifications to the database.
  5. This in turn starts the cycle over again, where the frontend state now has to be updated from the updated server state.

This cycle is implemented again and again through increasingly complex layers. At the end of the day, you have data that needs to come from the database and data that needs to go to the database. Depending on the data, this cycle might start over again. That's all most webapps are.5

Mountaineer models this data cycle (frontend↔backend) as a core part of its logic. Instead of specifying an API just for frontend consumers, you specify the data and actions that your frontend will need to function. These are then created and injected dynamically to your frontend, so they end up looking like regular runtime objects that have always lived on the client side. On initial load, these come directly from the server. During data modification, these are synchronized via fetch().

Let's take a closer look.

Mountaineer is a MVC inspired architecture, so each controller needs a view - and each view a controller.6 The project scaffolding is a regular Python project that includes a separate "views" folder that defines your React application. Your average controller will look like this:

from mountaineer import sideeffect, ControllerBase, RenderBase
from mountaineer.database import DatabaseDependencies

from fastapi import Request, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import select

from my_webapp.models.todo import TodoItem

class HomeRender(RenderBase):
    todos: list[TodoItem]

class HomeController(ControllerBase):
    url = "/"
    view_path = "/app/home/page.tsx"

    async def render(
        self,
        session: AsyncSession = Depends(DatabaseDependencies.get_db_session)
    ) -> HomeRender:
        todos = await session.execute(select(TodoItem))

        return HomeRender(
            todos=todos.scalars().all()
        )

	@sideeffect
    async def add_todo(
        self,
        payload: NewTodoRequest,
        session: AsyncSession = Depends(DatabaseDependencies.get_db_session)
    ):
        new_todo =  TodoItem(description=payload.description)
        session.add(new_todo)
        await session.commit()

In the controller, render() will provide the initial data that is accessible to your view on load. It accepts all the function signatures that FastAPI supports: query parameters, url path variables, dependency injection, and more. render is called whenever your page is loaded and lets you fetch the data necessary to prepare the view.

The @sideeffect decorator will generate a POST API that the frontend client can call to influence server state. Except instead of a regular POST, it will perform your logic, then re-call render() to get the latest state from the database and push this back to the client.

A matching frontend view will look like:

import React from "react";
import { useServer, ServerState } from "./_server/useServer";
import CreateTodo from "./create_todo";

const Home = () => {
  const serverState = useServer();

  return (
    <div>
      <p>
        Hello, you have {serverState.todos.length} todo items.
      </p>
      <CreateTodo serverState={serverState} />
      {
        /* Todo items are exposed as typehinted Typescript interfaces */
        serverState.todos.map((todo) => (
          <div key={todo.id}>
            <div>{todo.description}</div>
          </div>
        ))
      }
    </div>
  );
};

export default Home;

The _server import is the first indication that this is a Mountaineer project. Every file in _server is automatically generated for you by the local development server. You can also build it manually for use in CI. This page just echos the data provided by render() and includes a component to create a new todo item. You can import components and pass the server state as you expect with a regular React project:

import React, { useState } from "react";
import { useServer, ServerState } from "./_server/useServer";

const CreateTodo = ({ serverState }: { serverState: ServerState }) => {
  const [newTodo, setNewTodo] = useState("");

  return (
    <div>
	  <input
		type="text"
		value={newTodo}
		onChange={(e) => setNewTodo(e.target.value)}
	  />
	  <button
		onClick={
		  /* Here we call our sideeffect function */
		  async () => {
			await serverState.add_todo({
			  requestBody: {
				description: newTodo,
			  },
			});
			setNewTodo("");
			setShowNew(false);
		  }
		}
	  >
		Create
	  </button>
    </div>
  );
};

export default CreateTodo;

Out of the box, you can run a local server with the following:

poetry run runserver

With every update to your server definitions, Mountaineer will build your managed client dependencies in Typescript. To you - and your IDE - it will look like you just have a few more typehints that are always in sync with the server definitions.

Your entrypoint to server data is the useServer() hook. Included within this object are all the bits of data that come from the server, all the actions you're allowed to perform on the client side, and some helpful functions to generate links or check for errors. This is what it looks like when evaluated:

> console.log(serverState)

Object {
	todos: [...]
	add_todo: async (...args) => {…}
	linkGenerator: {
		detailController: ({   detail_id }) => {…}
		homeController: ({}) => {…}
	}
}

You can use these variables just like they're any other React objects. Insert them into the view, apply a map on top of them, subscribe to changes in useEffect, etc. Mountaineer will initially render these on the server side when the client loads your view - returning plain html to the browser. This html is then hydrated with React so you get interactivity once Javascript has loaded.

Internal architecture

While Python and React are the key development languages for end developers, Mountaineer internally uses Rust for logic in the hot-path that benefits from compiled optimizations. Right now we have spent a lot of upfront time optimizing the server-side rendering flow, alongside sourcemap parsing. As the project matures, more core code will be moved into Rust to achieve speed gains on the repetitive logic.

The server layer is built on top of FastAPI and Pydantic, to leverage their solid typehinting primitives and plug into the existing Starlette ecosystem. This gives you the ability to serve your webapp with gunicorn or any other compatible host in production.

The two top design goals for Mountaineer at the moment are:

  • Best developer UX possible, embracing Python and React conventions
  • Production speed to handle your first user and grow to scale

Head on over to the Github for more technical details.

Where we are

Mountaineer is really new. That said, it's already being dogfooded aggressively. It's hosting all our internal webapps at MonkeySee and being prototyped by some others. My goal at the moment is to make the core Mountaineer codebase as robust and fast as possible. After the API crystallizes then add some common plugins.

If you're interested in a stack focused on Python and React, please take Mountaineer for a spin. It should already be powerful enough to implement a wide variety of webapps on top of it. Let us know what it's missing and what would give you further superpowers in your webapp workflow.

  1. Ah, the good old days when the flame wars were just Angular vs. React. Now it's Svelte vs. Vue vs. SolidJS, etc. That's not to mention all the frameworks that are built out on top of each of these.
    Do you smell that? This post is burning already. ↩

  2. This is overly simplistic, I know. But it rings true. ↩

  3. The state of our bloated ad-tracker ecosystem where the initial load of a blog article might be 30MB doesn't help here either. ↩

  4. I say this even as someone who loves Rust. I love Rust's memory guarantees, handling of macros, and aggressive typing. Mostly I love its speed. But the diligence required to achieve that speed can butt heads against the cultural development cycle of webapps. ↩

  5. So much effort has been spent to let backends develop organically of their frontend. I've never seen this work well. Attempting to "decouple" frontend systems from its backend leads to frustrating development flows - because by definition they are coupled. Embracing the coupling lets you make a lot more assumptions and abstract away a lot more annoyances. I say let the great re-coupling begin. ↩

  6. Together there is harmony. ↩