Плагин Event (событийный плагин / RPC плагин)

EventPlugin:

  1. Позволяет создавать событийное API (RPC)

  2. Интегрируется с плагином ApiSpecPlugin позволяя создавать документацию для RPC views и отображать вместе с общей документацией. Описание view производится при помощи yaml.

  3. Интегрируется с плагином PermissionPlugin, можно из view получить доступ к ограничения по любой модели. Также доступ к view ограничивается общими декораторами, которые задаются при инициализации API.

Работа с плагином

Чтобы создать RPC API связанного с какой-либо моделью, нужно:

  1. Создать класс от combojsonapi.event.resource.EventsResource, ниже будет более подробно сказано об этом.

  2. В ресурс менеджере используется атрибут events, там нужно указать класс, созданный в первом пункте. Если использовать в ресурс менеджере ResourceDetail, то в каждую view RPC API будет приходить также id модели (которая указана в ресурс менеджере).

Описание работы плагина

После того как создали класс, унаследованный от combojsonapi.event.resource.EventsResource,любой метод в этом классе, который начинается с event_ будет считаться самостоятельным view.Адрес нового view будет формироваться в формате .../<url ресурс менеджера, к которому привязанданный класс с методами RPC API>/event_<название нашего метода, после event_>.

Есть возможность переопределить суффикс url, для этогоиспользуйте see extra параметры Event плагина.

По умолчанию создаются POST ресурсы. Но можно создать GET ресурс, назвав метод event_get_something. Также поддерживается event_post_something - создастся POST ресурс.

Есть возможность переопределить метод, для этогоиспользуйте see extra параметры Event плагина.

Другие методы и атрибуты Event класса не будут видны внутри view.

Описания view

  1. Метод event[_опционально тип запроса]_<название метода> должен принимать следующие параметры:
    • id: int [опционально] - id сущности модели. Если класс с данным view указан в ресурс менеджере ResourceDetail.

    • _permission_user: PermissionUser = None - пермишены для данного авторизованного пользователя (при условии, что подключен плагин PermissionPlugin)

    • *args

    • **kwargs

  2. При описании ответов view используйте формат JSONAPI

  3. В начале метода нужно описать документацию к view на yaml (swagger/openapi), чтобы выполнить интеграцию с плагином автодокументации ApiSpecPlugin

Extra параметры плагина

Event ресурс может быть модифицирован при помощи extra параметров:

  • method - метод view - GET/POST/PUT/PATCH/DELETE

  • url_suffix - кастомный суффикс url вместо имени метода (функции)

В этом примере новый view будет создан: PUT /user/{id}/update_online/. Без extra параметров для event результат был бы такой: POST /user/{id}/event_update_user_online_status/.

from flask import request
from flask_combo_jsonapi import ResourceDetail
from combojsonapi.event.resource import EventsResource


class UserDetailEvents(EventsResource):
    def event_update_user_online_status(self, *args, **kwargs):
        # language=YAML
        """
        ---
        # some swagger description
        """
        result = some_custom_stuff_to_do(kwargs, request.json)
        return result

    event_update_user_online_status.extra = {
        "method": "PUT",
        "url_suffix": "update_online",
    }


class UserResourceDetail(ResourceDetail):
    schema = UserSchema
    events = UserDetailEvents
    methods = []
    data_layer = {
        'session': db.session,
        'model': User,
    }

Пример подключения плагина

Мы хотим загрузить аватарку пользователя. Здесь мы также используем плагин ApiSpecPlugin, чтобы посмотреть на его работу.

import os
from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import Query, load_only, scoped_session
from flask_combo_jsonapi.marshmallow_fields import Relationship
from flask_combo_jsonapi import Api, ResourceList, ResourceDetail
from flask_combo_jsonapi.plugin import BasePlugin
from flask_combo_jsonapi.querystring import QueryStringManager
from combojsonapi.event.resource import EventsResource
from combojsonapi.event import EventPlugin
from combojsonapi.spec import ApiSpecPlugin
from marshmallow_jsonapi.flask import Schema
from marshmallow_jsonapi import fields


app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)

"""Models description"""

class User(db.Model):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    fullname = Column(String)
    email = Column(String)
    url_avatar = Column(String)
    password = Column(String)


db.create_all()

"""Models' schemas"""

class UserSchema(Schema):
    class Meta:
        type_ = 'user'
        self_view = 'user_detail'
        self_view_kwargs = {'id': '<id>'}
        self_view_many = 'user_list'
        ordered = True
    id = fields.Integer(as_string=True)
    name = fields.String()
    fullname = fields.String()
    email = fields.String()
    url_avatar = fields.String()
    password = fields.String()

"""Resource managers description for API"""

class UserResourceDetailEvents(EventsResource):
    def event_update_avatar(self, *args, id: int = None, **kwargs):
        # language=YAML
        """
        ---
        summary: Обновление аватарки пользователя
        tags:
        - User
        parameters:
        - in: path
          name: id
          required: True
          type: integer
          format: int32
          description: 'id пользователя'
        - in: formData
          name: new_avatar
          type: file
          description: Новая аватарка пользователя
        consumes:
        - application/json
        responses:
          200:
            description: Ничего не вернёт
        """
        user = User.query.filter(User.id == id).one_or_none()
        if user is None:
            raise AccessDenied('You can not work with the user')

        avatar = request.files.get('new_avatar')
        if avatar:
            if avatar:
                filename = avatar.filename
                avatar.save(os.path.join(filename))
            user.url_avatar = os.path.join(filename)
            db.session.commit()
        return 'success', 201

    def event_get_info(self, *args, **kwargs):
        return {'message': 'GET INFO'}

    def event_post_info(self, *args, **kwargs):
        data = request.json
        data.update(message='POST INFO')
        return data

class UserResourceDetail(ResourceDetail):
    schema = UserSchema
    events = UserResourceDetailEvents
    methods = ['GET']
    data_layer = {
        'session': db.session,
        'model': User,
    }

class UserResourceList(ResourceList):
    schema = UserSchema
    methods = ['GET', 'POST']
    data_layer = {
        'session': db.session,
        'model': User,
    }

"""Initializing the API"""

app.config['OPENAPI_URL_PREFIX'] = '/api/swagger'
app.config['OPENAPI_VERSION'] = '3.0.0'
app.config['OPENAPI_SWAGGER_UI_PATH'] = '/'
app.config['OPENAPI_SWAGGER_UI_VERSION'] = '3.45.0'

api_spec_plugin = ApiSpecPlugin(
    app=app,
    # Declaring tags list with their descriptions, so API gets organized into groups. This is optional: when there's no tags,
    # api will be grouped automatically by type schemas names (type_)
    tags={
        'User': 'User API'
    }
)

api_json = Api(
    app,
    plugins=[
        api_spec_plugin,
        EventPlugin()
    ]
)
api_json.route(UserResourceDetail, 'user_detail', '/api/user/<int:id>/', tag='User')
api_json.route(UserResourceList, 'user_list', '/api/user/', tag='User')


if __name__ == '__main__':
    for i in range(10):
        u = User(name=f'name{i}', fullname=f'fullname{i}', email=f'email{i}', password=f'password{i}')
        db.session.add(u)
    db.session.commit()
    app.run(port='9999')