Giter VIP home page Giter VIP logo

telegrinder's People

Contributors

dependabot[bot] avatar feeeek avatar kesha1225 avatar luwqz1 avatar prostomarkeloff avatar sicquze avatar timoniq avatar vffuunnyy avatar vodogamer avatar yallxe avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

telegrinder's Issues

Token for `API` can't be just a string.

It's cool that using the Token class we can load tokens from environment variables, but if the environment variables are loaded elsewhere, then passing a simple string to the API constructor without mypy errors is impossible.

fix: warning 'coroutine was never awaited'

✏️ Description ✏️

After the program exits warning messages appear: coroutine 'coro_name' was never awaited. The reason for this bug is the function 'to_coroutine_task'.

🪄 Code example 🪄

from telegrinder import LoopWrapper

async def create_tables() -> None:
    ...

lw = LoopWrapper()
lw.lifespan.on_startup(create_tables())
lw.run_event_loop()

📝 Logs 📝

RuntimeWarning: coroutine 'create_tables' was never awaited

.

feature: implement Option monad

Option monad is either variant Some contains a some value, variant Nothing which contains nothing.

Option = Some[Value] | Nothing

For example:

from telegrinder.option import Option, Nothing
from telegrinder.model import Model

class User(Model):
    id: int
    is_bot: bool
    first_name: str
    last_name: Option[str] = Nothing

def repr_user(user: User) -> str:
    # returns first_name + last_name
    return user.first_name + user.last_name.map(lambda value: " " + value).unwrap_or("")

class MessageView(ABCView[MessageCute]):
    async def check(self, event: Update) -> bool:
        return bool(event.message)

    async def process(self, event: Update, api: ABCAPI):
         # event.message.unwrap() is guaranteed to return variant Some after check method, which is good for logic and linter
         msg = MessageCute(**event.message.unwrap().to_dict(), api=api)
         return await process_inner(msg, event, self.middlewares, self.handlers)

get rid of ctx

The problem of shared mutable state, which is not good.
For example, a user created his own rule:

from telegrinder import API, Telegrinder, Token, Message
from telegrinder.rules import MessageRule, Markup

api = API(Token.from_env())
bot = Telegrinder(api)


class WithReply(MessageRule):
    async def check(self, message: Message, ctx: dict) -> bool:
        ctx.clear()  # accidental context deletion
        return message.reply_to_message is not None

        
@bot.on.message(Markup(["/give <item> <count:int>", "/give <item>"]), WithReply())
async def give_item(message: Message, item: str, count: int = 1): 
    # an exception will be raised because the ctx was
    # empty and there is nothing pass to the handler
    ...

feat: implement AST parser for CuteType shortcuts

🚀 Feature Request 🚀

✏️ Feature description

Compatible with the latest version of Telegram Bot API.

🪄 Provide a minimal example 🪄

No response

✨ Teachability, Documentation, Adoption ✨

No response

feature: KeyboardEnum, InlineKeyboardEnum for the aesthetic creation of keyboards and for a state like RuleEnum

For example:

from telegrinder import InlineKeyboardEnum, KeyboardEnum, InlineButton, Button


class FoodKeyboard(InlineKeyboardEnum):
    PRETZEL = InlineButton("pretzel", callback_data="food/pretzel")
    PIZZA = InlineButton("pizza", callback_data="food/pizza")
    PASTA = InlineButton("pasta", callback_data="food/pasta")
    RICE = InlineButton("rice", callback_data="food/rice")
    DUMPLINGS = InlineButton("dumplings", callback_data="food/dumplings")


class MenuKeyboard(KeyboardEnum):
    START = Button("start")
    EAT = Button("eat")
    SEND_CONTACT = Button("send_contact", request_contact=True)


food_kb_markup = FoodKeyboard.get_markup()
menu_kb_markup = MenuKeyboard.get_markup()


@bot.on.callback_query(FoodKeyboard.PRETZEL)
...


@bot.on.message(MenuKeyboard.EAT)
...

add RuleEnum

example:

class MySet(RuleEnum):
    CANCEL = Text("/cancel")
    USERNAME = Mention() | Markup("t.me/<username>")

@dp.message(MySet())

Fails on any inline query

⚪️ Checklist ⚪️

  • ❕ I'm sure this is a bug in the framework
  • 🔎 I have searched for similar bugs in issues/PRs 🔍
  • 🟢 I have the latest version of the framework installed 🟢

✏️ Description ✏️

Fails on any inline query

🪄 Code example 🪄

