Skip to main content

No project description provided

Project description

Mountaineer Header

Move fast. Climb mountains. Don't break things.

Mountaineer 🏔️ is a framework to easily build webapps in Python and React. If you've used either of these languages before for development, we think you'll be right at home.

Main Features

Each framework has its own unique features and tradeoffs. Mountaineer focuses on developer productivity above all else, with production speed a close second.

  • 📝 Typehints up and down the stack: frontend, backend, and database
  • 🎙️ Trivially easy client<->server communication, data binding, and function calling
  • 🌎 Optimized server rendering for better accessibility and SEO
  • 🏹 Static analysis of web pages for strong validation: link validity, data access, etc.
  • 🤩 Skip the API or Node.js server just to serve frontend clients

We built Mountaineer out of a frustration that we were reinventing the webapp wheel time and time again. We love Python for backend development and the interactivity of React for frontend UX. But they don't work seamlessly together without a fair amount of glue. So: we built the glue. While we were at it, we embedded a V8 engine to provide server-side rendering, added conventions for application configuration, built native Typescript integrations, and more. Our vision is for you to import one slim dependency and you're off to the races.

We're eager for you to give Mountaineer a try, and equally devoted to making you successful if you like it. File an Issue if you see anything unexpected or if there's a steeper learning curve than you expect. There's much more to do - and we're excited to do it together.

~ Pierce

Getting Started

New Project

To get started as quickly as possible, we bundle a project generator that sets up a simple project after a quick Q&A. Make sure you have pipx installed.

$ pipx run create-mountaineer-app

? Project name [my-project]: my_webapp
? Author [Pierce Freeman <pierce@freeman.vc>] Default
? Use poetry for dependency management? [Yes] Yes
? Create stub MVC files? [Yes] Yes
? Use Tailwind CSS? [Yes] Yes
? Add editor configuration? [vscode] vscode

Mountaineer projects all follow a similar structure. After running this CLI you should see a new folder called my_webapp, with folders like the following:

my_webapp
  /controllers
    /home.py
  /models
    /mymodel.py
  /views
    /app
      /home
        /page.tsx
      /layout.tsx
    /package.json
    /tsconfig.json
  /app.py
  /cli.py
pyproject.toml
poetry.lock

Every service file is nested under the my_webapp root package. Views are defined in a disk-based hierarchy (views) where nested routes are in nested folders. This folder acts as your React project and is where you can define requirements and build parameters in package.json and tsconfig.json. Controllers are defined nearby in a flat folder (controllers) where each route is a separate file. Everything else is just standard Python code for you to modify as needed.

Development

If you're starting a new application from scratch, you'll typically want to create your new database tables. Make sure you have postgres running. We bundle a docker compose file for convenience with create-mountaineer-app.

docker compose up -d
poetry run createdb

Of course you can also use an existing database instance, simply configure it in the .env file in the project root.

Mountaineer relies on watching your project for changes and doing progressive compilation. We provide a few CLI commands to help with this.

While doing development work, you'll usually want to preview the frontend and automatically build dependent files. You can do this with:

$ poetry run runserver

INFO:     Started server process [93111]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:5006 (Press CTRL+C to quit)

Navigate to http://127.0.0.1:5006 to see your new webapp running.

Or, if you just want to watch the source tree for changes without hosting the server. Watching will allow your frontend to pick up API definitions from your backend controllers:

$ poetry run watch

Both of these CLI commands are specified in your project's cli.py file.

Walkthrough

Below we go through some of the unique aspects of Mountaineer. Let's create a simple Todo list where we can add new items.

For the purposes of this walkthrough we assume your project is generated with create-mountaineer-app and you've skipped MVC stub files. If not, you'll have to delete some of the pre-existing files.

Let's get started by creating the data models that will persist app state to the database. These definitions are effectively Pydantic schemas that will be bridged to the database via SQLModel.

# my_webapp/models/todo.py

from mountaineer.database import SQLModel, Field
from uuid import UUID, uuid4

class TodoItem(SQLModel, table=True):
    id: UUID = Field(default_factory=uuid4, primary_key=True)

    description: str
    completed: bool = False

Update the index file as well:

# my_webapp/models/__init__.py

from .todo import TodoItem # noqa: F401

Make sure you have a Postgres database running. We bundle a docker compose file for convenience with create-mountaineer-app. Launch it in the background and create the new database tables from these code definitions:

docker compose up -d
poetry run createdb
poetry run runserver

Great! At this point we have our database tables created and have a basic server running. We next move to creating a new controller, since this will define which data you can push and pull to your frontend.

