uhttp-web
Web framework layer for uhttp - views, routing, and templates.
Installation
# Basic installation
pip install uhttp-web
# With Jinja2 template support
pip install uhttp-web[jinja]
Quick Start
JSON API View
from uhttp.server import HttpServer
from uhttp.web import JsonView, Router, NotFoundException
class UserView(JsonView):
PATTERN = '/api/user/{id:int}' # id is automatically converted to int
def do_get(self):
user = get_user(self.path_params['id']) # already int
if not user:
raise NotFoundException("User not found")
self.respond(user)
def do_delete(self):
delete_user(self.path_params['id'])
self.respond({'deleted': True})
router = Router()
router.add(UserView)
server = HttpServer(port=8080)
while True:
client = server.wait()
if client:
result = router.dispatch(manager, client)
if result is True:
pass # static file served
elif result:
result.request()
else:
client.respond({'error': 'Not found'}, status=404)
HTML View with Jinja2
from uhttp.web import HtmlView, RedirectException
class HomeView(HtmlView):
PATTERN = '/'
TEMPLATE = 'home.html.jinja'
def do_check(self):
if not self.connection.cookies.get('session'):
raise RedirectException('/login')
def do_get(self):
self.add_data(title='Home')
self.add_entity(users=User.list()) # auto-converts entities
self.respond()
URL Patterns with Type Conversion
Patterns support path parameters with automatic type conversion:
class ItemView(JsonView):
PATTERN = '/api/item/{id:int}' # int parameter
PATTERN = '/price/{min:float}/{max:float}' # float parameters
PATTERN = '/tag/{name}' # string (default)
PATTERN = '/api/{version}/user/{id:int}' # mixed
Supported types: str (default), int, float
If conversion fails, the view doesn't match (router tries next view).
Pattern Inheritance
Patterns are inherited and combined from parent classes:
class BaseView(HtmlView):
PATTERN = ''
def do_check(self):
self.user = self.get_logged_user()
class SiteView(BaseView):
PATTERN = '/{site}'
@property
def path_site(self):
# Custom conversion with caching
if not hasattr(self, '_site'):
self._site = Site.get(self.path_params['site'])
if not self._site:
raise NotFoundException()
return self._site
def do_check(self):
super().do_check()
_ = self.path_site # trigger loading
class CargoListView(SiteView):
PATTERN = '/cargo'
# Full pattern: /{site}/cargo
def do_get(self):
cargos = Cargo.list(site=self.path_site)
self.respond({'cargos': cargos})
class CargoDetailView(SiteView):
PATTERN = '/cargo/{id:int}'
# Full pattern: /{site}/cargo/{id:int}
def do_get(self):
cargo = Cargo.get(self.path_id) # lazy access
self.respond(cargo)
Benefits:
- Shared logic in parent
do_check()(auth, loading site, etc.) - Access to parent's instance variables (
self.user,self.path_site) - DRY patterns - no need to repeat
/{site}prefix
Parameter Access
Path and query parameters are accessible via path_* and query_* attributes:
class UserListView(JsonView):
PATTERN = '/users/{role}'
QUERY_PARAMS = {
'page': (int, 0), # (type, default)
'limit': (int, 20),
'search': (str, None), # optional
}
def do_get(self):
# Lazy-load with caching:
role = self.path_role # from URL path
page = self.query_page # from ?page=N, default 0
limit = self.query_limit # from ?limit=N, default 20
search = self.query_search # from ?search=X, default None
users = User.list(role=role, page=page, limit=limit)
self.respond({'users': users})
Features:
- Lazy-loaded on first access, then cached
- Type conversion with validation (raises
BadRequestExceptionon invalid value) QUERY_PARAMSinherited from parent classes- Override with
@propertyfor custom logic
Form Data
Access POST/PUT form data or JSON body:
class UserEditView(HtmlView):
PATTERN = '/user/{id:int}/edit'
def do_post(self):
if self.has_form('save'):
# form_data returns dict or {} if not available
user.set_from_form_data(self.form_data)
user.db_save(db)
raise RedirectException(f'/user/{self.path_id}')
if self.has_form('delete'):
user.db_delete(db)
raise RedirectException('/users')
# Get individual fields with defaults
name = self.get_form('name', '')
email = self.get_form('email') # None if missing
| Method | Description |
|---|---|
form_data |
Property returning form dict or {} |
get_form(key, default=None) |
Get field value or default |
has_form(*keys) |
True if all keys present |
Method Routing
Define handlers for specific HTTP methods:
class UserView(JsonView):
PATTERN = '/user/{id:int}'
def do_check(self):
# Called before any handler - auth, validation
if not self.is_authenticated():
raise UnauthorizedException()
def do_get(self):
self.respond(get_user(self.path_params['id']))
def do_post(self):
self.respond({'updated': True})
def do_delete(self):
self.respond({'deleted': True})
# PUT, PATCH, etc. → 405 Method Not Allowed
Request flow:
- Router matches URL pattern + type conversion
- Find handler:
do_get(),do_post(),do_put(),do_delete(),do_patch() - If no handler for method → 405 Method Not Allowed
do_check()→ validation, auth, permissionsdo_{method}()→ business logic
Backwards compatible: Views with only do_request() handle all methods.
Static File Serving
router = Router(debug=True) # debug=True → no-cache headers
router.add_static('/res/', './resources/')
router.add_static('/images/', '~/Storage/images/')
Content-type is detected automatically. Path traversal attacks are blocked.
Router Composition
Include sub-routers with URL prefixes for modular organization:
# admin/views.py
from uhttp.web import Router, JsonView
admin_router = Router()
class AdminHomeView(JsonView):
PATTERN = '/'
def do_get(self):
self.respond({'admin': True})
class UserListView(JsonView):
PATTERN = '/users'
def do_get(self):
self.respond({'users': []})
admin_router.add(AdminHomeView)
admin_router.add(UserListView)
# main.py
from uhttp.web import Router
from admin.views import admin_router
main_router = Router()
main_router.include(admin_router, prefix='/admin')
# Routes:
# /admin/ → AdminHomeView
# /admin/users → UserListView
Routers can be nested:
users_router = Router()
users_router.add(UserListView)
admin_router = Router()
admin_router.include(users_router, prefix='/users')
main_router = Router()
main_router.include(admin_router, prefix='/admin')
# /admin/users/ → UserListView
Exceptions
| Exception | Status | Description |
|---|---|---|
BadRequestException |
400 | Invalid request |
UnauthorizedException |
401 | Authentication required |
ForbiddenException |
403 | Access denied |
NotFoundException |
404 | Resource not found |
MethodNotAllowedException |
405 | Method not supported |
ServiceUnavailableException |
503 | Service unavailable |
RedirectException |
302 | Redirect to URL |
Entity Integration
Works with any ORM that provides get_template_data() method (e.g., dbentity):
from uhttp.web import entity_to_dict
# Single entity
user_dict = entity_to_dict(user)
# Collection
users_list = entity_to_dict(users)
# In HtmlView
self.add_entity(user=user, items=items)
Debug Helper
Pretty print nested data structures:
from uhttp.web import pp
data = {'users': [{'name': 'John'}, {'name': 'Jane'}]}
print(pp(data))
# {
# 'users': [
# {'name': 'John'},
# {'name': 'Jane'},
# ],
# }
Manager Interface
Views expect a manager object with optional attributes:
class Manager:
log = logging.getLogger() # for error logging
http_debug = False # show debug info in errors
uptime = {'seconds': 0} # server uptime
def get_template(self, name): # required for HtmlView
return jinja_env.get_template(name)
Examples
See examples/ directory:
json_api.py- JSON API with CRUD operationshtml_app.py- HTML app with Jinja2 templates, sessions, authsite_app.py- Multi-site app with pattern inheritance
Metadata
Release files for uhttp-web 1.0.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 | |
|---|---|---|---|
| uhttp_web-1.0.0.tar.gz | 28.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| uhttp_web-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 39.9 kB
Release files / uhttp_web-1.0.0.tar.gz
| Download URL | uhttp_web-1.0.0.tar.gz |
|---|---|
| Size | 28.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ca32fdf508ab395e8bf6c5789f6dc540fe12fba6d68f560ae1fb474e534828e6
|
|
BLAKE2b-256 checksum How to use checksums |
29a4a725c1412f8f73fe1d11cb448cf88079e6646ab3785d9ceb6860c3d54134
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Apr 1, 2026.
Transparency logRelease files / uhttp_web-1.0.0-py3-none-any.whl
| Download URL | uhttp_web-1.0.0-py3-none-any.whl |
|---|---|
| Size | 11.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9ee60364fe5f98ead589b8ea07dd72875dfbc2d67cfbf42b917a3fc41e68d805
|
|
BLAKE2b-256 checksum How to use checksums |
e2e04eda85e6d575a5e2bb5e82a756c79a4fbe106ab6a6bb9581e7ec2babb2cf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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 Apr 1, 2026.
Transparency log