Add 4xx and 5xx http-code exception wrappers

This commit is contained in:
2025-07-16 04:15:22 +03:00
parent 751748f82a
commit 73da386003
6 changed files with 406 additions and 7 deletions

View File

@@ -0,0 +1,2 @@
from .client_errors import *
from .server_errors import *

View File

@@ -0,0 +1,265 @@
from turbosloth.exceptions.http_base import HTTPException
_client_error_codes = {
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Timeout',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Content Too Large',
414: 'URI Too Long',
415: 'Unsupported Media Type',
416: 'Range Not Satisfiable',
417: 'Expectation Failed',
418: 'I\'m a teapot',
421: 'Misdirected Request',
422: 'Unprocessable Content',
423: 'Locked',
424: 'Failed Dependency',
425: 'Too Early Experimental',
426: 'Upgrade Required',
428: 'Precondition Required',
429: 'Too Many Requests',
431: 'Request Header Fields Too Large',
451: 'Unavailable For Legal Reasons',
}
class BadRequestException(HTTPException):
code = 400
description = _client_error_codes[400]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class UnauthorizedException(HTTPException):
code = 401
description = _client_error_codes[401]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class PaymentRequiredException(HTTPException):
code = 402
description = _client_error_codes[402]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class ForbiddenException(HTTPException):
code = 403
description = _client_error_codes[403]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class NotFoundException(HTTPException):
code = 404
description = _client_error_codes[404]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class MethodNotAllowedException(HTTPException):
code = 405
description = _client_error_codes[405]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class NotAcceptableException(HTTPException):
code = 406
description = _client_error_codes[406]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class ProxyAuthenticationRequiredException(HTTPException):
code = 407
description = _client_error_codes[407]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class RequestTimeoutException(HTTPException):
code = 408
description = _client_error_codes[408]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class ConflictException(HTTPException):
code = 409
description = _client_error_codes[409]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class GoneException(HTTPException):
code = 410
description = _client_error_codes[410]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class LengthRequiredException(HTTPException):
code = 411
description = _client_error_codes[411]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class PreconditionFailedException(HTTPException):
code = 412
description = _client_error_codes[412]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class ContentTooLargeException(HTTPException):
code = 413
description = _client_error_codes[413]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class URITooLongException(HTTPException):
code = 414
description = _client_error_codes[414]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class UnsupportedMediaTypeException(HTTPException):
code = 415
description = _client_error_codes[415]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class RangeNotSatisfiableException(HTTPException):
code = 416
description = _client_error_codes[416]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class ExpectationFailedException(HTTPException):
code = 417
description = _client_error_codes[417]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class ImaTeapotException(HTTPException):
code = 418
description = _client_error_codes[418]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class MisdirectedRequestException(HTTPException):
code = 421
description = _client_error_codes[421]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class UnprocessableContentException(HTTPException):
code = 422
description = _client_error_codes[422]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class LockedException(HTTPException):
code = 423
description = _client_error_codes[423]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class FailedDependencyException(HTTPException):
code = 424
description = _client_error_codes[424]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class TooEarlyExperimentalException(HTTPException):
code = 425
description = _client_error_codes[425]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class UpgradeRequiredException(HTTPException):
code = 426
description = _client_error_codes[426]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class PreconditionRequiredException(HTTPException):
code = 428
description = _client_error_codes[428]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class TooManyRequestsException(HTTPException):
code = 429
description = _client_error_codes[429]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class RequestHeaderFieldsTooLargeException(HTTPException):
code = 431
description = _client_error_codes[431]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class UnavailableForLegalReasonsException(HTTPException):
code = 451
description = _client_error_codes[451]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)

View File

@@ -0,0 +1,19 @@
from typing import Callable, Optional
class HTTPException(Exception):
def __init__(self, code: int, description: str, message: str | None = None):
m = f'{code}\t{description}'
if message is not None:
m += f': {message}'
super().__init__(m)
self.code = code
self.description = description
self.message = message
def AutoException(code: int, description: str) -> Callable[[Optional[str]], HTTPException]:
def foo(message: str | None = None):
return HTTPException(code, description, message)
return foo

View File

@@ -0,0 +1,103 @@
from turbosloth.exceptions import HTTPException
_server_error_codes = {
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Timeout',
505: 'HTTP Version Not Supported',
506: 'Variant Also Negotiates',
507: 'Insufficient Storage',
508: 'Loop Detected',
510: 'Not Extended',
511: 'Network Authentication Required',
}
class InternalServerError(HTTPException):
code = 500
description = _server_error_codes[500]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class NotImplementedException(HTTPException):
code = 501
description = _server_error_codes[501]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class BadGatewayException(HTTPException):
code = 502
description = _server_error_codes[502]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class ServiceUnavailableException(HTTPException):
code = 503
description = _server_error_codes[503]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class GatewayTimeoutException(HTTPException):
code = 504
description = _server_error_codes[504]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class HTTPVersionNotSupportedException(HTTPException):
code = 505
description = _server_error_codes[505]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class VariantAlsoNegotiatesException(HTTPException):
code = 506
description = _server_error_codes[506]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class InsufficientStorageException(HTTPException):
code = 507
description = _server_error_codes[507]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class LoopDetectedException(HTTPException):
code = 508
description = _server_error_codes[508]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class NotExtendedException(HTTPException):
code = 510
description = _server_error_codes[510]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)
class NetworkAuthenticationRequiredException(HTTPException):
code = 511
description = _server_error_codes[511]
def __init__(self, message: str | None = None):
super().__init__(self.code, self.description, message)

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
import typing
from typing import Optional, Sequence, get_args
from .exceptions import MethodNotAllowedException, NotFoundException, HTTPException
from .types import HandlerType, MethodType
@@ -30,12 +31,11 @@ class Route:
if len(sequence) == 0:
ret = self.handler.get(method)
if ret is None:
# TODO: extract exceptions
raise ValueError('405')
raise MethodNotAllowedException(', '.join(map(str, self.handler.keys())))
return ret
subroute = self.static_subroutes.get(sequence[0])
if subroute is None:
raise ValueError('404')
raise NotFoundException('/'.join(sequence))
return subroute.get(method, sequence[1:])
@@ -59,6 +59,12 @@ class Router:
while len(segments) > 0 and len(segments[0]) == 0:
segments = segments[1:]
h = self._root.get(method, segments)
try:
h = self._root.get(method, segments)
except NotFoundException as e:
raise NotFoundException(path or '/') from e
except MethodNotAllowedException as e:
raise MethodNotAllowedException(
f'{method} /{path}' + ', allowed: ' + (e.message if len(e.message) > 0 else 'none')) \
from e
return h

View File

@@ -1,6 +1,7 @@
import pytest
from src.turbosloth.router import Router
from src.turbosloth.exceptions import NotFoundException, MethodNotAllowedException
def test_router_root_handler():
@@ -46,7 +47,10 @@ def test_router_match():
r = Router()
r.add('GET', 'asdf', f)
assert r.match('GET', '/asdf')
with pytest.raises(ValueError, match='404'):
with pytest.raises(NotFoundException, match='404\tNot Found: asd'):
r.match('GET', 'asd')
with pytest.raises(ValueError, match='405'):
with pytest.raises(MethodNotAllowedException, match=f'405\tMethod Not Allowed: POST /asdf, allowed: GET'):
r.match('POST', 'asdf')
with pytest.raises(MethodNotAllowedException, match=f'405\tMethod Not Allowed: POST /, allowed: none'):
r.match('POST', '')