# my_webapp/controllers/home.py

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

from fastapi import Request, Depends
from mountaineer.database.session import AsyncSession
from sqlmodel import select

from my_webapp.models.todo import TodoItem

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

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

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

        return HomeRender(
            client_ip=(
                request.client.host
                if request.client
                else "unknown"
            ),
            todos=todos
        )

The only three requirements of a controller are setting the:

  • URL
  • View path
  • Initial data payload

This render() function is a core building block of Mountaineer. All Controllers need to have one. It defines all the data that your frontend will need to resolve its view. This particular controller retrieves all Todo items from the database, alongside the user's current IP.

[!TIP] render() functions accepts all parameters that FastAPI endpoints do: paths, query parameters, and dependency injected functions. Right now we're just grabbing the Request object to get the client IP.

Note that the database session is provided via dependency injection, which plug-and-plays with FastAPI's Depends syntax. The standard library provides two main dependency providers:

  • mountaineer.CoreDependencies: helper functions for configurations and general dependency injection
  • mountaineer.database.DatabaseDependencies: helper functions for database lifecycle and management

Now that we've newly created this controller, we wire it up to the application. This registers it for display when you load the homepage.

# my_webapp/app.py
from mountaineer.app import AppController
from mountaineer.client_compiler.postcss import PostCSSBundler
from mountaineer.render import LinkAttribute, Metadata

from my_webapp.config import AppConfig
from my_webapp.controllers.home import HomeController

controller = AppController(
    config=AppConfig(),
    global_metadata=Metadata(
        links=[LinkAttribute(rel="stylesheet", href="/static/app_main.css")]
    ),
    custom_builders=[
        PostCSSBundler(),
    ],
)

controller.register(HomeController())

Let's move over to the frontend.

/* my_webapp/views/app/home/page.tsx */

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

const CreateTodo = ({ serverState }: { serverState: ServerState }) => {
  return (
    <div className="flex gap-x-4">
      <input
        type="text"
        className="grow rounded border-2 border-gray-200 px-4 py-2"
      />
      <button className="rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700">
        Create
      </button>
    </div>
  );
};

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

  return (
    <div className="mx-auto max-w-2xl space-y-8 p-8 text-2xl">
      <p>
        Hello {serverState.client_ip}, 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} className="rounded border-2 border-gray-200 p-4">
            <div>{todo.description}</div>
          </div>
        ))
      }
    </div>
  );
};

export default Home;

We define a simple view to show the data coming from the backend. To accomplish this conventionally, we'd need to wire up an API layer, a Node server, or format the page with Jinja templates.

Here instead we use our automatically generated useServer() hook. This hook payload will provide all the HomeRender fields as properties of serverState. And it's available instantly on page load without any roundtrip fetches. Also - if your IDE supports language servers (which most do these days), you should see the fields auto-suggesting for serverState as you type.

IDE Typehints

If you access this in your browser at localhost:5006/ we can see our welcome message, but we can't really do anything with the todos yet. Let's add some interactivity.

[!TIP] Try disabling Javascript in your browser. The page will still render as-is with all variables intact, thanks to our server-side rendering.

Server-side rendering

What good is todo list that doesn't get longer? We define a add_todo function that accepts a pydantic model NewTodoRequest, which defines the required parameters for a new todo item. We then cast this to a database object and add it to the postgres table.

# my_webapp/controllers/home.py

from pydantic import BaseModel

class NewTodoRequest(BaseModel):
    description: str

class HomeController(ControllerBase):
    ...

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

The important part here is the @sideeffect. Once you create a new Todo item, the previous state on the frontend is outdated. It will only show the todos before you created a new one. That's not what we want in an interactive app. This decorator indicates that we want the frontend to refresh its data, since after we update the todo list on the server the client state will be newly outdated.

Mountaineer detects the presence of this sideeffect function and analyzes its signature. It then exposes this to the frontend as a normal async function.

/* my_webapp/views/app/home/page.tsx */

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

/* Replace the existing CreateTodo component definition you have */
const CreateTodo = ({ serverState }: { serverState: ServerState }) => {
  const [newTodo, setNewTodo] = useState("");

  return (
    <div className="flex gap-x-4">
      <input
        type="text"
        className="grow rounded border-2 border-gray-200 px-4 py-2"
        value={newTodo}
        onChange={(e) => setNewTodo(e.target.value)}
      />
      <button
        className="rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700"
        onClick={
          /* Here we call our sideeffect function */
          async () => {
            await serverState.add_todo({
              requestBody: {
                description: newTodo,
              },
            });
            setNewTodo("");
          }
        }
      >
        Create
      </button>
    </div>
  );
};

