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
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
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.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(
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
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
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]
app.get("%s/*" % route, route_handler)
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
async def middleware_route(res, req, data=None):
some_async_as_run = False
@ -149,67 +210,195 @@ def middleware(*functions):
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:
def __init__(self, app, *middlewares):
self.app = app
self.middlewares = middlewares
def get(self, path, handler):
middies = list(self.middlewares)
middies = list(*self.middlewares)
middies.append(handler)
self.app.get(path, middleware(*middies))
return self
def post(self, path, handler):
middies = list(self.middlewares)
middies = list(*self.middlewares)
middies.append(handler)
self.app.post(path, middleware(*middies))
return self
def options(self, path, handler):
middies = list(self.middlewares)
middies = list(*self.middlewares)
middies.append(handler)
self.app.options(path, middleware(*middies))
return self
def delete(self, path, handler):
middies = list(self.middlewares)
middies = list(*self.middlewares)
middies.append(handler)
self.app.delete(path, middleware(*middies))
return self
def patch(self, path, handler):
middies = list(self.middlewares)
middies = list(*self.middlewares)
middies.append(handler)
self.app.patch(path, middleware(*middies))
return self
def put(self, path, handler):
middies = list(self.middlewares)
middies = list(*self.middlewares)
middies.append(handler)
self.app.put(path, middleware(*middies))
return self
def head(self, path, handler):
middies = list(self.middlewares)
middies = list(*self.middlewares)
middies.append(handler)
self.app.head(path, middleware(*middies))
return self
def connect(self, path, handler):
middies = list(self.middlewares)
middies = list(*self.middlewares)
middies.append(handler)
self.app.connect(path, middleware(*middies))
return self
def trace(self, path, handler):
middies = list(self.middlewares)
middies = list(*self.middlewares)
middies.append(handler)
self.app.trace(path, middleware(*middies))
return self
def any(self, path, handler):
middies = list(self.middlewares)
middies = list(*self.middlewares)
middies.append(handler)
self.app.any(path, middleware(*middies))
return self

Wyświetl plik

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

Wyświetl plik

@ -25,7 +25,17 @@ def extension(request, response, ws):
# extensions must be registered before routes
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)
cart = await req.get_cart()
print("cart", cart)
@ -34,7 +44,7 @@ async def home(res, req):
print("token", req.token)
res.send("Hello World!")
app.get("/", home)
app.listen(
3000,
lambda config: print("Listening on port http://localhost:%d now\n" % config.port),