markusressel/telegram-click

View on GitHub
telegram_click/permission/base.py

Summary

Maintainability
A
0 mins
Test Coverage
#  Copyright (c) 2019 Markus Ressel
#  .
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to deal
#  in the Software without restriction, including without limitation the rights
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#  copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#  .
#  The above copyright notice and this permission notice shall be included in all
#  copies or substantial portions of the Software.
#  .
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#  SOFTWARE.

import operator
from abc import abstractmethod
from functools import reduce
from typing import Dict

from telegram import Update
from telegram.ext import ContextTypes


class Permission:

    async def __call__(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
        return await self.evaluate(update, context)

    def __and__(self, other):
        return self._merge(other, operator.and_)

    def __or__(self, other):
        return self._merge(other, operator.or_)

    def _merge(self, other, op: operator):
        return MergedPermission([self, other], op)

    def __invert__(self):
        return InvertedPermission(self)

    def __str__(self):
        return self.__class__.__name__

    def __repr__(self):
        return "<{}>".format(self.__class__.__name__)

    @abstractmethod
    async def evaluate(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
        """
        Evaluates if the permission should be granted
        :param update: the message update
        :param context: the message context
        :return: True if the permission is granted, False otherwise
        """
        raise NotImplementedError()


class InvertedPermission(Permission):
    """
    Represents a permission that has been inverted.
    """

    def __init__(self, original_permission: Permission):
        """
        :param original_permission: the non-inverted permission
        """
        self.original_permission = original_permission

    async def evaluate(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
        return not bool(await self.original_permission(update, context))

    def __str__(self):
        return "(not {})".format(self.original_permission.__str__())

    def __repr__(self):
        return "<not {}>".format(self.original_permission.__repr__())


class MergedPermission(Permission):
    """
    Represents a permission consisting of two other permissions.
    """

    def __init__(self, permissions: list, op: operator):
        """

        :param permissions: Permission objects to merge
        :param op: operation to use
        """
        self.permissions = permissions
        self.op = op

        if self.op not in [operator.and_, operator.or_]:
            raise ValueError("Only operator.and_, operator.or_ are supported")

    async def evaluate(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
        """
        Evaluates all given permissions and combines their result using the given operator
        :return: reduced evaluation result
        """
        evaluations = [await x.evaluate(update, context) for x in self.permissions]
        return reduce(lambda x, y: self.op(x, y), evaluations)

    def __str__(self):
        permission_class_names = list(map(lambda x: x.__str__(), self.permissions))
        string = " {} ".format(self.op.__name__).join(permission_class_names)
        return "({})".format(string)

    def __repr__(self):
        permission_class_names = list(map(lambda x: x.__repr__(), self.permissions))
        repr = " {} ".format(self.op.__name__[1:]).join(permission_class_names)
        return "<{}>".format(repr)


async def get_evaluation_tree(update: Update, context: ContextTypes.DEFAULT_TYPE, permission: Permission) -> any:
    async def add_child(tree_node: Dict, permission: Permission):
        evaluation = await permission.evaluate(update, context)
        tree_node["permission"] = permission
        tree_node["evaluation"] = evaluation

        if isinstance(permission, MergedPermission):
            tree_node["op"] = permission.op
            tree_node["left"] = {}
            await add_child(tree_node["left"], permission.permissions[0])
            tree_node["right"] = {}
            await add_child(tree_node["right"], permission.permissions[1])

    root = {}
    await add_child(root, permission)
    return root