...

export default Home;

useServer() exposes our add_todo function so we can call our backend directly from our frontend. Also notice that we don't have to read or parse the output value of this function to render the new todo item to the list. Since the function is marked as a sideeffect, the frontend will automatically refresh its data after the function is called.

Go ahead and load it in your browser. If you open up your web tools, you can create a new Todo and see POST requests sending data to the backend and receiving the current server state. The actual data updates and merging happens internally by Mountaineer.

Getting Started Final TODO App

Getting Started Final TODO App

You can use these serverState variables anywhere you'd use dynamic React state variables (useEffect, useCallback, etc). But unlike React state, these variables are automatically updated when a relevant sideeffect is triggered.

And that's it. We've just built a fully interactive web application without having to worry about an explicit API. You specify the data model and actions on the server and the appropriate frontend hooks are generated and updated automatically. It gives you the power of server rendered html and the interactivity of a virtual DOM, without having to compromise on complicated data mutations to keep everything in sync.

Learn More

We have additional documentation that does more of a technical deep dive on different features of Mountaineer. Check out mountaineer.sh.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mountaineer-0.7.1.tar.gz (3.3 MB view details)

Uploaded Source

Built Distributions

mountaineer-0.7.1-cp312-none-win_amd64.whl (17.3 MB view details)

Uploaded CPython 3.12 Windows x86-64

