From 92445c58883c85be0d4831d12ab0d564680fd739 Mon Sep 17 00:00:00 2001 From: Cam Sima Date: Thu, 29 Aug 2024 11:54:47 -0400 Subject: [PATCH] update readme --- README.md | 64 +++++++++++++++++++++++++++++-- src/ziplineio/app.py | 43 +++++++++++---------- test/test_dependency_injection.py | 2 - 3 files changed, 82 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index a7fc103..e8b7797 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,10 @@ Middleware functions are called in the order they are added to the stack, and pa The first handler in the stack to return something other than a `Request` object (including `Exception`) will short-circuit the stack and return the response. +Middleware can be can be applied at the application, router, or individual route level. + ```python -from zipline import ZipLine, middleware +from zipline import ZipLine, Router, middleware # middleware functions @@ -60,21 +62,31 @@ def auth_guard(request, ctx): if not ctx.get("is_authed"): raise Exception("Unauthorized") +def is_user_guard(request, ctx): + if ctx.get("user_id") != request.path_params.get("id"): + raise Exception("Forbidden") + app = ZipLine() + # apply middleware to all routes -app.middleware(auth_middleware) +app.middleware([auth_middleware]) + +user_router = Router("/user") + +# apply middleware at the router-level +user_router.middleware([auth_guard]) @app.get("/profile") -@middleware([auth_guard]) +@middleware([is_user_guard]) # apply middleware to one router async def user_profile(request): return "Hello, World!" ``` ## Dependency Injection -Like with middeleware, ZipLine supports dependency injection at the route, router, or application level. Dependencies are passed to the handler function as keyword arguments. +Like with middeleware, ZipLine supports dependency injection at the route, router, or application level. In addition, dependencies can be injected into other dependencies. Dependencies are passed to the handler function as keyword arguments. ```python from zipline import ZipLine, inject @@ -102,6 +114,50 @@ async def home(request, user_service: UserService, logger: LoggingService): return user_service.get_user() ``` +Services can be any class, but Zipline includes a special `Service` class. Classes that inherit from `Service` have the ability to access all other services in their scope. + +```python +from zipline import ZipLine, Service, inject + +class LoggingService(Service): + def __init__(self): + self.name "logger" + + def error(self, message): + print(f"Error! {message}") + +class DBService(Service): + def __init__(self, logger: LoggingService): + self.name = "db_service" + + def get_connection(self): + try: + return db.connect() + except Exception as e: + self.logger.error(e) + +class UserService(Service): + def __init__(self, db_service: DBService, logger: LoggingService): + self.name = "user_service" + + def get_user(id: str): + conn = self.db_service.get_connection() + try: + return conn.query("SELECT * FROM users WHERE id = ?", id) + except Exception as e: + self.logger.error(f"User {id} not found") + + +app = ZipLine() +# inject all services; order doesn't matter +app.inject([LoggingService, DBService, UserService]) + +@app.route("/user/:id") +async def get_user(request, user_service: UserService): + user_id = request.path_params.get("id") + return user_service.get_user(user_id) +``` + ## Routing Like Express.js, ZipLine supports multiple, nested routers. diff --git a/src/ziplineio/app.py b/src/ziplineio/app.py index 7577d9a..f6f0472 100644 --- a/src/ziplineio/app.py +++ b/src/ziplineio/app.py @@ -1,4 +1,3 @@ -from ast import Not import inspect from typing import Any, Callable, List, Tuple, Type @@ -77,32 +76,34 @@ def middleware(self, middlewares: List[Callable]) -> None: def static(self, path: str, path_prefix: str = "/static") -> None: self.middleware([staticfiles(path, path_prefix)]) - def __call__(self, *args: Any, **kwds: Any) -> Any: - async def uvicorn_handler(scope: dict, receive: Any, send: Any) -> None: - if scope["type"] == "http": - req = parse_scope(scope) - handler, path_params = self.get_handler(req.method, req.path) - req.path_params = path_params + async def _get_and_call_handler( + self, method: str, path: str, req: Request + ) -> Callable: + handler, path_params = self.get_handler(method, path) + req.path_params = path_params - if handler is None: - # try running through middlewares - req, ctx, res = await run_middleware_stack( - self._router._router_level_middelwares, req, **{} - ) + if handler is None: + # try running through middlewares + req, ctx, res = await run_middleware_stack( + self._router._router_level_middelwares, req, **{} + ) - if res is None: - response = NotFoundHttpException() - else: - response = res + if res is None: + response = NotFoundHttpException() + else: + response = res - else: - response = await call_handler(handler, req) + else: + response = await call_handler(handler, req) + return response + def __call__(self, *args: Any, **kwds: Any) -> Any: + async def uvicorn_handler(scope: dict, receive: Any, send: Any) -> None: + if scope["type"] == "http": + req = parse_scope(scope) + response = await self._get_and_call_handler(req.method, req.path, req) raw_response = format_response(response, settings.DEFAULT_HEADERS) - print("SENDING RESPONSE") - print(raw_response) - await send( { "type": "http.response.start", diff --git a/test/test_dependency_injection.py b/test/test_dependency_injection.py index 6979761..2de39b3 100644 --- a/test/test_dependency_injection.py +++ b/test/test_dependency_injection.py @@ -1,5 +1,3 @@ -from operator import call -from os import name import unittest from ziplineio.app import App