Source code for mi.utils

"""
Mi.pyを使用する上でちょっとした際に便利なツール一覧
"""
from __future__ import annotations

import json
import logging
import re
from datetime import datetime, timedelta
from inspect import isawaitable
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, TypeVar

import emoji
from mi import config
from mi.types.util import EmojiList

if TYPE_CHECKING:
    from mi.framework.models.note import Note

__all__ = (
    'deprecated_func',
    'MiTime',
    'get_cache_key',
    'key_builder',
    'get_module_logger',
    'get_unicode_emojis',
    'get_emoji_list',
    'emoji_count',
    'check_multi_arg',
    'async_all',
    'find',
    'remove_list_empty',
    'remove_dict_empty',
    'upper_to_lower',
    'str_lower',
    'bool_to_string'
)

T = TypeVar("T")


def deprecated_func(func):
    print(f'deprecated function:{func.__name__}')


class MiTime:
    def __init__(self, start: timedelta, end: datetime):
        self.start = start
        self.end = end


def get_cache_key(func):
    async def decorator(self, *args, **kwargs):
        ordered_kwargs = sorted(kwargs.items())
        key = (
                (func.__module__ or "")
                + '.{0}'
                + f'{self}'
                + str(args)
                + str(ordered_kwargs)
        )
        return await func(self, *args, **kwargs, cache_key=key)

    return decorator


def key_builder(func, cls, *args, **kwargs):
    ordered_kwargs = sorted(kwargs.items())
    key = (
            (func.__module__ or "")
            + f'.{func.__name__}'
            + f'{cls}'
            + str(args)
            + str(ordered_kwargs)
    )
    return key


def get_module_logger(module_name):
    logger = logging.getLogger(module_name)
    log_level = logging.DEBUG if config.debug else logging.INFO
    logger.setLevel(log_level)
    if not logger.hasHandlers():
        handler = logging.StreamHandler()
        formatter = logging.Formatter(
            '%(asctime)s %(name)s:%(lineno)s %(funcName)s [%(levelname)s]: '
            '%(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
    return logger


def get_unicode_emojis(text: str) -> List[str]:
    """
    テキストからunicode emojiを取得します

    Parameters
    ----------
    text : str
        テキスト

    Returns
    -------
    List[str]
        unicode emojiのリスト
    """

    return re.findall(emoji.get_emoji_regexp(), text)


def get_emoji_list(
        note_list: List[Note],
        *,
        include_unicode: bool = True,
        include_custom_emoji: bool = True
) -> Dict[EmojiList]:
    emoji_list = {}
    for note in note_list:
        if include_unicode and note.content:
            for i in get_unicode_emojis(note.content):
                emoji_list[i] = {
                    'name': i,
                    'count': emoji_list.get(i, {}).get('count', 0) + 1
                }

        if include_custom_emoji and note.emojis:
            for i in note.emojis:
                emoji_list[i.name] = {
                    'name': i.name,
                    'object': i,
                    'count': emoji_list.get(i.name, {}).get('count', 0) + 1
                }
    return emoji_list


[docs]def emoji_count(text: Optional[str] = None, emojis: Optional[List[str]] = None): """ テキストの中にいくつのemojiが含まれているかをカウントします Parameters ---------- text emojis """ if emojis is None: emojis = [] return len(emojis) if text is None else len(emojis) + emoji.emoji_count( text)
def check_multi_arg(*args: Any) -> bool: """ 複数の値を受け取り値が存在するかをboolで返します Parameters ---------- args : list 確認したい変数のリスト Returns ------- bool 存在する場合はTrue, 存在しない場合はFalse """ return bool([i for i in args if i]) async def async_all(gen, *, check=isawaitable): for elem in gen: if check(elem): elem = await elem if not elem: return False return True def find(predicate: Callable[[T], Any], seq: Iterable[T]) -> Optional[T]: """A helper to return the first element found in the sequence that meets the predicate. For example: :: member = discord.utils.find(lambda m: m.name == 'Mighty', channel.guild.members) would find the first :class:`~discord.Member` whose name is 'Mighty' and return it. If an entry is not found, then ``None`` is returned. This is different from :func:`py:filter` due to the fact it stops the moment it finds a valid entry. Parameters ----------- predicate A function that returns a boolean-like result. seq: :class:`collections.abc.Iterable` The iterable to search through. """ for element in seq: if predicate(element): return element return None def remove_list_empty(data: List[Any]) -> List[Any]: """ Parameters ---------- data: dict 空のkeyを削除したいdict Returns ------- Dict[str, Any] 空のkeyがなくなったdict """ return [k for k in data if k] def remove_dict_empty(data: Dict[str, Any]) -> Dict[str, Any]: """ Parameters ---------- data: dict 空のkeyを削除したいdict Returns ------- _data: dict 空のkeyがなくなったdict """ _data = {} _data = {k: v for k, v in data.items() if v is not None} _data = {k: v for k, v in data.items() if v} return _data def upper_to_lower( data: Dict[str, Any], field: Optional[Dict[str, Any]] = None, nest: bool = True, replace_list: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """ Parameters ---------- data: dict 小文字にしたいkeyがあるdict field: dict, default=None nest: bool, default=True ネストされたdictのkeyも小文字にするか否か replace_list: dict, default=None dictのkey名を特定の物に置き換える Returns ------- field : dict 小文字になった, key名が変更されたdict """ if data is None: return {} if replace_list is None: replace_list = {} if field is None: field = {} for attr in data: pattern = re.compile("[A-Z]") large = [i.group().lower() for i in pattern.finditer(attr)] result = [None] * (len(large + pattern.split(attr))) result[::2] = pattern.split(attr) result[1::2] = ["_" + i.lower() for i in large] default_key = "".join(result) if replace_list.get(attr): default_key = default_key.replace(attr, replace_list.get(attr)) field[default_key] = data[attr] if isinstance(field[default_key], dict) and nest: field[default_key] = upper_to_lower(field[default_key]) return field def str_lower(text: str): pattern = re.compile("[A-Z]") large = [i.group().lower() for i in pattern.finditer(text)] result = [None] * (len(large + pattern.split(text))) result[::2] = pattern.split(text) result[1::2] = ["_" + i.lower() for i in large] return "".join(result) def bool_to_string(boolean: bool) -> str: """ boolを小文字にして文字列として返します Parameters ---------- boolean : bool 変更したいbool値 Returns ------- true or false: str 小文字になったbool文字列 """ return "true" if boolean else "false"