Web framework layer for uhttp - views, routing, and templates
Project description
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
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 uhttp_web-1.0.0.tar.gz.
File metadata
- Download URL: uhttp_web-1.0.0.tar.gz
- Upload date:
- Size: 28.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca32fdf508ab395e8bf6c5789f6dc540fe12fba6d68f560ae1fb474e534828e6
|
|
| MD5 |
f98b3028aa509b9405d57ed6ad51771e
|
|
| BLAKE2b-256 |
29a4a725c1412f8f73fe1d11cb448cf88079e6646ab3785d9ceb6860c3d54134
|
Provenance
The following attestation bundles were made for uhttp_web-1.0.0.tar.gz:
Publisher:
publish.yml on pavelrevak/uhttp-web
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
uhttp_web-1.0.0.tar.gz -
Subject digest:
ca32fdf508ab395e8bf6c5789f6dc540fe12fba6d68f560ae1fb474e534828e6 - Sigstore transparency entry: 1206193013
- Sigstore integration time:
-
Permalink:
pavelrevak/uhttp-web@b83eaa551cd57130cece6305fd0443658e23c700 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/pavelrevak
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b83eaa551cd57130cece6305fd0443658e23c700 -
Trigger Event:
release
-
Statement type:
File details
Details for the file uhttp_web-1.0.0-py3-none-any.whl.
File metadata
- Download URL: uhttp_web-1.0.0-py3-none-any.whl
- Upload date:
- Size: 11.3 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 |
9ee60364fe5f98ead589b8ea07dd72875dfbc2d67cfbf42b917a3fc41e68d805
|
|
| MD5 |
85e3ab5092e427a54776702b35f1bbfd
|
|
| BLAKE2b-256 |
e2e04eda85e6d575a5e2bb5e82a756c79a4fbe106ab6a6bb9581e7ec2babb2cf
|
Provenance
The following attestation bundles were made for uhttp_web-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on pavelrevak/uhttp-web
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
uhttp_web-1.0.0-py3-none-any.whl -
Subject digest:
9ee60364fe5f98ead589b8ea07dd72875dfbc2d67cfbf42b917a3fc41e68d805 - Sigstore transparency entry: 1206193017
- Sigstore integration time:
-
Permalink:
pavelrevak/uhttp-web@b83eaa551cd57130cece6305fd0443658e23c700 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/pavelrevak
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b83eaa551cd57130cece6305fd0443658e23c700 -
Trigger Event:
release
-
Statement type: