Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V20 updates #47

Merged
merged 27 commits into from
Jun 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
if [[ ${{ matrix.python-version }} == 3.10 ]]; then
meta_exit=0
else
pytest -k test_meta
pytest -v tests/test_meta.py
meta_exit=$?
fi

Expand Down
2 changes: 1 addition & 1 deletion ptbcontrib/get_chat_link/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Please see the docstrings for more details.

## Requirements

* `20>python-telegram-bot>=13.4`
* `python-telegram-bot==20.0a0`

## Authors

Expand Down
12 changes: 8 additions & 4 deletions ptbcontrib/get_chat_link/get_chat_link.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from telegram.error import BadRequest


def get_chat_link(chat: Chat) -> Optional[str]:
async def get_chat_link(chat: Chat) -> Optional[str]:
"""
Gets a link for the chat in the following order if the link is valid:
1. Chat's link (`chat.link`).
Expand All @@ -45,19 +45,23 @@ def get_chat_link(chat: Chat) -> Optional[str]:

Returns:
:obj:`Optional[str]`: Chat link as a URL if there is any. Otherwise None.

Raises:
RuntimeError: If there is no bot associated with the ``chat``, :meth:`Chat.get_bot` raises
a :exc:`RuntimeError`.
"""
bot = chat.bot
bot = chat.get_bot()
if chat.link:
return chat.link
if chat.invite_link:
return chat.invite_link

bot_chat = bot.get_chat(chat.id)
bot_chat = await bot.get_chat(chat.id)
if bot_chat.invite_link:
return bot_chat.invite_link

try:
return chat.export_invite_link()
return await chat.export_invite_link()
except BadRequest as exc:
if "not enough rights" in exc.message.lower():
return None
Expand Down
2 changes: 1 addition & 1 deletion ptbcontrib/get_chat_link/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
python-telegram-bot>=13.4,<20
python-telegram-bot==20.0a0
9 changes: 4 additions & 5 deletions ptbcontrib/postgres_persistence/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Persistence class with PostgreSQL database as backend

## Notes
Data must needs to be JSON serialisable since `PostgresPersistence` is a
Data must be JSON serializable since `PostgresPersistence` is a
subclass of PTB's `DictPersistence` which encodes and saves data in JSON format.

## How to use
Expand All @@ -15,7 +15,7 @@ from ptbcontrib.postgres_persistence import PostgresPersistence
# Your Postgresql database URL
DB_URI = "postgresql://username:pw@hostname:port/db_name"

updater = Updater(..., persistence=PostgresPersistence(url=DB_URI))
application = Application.builder().token(...).persistence(PostgresPersistence(url=DB_URI)).build()
```

**Using with existing SQLAlchemy scoped session**
Expand All @@ -34,15 +34,14 @@ def start_session() -> scoped_session:
engine = create_engine(DB_URI, client_encoding="utf8")
return scoped_session(sessionmaker(bind=engine, autoflush=False))

updater = Updater(..., persistence=PostgresPersistence(session=start_session()))
application = Application.builder().token(...).persistence(PostgresPersistence(session=start_session())).build()
```


## Requirements

* `20>python-telegram-bot>=12.0`
* `python-telegram-bot==20.0a0`
* `SQLAlchemy`
* `ujson` (Optional)

## Authors

Expand Down
124 changes: 67 additions & 57 deletions ptbcontrib/postgres_persistence/postgrespersistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,40 +19,32 @@
"""This module contains PostgresqlPersistence class"""


from collections import defaultdict
import json
from logging import getLogger
from typing import Any, Callable, Dict, Optional, Tuple
from typing import Any, Dict, List, Optional, Tuple

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.sql import text
from telegram.ext import DictPersistence
from telegram.utils.helpers import decode_conversations_from_json, encode_conversations_to_json

try:
import ujson as json
except ImportError:
import json # type: ignore[no-redef]
CDCData = Tuple[List[Tuple[str, float, Dict[str, Any]]], Dict[str, str]]


class PostgresPersistence(DictPersistence):
"""Using Postgresql database to make user/chat/bot data persistent across reboots.

Attributes:
store_user_data (:obj:`bool`): Whether user_data should be saved by this
persistence class.
store_chat_data (:obj:`bool`): Whether chat_data should be saved by this
persistence class.
store_bot_data (:obj:`bool`): Whether bot_data should be saved by this
persistence class.

Args:
url (:obj:`str`, Optional) the postgresql database url.
session (:obj:`scoped_session`, Optional): sqlalchemy scoped session.
on_flush (:obj:`bool`, optional): if set to :obj:`True` :class:`PostgresPersistence`
will only update bot/chat/user data when :meth:flush is called.
**kwargs (:obj:`dict`): Arbitrary keyword Arguments to be passed to
the DictPersistence constructor.

Attributes:
store_data (:class:`PersistenceInput`): Specifies which kinds of data will be saved by this
persistence instance.
"""

