router and request changes

* redo route parameters
* add `Application.route` method
* disable generics on `Application` in `Request` for now
This commit is contained in:
Izalia Mae 2024-04-20 18:43:31 -04:00
parent cad946555b
commit 4d5da1ed50
5 changed files with 65 additions and 29 deletions

View file

@ -2,7 +2,7 @@ from __future__ import annotations
import traceback
from blib import HttpError, Signal
from blib import HttpError, Signal, Url
from collections.abc import Callable
from mimetypes import guess_type
from os.path import normpath
@ -36,7 +36,8 @@ class Application(Generic[R, RT, AT]):
template_context: TemplateContextCallback | None = None,
request_class: type[R] = Request, # type: ignore[assignment]
request_state_class: type[RT] = StateProxy, # type: ignore[assignment]
app_state_class: type[AT] = StateProxy) -> None: # type: ignore[assignment]
app_state_class: type[AT] = StateProxy, # type: ignore[assignment]
http_proxy: Url | str | None = None) -> None:
"""
Create a new ``Application``
@ -76,7 +77,7 @@ class Application(Generic[R, RT, AT]):
self.router: Router = get_router(self.name)
"Contains all of the routes the application will handle"
self.client: Client = Client()
self.client: Client = Client(name, http_proxy)
"Customized httpx client"
self.template: Template = Template(
@ -195,7 +196,7 @@ class Application(Generic[R, RT, AT]):
...
def add_route(self, method: str, path: str, handler: RouteHandler) -> None:
def add_route(self, handler: RouteHandler, method: str, *paths: str) -> None:
"""
Add a route handler
@ -204,7 +205,7 @@ class Application(Generic[R, RT, AT]):
:param handler: Function to call when the route is requested
"""
self.router.bind(handler, path, methods = [method])
self.router.bind(handler, method, *paths)
def add_static(self, path: str, location: Path | str, cached: bool = False) -> None:
@ -219,7 +220,7 @@ class Application(Generic[R, RT, AT]):
"""
handler = StaticHandler(path, location, cached)
self.add_route("GET", handler.path, handler)
self.add_route(handler, "GET", handler.path)
def print_access_log(self, request: Request, response: Response) -> None:
@ -246,6 +247,21 @@ class Application(Generic[R, RT, AT]):
print(message, flush = True)
def route(self, method: str, *paths: str) -> Callable[[RouteHandler], RouteHandler]:
"""
Decorator for adding a route from a callable
:param method: HTTP method the route should handle
:param paths: Paths the route should handle
"""
def wrapper(func: RouteHandler) -> RouteHandler:
self.router.bind(func, method, *paths)
return func
return wrapper
def _handle_error(self, request: Request, error: Exception) -> Response:
try:
return self.error_handlers[type(error)](request, error)

View file

