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
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:
Server reload(use_reloaderinapp.run(...))Browser auto-refresh(live_reloadinMallo(...))
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 responsedict/list-> JSON response withapplication/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, port8000, debugon. - On Windows,
mallo runprints a gunicorn support warning. Usepython 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mallo-0.3.0a5.tar.gz.
File metadata
- Download URL: mallo-0.3.0a5.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
19f4108b7a485b5a0c34ac9dcbf51361f9208fd6d02befb89b60b780f6c310fc
|
|
| MD5 |
2e16daa563906f66d67a01ebebdde9bf
|
|
| BLAKE2b-256 |
aa42e6869acfe722b84405cf1417d29f1c21a4dff75c2ca88752de05132a5445
|
Provenance
The following attestation bundles were made for mallo-0.3.0a5.tar.gz:
Publisher:
publish.yml on Betrand-dev/mallo-fr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mallo-0.3.0a5.tar.gz -
Subject digest:
19f4108b7a485b5a0c34ac9dcbf51361f9208fd6d02befb89b60b780f6c310fc - Sigstore transparency entry: 962281680
- Sigstore integration time:
-
Permalink:
Betrand-dev/mallo-fr@8bbe0a00e8a9d7110b926f21042bf65fcce2cf16 -
Branch / Tag:
refs/tags/v0.3.0a5 - Owner: https://github.com/Betrand-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8bbe0a00e8a9d7110b926f21042bf65fcce2cf16 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mallo-0.3.0a5-py3-none-any.whl.
File metadata
- Download URL: mallo-0.3.0a5-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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d95590e4bb00c820a77a9d522891af4937cec400997df2c953038db291a4cf7b
|
|
| MD5 |
47cfdfe1f40daf8d733a66ef70cb7c2b
|
|
| BLAKE2b-256 |
cee3a3e0756a143b69f025ff96df4803cc87ef63b0e92727cceb86b92929391c
|
Provenance
The following attestation bundles were made for mallo-0.3.0a5-py3-none-any.whl:
Publisher:
publish.yml on Betrand-dev/mallo-fr
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mallo-0.3.0a5-py3-none-any.whl -
Subject digest:
d95590e4bb00c820a77a9d522891af4937cec400997df2c953038db291a4cf7b - Sigstore transparency entry: 962281722
- Sigstore integration time:
-
Permalink:
Betrand-dev/mallo-fr@8bbe0a00e8a9d7110b926f21042bf65fcce2cf16 -
Branch / Tag:
refs/tags/v0.3.0a5 - Owner: https://github.com/Betrand-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8bbe0a00e8a9d7110b926f21042bf65fcce2cf16 -
Trigger Event:
push
-
Statement type: