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

Uploaded Source

Built Distributions

mountaineer-0.5.1-cp312-none-win_amd64.whl (17.2 MB view details)

Uploaded CPython 3.12 Windows x86-64

mountaineer-0.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (20.6 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.17+ x86-64

mountaineer-0.5.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.5.1-cp312-cp312-macosx_11_0_arm64.whl (17.8 MB view details)

Uploaded CPython 3.12 macOS 11.0+ ARM64

mountaineer-0.5.1-cp312-cp312-macosx_10_12_x86_64.whl (18.7 MB view details)

Uploaded CPython 3.12 macOS 10.12+ x86-64

mountaineer-0.5.1-cp311-none-win_amd64.whl (17.2 MB view details)

Uploaded CPython 3.11 Windows x86-64

mountaineer-0.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (20.6 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.17+ x86-64

mountaineer-0.5.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.5.1-cp311-cp311-macosx_11_0_arm64.whl (17.8 MB view details)

Uploaded CPython 3.11 macOS 11.0+ ARM64

mountaineer-0.5.1-cp311-cp311-macosx_10_12_x86_64.whl (18.7 MB view details)

Uploaded CPython 3.11 macOS 10.12+ x86-64

mountaineer-0.5.1-cp310-none-win_amd64.whl (17.2 MB view details)

Uploaded CPython 3.10 Windows x86-64

mountaineer-0.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (20.6 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ x86-64

mountaineer-0.5.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.5.1-cp310-cp310-macosx_11_0_arm64.whl (17.8 MB view details)

Uploaded CPython 3.10 macOS 11.0+ ARM64

mountaineer-0.5.1-cp310-cp310-macosx_10_12_x86_64.whl (18.7 MB view details)

Uploaded CPython 3.10 macOS 10.12+ x86-64

File details

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

File metadata

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

File hashes

Hashes for mountaineer-0.5.1.tar.gz
Algorithm Hash digest
SHA256 8c75184ae57e34f9e5ddd0b6314528d5683cbe99de07fbeb6f594a11e82783ac
MD5 5bfaaeba22c0d4c4380fec53b6cae0f6
BLAKE2b-256 0299110bc2a1c06364bca5474486b5ebecc007f88ad7ab5c2a8a76576b80ca30

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp312-none-win_amd64.whl
Algorithm Hash digest
SHA256 9235c1ebc956d1d20676bee5a3a1d02145ca25d2a80febb7c259fb56a5215254
MD5 3529ef1e823fca2f000ddcecfcd6ed94
BLAKE2b-256 5501bea338b74348a249a98babdc6ca3c55139280011138400028ee2967b44d8

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 684af67c02ae3375066a7549ba942f042f87fc7be22faa60b51eeb220cf50a6a
MD5 2d67c096462e91a86926817c9e4b109e
BLAKE2b-256 0061623f84926248c1a40746e02cf095112e41a15d19fe62e5ed2037c5f7d054

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 2eb127067dfdfc96cc59c0fa4de2dcde5edd4f26fc451725d856c9f1bad04e93
MD5 18f399a87a9864a791cf5059e396491d
BLAKE2b-256 d12281326d8526dcbe776aefbec5ad415f5cb3530d8ee98b82bb55c8992781fc

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 78e3b8143f5d89e559b5fa4f664121bf10b5257ab149a0a92276d3555e261e4a
MD5 75949b5cb7035cc46ebefb46bd46c011
BLAKE2b-256 c116b0c64d9c1d434eb73318d404fb9e1e118f24ab1949ef3529b8cb73ed3c82

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp312-cp312-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 f3990460c10a7f09f7dc6161fbd48a6b016377987f54ac5eaa97e2c6333e9b36
MD5 5d5432abeeaeae74af327255b7c0d0ae
BLAKE2b-256 80934dc3937d170e5d8d0218181de0139fc3468a97d37da0b0d6567260111de6

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp311-none-win_amd64.whl
Algorithm Hash digest
SHA256 949b2cc6dc059ab8e7d7e4b9e06a4be6f364588c998fa5e441dc256e3697432c
MD5 adee8ed46b32b27be6cc7f1658f5f50b
BLAKE2b-256 fd1ded06161040c8f042c77ecb61a3015ecfa30e8c658b5b0f141040cfedb061

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 9c752dc3e56ce6379531090e97ab8734d77c473f06bdec146eea9137e4023a18
MD5 1f8103b3c47dc4c5c3d22596dab7d799
BLAKE2b-256 03e12d62c6247500abc06be1ae465dc0a52fd5d413cf67a3123efaf769e3c260

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 b456ddb18eb24650957d86490da56cd0d930361fd00a3b3bced10269e687f380
MD5 ec364ae889beaf66c1aa588a7e38face
BLAKE2b-256 de2dd2564fe457221df59d16a3e4de96088988351a8be5db9173003be3076f90

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 28f644835969f05bb03c193de37a8f90dd6b2d84e4fbc141f41edd5a0682c846
MD5 c8c6765aaadf84a62ce99b2bd3562362
BLAKE2b-256 c2af49f27b86a33d22e4f312a27463ab71d40beb89519807cff2f203bb881ae1

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp311-cp311-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 aea0a0d311ec602ef56561ac18eb331c064fc7d57ed24416b6361f629ea3906c
MD5 d993b3ed850624025ecb88ae0d6bfca4
BLAKE2b-256 3e8c5dc2ca6a1bb43ab1773ad2336ab43500fb7d5ee887533fa6f747c5e1e7b0

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp310-none-win_amd64.whl
Algorithm Hash digest
SHA256 63d89c4fc50c4b5ee367ce2ac18f29b9721f355bd4f78ef62c84587c442d3a32
MD5 0e2ab3d3b5e4150ce58b2d333474c427
BLAKE2b-256 1db01ddc8b1426a2c4c50071b9e4a0fe5a700afbe5789cc33b9aa2fda37e49b0

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 1cb2175c26602db15ab4ed8327b4b4db3689ef18b1fced24d8e969ca8426aa89
MD5 8a03ff68b39afefabd61df849e2c6e0e
BLAKE2b-256 e4efa0cdc16197d3f5a43b5384a77894d5e6eb8f992077067a173dfc679a5d2d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 3dbef9e4cedde26323adb4b94467d190655cda03935e338be532a68899c17b75
MD5 1772f74df04530ec4c56a5f9d8bd6b1a
BLAKE2b-256 0eaf7ab6473f975b8896ff9a1a92ca5d68fa63ef9d588d92f9802c3c12561cf3

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d3541958000c815801a3889bd9e54acd86478d0095dd1386f540ee6f20fcf624
MD5 ad59975f2978aee16fa93df7886b1803
BLAKE2b-256 33b7d550dbb4d87bc8ac7403a7b67f9be6db5f9343844d0c0cc4ce3b3373ddf1

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for mountaineer-0.5.1-cp310-cp310-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 508434a03a071a818a3b7d3839a9a985cf50e7f581316d80fa24dede48299cdf
MD5 418f082d6af614bbdc37b4308fc5a5c7
BLAKE2b-256 2f448aa355c6cfc866ac9888b1576bd93ff396e0df14591c0c364182260dc0ec

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