diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fbaa2cf..59b7168 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,7 +44,7 @@ jobs: cache-dependency-path: pyproject.toml - name: Install dependencies - run: python -m pip install --upgrade .[dev,test,redis,mongo,sqlalchemy] build pip + run: python -m pip install --upgrade .[dev,test,redis,mongo,sqlalchemy] build pip aiogram - name: Lint code run: make lint diff --git a/.gitignore b/.gitignore index cb4d9a2..af41e76 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,5 @@ .cache/ .codegen/ dist/ -reports/ __pycache__/ .python-version -.coverage diff --git a/README.md b/README.md index 08ac946..7ef91bf 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,13 @@ * #### From PyPI ```commandline -pip install -U aiogram-broadcaster +pip install --upgrade aiogram-broadcaster ``` * #### From GitHub (_Development build_) ```commandline -pip install https://github.com/loRes228/aiogram_broadcaster/archive/refs/heads/dev.zip +pip install https://github.com/loRes228/aiogram_broadcaster/archive/refs/heads/dev.zip --fore-reinstall ``` ## Creating a mailer and running broadcasting @@ -410,9 +410,9 @@ async def mailer_completed(my_data: 1) -> None: * #### [BaseMailerStorage](https://github.com/loRes228/aiogram_broadcaster/blob/main/aiogram_broadcaster/storages/base.py) Abstract class of storage. * #### [FileMailerStorage](https://github.com/loRes228/aiogram_broadcaster/blob/main/aiogram_broadcaster/storages/file.py) Saves the mailers to a file. -* #### [MongoDBMailerStorage](https://github.com/loRes228/aiogram_broadcaster/blob/main/aiogram_broadcaster/storages/mongodb.py) Saves the mailers to a MongoDB. -* #### [RedisMailerStorage](https://github.com/loRes228/aiogram_broadcaster/blob/main/aiogram_broadcaster/storages/redis.py) Saves the mailers to a Redis. -* #### [SQLAlchemyMailerStorage](https://github.com/loRes228/aiogram_broadcaster/blob/main/aiogram_broadcaster/storages/sqlalchemy.py) Saves the mailers using SQLAlchemy. +* #### [MongoDBMailerStorage](https://github.com/loRes228/aiogram_broadcaster/blob/main/aiogram_broadcaster/storages/mongodb.py) Saves the mailers to a MongoDB. (Extra: mongo) +* #### [RedisMailerStorage](https://github.com/loRes228/aiogram_broadcaster/blob/main/aiogram_broadcaster/storages/redis.py) Saves the mailers to a Redis. (Extra: redis) +* #### [SQLAlchemyMailerStorage](https://github.com/loRes228/aiogram_broadcaster/blob/main/aiogram_broadcaster/storages/sqlalchemy.py) Saves the mailers using SQLAlchemy. (Extra: sqlalchemy) #### Usage: diff --git a/aiogram_broadcaster/__meta__.py b/aiogram_broadcaster/__meta__.py index a34b2f6..3d18726 100644 --- a/aiogram_broadcaster/__meta__.py +++ b/aiogram_broadcaster/__meta__.py @@ -1 +1 @@ -__version__ = "0.4.7" +__version__ = "0.5.0" diff --git a/aiogram_broadcaster/broadcaster.py b/aiogram_broadcaster/broadcaster.py index 218bb19..97995c7 100644 --- a/aiogram_broadcaster/broadcaster.py +++ b/aiogram_broadcaster/broadcaster.py @@ -54,7 +54,7 @@ def __init__( def as_group(self) -> MailerGroup: if not self._mailers: raise RuntimeError("No mailers for grouping.") - return MailerGroup(*self._mailers.values()) + return MailerGroup(*self) async def create_mailers( self, @@ -189,7 +189,7 @@ async def restore_mailers(self) -> None: return bots = {bot.id: bot for bot in self.bots} for mailer_id in mailer_ids: - if mailer_id in self._mailers: + if mailer_id in self: continue try: record = await self.storage.get(mailer_id=mailer_id) diff --git a/aiogram_broadcaster/contents/animation.py b/aiogram_broadcaster/contents/animation.py index 40cb590..03999e7 100644 --- a/aiogram_broadcaster/contents/animation.py +++ b/aiogram_broadcaster/contents/animation.py @@ -24,9 +24,11 @@ class AnimationContent(BaseContent): caption: Optional[str] = None parse_mode: Optional[Union[str, Default]] = Default("parse_mode") caption_entities: Optional[List[MessageEntity]] = None + show_caption_above_media: Optional[Union[bool, Default]] = Default("show_caption_above_media") has_spoiler: Optional[bool] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -48,9 +50,11 @@ async def __call__(self, chat_id: int) -> SendAnimation: caption=self.caption, parse_mode=self.parse_mode, caption_entities=self.caption_entities, + show_caption_above_media=self.show_caption_above_media, has_spoiler=self.has_spoiler, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -69,9 +73,11 @@ def __init__( caption: Optional[str] = ..., parse_mode: Optional[Union[str, Default]] = ..., caption_entities: Optional[List[MessageEntity]] = ..., + show_caption_above_media: Optional[Union[bool, Default]] = ..., has_spoiler: Optional[bool] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/contents/audio.py b/aiogram_broadcaster/contents/audio.py index cebef98..4093dfa 100644 --- a/aiogram_broadcaster/contents/audio.py +++ b/aiogram_broadcaster/contents/audio.py @@ -26,6 +26,7 @@ class AudioContent(BaseContent): thumbnail: Optional[InputFile] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -49,6 +50,7 @@ async def __call__(self, chat_id: int) -> SendAudio: thumbnail=self.thumbnail, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -69,6 +71,7 @@ def __init__( thumbnail: Optional[InputFile] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/contents/contact.py b/aiogram_broadcaster/contents/contact.py index 06384e2..e21108c 100644 --- a/aiogram_broadcaster/contents/contact.py +++ b/aiogram_broadcaster/contents/contact.py @@ -20,6 +20,7 @@ class ContactContent(BaseContent): vcard: Optional[str] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -39,6 +40,7 @@ async def __call__(self, chat_id: int) -> SendContact: vcard=self.vcard, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -55,6 +57,7 @@ def __init__( vcard: Optional[str] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/contents/dice.py b/aiogram_broadcaster/contents/dice.py index 67ddf50..26916cb 100644 --- a/aiogram_broadcaster/contents/dice.py +++ b/aiogram_broadcaster/contents/dice.py @@ -17,6 +17,7 @@ class DiceContent(BaseContent): emoji: Optional[str] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -33,6 +34,7 @@ async def __call__(self, chat_id: int) -> SendDice: emoji=self.emoji, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -46,6 +48,7 @@ def __init__( emoji: Optional[str] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/contents/document.py b/aiogram_broadcaster/contents/document.py index 4e9fbb0..0c0443b 100644 --- a/aiogram_broadcaster/contents/document.py +++ b/aiogram_broadcaster/contents/document.py @@ -24,6 +24,7 @@ class DocumentContent(BaseContent): disable_content_type_detection: Optional[bool] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -45,6 +46,7 @@ async def __call__(self, chat_id: int) -> SendDocument: disable_content_type_detection=self.disable_content_type_detection, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -63,6 +65,7 @@ def __init__( disable_content_type_detection: Optional[bool] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/contents/from_chat.py b/aiogram_broadcaster/contents/from_chat.py index 4707d14..91d8f3e 100644 --- a/aiogram_broadcaster/contents/from_chat.py +++ b/aiogram_broadcaster/contents/from_chat.py @@ -19,6 +19,7 @@ class FromChatCopyContent(BaseContent): caption: Optional[str] = None parse_mode: Optional[Union[str, Default]] = Default("parse_mode") caption_entities: Optional[List[MessageEntity]] = None + show_caption_above_media: Optional[Union[bool, Default]] = Default("show_caption_above_media") disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") reply_markup: Optional[ @@ -38,6 +39,7 @@ async def __call__(self, chat_id: int) -> CopyMessage: caption=self.caption, parse_mode=self.parse_mode, caption_entities=self.caption_entities, + show_caption_above_media=self.show_caption_above_media, disable_notification=self.disable_notification, protect_content=self.protect_content, reply_markup=self.reply_markup, @@ -54,6 +56,7 @@ def __init__( caption: Optional[str] = ..., parse_mode: Optional[Union[str, Default]] = ..., caption_entities: Optional[List[MessageEntity]] = ..., + show_caption_above_media: Optional[Union[bool, Default]] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., reply_markup: Optional[ diff --git a/aiogram_broadcaster/contents/game.py b/aiogram_broadcaster/contents/game.py index 14d9472..27afe2b 100644 --- a/aiogram_broadcaster/contents/game.py +++ b/aiogram_broadcaster/contents/game.py @@ -12,6 +12,7 @@ class GameContent(BaseContent): business_connection_id: Optional[str] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[InlineKeyboardMarkup] = None async def __call__(self, chat_id: int) -> SendGame: @@ -21,6 +22,7 @@ async def __call__(self, chat_id: int) -> SendGame: business_connection_id=self.business_connection_id, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -34,6 +36,7 @@ def __init__( business_connection_id: Optional[str] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[InlineKeyboardMarkup] = ..., **kwargs: Any, ) -> None: ... diff --git a/aiogram_broadcaster/contents/invoice.py b/aiogram_broadcaster/contents/invoice.py index fb517bd..03c4854 100644 --- a/aiogram_broadcaster/contents/invoice.py +++ b/aiogram_broadcaster/contents/invoice.py @@ -11,9 +11,9 @@ class InvoiceContent(BaseContent): title: str description: str payload: str - provider_token: str currency: str prices: List[LabeledPrice] + provider_token: Optional[str] = None max_tip_amount: Optional[int] = None suggested_tip_amounts: Optional[List[int]] = None start_parameter: Optional[str] = None @@ -31,6 +31,7 @@ class InvoiceContent(BaseContent): is_flexible: Optional[bool] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[InlineKeyboardMarkup] = None async def __call__(self, chat_id: int) -> SendInvoice: @@ -39,9 +40,9 @@ async def __call__(self, chat_id: int) -> SendInvoice: title=self.title, description=self.description, payload=self.payload, - provider_token=self.provider_token, currency=self.currency, prices=self.prices, + provider_token=self.provider_token, max_tip_amount=self.max_tip_amount, suggested_tip_amounts=self.suggested_tip_amounts, start_parameter=self.start_parameter, @@ -59,6 +60,7 @@ async def __call__(self, chat_id: int) -> SendInvoice: is_flexible=self.is_flexible, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -71,9 +73,9 @@ def __init__( title: str, description: str, payload: str, - provider_token: str, currency: str, prices: List[LabeledPrice], + provider_token: Optional[str] = ..., max_tip_amount: Optional[int] = ..., suggested_tip_amounts: Optional[List[int]] = ..., start_parameter: Optional[str] = ..., @@ -91,6 +93,7 @@ def __init__( is_flexible: Optional[bool] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[InlineKeyboardMarkup] = ..., **kwargs: Any, ) -> None: ... diff --git a/aiogram_broadcaster/contents/key_based.py b/aiogram_broadcaster/contents/key_based.py index 885f225..46f427a 100644 --- a/aiogram_broadcaster/contents/key_based.py +++ b/aiogram_broadcaster/contents/key_based.py @@ -1,14 +1,12 @@ from typing import TYPE_CHECKING, Any, Dict, Optional from aiogram.methods import TelegramMethod -from pydantic import ConfigDict, SerializeAsAny +from pydantic import SerializeAsAny from .base import BaseContent class KeyBasedContent(BaseContent, register=False): - model_config = ConfigDict(extra="allow") - default: Optional[SerializeAsAny[BaseContent]] = None __pydantic_extra__: Dict[str, SerializeAsAny[BaseContent]] diff --git a/aiogram_broadcaster/contents/location.py b/aiogram_broadcaster/contents/location.py index ddf01b5..3a24347 100644 --- a/aiogram_broadcaster/contents/location.py +++ b/aiogram_broadcaster/contents/location.py @@ -22,6 +22,7 @@ class LocationContent(BaseContent): proximity_alert_radius: Optional[int] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -43,6 +44,7 @@ async def __call__(self, chat_id: int) -> SendLocation: proximity_alert_radius=self.proximity_alert_radius, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -61,6 +63,7 @@ def __init__( proximity_alert_radius: Optional[int] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/contents/media_group.py b/aiogram_broadcaster/contents/media_group.py index 7af0bb2..d7f7c09 100644 --- a/aiogram_broadcaster/contents/media_group.py +++ b/aiogram_broadcaster/contents/media_group.py @@ -19,6 +19,7 @@ class MediaGroupContent(BaseContent): business_connection_id: Optional[str] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None async def __call__(self, chat_id: int) -> SendMediaGroup: return SendMediaGroup( @@ -27,6 +28,7 @@ async def __call__(self, chat_id: int) -> SendMediaGroup: business_connection_id=self.business_connection_id, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, **(self.model_extra or {}), ) @@ -46,5 +48,6 @@ def __init__( business_connection_id: Optional[str] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., **kwargs: Any, ) -> None: ... diff --git a/aiogram_broadcaster/contents/message.py b/aiogram_broadcaster/contents/message.py index a6102a9..1cf4bbe 100644 --- a/aiogram_broadcaster/contents/message.py +++ b/aiogram_broadcaster/contents/message.py @@ -36,6 +36,7 @@ class MessageCopyContent(BaseContent): caption: Optional[str] = None parse_mode: Optional[Union[str, Default]] = Default("parse_mode") caption_entities: Optional[List[MessageEntity]] = None + show_caption_above_media: Optional[Union[bool, Default]] = Default("show_caption_above_media") disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") reply_markup: Optional[ @@ -53,6 +54,7 @@ async def __call__(self, chat_id: int) -> CopyMessage: caption=self.caption, parse_mode=self.parse_mode, caption_entities=self.caption_entities, + show_caption_above_media=self.show_caption_above_media, disable_notification=self.disable_notification, protect_content=self.protect_content, reply_markup=self.reply_markup, @@ -68,6 +70,7 @@ def __init__( caption: Optional[str] = ..., parse_mode: Optional[Union[str, Default]] = ..., caption_entities: Optional[List[MessageEntity]] = ..., + show_caption_above_media: Optional[Union[bool, Default]] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., reply_markup: Optional[ @@ -113,6 +116,7 @@ class MessageSendContent(BaseContent): reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = None business_connection_id: Optional[str] = None parse_mode: Optional[str] = None + message_effect_id: Optional[str] = None async def __call__( self, @@ -140,6 +144,7 @@ async def __call__( reply_markup=self.reply_markup, business_connection_id=self.business_connection_id, parse_mode=self.parse_mode, + message_effect_id=self.message_effect_id, ) if TYPE_CHECKING: @@ -152,5 +157,6 @@ def __init__( reply_markup: Optional[Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]] = ..., business_connection_id: Optional[str] = ..., parse_mode: Optional[str] = ..., + message_effect_id: Optional[str] = ..., **kwargs: Any, ) -> None: ... diff --git a/aiogram_broadcaster/contents/photo.py b/aiogram_broadcaster/contents/photo.py index 86626dc..215ca17 100644 --- a/aiogram_broadcaster/contents/photo.py +++ b/aiogram_broadcaster/contents/photo.py @@ -20,9 +20,11 @@ class PhotoContent(BaseContent): caption: Optional[str] = None parse_mode: Optional[Union[str, Default]] = Default("parse_mode") caption_entities: Optional[List[MessageEntity]] = None + show_caption_above_media: Optional[Union[bool, Default]] = Default("show_caption_above_media") has_spoiler: Optional[bool] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -40,9 +42,11 @@ async def __call__(self, chat_id: int) -> SendPhoto: caption=self.caption, parse_mode=self.parse_mode, caption_entities=self.caption_entities, + show_caption_above_media=self.show_caption_above_media, has_spoiler=self.has_spoiler, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -57,9 +61,11 @@ def __init__( caption: Optional[str] = ..., parse_mode: Optional[Union[str, Default]] = ..., caption_entities: Optional[List[MessageEntity]] = ..., + show_caption_above_media: Optional[Union[bool, Default]] = ..., has_spoiler: Optional[bool] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/contents/poll.py b/aiogram_broadcaster/contents/poll.py index 6b31b73..e5d5c71 100644 --- a/aiogram_broadcaster/contents/poll.py +++ b/aiogram_broadcaster/contents/poll.py @@ -33,6 +33,7 @@ class PollContent(BaseContent): is_closed: Optional[bool] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -62,6 +63,7 @@ async def __call__(self, chat_id: int) -> SendPoll: is_closed=self.is_closed, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -88,6 +90,7 @@ def __init__( is_closed: Optional[bool] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/contents/sticker.py b/aiogram_broadcaster/contents/sticker.py index d9af15f..a5335f5 100644 --- a/aiogram_broadcaster/contents/sticker.py +++ b/aiogram_broadcaster/contents/sticker.py @@ -19,6 +19,7 @@ class StickerContent(BaseContent): emoji: Optional[str] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -36,6 +37,7 @@ async def __call__(self, chat_id: int) -> SendSticker: emoji=self.emoji, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -50,6 +52,7 @@ def __init__( emoji: Optional[str] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/contents/text.py b/aiogram_broadcaster/contents/text.py index 8a8774e..5073a02 100644 --- a/aiogram_broadcaster/contents/text.py +++ b/aiogram_broadcaster/contents/text.py @@ -22,6 +22,7 @@ class TextContent(BaseContent): link_preview_options: Optional[Union[LinkPreviewOptions, Default]] = Default("link_preview") disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -41,6 +42,7 @@ async def __call__(self, chat_id: int) -> SendMessage: link_preview_options=self.link_preview_options, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -57,6 +59,7 @@ def __init__( link_preview_options: Optional[Union[LinkPreviewOptions, Default]] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/contents/venue.py b/aiogram_broadcaster/contents/venue.py index eee1029..1b60665 100644 --- a/aiogram_broadcaster/contents/venue.py +++ b/aiogram_broadcaster/contents/venue.py @@ -24,6 +24,7 @@ class VenueContent(BaseContent): google_place_type: Optional[str] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -47,6 +48,7 @@ async def __call__(self, chat_id: int) -> SendVenue: google_place_type=self.google_place_type, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -67,6 +69,7 @@ def __init__( google_place_type: Optional[str] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/contents/video.py b/aiogram_broadcaster/contents/video.py index 0b350f3..7762875 100644 --- a/aiogram_broadcaster/contents/video.py +++ b/aiogram_broadcaster/contents/video.py @@ -24,10 +24,12 @@ class VideoContent(BaseContent): caption: Optional[str] = None parse_mode: Optional[Union[str, Default]] = Default("parse_mode") caption_entities: Optional[List[MessageEntity]] = None + show_caption_above_media: Optional[Union[bool, Default]] = Default("show_caption_above_media") has_spoiler: Optional[bool] = None supports_streaming: Optional[bool] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -49,10 +51,12 @@ async def __call__(self, chat_id: int) -> SendVideo: caption=self.caption, parse_mode=self.parse_mode, caption_entities=self.caption_entities, + show_caption_above_media=self.show_caption_above_media, has_spoiler=self.has_spoiler, supports_streaming=self.supports_streaming, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -71,10 +75,12 @@ def __init__( caption: Optional[str] = ..., parse_mode: Optional[Union[str, Default]] = ..., caption_entities: Optional[List[MessageEntity]] = ..., + show_caption_above_media: Optional[Union[bool, Default]] = ..., has_spoiler: Optional[bool] = ..., supports_streaming: Optional[bool] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/contents/video_note.py b/aiogram_broadcaster/contents/video_note.py index 9b70bc5..a1a37c1 100644 --- a/aiogram_broadcaster/contents/video_note.py +++ b/aiogram_broadcaster/contents/video_note.py @@ -21,6 +21,7 @@ class VideoNoteContent(BaseContent): thumbnail: Optional[InputFile] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -40,6 +41,7 @@ async def __call__(self, chat_id: int) -> SendVideoNote: thumbnail=self.thumbnail, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -56,6 +58,7 @@ def __init__( thumbnail: Optional[InputFile] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/contents/voice.py b/aiogram_broadcaster/contents/voice.py index a77a7ff..9fe9e19 100644 --- a/aiogram_broadcaster/contents/voice.py +++ b/aiogram_broadcaster/contents/voice.py @@ -23,6 +23,7 @@ class VoiceContent(BaseContent): duration: Optional[int] = None disable_notification: Optional[bool] = None protect_content: Optional[Union[bool, Default]] = Default("protect_content") + message_effect_id: Optional[str] = None reply_markup: Optional[ Union[ InlineKeyboardMarkup, @@ -43,6 +44,7 @@ async def __call__(self, chat_id: int) -> SendVoice: duration=self.duration, disable_notification=self.disable_notification, protect_content=self.protect_content, + message_effect_id=self.message_effect_id, reply_markup=self.reply_markup, **(self.model_extra or {}), ) @@ -60,6 +62,7 @@ def __init__( duration: Optional[int] = ..., disable_notification: Optional[bool] = ..., protect_content: Optional[Union[bool, Default]] = ..., + message_effect_id: Optional[str] = ..., reply_markup: Optional[ Union[ InlineKeyboardMarkup, diff --git a/aiogram_broadcaster/event/observer.py b/aiogram_broadcaster/event/observer.py index edf5a70..391c2a2 100644 --- a/aiogram_broadcaster/event/observer.py +++ b/aiogram_broadcaster/event/observer.py @@ -26,8 +26,5 @@ def wrapper(callback: CallbackType) -> CallbackType: def register(self, *callbacks: CallbackType) -> Self: if not callbacks: raise ValueError("At least one callback must be provided to register.") - for callback in callbacks: - if not callable(callback): - raise TypeError("The callback must be callable.") - self.callbacks.append(CallableObject(callback=callback)) + self.callbacks.extend(CallableObject(callback=callback) for callback in callbacks) return self diff --git a/aiogram_broadcaster/mailer/container.py b/aiogram_broadcaster/mailer/container.py index 435cba8..51db9a2 100644 --- a/aiogram_broadcaster/mailer/container.py +++ b/aiogram_broadcaster/mailer/container.py @@ -12,10 +12,10 @@ def __init__(self, *mailers: Mailer) -> None: self._mailers = {mailer.id: mailer for mailer in mailers} def __repr__(self) -> str: - return f"{type(self).__name__}(total_mailers={len(self._mailers)})" + return f"{type(self).__name__}(total_mailers={len(self)})" def __str__(self) -> str: - mailers_string = ", ".join(map(repr, self._mailers.values())) + mailers_string = ", ".join(map(repr, self)) return f"{type(self).__name__}[{mailers_string}]" def __contains__(self, item: int) -> bool: @@ -25,7 +25,7 @@ def __getitem__(self, item: int) -> Mailer: return self._mailers[item] def __iter__(self) -> Iterator[Mailer]: - return iter(self.mailers.values()) + return iter(self._mailers.values()) def __len__(self) -> int: return len(self._mailers) @@ -33,7 +33,7 @@ def __len__(self) -> int: def __bool__(self) -> bool: if not self._mailers: return False - return all(self._mailers.values()) + return all(self) def __hash__(self) -> int: return hash(frozenset(self._mailers)) @@ -52,5 +52,5 @@ def get_mailer(self, mailer_id: int) -> Optional[Mailer]: def get_mailers(self, *statuses: MailerStatus) -> List[Mailer]: if not statuses: - return list(self._mailers.values()) - return [mailer for mailer in self._mailers.values() if mailer.status in statuses] + return list(self) + return [mailer for mailer in self if mailer.status in statuses] diff --git a/aiogram_broadcaster/mailer/group.py b/aiogram_broadcaster/mailer/group.py index 4526a8d..2fecd4a 100644 --- a/aiogram_broadcaster/mailer/group.py +++ b/aiogram_broadcaster/mailer/group.py @@ -1,4 +1,4 @@ -from asyncio import create_task, gather, wait +from asyncio import ensure_future, gather, wait from typing import Any, Coroutine, Dict, Iterable, Optional, Set, Union from .container import MailerContainer @@ -8,7 +8,7 @@ class MailerGroup(MailerContainer): def start(self) -> Dict[Mailer, Optional[Exception]]: results: Dict[Mailer, Optional[Exception]] = {} - for mailer in self._mailers.values(): + for mailer in self: try: mailer.start() results[mailer] = None @@ -17,36 +17,33 @@ def start(self) -> Dict[Mailer, Optional[Exception]]: return results async def wait(self) -> None: - futures = [mailer.wait() for mailer in self._mailers.values()] - await wait(futures) + if not self._mailers: + return + await wait(ensure_future(mailer.wait()) for mailer in self) async def run(self) -> Dict[Mailer, Union[Exception, bool]]: - futures = [mailer.run() for mailer in self._mailers.values()] - return await self._gather_futures(*futures) + return await self._gather_targets(mailer.run() for mailer in self) async def stop(self) -> Dict[Mailer, Optional[Exception]]: - futures = [mailer.stop() for mailer in self._mailers.values()] - return await self._gather_futures(*futures) + return await self._gather_targets(mailer.stop() for mailer in self) async def destroy(self) -> Dict[Mailer, Optional[Exception]]: - futures = [mailer.destroy() for mailer in self._mailers.values()] - return await self._gather_futures(*futures) + return await self._gather_targets(mailer.destroy() for mailer in self) async def add_chats(self, chats: Iterable[int]) -> Dict[Mailer, Union[Exception, Set[int]]]: - futures = [mailer.add_chats(chats=chats) for mailer in self._mailers.values()] - return await self._gather_futures(*futures) + return await self._gather_targets(mailer.add_chats(chats=chats) for mailer in self) async def reset_chats(self) -> Dict[Mailer, Union[Exception, bool]]: - futures = [mailer.reset_chats() for mailer in self._mailers.values()] - return await self._gather_futures(*futures) + return await self._gather_targets(mailer.reset_chats() for mailer in self) async def send(self, chat_id: int) -> Dict[Mailer, Any]: - futures = [mailer.send(chat_id=chat_id) for mailer in self._mailers.values()] - return await self._gather_futures(*futures) + return await self._gather_targets(mailer.send(chat_id=chat_id) for mailer in self) - async def _gather_futures(self, *futures: Coroutine[Any, Any, Any]) -> Dict[Mailer, Any]: - if not futures: + async def _gather_targets( + self, + targets: Iterable[Coroutine[Any, Any, Any]], + ) -> Dict[Mailer, Any]: + if not targets: return {} - tasks = [create_task(future) for future in futures] - results = await gather(*tasks, return_exceptions=True) - return dict(zip(self._mailers.values(), results)) + results = await gather(*targets, return_exceptions=True) + return dict(zip(self, results)) diff --git a/aiogram_broadcaster/placeholder/registry.py b/aiogram_broadcaster/placeholder/registry.py index d7898aa..b9b4152 100644 --- a/aiogram_broadcaster/placeholder/registry.py +++ b/aiogram_broadcaster/placeholder/registry.py @@ -62,13 +62,7 @@ def chain_keys(self) -> Generator[str, None, None]: def register(self, *items: PlaceholderItem) -> Self: if not items: raise ValueError("At least one placeholder item must be provided to register.") - for item in items: - if not isinstance(item, PlaceholderItem): - raise TypeError( - f"The placeholder item must be an instance of " - f"PlaceholderItem, not a {type(item).__name__}.", - ) - self.placeholders.add(item.as_placeholder()) + self.placeholders.update(item.as_placeholder() for item in items) return self def add(self, __mapping: Optional[Mapping[str, Any]] = None, /, **kwargs: Any) -> Self: @@ -76,8 +70,9 @@ def add(self, __mapping: Optional[Mapping[str, Any]] = None, /, **kwargs: Any) - kwargs.update(__mapping) if not kwargs: raise ValueError("At least one argument must be provided.") - for key, value in kwargs.items(): - self[key] = value + self.placeholders.update( + Placeholder(key=key, value=value) for key, value in kwargs.items() + ) return self def _chain_bind(self, entity: "PlaceholderRegistry") -> None: diff --git a/aiogram_broadcaster/utils/chain.py b/aiogram_broadcaster/utils/chain.py index 985eb51..1be014f 100644 --- a/aiogram_broadcaster/utils/chain.py +++ b/aiogram_broadcaster/utils/chain.py @@ -1,11 +1,11 @@ -from typing import Any, ClassVar, Generator, Generic, List, Optional, TypeVar +from typing import Any, ClassVar, Generator, Generic, List, Optional, Type, TypeVar EntityType = TypeVar("EntityType", bound="Chain[Any]") class Chain(Generic[EntityType]): - __chain_entity__: EntityType + __chain_entity__: Type[EntityType] __chain_sub_name__: ClassVar[str] __chain_root__: ClassVar[bool] name: str diff --git a/pyproject.toml b/pyproject.toml index 3507521..80be2e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ keywords = [ "wrapper" ] dependencies = [ - "aiogram>=3.6.0" + "aiogram>=3.7.0" ] [project.optional-dependencies] @@ -76,8 +76,10 @@ sqlalchemy = [ ] [project.urls] -Repository = "https://github.com/loRes228/aiogram_broadcaster.git" -Issues = "https://github.com/loRes228/aiogram_broadcaster/issues" +Homepage = "https://github.com/loRes228/aiogram_broadcaster.git" +Documentation = "https://github.com/loRes228/aiogram_broadcaster#readme" +"Source code" = "https://github.com/loRes228/aiogram_broadcaster.git" +"Issue Tracker" = "https://github.com/loRes228/aiogram_broadcaster/issues" [tool.hatch.version] diff --git a/tests/test_event/test_observer.py b/tests/test_event/test_observer.py index 5cc491a..19ad23a 100644 --- a/tests/test_event/test_observer.py +++ b/tests/test_event/test_observer.py @@ -38,15 +38,6 @@ def test_register_no_callbacks(self): ): observer.register() - def test_register_non_callable(self): - observer = EventObserver() - - with pytest.raises( - TypeError, - match="The callback must be callable.", - ): - observer.register("not a callable") - def test_callable_registration(self): observer = EventObserver() diff --git a/tests/test_placeholder/test_registry.py b/tests/test_placeholder/test_registry.py index 98024fa..2f4aa95 100644 --- a/tests/test_placeholder/test_registry.py +++ b/tests/test_placeholder/test_registry.py @@ -30,8 +30,6 @@ def test_register_placeholder_items(self): registry.register(item1, item2) assert registry.placeholders == {item1.as_placeholder(), item2.as_placeholder()} registry = PlaceholderRegistry() - with pytest.raises(TypeError): - registry.register("invalid_type") assert registry.register(item2) == registry with pytest.raises( ValueError,