@ -1,5 +1,4 @@
import httpx
import os
from typing import TypeVar
@ -24,7 +23,7 @@ class Client:
"HTTP client with useful methods for fetching ActivityPub resources"
def __init__(self, useragent: str = f"BarksharkASGI/{__version__}"):
def __init__(self, useragent: str = f"BarksharkASGI/{__version__}", proxy: str | None = None):
"""
Create a new ``Client`` object
@ -35,13 +34,10 @@ class Client:
http2 = True,
timeout = 5,
max_redirects = 3,
proxies = proxy,
headers = {
"User-Agent": useragent
},
proxies = {
"http": os.environ.get("all_proxy", os.environ.get("http_proxy")),
"https": os.environ.get("all_proxy", os.environ.get("https_proxy"))
}
)

View file

@ -10,10 +10,10 @@ from multipart.multipart import Field, File, create_form_parser
from typing import Any, Literal, TypedDict, TypeVar
from urllib.parse import parse_qsl
from .misc import Cookie, Stream
from .misc import Cookie, StateProxy, Stream
if typing.TYPE_CHECKING:
from .application import Application, R, RT, AT
from .application import Application
T = TypeVar("T", bound = JsonBase)
@ -45,7 +45,9 @@ class Request:
"Represents an incoming client request"
app: Application[R, RT, AT] # type: ignore[valid-type]
# I'm not sure how to "forward" generics without needing to specify then on every request,
# so it'll just be static for now until I can figure it out
app: Application[Request, StateProxy, StateProxy]
"Application the request is associated with"
stream: Stream
@ -75,7 +77,7 @@ class Request:
cookies: dict[str, Cookie]
"Cookies sent by the client"
state: RT # type: ignore[valid-type]
state: StateProxy
"Data to pass with the request"
extensions: dict[str, Any]
@ -92,7 +94,7 @@ class Request:
def __init__(self,
app: Application[R, RT, AT],
app: Application[Request, StateProxy, StateProxy],
scope: ScopeType,
stream: Stream) -> None:
@ -101,7 +103,7 @@ class Request:
raw_query = parse_qsl(scope["query_string"].decode("utf-8"), keep_blank_values = True)
raw_headers = ((k.decode("utf-8").title(), v.decode("utf-8")) for k, v in scope["headers"])
self.app: Application[R, RT, AT] = app
self.app: Application[Request, StateProxy, StateProxy] = app
self.stream: Stream = stream
self.method: str = scope["method"].upper()
self.path: str = scope["path"]
@ -111,7 +113,7 @@ class Request:
self.remote_raw: str | None = (scope.get("client") or (None,))[0]
self.local: str | None = (scope.get("server") or (None, ))[0]
self.cookies: dict[str, Cookie] = {}
self.state: RT = app.request_state_class(scope.get("state") or {})
self.state: StateProxy = app.request_state_class(scope.get("state") or {})
self.extensions: dict[str, Any] = scope.get("extensions", {}) # type: ignore[assignment]
# keep?

View file

@ -1,7 +1,7 @@
import http_router
from blib import HttpError
from collections.abc import Awaitable, Callable, Iterable
from collections.abc import Awaitable, Callable
from http_router.routes import RouteMatch
from typing import Any, Pattern
@ -55,23 +55,25 @@ class Router(http_router.Router):
def bind(self, # type: ignore[override]
target: RouteHandler,
*paths: str | Pattern[str],
methods: Iterable[str] | str | None = None) -> list[http_router.Route]:
method: str,
*paths: str | Pattern[str]) -> list[http_router.Route]:
"""
Add a route handler
:param target: Function to call on request
:param method: HTTP method the route should handle
:param paths: List of paths the route should handle
:param methods: List of methods the route should handle
"""
if isinstance(methods, list):
methods = [method.upper() for method in methods]
return http_router.Router.bind(self, target, *paths, methods = [method.upper()])
elif isinstance(methods, str):
methods = [methods.upper()]
return http_router.Router.bind(self, target, *paths, methods = methods)
def route(self, method: str, *paths: str) -> Callable[[RouteHandler], RouteHandler]: # type: ignore[override] # noqa: E501
def wrapper(handler: RouteHandler) -> RouteHandler:
self.bind(handler, method, *paths)
return handler
return wrapper
ROUTERS: dict[str, Router] = {
@ -120,7 +122,7 @@ def route(router: str, method: str, *paths: str) -> Callable[[RouteHandler], Rou
"""
def wrapper(func: RouteHandler) -> RouteHandler:
get_router(router).bind(func, *paths, methods = method)
get_router(router).bind(func, method, *paths)
return func
return wrapper

View file

@ -1,2 +1,22 @@
Usage
=====
Application
-----------
:class:`basgi.Application` is the base for a web server. Be sure to set a unique name.
.. code-block:: python
import basgi
@basgi.get("xyz.barkshark.BASGI", "/")
async def handle_home(request: basgi.Request) -> basgi.Response:
return basgi.Response(200, "Merp!")
app = basgi.Application("xyz.barkshark.BASGI")
@app.route("GET", "/about")
async def handle_about(request: basgi.Request) -> basgi.Response:
return basgi.Response(200, "")