Basic Web Backend
A small Python web backend framework designed to work with
lema-basic-web-server.
The project provides:
- static and dynamic routing;
- HTTP method handling;
- request parsing;
- response helper functions;
- JSON, form and multipart request processing;
- static file serving;
- cookies;
- a lightweight template engine;
- configurable error handlers;
- application logging;
- replaceable request and response adapters.
The framework uses only the Python standard library at runtime.
Requirements
- Python 3.10 or newer
lema-basic-web-serverwhen using the default Lema adapters
Development installation
Clone the repository and install it in editable mode:
git clone https://github.com/lema2000-dev/basic_web_backend.git
cd basic_web_backend
python -m pip install -e ".[dev]"
Run the test suite:
python -m pytest -v
Minimal application
from basic_web_backend import (
WebApplication,
html_response,
)
from basic_web_server import Server
app = WebApplication()
@app.route("/")
def index(request):
return html_response(
"<h1>Hello from Basic Web Backend!</h1>"
)
if __name__ == "__main__":
server = Server(app)
server.start_console()
After starting the console, use the server's run command to select
the listening address and port.
Routing
Routes are registered with the route() decorator:
@app.route("/about")
def about(request):
return html_response("<h1>About</h1>")
Multiple HTTP methods can be registered:
@app.route(
"/users",
methods=["GET", "POST"],
)
def users(request):
return html_response("<h1>Users</h1>")
Dynamic routes
Supported route converters:
| Converter | Example | Python value |
|---|---|---|
string |
/users/<string:name> |
str |
int |
/users/<int:user_id> |
int |
float |
/values/<float:value> |
float |
path |
/files/<path:filename> |
str |
Example:
@app.route("/users/<int:user_id>")
def user_profile(request, user_id):
return html_response(
f"<h1>User {user_id}</h1>"
)
The path converter can contain / characters and must be the final
route segment.
Request data
A view receives a backend Request object:
@app.route("/search")
def search(request):
query = request.query.get(
"query",
[""],
)[0]
return html_response(
f"<p>Search: {query}</p>"
)
Frequently used request attributes and methods:
request.method
request.path
request.query_string
request.query
request.headers
request.body
request.get_header("Content-Type")
request.get_headers("Cookie")
request.cookies
JSON
@app.route("/api/users", methods=["POST"])
def create_user(request):
data = request.get_json()
return json_response(
{
"name": data["name"],
"created": True,
},
status_code=201,
)
URL-encoded forms
form = request.get_form()
username = form["username"][0]
Multipart forms and file uploads
form, files = request.get_multipart()
description = form["description"][0]
uploaded_file = files["document"][0]
uploaded_file.filename
uploaded_file.content_type
uploaded_file.body
uploaded_file.headers
Repeated fields and files are preserved as lists.
Response helpers
The public response helpers are:
content_response()
html_response()
text_response()
json_response()
file_response()
redirect_response()
empty_response()
set_cookie()
delete_cookie()
Examples:
return text_response(
"Application is running"
)
return json_response(
{"successful": True},
status_code=200,
)
return redirect_response(
"/login",
status_code=302,
)
return empty_response(
status_code=204
)
Character encoding
Text, HTML and JSON responses support configurable character encoding:
return html_response(
"<h1>Hello</h1>",
charset="utf-16",
)
Cookies
response = json_response(
{"authenticated": True}
)
return set_cookie(
response=response,
key="session_id",
value="abc123",
max_age=3600,
secure=True,
http_only=True,
same_site="Lax",
)
Delete a cookie:
response = text_response("Logged out")
return delete_cookie(
response=response,
key="session_id",
)
Static files
Static files are served from the static directory by default:
static/
├── css/
│ └── style.css
└── js/
└── application.js
They are available under:
/static/css/style.css
/static/js/application.js
Custom configuration:
from basic_web_backend import (
ApplicationConfig,
WebApplication,
)
config = ApplicationConfig(
static_folder="assets",
static_url_path="/assets",
)
app = WebApplication(config=config)
Disable built-in static file serving:
config = ApplicationConfig(
static_folder=None
)
The static file handler prevents paths from escaping the configured static directory.
Template engine
Templates are loaded from the templates directory by default.
Example template:
<h1>Hello, {{ username }}!</h1>
{% if active %}
<p>Your account is active.</p>
{% else %}
<p>Your account is inactive.</p>
{% endif %}
<ul>
{% for item in items %}
<li>{{ item.name }}</li>
{% endfor %}
</ul>
Render the template from a view:
@app.route("/users/<string:username>")
def profile(request, username):
return app.render_template(
"profile.html",
username=username,
active=True,
items=[
{"name": "First"},
{"name": "Second"},
],
)
Template output is HTML-escaped by default.
Template configuration:
config = ApplicationConfig(
template_folder="templates",
template_encoding="utf-8",
template_autoescape=True,
template_cache=True,
template_auto_reload=False,
)
HTTP errors
Views can raise public HTTP exceptions:
from basic_web_backend import (
BadRequest,
NotFound,
)
@app.route("/products/<int:product_id>")
def product(request, product_id):
if product_id == 0:
raise NotFound(
path=request.path
)
if product_id < 0:
raise BadRequest(
"Product ID cannot be negative."
)
return html_response(
f"<h1>Product {product_id}</h1>"
)
Supported HTTP exceptions include:
BadRequest— 400Unauthorized— 401Forbidden— 403NotFound— 404MethodNotAllowed— 405Conflict— 409PayloadTooLarge— 413UnsupportedMediaType— 415UnprocessableContent— 422
Custom error handlers
@app.errorhandler(404)
def handle_not_found(error):
return html_response(
"<h1>Custom 404 page</h1>",
status_code=404,
)
@app.errorhandler(500)
def handle_internal_error(error):
return html_response(
"<h1>The service is unavailable</h1>",
status_code=500,
)
Unexpected exceptions produce a generic 500 response in production mode. Debug mode includes exception details in the response and must not be enabled in production.
Logging
Enable rotating application logs through ApplicationConfig:
config = ApplicationConfig(
log_file="logs/backend.log",
log_level="INFO",
log_max_bytes=1_000_000,
log_backup_count=5,
)
app = WebApplication(config=config)
When the active log reaches the configured size, older logs are stored as:
backend.log.1
backend.log.2
backend.log.3
Unexpected application errors and adapter failures are logged with their traceback. Normal 4xx client errors are not logged by the backend.
Do not store request bodies, passwords, authorization headers or cookie values in application logs.
Custom server adapters
A request adapter must provide:
class CustomRequestAdapter:
def convert(self, server_request):
return Request(
method=server_request.method,
path=server_request.path,
query_string=server_request.query_string,
headers=server_request.headers,
body=server_request.body,
)
It must return a backend Request object.
A response adapter must provide:
class CustomResponseAdapter:
def convert(self, backend_response):
return backend_response
Configure them with:
config = ApplicationConfig(
request_adapter=CustomRequestAdapter(),
response_adapter=CustomResponseAdapter(),
)
app = WebApplication(config=config)
A response adapter failure is logged and then propagated to the web server, because a failed response adapter cannot safely convert a backend-generated error response.
Version
import basic_web_backend
print(basic_web_backend.__version__)
License
This project is licensed under the MIT License.
Metadata
Release files for lema-basic-web-backend 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| lema_basic_web_backend-0.1.0.tar.gz | 43.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lema_basic_web_backend-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 69.6 kB
Release files / lema_basic_web_backend-0.1.0.tar.gz
| Download URL | lema_basic_web_backend-0.1.0.tar.gz |
|---|---|
| Size | 43.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9f589f27fa5adfef4430cce6d80ad3a3456fe8dd25d700aee72b015532c0f21a
|
|
BLAKE2b-256 checksum How to use checksums |
b23842ccbd5d01d71178efe21d0b60c5bd976c0a2361ac4cc597ebe630600a00
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency logRelease files / lema_basic_web_backend-0.1.0-py3-none-any.whl
| Download URL | lema_basic_web_backend-0.1.0-py3-none-any.whl |
|---|---|
| Size | 25.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ea3561ad16ce16c0f1a747ec4038ac7f1afbf2d8aea31610c2c913315a132c97
|
|
BLAKE2b-256 checksum How to use checksums |
7547250b2945d66a0ff829fa143e00c0aa510a8d35dedf5d436a7ca435d56594
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency log