Skip to main content

A lightweight web framework with hot reload

Project description

Mallo

Lightweight Python web framework with a small core, built-in live reload, templates, routing, static files, and customizable error pages.

Installation

Install from PyPI:

pip install mallo

Recommended after install try mallo cli:

mallo create myapp
cd myapp
python app.py

Quick Start

Create app.py:

from mallo import Mallo

app = Mallo(__name__, live_reload=True)

@app.get('/')
def home(request):
    return '<h1>Hello from Mallo</h1>'

if __name__ == '__main__':
    app.run(debug=True, use_reloader=True)

Run:

python app.py

Open http://localhost:8000.


Hot Reload

Mallo has two reload-related behaviors:

  1. Server reload (use_reloader in app.run(...))
  2. Browser auto-refresh (live_reload in Mallo(...))

What the hot reloader does:

  • Watches your project files for changes (.py, .html, .css, .js, and others).
  • When a change is detected, it stops the running app process and starts a new one.
  • In debug mode with live_reload=True, HTML responses include a small script that checks a reload endpoint.
  • If the app process restarts, the browser detects a token change and refreshes automatically.

Enable both:

app = Mallo(__name__, live_reload=True)
app.run(debug=True, use_reloader=True)

When hot reloader is deactivated:

app = Mallo(__name__, live_reload=False)
app.run(debug=True, use_reloader=False)

What this means:

  • use_reloader=False: Python process will not restart on file changes.
  • live_reload=False: Browser auto-refresh script will not be injected.
  • You need to restart the app manually after code changes.

Template Rendering

Mallo supports 2 ways to render templates.

1) render_template() (from templates/ folder)

Use when your templates live in a templates directory.

from mallo import Mallo, render_template

app = Mallo(__name__)

@app.get('/')
def home(request):
    return render_template('index.html', name='Mallo')

Expected structure:

project/
  app.py
  templates/
    index.html

You can change template folder:

app = Mallo(__name__, template_folder='views')

2) render_template_file() (explicit file path)

Use when you want to render by full/relative file path directly.

from mallo import Mallo, render_template_file

app = Mallo(__name__)

@app.get('/about')
def about(request):
    return render_template_file('pages/about.html', title='About')

Template syntax

  • Variables: {{ name }}
  • Safe output (skip escaping): {{ html_content | safe }}
  • If blocks: {% if show %}...{% endif %}
  • For blocks: {% for item in items %}...{% endfor %}

Routing

General route decorator:

@app.route('/about')
def about(request):
    return 'About page'

@app.route() defaults to GET.

Route with multiple methods:

@app.route('/contact', methods=['GET', 'POST'])
def contact(request):
    if request.method == 'POST':
        return 'Form submitted'
    return 'Contact form'

Method shortcuts:

@app.get('/hello/<name>')
def hello(request, name):
    return f'Hello {name}'

@app.get('/user/<int:id>')
def user(request, id):
    return f'User #{id}'

@app.get('/file/<path:filepath>')
def file_route(request, filepath):
    return filepath

Generate routes by handler name:

profile_url = app.url_for('user', id=10)  # /user/10

Request Data

@app.get('/search')
def search(request):
    q = request.get('q', '')
    return f'query={q}'

@app.post('/submit')
def submit(request):
    name = request.post('name', '')
    return f'name={name}'

JSON body:

@app.post('/api')
def api(request):
    data = request.json or {}
    return {'received': data}

Multipart/form-data files:

from mallo.response import Response

@app.post('/upload')
def upload(request):
    file_info = request.files.get('file')
    if not file_info:
        return Response('No file', status=400)
    return f"Uploaded: {file_info['filename']}"

Responses

Return simple values:

  • str -> HTML response
  • dict / list -> JSON response with application/json

Or use response classes:

from mallo.response import JSONResponse, RedirectResponse, FileResponse

@app.get('/json')
def json_route(request):
    return JSONResponse({'ok': True})

@app.get('/go')
def go(request):
    return RedirectResponse('/')

Sessions and CSRF

Enable sessions by setting secret_key.

app = Mallo(__name__, secret_key='change-this-in-production')

Use session:

