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 sqlalchemy.ext.asyncio 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.execute(select(TodoItem))

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

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.js_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.5.0.tar.gz (3.2 MB view details)

Uploaded Source

Built Distributions

mountaineer-0.5.0-cp312-none-win_amd64.whl (17.1 MB view details)

Uploaded CPython 3.12 Windows x86-64

mountaineer-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (24.6 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.17+ x86-64

mountaineer-0.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (23.8 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.17+ ARM64

mountaineer-0.5.0-cp312-cp312-macosx_11_0_arm64.whl (18.5 MB view details)

Uploaded CPython 3.12 macOS 11.0+ ARM64

mountaineer-0.5.0-cp312-cp312-macosx_10_12_x86_64.whl (19.4 MB view details)

Uploaded CPython 3.12 macOS 10.12+ x86-64

mountaineer-0.5.0-cp311-none-win_amd64.whl (17.1 MB view details)

Uploaded CPython 3.11 Windows x86-64

mountaineer-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (24.6 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.17+ x86-64

mountaineer-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (23.8 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.17+ ARM64

mountaineer-0.5.0-cp311-cp311-macosx_11_0_arm64.whl (18.5 MB view details)

Uploaded CPython 3.11 macOS 11.0+ ARM64

mountaineer-0.5.0-cp311-cp311-macosx_10_12_x86_64.whl (19.4 MB view details)

Uploaded CPython 3.11 macOS 10.12+ x86-64

mountaineer-0.5.0-cp310-none-win_amd64.whl (17.1 MB view details)

Uploaded CPython 3.10 Windows x86-64

mountaineer-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (24.6 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ x86-64

mountaineer-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (23.8 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ ARM64

mountaineer-0.5.0-cp310-cp310-macosx_11_0_arm64.whl (18.5 MB view details)

Uploaded CPython 3.10 macOS 11.0+ ARM64

mountaineer-0.5.0-cp310-cp310-macosx_10_12_x86_64.whl (19.4 MB view details)

Uploaded CPython 3.10 macOS 10.12+ x86-64

File details

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

File metadata

  • Download URL: mountaineer-0.5.0.tar.gz
  • Upload date:
  • Size: 3.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.0.0 CPython/3.12.4

File hashes

Hashes for mountaineer-0.5.0.tar.gz
Algorithm Hash digest
SHA256 950754c409c5243544029981a8444e3b955a4da4758b3f0b03cd58f8f2c1418a
MD5 e353b874b64bdc0625f067fb08c87714
BLAKE2b-256 7495912402206dab3d9f54fe0679ab3e99fe3a1fccae58e742dd4b7f5478d23c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp312-none-win_amd64.whl
Algorithm Hash digest
SHA256 2f3816e3a61b307822e84aa2491889d37a6a49cb0ff3f2fa58ff84fcee70d450
MD5 d8475f7b9bac75f55b35a7efb0b8bdc0
BLAKE2b-256 608b660592db0126c3d22a3d57e6733807f80976cc28da170993cd32fa68a990

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a1e2b3744f885b63fe80deedc07dd4dd7a221ff0135f2b5c6b0db7b8146c4b38
MD5 6025d9efcab813da2cb9c7091e5d20e4
BLAKE2b-256 c0e9303d74bc10256b29da57837bd6421a9bc3bc0d8c1a1edacb3c1bc0e3e83f

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 e6c0f933f6736bbdb55eb53f8e8b8258e877ae154df77a9873f854f6572e997b
MD5 39d965b9325557dcccf52e58e4328999
BLAKE2b-256 c3b52017486d0806f80d96e8d022acc79d302a166c8a416c1ce0caf297bd4d50

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 5a43f24fba0fcd0f6841dc4623d65ab29daa18b45c2d21b0c57a95a870274888
MD5 2860704f1adc0b7b524d772469f6f129
BLAKE2b-256 064f3d11b17ed330902cf0dd81b7342e19d12213978d2978284b6f6437cacb12

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp312-cp312-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 b80031c8bc5483032aedd870041e260423d3101a4e246e8bed58f13929178de2
MD5 9b69ea2fc7b42e5da8edc97fbbddab73
BLAKE2b-256 81443ac483ab472b4e13ef72ffe502e03a1d8811e74a00a8d4280238602b31c6

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp311-none-win_amd64.whl
Algorithm Hash digest
SHA256 b3a6200bf3b475546dc0d22b0bbb7003c80585b4fe8bead1fffc00fb7e4f4d00
MD5 ae928838aa63a8f7811157475440f7d2
BLAKE2b-256 1babaafcf60572fb464d2ec819df664fb777e092d2b05d6583897e5f098ecef7

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d7a90c31272060d829242b5d80f995c0c545ea635e3c7b50101f473fc46e7bd0
MD5 304e82c570982bd4afbd45099aa47aa5
BLAKE2b-256 2a03c67c441d8b6313742c6a3d30754ce05b26cd6fee42b10bd8026e56ccdddd

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 fe8b99342f494a17038a11a6cf528333f7844c3e26acec8d09dc5df45fa30c6d
MD5 2bd1959cdb691151a8d59e730229420d
BLAKE2b-256 d7e2aabe808ebbc2d9030e51ebd961e14f2b14b4dbc5b4c3fdc837c2d6a91c79

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 784fcea9d2693fd75e995953dc43b90b62f766296528e22f6d8accf19b00e6d4
MD5 a530c1966895abb22105e9aff11044c1
BLAKE2b-256 03cf3d549c033d97cd96812fa25fa5ebd00574fb9a63516941a6fd2a654eb87e

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp311-cp311-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 1b3a77e9c1201ab4786f4ad472ba3b6e49a33678dfd648c622e758f9d35bd158
MD5 7d6789dc486aa1e231f6623e7f5f9275
BLAKE2b-256 d2027fc395b2c0a0f99d1def56ca7c9655d319578a33fd79899829da2547531a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp310-none-win_amd64.whl
Algorithm Hash digest
SHA256 8bcbdb21b839cf79ca5a6a43af87fc19ed27516fadbae927c981a4c3aae3b0cc
MD5 63a28e946dfcb92b3b8df3e67e3484d1
BLAKE2b-256 51d0ec3cde96feeb0b131a773c787cde1fb56889fb7d7a4dc4b61202d7fc3a09

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 6bbb51f6a32100e6af1588dce3f6a5d48660ae291922635f8b8972777a8398ed
MD5 d2d29a04b5fbb1f3f996c9bfffffcd90
BLAKE2b-256 2990584a18d355ad5559f8be438c9402477d601ae9815a205f3e6f4dbe17df74

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 64b8bcf91b6dc048898e91c42967cc342f8cdd3f3fc0c953f393e13368be5e2e
MD5 6a46ce684d62025bbc4490b95821d190
BLAKE2b-256 14e8e98952a61d55c55f853d84c7818166a7bc0ddf25c8de617aa7a7f2000823

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d8c2410ab54c50d7c5e8f8d7a264a3ebb0b10a83a8e76cf22f612212580cbcf6
MD5 336d07dad72352b3fc3d025bf5154b2c
BLAKE2b-256 97454985ca0e13d40c25e5b6e48483c9e2646661797ce954059c516cf0528f59

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.0-cp310-cp310-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 e929e69cc8d55ba29870f18d951ea58b865a0f133bd1b2e367ab0b579c6685c5
MD5 45446e7dc82287f44503044c75f7d917
BLAKE2b-256 8fb72f0654971635f8e7a4716409b15b62040ad720bf0d55b0a63c26b603c84d

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