decorator router fully implemented

pull/75/head
Ciro 2023-01-06 18:20:34 -03:00
rodzic e901433da0
commit e5bf0b201f
6 zmienionych plików z 252 dodań i 14 usunięć

Wyświetl plik

@ -184,7 +184,7 @@ def route_handler(res, req):
## Using ujson, orjson or any custom JSON serializer ## Using ujson, orjson or any custom JSON serializer
socketify by default uses built in `json` module with have great performance on PyPy, but if you wanna to use another module instead of the default you can just register using `app.json_serializer(module)` socketify by default uses built-in `json` module with has great performance on PyPy, but if you wanna use another module instead of the default you can just register using `app.json_serializer(module)`
```python ```python
from socketify import App from socketify import App

Wyświetl plik

@ -57,6 +57,13 @@ auth_router.get("/another", middleware(another_middie, home))
other_router = MiddlewareRouter(app, auth, another_middie) other_router = MiddlewareRouter(app, auth, another_middie)
other_router.get("/another_way", home) other_router.get("/another_way", home)
# you can also use middlewares when using the decorator router
private = app.router("/api", auth, another_middie)
# will serve in /api/users and use auth_middleware
@private.get("/users")
def get_users(res, req, user):
res.cork_end("Hello private API!")
app.listen( app.listen(
3000, 3000,

Wyświetl plik

@ -24,6 +24,34 @@ app.post("/", home)
``` ```
> Whenever your callback is a coroutine, such as the async/await, automatic corking can only happen in the very first portion of the coroutine (consider await a separator which essentially cuts the coroutine into smaller segments). Only the first "segment" of the coroutine will be called from socketify, the following async segments will be called by the asyncio event loop at a later point in time and will thus not be under our control with default corking enabled, HttpRequest object being stack-allocated and only valid in one single callback invocation so only valid in the first "segment" before the first await. If you just want to preserve headers, url, method, cookies and query string you can use `req.preserve()` to copy all data and keep it in the request object, but will be some performance penalty. Take a look in [Corking](corking.md) for get a more in deph information > Whenever your callback is a coroutine, such as the async/await, automatic corking can only happen in the very first portion of the coroutine (consider await a separator which essentially cuts the coroutine into smaller segments). Only the first "segment" of the coroutine will be called from socketify, the following async segments will be called by the asyncio event loop at a later point in time and will thus not be under our control with default corking enabled, HttpRequest object being stack-allocated and only valid in one single callback invocation so only valid in the first "segment" before the first await. If you just want to preserve headers, url, method, cookies and query string you can use `req.preserve()` to copy all data and keep it in the request object, but will be some performance penalty. Take a look in [Corking](corking.md) for get a more in deph information
You can also use the `Decorator router` as the name suggests this router allows to use of decorators for routing and also comes up with a prefix option, and middleware support.
```python
from socketify import App
app = App()
router = app.router()
@router.get("/")
def home(res, req):
res.end("Hello World!")
api = app.router(prefix="/api")
# will serve in /api/hello
@api.get("/hello")
def hello(res, req):
res.end("Hello API!")
private = app.router("/api", auth_middleware)
# will serve in /api/users and use auth_middleware
@private.get("/users")
def get_users(res, req, auth):
res.end("Hello private API!")
```
## Pattern matching ## Pattern matching
Routes are matched in order of specificity, not by the order you register them: Routes are matched in order of specificity, not by the order you register them:

Wyświetl plik

@ -123,8 +123,69 @@ def static_route(app, route, directory):
route = route[:-1] route = route[:-1]
app.get("%s/*" % route, route_handler) app.get("%s/*" % route, route_handler)
def middleware(*functions): def middleware(*functions):
syncs = []
asyncs = []
for function in functions:
# all is async after the first async
if inspect.iscoroutinefunction(function) or len(asyncs) > 0:
asyncs.append(function)
else:
syncs.append(function)
if len(asyncs) == 0: # pure sync
return sync_middleware(*functions)
if len(syncs) == 0: # pure async
return async_middleware(*functions)
# we use Optional data=None at the end so you can use and middleware inside a middleware
def optimized_middleware_route(res, req, data=None):
# cicle to all middlewares
for function in syncs:
# call middlewares
data = function(res, req, data)
# stops if returns Falsy
if not data:
return
async def wrapper(res, req, data):
# cicle to all middlewares
for function in asyncs:
# detect if is coroutine or not
if inspect.iscoroutinefunction(function):
data = await function(res, req, data)
else:
# call sync middleware
data = function(res, req, data)
# stops if returns Falsy
if not data:
break
return data
# in async query string, arguments and headers are only valid until the first await
# preserve queries, headers, parameters, url, full_url and method
req.preserve()
# go async
res.run_async(wrapper(res, req, data))
return optimized_middleware_route
def sync_middleware(*functions):
# we use Optional data=None at the end so you can use and middleware inside a middleware
def middleware_route(res, req, data=None):
# cicle to all middlewares
for function in functions:
# call middlewares
data = function(res, req, data)
# stops if returns Falsy
if not data:
break
return data
return middleware_route
def async_middleware(*functions):
# we use Optional data=None at the end so you can use and middleware inside a middleware # we use Optional data=None at the end so you can use and middleware inside a middleware
async def middleware_route(res, req, data=None): async def middleware_route(res, req, data=None):
some_async_as_run = False some_async_as_run = False
@ -149,67 +210,195 @@ def middleware(*functions):
return middleware_route return middleware_route
class DecoratorRouter:
def __init__(self, app, prefix: str="", *middlewares):
self.app = app
self.middlewares = middlewares
self.prefix = prefix
def get(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
middies.append(handler)
self.app.get(path, middleware(*middies))
else:
self.app.get(path, handler)
return handler
return decorator
def post(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
middies.append(handler)
self.app.post(path, middleware(*middies))
else:
self.app.post(path, handler)
return decorator
def options(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
middies.append(handler)
self.app.options(path, middleware(*middies))
else:
self.app.options(path, handler)
return decorator
def delete(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
middies.append(handler)
self.app.delete(path, middleware(*middies))
else:
self.app.delete(path, handler)
return decorator
def patch(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
middies.append(handler)
self.app.patch(path, middleware(*middies))
else:
self.app.patch(path, handler)
return decorator
def put(self, path: str):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
middies.append(handler)
self.app.put(path, middleware(*middies))
else:
self.app.put(path, handler)
return decorator
def head(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
middies.append(handler)
self.app.head(path, middleware(*middies))
else:
self.app.head(path, handler)
return decorator
def connect(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
middies.append(handler)
self.app.connect(path, middleware(*middies))
else:
self.app.connect(path, handler)
return decorator
def trace(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
middies.append(handler)
self.app.trace(path, middleware(*middies))
else:
self.app.trace(path, handler)
return decorator
def any(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
middies.append(handler)
self.app.any(path, middleware(*middies))
else:
self.app.any(path, handler)
return decorator
class MiddlewareRouter: class MiddlewareRouter:
def __init__(self, app, *middlewares): def __init__(self, app, *middlewares):
self.app = app self.app = app
self.middlewares = middlewares self.middlewares = middlewares
def get(self, path, handler): def get(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.get(path, middleware(*middies)) self.app.get(path, middleware(*middies))
return self return self
def post(self, path, handler): def post(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.post(path, middleware(*middies)) self.app.post(path, middleware(*middies))
return self return self
def options(self, path, handler): def options(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.options(path, middleware(*middies)) self.app.options(path, middleware(*middies))
return self return self
def delete(self, path, handler): def delete(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.delete(path, middleware(*middies)) self.app.delete(path, middleware(*middies))
return self return self
def patch(self, path, handler): def patch(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.patch(path, middleware(*middies)) self.app.patch(path, middleware(*middies))
return self return self
def put(self, path, handler): def put(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.put(path, middleware(*middies)) self.app.put(path, middleware(*middies))
return self return self
def head(self, path, handler): def head(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.head(path, middleware(*middies)) self.app.head(path, middleware(*middies))
return self return self
def connect(self, path, handler): def connect(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.connect(path, middleware(*middies)) self.app.connect(path, middleware(*middies))
return self return self
def trace(self, path, handler): def trace(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.trace(path, middleware(*middies)) self.app.trace(path, middleware(*middies))
return self return self
def any(self, path, handler): def any(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.any(path, middleware(*middies)) self.app.any(path, middleware(*middies))
return self return self

Wyświetl plik

@ -17,6 +17,7 @@ from .loop import Loop
from .status_codes import status_codes from .status_codes import status_codes
from .helpers import static_route from .helpers import static_route
from dataclasses import dataclass from dataclasses import dataclass
from .helpers import DecoratorRouter
mimetypes.init() mimetypes.init()
@ -2159,6 +2160,9 @@ class App:
self._response_extension = None self._response_extension = None
self._ws_extension = None self._ws_extension = None
def router(self, prefix: str="", *middlewares):
return DecoratorRouter(self, prefix, middlewares)
def register(self, extension): def register(self, extension):
if self._request_extension is None: if self._request_extension is None:
self._request_extension = AppExtension() self._request_extension = AppExtension()

Wyświetl plik

@ -25,7 +25,17 @@ def extension(request, response, ws):
# extensions must be registered before routes # extensions must be registered before routes
app.register(extension) app.register(extension)
async def home(res, req): def auth_middleware(res, req, data):
token = req.get_query("token")
print("token?", token)
req.token = token
return { "name": "Test" } if token else { "name", "Anonymous" }
router = app.router("", auth_middleware)
@router.get("/")
async def home(res, req, data=None):
print(data)
print("token", req.token) print("token", req.token)
cart = await req.get_cart() cart = await req.get_cart()
print("cart", cart) print("cart", cart)
@ -34,7 +44,7 @@ async def home(res, req):
print("token", req.token) print("token", req.token)
res.send("Hello World!") res.send("Hello World!")
app.get("/", home)
app.listen( app.listen(
3000, 3000,
lambda config: print("Listening on port http://localhost:%d now\n" % config.port), lambda config: print("Listening on port http://localhost:%d now\n" % config.port),