Build Process

Our "client builder" collection of classes manages generating the Typescript definitions from the defined Python controllers. While this component doesn't affect the actual value of the parsed values retrieved from the fetched() API at runtime, it's critical for development (IDE typehints and CI static typechecking). Working with type interfaces that are always in sync with the backend is one of the core benefits of using Mountaineer.

Note that this documentation is mostly intended for core maintainers who are working on the Mountaineer codebase, versus on projects that use Mountaineer.

Evolution

In our 0.1-0.8 releases, the client builder operated on the OpenAPI schema that was generated from the API server. This ensured that definitions always matched the JS-like typing of the OpenAPI. There were some limitations to this approach:

  • Loss of inheritance tree: Each controller provided flat functions into the OpenAPI schema, without any of their class inheritance structure. This made it impossible to define function signatures in TS that relied on a common ancessor without specifying all of the downstream client subclasses.
  • Formatting of enum: Enums only define their values within OpenAPI, not their Python names. We would build up a synthetic enum key/value pair based on the string value but in cases where key/value in Python were meaningfully different (e.g. Enum("foo", "bar")), we would lose that information.

In 0.9 we moved to a new approach that uses the Python controllers directly to generate the TypeScript definitions. This corrects all of the drawbacks of the OpenAPI approach in addition to cleaning up the code to more easily make extensions in the future.

Our new architecture is structured like so:

In-Memory Controllers: Controllers are loaded into memory from their disk files so we have full access to their code signatures and runtime state.

Parser: Extracts all metadata of the controllers/models necessary to generate TypeScript definitions. These are stored in intermediate data classes.

  • ControllerWrapper
  • FieldWrapper
  • ActionWrapper
  • ModelWrapper
  • EnumWrapper

InterfaceBuilder: Takes the metadata and generates isolated TypeScript definitions. These are separated by output type (interface vs. enum for instance) so we can compartmentalize the string construction of frontend files.

  • ControllerInterface
  • ModelInterface
  • EnumInterface
  • ActionInterface

FileGenerators: Write the full TypeScript files to disk, which often requires collecting multiple interfaces, tracking their imports, and assembling into a single file

App Isolation

When you run our development server via runserver, we spawn your app in a new process. This process is isolated from the main runserver process by a message broker. This allows us to send messages to the app to reload code, restart the server, or shutdown the app.

For small projects, performing a full process restart can be sufficient to update code files. But as projects grow larger, the bootup time for Python to read all your modules into memory can become meaningful. Mountaineer works around this limitation by implementing a hot-reloader. Our hot reloader waits for changes to files on disk and then reloads the changed modules in-memory without a full server restart. We do a best-effort approach to build up a DAG of dependencies between modules so we can reload them in the correct order.

There are some cases, however, where we can't naively reload a module. One common case is in SQLAlchemy where Table definitions can only be added once to the global registry. If we were to reload a module that added a table to the registry, it raises a runtime exception. In these cases our dev app manager will perform a full server restart by shutting down the old process and starting a new one.

Regardless of the approach, the main process will wait for the new server to boot up and then send a notification to the frontend via our websocket server WatcherWebservice.


::: mountaineer.development.manager.DevAppManager


CLASSmountaineer.development.isolation.IsolatedAppContext

Manages the app controller, server, and compilation.

Class Constructor

  • Name
    package
    Type
    str
    Description

    Name of the main package

  • Name
    package_path
    Type
    Path
    Description

    Path to the package on disk

  • Name
    module_name
    Type
    str
    Description

    Module name containing the app controller

  • Name
    controller_name
    Type
    str
    Description

    Variable name of the app controller within the module

Class Attributes

  • Name
    package
    Type
    Description
  • Name
    package_path
    Type
    Description
  • Name
    module_name
    Type
    Description
  • Name
    controller_name
    Type
    Description
  • Name
    webservice_thread
    Type
    UvicornThread | None
    Description
  • Name
    app_controller
    Type
    AppController | None
    Description
  • Name
    js_compiler
    Type
    APIBuilder | None
    Description
  • Name
    app_compiler
    Type
    ClientCompiler | None
    Description

Class Methods

  • Name
    from_webcontroller
    Return type
    Description
  • Name
    run_async
    Return type
    Description

    Asynchronous main loop that processes messages from the broker.

    This is the core message handling loop that:

    1. Retrieves messages from the queue
    2. Dispatches them to appropriate handlers
    3. Sends responses back through the broker

    The loop continues until a ShutdownMessage is received.

  • Name
    handle_build_use_server
    Return type
    Description

    Build the useServer support files for client-side React Server Components.

  • Name
    handle_js_build
    Return type
    Description

    Compile JavaScript files based on updated paths.

    Runs the builder plugins and invalidates any affected views in the app controller.

  • Name
    initialize_app_state
    Return type
    Description

    Initialize all application state components within the isolated context.

    This method:

    1. Loads the web service and app controller
    2. Initializes the hot reloader
    3. Mounts the exception controller for development error pages
    4. Sets up JS and app compilers
  • Name
    handle_start_server
    Return type
    Description

    Start the Uvicorn server for the web application.

    Configures and launches a UvicornThread to serve the application. If a server is already running, it will be stopped first.

  • Name
    mount_exceptions
    Return type
    Description

    Mount the exception controller to the app controller for custom error pages.

    This adds development-friendly error pages with stack traces and debugging information. It ensures the exception controller is only mounted once.

  • Name
    handle_dev_exception
    Return type
    Description

    Handle exceptions in development mode with enhanced error pages.

    For GET requests, renders a detailed error page with stack traces and debugging info. For other request types, re-raises the exception to be handled by the framework.