mountaineer-0.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (20.7 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.17+ x86-64

mountaineer-0.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (20.3 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.17+ ARM64

mountaineer-0.7.1-cp312-cp312-macosx_11_0_arm64.whl (17.9 MB view details)

Uploaded CPython 3.12 macOS 11.0+ ARM64

mountaineer-0.7.1-cp312-cp312-macosx_10_12_x86_64.whl (18.8 MB view details)

Uploaded CPython 3.12 macOS 10.12+ x86-64

mountaineer-0.7.1-cp311-none-win_amd64.whl (17.3 MB view details)

Uploaded CPython 3.11 Windows x86-64

mountaineer-0.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (20.7 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.17+ x86-64

mountaineer-0.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (20.3 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.17+ ARM64

mountaineer-0.7.1-cp311-cp311-macosx_11_0_arm64.whl (17.9 MB view details)

Uploaded CPython 3.11 macOS 11.0+ ARM64

mountaineer-0.7.1-cp311-cp311-macosx_10_12_x86_64.whl (18.8 MB view details)

Uploaded CPython 3.11 macOS 10.12+ x86-64

mountaineer-0.7.1-cp310-none-win_amd64.whl (17.3 MB view details)

Uploaded CPython 3.10 Windows x86-64

mountaineer-0.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (20.7 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ x86-64

mountaineer-0.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (20.3 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ ARM64

mountaineer-0.7.1-cp310-cp310-macosx_11_0_arm64.whl (17.9 MB view details)

Uploaded CPython 3.10 macOS 11.0+ ARM64

mountaineer-0.7.1-cp310-cp310-macosx_10_12_x86_64.whl (18.8 MB view details)

Uploaded CPython 3.10 macOS 10.12+ x86-64

File details

Details for the file mountaineer-0.7.1.tar.gz.

File metadata

  • Download URL: mountaineer-0.7.1.tar.gz
  • Upload date:
  • Size: 3.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.7

File hashes

Hashes for mountaineer-0.7.1.tar.gz
Algorithm Hash digest
SHA256 8871db016f4c0c6ebb3d2bd45f825527d525e0515211c2983bb30a4edcde2bef
MD5 293bd326a2abee04f48c4716d0e0bfca
BLAKE2b-256 05f5693650064de4964b381366d735497dd0ba6ed393b7422f6f46183a843a1b

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp312-none-win_amd64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp312-none-win_amd64.whl
Algorithm Hash digest
SHA256 abde85fd054afba957aa80cce073d155e1d8d20abf6ae6589d95e5a2fb20e6f4
MD5 95b686996bf4f36a45e22cef6bdc40d2
BLAKE2b-256 92dbf39484a13bf6b00dadc35696fa96931ee4a93f17e1e08b1633ad5fb4803f

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 89f8f869ccce0d8be63712447f7369a0e45478904ad7ff52dee9e948bc8d8f86
MD5 939ddc56aeaa2b3c96ffa96d5df132db
BLAKE2b-256 90b77468e250a842278c3e0b582f42119fa8f2a7f93281bf1862b38b1a396fa7

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 85bbf3a87899727259faae3e49273ee5cb1fc31c05660cfac688e52897ed8807
MD5 ace753077f0b64804e2177f7e104a50f
BLAKE2b-256 3f39007b4442c65648aaa534c72f48b3b62f3d28ea5eaab4c44c7b0f6fea4e81

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 10b240d7b9341e92ffaeba5dbbced2432c00242f1a5807352ae805e9446ee8f6
MD5 654049861c04494739b61ddeebc5129d
BLAKE2b-256 f4b6e4eeb6cdebb1ade360935e9fedd926ccf97bb7c3a72d5b74674c52717dab

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp312-cp312-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp312-cp312-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 a446f59a983c4433a9106b5558ad7201c302df61c9ef8304d06c5e3deb73622e
MD5 23013ccd2b9365667aa2b12fb28359b0
BLAKE2b-256 d54c0ac65e96735b8d8395dabb0afe090764c03c224be5195c171a8aedb91a2c

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp311-none-win_amd64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp311-none-win_amd64.whl
Algorithm Hash digest
SHA256 87e06948f22fc83da55739db671ca1fe14490b2a3a330376700a776bae7f4cc4
MD5 af4a9eaeb5577fb96ce79477c390c577
BLAKE2b-256 2d9d3da2ba6491021151ac5a53876700bfa25abb97e8a8dcb56896490f3b3f02

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 402ebbdd46c7f48e58c16f52e817791d4ee99e2065e99a6f61c79e58b3865d4a
MD5 e4ff474c85c5a69a513f0a31b4785bde
BLAKE2b-256 8979afd0bf7c65a0ff93cec525853a89f5f81f164079fd9e64fb545f16f9a395

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 88a65c500ed81cc13d0a6f73a201ed803724143a514def3aa50c5ed2510fc1d0
MD5 14fae3f06dab580032675808369631dd
BLAKE2b-256 6a89341e2ca5129f488c21a81623dd4f3916ead34d69d2306246234cfb4cedb7

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 ef4146ba8f2677ee0ef9cd0f47b6fd3f33166d475e4e24b20160d688d07dfcb5
MD5 252e236f346cde9cec017ad4f6778653
BLAKE2b-256 4e3da58e3dfe81c069e856ef54a797c1be57e73730fb539ac97b857d726ab134

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp311-cp311-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp311-cp311-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 c988d0c861e436fb383bca9375785f1db18171ae25b1b892f3224ea829ed99a0
MD5 85907339d7c58f72834e8417925780f9
BLAKE2b-256 b06f837fe68525faafba93b6363dbebd1708e5cbaa9764b1a475b64af85d340a

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp310-none-win_amd64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp310-none-win_amd64.whl
Algorithm Hash digest
SHA256 d5597fd85dd75e704301bc7e73880f3aa45dad2ac548f3f82338df9aae56e7bd
MD5 38b3dc751f823bfbb41166e27057e776
BLAKE2b-256 166e8cd7ae66ff21c2430ed3ca39ceb8a04ce144404da65edcec1417a724ba1b

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 22a618855ec7adfa39be0a95c1fdf95bb478af2a97fe52104c504e777e7018a9
MD5 87876e5b5a3e7d76df1003c5d2108e76
BLAKE2b-256 6b0d6cca1bd5146e3a0097aca7f1e0e0191329e74ffeca25b8bca63c9c3138a4

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 7d34c39b10e07b4a826864cf1458f968e77dab64d08e1dbaf53c0bd9d60621b4
MD5 0b7cabddf2f34f6047bfac9334663a4c
BLAKE2b-256 95158be47ff39ce6e19ae81ef012159a78c9d34d627589e6ec5c248496ce0ca5

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 22d95ba779fa2ee8c29cde4610d01b77b432bcd67b31500c74e53bc7c8fc3f52
MD5 caad61732f9610d03622dc3d25b31e9c
BLAKE2b-256 39df1883786d5ae1a2554b0528d39114a24de66e0e2bf024ac2f14df617a8221

See more details on using hashes here.

File details

Details for the file mountaineer-0.7.1-cp310-cp310-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for mountaineer-0.7.1-cp310-cp310-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 386bb51dbde1955aad301c2bb113f4906e97eef20c661d03deffeed9a099c52a
MD5 5df37640939dc1196487a9700526b04e
BLAKE2b-256 9be8f417d8a3661eec4046230abe65dfe8c342ca9f7dce5cf2711019e8e4b23c

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page