def __init__(
Expand All @@ -78,57 +70,61 @@ def __init__(
raise TypeError("You must need to provide either url or session.")

self.logger = getLogger(__name__)
super().__init__(**kwargs)

self.on_flush = on_flush
self.__init_database()
self.__load_database()

def __init_database(self) -> None:
"""
creates table for storing the data if table
doesn't exist already inside database.
"""
create_table_qry = """
CREATE TABLE IF NOT EXISTS persistence(
data json NOT NULL);"""
self._session.execute(text(create_table_qry))
self._session.commit()

def __load_database(self) -> None:
try:
self.logger.info("Loading database....")
data_ = self._session.execute(text("SELECT data FROM persistence")).first()
data = data_[0] if data_ is not None else {}

self.logger.info("Loading database....")
self._chat_data = defaultdict(dict, self._key_mapper(data.get("chat_data", {}), int))
self._user_data = defaultdict(dict, self._key_mapper(data.get("user_data", {}), int))
self._bot_data = data.get("bot_data", {})
self._conversations = decode_conversations_from_json(data.get("conversations", "{}"))
chat_data_json = data.get("chat_data", "")
user_data_json = data.get("user_data", "")
bot_data_json = data.get("bot_data", "")
conversations_json = data.get("conversations", "{}")
callback_data_json = data.get("callback_data_json", "")

self.logger.info("Database loaded successfully!")

# if it is a fresh setup we'll add some placeholder data so we
# can perform `UPDATE` opearations on it, cause SQL only allows
# can perform `UPDATE` operations on it, cause SQL only allows
# `UPDATE` operations if column have some data already present inside it.
if not data:
insert_qry = "INSERT INTO persistence (data) VALUES (:jsondata)"
self._session.execute(text(insert_qry), {"jsondata": "{}"})
self._session.commit()

super().__init__(
**kwargs,
chat_data_json=chat_data_json,
user_data_json=user_data_json,
bot_data_json=bot_data_json,
callback_data_json=callback_data_json,
conversations_json=conversations_json,
)
finally:
self._session.close()

@staticmethod
def _key_mapper(iterable: Dict, func: Callable) -> Dict:
return {func(k): v for k, v in iterable.items()}
def __init_database(self) -> None:
"""
creates table for storing the data if table
doesn't exist already inside database.
"""
create_table_qry = """
CREATE TABLE IF NOT EXISTS persistence(
data json NOT NULL);"""
self._session.execute(text(create_table_qry))
self._session.commit()

def _dump_into_json(self) -> Any:
"""Dumps data into json format for inserting in db."""

to_dump = {
"chat_data": self._chat_data,
"user_data": self._user_data,
"bot_data": self.bot_data,
"conversations": encode_conversations_to_json(self._conversations),
"chat_data": self.chat_data_json,
"user_data": self.user_data_json,
"bot_data": self.bot_data_json,
"conversations": self.conversations_json,
"callback_data": self.callback_data_json,
}
self.logger.debug("Dumping %s", to_dump)
return json.dumps(to_dump)
Expand All @@ -147,51 +143,65 @@ def _update_database(self) -> None:
exc_info=excp,
)

def update_conversation(
async def update_conversation(
self, name: str, key: Tuple[int, ...], new_state: Optional[object]
) -> None:
"""Will update the conversations for the given handler.

Args:
name (:obj:`str`): The handler's name.
key (:obj:`tuple`): The key the state is changed for.
new_state (:obj:`tuple` | :obj:`any`): The new state for the given key.
"""
super().update_conversation(name, key, new_state)
await super().update_conversation(name, key, new_state)
if not self.on_flush:
self._update_database()
await self.flush()

def update_user_data(self, user_id: int, data: Dict) -> None:
async def update_user_data(self, user_id: int, data: Dict) -> None:
"""Will update the user_data (if changed).
Args:
user_id (:obj:`int`): The user the data might have been changed for.
data (:obj:`dict`): The :attr:`telegram.ext.Dispatcher.user_data` ``[user_id]``.
"""
super().update_user_data(user_id, data)
await super().update_user_data(user_id, data)
if not self.on_flush:
self._update_database()
await self.flush()

def update_chat_data(self, chat_id: int, data: Dict) -> None:
async def update_chat_data(self, chat_id: int, data: Dict) -> None:
"""Will update the chat_data (if changed).
Args:
chat_id (:obj:`int`): The chat the data might have been changed for.
data (:obj:`dict`): The :attr:`telegram.ext.Dispatcher.chat_data` ``[chat_id]``.
"""
super().update_chat_data(chat_id, data)
await super().update_chat_data(chat_id, data)
if not self.on_flush:
self._update_database()
await self.flush()

def update_bot_data(self, data: Dict) -> None:
async def update_bot_data(self, data: Dict) -> None:
"""Will update the bot_data (if changed).
Args:
data (:obj:`dict`): The :attr:`telegram.ext.Dispatcher.bot_data`.
"""
super().update_bot_data(data)
await super().update_bot_data(data)
if not self.on_flush:
self._update_database()
await self.flush()

def flush(self) -> None:
Bibo-Joshi marked this conversation as resolved.
Show resolved Hide resolved
async def update_callback_data(self, data: CDCData) -> None:
"""Will update the callback_data (if changed).

Args:
data (Tuple[List[Tuple[:obj:`str`, :obj:`float`, Dict[:obj:`str`, :class:`object`]]], \
Dict[:obj:`str`, :obj:`str`]]): The relevant data to restore
:class:`telegram.ext.CallbackDataCache`.
"""
await super().update_callback_data(data)
if not self.on_flush:
await self.flush()

async def flush(self) -> None:
"""Will be called by :class:`telegram.ext.Updater` upon receiving a stop signal. Gives the
persistence a chance to finish up saving or close a database connection gracefully.
"""
self._update_database()
self.logger.info("Closing database...")
if not self.on_flush:
self.logger.info("Closing database...")
2 changes: 1 addition & 1 deletion ptbcontrib/postgres_persistence/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
python-telegram-bot>=12.0,<20
python-telegram-bot==20.0a0
SQLAlchemy
2 changes: 1 addition & 1 deletion ptbcontrib/reply_to_message_filter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ documents_as_reply_to_documents = MessageHandler(

## Requirements

* `20>python-telegram-bot>=13.0`
* `python-telegram-bot==20.0a0`

## Authors

Expand Down
12 changes: 6 additions & 6 deletions ptbcontrib/reply_to_message_filter/replytomessagefilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from typing import Dict, Optional, Union

from telegram import Update
from telegram.ext import BaseFilter, UpdateFilter
from telegram.ext.filters import BaseFilter, UpdateFilter


class ReplyToMessageFilter(UpdateFilter):
Expand All @@ -36,8 +36,8 @@ class ReplyToMessageFilter(UpdateFilter):
"""

def __init__(self, filters: BaseFilter):
super().__init__(name=str(filters), data_filter=filters.data_filter)
self.filters = filters
self.data_filter = self.filters.data_filter

def filter(self, update: Update) -> Optional[Union[bool, Dict]]:
"""See :meth:`telegram.ext.BaseFilter.filter`."""
Expand All @@ -46,11 +46,11 @@ def filter(self, update: Update) -> Optional[Union[bool, Dict]]:

reply_to_message = update.effective_message.reply_to_message
if update.channel_post:
return self.filters(Update(1, channel_post=reply_to_message))
return self.filters.check_update(Update(1, channel_post=reply_to_message))
if update.edited_channel_post:
return self.filters(Update(1, edited_channel_post=reply_to_message))
return self.filters.check_update(Update(1, edited_channel_post=reply_to_message))
if update.message:
return self.filters(Update(1, message=reply_to_message))
return self.filters.check_update(Update(1, message=reply_to_message))
if update.edited_message:
return self.filters(Update(1, edited_message=reply_to_message))
return self.filters.check_update(Update(1, edited_message=reply_to_message))
return False
2 changes: 1 addition & 1 deletion ptbcontrib/reply_to_message_filter/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
python-telegram-bot>=13.0,<20
python-telegram-bot==20.0a0
2 changes: 1 addition & 1 deletion ptbcontrib/roles/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Unfortunately `RolesHandler(my_conversation_handler, roles=roles)` does *not* wo

## Requirements

* `20>python-telegram-bot>=13.1`
* `python-telegram-bot==20.0a0`

## Authors

Expand Down
4 changes: 1 addition & 3 deletions ptbcontrib/roles/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains classes and methods for granular, hierarchical user access management."""

from .roles import ChatAdminsRole, ChatCreatorRole, InvertedRole, Role, Roles
from .roles import InvertedRole, Role, Roles
from .roleshandler import BOT_DATA_KEY, RolesBotData, RolesHandler, setup_roles

__all__ = [
"Roles",
"Role",
"ChatCreatorRole",
"ChatAdminsRole",
"InvertedRole",
"RolesHandler",
"setup_roles",
Expand Down
2 changes: 1 addition & 1 deletion ptbcontrib/roles/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
python-telegram-bot==13.6,<20
python-telegram-bot==20.0a0
Loading