Merged with style/add-flake8-type-checking

This commit is contained in:
Maxim Slipenko 2024-08-26 20:18:12 +03:00
commit 5950fa3bb8
18 changed files with 115 additions and 80 deletions

View File

@ -4,3 +4,5 @@ per-file-ignores =
max-line-length = 88 max-line-length = 88
count = true count = true
extend-ignore = E203,E701 extend-ignore = E203,E701
extend-select = TC010,TC200

View File

@ -29,6 +29,8 @@ repos:
rev: 7.1.0 # sync:flake8:poetry.lock rev: 7.1.0 # sync:flake8:poetry.lock
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies:
- flake8-type-checking
- repo: https://github.com/PyCQA/bandit - repo: https://github.com/PyCQA/bandit
rev: 1.7.9 # sync:bandit:poetry.lock rev: 1.7.9 # sync:bandit:poetry.lock
hooks: hooks:

View File

@ -1,6 +1,7 @@
from typing import TYPE_CHECKING
from aiogram import Bot, Router from aiogram import Bot, Router
from aiogram.enums import ParseMode from aiogram.enums import ParseMode
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup from aiogram.fsm.state import State, StatesGroup
from aiogram.types import ( from aiogram.types import (
BufferedInputFile, BufferedInputFile,
@ -14,6 +15,9 @@ from karkas_core.modules_system.public_api import Utils, get_fsm_context
from .report import Report from .report import Report
if TYPE_CHECKING:
from aiogram.fsm.context import FSMContext
router = Router() router = Router()
@ -65,7 +69,7 @@ app_info_message = """Укажите название и версию прило
@router.message(ReportState.input_system_info) @router.message(ReportState.input_system_info)
async def system_entered(message: Message, state: FSMContext): async def system_entered(message: Message, state: "FSMContext"):
await state.update_data(system=message.text) await state.update_data(system=message.text)
await message.answer( await message.answer(
text=app_info_message, text=app_info_message,
@ -80,28 +84,28 @@ step_by_step_message = (
@router.message(ReportState.input_app_name) @router.message(ReportState.input_app_name)
async def app_name_entered(message: Message, state: FSMContext): async def app_name_entered(message: Message, state: "FSMContext"):
await state.update_data(app=message.text) await state.update_data(app=message.text)
await message.answer(text=step_by_step_message) await message.answer(text=step_by_step_message)
await state.set_state(ReportState.input_problem_step_by_step) await state.set_state(ReportState.input_problem_step_by_step)
@router.message(ReportState.input_problem_step_by_step) @router.message(ReportState.input_problem_step_by_step)
async def problem_step_by_step_entered(message: Message, state: FSMContext): async def problem_step_by_step_entered(message: Message, state: "FSMContext"):
await state.update_data(problem_step_by_step=message.text) await state.update_data(problem_step_by_step=message.text)
await message.answer(text="Опиши, что произошло (фактический результат).") await message.answer(text="Опиши, что произошло (фактический результат).")
await state.set_state(ReportState.input_actual_result) await state.set_state(ReportState.input_actual_result)
@router.message(ReportState.input_actual_result) @router.message(ReportState.input_actual_result)
async def actual_result_entered(message: Message, state: FSMContext): async def actual_result_entered(message: Message, state: "FSMContext"):
await state.update_data(actual=message.text) await state.update_data(actual=message.text)
await message.answer(text="Опиши ожидаемый результат.") await message.answer(text="Опиши ожидаемый результат.")
await state.set_state(ReportState.input_expected_result) await state.set_state(ReportState.input_expected_result)
@router.message(ReportState.input_expected_result) @router.message(ReportState.input_expected_result)
async def expected_result_entered(message: Message, state: FSMContext): async def expected_result_entered(message: Message, state: "FSMContext"):
await state.update_data(expected=message.text) await state.update_data(expected=message.text)
await message.answer( await message.answer(
text="Если есть дополнительная информация, то напиши ее.", text="Если есть дополнительная информация, то напиши ее.",
@ -116,7 +120,7 @@ async def expected_result_entered(message: Message, state: FSMContext):
@router.message(ReportState.input_additional_info) @router.message(ReportState.input_additional_info)
async def additional_info_entered(message: Message, state: FSMContext): async def additional_info_entered(message: Message, state: "FSMContext"):
if message.text == "Дополнительной информации нет": if message.text == "Дополнительной информации нет":
additional_info = "" additional_info = ""
else: else:

View File

@ -1,12 +1,14 @@
from typing import Any, Awaitable, Callable, Dict from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict
from aiogram import BaseMiddleware from aiogram import BaseMiddleware
from aiogram.types import Chat, TelegramObject
from .db.tables import ChatInfo from .db.tables import ChatInfo
if TYPE_CHECKING:
from aiogram.types import Chat, TelegramObject
async def update_chat_info(chat: Chat):
async def update_chat_info(chat: "Chat"):
chat_name = chat.title if chat.type != "private" else "" chat_name = chat.title if chat.type != "private" else ""
await ChatInfo.insert( await ChatInfo.insert(
@ -24,8 +26,8 @@ async def update_chat_info(chat: Chat):
class ChatsMiddleware(BaseMiddleware): class ChatsMiddleware(BaseMiddleware):
async def __call__( async def __call__(
self, self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], handler: Callable[["TelegramObject", Dict[str, Any]], Awaitable[Any]],
event: TelegramObject, event: "TelegramObject",
data: Dict[str, Any], data: Dict[str, Any],
) -> Any: ) -> Any:
chat = event.chat chat = event.chat

View File

@ -1,8 +1,6 @@
import asyncio import asyncio
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from .config_manager import ConfigManager
try: try:
import dash_bootstrap_components as dbc import dash_bootstrap_components as dbc
import flask import flask
@ -17,8 +15,10 @@ from karkas_core.modules_system.public_api import get_module
if TYPE_CHECKING: if TYPE_CHECKING:
from karkas_blocks.standard.roles import Roles as IRoles from karkas_blocks.standard.roles import Roles as IRoles
from .config_manager import ConfigManager
def create_control(key: str, config: ConfigManager):
def create_control(key: str, config: "ConfigManager"):
value = config.get(key) value = config.get(key)
meta = config.get_meta(key) meta = config.get_meta(key)
@ -85,7 +85,7 @@ def create_control(key: str, config: ConfigManager):
return dbc.Row(row, className="mb-3 mx-1") return dbc.Row(row, className="mb-3 mx-1")
def build_settings_tree(config: ConfigManager): def build_settings_tree(config: "ConfigManager"):
tree = {} tree = {}
for key, value in config._metadata.items(): for key, value in config._metadata.items():
@ -134,7 +134,7 @@ def create_settings_components(tree, level=0):
return components return components
def get_miniapp_blueprint(config: ConfigManager, prefix: str): def get_miniapp_blueprint(config: "ConfigManager", prefix: str):
Roles: "type[IRoles]" = get_module("standard.roles", "Roles") Roles: "type[IRoles]" = get_module("standard.roles", "Roles")
roles = Roles() roles = Roles()

View File

@ -3,7 +3,6 @@ from typing import TYPE_CHECKING
from aiogram import Router from aiogram import Router
from aiogram.filters import Command from aiogram.filters import Command
from aiogram.types import Message
from karkas_core.modules_system.public_api import ( from karkas_core.modules_system.public_api import (
get_metainfo, get_metainfo,
@ -12,6 +11,8 @@ from karkas_core.modules_system.public_api import (
) )
if TYPE_CHECKING: if TYPE_CHECKING:
from aiogram.types import Message
from karkas_blocks.standard.config import IConfig from karkas_blocks.standard.config import IConfig
config: "IConfig" = get_module("standard.config", "config") config: "IConfig" = get_module("standard.config", "config")
@ -47,7 +48,7 @@ def format_commands(commands_dict):
return "\n".join(formatted_commands) return "\n".join(formatted_commands)
async def help(message: Message): async def help(message: "Message"):
commands = "" commands = ""
version = "" version = ""

View File

@ -1,9 +1,13 @@
from typing import TYPE_CHECKING
import flask import flask
from aiogram.utils.web_app import safe_parse_webapp_init_data from aiogram.utils.web_app import safe_parse_webapp_init_data
from dash import Dash
from dash_extensions.enrich import Input, Output from dash_extensions.enrich import Input, Output
from flask import request from flask import request
if TYPE_CHECKING:
from dash import Dash
# TODO: добавить прокидывание BASE_PATH, т.к. это параметр из настроек # TODO: добавить прокидывание BASE_PATH, т.к. это параметр из настроек
WEBAPP_LOADER_TEMPLATE = """ WEBAPP_LOADER_TEMPLATE = """
@ -112,7 +116,7 @@ def get_auth_server(bot_token: str):
return server return server
def setup_auth_clientcallbacks(app: Dash): def setup_auth_clientcallbacks(app: "Dash"):
app.clientside_callback( app.clientside_callback(
""" """
function(n_intervals) { function(n_intervals) {

View File

@ -1,12 +1,14 @@
from typing import Any, Awaitable, Callable, Dict from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict
from aiogram import BaseMiddleware from aiogram import BaseMiddleware
from aiogram.types import Message, TelegramObject
from .db.tables import ChatStats, Messages, UserStats from .db.tables import ChatStats, Messages, UserStats
if TYPE_CHECKING:
from aiogram.types import Message, TelegramObject
async def update_chat_stats(event: Message):
async def update_chat_stats(event: "Message"):
await ChatStats.insert( await ChatStats.insert(
ChatStats(chat_id=event.chat.id, date=event.date, messages_count=1) ChatStats(chat_id=event.chat.id, date=event.date, messages_count=1)
).on_conflict( ).on_conflict(
@ -18,7 +20,7 @@ async def update_chat_stats(event: Message):
).run() ).run()
async def update_user_stats(event: Message): async def update_user_stats(event: "Message"):
await UserStats.insert( await UserStats.insert(
UserStats( UserStats(
key=f"{event.chat.id}-{event.from_user.id}", key=f"{event.chat.id}-{event.from_user.id}",
@ -36,7 +38,7 @@ async def update_user_stats(event: Message):
).run() ).run()
async def save_messages(event: Message): async def save_messages(event: "Message"):
await Messages.insert( await Messages.insert(
Messages( Messages(
key=f"{event.chat.id}-{event.message_id}", key=f"{event.chat.id}-{event.message_id}",
@ -54,8 +56,8 @@ async def save_messages(event: Message):
class StatisticsMiddleware(BaseMiddleware): class StatisticsMiddleware(BaseMiddleware):
async def __call__( async def __call__(
self, self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], handler: Callable[["TelegramObject", Dict[str, Any]], Awaitable[Any]],
event: TelegramObject, event: "TelegramObject",
data: Dict[str, Any], data: Dict[str, Any],
) -> Any: ) -> Any:

View File

@ -1,12 +1,14 @@
from typing import Any, Awaitable, Callable, Dict from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict
from aiogram import BaseMiddleware from aiogram import BaseMiddleware
from aiogram.types import TelegramObject, User
from .db.tables import UserInfo from .db.tables import UserInfo
if TYPE_CHECKING:
from aiogram.types import TelegramObject, User
async def update_user_info(user: User):
async def update_user_info(user: "User"):
if user.last_name is None: if user.last_name is None:
user_name = user.first_name user_name = user.first_name
else: else:
@ -26,8 +28,8 @@ async def update_user_info(user: User):
class UsersMiddleware(BaseMiddleware): class UsersMiddleware(BaseMiddleware):
async def __call__( async def __call__(
self, self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], handler: Callable[["TelegramObject", Dict[str, Any]], Awaitable[Any]],
event: TelegramObject, event: "TelegramObject",
data: Dict[str, Any], data: Dict[str, Any],
) -> Any: ) -> Any:
user = event.from_user user = event.from_user

View File

@ -7,7 +7,6 @@ from aiogram import Bot, Router, types
from aiogram.enums import ChatMemberStatus, ParseMode from aiogram.enums import ChatMemberStatus, ParseMode
from aiogram.exceptions import TelegramBadRequest from aiogram.exceptions import TelegramBadRequest
from aiogram.filters import JOIN_TRANSITION, LEAVE_TRANSITION, ChatMemberUpdatedFilter from aiogram.filters import JOIN_TRANSITION, LEAVE_TRANSITION, ChatMemberUpdatedFilter
from aiogram.types import ChatMemberUpdated, PollAnswer
from karkas_core.modules_system.public_api import get_module, log, register_router from karkas_core.modules_system.public_api import get_module, log, register_router
@ -22,6 +21,8 @@ from .verifications_methods.simple import (
from .verifications_methods.utils import user_mention from .verifications_methods.utils import user_mention
if TYPE_CHECKING: if TYPE_CHECKING:
from aiogram.types import ChatMemberUpdated, PollAnswer
from karkas_blocks.standard.config import IConfig from karkas_blocks.standard.config import IConfig
from karkas_blocks.standard.filters import ChatIDFilter as IChatIDFilter from karkas_blocks.standard.filters import ChatIDFilter as IChatIDFilter
@ -78,7 +79,7 @@ verification_tasks = MultiKeyDict()
last_success = {} last_success = {}
async def new_member_handler(event: ChatMemberUpdated, bot: Bot): async def new_member_handler(event: "ChatMemberUpdated", bot: Bot):
# НЕ СРАБОТАЕТ, ЕСЛИ ЧЕЛОВЕК УЖЕ ОГРАНИЧЕН В ПРАВАХ (RESTRICTED) # НЕ СРАБОТАЕТ, ЕСЛИ ЧЕЛОВЕК УЖЕ ОГРАНИЧЕН В ПРАВАХ (RESTRICTED)
if event.new_chat_member.status == ChatMemberStatus.MEMBER: if event.new_chat_member.status == ChatMemberStatus.MEMBER:
task = task_manager.build_random_task(event, bot) task = task_manager.build_random_task(event, bot)
@ -87,7 +88,7 @@ async def new_member_handler(event: ChatMemberUpdated, bot: Bot):
verification_tasks.add(task, keys) verification_tasks.add(task, keys)
async def left_member_handler(event: ChatMemberUpdated, bot: Bot): async def left_member_handler(event: "ChatMemberUpdated", bot: Bot):
user_id = event.from_user.id user_id = event.from_user.id
chat_id = event.chat.id chat_id = event.chat.id
@ -144,7 +145,7 @@ async def success_end(task: BaseTask):
last_success[task.from_chat_id] = message.message_id last_success[task.from_chat_id] = message.message_id
async def handle_poll_verification(answer: PollAnswer, bot: Bot): async def handle_poll_verification(answer: "PollAnswer", bot: Bot):
key = key_from_poll(answer.poll_id) key = key_from_poll(answer.poll_id)
if not verification_tasks.exists(key): if not verification_tasks.exists(key):
return return

View File

@ -1,21 +1,24 @@
import asyncio import asyncio
from functools import wraps from functools import wraps
from typing import TYPE_CHECKING
from aiogram import Bot
from aiogram.exceptions import TelegramBadRequest from aiogram.exceptions import TelegramBadRequest
from aiogram.filters.callback_data import CallbackData from aiogram.filters.callback_data import CallbackData
from aiogram.types import ChatMemberUpdated
from karkas_core.modules_system.public_api import log from karkas_core.modules_system.public_api import log
from .utils import mute_user, unmute_user from .utils import mute_user, unmute_user
if TYPE_CHECKING:
from aiogram import Bot
from aiogram.types import ChatMemberUpdated
class BaseTask: class BaseTask:
def __init__( def __init__(
self, self,
event: ChatMemberUpdated, event: "ChatMemberUpdated",
bot: Bot, bot: "Bot",
timeout_func=None, timeout_func=None,
attempt_number=1, attempt_number=1,
max_attempts=1, max_attempts=1,

View File

@ -1,6 +1,6 @@
from string import Template from string import Template
from typing import TYPE_CHECKING
from aiogram import Bot
from aiogram.enums import ParseMode, PollType from aiogram.enums import ParseMode, PollType
from aiogram.types import ChatMemberUpdated, InlineKeyboardButton, InlineKeyboardMarkup from aiogram.types import ChatMemberUpdated, InlineKeyboardButton, InlineKeyboardMarkup
@ -8,6 +8,9 @@ from ..utils import get_plural_form, key_from_poll, key_from_user_chat
from .base import BaseTask, VerificationCallback, mute_while_task from .base import BaseTask, VerificationCallback, mute_while_task
from .utils import user_mention from .utils import user_mention
if TYPE_CHECKING:
from aiogram import Bot
class SimpleBaseTask(BaseTask): class SimpleBaseTask(BaseTask):
pass pass
@ -120,7 +123,7 @@ class SimplePollTask(SimpleVariantsBaseTask):
def __init__( def __init__(
self, self,
event: ChatMemberUpdated, event: ChatMemberUpdated,
bot: Bot, bot: "Bot",
timeout_func=None, timeout_func=None,
attempt_number=1, attempt_number=1,
max_attempts=1, max_attempts=1,

View File

@ -1,9 +1,12 @@
import time import time
from typing import TYPE_CHECKING
from aiogram import Bot
from aiogram.enums import ParseMode from aiogram.enums import ParseMode
from aiogram.types import ChatPermissions, User from aiogram.types import ChatPermissions, User
if TYPE_CHECKING:
from aiogram import Bot
def user_mention(user: User, mode=ParseMode.HTML): def user_mention(user: User, mode=ParseMode.HTML):
if mode == ParseMode.HTML: if mode == ParseMode.HTML:
@ -14,7 +17,7 @@ def user_mention(user: User, mode=ParseMode.HTML):
raise ValueError(f"Unknown parse mode {mode}") raise ValueError(f"Unknown parse mode {mode}")
async def mute_user(chat_id, user_id, until, bot: Bot): async def mute_user(chat_id, user_id, until, bot: "Bot"):
end_time = until + int(time.time()) end_time = until + int(time.time())
await bot.restrict_chat_member( await bot.restrict_chat_member(
chat_id, chat_id,
@ -40,7 +43,7 @@ async def mute_user(chat_id, user_id, until, bot: Bot):
) )
async def unmute_user(chat_id, user_id, bot: Bot): async def unmute_user(chat_id, user_id, bot: "Bot"):
await bot.restrict_chat_member( await bot.restrict_chat_member(
chat_id, chat_id,
user_id, user_id,

View File

@ -1,10 +1,14 @@
import importlib import importlib
import os import os
import traceback import traceback
from typing import TYPE_CHECKING
from aiogram import Bot, Dispatcher
from aiogram.types import Update from aiogram.types import Update
if TYPE_CHECKING:
from aiogram import Bot, Dispatcher
from fastapi import FastAPI, Request
def get_module_directory(module_name): def get_module_directory(module_name):
spec = importlib.util.find_spec(module_name) spec = importlib.util.find_spec(module_name)
@ -16,23 +20,15 @@ def get_module_directory(module_name):
return os.path.dirname(module_path) return os.path.dirname(module_path)
try: async def register_bot_webhook(app: "FastAPI", bot: "Bot", dp: "Dispatcher"):
from fastapi import FastAPI, Request async def handle_webhook(request: "Request"):
try:
update = Update.model_validate(await request.json(), context={"bot": bot})
await dp.feed_update(bot, update)
except Exception:
traceback.print_exc()
return {"ok": False}
async def register_bot_webhook(app: FastAPI, bot: Bot, dp: Dispatcher): return {"ok": True}
async def handle_webhook(request: Request):
try:
update = Update.model_validate(
await request.json(), context={"bot": bot}
)
await dp.feed_update(bot, update)
except Exception:
traceback.print_exc()
return {"ok": False}
return {"ok": True} app.post("/webhook")(handle_webhook)
app.post("/webhook")(handle_webhook)
except ImportError:
pass

View File

@ -1,9 +1,11 @@
import types
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, List, Optional, Union from typing import TYPE_CHECKING, Dict, List, Optional, Union
from dataclasses_json import dataclass_json from dataclasses_json import dataclass_json
if TYPE_CHECKING:
import types
@dataclass_json @dataclass_json
@dataclass @dataclass
@ -39,5 +41,5 @@ class AbstractLoader:
def info(self) -> ModuleInfo: def info(self) -> ModuleInfo:
raise NotImplementedError raise NotImplementedError
def load(self) -> types.ModuleType: def load(self) -> "types.ModuleType":
raise NotImplementedError raise NotImplementedError

View File

@ -1,8 +1,7 @@
import inspect import inspect
import types import types
from typing import Any, Tuple, Union from typing import TYPE_CHECKING, Any, Tuple, Union
from aiogram import BaseMiddleware, Router
from aiogram.fsm.context import FSMContext from aiogram.fsm.context import FSMContext
from aiogram.fsm.storage.base import StorageKey from aiogram.fsm.storage.base import StorageKey
@ -10,18 +9,21 @@ from aiogram.fsm.storage.base import StorageKey
from karkas_core.modules_system.loaders.base import DependencyInfo from karkas_core.modules_system.loaders.base import DependencyInfo
from karkas_core.singleton import Singleton from karkas_core.singleton import Singleton
if TYPE_CHECKING:
from aiogram import BaseMiddleware, Router
async def set_chat_menu_button(menu_button): async def set_chat_menu_button(menu_button):
app = Singleton() app = Singleton()
await app.bot.set_chat_menu_button(menu_button=menu_button) await app.bot.set_chat_menu_button(menu_button=menu_button)
def register_router(router: Router): def register_router(router: "Router"):
app = Singleton() app = Singleton()
app.storage["_routers"].append(router) app.storage["_routers"].append(router)
def register_outer_message_middleware(middleware: BaseMiddleware): def register_outer_message_middleware(middleware: "BaseMiddleware"):
app = Singleton() app = Singleton()
app.storage["_outer_message_middlewares"].append(middleware) app.storage["_outer_message_middlewares"].append(middleware)

View File

@ -1,6 +1,5 @@
import types import types
from _ast import AnnAssign from typing import TYPE_CHECKING, Any
from typing import Any
from aiogram import Bot from aiogram import Bot
from RestrictedPython import ( from RestrictedPython import (
@ -20,6 +19,9 @@ from RestrictedPython.Guards import ( # guarded_setattr,; full_write_guard,
from karkas_core.logger import log from karkas_core.logger import log
from karkas_core.modules_system.safe.zope_guards import extra_safe_builtins from karkas_core.modules_system.safe.zope_guards import extra_safe_builtins
if TYPE_CHECKING:
from _ast import AnnAssign
class RestrictedPythonPolicy(RestrictingNodeTransformer): class RestrictedPythonPolicy(RestrictingNodeTransformer):
def visit_AsyncFunctionDef(self, node): def visit_AsyncFunctionDef(self, node):
@ -50,7 +52,7 @@ class RestrictedPythonPolicy(RestrictingNodeTransformer):
return self.node_contents_visit(node) return self.node_contents_visit(node)
""" """
def visit_AnnAssign(self, node: AnnAssign) -> Any: def visit_AnnAssign(self, node: "AnnAssign") -> Any:
# missing in RestrictingNodeTransformer # missing in RestrictingNodeTransformer
# this doesn't need the logic that is in visit_Assign # this doesn't need the logic that is in visit_Assign
# because it doesn't have a "targets" attribute, # because it doesn't have a "targets" attribute,

View File

@ -1,7 +1,11 @@
from aiogram import Bot, Dispatcher from typing import TYPE_CHECKING
from aiogram.fsm.storage.memory import MemoryStorage from aiogram.fsm.storage.memory import MemoryStorage
from karkas_core.modules_system import ModulesManager if TYPE_CHECKING:
from aiogram import Bot, Dispatcher
from karkas_core.modules_system import ModulesManager
class SingletonMeta(type): class SingletonMeta(type):
@ -15,9 +19,9 @@ class SingletonMeta(type):
class Singleton(metaclass=SingletonMeta): class Singleton(metaclass=SingletonMeta):
bot: Bot bot: "Bot"
dp: Dispatcher = None dp: "Dispatcher" = None
modules_manager: ModulesManager = None modules_manager: "ModulesManager" = None
storage = { storage = {
"_fsm_storage": MemoryStorage(), "_fsm_storage": MemoryStorage(),
"_routers": [], "_routers": [],