::: mountaineer.development.hotreload.HotReloader


CLASSmountaineer.development.watch.PackageWatchdog

Monitors Python packages for file changes and triggers registered callbacks when changes occur.

Manages the lifecycle of file watching across multiple packages, optimizing the watch paths to avoid redundant watchers, and verifying package availability. Used as the foundation for hot-reloading and development tooling in Mountaineer.

Class Constructor

  • Name
    main_package
    Type
    str
    Description

    Primary package to monitor for file changes

  • Name
    dependent_packages
    Type
    list[str]
    Description

    Additional packages to monitor for changes

  • Name
    callbacks
    Type
    list[CallbackDefinition] | None
    Description

    Default: None

    List of callback definitions to execute when changes occur

  • Name
    run_on_bootup
    Type
    bool
    Description

    Default: False

    Typically, we will only notify callback if there has been a change to the filesystem. If this is set to True, we will run all callbacks on bootup as well.

Class Attributes

  • Name
    main_package
    Type
    Description
  • Name
    packages
    Type
    Description
  • Name
    paths
    Type
    list[str]
    Description
  • Name
    callbacks
    Type
    list[CallbackDefinition]
    Description
  • Name
    run_on_bootup
    Type
    Description
  • Name
    stop_event
    Type
    Description
  • Name
    running
    Type
    Description

Class Methods

  • Name
    start_watching
    Return type
    Description

    Begin asynchronously watching all package paths for file changes.

    If configured with run_on_bootup=True, immediately runs all callbacks once. Sets up the FileWatcher with registered callbacks and processes changes as they occur on the filesystem.

  • Name
    stop_watching
    Return type
    Description

    Stop watching all file paths and reset the watchdog state.

    Signals the underlying watchfiles library to stop watching by setting the stop event and resets internal state for potential restart.

  • Name
    check_packages_installed
    Return type
    Description

    Verify that all packages being watched are installed in the current environment.

  • Name
    get_package_paths
    Return type
    Description

    Resolve filesystem paths for all monitored packages.

    Finds each package's location on disk using importlib and optimizes the path list to eliminate redundant watchers through merge_paths.

  • Name
    merge_paths
    Return type
    list[str]
    Description

    Optimize the list of paths by removing subdirectories when their parent is already watched.

    If one path is a subdirectory of another, we only want to watch the parent directory. This function merges the paths to avoid duplicate watchers.

Code

import asyncio from mountaineer.development.watch import PackageWatchdog, CallbackDefinition, CallbackType, CallbackMetadata # Define a callback function to handle file changes async def reload_modules(metadata: CallbackMetadata) -> None: print(f"Changes detected in {len(metadata.events)} files") for event in metadata.events: print(f" {event.action.name}: {event.path}") # You would typically reload modules or trigger other actions here # Create a watchdog for your main package and any dependencies watchdog = PackageWatchdog( main_package="my_app", dependent_packages=["my_library"], callbacks=[ CallbackDefinition( action=CallbackType.MODIFIED | CallbackType.CREATED, callback=reload_modules ) ], run_on_bootup=True ) await watchdog.start_watching()

CLASSmountaineer.development.watch_server.WatcherWebservice

A simple webserver to notify frontends about updated builds via WebSockets.

The WatcherWebservice provides a multiprocessing safe queue that's accessible as notification_queue. Each time that a process wants to update the frontend, it can push a message into the queue.

This service is a critical component in the hot-reloading architecture, enabling backend changes to trigger frontend refreshes without manual intervention. It runs as a lightweight FastAPI application in a separate thread.

Class Constructor

  • Name
    webservice_host
    Type
    str
    Description

    The host address to bind the webservice to (e.g. '127.0.0.1')

  • Name
    webservice_port
    Type
    int | None
    Description

    Default: None

    Optional port number to use. If not provided, a free port will be allocated.

Class Attributes

  • Name
    app
    Type
    Description
  • Name
    websockets
    Type
    list[WebSocket]
    Description
  • Name
    host
    Type
    Description
  • Name
    port
    Type
    Description
  • Name
    notification_queue
    Type
    Queue[bool | None]
    Description
  • Name
    webservice_thread
    Type
    UvicornThread | None
    Description
  • Name
    monitor_build_thread
    Type
    Thread | None
    Description
  • Name
    has_started
    Type
    Description

Class Methods

  • Name
    build_app
    Return type
    Description

    Construct the FastAPI application with necessary routes and WebSocket endpoints.

    Creates a simple API with:

    • A root endpoint returning basic service information
    • A WebSocket endpoint at "/build-events" for real-time build notifications
  • Name
    broadcast_listeners
    Return type
    Description

    Send a notification to all connected WebSocket clients.

    This method is called when a new build is available, signaling all connected frontends to refresh.

  • Name
    monitor_builds
    Return type
    Description

    Monitor the notification queue and broadcast updates to connected clients.

    This method runs in a separate thread, blocking on the notification queue and calling broadcast_listeners when a notification is received. The thread terminates when None is pushed to the queue.

  • Name
    start
    Return type
    Description

    Start the WatcherWebservice by launching the necessary threads.

    Initializes and starts:

    • A UvicornThread to serve the FastAPI application
    • A monitor thread to watch for build notifications
  • Name
    stop
    Return type
    bool
    Description

    Attempts to stop the separate WatcherWebservice threads.

    Sends termination signals to the Uvicorn server and monitor threads, then waits for them to complete within the specified timeout period.


::: mountaineer.development.messages.AsyncMessageBroker