Host a website in three lines of Python. SimpleSite is a thin, ergonomic layer over Quart that removes the boilerplate of serving static pages, without hiding Quart underneath if you ever need it.
from SimpleSite.easyhost import App
app = App()
app.hostStatic("/", "index.html")
app.hostStatic("/about", "<h1>About us</h1>")
app.run(port=8000)Serving a simple static page or two with Quart/Flask normally means writing a route, a view function and wiring up templates by hand. Every time. SimpleSite collapses that down to one call per page, while still giving you:
-
File or inline HTML: point
hostStaticat a file, a folder or just pass raw HTML directly -
Automatic error pages: Drop a
404.html/500.html/ etc. into yourtemplates/folder and SimpleSite serves it automatically on the matching error. No file? You get a built-in fallback instead of a stack trace. -
Still just Quart: The underlying app is a real
quart.Quartinstance. Nothing is hidden. More on how to still usequart.Quartlater. You can reach in and use Quart/Jinja features directly whenever you need to.
Requires Python>=3.10
pip install quart-simplesiteThe PyPI distribution is named quart-simplesite (the name SimpleSite was already taken), but the import stays the same:
from SimpleSite.easyhost import Appfrom SimpleSite.easyhost import App
app = App()
# Serve a file
app.hostStatic("/", "index.html")
# Serve a whole folder (looks for index.html inside it)
app.hostStatic("/docs", "docs/")
# Serve raw HTML directly, no file needed
app.hostStatic("/hello", "<h1>Hello, world!</h1>")
# Finally host the page
app.run(host="127.0.0.1", port=8000)SimpleSite scans your templates/ folder at startup for files named after HTTP status codes:
templates/
├── 404.html
├── 500.html
└── 403.html
If a matching file exists, it's served automatically when that error occurs. If not, SimpleSite falls back to a built-in error page. Your site never shows a raw traceback to visitors thanks to the fallbacks.
Supported codes out of the box: 400, 401, 403, 404, 405, 500, 502, 503
Creates a new SimpleSite application, wrapping a Quart app internally.
Registers a route
source is... |
Behaviour |
|---|---|
| a file path | Serves that file at route |
| a directory path | Serves index.html from that directory |
| a raw HTML string | Serves the string directly as the page content |
To distinguish those three types, SimpleSite checks if the source is a file. If that check misses, it checks if the path is a directory. If that misses too, it currently assumes that it is a raw HTML string.
route should be named /<route> as a naming convention, but can also be <route>.
super duper route to my site
will be parsed as/super-duper-route-to-my-site.
Hosts with the secure=True attribute will require being logged in. For that one has to create a .env-file within the project directory (see Auth / login below), and html has to point at a real template file rather than a raw string or directory.
Registers a login endpoint at route: GET serves the login form, POST checks the submitted user / password form fields and, on success, logs the user in.
html: optional path to a custom login-page template. If omitted, a minimal built-in form is served.- Requires auth to be configured (see below) — raises if
DEF_USER/DEF_PASS/SECRET_KEYaren't set.
app.createLogin("/login")
app.hostStatic("/dashboard", "dashboard.html", secure=True)Starts the server (via uvicorn under the hood)
from SimpleSite.easyhost import App
app = App()
# Use the Quart component to create a specific endpoint
@app.app.route("/")
async def index():
return await app.quart.render_template("index.html")| Name | What it is | What it can be used for |
|---|---|---|
| app | The Quart-Object | Used to add more routes or websockets |
| quart | The thing | Call any function requires the application context, but not the object (e.g. render_template()) |
SimpleSite/
├── conf/
│ ├── paths.toml
│ └── customs.toml
├── requirements.txt
├── ReadMe.md
├── .gitignore
├── templates/
├── .env
├── easyhost.py
└── error_registry.py
Content for .env:
DEF_USER='Admin-Username'
DEF_PASS='super-secure-admin-pass'
SECRET_KEY='some-long-random-string'
USE_DB=True| Fieldname | What it is used for | IsRequired | What happens without? |
|---|---|---|---|
| DEF_USER | The username you need for any login added using SimpleSite | False | Auth is considered disabled: createLogin() and secure=True both raise, since there's no login to check against |
| DEF_PASS | The password you need for any login added using SimpleSite | False | Same as above |
| SECRET_KEY | Signs the login session cookie (via quart-auth) |
Only if DEF_USER/DEF_PASS are set |
The app refuses to start (RuntimeError) — a login without a secret key can't be trusted |
| USE_DB | Determine if the DB should be used | False | The database won't be used. However, if DEF_PASS and DEF_USER are given it uses plain-text comparison. With DB an Argon2 hash will be used. |
Set DEF_USER and DEF_PASS in .env to turn auth on. Once that's done, SECRET_KEY becomes required too — SimpleSite raises at startup if it's missing, rather than starting up with an insecure or broken login.
from SimpleSite.easyhost import App
app = App()
app.createLogin("/login")
app.hostStatic("/", "index.html")
app.hostStatic("/admin", "admin.html", secure=True)
app.run(port=8000)Without USE_DB, credentials are checked directly against DEF_USER/DEF_PASS. With USE_DB=True, credentials are checked against the database instead (Argon2-hashed).
SimpleSite/
Project/
├── main.py
├── templates (folder with all html-files)
├── static (folder with all scripts/stylesheets etc)
└── others
Import for main.py
from SimpleSite.easyhost import App- hostFolder(): auto-map an entire directory to routes, file-based-routing style
- Dev mode with live reload
- Config file support for error template overrides
- Login-Templating (
createLogin(html=...))
MIT