@app.get('/set')
def set_session(request):
    request.session['name'] = 'Betrand'
    return 'saved'

For unsafe methods (POST, PUT, DELETE), CSRF token is validated by default. Include token in forms:

@app.get('/form')
def form(request):
    return f"""
    <form method="post" action="/form">
      <input type="hidden" name="csrf_token" value="{request.csrf_token}">
      <input name="name">
      <button type="submit">Send</button>
    </form>
    """

Or send token in header X-Csrf-Token.


Static Files

If static/ exists, files are served at /static/....

project/
  static/
    styles.css

In HTML:

<link rel="stylesheet" href="/static/styles.css">

Error Pages (404/500)

Mallo ships with default styled error pages.

Option 1: Configure custom error HTML files

app = Mallo(
    __name__,
    error_page_404='templates/errors/404.html',
    error_page_500='templates/errors/500.html',
)

Option 2: Register custom handlers

from mallo import Mallo, render_template

app = Mallo(__name__)

@app.errorhandler(404)
def not_found(request):
    return render_template('errors/404.html')

@app.errorhandler(500)
def server_error(request):
    return render_template('errors/500.html')

Custom handler takes precedence over error_page_404 / error_page_500.


Request Hooks

@app.before_request
def before(request):
    request.trace_id = 'abc123'

@app.after_request
def after(request, response):
    response.headers['X-Trace-Id'] = request.trace_id
    return response

CLI

Create project:

mallo create myapp

Run with gunicorn (Linux/macOS):

mallo run app:app

Options:

mallo run app:app --host 0.0.0.0 --port 9000 --no-debug --no-reload

Notes:

  • Defaults: host localhost, port 8000, debug on.
  • On Windows, mallo run prints a gunicorn support warning. Use python app.py.

Minimal Full Example

from mallo import Mallo, render_template

app = Mallo(__name__, secret_key='dev-secret', live_reload=True)

@app.get('/')
def home(request):
    return render_template('index.html', csrf_token=request.csrf_token, name='Mallo')

@app.post('/save')
def save(request):
    name = request.post('name', '')
    request.session['name'] = name
    return f'Saved: {name}'

@app.errorhandler(404)
def custom_404(request):
    return render_template('errors/404.html')

if __name__ == '__main__':
    app.run(debug=True, use_reloader=True)

Contributing

Repository:

https://github.com/Betrand-dev/mallo-fr.git

Contributions are welcome for:

  • testing coverage
  • middleware improvements
  • session backends
  • template engine features
  • documentation and examples

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

mallo-0.3.0a6.tar.gz (27.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mallo-0.3.0a6-py3-none-any.whl (30.9 kB view details)

Uploaded Python 3

File details

Details for the file mallo-0.3.0a6.tar.gz.

File metadata

  • Download URL: mallo-0.3.0a6.tar.gz
  • Upload date:
  • Size: 27.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mallo-0.3.0a6.tar.gz
Algorithm Hash digest
SHA256 bc5349edc4d98fa03445df3c447890ac3a2539fed6f88c2bc4d158477813aa3d
MD5 bd6a5e071f30481386220e2dd13abbe3
BLAKE2b-256 d5d75556f90e1e2c190e51eba98c532bd057e149c7a28885cf27df38bbdda5ef

See more details on using hashes here.

Provenance

The following attestation bundles were made for mallo-0.3.0a6.tar.gz:

Publisher: publish.yml on Betrand-dev/mallo-fr

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mallo-0.3.0a6-py3-none-any.whl.

File metadata

  • Download URL: mallo-0.3.0a6-py3-none-any.whl
  • Upload date:
  • Size: 30.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mallo-0.3.0a6-py3-none-any.whl
Algorithm Hash digest
SHA256 22d3bfab9b88b0ed8d210fecd8187402a4ea0cad2963e9c3fd86b0297713121c
MD5 8ca094a3f7b5257f3af9211d6230329a
BLAKE2b-256 5eec558694d26e56a730aa659cf32f25046826972a3ed0bfca045f41afefd18a

See more details on using hashes here.

Provenance

The following attestation bundles were made for mallo-0.3.0a6-py3-none-any.whl:

Publisher: publish.yml on Betrand-dev/mallo-fr

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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