Перейти к содержанию

Уровни приложения

Приложение Esmerald состоит из уровней. Уровни могут быть Gateway, WebSocketGateway, Include, handlers или даже другое приложение Esmerald или ChildEsmerald.

Существуют различные уровни в приложении, давайте рассмотрим пример.

from esmerald import Esmerald, Gateway, Include, Request, get


@get("/me")
async def me(request: Request) -> str:
    return "Hello, world!"


app = Esmerald(routes=[Include("/", routes=[Gateway(handler=me)])])

Уровни:

  1. Esmerald — экземпляр приложения.
  2. Include — второй уровень.
  3. Gateway — третий уровень, внутри Include.
  4. Handler — четвертый уровень, обработчик Gateway.

Вы можете создавать столько уровней, сколько захотите, включая вложенные Include и ChildEsmerald, создавая собственную архитектуру.

С использованием ChildEsmerald:

from pydantic import BaseModel, EmailStr

from esmerald import (
    APIView,
    ChildEsmerald,
    Esmerald,
    Gateway,
    Include,
    Request,
    WebSocket,
    WebSocketGateway,
    get,
    post,
    websocket,
)


@get("/me")
async def me(request: Request) -> str:
    return "Hello, world!"


@websocket(path="/ws")
async def websocket_endpoint_include(socket: WebSocket) -> None:
    await socket.accept()
    await socket.send_text("Hello, new world!")
    await socket.close()


class User(BaseModel):
    name: str
    email: EmailStr
    password: str


class UserApiView(APIView):
    path = "/users"

    @post("/create")
    async def create_user(self, data: User, request: Request) -> None: ...

    @websocket(path="/ws")
    async def websocket_endpoint(self, socket: WebSocket) -> None:
        await socket.accept()
        await socket.send_text("Hello, world!")
        await socket.close()


child_esmerald = ChildEsmerald(routes=[Gateway(handler=UserApiView)])

app = Esmerald(
    routes=[
        Include(
            "/",
            routes=[
                Gateway(handler=me),
                WebSocketGateway(handler=websocket_endpoint_include),
                Include("/child", child_esmerald),
            ],
        )
    ]
)

Уровни:

  1. Esmerald — Основной экземпляр приложения. Первый уровень.
  2. GatewayВторой уровень, внутри маршрутов приложения.
    1. HandlerТретий уровень, внутри Gateway.
  3. WebSocketGatewayВторой уровень, внутри маршрутов приложения.
    1. HandlerТретий уровень, внутри WebSocketGateway.
  4. IncludeВторой уровень, внутри маршрутов приложения.
    1. ChildEsmeraldТретий уровень внутри Include и также первый уровень как независимый экземпляр.
      1. GatewayВторой уровень, внутри ChildEsmerald.
        1. HandlerТретий уровень, внутри Gateway.

Warning

ChildEsmerald — это независимый экземпляр, подключаемый к основному приложению Esmerald. Поскольку он ведет себя как другой объект Esmerald, ChildEsmerald не имеет приоритета над верхним уровнем приложения. Вместо этого он обрабатывает свои собственные Gateway, WebSocketGateway, Include, handlers или даже другой Esmerald или ChildEsmerald и параметры в изоляции.

Исключения

ChildEsmerald, как указано в предупреждении выше, действует по своим собственным правилам, но у каждого правила есть исключения. Хотя это независимый экземпляр с собственными правилами, они не применяются ко всем параметрам.

Middleware и Permissions являются глобальными, и правила приоритета могут применяться между экземпляром Esmerald и соответствующими приложениями ChildEsmerald.

Другими словами, нет необходимости создавать или дублировать, одни и те же permissions и middleware (общие для обоих) для каждого экземпляра. Они могут быть применены глобально из основного объекта Esmerald.

from pydantic import BaseModel, EmailStr

from esmerald import (
    APIView,
    ChildEsmerald,
    Esmerald,
    Gateway,
    Include,
    Request,
    WebSocket,
    WebSocketGateway,
    get,
    post,
    settings,
    websocket,
)
from esmerald.config.jwt import JWTConfig
from esmerald.contrib.auth.saffier.base_user import User
from esmerald.exceptions import NotAuthorized
from esmerald.middleware.authentication import AuthResult, BaseAuthMiddleware
from esmerald.permissions import IsAdminUser
from esmerald.security.jwt.token import Token
from lilya._internal._connection import Connection
from lilya.middleware import DefineMiddleware as LilyaMiddleware
from lilya.types import ASGIApp
from saffier.exceptions import ObjectNotFound


class JWTAuthMiddleware(BaseAuthMiddleware):
    def __init__(self, app: "ASGIApp", config: "JWTConfig"):
        super().__init__(app)
        self.app = app
        self.config = config

    async def retrieve_user(self, user_id) -> User:
        try:
            return await User.get(pk=user_id)
        except ObjectNotFound:
            raise NotAuthorized()

    async def authenticate(self, request: Connection) -> AuthResult:
        token = request.headers.get(self.config.api_key_header)

        if not token:
            raise NotAuthorized("JWT token not found.")

        token = Token.decode(
            token=token, key=self.config.signing_key, algorithm=self.config.algorithm
        )

        user = await self.retrieve_user(token.sub)
        return AuthResult(user=user)


class IsAdmin(IsAdminUser):
    def is_user_staff(self, request: "Request") -> bool:
        """
        Add logic to verify if a user is staff
        """


@get()
async def home() -> None: ...


@get("/me")
async def me(request: Request) -> str:
    return "Hello, world!"


@websocket(path="/ws")
async def websocket_endpoint_include(socket: WebSocket) -> None:
    await socket.accept()
    await socket.send_text("Hello, new world!")
    await socket.close()


class User(BaseModel):
    name: str
    email: EmailStr
    password: str


class UserApiView(APIView):
    path = "/users"

    @post("/create")
    async def create_user(self, data: User, request: Request) -> None: ...

    @websocket(path="/ws")
    async def websocket_endpoint(self, socket: WebSocket) -> None:
        await socket.accept()
        await socket.send_text("Hello, world!")
        await socket.close()


child_esmerald = ChildEsmerald(
    routes=[Gateway("/home", handler=home), Gateway(handler=UserApiView)]
)

jwt_config = JWTConfig(
    signing_key=settings.secret_key,
)


app = Esmerald(
    routes=[
        Include(
            "/",
            routes=[
                Gateway(handler=me),
                WebSocketGateway(handler=websocket_endpoint_include),
                Include("/admin", child_esmerald),
            ],
        )
    ],
    permissions=[IsAdmin],
    middleware=[LilyaMiddleware(JWTAuthMiddleware, config=jwt_config)],
)

Заметки

Приведенный пример намеренно показан большим и "сложным", чтобы продемонстрировать, что даже несмотря на такую сложность, middleware и permissions остаются глобальными для всего приложения без необходимости реализовывать их как в Esmerald, так и в ChildEsmerald.