Any inline query code

📝 Logs 📝

msgspec.ValidationError: Expected object of type `InlineQuery`, got `dict`. - at `$[0].inline_query`

2024-08-21 11:24:00,488 - [ERROR] - root - (custom.py).listen(62) - Expected object of type `InlineQuery`, got `dict`. - at `$[0].inline_query`

Traceback (most recent call last):

  File "/home/prostomarkeloff/projects/umi_game_bot/.venv/lib/python3.12/site-packages/telegrinder/msgspec_utils.py", line 210, in dec_hook

    return self.dec_hooks[origin_type](tp, obj)

           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/home/prostomarkeloff/projects/umi_game_bot/.venv/lib/python3.12/site-packages/telegrinder/msgspec_utils.py", line 81, in option_dec_hook

    return fntypes.option.Some(msgspec_convert(obj, value_type).unwrap())

                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/home/prostomarkeloff/projects/umi_game_bot/.venv/lib/python3.12/site-packages/fntypes/result/result.py", line 114, in unwrap

    raise UnwrapError(self.error)

fntypes.error.UnwrapError: Expected object of type `InlineQuery`, got `dict`.



The above exception was the direct cause of the following exception:



Traceback (most recent call last):

  File "/home/prostomarkeloff/projects/umi_game_bot/bot/custom.py", line 33, in listen

    updates_list: list[Update] = decoder.decode(updates, type=list[Update])

                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/home/prostomarkeloff/projects/umi_game_bot/.venv/lib/python3.12/site-packages/telegrinder/msgspec_utils.py", line 248, in decode

    return msgspec.json.decode(

           ^^^^^^^^^^^^^^^^^^^^

msgspec.ValidationError: Expected object of type `InlineQuery`, got `dict`. - at `$[0].inline_query`

2024-08-21 11:24:00,627 - [ERROR] - root - (custom.py).listen(62) - Expected object of type `InlineQuery`, got `dict`. - at `$[0].inline_query`

Traceback (most recent call last):

  File "/home/prostomarkeloff/projects/umi_game_bot/.venv/lib/python3.12/site-packages/telegrinder/msgspec_utils.py", line 210, in dec_hook

    return self.dec_hooks[origin_type](tp, obj)

           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/home/prostomarkeloff/projects/umi_game_bot/.venv/lib/python3.12/site-packages/telegrinder/msgspec_utils.py", line 81, in option_dec_hook

    return fntypes.option.Some(msgspec_convert(obj, value_type).unwrap())

                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/home/prostomarkeloff/projects/umi_game_bot/.venv/lib/python3.12/site-packages/fntypes/result/result.py", line 114, in unwrap

    raise UnwrapError(self.error)

fntypes.error.UnwrapError: Expected object of type `InlineQuery`, got `dict`.



The above exception was the direct cause of the following exception:



Traceback (most recent call last):

  File "/home/prostomarkeloff/projects/umi_game_bot/bot/custom.py", line 33, in listen

    updates_list: list[Update] = decoder.decode(updates, type=list[Update])

                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

  File "/home/prostomarkeloff/projects/umi_game_bot/.venv/lib/python3.12/site-packages/telegrinder/msgspec_utils.py", line 248, in decode

    return msgspec.json.decode(

           ^^^^^^^^^^^^^^^^^^^^

msgspec.ValidationError: Expected object of type `InlineQuery`, got `dict`. - at `$[0].inline_query`

feat: implement dataclass to define views in Dispatch

Now they are defined like this:

class Dispatch(ABCDispatch):
    # ...
    message = MessageView()
    views = {"message"}

Implementation using dataclass:

import dataclasses

import typing_extensions as typing

from telegrinder.bot.dispatch.view import ABCView, MessageView

MessageViewT = typing.TypeVar("MessageViewT", bound=ABCView, default=MessageView)


@dataclasses.dataclass(kw_only=True)
class Views(typing.Generic[MessageViewT]):
    message: MessageViewT = dataclasses.field(default_factory=lambda: MessageView())


class Dispatch(ABCDispatch, Views[MessageViewT]):
    ...


dp = Dispatch()
dp.message # default instance 'MessageView'
dp = Dispatch(message=CustomMessageView())
dp.message # custom instance 'CustomMessageView'

feature: dependencies

i had a problem building simple bot consumer with some extra data passed from webhook router

bot = Telegrinder(...)
await bot.dispatch.feed(update, api)

i need to pass extra context or create a dependency system

feed(update, api, context={"shop_id": shop_id})
# or
bot.dependency["shop_id"] = Value(shop_id)  
# ^ value wrapper is needed to separate values from async getters

Update: use compositions

fix: LoopWrapper

It is necessary to fix the problem with processing the asyncio event loop and launching tasks.

bot api types

telegrinder uses schema at https://github.com/ark0f/tg-bot-api to generate types. its super old and not getting updates :c
probably new schema must be applied and *unfortunately* most likely type generator will have to be revised for the found new schema specifications

`get_event_loop` calls is deprecated in python3.10

In python3.10 the get_event_loop function from asyncio is deprecated and should be replaced by get_running_loop, but it should only be called inside asynchronous functions, so I recommend removing all .loop attributes and using get_running_loop instead, or new_event_loop in case of run_forever function, because it makes no sense to cache the loop object - it can lead to a lot of problems in future and doesn't improve performance.

enhance issue templates

  • add html-like comments with info specified for issue author
<!-- Please write boba, aboba ... -->
  • feature request templates should not ask to describe the problem as they may not be intended to fix anything. more likely they should ask to describe why the proposed feature is needed and a proposed code sample to bear a reference for further implementation

^^

feature: cached media

media which is uploaded once and the sending data is saved for next uploads

kitten = CachedMedia(photo="assets/kitten.jpg")

async def runtime(...):
    await api.send_photo(message.chat.id, caption="you lol", photo=await kitten.get())

`IsPrivate` rule (and some other) do not support `CallbackQueryCute` even when the latter must be supported

⚪️ Checklist ⚪️

  • ❕ I'm sure this is a bug in the framework
  • 🟢 I have the latest version of the framework installed 🟢

✏️ Description ✏️

IsPrivate rule (and some other) do not support CallbackQueryCute even when the latter must be supported.

IsPrivate rule checks if chat_type is private. Message does support it and IsPrivate inherits from MessageRule that doesn't let anything but Message go there. There should be something like CallbackQueryOrMessageRule, adapter of which supports both Message and CallbackQuery

typechecking 'strict' mode

to improve typing guarantees, it is suggested to set the 'strict' mode for pyright. it is expected that there are over a thousand type errors. it will take some time to fix them

[tool.pyright]
typeCheckingMode = "strict"

implement immut contexts for rules

maybe middlewares also but they prob need adaptor refactoring

implementation in progress

syntax proposed:

class Rational(MessageRule, composite=True, requires=[HasText()]):
    nom: int
    denom: int

    async def check(self, message: Message) -> Result["Rational", str]:
        rational = parse_rational(message.text, raise_error=False)
        if rational is None:
            return Error("Text is not a rational number")
        return Ok(
            self.context(rational.nominator, rational.denominator)
        )

perhaps other rule can be treated as adaptor to reach event-independent handling for primitive datatypes like text values

class Text(MessageRule):
    async def check(self, message: Message) -> Result[str, str]:
        if not message.text:
            return Error("Message has no text")
        return Ok(message.text)

class Rational(ABCRule, composite=True, adaptor=Text):
    async def check(self, text: str) -> Result["Rational", str]:
        ...

for release: v5

feat: implement CallbackDataMap rule

from telegrinder.rules import CallbackDataMap

@bot.on.callback_query(CallbackDataMap({"count": int}))
async def cb_handler(cb: CallbackQuery, count: int):
    await cb.answer(str(count))
 
bot.run_forever()

exception in task leads to task cancellation

⚪️ Checklist ⚪️

  • ❕ I'm sure this is a bug in the framework
  • 🔎 I have searched for similar bugs in issues/PRs 🔍
  • 🟢 I have the latest version of the framework installed 🟢

✏️ Description ✏️

The bot task is closed if there are issues with the internet connection on the client's side.

🪄 Code example 🪄

from envparse import Env
from telegrinder import Telegrinder, API, Token
from telegrinder import logger
from telegrinder.client import AiohttpClient
import asyncio
from app.handlers import dps

api = API(Token.from_env())
bot = Telegrinder(api)
env = Env()
logger.set_level(env.str("LOGGING"))
SUPPLIERS_URL_API = env.str("SUPPLIERS_URL_API")
DISCOUNT_URL_API = env.str("DISCOUNT_URL_API")


@bot.loop_wrapper.interval(minutes=10)
async def update():
    client = AiohttpClient()
    headers = {}
    response = await client.request_json(
        SUPPLIERS_URL_API + "/api/v3/orders/new", "GET", headers=headers
    )

    # other things


if __name__ == "__main__":
    for dp in dps:
        bot.dispatch.load(dp)
    bot.run_forever()

📝 Logs 📝

Traceback (most recent call last):
File "main.py", line 1, in <module>
...
telegrinder | ERROR   | 2024-07-15 00:50:52 | telegrinder.bot.polling.polling:listen:119 > Client connection failed, attempted to reconnect...
telegrinder | ERROR   | 2024-07-15 14:18:57 | telegrinder.bot.polling.polling:listen:119 > Client connection failed, attempted to reconnect...
telegrinder | ERROR   | 2024-07-15 21:03:56 | telegrinder.tools.loop_wrapper.loop_wrapper:run_event_loop:117 > Server disconnected
  File "/project/venv/lib/python3.12/site-packages/telegrinder/tools/loop_wrapper/loop_wrapper.py", line 115, in run_event_loop
    task_result.result()
  File "/project/venv/lib/python3.12/site-packages/telegrinder/tools/loop_wrapper/loop_wrapper.py", line 51, in __call__
    await self.handler(*args, **kwargs)
  File "/project/app/__main__.py", line 27, in update
    response = await client.request_json(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/project/venv/lib/python3.12/site-packages/telegrinder/client/aiohttp.py", line 66, in request_json
    response = await self.request_raw(url, method, data, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/project/venv/lib/python3.12/site-packages/telegrinder/client/aiohttp.py", line 49, in request_raw
    async with self.session.request(
  File "/project/venv/lib/python3.12/site-packages/aiohttp/client.py", line 1197, in __aenter__
    self._resp = await self._coro
                 ^^^^^^^^^^^^^^^^
  File "/project/venv/lib/python3.12/site-packages/aiohttp/client.py", line 608, in _request
    await resp.start(conn)
  File "/project/venv/lib/python3.12/site-packages/aiohttp/client_reqrep.py", line 976, in start
    message, payload = await protocol.read()  # type: ignore[union-attr]
                       ^^^^^^^^^^^^^^^^^^^^^
  File "/project/venv/lib/python3.12/site-packages/aiohttp/streams.py", line 640, in read
    await self._waiter
aiohttp.client_exceptions.ServerDisconnectedError: Server disconnected
Task was destroyed but it is pending!
task: <Task pending name='Task-35647' coro=<Dispatch.feed() done, defined at /project/venv/lib/python3.12/site-packages/telegrinder/bot/dispatch/dispatch.py:136> wait_for=<Future pending cb=[Task.task_wakeup()]>>
telegrinder | ERROR | 2024-07-16 14:37:55 | telegrinder.bot.polling.polling:listen:119 > Client connection failed, attempted to reconnect...

Default parse_mode value

🚀 Feature Request 🚀

✏️ New feature description ✏️

Could you add a default value for the parse_mode parameter? This will reduce lines of code for developers.

🪄 Provide a minimal example 🪄

api = API(token=token, prase_mode=ParseMode.HTML)

async def handler(m):
    await m.answer(
        HTMLFormatter(bold(italic("bold italic text!"))),
    )

# instead of

async def handler(m):
  await m.answer(
      HTMLFormatter(bold(italic("bold italic text!"))),
      parse_mode=HTMLFormatter.PARSE_MODE,
  )

WaiterMachine base lifetime of all wait tasks

🚀 Feature Request 🚀

✏️ New feature description ✏️

Changeable base lifetime of all waiting tasks, instead of limiting their number (but they can be combined) or specifying the lifetime of each specific task.

🪄 Provide a minimal example 🪄

class WaiterMachine:
    def __init__(self, *, max_storage_size: int = 1000, base_task_lifetime: float | timedelta | None = None) -> None:
        self.max_storage_size = max_storage_size
        self.base_task_lifetime = base_task_lifetime
        self.storage: Storage = {}

✨ Teachability, Documentation, Adoption ✨

feat: use limited size dict to store waiters

In order to avoid storing dead waiter states it is required to implement limited size dict to store short waiters that will delete old records on exceeding maximum size limit

feat: compose polymorph implementations

a node must allow adding multiple compose implementations

for different sets of child nodes. (this may have a lot of test cases to handle)

class Text(ScalarNode, str):
    @composer
    async def compose_message(cls, message: MessageNode) -> typing.Self:
        ...
        return cls(message.text.unwrap())
    
    @composer
    async def compose_caption(cls, attachment: Attachment) -> typing.Self:
        # works with attachments with caption
        ...
        return cls(attachment.caption.unwrap())

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.