Compare commits

..

17 Commits

Author SHA1 Message Date
Hinrich Mahler 4aedb33d37 Bump version to v20.2 2023-03-25 13:25:59 +01:00
Bibo-Joshi 4ddc36c625 Documentation Improvements (#3565, #3600)
Co-authored-by: Dmitry Kolomatskiy <58207913+lemontree210@users.noreply.github.com>
Co-authored-by: poolitzer <github@poolitzer.eu>
Co-authored-by: Louis Wang <beiluonever@users.noreply.github.com>
2023-03-25 12:48:34 +01:00
dependabot[bot] 311f88a716 Bump pytest-asyncio from 0.20.3 to 0.21.0 (#3624) 2023-03-25 12:25:57 +01:00
dependabot[bot] 179cb49dbc Bump furo from 2022.12.7 to 2023.3.23 (#3625) 2023-03-25 12:25:36 +01:00
Bibo-Joshi 512e390738 API 6.6 (#3584)
Co-authored-by: poolitzer <github@poolitzer.eu>
Co-authored-by: Dmitry Kolomatskiy <58207913+lemontree210@users.noreply.github.com>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2023-03-25 11:47:26 +01:00
dependabot[bot] 33b677aeb4 Bump pytest-xdist from 3.2.0 to 3.2.1 (#3606) 2023-03-19 10:54:03 +01:00
Poolitzer 800598ced4 Revert to HTTP/1.1 as Default and make HTTP/2 an Optional Dependency (#3576)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2023-03-12 16:30:39 +01:00
pre-commit-ci[bot] ec20f27a82 pre-commit autoupdate (#3577)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-08 20:46:51 +01:00
Bibo-Joshi cd59c1c075 Handle Symbolic Links in was_called_by (#3552)
Co-authored-by: hyfc <myloxyloto2012@gmail.com>
2023-03-06 21:59:01 +01:00
dependabot[bot] 52b0f2c3c9 Update apscheduler requirement from ~=3.10.0 to ~=3.10.1 (#3572)
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
2023-03-06 21:09:29 +01:00
dependabot[bot] 598f44f4c2 Bump pytest from 7.2.1 to 7.2.2 (#3573) 2023-03-05 16:21:57 +01:00
Bibo-Joshi 22d0dd1301 Tidy Up Tests Directory (#3553) 2023-02-22 20:19:46 +01:00
sam-mosleh c6b6b0a370 Enhance Application.create_task (#3543) 2023-02-20 19:53:27 +01:00
Bibo-Joshi ee6c8a5995 Make Type Completeness Workflow Usable for PRs from Forks (#3551) 2023-02-13 20:39:12 +01:00
dependabot[bot] d5a1a48145 Bump pytest-xdist from 3.1.0 to 3.2.0 (#3550) 2023-02-11 17:42:10 +01:00
Harshil 963edbf191 Refactor and Overhaul the Test Suite (#3426) 2023-02-11 10:45:17 +01:00
dependabot[bot] 43c3c8f568 Bump sphinxcontrib-mermaid from 0.7.1 to 0.8 (#3549) 2023-02-11 10:43:38 +01:00
249 changed files with 13667 additions and 8066 deletions
+5 -27
View File
@@ -84,35 +84,13 @@ Here's how to make a one-off code change.
- In addition, PTB uses some formatting/styling and linting tools in the pre-commit setup. Some of those tools also have command line tools that can help to run these tools outside of the pre-commit step. If you'd like to leverage that, please have a look at the `pre-commit config file`_ for an overview of which tools (and which versions of them) are used. For example, we use `Black`_ for code formatting. Plugins for Black exist for some `popular editors`_. You can use those instead of manually formatting everything.
- Please ensure that the code you write is well-tested.
- Please ensure that the code you write is well-tested and that all automated tests still pass. We
have dedicated an `testing page`_ to help you with that.
- In addition to that, we provide the `dev` marker for pytest. If you write one or multiple tests and want to run only those, you can decorate them via `@pytest.mark.dev` and then run it with minimal overhead with `pytest ./path/to/test_file.py -m dev`.
- Dont break backward compatibility.
- Don't break backward compatibility.
- Add yourself to the AUTHORS.rst_ file in an alphabetical fashion.
- Before making a commit ensure that all automated tests still pass:
.. code-block:: bash
$ pytest -v
Since the tests can take a while to run, you can speed things up by running them in parallel
using `pytest-xdist`_ (note that this may effect the result of the test in some rare cases):
.. code-block:: bash
$ pytest -v -n auto --dist=loadfile
To run ``test_official`` (particularly useful if you made API changes), run
.. code-block:: bash
$ export TEST_OFFICIAL=true
prior to running the tests.
- If you want run style & type checks before committing run
.. code-block:: bash
@@ -205,7 +183,7 @@ or, if you don't have ``make`` available (e.g. on Windows):
Once the process terminates, you can view the built documentation by opening ``docs/build/html/index.html`` with a browser.
- Add ``.. versionadded:: version``, ``.. versionchanged:: version`` or ``.. deprecated:: version`` to the associated documentation of your changes, depending on what kind of change you made. This only applies if the change you made is visible to an end user. The directives should be added to class/method descriptions if their general behaviour changed and to the description of all arguments & attributes that changed.
- Add ``.. versionadded:: NEXT.VERSION``, ``.. versionchanged:: NEXT.VERSION`` or ``.. deprecated:: NEXT.VERSION`` to the associated documentation of your changes, depending on what kind of change you made. This only applies if the change you made is visible to an end user. The directives should be added to class/method descriptions if their general behaviour changed and to the description of all arguments & attributes that changed.
Dev facing documentation
------------------------
@@ -287,4 +265,4 @@ break the API classes. For example:
.. _`RTD build`: https://docs.python-telegram-bot.org/en/doc-fixes
.. _`CSI`: https://standards.mousepawmedia.com/en/stable/csi.html
.. _`section`: #documenting
.. _`pytest-xdist`: https://github.com/pytest-dev/pytest-xdist
.. _`testing page`: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/tests/README.rst
+12 -30
View File
@@ -44,42 +44,24 @@ jobs:
# The first & second one are achieved by mocking the corresponding import
# See test_helpers.py & test_no_passport.py for details
run: |
# Test without passport
pytest -v --cov -k test_no_passport.py
# We test without optional dependencies first. This includes:
# - without pytz
# - without jobqueue
# - without ratelimiter
# - without webhooks
# - without arbitrary callback data
# - without socks support
# - without http2 support
TO_TEST="test_no_passport.py or test_datetime.py or test_defaults.py or test_jobqueue.py or test_applicationbuilder.py or test_ratelimiter.py or test_updater.py or test_callbackdatacache.py or test_request.py"
pytest -v --cov -k "${TO_TEST}"
status=$?
# test without pytz
pytest -v --cov --cov-append -k test_datetime.py
status=$(( $? > status ? $? : status))
pytest -v --cov --cov-append -k test_defaults.py
status=$(( $? > status ? $? : status))
# test without pytz & jobqueue
pytest -v --cov --cov-append -k test_jobqueue.py
pytest -v --cov --cov-append -k test_applicationbuilder.py
status=$(( $? > status ? $? : status))
# Test without ratelimiter
pytest -v --cov --cov-append -k test_ratelimiter.py
status=$(( $? > status ? $? : status))
# Test without webhooks
pytest -v --cov --cov-append -k test_updater.py
status=$(( $? > status ? $? : status))
# Test without callback-data
pytest -v --cov --cov-append -k test_callbackdatacache.py
status=$(( $? > status ? $? : status))
# Test without socks
pytest -v --cov --cov-append -k test_request.py
status=$(( $? > status ? $? : status))
# Test the rest
export TEST_WITH_OPT_DEPS='true'
pip install -r requirements-opts.txt
# `-n auto --dist loadfile` uses pytest-xdist to run each test file on a different CPU
# worker
# worker. Increasing number of workers has little effect on test duration, but it seems
# to increase flakyness, specially on python 3.7 with --dist=loadgroup.
pytest -v --cov --cov-append -n auto --dist loadfile
status=$(( $? > status ? $? : status))
exit ${status}
+8 -7
View File
@@ -23,21 +23,22 @@ jobs:
- name: Install Pyright
run: |
python -W ignore -m pip install pyright~=1.1.291
- name: Get PR Completeness
# Must run before base completeness, as base completeness will checkout the base branch
# And we can't go back to the PR branch after that in case the PR is coming from a fork
run: |
pip install . -U
pyright --verifytypes telegram --ignoreexternal --outputjson > pr.json || true
pyright --verifytypes telegram --ignoreexternal > pr.readable || true
- name: Get Base Completeness
run: |
git checkout ${{ github.base_ref }}
pip install . -U
pyright --verifytypes telegram --ignoreexternal --outputjson > base.json || true
- name: Get PR Completeness
run: |
git checkout ${{ github.head_ref }}
pip install . -U
pyright --verifytypes telegram --ignoreexternal --outputjson > pr.json || true
pyright --verifytypes telegram --ignoreexternal > pr.readable || true
- name: Compare Completeness
uses: jannekem/run-python-script-action@v1
with:
script: |
script: |
import json
import os
from pathlib import Path
+2
View File
@@ -51,6 +51,8 @@ nosetests.xml
coverage.xml
*,cover
.coveralls.yml
.testmondata
.testmondata-journal
# Translations
*.mo
+7 -7
View File
@@ -20,7 +20,7 @@ repos:
hooks:
- id: flake8
- repo: https://github.com/PyCQA/pylint
rev: v2.16.1
rev: v2.16.4
hooks:
- id: pylint
files: ^(telegram|examples)/.*\.py$
@@ -33,12 +33,12 @@ repos:
additional_dependencies:
- httpx~=0.23.3
- tornado~=6.2
- APScheduler~=3.10.0
- APScheduler~=3.10.1
- cachetools~=5.3.0
- aiolimiter~=1.0.0
- . # this basically does `pip install -e .`
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.991
rev: v1.0.1
hooks:
- id: mypy
name: mypy-ptb
@@ -49,7 +49,7 @@ repos:
- types-cachetools
- httpx~=0.23.3
- tornado~=6.2
- APScheduler~=3.10.0
- APScheduler~=3.10.1
- cachetools~=5.3.0
- aiolimiter~=1.0.0
- . # this basically does `pip install -e .`
@@ -61,7 +61,7 @@ repos:
- --follow-imports=silent
additional_dependencies:
- tornado~=6.2
- APScheduler~=3.10.0
- APScheduler~=3.10.1
- cachetools~=5.3.0
- . # this basically does `pip install -e .`
- repo: https://github.com/asottile/pyupgrade
@@ -80,7 +80,7 @@ repos:
- --diff
- --check
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.243'
rev: 'v0.0.254'
hooks:
- id: ruff
name: ruff
@@ -88,6 +88,6 @@ repos:
additional_dependencies:
- httpx~=0.23.3
- tornado~=6.2
- APScheduler~=3.10.0
- APScheduler~=3.10.1
- cachetools~=5.3.0
- aiolimiter~=1.0.0
+1
View File
@@ -100,6 +100,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `Riko Naka <https://github.com/rikonaka>`_
- `Rizlas <https://github.com/rizlas>`_
- `Sahil Sharma <https://github.com/sahilsharma811>`_
- `Sam Mosleh <https://github.com/sam-mosleh>`_
- `Sascha <https://github.com/saschalalala>`_
- `Shelomentsev D <https://github.com/shelomentsevd>`_
- `Shivam Saini <https://github.com/shivamsn97>`_
+50 -1
View File
@@ -2,6 +2,55 @@
Changelog
=========
Version 20.2
============
*Released 2023-03-25*
This is the technical changelog for version 20.2. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
Major Changes
-------------
- Full Support for API 6.6 (`#3584`_)
- Revert to HTTP/1.1 as Default and make HTTP/2 an Optional Dependency (`#3576`_)
Minor Changes, Documentation Improvements and CI
------------------------------------------------
- Documentation Improvements (`#3565`_, `#3600`_)
- Handle Symbolic Links in ``was_called_by`` (`#3552`_)
- Tidy Up Tests Directory (`#3553`_)
- Enhance ``Application.create_task`` (`#3543`_)
- Make Type Completeness Workflow Usable for ``PRs`` from Forks (`#3551`_)
- Refactor and Overhaul the Test Suite (`#3426`_)
Dependencies
------------
- Bump ``pytest-asyncio`` from 0.20.3 to 0.21.0 (`#3624`_)
- Bump ``furo`` from 2022.12.7 to 2023.3.23 (`#3625`_)
- Bump ``pytest-xdist`` from 3.2.0 to 3.2.1 (`#3606`_)
- ``pre-commit`` autoupdate (`#3577`_)
- Update ``apscheduler`` requirement from ~=3.10.0 to ~=3.10.1 (`#3572`_)
- Bump ``pytest`` from 7.2.1 to 7.2.2 (`#3573`_)
- Bump ``pytest-xdist`` from 3.1.0 to 3.2.0 (`#3550`_)
- Bump ``sphinxcontrib-mermaid`` from 0.7.1 to 0.8 (`#3549`_)
.. _`#3584`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3584
.. _`#3576`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3576
.. _`#3565`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3565
.. _`#3600`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3600
.. _`#3552`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3552
.. _`#3553`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3553
.. _`#3543`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3543
.. _`#3551`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3551
.. _`#3426`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3426
.. _`#3624`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3624
.. _`#3625`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3625
.. _`#3606`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3606
.. _`#3577`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3577
.. _`#3572`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3572
.. _`#3573`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3573
.. _`#3550`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3550
.. _`#3549`: https://github.com/python-telegram-bot/python-telegram-bot/pull/3549
Version 20.1
============
*Released 2023-02-09*
@@ -2108,7 +2157,7 @@ Changes
- Lots of small improvements to our tests and documentation.
.. _`see docs`: https://docs.python-telegram-bot.org/en/stable/telegram.ext.dispatcher.html#telegram.ext.Dispatcher.add_handler
.. _`see docs`: https://docs.python-telegram-bot.org/en/v13.11/telegram.ext.dispatcher.html?highlight=Dispatcher.add_handler#telegram.ext.Dispatcher.add_handler
.. _`#777`: https://github.com/python-telegram-bot/python-telegram-bot/pull/777
.. _`#806`: https://github.com/python-telegram-bot/python-telegram-bot/pull/806
.. _`#766`: https://github.com/python-telegram-bot/python-telegram-bot/pull/766
+6 -6
View File
@@ -14,7 +14,7 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-6.5-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.6-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@@ -93,7 +93,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support
====================
All types and methods of the Telegram Bot API **6.5** are supported.
All types and methods of the Telegram Bot API **6.6** are supported.
Installing
==========
@@ -135,9 +135,8 @@ As these features are *optional*, the corresponding 3rd party dependencies are n
Instead, they are listed as optional dependencies.
This allows to avoid unnecessary dependency conflicts for users who don't need the optional features.
The only required dependency is `httpx[http2] ~= 0.23.3 <https://www.python-httpx.org>`_ for
``telegram.request.HTTPXRequest``, the default networking backend. By default, HTTP/2 is used, as it
provides greater performance and stability, specially for concurrent requests.
The only required dependency is `httpx ~= 0.23.3 <https://www.python-httpx.org>`_ for
``telegram.request.HTTPXRequest``, the default networking backend.
``python-telegram-bot`` is most useful when used along with additional libraries.
To minimize dependency conflicts, we try to be liberal in terms of version requirements on the (optional) dependencies.
@@ -151,10 +150,11 @@ PTB can be installed with optional dependencies:
* ``pip install python-telegram-bot[passport]`` installs the `cryptography>=39.0.1 <https://cryptography.io/en/stable>`_ library. Use this, if you want to use Telegram Passport related functionality.
* ``pip install python-telegram-bot[socks]`` installs `httpx[socks] <https://www.python-httpx.org/#dependencies>`_. Use this, if you want to work behind a Socks5 server.
* ``pip install python-telegram-bot[http2]`` installs `httpx[http2] <https://www.python-httpx.org/#dependencies>`_. Use this, if you want to use HTTP/2.
* ``pip install python-telegram-bot[rate-limiter]`` installs `aiolimiter~=1.0.0 <https://aiolimiter.readthedocs.io/en/stable/>`_. Use this, if you want to use ``telegram.ext.AIORateLimiter``.
* ``pip install python-telegram-bot[webhooks]`` installs the `tornado~=6.2 <https://www.tornadoweb.org/en/stable/>`_ library. Use this, if you want to use ``telegram.ext.Updater.start_webhook``/``telegram.ext.Application.run_webhook``.
* ``pip install python-telegram-bot[callback-data]`` installs the `cachetools~=5.3.0 <https://cachetools.readthedocs.io/en/latest/>`_ library. Use this, if you want to use `arbitrary callback_data <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Arbitrary-callback_data>`_.
* ``pip install python-telegram-bot[job-queue]`` installs the `APScheduler~=3.10.0 <https://apscheduler.readthedocs.io/en/3.x/>`_ library and enforces `pytz>=2018.6 <https://pypi.org/project/pytz/>`_, where ``pytz`` is a dependency of ``APScheduler``. Use this, if you want to use the ``telegram.ext.JobQueue``.
* ``pip install python-telegram-bot[job-queue]`` installs the `APScheduler~=3.10.1 <https://apscheduler.readthedocs.io/en/3.x/>`_ library and enforces `pytz>=2018.6 <https://pypi.org/project/pytz/>`_, where ``pytz`` is a dependency of ``APScheduler``. Use this, if you want to use the ``telegram.ext.JobQueue``.
To install multiple optional dependencies, separate them by commas, e.g. ``pip install python-telegram-bot[socks,webhooks]``.
+5 -5
View File
@@ -14,7 +14,7 @@
:target: https://pypi.org/project/python-telegram-bot-raw/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-6.5-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.6-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@@ -89,7 +89,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support
====================
All types and methods of the Telegram Bot API **6.5** are supported.
All types and methods of the Telegram Bot API **6.6** are supported.
Installing
==========
@@ -136,9 +136,8 @@ As these features are *optional*, the corresponding 3rd party dependencies are n
Instead, they are listed as optional dependencies.
This allows to avoid unnecessary dependency conflicts for users who don't need the optional features.
The only required dependency is `httpx[http2] ~= 0.23.3 <https://www.python-httpx.org>`_ for
``telegram.request.HTTPXRequest``, the default networking backend. By default, HTTP/2 is used, as
it provides greater performance and stability, specially for concurrent requests.
The only required dependency is `httpx ~= 0.23.3 <https://www.python-httpx.org>`_ for
``telegram.request.HTTPXRequest``, the default networking backend.
``python-telegram-bot`` is most useful when used along with additional libraries.
To minimize dependency conflicts, we try to be liberal in terms of version requirements on the (optional) dependencies.
@@ -152,6 +151,7 @@ PTB can be installed with optional dependencies:
* ``pip install python-telegram-bot-raw[passport]`` installs the `cryptography>=39.0.1 <https://cryptography.io/en/stable>`_ library. Use this, if you want to use Telegram Passport related functionality.
* ``pip install python-telegram-bot-raw[socks]`` installs `httpx[socks] <https://www.python-httpx.org/#dependencies>`_. Use this, if you want to work behind a Socks5 server.
* ``pip install python-telegram-bot[http2]`` installs `httpx[http2] <https://www.python-httpx.org/#dependencies>`_. Use this, if you want to use HTTP/2.
To install multiple optional dependencies, separate them by commas, e.g. ``pip install python-telegram-bot-raw[passport,socks]``.
+2 -2
View File
@@ -1,7 +1,7 @@
sphinx==6.1.3
sphinx-pypi-upload
furo==2022.12.7
furo==2023.3.23
git+https://github.com/harshil21/furo-sphinx-search@01efc7be422d7dc02390aab9be68d6f5ce1a5618#egg=furo-sphinx-search
sphinx-paramlinks==0.5.4
sphinxcontrib-mermaid==0.7.1
sphinxcontrib-mermaid==0.8.1
sphinx-copybutton==0.5.1
+2 -2
View File
@@ -21,9 +21,9 @@ author = "Leandro Toledo"
# built documents.
#
# The short X.Y version.
version = "20.1" # telegram.__version__[:3]
version = "20.2" # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags.
release = "20.1" # telegram.__version__
release = "20.2" # telegram.__version__
# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = "6.1.3"
+39 -4
View File
@@ -157,6 +157,23 @@
- Used for getting the number of members in a chat
* - :meth:`~telegram.Bot.get_chat_member`
- Used for getting a member of a chat
* - :meth:`~telegram.Bot.leave_chat`
- Used for leaving a chat
.. raw:: html
</details>
<br>
.. raw:: html
<details>
<summary>Bot settings</summary>
.. list-table::
:align: left
:widths: 1 4
* - :meth:`~telegram.Bot.set_my_commands`
- Used for setting the list of commands
* - :meth:`~telegram.Bot.delete_my_commands`
@@ -171,8 +188,14 @@
- Used for obtaining the menu button of a private chat or the default menu button
* - :meth:`~telegram.Bot.set_chat_menu_button`
- Used for setting the menu button of a private chat or the default menu button
* - :meth:`~telegram.Bot.leave_chat`
- Used for leaving a chat
* - :meth:`~telegram.Bot.set_my_description`
- Used for setting the description of the bot
* - :meth:`~telegram.Bot.get_my_description`
- Used for obtaining the description of the bot
* - :meth:`~telegram.Bot.set_my_short_description`
- Used for setting the short description of the bot
* - :meth:`~telegram.Bot.get_my_short_description`
- Used for obtaining the short description of the bot
.. raw:: html
@@ -194,14 +217,26 @@
- Used for deleting a sticker from a set
* - :meth:`~telegram.Bot.create_new_sticker_set`
- Used for creating a new sticker set
* - :meth:`~telegram.Bot.delete_sticker_set`
- Used for deleting a sticker set made by a bot
* - :meth:`~telegram.Bot.set_chat_sticker_set`
- Used for setting a sticker set
- Used for setting a sticker set of a chat
* - :meth:`~telegram.Bot.delete_chat_sticker_set`
- Used for deleting the set sticker set
- Used for deleting the set sticker set of a chat
* - :meth:`~telegram.Bot.set_sticker_position_in_set`
- Used for moving a sticker's position in the set
* - :meth:`~telegram.Bot.set_sticker_set_title`
- Used for setting the title of a sticker set
* - :meth:`~telegram.Bot.set_sticker_emoji_list`
- Used for setting the emoji list of a sticker
* - :meth:`~telegram.Bot.set_sticker_keywords`
- Used for setting the keywords of a sticker
* - :meth:`~telegram.Bot.set_sticker_mask_position`
- Used for setting the mask position of a mask sticker
* - :meth:`~telegram.Bot.set_sticker_set_thumb`
- Used for setting the thumbnail of a sticker set
* - :meth:`~telegram.Bot.set_custom_emoji_sticker_set_thumbnail`
- Used for setting the thumbnail of a custom emoji sticker set
* - :meth:`~telegram.Bot.get_sticker_set`
- Used for getting a sticker set
* - :meth:`~telegram.Bot.upload_sticker_file`
+2 -3
View File
@@ -32,7 +32,6 @@
GitHub Repository <https://github.com/python-telegram-bot/python-telegram-bot/>
Telegram Channel <https://t.me/pythontelegrambotchannel/>
Telegram User Group <https://t.me/pythontelegrambotgroup/>
contributing
coc
contributing
testing
+3
View File
@@ -15,6 +15,8 @@ Available Types
telegram.botcommandscopechatadministrators
telegram.botcommandscopechatmember
telegram.botcommandscopedefault
telegram.botdescription
telegram.botshortdescription
telegram.callbackquery
telegram.chat
telegram.chatadministratorrights
@@ -53,6 +55,7 @@ Available Types
telegram.inputmediadocument
telegram.inputmediaphoto
telegram.inputmediavideo
telegram.inputsticker
telegram.keyboardbutton
telegram.keyboardbuttonpolltype
telegram.keyboardbuttonrequestchat
+6
View File
@@ -0,0 +1,6 @@
BotDescription
==============
.. autoclass:: telegram.BotDescription
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
BotShortDescription
===================
.. autoclass:: telegram.BotShortDescription
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
InputSticker
============
.. autoclass:: telegram.InputSticker
:members:
:show-inheritance:
+1
View File
@@ -0,0 +1 @@
.. include:: ../../tests/README.rst
+4
View File
@@ -14,6 +14,10 @@
.. |thumbdocstringnopath| replace:: |thumbdocstringbase| |uploadinputnopath|
.. |thumbargumentdeprecation| replace:: As of Bot API 6.6 this argument is deprecated in favor of
.. |thumbattributedeprecation| replace:: As of Bot API 6.6 this attribute is deprecated in favor of
.. |editreplymarkup| replace:: It is currently only possible to edit messages without :attr:`telegram.Message.reply_markup` or with inline keyboards.
.. |toapikwargsbase| replace:: These arguments are also considered by :meth:`~telegram.TelegramObject.to_dict` and :meth:`~telegram.TelegramObject.to_json`, i.e. when passing objects to Telegram. Passing them to Telegram is however not guaranteed to work for all kinds of objects, e.g. this will fail for objects that can not directly be JSON serialized.
+2 -2
View File
@@ -18,8 +18,8 @@ Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
Note:
To use arbitrary callback data, you must install ptb via
`pip install python-telegram-bot[callback-data]`
To use the JobQueue, you must install PTB via
`pip install python-telegram-bot[job-queue]`
"""
import logging
+6 -6
View File
@@ -1,10 +1,10 @@
pre-commit
pytest==7.2.1
pytest-asyncio==0.20.3
pytest-timeout==2.1.0 # used to timeout tests
pytest-xdist==3.1.0 # xdist runs tests in parallel
pre-commit # needed for pre-commit hooks in the git commit command
# For the test suite
pytest==7.2.2
pytest-asyncio==0.21.0 # needed because pytest doesn't come with native support for coroutines as tests
pytest-xdist==3.2.1 # xdist runs tests in parallel
flaky # Used for flaky tests (flaky decorator)
beautifulsoup4 # used in test_official for parsing tg docs
wheel # required for building the wheels for releases
+2 -1
View File
@@ -11,6 +11,7 @@
# versions and only increase the lower bound if necessary
httpx[socks] # socks
httpx[http2] # http2
cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1 # passport
aiolimiter~=1.0.0 # rate-limiter!ext
@@ -20,7 +21,7 @@ tornado~=6.2 # webhooks!ext
# Cachetools and APS don't have a strict stability policy.
# Let's be cautious for now.
cachetools~=5.3.0 # callback-data!ext
APScheduler~=3.10.0 # job-queue!ext
APScheduler~=3.10.1 # job-queue!ext
# pytz is required by APS and just needs the lower bound due to #2120
pytz>=2018.6 # job-queue!ext
+1 -2
View File
@@ -6,5 +6,4 @@
# versions and only increase the lower bound if necessary
# httpx has no stable release yet, so let's be cautious for now
# HTTP/2 is more performant and stable than HTTP/1.1, specially for concurrent requests
httpx[http2] ~= 0.23.3
httpx ~= 0.23.3
+4 -1
View File
@@ -36,7 +36,10 @@ filterwarnings =
; Unfortunately due to https://github.com/pytest-dev/pytest/issues/8343 we can't have this here
; and instead do a trick directly in tests/conftest.py
; ignore::telegram.utils.deprecate.TelegramDeprecationWarning
markers = dev: If you want to test a specific test, use this
markers =
dev: If you want to test a specific test, use this
no_req
req
asyncio_mode = auto
[coverage:run]
+5
View File
@@ -37,6 +37,8 @@ __all__ = ( # Keep this alphabetically ordered
"BotCommandScopeChatAdministrators",
"BotCommandScopeChatMember",
"BotCommandScopeDefault",
"BotDescription",
"BotShortDescription",
"CallbackGame",
"CallbackQuery",
"Chat",
@@ -114,6 +116,7 @@ __all__ = ( # Keep this alphabetically ordered
"InputMediaPhoto",
"InputMediaVideo",
"InputMessageContent",
"InputSticker",
"InputTextMessageContent",
"InputVenueMessageContent",
"Invoice",
@@ -200,6 +203,7 @@ from ._botcommandscope import (
BotCommandScopeChatMember,
BotCommandScopeDefault,
)
from ._botdescription import BotDescription, BotShortDescription
from ._callbackquery import CallbackQuery
from ._chat import Chat
from ._chatadministratorrights import ChatAdministratorRights
@@ -234,6 +238,7 @@ from ._files.inputmedia import (
InputMediaPhoto,
InputMediaVideo,
)
from ._files.inputsticker import InputSticker
from ._files.location import Location
from ._files.photosize import PhotoSize
from ._files.sticker import MaskPosition, Sticker, StickerSet
+931 -50
View File
File diff suppressed because it is too large Load Diff
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains two objects that represent a Telegram bots (short) description."""
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class BotDescription(TelegramObject):
"""This object represents the bot's description.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`description` is equal.
.. versionadded:: 20.2
Args:
description (:obj:`str`): The bot's description.
Attributes:
description (:obj:`str`): The bot's description.
"""
__slots__ = ("description",)
def __init__(self, description: str, *, api_kwargs: JSONDict = None):
super().__init__(api_kwargs=api_kwargs)
self.description = description
self._id_attrs = (self.description,)
self._freeze()
class BotShortDescription(TelegramObject):
"""This object represents the bot's short description.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`short_description` is equal.
.. versionadded:: 20.2
Args:
short_description (:obj:`str`): The bot's short description.
Attributes:
short_description (:obj:`str`): The bot's short description.
"""
__slots__ = ("short_description",)
def __init__(self, short_description: str, *, api_kwargs: JSONDict = None):
super().__init__(api_kwargs=api_kwargs)
self.short_description = short_description
self._id_attrs = (self.short_description,)
self._freeze()
+12
View File
@@ -1532,6 +1532,7 @@ class Chat(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1572,6 +1573,7 @@ class Chat(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
thumbnail=thumbnail,
)
async def send_document(
@@ -1588,6 +1590,7 @@ class Chat(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1620,6 +1623,7 @@ class Chat(TelegramObject):
pool_timeout=pool_timeout,
parse_mode=parse_mode,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
disable_content_type_detection=disable_content_type_detection,
allow_sending_without_reply=allow_sending_without_reply,
@@ -1875,6 +1879,7 @@ class Chat(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1916,6 +1921,7 @@ class Chat(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
)
async def send_sticker(
@@ -1927,6 +1933,7 @@ class Chat(TelegramObject):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
emoji: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -1958,6 +1965,7 @@ class Chat(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
emoji=emoji,
)
async def send_venue(
@@ -2036,6 +2044,7 @@ class Chat(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2071,6 +2080,7 @@ class Chat(TelegramObject):
parse_mode=parse_mode,
supports_streaming=supports_streaming,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
allow_sending_without_reply=allow_sending_without_reply,
caption_entities=caption_entities,
@@ -2092,6 +2102,7 @@ class Chat(TelegramObject):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2123,6 +2134,7 @@ class Chat(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
allow_sending_without_reply=allow_sending_without_reply,
filename=filename,
+44 -6
View File
@@ -22,6 +22,10 @@ from typing import TYPE_CHECKING, Optional, Type, TypeVar
from telegram._files._basemedium import _BaseMedium
from telegram._files.photosize import PhotoSize
from telegram._utils.types import JSONDict
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
if TYPE_CHECKING:
from telegram import Bot
@@ -46,18 +50,25 @@ class _BaseThumbedMedium(_BaseMedium):
file_size (:obj:`int`, optional): File size.
thumb (:class:`telegram.PhotoSize`, optional): Thumbnail as defined by sender.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
thumbnail (:class:`telegram.PhotoSize`, optional): Thumbnail as defined by sender.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): File identifier.
file_unique_id (:obj:`str`): Unique identifier for this file, which
is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file.
file_size (:obj:`int`): Optional. File size.
thumb (:class:`telegram.PhotoSize`): Optional. Thumbnail as defined by sender.
thumbnail (:class:`telegram.PhotoSize`): Optional. Thumbnail as defined by sender.
.. versionadded:: 20.2
"""
__slots__ = ("thumb",)
__slots__ = ("thumbnail",)
def __init__(
self,
@@ -65,6 +76,7 @@ class _BaseThumbedMedium(_BaseMedium):
file_unique_id: str,
file_size: int = None,
thumb: PhotoSize = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -74,7 +86,27 @@ class _BaseThumbedMedium(_BaseMedium):
file_size=file_size,
api_kwargs=api_kwargs,
)
self.thumb: Optional[PhotoSize] = thumb
self.thumbnail: Optional[PhotoSize] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb,
new_arg=thumbnail,
deprecated_arg_name="thumb",
new_arg_name="thumbnail",
bot_api_version="6.6",
stacklevel=4,
)
@property
def thumb(self) -> Optional[PhotoSize]:
""":class:`telegram.PhotoSize`: Optional. Thumbnail as defined by sender.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb", new_attr_name="thumbnail", bot_api_version="6.6"
)
return self.thumbnail
@classmethod
def de_json(
@@ -87,7 +119,13 @@ class _BaseThumbedMedium(_BaseMedium):
return None
# In case this wasn't already done by the subclass
if not isinstance(data.get("thumb"), PhotoSize):
data["thumb"] = PhotoSize.de_json(data.get("thumb"), bot)
if not isinstance(data.get("thumbnail"), PhotoSize):
data["thumbnail"] = PhotoSize.de_json(data.get("thumbnail"), bot)
return super().de_json(data=data, bot=bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
# Let's filter it out to speed up the de-json process
if data.get("thumb") is not None:
api_kwargs["thumb"] = data.pop("thumb")
return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs)
+12 -1
View File
@@ -40,9 +40,16 @@ class Animation(_BaseThumbedMedium):
height (:obj:`int`): Video height as defined by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`, optional): Animation thumbnail as defined by sender.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
file_name (:obj:`str`, optional): Original animation filename as defined by sender.
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender.
file_size (:obj:`int`, optional): File size in bytes.
thumbnail (:class:`telegram.PhotoSize`, optional): Animation thumbnail as defined by
sender.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
@@ -53,11 +60,13 @@ class Animation(_BaseThumbedMedium):
width (:obj:`int`): Video width as defined by sender.
height (:obj:`int`): Video height as defined by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`): Optional. Animation thumbnail as defined by sender.
file_name (:obj:`str`): Optional. Original animation filename as defined by sender.
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender.
file_size (:obj:`int`): Optional. File size in bytes.
thumbnail (:class:`telegram.PhotoSize`): Optional. Animation thumbnail as defined by
sender.
.. versionadded:: 20.2
"""
@@ -74,6 +83,7 @@ class Animation(_BaseThumbedMedium):
file_name: str = None,
mime_type: str = None,
file_size: int = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -83,6 +93,7 @@ class Animation(_BaseThumbedMedium):
file_size=file_size,
thumb=thumb,
api_kwargs=api_kwargs,
thumbnail=thumbnail,
)
with self._unfrozen():
# Required
+12 -1
View File
@@ -46,6 +46,13 @@ class Audio(_BaseThumbedMedium):
thumb (:class:`telegram.PhotoSize`, optional): Thumbnail of the album cover to
which the music file belongs.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
thumbnail (:class:`telegram.PhotoSize`, optional): Thumbnail of the album cover to
which the music file belongs.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
or reuse the file.
@@ -58,9 +65,11 @@ class Audio(_BaseThumbedMedium):
file_name (:obj:`str`): Optional. Original filename as defined by sender.
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender.
file_size (:obj:`int`): Optional. File size in bytes.
thumb (:class:`telegram.PhotoSize`): Optional. Thumbnail of the album cover to
thumbnail (:class:`telegram.PhotoSize`): Optional. Thumbnail of the album cover to
which the music file belongs.
.. versionadded:: 20.2
"""
@@ -77,6 +86,7 @@ class Audio(_BaseThumbedMedium):
file_size: int = None,
thumb: PhotoSize = None,
file_name: str = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -85,6 +95,7 @@ class Audio(_BaseThumbedMedium):
file_unique_id=file_unique_id,
file_size=file_size,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
)
with self._unfrozen():
+11 -1
View File
@@ -37,19 +37,27 @@ class Document(_BaseThumbedMedium):
file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be
the same over time and for different bots. Can't be used to download or reuse the file.
thumb (:class:`telegram.PhotoSize`, optional): Document thumbnail as defined by sender.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
file_name (:obj:`str`, optional): Original filename as defined by sender.
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender.
file_size (:obj:`int`, optional): File size in bytes.
thumbnail (:class:`telegram.PhotoSize`, optional): Document thumbnail as defined by sender.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
or reuse the file.
file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be
the same over time and for different bots. Can't be used to download or reuse the file.
thumb (:class:`telegram.PhotoSize`): Optional. Document thumbnail as defined by sender.
file_name (:obj:`str`): Optional. Original filename as defined by sender.
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender.
file_size (:obj:`int`): Optional. File size in bytes.
thumbnail (:class:`telegram.PhotoSize`): Optional. Document thumbnail as defined by sender.
.. versionadded:: 20.2
"""
@@ -63,6 +71,7 @@ class Document(_BaseThumbedMedium):
file_name: str = None,
mime_type: str = None,
file_size: int = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -71,6 +80,7 @@ class Document(_BaseThumbedMedium):
file_unique_id=file_unique_id,
file_size=file_size,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
)
with self._unfrozen():
+123 -12
View File
@@ -31,6 +31,10 @@ from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.files import parse_file_input
from telegram._utils.types import FileInput, JSONDict, ODVInput
from telegram._utils.warnings_transition import (
warn_about_deprecated_attr_in_property,
warn_about_thumb_return_thumbnail,
)
from telegram.constants import InputMediaType
MediaType = Union[Animation, Audio, Document, PhotoSize, Video]
@@ -138,6 +142,9 @@ class InputMediaAnimation(InputMedia):
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
caption (:obj:`str`, optional): Caption of the animation to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters
after entities parsing.
@@ -154,11 +161,14 @@ class InputMediaAnimation(InputMedia):
with a spoiler animation.
.. versionadded:: 20.0
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.ANIMATION`.
media (:obj:`str` | :class:`telegram.InputFile`): Animation to send.
thumb (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
caption (:obj:`str`): Optional. Caption of the animation to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters
after entities parsing.
@@ -176,9 +186,12 @@ class InputMediaAnimation(InputMedia):
spoiler animation.
.. versionadded:: 20.0
thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
.. versionadded:: 20.2
"""
__slots__ = ("duration", "height", "thumb", "width", "has_spoiler")
__slots__ = ("duration", "height", "width", "has_spoiler", "thumbnail")
def __init__(
self,
@@ -192,6 +205,7 @@ class InputMediaAnimation(InputMedia):
caption_entities: Sequence[MessageEntity] = None,
filename: str = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
api_kwargs: JSONDict = None,
):
@@ -205,6 +219,7 @@ class InputMediaAnimation(InputMedia):
# things to work in local mode.
media = parse_file_input(media, filename=filename, attach=True, local_mode=True)
thumbnail = warn_about_thumb_return_thumbnail(deprecated_arg=thumb, new_arg=thumbnail)
super().__init__(
InputMediaType.ANIMATION,
media,
@@ -214,12 +229,26 @@ class InputMediaAnimation(InputMedia):
api_kwargs=api_kwargs,
)
with self._unfrozen():
self.thumb: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumb)
self.thumbnail: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumbnail)
self.width: Optional[int] = width
self.height: Optional[int] = height
self.duration: Optional[int] = duration
self.has_spoiler: Optional[bool] = has_spoiler
@property
def thumb(self) -> Optional[Union[str, InputFile]]:
""":class:`telegram.InputFile`: Optional. |thumbdocstringbase|
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb",
new_attr_name="thumbnail",
bot_api_version="6.6",
)
return self.thumbnail
class InputMediaPhoto(InputMedia):
"""Represents a photo to be sent.
@@ -343,10 +372,17 @@ class InputMediaVideo(InputMedia):
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
has_spoiler (:obj:`bool`, optional): Pass :obj:`True`, if the video needs to be covered
with a spoiler animation.
.. versionadded:: 20.0
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.VIDEO`.
@@ -366,14 +402,23 @@ class InputMediaVideo(InputMedia):
duration (:obj:`int`): Optional. Video duration in seconds.
supports_streaming (:obj:`bool`): Optional. :obj:`True`, if the uploaded video is
suitable for streaming.
thumb (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
has_spoiler (:obj:`bool`): Optional. :obj:`True`, if the video is covered with a
spoiler animation.
.. versionadded:: 20.0
thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
.. versionadded:: 20.2
"""
__slots__ = ("duration", "height", "thumb", "supports_streaming", "width", "has_spoiler")
__slots__ = (
"duration",
"height",
"supports_streaming",
"width",
"has_spoiler",
"thumbnail",
)
def __init__(
self,
@@ -388,6 +433,7 @@ class InputMediaVideo(InputMedia):
caption_entities: Sequence[MessageEntity] = None,
filename: str = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
api_kwargs: JSONDict = None,
):
@@ -401,6 +447,7 @@ class InputMediaVideo(InputMedia):
# things to work in local mode.
media = parse_file_input(media, filename=filename, attach=True, local_mode=True)
thumbnail = warn_about_thumb_return_thumbnail(deprecated_arg=thumb, new_arg=thumbnail)
super().__init__(
InputMediaType.VIDEO,
media,
@@ -413,10 +460,24 @@ class InputMediaVideo(InputMedia):
self.width: Optional[int] = width
self.height: Optional[int] = height
self.duration: Optional[int] = duration
self.thumb: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumb)
self.thumbnail: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumbnail)
self.supports_streaming: Optional[bool] = supports_streaming
self.has_spoiler: Optional[bool] = has_spoiler
@property
def thumb(self) -> Optional[Union[str, InputFile]]:
""":class:`telegram.InputFile`: Optional. |thumbdocstringbase|
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb",
new_attr_name="thumbnail",
bot_api_version="6.6",
)
return self.thumbnail
class InputMediaAudio(InputMedia):
"""Represents an audio file to be treated as music to be sent.
@@ -459,6 +520,13 @@ class InputMediaAudio(InputMedia):
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.AUDIO`.
media (:obj:`str` | :class:`telegram.InputFile`): Audio file to send.
@@ -476,11 +544,13 @@ class InputMediaAudio(InputMedia):
performer (:obj:`str`): Optional. Performer of the audio as defined by sender or by audio
tags.
title (:obj:`str`): Optional. Title of the audio as defined by sender or by audio tags.
thumb (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
.. versionadded:: 20.2
"""
__slots__ = ("duration", "performer", "thumb", "title")
__slots__ = ("duration", "performer", "title", "thumbnail")
def __init__(
self,
@@ -493,6 +563,7 @@ class InputMediaAudio(InputMedia):
title: str = None,
caption_entities: Sequence[MessageEntity] = None,
filename: str = None,
thumbnail: FileInput = None,
*,
api_kwargs: JSONDict = None,
):
@@ -506,6 +577,7 @@ class InputMediaAudio(InputMedia):
# things to work in local mode.
media = parse_file_input(media, filename=filename, attach=True, local_mode=True)
thumbnail = warn_about_thumb_return_thumbnail(deprecated_arg=thumb, new_arg=thumbnail)
super().__init__(
InputMediaType.AUDIO,
media,
@@ -515,11 +587,25 @@ class InputMediaAudio(InputMedia):
api_kwargs=api_kwargs,
)
with self._unfrozen():
self.thumb: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumb)
self.thumbnail: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumbnail)
self.duration: Optional[int] = duration
self.title: Optional[str] = title
self.performer: Optional[str] = performer
@property
def thumb(self) -> Optional[Union[str, InputFile]]:
""":class:`telegram.InputFile`: Optional. |thumbdocstringbase|
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb",
new_attr_name="thumbnail",
bot_api_version="6.6",
)
return self.thumbnail
class InputMediaDocument(InputMedia):
"""Represents a general file to be sent.
@@ -552,9 +638,16 @@ class InputMediaDocument(InputMedia):
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
disable_content_type_detection (:obj:`bool`, optional): Disables automatic server-side
content type detection for files uploaded using multipart/form-data. Always
:obj:`True`, if the document is sent as part of an album.
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.DOCUMENT`.
@@ -569,14 +662,15 @@ class InputMediaDocument(InputMedia):
* |tupleclassattrs|
* |alwaystuple|
thumb (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
disable_content_type_detection (:obj:`bool`): Optional. Disables automatic server-side
content type detection for files uploaded using multipart/form-data. Always
:obj:`True`, if the document is sent as part of an album.
thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
.. versionadded:: 20.2
"""
__slots__ = ("disable_content_type_detection", "thumb")
__slots__ = ("disable_content_type_detection", "thumbnail")
def __init__(
self,
@@ -587,12 +681,15 @@ class InputMediaDocument(InputMedia):
disable_content_type_detection: bool = None,
caption_entities: Sequence[MessageEntity] = None,
filename: str = None,
thumbnail: FileInput = None,
*,
api_kwargs: JSONDict = None,
):
# We use local_mode=True because we don't have access to the actual setting and want
# things to work in local mode.
media = parse_file_input(media, Document, filename=filename, attach=True, local_mode=True)
thumbnail = warn_about_thumb_return_thumbnail(deprecated_arg=thumb, new_arg=thumbnail)
super().__init__(
InputMediaType.DOCUMENT,
media,
@@ -602,5 +699,19 @@ class InputMediaDocument(InputMedia):
api_kwargs=api_kwargs,
)
with self._unfrozen():
self.thumb: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumb)
self.thumbnail: Optional[Union[str, InputFile]] = self._parse_thumb_input(thumbnail)
self.disable_content_type_detection: Optional[bool] = disable_content_type_detection
@property
def thumb(self) -> Optional[Union[str, InputFile]]:
""":class:`telegram.InputFile`: Optional. |thumbdocstringbase|
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb",
new_attr_name="thumbnail",
bot_api_version="6.6",
)
return self.thumbnail
+95
View File
@@ -0,0 +1,95 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram InputSticker."""
from typing import Optional, Sequence, Tuple, Union
from telegram._files.inputfile import InputFile
from telegram._files.sticker import MaskPosition
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.files import parse_file_input
from telegram._utils.types import FileInput, JSONDict
class InputSticker(TelegramObject):
"""
This object describes a sticker to be added to a sticker set.
.. versionadded:: 20.2
Args:
sticker (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path`): The
added sticker. |uploadinputnopath| Animated and video stickers can't be uploaded via
HTTP URL.
emoji_list (Sequence[:obj:`str`]): Sequence of
:tg-const:`telegram.constants.StickerLimit.MIN_STICKER_EMOJI` -
:tg-const:`telegram.constants.StickerLimit.MAX_STICKER_EMOJI` emoji associated with the
sticker.
mask_position (:obj:`telegram.MaskPosition`, optional): Position where the mask should be
placed on faces. For ":tg-const:`telegram.constants.StickerType.MASK`" stickers only.
keywords (Sequence[:obj:`str`], optional): Sequence of
0-:tg-const:`telegram.constants.StickerLimit.MAX_SEARCH_KEYWORDS` search keywords
for the sticker with the total length of up to
:tg-const:`telegram.constants.StickerLimit.MAX_KEYWORD_LENGTH` characters. For
":tg-const:`telegram.constants.StickerType.REGULAR`" and
":tg-const:`telegram.constants.StickerType.CUSTOM_EMOJI`" stickers only.
Attributes:
sticker (:obj:`str` | :class:`telegram.InputFile`): The added sticker.
emoji_list (Tuple[:obj:`str`]): Tuple of
:tg-const:`telegram.constants.StickerLimit.MIN_STICKER_EMOJI` -
:tg-const:`telegram.constants.StickerLimit.MAX_STICKER_EMOJI` emoji associated with the
sticker.
mask_position (:obj:`telegram.MaskPosition`): Optional. Position where the mask should be
placed on faces. For ":tg-const:`telegram.constants.StickerType.MASK`" stickers only.
keywords (Tuple[:obj:`str`]): Optional. Tuple of
0-:tg-const:`telegram.constants.StickerLimit.MAX_SEARCH_KEYWORDS` search keywords
for the sticker with the total length of up to
:tg-const:`telegram.constants.StickerLimit.MAX_KEYWORD_LENGTH` characters. For
":tg-const:`telegram.constants.StickerType.REGULAR`" and
":tg-const:`telegram.constants.StickerType.CUSTOM_EMOJI`" stickers only.
"""
__slots__ = ("sticker", "emoji_list", "mask_position", "keywords")
def __init__(
self,
sticker: FileInput,
emoji_list: Sequence[str],
mask_position: MaskPosition = None,
keywords: Sequence[str] = None,
*,
api_kwargs: JSONDict = None,
):
super().__init__(api_kwargs=api_kwargs)
# We use local_mode=True because we don't have access to the actual setting and want
# things to work in local mode.
self.sticker: Union[str, InputFile] = parse_file_input(
sticker,
local_mode=True,
attach=True,
)
self.emoji_list: Tuple[str, ...] = parse_sequence_arg(emoji_list)
self.mask_position: Optional[MaskPosition] = mask_position
self.keywords: Tuple[str, ...] = parse_sequence_arg(keywords)
self._freeze()
+73 -12
View File
@@ -26,6 +26,10 @@ from telegram._files.photosize import PhotoSize
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
from telegram._utils.warnings_transition import (
warn_about_deprecated_attr_in_property,
warn_about_thumb_return_thumbnail,
)
if TYPE_CHECKING:
from telegram import Bot
@@ -61,6 +65,9 @@ class Sticker(_BaseThumbedMedium):
.. versionadded:: 20.0
thumb (:class:`telegram.PhotoSize`, optional): Sticker thumbnail in the ``.WEBP`` or
``.JPG`` format.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
emoji (:obj:`str`, optional): Emoji associated with the sticker
set_name (:obj:`str`, optional): Name of the sticker set to which the sticker belongs.
mask_position (:class:`telegram.MaskPosition`, optional): For mask stickers, the position
@@ -75,6 +82,15 @@ class Sticker(_BaseThumbedMedium):
custom emoji.
.. versionadded:: 20.0
thumbnail (:class:`telegram.PhotoSize`, optional): Sticker thumbnail in the ``.WEBP`` or
``.JPG`` format.
.. versionadded:: 20.2
needs_repainting (:obj:`bool`, optional): :obj:`True`, if the sticker must be repainted to
a text color in messages, the color of the Telegram Premium badge in emoji status,
white color on chat photos, or another appropriate color in other places.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
@@ -93,8 +109,6 @@ class Sticker(_BaseThumbedMedium):
format, which is determined by the fields :attr:`is_animated` and :attr:`is_video`.
.. versionadded:: 20.0
thumb (:class:`telegram.PhotoSize`): Optional. Sticker thumbnail in the ``.WEBP`` or
``.JPG`` format.
emoji (:obj:`str`): Optional. Emoji associated with the sticker.
set_name (:obj:`str`): Optional. Name of the sticker set to which the sticker belongs.
mask_position (:class:`telegram.MaskPosition`): Optional. For mask stickers, the position
@@ -109,6 +123,15 @@ class Sticker(_BaseThumbedMedium):
custom emoji.
.. versionadded:: 20.0
thumbnail (:class:`telegram.PhotoSize`): Optional. Sticker thumbnail in the ``.WEBP`` or
``.JPG`` format.
.. versionadded:: 20.2
needs_repainting (:obj:`bool`): Optional. :obj:`True`, if the sticker must be repainted to
a text color in messages, the color of the Telegram Premium badge in emoji status,
white color on chat photos, or another appropriate color in other places.
.. versionadded:: 20.2
"""
__slots__ = (
@@ -122,6 +145,7 @@ class Sticker(_BaseThumbedMedium):
"premium_animation",
"type",
"custom_emoji_id",
"needs_repainting",
)
def __init__(
@@ -140,6 +164,8 @@ class Sticker(_BaseThumbedMedium):
mask_position: "MaskPosition" = None,
premium_animation: "File" = None,
custom_emoji_id: str = None,
thumbnail: PhotoSize = None,
needs_repainting: bool = None,
*,
api_kwargs: JSONDict = None,
):
@@ -148,6 +174,7 @@ class Sticker(_BaseThumbedMedium):
file_unique_id=file_unique_id,
file_size=file_size,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
)
with self._unfrozen():
@@ -163,6 +190,7 @@ class Sticker(_BaseThumbedMedium):
self.mask_position: Optional[MaskPosition] = mask_position
self.premium_animation: Optional[File] = premium_animation
self.custom_emoji_id: Optional[str] = custom_emoji_id
self.needs_repainting: Optional[bool] = needs_repainting
REGULAR: ClassVar[str] = constants.StickerType.REGULAR
""":const:`telegram.constants.StickerType.REGULAR`"""
@@ -179,11 +207,17 @@ class Sticker(_BaseThumbedMedium):
if not data:
return None
data["thumb"] = PhotoSize.de_json(data.get("thumb"), bot)
data["thumbnail"] = PhotoSize.de_json(data.get("thumbnail"), bot)
data["mask_position"] = MaskPosition.de_json(data.get("mask_position"), bot)
data["premium_animation"] = File.de_json(data.get("premium_animation"), bot)
return super().de_json(data=data, bot=bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
# Let's filter it out to speed up the de-json process
if data.get("thumb") is not None:
api_kwargs["thumb"] = data.pop("thumb")
return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs)
class StickerSet(TelegramObject):
@@ -220,6 +254,13 @@ class StickerSet(TelegramObject):
thumb (:class:`telegram.PhotoSize`, optional): Sticker set thumbnail in the ``.WEBP``,
``.TGS``, or ``.WEBM`` format.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
thumbnail (:class:`telegram.PhotoSize`, optional): Sticker set thumbnail in the ``.WEBP``,
``.TGS``, or ``.WEBM`` format.
.. versionadded:: 20.2
Attributes:
name (:obj:`str`): Sticker set name.
title (:obj:`str`): Sticker set title.
@@ -237,9 +278,10 @@ class StickerSet(TelegramObject):
:attr:`telegram.Sticker.CUSTOM_EMOJI`.
.. versionadded:: 20.0
thumb (:class:`telegram.PhotoSize`): Optional. Sticker set thumbnail in the ``.WEBP``,
thumbnail (:class:`telegram.PhotoSize`): Optional. Sticker set thumbnail in the ``.WEBP``,
``.TGS``, or ``.WEBM`` format.
.. versionadded:: 20.2
"""
__slots__ = (
@@ -247,7 +289,7 @@ class StickerSet(TelegramObject):
"is_video",
"name",
"stickers",
"thumb",
"thumbnail",
"title",
"sticker_type",
)
@@ -261,6 +303,7 @@ class StickerSet(TelegramObject):
is_video: bool,
sticker_type: str,
thumb: PhotoSize = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -272,26 +315,44 @@ class StickerSet(TelegramObject):
self.stickers: Tuple[Sticker, ...] = parse_sequence_arg(stickers)
self.sticker_type: str = sticker_type
# Optional
self.thumb: Optional[PhotoSize] = thumb
self.thumbnail: Optional[PhotoSize] = warn_about_thumb_return_thumbnail(
deprecated_arg=thumb, new_arg=thumbnail
)
self._id_attrs = (self.name,)
self._freeze()
@property
def thumb(self) -> Optional[PhotoSize]:
""":class:`telegram.PhotoSize`: Optional. Sticker set thumbnail in the ``.WEBP``,
``.TGS``, or ``.WEBM`` format.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb",
new_attr_name="thumbnail",
bot_api_version="6.6",
)
return self.thumbnail
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["StickerSet"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
if not data:
return None
data["thumb"] = PhotoSize.de_json(data.get("thumb"), bot)
data["thumbnail"] = PhotoSize.de_json(data.get("thumbnail"), bot)
data["stickers"] = Sticker.de_list(data.get("stickers"), bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
# Let's filter it out to speed up the de-json process
if "contains_masks" in data:
api_kwargs["contains_masks"] = data.pop("contains_masks")
# These are deprecated fields that TG still returns for backwards compatibility
# Let's filter them out to speed up the de-json process
for deprecated_field in ("contains_masks", "thumb"):
if deprecated_field in data:
api_kwargs[deprecated_field] = data.pop(deprecated_field)
return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs)
+10 -1
View File
@@ -40,9 +40,15 @@ class Video(_BaseThumbedMedium):
height (:obj:`int`): Video height as defined by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`, optional): Video thumbnail.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
file_name (:obj:`str`, optional): Original filename as defined by sender.
mime_type (:obj:`str`, optional): MIME type of a file as defined by sender.
file_size (:obj:`int`, optional): File size in bytes.
thumbnail (:class:`telegram.PhotoSize`, optional): Video thumbnail.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
@@ -53,11 +59,12 @@ class Video(_BaseThumbedMedium):
width (:obj:`int`): Video width as defined by sender.
height (:obj:`int`): Video height as defined by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`): Optional. Video thumbnail.
file_name (:obj:`str`): Optional. Original filename as defined by sender.
mime_type (:obj:`str`): Optional. MIME type of a file as defined by sender.
file_size (:obj:`int`): Optional. File size in bytes.
thumbnail (:class:`telegram.PhotoSize`): Optional. Video thumbnail.
.. versionadded:: 20.2
"""
__slots__ = ("duration", "file_name", "height", "mime_type", "width")
@@ -73,6 +80,7 @@ class Video(_BaseThumbedMedium):
mime_type: str = None,
file_size: int = None,
file_name: str = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -81,6 +89,7 @@ class Video(_BaseThumbedMedium):
file_unique_id=file_unique_id,
file_size=file_size,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
)
with self._unfrozen():
+11 -1
View File
@@ -39,7 +39,13 @@ class VideoNote(_BaseThumbedMedium):
by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`, optional): Video thumbnail.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail`.
file_size (:obj:`int`, optional): File size in bytes.
thumbnail (:class:`telegram.PhotoSize`, optional): Video thumbnail.
.. versionadded:: 20.2
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
@@ -50,8 +56,10 @@ class VideoNote(_BaseThumbedMedium):
length (:obj:`int`): Video width and height (diameter of the video message) as defined
by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender.
thumb (:class:`telegram.PhotoSize`): Optional. Video thumbnail.
file_size (:obj:`int`): Optional. File size in bytes.
thumbnail (:class:`telegram.PhotoSize`): Optional. Video thumbnail.
.. versionadded:: 20.2
"""
@@ -65,6 +73,7 @@ class VideoNote(_BaseThumbedMedium):
duration: int,
thumb: PhotoSize = None,
file_size: int = None,
thumbnail: PhotoSize = None,
*,
api_kwargs: JSONDict = None,
):
@@ -73,6 +82,7 @@ class VideoNote(_BaseThumbedMedium):
file_unique_id=file_unique_id,
file_size=file_size,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
)
with self._unfrozen():
+100 -9
View File
@@ -23,6 +23,10 @@ from typing import TYPE_CHECKING, Optional
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._inline.inlinequeryresult import InlineQueryResult
from telegram._utils.types import JSONDict
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -49,9 +53,27 @@ class InlineQueryResultArticle(InlineQueryResult):
in the message.
description (:obj:`str`, optional): Short description of the result.
thumb_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
thumb_width (:obj:`int`, optional): Thumbnail width.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_width`.
thumb_height (:obj:`int`, optional): Thumbnail height.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_height`.
thumbnail_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`, optional): Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`, optional): Thumbnail height.
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.ARTICLE`.
id (:obj:`str`): Unique identifier for this result,
@@ -66,22 +88,28 @@ class InlineQueryResultArticle(InlineQueryResult):
hide_url (:obj:`bool`): Optional. Pass :obj:`True`, if you don't want the URL to be shown
in the message.
description (:obj:`str`): Optional. Short description of the result.
thumb_url (:obj:`str`): Optional. Url of the thumbnail for the result.
thumb_width (:obj:`int`): Optional. Thumbnail width.
thumb_height (:obj:`int`): Optional. Thumbnail height.
thumbnail_url (:obj:`str`): Optional. Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`): Optional. Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`): Optional. Thumbnail height.
.. versionadded:: 20.2
"""
__slots__ = (
"reply_markup",
"thumb_width",
"thumb_height",
"hide_url",
"url",
"title",
"description",
"input_message_content",
"thumb_url",
"thumbnail_width",
"thumbnail_height",
"thumbnail_url",
)
def __init__(
@@ -96,6 +124,9 @@ class InlineQueryResultArticle(InlineQueryResult):
thumb_url: str = None,
thumb_width: int = None,
thumb_height: int = None,
thumbnail_url: str = None,
thumbnail_width: int = None,
thumbnail_height: int = None,
*,
api_kwargs: JSONDict = None,
):
@@ -110,6 +141,66 @@ class InlineQueryResultArticle(InlineQueryResult):
self.url: Optional[str] = url
self.hide_url: Optional[bool] = hide_url
self.description: Optional[str] = description
self.thumb_url: Optional[str] = thumb_url
self.thumb_width: Optional[int] = thumb_width
self.thumb_height: Optional[int] = thumb_height
self.thumbnail_url: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
self.thumbnail_width: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_width,
new_arg=thumbnail_width,
deprecated_arg_name="thumb_width",
new_arg_name="thumbnail_width",
bot_api_version="6.6",
)
self.thumbnail_height: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_height,
new_arg=thumbnail_height,
deprecated_arg_name="thumb_height",
new_arg_name="thumbnail_height",
bot_api_version="6.6",
)
@property
def thumb_url(self) -> Optional[str]:
""":obj:`str`: Optional. Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_width(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail width.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_width`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_width",
new_attr_name="thumbnail_width",
bot_api_version="6.6",
)
return self.thumbnail_width
@property
def thumb_height(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail height.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_height`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_height",
new_attr_name="thumbnail_height",
bot_api_version="6.6",
)
return self.thumbnail_height
+100 -9
View File
@@ -23,6 +23,10 @@ from typing import TYPE_CHECKING, Optional
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._inline.inlinequeryresult import InlineQueryResult
from telegram._utils.types import JSONDict
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -49,9 +53,27 @@ class InlineQueryResultContact(InlineQueryResult):
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the contact.
thumb_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
thumb_width (:obj:`int`, optional): Thumbnail width.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_width`.
thumb_height (:obj:`int`, optional): Thumbnail height.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_height`.
thumbnail_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`, optional): Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`, optional): Thumbnail height.
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.CONTACT`.
id (:obj:`str`): Unique identifier for this result,
@@ -66,22 +88,28 @@ class InlineQueryResultContact(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the contact.
thumb_url (:obj:`str`): Optional. Url of the thumbnail for the result.
thumb_width (:obj:`int`): Optional. Thumbnail width.
thumb_height (:obj:`int`): Optional. Thumbnail height.
thumbnail_url (:obj:`str`): Optional. Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`): Optional. Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`): Optional. Thumbnail height.
.. versionadded:: 20.2
"""
__slots__ = (
"reply_markup",
"thumb_width",
"thumb_height",
"thumbnail_width",
"thumbnail_height",
"vcard",
"first_name",
"last_name",
"phone_number",
"input_message_content",
"thumb_url",
"thumbnail_url",
)
def __init__(
@@ -96,6 +124,9 @@ class InlineQueryResultContact(InlineQueryResult):
thumb_width: int = None,
thumb_height: int = None,
vcard: str = None,
thumbnail_url: str = None,
thumbnail_width: int = None,
thumbnail_height: int = None,
*,
api_kwargs: JSONDict = None,
):
@@ -110,6 +141,66 @@ class InlineQueryResultContact(InlineQueryResult):
self.vcard: Optional[str] = vcard
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumb_url: Optional[str] = thumb_url
self.thumb_width: Optional[int] = thumb_width
self.thumb_height: Optional[int] = thumb_height
self.thumbnail_url: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
self.thumbnail_width: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_width,
new_arg=thumbnail_width,
deprecated_arg_name="thumb_width",
new_arg_name="thumbnail_width",
bot_api_version="6.6",
)
self.thumbnail_height: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_height,
new_arg=thumbnail_height,
deprecated_arg_name="thumb_height",
new_arg_name="thumbnail_height",
bot_api_version="6.6",
)
@property
def thumb_url(self) -> Optional[str]:
""":obj:`str`: Optional. Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_width(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail width.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_width`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_width",
new_attr_name="thumbnail_width",
bot_api_version="6.6",
)
return self.thumbnail_width
@property
def thumb_height(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail height.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_height`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_height",
new_attr_name="thumbnail_height",
bot_api_version="6.6",
)
return self.thumbnail_height
+101 -9
View File
@@ -25,6 +25,10 @@ from telegram._messageentity import MessageEntity
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -63,9 +67,28 @@ class InlineQueryResultDocument(InlineQueryResult):
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the file.
thumb_url (:obj:`str`, optional): URL of the thumbnail (JPEG only) for the file.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
thumb_width (:obj:`int`, optional): Thumbnail width.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_width`.
thumb_height (:obj:`int`, optional): Thumbnail height.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_height`.
thumbnail_url (:obj:`str`, optional): URL of the thumbnail (JPEG only) for the file.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`, optional): Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`, optional): Thumbnail height.
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.DOCUMENT`.
id (:obj:`str`): Unique identifier for this result,
@@ -90,9 +113,15 @@ class InlineQueryResultDocument(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the file.
thumb_url (:obj:`str`): Optional. URL of the thumbnail (JPEG only) for the file.
thumb_width (:obj:`int`): Optional. Thumbnail width.
thumb_height (:obj:`int`): Optional. Thumbnail height.
thumbnail_url (:obj:`str`): Optional. URL of the thumbnail (JPEG only) for the file.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`): Optional. Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`): Optional. Thumbnail height.
.. versionadded:: 20.2
"""
@@ -100,14 +129,14 @@ class InlineQueryResultDocument(InlineQueryResult):
"reply_markup",
"caption_entities",
"document_url",
"thumb_width",
"thumb_height",
"thumbnail_width",
"thumbnail_height",
"caption",
"title",
"description",
"parse_mode",
"mime_type",
"thumb_url",
"thumbnail_url",
"input_message_content",
)
@@ -126,6 +155,9 @@ class InlineQueryResultDocument(InlineQueryResult):
thumb_height: int = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Sequence[MessageEntity] = None,
thumbnail_url: str = None,
thumbnail_width: int = None,
thumbnail_height: int = None,
*,
api_kwargs: JSONDict = None,
):
@@ -143,6 +175,66 @@ class InlineQueryResultDocument(InlineQueryResult):
self.description: Optional[str] = description
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumb_url: Optional[str] = thumb_url
self.thumb_width: Optional[int] = thumb_width
self.thumb_height: Optional[int] = thumb_height
self.thumbnail_url: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
self.thumbnail_width: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_width,
new_arg=thumbnail_width,
deprecated_arg_name="thumb_width",
new_arg_name="thumbnail_width",
bot_api_version="6.6",
)
self.thumbnail_height: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_height,
new_arg=thumbnail_height,
deprecated_arg_name="thumb_height",
new_arg_name="thumbnail_height",
bot_api_version="6.6",
)
@property
def thumb_url(self) -> Optional[str]:
""":obj:`str`: Optional. URL of the thumbnail (JPEG only) for the file.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_width(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail width.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_width`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_width",
new_attr_name="thumbnail_width",
bot_api_version="6.6",
)
return self.thumbnail_width
@property
def thumb_height(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail height.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_height`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_height",
new_attr_name="thumbnail_height",
bot_api_version="6.6",
)
return self.thumbnail_height
+97 -11
View File
@@ -25,6 +25,10 @@ from telegram._messageentity import MessageEntity
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -47,10 +51,20 @@ class InlineQueryResultGif(InlineQueryResult):
gif_width (:obj:`int`, optional): Width of the GIF.
gif_height (:obj:`int`, optional): Height of the GIF.
gif_duration (:obj:`int`, optional): Duration of the GIF in seconds.
thumb_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for
the result.
thumb_mime_type (:obj:`str`, optional): MIME type of the thumbnail, must be one of
thumbnail_url (:obj:`str`, optional): URL of the static (JPEG or GIF) or animated (MPEG4)
thumbnail for the result.
Warning:
The Bot API does **not** define this as an optional argument. It is formally
optional for backwards compatibility with the deprecated :paramref:`thumb_url`.
If you pass neither :paramref:`thumbnail_url` nor :paramref:`thumb_url`,
:class:`ValueError` will be raised.
.. versionadded:: 20.2
thumbnail_mime_type (:obj:`str`, optional): MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. versionadded:: 20.2
title (:obj:`str`, optional): Title for the result.
caption (:obj:`str`, optional): Caption of the GIF file to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters
@@ -65,6 +79,20 @@ class InlineQueryResultGif(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the GIF animation.
thumb_mime_type (:obj:`str`, optional): MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_mime_type`.
thumb_url (:obj:`str`, optional): URL of the static (JPEG or GIF) or animated (MPEG4)
thumbnail for the result.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
Raises:
:class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is
supplied or if both are supplied and are not equal.
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.GIF`.
@@ -75,10 +103,14 @@ class InlineQueryResultGif(InlineQueryResult):
gif_width (:obj:`int`): Optional. Width of the GIF.
gif_height (:obj:`int`): Optional. Height of the GIF.
gif_duration (:obj:`int`): Optional. Duration of the GIF in seconds.
thumb_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for
the result.
thumb_mime_type (:obj:`str`): Optional. MIME type of the thumbnail, must be one of
thumbnail_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail
for the result.
.. versionadded:: 20.2
thumbnail_mime_type (:obj:`str`): Optional. MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. versionadded:: 20.2
title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption of the GIF file to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters
@@ -100,7 +132,7 @@ class InlineQueryResultGif(InlineQueryResult):
__slots__ = (
"reply_markup",
"gif_height",
"thumb_mime_type",
"thumbnail_mime_type",
"caption_entities",
"gif_width",
"title",
@@ -109,14 +141,17 @@ class InlineQueryResultGif(InlineQueryResult):
"gif_duration",
"input_message_content",
"gif_url",
"thumb_url",
"thumbnail_url",
)
def __init__(
self,
id: str, # pylint: disable=redefined-builtin
gif_url: str,
thumb_url: str,
# thumbnail_url is not optional in Telegram API, but we want to support thumb_url as well,
# so thumbnail_url may not be passed. We will raise ValueError manually if neither
# thumbnail_url nor thumb_url are passed
thumbnail_url: str = None,
gif_width: int = None,
gif_height: int = None,
title: str = None,
@@ -127,14 +162,29 @@ class InlineQueryResultGif(InlineQueryResult):
parse_mode: ODVInput[str] = DEFAULT_NONE,
thumb_mime_type: str = None,
caption_entities: Sequence[MessageEntity] = None,
thumbnail_mime_type: str = None,
# thumb_url is not optional in Telegram API, but it is here, along with thumbnail_url.
thumb_url: str = None,
*,
api_kwargs: JSONDict = None,
):
if not (thumbnail_url or thumb_url):
raise ValueError(
"You must pass either 'thumbnail_url' or 'thumb_url'. Note that 'thumb_url' is "
"deprecated."
)
# Required
super().__init__(InlineQueryResultType.GIF, id, api_kwargs=api_kwargs)
with self._unfrozen():
self.gif_url: str = gif_url
self.thumb_url: str = thumb_url
self.thumbnail_url: str = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
# Optionals
self.gif_width: Optional[int] = gif_width
@@ -146,4 +196,40 @@ class InlineQueryResultGif(InlineQueryResult):
self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities)
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumb_mime_type: Optional[str] = thumb_mime_type
self.thumbnail_mime_type: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_mime_type,
new_arg=thumbnail_mime_type,
deprecated_arg_name="thumb_mime_type",
new_arg_name="thumbnail_mime_type",
bot_api_version="6.6",
)
@property
def thumb_url(self) -> str:
""":obj:`str`: URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the
result.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_mime_type(self) -> Optional[str]:
""":obj:`str`: Optional. Optional. MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_mime_type`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_mime_type",
new_attr_name="thumbnail_mime_type",
bot_api_version="6.6",
)
return self.thumbnail_mime_type
+100 -9
View File
@@ -24,6 +24,10 @@ from telegram import constants
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._inline.inlinequeryresult import InlineQueryResult
from telegram._utils.types import JSONDict
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
if TYPE_CHECKING:
from telegram import InputMessageContent
@@ -63,9 +67,27 @@ class InlineQueryResultLocation(InlineQueryResult):
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the location.
thumb_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
thumb_width (:obj:`int`, optional): Thumbnail width.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_width`.
thumb_height (:obj:`int`, optional): Thumbnail height.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_height`.
thumbnail_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`, optional): Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`, optional): Thumbnail height.
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.LOCATION`.
id (:obj:`str`): Unique identifier for this result,
@@ -94,17 +116,23 @@ class InlineQueryResultLocation(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the location.
thumb_url (:obj:`str`): Optional. Url of the thumbnail for the result.
thumb_width (:obj:`int`): Optional. Thumbnail width.
thumb_height (:obj:`int`): Optional. Thumbnail height.
thumbnail_url (:obj:`str`): Optional. Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`): Optional. Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`): Optional. Thumbnail height.
.. versionadded:: 20.2
"""
__slots__ = (
"longitude",
"reply_markup",
"thumb_width",
"thumb_height",
"thumbnail_width",
"thumbnail_height",
"heading",
"title",
"live_period",
@@ -112,7 +140,7 @@ class InlineQueryResultLocation(InlineQueryResult):
"input_message_content",
"latitude",
"horizontal_accuracy",
"thumb_url",
"thumbnail_url",
)
def __init__(
@@ -130,6 +158,9 @@ class InlineQueryResultLocation(InlineQueryResult):
horizontal_accuracy: float = None,
heading: int = None,
proximity_alert_radius: int = None,
thumbnail_url: str = None,
thumbnail_width: int = None,
thumbnail_height: int = None,
*,
api_kwargs: JSONDict = None,
):
@@ -144,15 +175,75 @@ class InlineQueryResultLocation(InlineQueryResult):
self.live_period: Optional[int] = live_period
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumb_url: Optional[str] = thumb_url
self.thumb_width: Optional[int] = thumb_width
self.thumb_height: Optional[int] = thumb_height
self.thumbnail_url: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
self.thumbnail_width: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_width,
new_arg=thumbnail_width,
deprecated_arg_name="thumb_width",
new_arg_name="thumbnail_width",
bot_api_version="6.6",
)
self.thumbnail_height: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_height,
new_arg=thumbnail_height,
deprecated_arg_name="thumb_height",
new_arg_name="thumbnail_height",
bot_api_version="6.6",
)
self.horizontal_accuracy: Optional[float] = horizontal_accuracy
self.heading: Optional[int] = heading
self.proximity_alert_radius: Optional[int] = (
int(proximity_alert_radius) if proximity_alert_radius else None
)
@property
def thumb_url(self) -> Optional[str]:
""":obj:`str`: Optional. Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_width(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail width.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_width`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_width",
new_attr_name="thumbnail_width",
bot_api_version="6.6",
)
return self.thumbnail_width
@property
def thumb_height(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail height.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_height`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_height",
new_attr_name="thumbnail_height",
bot_api_version="6.6",
)
return self.thumbnail_height
HORIZONTAL_ACCURACY: ClassVar[int] = constants.LocationLimit.HORIZONTAL_ACCURACY
""":const:`telegram.constants.LocationLimit.HORIZONTAL_ACCURACY`
+87 -11
View File
@@ -25,6 +25,10 @@ from telegram._messageentity import MessageEntity
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -48,10 +52,20 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
mpeg4_width (:obj:`int`, optional): Video width.
mpeg4_height (:obj:`int`, optional): Video height.
mpeg4_duration (:obj:`int`, optional): Video duration in seconds.
thumb_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for
the result.
thumb_mime_type (:obj:`str`): Optional. MIME type of the thumbnail, must be one of
thumbnail_url (:obj:`str`, optional): URL of the static (JPEG or GIF) or animated (MPEG4)
thumbnail for the result.
Warning:
The Bot API does **not** define this as an optional argument. It is formally
optional for backwards compatibility with the deprecated :paramref:`thumb_url`.
If you pass neither :paramref:`thumbnail_url` nor :paramref:`thumb_url`,
:class:`ValueError` will be raised.
.. versionadded:: 20.2
thumbnail_mime_type (:obj:`str`, optional): MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. versionadded:: 20.2
title (:obj:`str`, optional): Title for the result.
caption (:obj:`str`, optional): Caption of the MPEG-4 file to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters
@@ -68,6 +82,10 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the video animation.
Raises:
:class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is
supplied or if both are supplied and are not equal.
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.MPEG4GIF`.
id (:obj:`str`): Unique identifier for this result,
@@ -77,10 +95,14 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
mpeg4_width (:obj:`int`): Optional. Video width.
mpeg4_height (:obj:`int`): Optional. Video height.
mpeg4_duration (:obj:`int`): Optional. Video duration in seconds.
thumb_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for
the result.
thumb_mime_type (:obj:`str`): Optional. MIME type of the thumbnail, must be one of
thumbnail_url (:obj:`str`): URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail
for the result.
.. versionadded:: 20.2
thumbnail_mime_type (:obj:`str`): Optional. MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. versionadded:: 20.2
title (:obj:`str`): Optional. Title for the result.
caption (:obj:`str`): Optional. Caption of the MPEG-4 file to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters
@@ -102,7 +124,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
__slots__ = (
"reply_markup",
"thumb_mime_type",
"thumbnail_mime_type",
"caption_entities",
"mpeg4_duration",
"mpeg4_width",
@@ -112,14 +134,17 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
"input_message_content",
"mpeg4_url",
"mpeg4_height",
"thumb_url",
"thumbnail_url",
)
def __init__(
self,
id: str, # pylint: disable=redefined-builtin
mpeg4_url: str,
thumb_url: str,
# thumbnail_url is not optional in Telegram API, but we want to support thumb_url as well,
# so thumbnail_url may not be passed. We will raise ValueError manually if neither
# thumbnail_url nor thumb_url are passed
thumbnail_url: str = None,
mpeg4_width: int = None,
mpeg4_height: int = None,
title: str = None,
@@ -130,14 +155,29 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
parse_mode: ODVInput[str] = DEFAULT_NONE,
thumb_mime_type: str = None,
caption_entities: Sequence[MessageEntity] = None,
thumbnail_mime_type: str = None,
# thumb_url is not optional in Telegram API, but it is here, along with thumbnail_url.
thumb_url: str = None,
*,
api_kwargs: JSONDict = None,
):
if not (thumbnail_url or thumb_url):
raise ValueError(
"You must pass either 'thumbnail_url' or 'thumb_url'. Note that 'thumb_url' is "
"deprecated."
)
# Required
super().__init__(InlineQueryResultType.MPEG4GIF, id, api_kwargs=api_kwargs)
with self._unfrozen():
self.mpeg4_url: str = mpeg4_url
self.thumb_url: str = thumb_url
self.thumbnail_url: str = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
# Optional
self.mpeg4_width: Optional[int] = mpeg4_width
@@ -149,4 +189,40 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities)
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumb_mime_type: Optional[str] = thumb_mime_type
self.thumbnail_mime_type: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_mime_type,
new_arg=thumbnail_mime_type,
deprecated_arg_name="thumb_mime_type",
new_arg_name="thumbnail_mime_type",
bot_api_version="6.6",
)
@property
def thumb_url(self) -> str:
""":obj:`str`: URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the
result.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_mime_type(self) -> Optional[str]:
""":obj:`str`: Optional. Optional. MIME type of the thumbnail, must be one of
``'image/jpeg'``, ``'image/gif'``, or ``'video/mp4'``. Defaults to ``'image/jpeg'``.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_mime_type`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_mime_type",
new_attr_name="thumbnail_mime_type",
bot_api_version="6.6",
)
return self.thumbnail_mime_type
+56 -5
View File
@@ -25,6 +25,10 @@ from telegram._messageentity import MessageEntity
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -45,7 +49,15 @@ class InlineQueryResultPhoto(InlineQueryResult):
:tg-const:`telegram.InlineQueryResult.MAX_ID_LENGTH` Bytes.
photo_url (:obj:`str`): A valid URL of the photo. Photo must be in JPEG format. Photo size
must not exceed 5MB.
thumb_url (:obj:`str`): URL of the thumbnail for the photo.
thumbnail_url (:obj:`str`, optional): URL of the thumbnail for the photo.
Warning:
The Bot API does **not** define this as an optional argument. It is formally
optional for backwards compatibility with the deprecated :paramref:`thumb_url`.
If you pass neither :paramref:`thumbnail_url` nor :paramref:`thumb_url`,
:class:`ValueError` will be raised.
.. versionadded:: 20.2
photo_width (:obj:`int`, optional): Width of the photo.
photo_height (:obj:`int`, optional): Height of the photo.
title (:obj:`str`, optional): Title for the result.
@@ -63,6 +75,14 @@ class InlineQueryResultPhoto(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the photo.
thumb_url (:obj:`str`, optional): URL of the thumbnail for the photo.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
Raises:
:class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is
supplied or if both are supplied and are not equal.
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.PHOTO`.
@@ -71,7 +91,7 @@ class InlineQueryResultPhoto(InlineQueryResult):
:tg-const:`telegram.InlineQueryResult.MAX_ID_LENGTH` Bytes.
photo_url (:obj:`str`): A valid URL of the photo. Photo must be in JPEG format. Photo size
must not exceed 5MB.
thumb_url (:obj:`str`): URL of the thumbnail for the photo.
thumbnail_url (:obj:`str`): URL of the thumbnail for the photo.
photo_width (:obj:`int`): Optional. Width of the photo.
photo_height (:obj:`int`): Optional. Height of the photo.
title (:obj:`str`): Optional. Title for the result.
@@ -104,14 +124,17 @@ class InlineQueryResultPhoto(InlineQueryResult):
"parse_mode",
"input_message_content",
"photo_height",
"thumb_url",
"thumbnail_url",
)
def __init__(
self,
id: str, # pylint: disable=redefined-builtin
photo_url: str,
thumb_url: str,
# thumbnail_url is not optional in Telegram API, but we want to support thumb_url as well,
# so thumbnail_url may not be passed. We will raise ValueError manually if neither
# thumbnail_url nor thumb_url are passed
thumbnail_url: str = None,
photo_width: int = None,
photo_height: int = None,
title: str = None,
@@ -121,14 +144,28 @@ class InlineQueryResultPhoto(InlineQueryResult):
input_message_content: "InputMessageContent" = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Sequence[MessageEntity] = None,
# thumb_url is not optional in Telegram API, but it is here, along with thumbnail_url.
thumb_url: str = None,
*,
api_kwargs: JSONDict = None,
):
if not (thumbnail_url or thumb_url):
raise ValueError(
"You must pass either 'thumbnail_url' or 'thumb_url'. Note that 'thumb_url' is "
"deprecated."
)
# Required
super().__init__(InlineQueryResultType.PHOTO, id, api_kwargs=api_kwargs)
with self._unfrozen():
self.photo_url: str = photo_url
self.thumb_url: str = thumb_url
self.thumbnail_url: str = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
# Optionals
self.photo_width: Optional[int] = photo_width
@@ -140,3 +177,17 @@ class InlineQueryResultPhoto(InlineQueryResult):
self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities)
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
@property
def thumb_url(self) -> Optional[str]:
""":obj:`str`: URL of the thumbnail for the photo.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
+100 -9
View File
@@ -23,6 +23,10 @@ from typing import TYPE_CHECKING, Optional
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._inline.inlinequeryresult import InlineQueryResult
from telegram._utils.types import JSONDict
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -60,9 +64,27 @@ class InlineQueryResultVenue(InlineQueryResult):
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
message to be sent instead of the venue.
thumb_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
thumb_width (:obj:`int`, optional): Thumbnail width.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_width`.
thumb_height (:obj:`int`, optional): Thumbnail height.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_height`.
thumbnail_url (:obj:`str`, optional): Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`, optional): Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`, optional): Thumbnail height.
.. versionadded:: 20.2
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.VENUE`.
id (:obj:`str`): Unique identifier for this result,
@@ -84,9 +106,15 @@ class InlineQueryResultVenue(InlineQueryResult):
to the message.
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
message to be sent instead of the venue.
thumb_url (:obj:`str`): Optional. Url of the thumbnail for the result.
thumb_width (:obj:`int`): Optional. Thumbnail width.
thumb_height (:obj:`int`): Optional. Thumbnail height.
thumbnail_url (:obj:`str`): Optional. Url of the thumbnail for the result.
.. versionadded:: 20.2
thumbnail_width (:obj:`int`): Optional. Thumbnail width.
.. versionadded:: 20.2
thumbnail_height (:obj:`int`): Optional. Thumbnail height.
.. versionadded:: 20.2
"""
@@ -94,8 +122,8 @@ class InlineQueryResultVenue(InlineQueryResult):
"longitude",
"reply_markup",
"google_place_type",
"thumb_width",
"thumb_height",
"thumbnail_width",
"thumbnail_height",
"title",
"address",
"foursquare_id",
@@ -103,7 +131,7 @@ class InlineQueryResultVenue(InlineQueryResult):
"google_place_id",
"input_message_content",
"latitude",
"thumb_url",
"thumbnail_url",
)
def __init__(
@@ -122,6 +150,9 @@ class InlineQueryResultVenue(InlineQueryResult):
thumb_height: int = None,
google_place_id: str = None,
google_place_type: str = None,
thumbnail_url: str = None,
thumbnail_width: int = None,
thumbnail_height: int = None,
*,
api_kwargs: JSONDict = None,
):
@@ -140,6 +171,66 @@ class InlineQueryResultVenue(InlineQueryResult):
self.google_place_type: Optional[str] = google_place_type
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
self.thumb_url: Optional[str] = thumb_url
self.thumb_width: Optional[int] = thumb_width
self.thumb_height: Optional[int] = thumb_height
self.thumbnail_url: Optional[str] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
self.thumbnail_width: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_width,
new_arg=thumbnail_width,
deprecated_arg_name="thumb_width",
new_arg_name="thumbnail_width",
bot_api_version="6.6",
)
self.thumbnail_height: Optional[int] = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_height,
new_arg=thumbnail_height,
deprecated_arg_name="thumb_height",
new_arg_name="thumbnail_height",
bot_api_version="6.6",
)
@property
def thumb_url(self) -> Optional[str]:
""":obj:`str`: Optional. Url of the thumbnail for the result.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
@property
def thumb_width(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail width.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_width`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_width",
new_attr_name="thumbnail_width",
bot_api_version="6.6",
)
return self.thumbnail_width
@property
def thumb_height(self) -> Optional[int]:
""":obj:`str`: Optional. Thumbnail height.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_height`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_height",
new_attr_name="thumbnail_height",
bot_api_version="6.6",
)
return self.thumbnail_height
+75 -7
View File
@@ -25,6 +25,10 @@ from telegram._messageentity import MessageEntity
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.warnings_transition import (
warn_about_deprecated_arg_return_new_arg,
warn_about_deprecated_attr_in_property,
)
from telegram.constants import InlineQueryResultType
if TYPE_CHECKING:
@@ -50,8 +54,22 @@ class InlineQueryResultVideo(InlineQueryResult):
:tg-const:`telegram.InlineQueryResult.MAX_ID_LENGTH` Bytes.
video_url (:obj:`str`): A valid URL for the embedded video player or video file.
mime_type (:obj:`str`): Mime type of the content of video url, "text/html" or "video/mp4".
thumb_url (:obj:`str`): URL of the thumbnail (JPEG only) for the video.
title (:obj:`str`): Title for the result.
thumbnail_url (:obj:`str`, optional): URL of the thumbnail (JPEG only) for the video.
Warning:
The Bot API does **not** define this as an optional argument. It is formally
optional for backwards compatibility with the deprecated :paramref:`thumb_url`.
If you pass neither :paramref:`thumbnail_url` nor :paramref:`thumb_url`,
:class:`ValueError` will be raised.
.. versionadded:: 20.2
title (:obj:`str`, optional): Title for the result.
Warning:
The Bot API does **not** define this as an optional argument. It is formally
optional to ensure backwards compatibility of :paramref:`thumbnail_url` with the
deprecated :paramref:`thumb_url`, which required that :paramref:`thumbnail_url`
become optional. :class:`TypeError` will be raised if no ``title`` is passed.
caption (:obj:`str`, optional): Caption of the video to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after entities
parsing.
@@ -71,6 +89,15 @@ class InlineQueryResultVideo(InlineQueryResult):
message to be sent instead of the video. This field is required if
``InlineQueryResultVideo`` is used to send an HTML-page as a result
(e.g., a YouTube video).
thumb_url (:obj:`str`, optional): URL of the thumbnail (JPEG only) for the video.
.. deprecated:: 20.2
|thumbargumentdeprecation| :paramref:`thumbnail_url`.
Raises:
:class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is
supplied or if both are supplied and are not equal.
:class:`TypeError`: If no :paramref:`title` is passed.
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.VIDEO`.
@@ -79,7 +106,9 @@ class InlineQueryResultVideo(InlineQueryResult):
:tg-const:`telegram.InlineQueryResult.MAX_ID_LENGTH` Bytes.
video_url (:obj:`str`): A valid URL for the embedded video player or video file.
mime_type (:obj:`str`): Mime type of the content of video url, "text/html" or "video/mp4".
thumb_url (:obj:`str`): URL of the thumbnail (JPEG only) for the video.
thumbnail_url (:obj:`str`): URL of the thumbnail (JPEG only) for the video.
.. versionadded:: 20.2
title (:obj:`str`): Title for the result.
caption (:obj:`str`): Optional. Caption of the video to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after entities
@@ -119,7 +148,7 @@ class InlineQueryResultVideo(InlineQueryResult):
"input_message_content",
"video_height",
"video_width",
"thumb_url",
"thumbnail_url",
)
def __init__(
@@ -127,8 +156,13 @@ class InlineQueryResultVideo(InlineQueryResult):
id: str, # pylint: disable=redefined-builtin
video_url: str,
mime_type: str,
thumb_url: str,
title: str,
# thumbnail_url and title are not optional in Telegram API, but we want to support
# thumb_url as well, so thumbnail_url may not be passed if thumb_url is passed.
# We will raise ValueError manually if neither thumbnail_url nor thumb_url are passed.
thumbnail_url: str = None,
# title had to be made optional because of thumbnail_url. This is compensated by raising
# TypeError manually if title is not passed.
title: str = None,
caption: str = None,
video_width: int = None,
video_height: int = None,
@@ -138,15 +172,35 @@ class InlineQueryResultVideo(InlineQueryResult):
input_message_content: "InputMessageContent" = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Sequence[MessageEntity] = None,
# thumb_url is not optional in Telegram API, but it is here, along with thumbnail_url.
thumb_url: str = None,
*,
api_kwargs: JSONDict = None,
):
if not (thumbnail_url or thumb_url):
raise ValueError(
"You must pass either 'thumbnail_url' or 'thumb_url'. Note that 'thumb_url' is "
"deprecated."
)
if title is None:
raise TypeError(
"InlineQueryResultVideo.__init__() missing a required argument: you forgot to pass"
" either 'title' or 'thumbnail_url'."
)
# Required
super().__init__(InlineQueryResultType.VIDEO, id, api_kwargs=api_kwargs)
with self._unfrozen():
self.video_url: str = video_url
self.mime_type: str = mime_type
self.thumb_url: str = thumb_url
self.thumbnail_url: str = warn_about_deprecated_arg_return_new_arg(
deprecated_arg=thumb_url,
new_arg=thumbnail_url,
deprecated_arg_name="thumb_url",
new_arg_name="thumbnail_url",
bot_api_version="6.6",
)
self.title: str = title
# Optional
@@ -159,3 +213,17 @@ class InlineQueryResultVideo(InlineQueryResult):
self.description: Optional[str] = description
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.input_message_content: Optional[InputMessageContent] = input_message_content
@property
def thumb_url(self) -> str:
""":obj:`str`: URL of the thumbnail (JPEG only) for the video.
.. deprecated:: 20.2
|thumbattributedeprecation| :attr:`thumbnail_url`.
"""
warn_about_deprecated_attr_in_property(
deprecated_attr_name="thumb_url",
new_attr_name="thumbnail_url",
bot_api_version="6.6",
)
return self.thumbnail_url
+8
View File
@@ -34,6 +34,10 @@ class KeyboardButtonRequestUser(TelegramObject):
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`request_id` is equal.
.. seealso::
`Telegram Docs on requesting users \
<https://core.telegram.org/bots/features#chat-and-user-selection>`_
.. versionadded:: 20.1
Args:
@@ -87,6 +91,10 @@ class KeyboardButtonRequestChat(TelegramObject):
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`request_id` is equal.
.. seealso::
`Telegram Docs on requesting chats \
<https://core.telegram.org/bots/features#chat-and-user-selection>`_
.. versionadded:: 20.1
Args:
+12
View File
@@ -1374,6 +1374,7 @@ class Message(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
quote: bool = None,
@@ -1421,6 +1422,7 @@ class Message(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
thumbnail=thumbnail,
)
async def reply_document(
@@ -1437,6 +1439,7 @@ class Message(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
quote: bool = None,
@@ -1482,6 +1485,7 @@ class Message(TelegramObject):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
)
async def reply_animation(
@@ -1501,6 +1505,7 @@ class Message(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
quote: bool = None,
@@ -1550,6 +1555,7 @@ class Message(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
)
async def reply_sticker(
@@ -1561,6 +1567,7 @@ class Message(TelegramObject):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
emoji: str = None,
*,
quote: bool = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1599,6 +1606,7 @@ class Message(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
emoji=emoji,
)
async def reply_video(
@@ -1619,6 +1627,7 @@ class Message(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
quote: bool = None,
@@ -1668,6 +1677,7 @@ class Message(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
)
async def reply_video_note(
@@ -1682,6 +1692,7 @@ class Message(TelegramObject):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
quote: bool = None,
@@ -1726,6 +1737,7 @@ class Message(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
)
async def reply_voice(
+12
View File
@@ -553,6 +553,7 @@ class User(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -596,6 +597,7 @@ class User(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
thumbnail=thumbnail,
)
async def send_chat_action(
@@ -748,6 +750,7 @@ class User(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -783,6 +786,7 @@ class User(TelegramObject):
pool_timeout=pool_timeout,
parse_mode=parse_mode,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
disable_content_type_detection=disable_content_type_detection,
allow_sending_without_reply=allow_sending_without_reply,
@@ -1005,6 +1009,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1049,6 +1054,7 @@ class User(TelegramObject):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
)
async def send_sticker(
@@ -1060,6 +1066,7 @@ class User(TelegramObject):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
emoji: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -1094,6 +1101,7 @@ class User(TelegramObject):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
emoji=emoji,
)
async def send_video(
@@ -1114,6 +1122,7 @@ class User(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1152,6 +1161,7 @@ class User(TelegramObject):
parse_mode=parse_mode,
supports_streaming=supports_streaming,
thumb=thumb,
thumbnail=thumbnail,
api_kwargs=api_kwargs,
allow_sending_without_reply=allow_sending_without_reply,
caption_entities=caption_entities,
@@ -1234,6 +1244,7 @@ class User(TelegramObject):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1273,6 +1284,7 @@ class User(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
)
async def send_voice(
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains functionality used for transition warnings issued by this library.
It was created to prevent circular imports that would be caused by creating the warnings
inside warnings.py.
.. versionadded:: 20.2
"""
import functools
from typing import Any
from telegram._utils.warnings import warn
from telegram.warnings import PTBDeprecationWarning
# Narrower type hints will cause linting errors and/or circular imports.
# We'll use `Any` here and put type hints in the calling code.
def warn_about_deprecated_arg_return_new_arg(
deprecated_arg: Any,
new_arg: Any,
deprecated_arg_name: str,
new_arg_name: str,
bot_api_version: str,
stacklevel: int = 3,
) -> Any:
"""A helper function for the transition in API when argument is renamed.
Checks the `deprecated_arg` and `new_arg` objects; warns if non-None `deprecated_arg` object
was passed. Returns `new_arg` object (either the one originally passed by the user or the one
that user passed as `deprecated_arg`).
Raises `ValueError` if both `deprecated_arg` and `new_arg` objects were passed, and they are
different.
"""
if deprecated_arg and new_arg and deprecated_arg != new_arg:
raise ValueError(
f"You passed different entities as '{deprecated_arg_name}' and '{new_arg_name}'. "
f"The parameter '{deprecated_arg_name}' was renamed to '{new_arg_name}' in Bot API "
f"{bot_api_version}. We recommend using '{new_arg_name}' instead of "
f"'{deprecated_arg_name}'."
)
if deprecated_arg:
warn(
f"Bot API {bot_api_version} renamed the argument '{deprecated_arg_name}' to "
f"'{new_arg_name}'.",
PTBDeprecationWarning,
stacklevel=stacklevel,
)
return deprecated_arg
return new_arg
def warn_about_deprecated_attr_in_property(
deprecated_attr_name: str,
new_attr_name: str,
bot_api_version: str,
stacklevel: int = 3,
) -> None:
"""A helper function for the transition in API when attribute is renamed. Call from properties.
The properties replace deprecated attributes in classes and issue these deprecation warnings.
"""
warn(
f"Bot API {bot_api_version} renamed the attribute '{deprecated_attr_name}' to "
f"'{new_attr_name}'.",
PTBDeprecationWarning,
stacklevel=stacklevel,
)
warn_about_thumb_return_thumbnail = functools.partial(
warn_about_deprecated_arg_return_new_arg,
deprecated_arg_name="thumb",
new_arg_name="thumbnail",
bot_api_version="6.6",
)
"""A helper function to warn about using a deprecated 'thumb' argument and return it or the new
'thumbnail' argument, introduced in API 6.6.
"""
+1 -1
View File
@@ -50,7 +50,7 @@ class Version(NamedTuple):
return version
__version_info__ = Version(major=20, minor=1, micro=0, releaselevel="final", serial=0)
__version_info__ = Version(major=20, minor=2, micro=0, releaselevel="final", serial=0)
__version__ = str(__version_info__)
# # SETUP.PY MARKER
+117 -2
View File
@@ -36,6 +36,7 @@ __all__ = [
"BOT_API_VERSION_INFO",
"BotCommandLimit",
"BotCommandScopeType",
"BotDescriptionLimit",
"CallbackQueryLimit",
"ChatAction",
"ChatID",
@@ -73,7 +74,9 @@ __all__ = [
"PollType",
"ReplyLimit",
"SUPPORTED_WEBHOOK_PORTS",
"StickerFormat",
"StickerLimit",
"StickerSetLimit",
"StickerType",
"WebhookLimit",
"UpdateType",
@@ -111,7 +114,7 @@ class _BotAPIVersion(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=5)
BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=6)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@@ -184,6 +187,28 @@ class BotCommandScopeType(StringEnum):
""":obj:`str`: The type of :class:`telegram.BotCommandScopeChatMember`."""
class BotDescriptionLimit(IntEnum):
"""This enum contains limitations for the methods :meth:`telegram.Bot.set_my_description` and
:meth:`telegram.Bot.set_my_short_description`. The enum members of this enumeration are
instances of :class:`int` and can be treated as such.
.. versionadded:: 20.2
"""
__slots__ = ()
MAX_DESCRIPTION_LENGTH = 512
""":obj:`int`: Maximum length for the parameter
:paramref:`~telegram.Bot.set_my_description.description` of
:meth:`telegram.Bot.set_my_description`
"""
MAX_SHORT_DESCRIPTION_LENGTH = 120
""":obj:`int`: Maximum length for the parameter
:paramref:`~telegram.Bot.set_my_short_description.short_description` of
:meth:`telegram.Bot.set_my_short_description`
"""
class CallbackQueryLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.CallbackQuery`/
:meth:`telegram.Bot.answer_callback_query`. The enum members of this enumeration are instances
@@ -1243,8 +1268,26 @@ class ReplyLimit(IntEnum):
"""
class StickerFormat(StringEnum):
"""This enum contains the available formats of :class:`telegram.Sticker` in the set. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: 20.2
"""
__slots__ = ()
STATIC = "static"
""":obj:`str`: Static sticker."""
ANIMATED = "animated"
""":obj:`str`: Animated sticker."""
VIDEO = "video"
""":obj:`str`: Video sticker."""
class StickerLimit(IntEnum):
"""This enum contains limitations for :meth:`telegram.Bot.create_new_sticker_set`.
"""This enum contains limitations for various sticker methods, such as
:meth:`telegram.Bot.create_new_sticker_set`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: 20.0
@@ -1264,6 +1307,78 @@ class StickerLimit(IntEnum):
:paramref:`~telegram.Bot.create_new_sticker_set.title` parameter of
:meth:`telegram.Bot.create_new_sticker_set`.
"""
MIN_STICKER_EMOJI = 1
""":obj:`int`: Minimum number of emojis associated with a sticker, passed as the
:paramref:`~telegram.Bot.setStickerEmojiList.emoji_list` parameter of
:meth:`telegram.Bot.set_sticker_emoji_list`.
.. versionadded:: 20.2
"""
MAX_STICKER_EMOJI = 20
""":obj:`int`: Maximum number of emojis associated with a sticker, passed as the
:paramref:`~telegram.Bot.setStickerEmojiList.emoji_list` parameter of
:meth:`telegram.Bot.set_sticker_emoji_list`.
.. versionadded:: 20.2
"""
MAX_SEARCH_KEYWORDS = 20
""":obj:`int`: Maximum number of search keywords for a sticker, passed as the
:paramref:`~telegram.Bot.set_sticker_keywords.keywords` parameter of
:meth:`telegram.Bot.set_sticker_keywords`.
.. versionadded:: 20.2
"""
MAX_KEYWORD_LENGTH = 64
""":obj:`int`: Maximum number of characters in a search keyword for a sticker, for each item in
:paramref:`~telegram.Bot.set_sticker_keywords.keywords` sequence of
:meth:`telegram.Bot.set_sticker_keywords`.
.. versionadded:: 20.2
"""
class StickerSetLimit(IntEnum):
"""This enum contains limitations for various sticker set methods, such as
:meth:`telegram.Bot.create_new_sticker_set` and :meth:`telegram.Bot.add_sticker_to_set`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: 20.2
"""
__slots__ = ()
MIN_INITIAL_STICKERS = 1
""":obj:`int`: Minimum number of stickers needed to create a sticker set, passed as the
:paramref:`~telegram.Bot.create_new_sticker_set.stickers` parameter of
:meth:`telegram.Bot.create_new_sticker_set`.
"""
MAX_INITIAL_STICKERS = 50
""":obj:`int`: Maximum number of stickers allowed while creating a sticker set, passed as the
:paramref:`~telegram.Bot.create_new_sticker_set.stickers` parameter of
:meth:`telegram.Bot.create_new_sticker_set`.
"""
MAX_EMOJI_STICKERS = 200
""":obj:`int`: Maximum number of stickers allowed in an emoji sticker set, as given in
:meth:`telegram.Bot.add_sticker_to_set`.
"""
MAX_ANIMATED_STICKERS = 50
""":obj:`int`: Maximum number of stickers allowed in an animated or video sticker set, as given
in :meth:`telegram.Bot.add_sticker_to_set`.
"""
MAX_STATIC_STICKERS = 120
""":obj:`int`: Maximum number of stickers allowed in a static sticker set, as given in
:meth:`telegram.Bot.add_sticker_to_set`.
"""
MAX_STATIC_THUMBNAIL_SIZE = 128
""":obj:`int`: Maximum size of the thumbnail if it is a **.WEBP** or **.PNG** in kilobytes,
as given in :meth:`telegram.Bot.set_sticker_set_thumbnail`."""
MAX_ANIMATED_THUMBNAIL_SIZE = 32
""":obj:`int`: Maximum size of the thumbnail if it is a **.TGS** or **.WEBM** in kilobytes,
as given in :meth:`telegram.Bot.set_sticker_set_thumbnail`."""
STATIC_THUMB_DIMENSIONS = 100
""":obj:`int`: Exact height and width of the thumbnail if it is a **.WEBP** or **.PNG** in
pixels, as given in :meth:`telegram.Bot.set_sticker_set_thumbnail`."""
class StickerType(StringEnum):
+22 -9
View File
@@ -31,10 +31,12 @@ from typing import (
TYPE_CHECKING,
Any,
AsyncContextManager,
Awaitable,
Callable,
Coroutine,
DefaultDict,
Dict,
Generator,
Generic,
List,
Mapping,
@@ -71,7 +73,6 @@ if TYPE_CHECKING:
DEFAULT_GROUP: int = 0
_AppType = TypeVar("_AppType", bound="Application") # pylint: disable=invalid-name
_RT = TypeVar("_RT")
_STOP_SIGNAL = object()
_logger = logging.getLogger(__name__)
@@ -934,7 +935,9 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
loop.close()
def create_task(
self, coroutine: Coroutine[Any, Any, RT], update: object = None
self,
coroutine: Union[Generator[Optional["asyncio.Future[object]"], None, RT], Awaitable[RT]],
update: object = None,
) -> "asyncio.Task[RT]":
"""Thin wrapper around :func:`asyncio.create_task` that handles exceptions raised by
the :paramref:`coroutine` with :meth:`process_error`.
@@ -948,7 +951,10 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
.. seealso:: :wiki:`Concurrency`
Args:
coroutine (:term:`coroutine function`): The coroutine to run as task.
coroutine (:term:`awaitable`): The awaitable to run as task.
.. versionchanged:: 20.2
Accepts :class:`asyncio.Future` and generator-based coroutine functions.
update (:obj:`object`, optional): If set, will be passed to :meth:`process_error`
as additional information for the error handlers. Moreover, the corresponding
:attr:`chat_data` and :attr:`user_data` entries will be updated in the next run of
@@ -960,13 +966,16 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
return self.__create_task(coroutine=coroutine, update=update)
def __create_task(
self, coroutine: Coroutine, update: object = None, is_error_handler: bool = False
) -> asyncio.Task:
self,
coroutine: Union[Generator[Optional["asyncio.Future[object]"], None, RT], Awaitable[RT]],
update: object = None,
is_error_handler: bool = False,
) -> "asyncio.Task[RT]":
# Unfortunately, we can't know if `coroutine` runs one of the error handler functions
# but by passing `is_error_handler=True` from `process_error`, we can make sure that we
# get at most one recursion of the user calls `create_task` manually with an error handler
# function
task = asyncio.create_task(
task: "asyncio.Task[RT]" = asyncio.create_task(
self.__create_task_callback(
coroutine=coroutine, update=update, is_error_handler=is_error_handler
)
@@ -995,11 +1004,13 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
async def __create_task_callback(
self,
coroutine: Coroutine[Any, Any, _RT],
coroutine: Union[Generator[Optional["asyncio.Future[object]"], None, RT], Awaitable[RT]],
update: object = None,
is_error_handler: bool = False,
) -> _RT:
) -> RT:
try:
if isinstance(coroutine, Generator):
return await asyncio.create_task(coroutine)
return await coroutine
except asyncio.CancelledError as cancel:
# TODO: in py3.8+, CancelledError is a subclass of BaseException, so we can drop this
@@ -1562,7 +1573,9 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
update: Optional[object],
error: Exception,
job: "Job[CCT]" = None,
coroutine: Coroutine[Any, Any, Any] = None,
coroutine: Union[
Generator[Optional["asyncio.Future[object]"], None, RT], Awaitable[RT]
] = None,
) -> bool:
"""Processes an error by passing it to all error handlers registered with
:meth:`add_error_handler`. If one of the error handlers raises
+42 -9
View File
@@ -178,7 +178,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
self._get_updates_write_timeout: ODVInput[float] = DEFAULT_NONE
self._get_updates_pool_timeout: ODVInput[float] = DEFAULT_NONE
self._get_updates_request: DVInput["BaseRequest"] = DEFAULT_NONE
self._get_updates_http_version: DVInput[str] = DefaultValue("2")
self._get_updates_http_version: DVInput[str] = DefaultValue("1.1")
self._private_key: ODVInput[bytes] = DEFAULT_NONE
self._private_key_password: ODVInput[bytes] = DEFAULT_NONE
self._defaults: ODVInput["Defaults"] = DEFAULT_NONE
@@ -204,7 +204,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
self._post_shutdown: Optional[Callable[[Application], Coroutine[Any, Any, None]]] = None
self._post_stop: Optional[Callable[[Application], Coroutine[Any, Any, None]]] = None
self._rate_limiter: ODVInput["BaseRateLimiter"] = DEFAULT_NONE
self._http_version: DVInput[str] = DefaultValue("2")
self._http_version: DVInput[str] = DefaultValue("1.1")
def _build_request(self, get_updates: bool) -> BaseRequest:
prefix = "_get_updates_" if get_updates else "_"
@@ -232,7 +232,7 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
key: value for key, value in timeouts.items() if not isinstance(value, DefaultValue)
}
http_version = DefaultValue.get_value(getattr(self, f"{prefix}http_version")) or "2"
http_version = DefaultValue.get_value(getattr(self, f"{prefix}http_version")) or "1.1"
return HTTPXRequest(
connection_pool_size=connection_pool_size,
@@ -564,15 +564,32 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
def http_version(self: BuilderType, http_version: str) -> BuilderType:
"""Sets the HTTP protocol version which is used for the
:paramref:`~telegram.request.HTTPXRequest.http_version` parameter of
:attr:`telegram.Bot.request`. By default, HTTP/2 is used.
:attr:`telegram.Bot.request`. By default, HTTP/1.1 is used.
.. seealso:: :meth:`get_updates_http_version`
Note:
Users have observed stability issues with HTTP/2, which happen due to how the `h2
library handles <https://github.com/python-hyper/h2/issues/1181>` cancellations of
keepalive connections. See `#3556 <https://github.com/python-telegram-bot/
python-telegram-bot/issues/3556>`_ for a discussion.
If you want to use HTTP/2, you must install PTB with the optional requirement
``http2``, i.e.
.. code-block:: bash
pip install python-telegram-bot[http2]
Keep in mind that the HTTP/1.1 implementation may be considered the `"more
robust option at this time" <https://www.python-httpx.org/http2#enabling-http2>`_.
.. versionadded:: 20.1
.. versionchanged:: 20.2
Reset the default version to 1.1.
Args:
http_version (:obj:`str`): Pass ``"1.1"`` if you'd like to use HTTP/1.1 for making
requests to Telegram. Defaults to ``"2"``, in which case HTTP/2 is used.
http_version (:obj:`str`): Pass ``"2"`` if you'd like to use HTTP/2 for making
requests to Telegram. Defaults to ``"1.1"``, in which case HTTP/1.1 is used.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
@@ -705,15 +722,31 @@ class ApplicationBuilder(Generic[BT, CCT, UD, CD, BD, JQ]):
def get_updates_http_version(self: BuilderType, get_updates_http_version: str) -> BuilderType:
"""Sets the HTTP protocol version which is used for the
:paramref:`~telegram.request.HTTPXRequest.http_version` parameter which is used in the
:meth:`telegram.Bot.get_updates` request. By default, HTTP/2 is used.
:meth:`telegram.Bot.get_updates` request. By default, HTTP/1.1 is used.
.. seealso:: :meth:`http_version`
Note:
Users have observed stability issues with HTTP/2, which happen due to how the `h2
library handles <https://github.com/python-hyper/h2/issues/1181>` cancellations of
keepalive connections. See `#3556 <https://github.com/python-telegram-bot/
python-telegram-bot/issues/3556>`_ for a discussion.
You will also need to install the http2 dependency. Keep in mind that the HTTP/1.1
implementation may be considered the `"more robust option at this time"
<https://www.python-httpx.org/http2#enabling-http2>`_.
.. code-block:: bash
pip install httpx[http2]
.. versionadded:: 20.1
.. versionchanged:: 20.2
Reset the default version to 1.1.
Args:
get_updates_http_version (:obj:`str`): Pass ``"1.1"`` if you'd like to use HTTP/1.1 for
making requests to Telegram. Defaults to ``"2"``, in which case HTTP/2 is used.
get_updates_http_version (:obj:`str`): Pass ``"2"`` if you'd like to use HTTP/2 for
making requests to Telegram. Defaults to ``"1.1"``, in which case HTTP/1.1 is used.
Returns:
:class:`ApplicationBuilder`: The same builder with the updated argument.
+13 -7
View File
@@ -20,14 +20,16 @@
from typing import (
TYPE_CHECKING,
Any,
Coroutine,
Awaitable,
Dict,
Generator,
Generic,
List,
Match,
NoReturn,
Optional,
Type,
Union,
)
from telegram._callbackquery import CallbackQuery
@@ -37,7 +39,7 @@ from telegram.ext._extbot import ExtBot
from telegram.ext._utils.types import BD, BT, CD, UD
if TYPE_CHECKING:
from asyncio import Queue
from asyncio import Future, Queue
from telegram.ext import Application, Job, JobQueue # noqa: F401
from telegram.ext._utils.types import CCT
@@ -96,8 +98,8 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
.. versionadded:: 20.0
Attributes:
coroutine (:term:`coroutine function`): Optional. Only present in error handlers if the
error was caused by a coroutine run with :meth:`Application.create_task` or a handler
coroutine (:term:`awaitable`): Optional. Only present in error handlers if the
error was caused by an awaitable run with :meth:`Application.create_task` or a handler
callback with :attr:`block=False <BaseHandler.block>`.
matches (List[:meth:`re.Match <re.Match.expand>`]): Optional. If the associated update
originated from a :class:`filters.Regex`, this will contain a list of match objects for
@@ -143,7 +145,9 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
self.matches: Optional[List[Match[str]]] = None
self.error: Optional[Exception] = None
self.job: Optional["Job[CCT]"] = None
self.coroutine: Optional[Coroutine[Any, Any, Any]] = None
self.coroutine: Optional[
Union[Generator[Optional["Future[object]"], None, Any], Awaitable[Any]]
] = None
@property
def application(self) -> "Application[BT, CCT, UD, CD, BD, Any]":
@@ -275,7 +279,7 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
error: Exception,
application: "Application[BT, CCT, UD, CD, BD, Any]",
job: "Job[Any]" = None,
coroutine: Coroutine[Any, Any, Any] = None,
coroutine: Union[Generator[Optional["Future[object]"], None, Any], Awaitable[Any]] = None,
) -> "CCT":
"""
Constructs an instance of :class:`telegram.ext.CallbackContext` to be passed to the error
@@ -295,13 +299,15 @@ class CallbackContext(Generic[BT, UD, CD, BD]):
job (:class:`telegram.ext.Job`, optional): The job associated with the error.
.. versionadded:: 20.0
coroutine (:term:`coroutine function`, optional): The coroutine function associated
coroutine (:term:`awaitable`, optional): The awaitable associated
with this error if the error was caused by a coroutine run with
:meth:`Application.create_task` or a handler callback with
:attr:`block=False <BaseHandler.block>`.
.. versionadded:: 20.0
.. versionchanged:: 20.2
Accepts :class:`asyncio.Future` and generator-based coroutine functions.
Returns:
:class:`telegram.ext.CallbackContext`
"""
+1 -1
View File
@@ -259,7 +259,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
when :attr:`per_message`, :attr:`per_chat`, :attr:`per_user` are all :obj:`False`.
Attributes:
block (:obj:`bool`): Determines whether the callback will run in a blocking way.. Always
block (:obj:`bool`): Determines whether the callback will run in a blocking way. Always
:obj:`True` since conversation handlers handle any non-blocking callbacks internally.
"""
+305 -12
View File
@@ -18,6 +18,7 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Bot with convenience extensions."""
import warnings
from copy import copy
from datetime import datetime
from typing import (
@@ -44,6 +45,8 @@ from telegram import (
Bot,
BotCommand,
BotCommandScope,
BotDescription,
BotShortDescription,
CallbackQuery,
Chat,
ChatAdministratorRights,
@@ -58,6 +61,7 @@ from telegram import (
GameHighScore,
InlineKeyboardMarkup,
InputMedia,
InputSticker,
Location,
MaskPosition,
MenuButton,
@@ -82,9 +86,11 @@ from telegram import (
from telegram._utils.datetime import to_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.warnings import warn
from telegram.ext._callbackdatacache import CallbackDataCache
from telegram.ext._utils.types import RLARGS
from telegram.request import BaseRequest
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import (
@@ -706,11 +712,12 @@ class ExtBot(Bot, Generic[RLARGS]):
self,
user_id: Union[str, int],
name: str,
emojis: str,
emojis: str = None, # Was made optional for compatibility reasons
png_sticker: FileInput = None,
mask_position: MaskPosition = None,
tgs_sticker: FileInput = None,
webm_sticker: FileInput = None,
sticker: InputSticker = None, # Actually a required param, but is optional for compat.
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -722,6 +729,7 @@ class ExtBot(Bot, Generic[RLARGS]):
return await super().add_sticker_to_set(
user_id=user_id,
name=name,
sticker=sticker,
emojis=emojis,
png_sticker=png_sticker,
mask_position=mask_position,
@@ -1031,12 +1039,15 @@ class ExtBot(Bot, Generic[RLARGS]):
user_id: Union[str, int],
name: str,
title: str,
emojis: str,
emojis: str = None, # Was made optional for compatibility purposes
png_sticker: FileInput = None,
mask_position: MaskPosition = None,
tgs_sticker: FileInput = None,
webm_sticker: FileInput = None,
sticker_type: str = None,
stickers: Sequence[InputSticker] = None, # Actually a required param. Optional for compat.
sticker_format: str = None, # Actually a required param. Optional for compat.
needs_repainting: bool = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -1049,6 +1060,9 @@ class ExtBot(Bot, Generic[RLARGS]):
user_id=user_id,
name=name,
title=title,
stickers=stickers,
sticker_format=sticker_format,
needs_repainting=needs_repainting,
emojis=emojis,
png_sticker=png_sticker,
mask_position=mask_position,
@@ -2150,6 +2164,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2176,6 +2191,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
filename=filename,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -2201,6 +2217,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2226,6 +2243,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
filename=filename,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -2349,6 +2367,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2372,6 +2391,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
filename=filename,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -2724,6 +2744,7 @@ class ExtBot(Bot, Generic[RLARGS]):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
emoji: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -2745,6 +2766,7 @@ class ExtBot(Bot, Generic[RLARGS]):
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
emoji=emoji,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
@@ -2817,6 +2839,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2844,6 +2867,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
filename=filename,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -2865,6 +2889,7 @@ class ExtBot(Bot, Generic[RLARGS]):
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
thumbnail: FileInput = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2886,6 +2911,7 @@ class ExtBot(Bot, Generic[RLARGS]):
allow_sending_without_reply=allow_sending_without_reply,
protect_content=protect_content,
message_thread_id=message_thread_id,
thumbnail=thumbnail,
filename=filename,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -3218,6 +3244,30 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_sticker_set_thumbnail(
self,
name: str,
user_id: Union[str, int],
thumbnail: FileInput = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_sticker_set_thumbnail(
name=name,
user_id=user_id,
thumbnail=thumbnail,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_sticker_set_thumb(
self,
name: str,
@@ -3231,16 +3281,30 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_sticker_set_thumb(
name=name,
user_id=user_id,
thumb=thumb,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
# Manually issue deprecation here to get the stacklevel right
# Suppress the warning issued by super().set_sticker_set_thumb just in case
# the user explicitly enables deprecation warnings
# Unfortunately this is not entirely reliable (see tests), but it's a best effort solution
warn(
message=(
"Bot API 6.6 renamed the method 'setStickerSetThumb' to 'setStickerSetThumbnail', "
"hence method 'set_sticker_set_thumb' was renamed to 'set_sticker_set_thumbnail' "
"in PTB."
),
category=PTBDeprecationWarning,
stacklevel=2,
)
with warnings.catch_warnings():
return await super().set_sticker_set_thumb(
name=name,
user_id=user_id,
thumb=thumb,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_webhook(
self,
@@ -3413,7 +3477,9 @@ class ExtBot(Bot, Generic[RLARGS]):
async def upload_sticker_file(
self,
user_id: Union[str, int],
png_sticker: FileInput,
png_sticker: FileInput = None, # Deprecated since bot api 6.6. Optional for compatiblity.
sticker: FileInput = None, # Actually required, but optional for compatibility.
sticker_format: str = None, # Actually required, but optional for compatibility.
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -3424,6 +3490,8 @@ class ExtBot(Bot, Generic[RLARGS]):
) -> File:
return await super().upload_sticker_file(
user_id=user_id,
sticker=sticker,
sticker_format=sticker_format,
png_sticker=png_sticker,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -3432,6 +3500,220 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_my_description(
self,
description: str = None,
language_code: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_my_description(
description=description,
language_code=language_code,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_my_short_description(
self,
short_description: str = None,
language_code: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_my_short_description(
short_description=short_description,
language_code=language_code,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def get_my_description(
self,
language_code: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> BotDescription:
return await super().get_my_description(
language_code=language_code,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def get_my_short_description(
self,
language_code: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> BotShortDescription:
return await super().get_my_short_description(
language_code=language_code,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_custom_emoji_sticker_set_thumbnail(
self,
name: str,
custom_emoji_id: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_custom_emoji_sticker_set_thumbnail(
name=name,
custom_emoji_id=custom_emoji_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_sticker_set_title(
self,
name: str,
title: str,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_sticker_set_title(
name=name,
title=title,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def delete_sticker_set(
self,
name: str,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().delete_sticker_set(
name=name,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_sticker_emoji_list(
self,
sticker: str,
emoji_list: Sequence[str],
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_sticker_emoji_list(
sticker=sticker,
emoji_list=emoji_list,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_sticker_keywords(
self,
sticker: str,
keywords: Sequence[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_sticker_keywords(
sticker=sticker,
keywords=keywords,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_sticker_mask_position(
self,
sticker: str,
mask_position: MaskPosition = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_sticker_mask_position(
sticker=sticker,
mask_position=mask_position,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
# updated camelCase aliases
getMe = get_me
sendMessage = send_message
@@ -3507,6 +3789,7 @@ class ExtBot(Bot, Generic[RLARGS]):
setStickerPositionInSet = set_sticker_position_in_set
deleteStickerFromSet = delete_sticker_from_set
setStickerSetThumb = set_sticker_set_thumb
setStickerSetThumbnail = set_sticker_set_thumbnail
setPassportDataErrors = set_passport_data_errors
sendPoll = send_poll
stopPoll = stop_poll
@@ -3533,3 +3816,13 @@ class ExtBot(Bot, Generic[RLARGS]):
reopenGeneralForumTopic = reopen_general_forum_topic
hideGeneralForumTopic = hide_general_forum_topic
unhideGeneralForumTopic = unhide_general_forum_topic
setMyDescription = set_my_description
getMyDescription = get_my_description
setMyShortDescription = set_my_short_description
getMyShortDescription = get_my_short_description
setCustomEmojiStickerSetThumbnail = set_custom_emoji_sticker_set_thumbnail
setStickerSetTitle = set_sticker_set_title
deleteStickerSet = delete_sticker_set
setStickerEmojiList = set_sticker_emoji_list
setStickerKeywords = set_sticker_keywords
setStickerMaskPosition = set_sticker_mask_position
+16 -2
View File
@@ -25,10 +25,13 @@ Warning:
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
import logging
from pathlib import Path
from types import FrameType
from typing import Optional
_logger = logging.getLogger(__name__)
def was_called_by(frame: Optional[FrameType], caller: Path) -> bool:
"""Checks if the passed frame was called by the specified file.
@@ -51,11 +54,22 @@ def was_called_by(frame: Optional[FrameType], caller: Path) -> bool:
if frame is None:
return False
try:
return _was_called_by(frame, caller)
except Exception as exc:
_logger.debug(
"Failed to check if frame was called by `caller`. Assuming that it was not.",
exc_info=exc,
)
return False
def _was_called_by(frame: FrameType, caller: Path) -> bool:
# https://stackoverflow.com/a/57712700/10606962
if Path(frame.f_code.co_filename) == caller:
if Path(frame.f_code.co_filename).resolve() == caller:
return True
while frame.f_back:
frame = frame.f_back
if Path(frame.f_code.co_filename) == caller:
if Path(frame.f_code.co_filename).resolve() == caller:
return True
return False
+24 -7
View File
@@ -82,14 +82,16 @@ class HTTPXRequest(BaseRequest):
With a finite pool timeout, you must expect :exc:`telegram.error.TimedOut`
exceptions to be thrown when more requests are made simultaneously than there are
connections in the connection pool!
http_version (:obj:`str`, optional): If ``"1.1"``, HTTP/1.1 will be used instead of HTTP/2.
Defaults to ``"2"``.
http_version (:obj:`str`, optional): If ``"2"``, HTTP/2 will be used instead of HTTP/1.1.
Defaults to ``"1.1"``.
.. versionadded:: 20.1
.. versionchanged:: 20.2
Reset the default version to 1.1.
"""
__slots__ = ("_client", "_client_kwargs")
__slots__ = ("_client", "_client_kwargs", "_http_version")
def __init__(
self,
@@ -99,8 +101,9 @@ class HTTPXRequest(BaseRequest):
write_timeout: Optional[float] = 5.0,
connect_timeout: Optional[float] = 5.0,
pool_timeout: Optional[float] = 1.0,
http_version: str = "2",
http_version: str = "1.1",
):
self._http_version = http_version
timeout = httpx.Timeout(
connect=connect_timeout,
read=read_timeout,
@@ -130,14 +133,28 @@ class HTTPXRequest(BaseRequest):
try:
self._client = self._build_client()
except ImportError as exc:
if "httpx[socks]" not in str(exc):
if "httpx[http2]" not in str(exc) and "httpx[socks]" not in str(exc):
raise exc
if "httpx[socks]" in str(exc):
raise RuntimeError(
"To use Socks5 proxies, PTB must be installed via `pip install "
"python-telegram-bot[socks]`."
) from exc
raise RuntimeError(
"To use Socks5 proxies, PTB must be installed via `pip install "
"python-telegram-bot[socks]`."
"To use HTTP/2, PTB must be installed via `pip install "
"python-telegram-bot[http2]`."
) from exc
@property
def http_version(self) -> str:
"""
:obj:`str`: Used HTTP version, see :paramref:`http_version`.
.. versionadded:: 20.2
"""
return self._http_version
def _build_client(self) -> httpx.AsyncClient:
return httpx.AsyncClient(**self._client_kwargs) # type: ignore[arg-type]
+14 -6
View File
@@ -24,6 +24,7 @@ from typing import List, Optional, Sequence, Tuple
from telegram._files.inputfile import InputFile
from telegram._files.inputmedia import InputMedia
from telegram._files.inputsticker import InputSticker
from telegram._telegramobject import TelegramObject
from telegram._utils.datetime import to_timestamp
from telegram._utils.enum import StringEnum
@@ -124,15 +125,22 @@ class RequestParameter:
else:
data.pop("media", None)
thumb = data.get("thumb", None)
if isinstance(thumb, InputFile):
if thumb.attach_uri:
data["thumb"] = thumb.attach_uri
thumbnail = data.get("thumbnail", None)
if isinstance(thumbnail, InputFile):
if thumbnail.attach_uri:
data["thumbnail"] = thumbnail.attach_uri
else:
data.pop("thumb", None)
return data, [value.media, thumb]
data.pop("thumbnail", None)
return data, [value.media, thumbnail]
return data, [value.media]
if isinstance(value, InputSticker) and isinstance(value.sticker, InputFile):
# We call to_dict and change the returned dict instead of overriding
# value.sticker in case the same value is reused for another request
data = value.to_dict()
data["sticker"] = value.sticker.attach_uri
return data, [value.sticker]
if isinstance(value, TelegramObject):
# Needs to be last, because InputMedia is a subclass of TelegramObject
return value.to_dict(), []
+2 -1
View File
@@ -47,7 +47,8 @@ class PTBRuntimeWarning(PTBUserWarning, RuntimeWarning):
# https://www.python.org/dev/peps/pep-0565/ recommends using a custom warning class derived from
# DeprecationWarning. We also subclass from TGUserWarning so users can easily 'switch off' warnings
# DeprecationWarning. We also subclass from PTBUserWarning so users can easily 'switch off'
# warnings
class PTBDeprecationWarning(PTBUserWarning, DeprecationWarning):
"""
Custom warning class for deprecations in this library.
+103
View File
@@ -0,0 +1,103 @@
==============
Testing in PTB
==============
PTB uses `pytest`_ for testing. To run the tests, you need to
have pytest installed along with a few other dependencies. You can find the list of dependencies
in the ``requirements-dev.txt`` file in the root of the repository.
Running tests
=============
To run the entire test suite, you can use the following command:
.. code-block:: bash
$ pytest
This will run all the tests, including the ones which make a request to the Telegram servers, which
may take a long time (total > 13 mins). To run only the tests that don't require a connection, you
can run the following command:
.. code-block:: bash
$ pytest -m no_req
Or alternatively, you can run the following command to run only the tests that require a connection:
.. code-block:: bash
$ pytest -m req
To further speed up the tests, you can run them in parallel using the ``-n`` flag (requires `pytest-xdist`_). But beware that
this will use multiple CPU cores on your machine. The ``--dist`` flag is used to specify how the
tests will be distributed across the cores. The ``loadgroup`` option is used to distribute the tests
such that tests marked with ``@pytest.mark.xdist_group("name")`` are run on the same core — important if you want avoid race conditions in some tests:
.. code-block:: bash
$ pytest -n auto --dist=loadgroup
This will result in a significant speedup, but may cause some tests to fail. If you want to run
the failed tests in isolation, you can use the ``--lf`` flag:
.. code-block:: bash
$ pytest --lf
Writing tests
=============
PTB has a separate test file for every file in the ``telegram.*`` namespace. Further, the tests for
the ``telegram`` module are split into two classes, based on whether the test methods in them make a
request or not. When writing tests, make sure to split them into these two classes, and make sure
to name the test class as: ``TestXXXWithoutRequest`` for tests that don't make a request, and ``TestXXXWithRequest`` for tests that do.
Writing tests is a creative process; allowing you to design your test however you'd like, but there
are a few conventions that you should follow:
- Each new test class needs a ``test_slot_behaviour``, ``test_to_dict``, ``test_de_json`` and
``test_equality`` (in most cases).
- Make use of pytest's fixtures and parametrize wherever possible. Having knowledge of pytest's
tooling can help you as well. You can look at the existing tests for examples and inspiration.
- New fixtures should go into ``conftest.py``. New auxiliary functions and classes, used either directly in the tests or in the fixtures, should go into the ``tests/auxil`` directory.
If you have made some API changes, you may want to run ``test_official`` to validate that the changes are
complete and correct. To run it, export an environment variable first:
.. code-block:: bash
$ export TEST_OFFICIAL=true
and then run ``pytest tests/test_official.py``.
We also have another marker, ``@pytest.mark.dev``, which you can add to tests that you want to run selectively.
Use as follows:
.. code-block:: bash
$ pytest -m dev
Bots used in tests
==================
If you run the tests locally, the test setup will use one of the two public bots available. Which
bot of the two gets chosen for the test session is random. Whereas when the tests on the
Github Actions CI are run, the test setup allocates a different, but same bot for every combination of Python version and
OS.
Thus, number of bots used for testing locally is 2 (called as fallback bots), and on the CI,
its [3.7, 3.8, 3.9, 3.10, 3.11] x [ubuntu-latest, macos-latest, windows-latest] = 15. Bringing the
total number of bots used for testing to 17.
That's it! If you have any questions, feel free to ask them in the `PTB dev
group`_.
.. _pytest: https://docs.pytest.org/en/stable/
.. _pytest-xdist: https://pypi.org/project/pytest-xdist/
.. _PTB dev group: https://t.me/pythontelegrambotgroup
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import os
from pathlib import Path
@@ -30,26 +31,26 @@ from tests.auxil.bot_method_checks import (
check_shortcut_call,
check_shortcut_signature,
)
from tests.conftest import data_file
from tests.auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs
from tests.auxil.files import data_file
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="function")
def animation_file():
f = data_file("game.gif").open("rb")
yield f
f.close()
@pytest.fixture(scope="class")
async def animation(bot, chat_id):
with data_file("game.gif").open("rb") as f:
thumb = data_file("thumb.jpg")
yield f
@pytest.fixture(scope="module")
async def animation(bot, chat_id):
with data_file("game.gif").open("rb") as f, data_file("thumb.jpg").open("rb") as thumb:
return (
await bot.send_animation(chat_id, animation=f, read_timeout=50, thumb=thumb.open("rb"))
await bot.send_animation(chat_id, animation=f, read_timeout=50, thumbnail=thumb)
).animation
class TestAnimation:
class TestAnimationBase:
animation_file_id = "CgADAQADngIAAuyVeEez0xRovKi9VAI"
animation_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e"
width = 320
@@ -63,7 +64,9 @@ class TestAnimation:
file_size = 5859
caption = "Test *animation*"
def test_slot_behaviour(self, animation, mro_slots):
class TestAnimationWithoutRequest(TestAnimationBase):
def test_slot_behaviour(self, animation):
for attr in animation.__slots__:
assert getattr(animation, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(animation)) == len(set(mro_slots(animation))), "duplicate slot"
@@ -78,9 +81,138 @@ class TestAnimation:
def test_expected_values(self, animation):
assert animation.mime_type == self.mime_type
assert animation.file_name.startswith("game.gif") == self.file_name.startswith("game.gif")
assert isinstance(animation.thumb, PhotoSize)
assert isinstance(animation.thumbnail, PhotoSize)
@pytest.mark.flaky(3, 1)
def test_thumb_property_deprecation_warning(self, recwarn):
animation = Animation(
self.animation_file_id,
self.animation_file_unique_id,
thumb=object(),
width=self.width,
height=self.height,
duration=self.duration,
)
assert animation.thumb is animation.thumbnail
check_thumb_deprecation_warnings_for_args_and_attrs(recwarn, __file__)
def test_de_json(self, bot, animation):
json_dict = {
"file_id": self.animation_file_id,
"file_unique_id": self.animation_file_unique_id,
"width": self.width,
"height": self.height,
"duration": self.duration,
"thumbnail": animation.thumbnail.to_dict(),
"file_name": self.file_name,
"mime_type": self.mime_type,
"file_size": self.file_size,
}
animation = Animation.de_json(json_dict, bot)
assert animation.api_kwargs == {}
assert animation.file_id == self.animation_file_id
assert animation.file_unique_id == self.animation_file_unique_id
assert animation.file_name == self.file_name
assert animation.mime_type == self.mime_type
assert animation.file_size == self.file_size
def test_to_dict(self, animation):
animation_dict = animation.to_dict()
assert isinstance(animation_dict, dict)
assert animation_dict["file_id"] == animation.file_id
assert animation_dict["file_unique_id"] == animation.file_unique_id
assert animation_dict["width"] == animation.width
assert animation_dict["height"] == animation.height
assert animation_dict["duration"] == animation.duration
assert animation_dict["thumbnail"] == animation.thumbnail.to_dict()
assert animation_dict["file_name"] == animation.file_name
assert animation_dict["mime_type"] == animation.mime_type
assert animation_dict["file_size"] == animation.file_size
def test_equality(self):
a = Animation(
self.animation_file_id,
self.animation_file_unique_id,
self.height,
self.width,
self.duration,
)
b = Animation("", self.animation_file_unique_id, self.height, self.width, self.duration)
d = Animation("", "", 0, 0, 0)
e = Voice(self.animation_file_id, self.animation_file_unique_id, 0)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
async def test_send_animation_custom_filename(self, bot, chat_id, animation_file, monkeypatch):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return list(request_data.multipart_data.values())[0][0] == "custom_filename"
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_animation(chat_id, animation_file, filename="custom_filename")
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_animation_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = (
data.get("animation") == expected and data.get("thumbnail") == expected
)
else:
test_flag = isinstance(data.get("animation"), InputFile) and isinstance(
data.get("thumbnail"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_animation(chat_id, file, thumbnail=file)
assert test_flag
finally:
bot._local_mode = False
async def test_send_with_animation(self, monkeypatch, bot, chat_id, animation):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.json_parameters["animation"] == animation.file_id
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_animation(animation=animation, chat_id=chat_id)
async def test_send_animation_with_local_files_throws_error_with_different_thumb_and_thumbnail(
self, bot, chat_id
):
file = data_file("telegram.jpg")
different_file = data_file("telegram_no_standard_header.jpg")
with pytest.raises(ValueError, match="different entities as 'thumb' and 'thumbnail'"):
await bot.send_animation(chat_id, file, thumbnail=file, thumb=different_file)
async def test_get_file_instance_method(self, monkeypatch, animation):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == animation.file_id
assert check_shortcut_signature(Animation.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(animation.get_file, animation.get_bot(), "get_file")
assert await check_defaults_handling(animation.get_file, animation.get_bot())
monkeypatch.setattr(animation.get_bot(), "get_file", make_assertion)
assert await animation.get_file()
class TestAnimationWithRequest(TestAnimationBase):
async def test_send_all_args(self, bot, chat_id, animation_file, animation, thumb_file):
message = await bot.send_animation(
chat_id,
@@ -92,7 +224,7 @@ class TestAnimation:
parse_mode="Markdown",
disable_notification=False,
protect_content=True,
thumb=thumb_file,
thumbnail=thumb_file,
has_spoiler=True,
)
@@ -104,25 +236,14 @@ class TestAnimation:
assert message.animation.file_name == animation.file_name
assert message.animation.mime_type == animation.mime_type
assert message.animation.file_size == animation.file_size
assert message.animation.thumb.width == self.width
assert message.animation.thumb.height == self.height
assert message.animation.thumbnail.width == self.width
assert message.animation.thumbnail.height == self.height
assert message.has_protected_content
try:
assert message.has_media_spoiler
except AssertionError:
pytest.xfail("This is a bug on Telegram's end")
@pytest.mark.flaky(3, 1)
async def test_send_animation_custom_filename(self, bot, chat_id, animation_file, monkeypatch):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return list(request_data.multipart_data.values())[0][0] == "custom_filename"
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_animation(chat_id, animation_file, filename="custom_filename")
monkeypatch.delattr(bot.request, "post")
@pytest.mark.flaky(3, 1)
async def test_get_and_download(self, bot, animation):
path = Path("game.gif")
if path.is_file():
@@ -130,14 +251,11 @@ class TestAnimation:
new_file = await bot.get_file(animation.file_id)
assert new_file.file_id == animation.file_id
assert new_file.file_path.startswith("https://")
new_filepath = await new_file.download_to_drive("game.gif")
assert new_filepath.is_file()
@pytest.mark.flaky(3, 1)
async def test_send_animation_url_file(self, bot, chat_id, animation):
message = await bot.send_animation(
chat_id=chat_id, animation=self.animation_file_url, caption=self.caption
@@ -157,7 +275,6 @@ class TestAnimation:
) == animation.file_name.startswith("game.gif")
assert message.animation.mime_type == animation.mime_type
@pytest.mark.flaky(3, 1)
async def test_send_animation_caption_entities(self, bot, chat_id, animation):
test_string = "Italic Bold Code"
entities = [
@@ -172,7 +289,6 @@ class TestAnimation:
assert message.caption == test_string
assert message.caption_entities == tuple(entities)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_animation_default_parse_mode_1(self, default_bot, chat_id, animation_file):
test_string = "Italic Bold Code"
@@ -184,7 +300,6 @@ class TestAnimation:
assert message.caption_markdown == test_markdown_string
assert message.caption == test_string
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_animation_default_parse_mode_2(self, default_bot, chat_id, animation_file):
test_markdown_string = "_Italic_ *Bold* `Code`"
@@ -195,7 +310,6 @@ class TestAnimation:
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_animation_default_parse_mode_3(self, default_bot, chat_id, animation_file):
test_markdown_string = "_Italic_ *Bold* `Code`"
@@ -206,32 +320,6 @@ class TestAnimation:
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_animation_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("animation") == expected and data.get("thumb") == expected
else:
test_flag = isinstance(data.get("animation"), InputFile) and isinstance(
data.get("thumb"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_animation(chat_id, file, thumb=file)
assert test_flag
monkeypatch.delattr(bot, "_post")
finally:
bot._local_mode = False
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize(
"default_bot,custom",
[
@@ -265,72 +353,26 @@ class TestAnimation:
chat_id, animation, reply_to_message_id=reply_to_message.message_id
)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_animation_default_protect_content(self, default_bot, chat_id, animation):
animation_protected = await default_bot.send_animation(chat_id, animation)
assert animation_protected.has_protected_content
ani_unprotected = await default_bot.send_animation(
chat_id, animation, protect_content=False
tasks = asyncio.gather(
default_bot.send_animation(chat_id, animation),
default_bot.send_animation(chat_id, animation, protect_content=False),
)
assert not ani_unprotected.has_protected_content
anim_protected, anim_unprotected = await tasks
assert anim_protected.has_protected_content
assert not anim_unprotected.has_protected_content
@pytest.mark.flaky(3, 1)
async def test_resend(self, bot, chat_id, animation):
message = await bot.send_animation(chat_id, animation.file_id)
assert message.animation == animation
async def test_send_with_animation(self, monkeypatch, bot, chat_id, animation):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.json_parameters["animation"] == animation.file_id
monkeypatch.setattr(bot.request, "post", make_assertion)
message = await bot.send_animation(animation=animation, chat_id=chat_id)
assert message
def test_de_json(self, bot, animation):
json_dict = {
"file_id": self.animation_file_id,
"file_unique_id": self.animation_file_unique_id,
"width": self.width,
"height": self.height,
"duration": self.duration,
"thumb": animation.thumb.to_dict(),
"file_name": self.file_name,
"mime_type": self.mime_type,
"file_size": self.file_size,
}
animation = Animation.de_json(json_dict, bot)
assert animation.api_kwargs == {}
assert animation.file_id == self.animation_file_id
assert animation.file_unique_id == self.animation_file_unique_id
assert animation.file_name == self.file_name
assert animation.mime_type == self.mime_type
assert animation.file_size == self.file_size
def test_to_dict(self, animation):
animation_dict = animation.to_dict()
assert isinstance(animation_dict, dict)
assert animation_dict["file_id"] == animation.file_id
assert animation_dict["file_unique_id"] == animation.file_unique_id
assert animation_dict["width"] == animation.width
assert animation_dict["height"] == animation.height
assert animation_dict["duration"] == animation.duration
assert animation_dict["thumb"] == animation.thumb.to_dict()
assert animation_dict["file_name"] == animation.file_name
assert animation_dict["mime_type"] == animation.mime_type
assert animation_dict["file_size"] == animation.file_size
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file(self, bot, chat_id):
animation_file = open(os.devnull, "rb")
with pytest.raises(TelegramError):
await bot.send_animation(chat_id=chat_id, animation=animation_file)
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file_id(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_animation(chat_id=chat_id, animation="")
@@ -338,36 +380,3 @@ class TestAnimation:
async def test_error_send_without_required_args(self, bot, chat_id):
with pytest.raises(TypeError):
await bot.send_animation(chat_id=chat_id)
async def test_get_file_instance_method(self, monkeypatch, animation):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == animation.file_id
assert check_shortcut_signature(Animation.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(animation.get_file, animation.get_bot(), "get_file")
assert await check_defaults_handling(animation.get_file, animation.get_bot())
monkeypatch.setattr(animation.get_bot(), "get_file", make_assertion)
assert await animation.get_file()
def test_equality(self):
a = Animation(
self.animation_file_id,
self.animation_file_unique_id,
self.height,
self.width,
self.duration,
)
b = Animation("", self.animation_file_unique_id, self.height, self.width, self.duration)
d = Animation("", "", 0, 0, 0)
e = Voice(self.animation_file_id, self.animation_file_unique_id, 0)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
+214 -211
View File
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import os
from pathlib import Path
@@ -30,25 +31,24 @@ from tests.auxil.bot_method_checks import (
check_shortcut_call,
check_shortcut_signature,
)
from tests.conftest import data_file
from tests.auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs
from tests.auxil.files import data_file
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="function")
def audio_file():
with open(data_file("telegram.mp3"), "rb") as f:
with data_file("telegram.mp3").open("rb") as f:
yield f
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
async def audio(bot, chat_id):
with data_file("telegram.mp3").open("rb") as f:
thumb = data_file("thumb.jpg")
return (
await bot.send_audio(chat_id, audio=f, read_timeout=50, thumb=thumb.open("rb"))
).audio
with data_file("telegram.mp3").open("rb") as f, data_file("thumb.jpg").open("rb") as thumb:
return (await bot.send_audio(chat_id, audio=f, read_timeout=50, thumbnail=thumb)).audio
class TestAudio:
class TestAudioBase:
caption = "Test *audio*"
performer = "Leandro Toledo"
title = "Teste"
@@ -65,7 +65,9 @@ class TestAudio:
audio_file_id = "5a3128a4d2a04750b5b58397f3b5e812"
audio_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e"
def test_slot_behaviour(self, audio, mro_slots):
class TestAudioWithoutRequest(TestAudioBase):
def test_slot_behaviour(self, audio):
for attr in audio.__slots__:
assert getattr(audio, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(audio)) == len(set(mro_slots(audio))), "duplicate slot"
@@ -84,179 +86,14 @@ class TestAudio:
assert audio.title is None
assert audio.mime_type == self.mime_type
assert audio.file_size == self.file_size
assert audio.thumb.file_size == self.thumb_file_size
assert audio.thumb.width == self.thumb_width
assert audio.thumb.height == self.thumb_height
assert audio.thumbnail.file_size == self.thumb_file_size
assert audio.thumbnail.width == self.thumb_width
assert audio.thumbnail.height == self.thumb_height
@pytest.mark.flaky(3, 1)
async def test_send_all_args(self, bot, chat_id, audio_file, thumb_file):
message = await bot.send_audio(
chat_id,
audio=audio_file,
caption=self.caption,
duration=self.duration,
performer=self.performer,
title=self.title,
disable_notification=False,
protect_content=True,
parse_mode="Markdown",
thumb=thumb_file,
)
assert message.caption == self.caption.replace("*", "")
assert isinstance(message.audio, Audio)
assert isinstance(message.audio.file_id, str)
assert isinstance(message.audio.file_unique_id, str)
assert message.audio.file_unique_id is not None
assert message.audio.file_id is not None
assert message.audio.duration == self.duration
assert message.audio.performer == self.performer
assert message.audio.title == self.title
assert message.audio.file_name == self.file_name
assert message.audio.mime_type == self.mime_type
assert message.audio.file_size == self.file_size
assert message.audio.thumb.file_size == self.thumb_file_size
assert message.audio.thumb.width == self.thumb_width
assert message.audio.thumb.height == self.thumb_height
assert message.has_protected_content
@pytest.mark.flaky(3, 1)
async def test_send_audio_custom_filename(self, bot, chat_id, audio_file, monkeypatch):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return list(request_data.multipart_data.values())[0][0] == "custom_filename"
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_audio(chat_id, audio_file, filename="custom_filename")
@pytest.mark.flaky(3, 1)
async def test_get_and_download(self, bot, audio):
path = Path("telegram.mp3")
if path.is_file():
path.unlink()
new_file = await bot.get_file(audio.file_id)
assert new_file.file_size == self.file_size
assert new_file.file_id == audio.file_id
assert new_file.file_unique_id == audio.file_unique_id
assert str(new_file.file_path).startswith("https://")
await new_file.download_to_drive("telegram.mp3")
assert path.is_file()
@pytest.mark.flaky(3, 1)
async def test_send_mp3_url_file(self, bot, chat_id, audio):
message = await bot.send_audio(
chat_id=chat_id, audio=self.audio_file_url, caption=self.caption
)
assert message.caption == self.caption
assert isinstance(message.audio, Audio)
assert isinstance(message.audio.file_id, str)
assert isinstance(message.audio.file_unique_id, str)
assert message.audio.file_unique_id is not None
assert message.audio.file_id is not None
assert message.audio.duration == audio.duration
assert message.audio.mime_type == audio.mime_type
assert message.audio.file_size == audio.file_size
@pytest.mark.flaky(3, 1)
async def test_resend(self, bot, chat_id, audio):
message = await bot.send_audio(chat_id=chat_id, audio=audio.file_id)
assert message.audio == audio
async def test_send_with_audio(self, monkeypatch, bot, chat_id, audio):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.json_parameters["audio"] == audio.file_id
monkeypatch.setattr(bot.request, "post", make_assertion)
message = await bot.send_audio(audio=audio, chat_id=chat_id)
assert message
@pytest.mark.flaky(3, 1)
async def test_send_audio_caption_entities(self, bot, chat_id, audio):
test_string = "Italic Bold Code"
entities = [
MessageEntity(MessageEntity.ITALIC, 0, 6),
MessageEntity(MessageEntity.ITALIC, 7, 4),
MessageEntity(MessageEntity.ITALIC, 12, 4),
]
message = await bot.send_audio(
chat_id, audio, caption=test_string, caption_entities=entities
)
assert message.caption == test_string
assert message.caption_entities == tuple(entities)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_audio_default_parse_mode_1(self, default_bot, chat_id, audio_file):
test_string = "Italic Bold Code"
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_audio(chat_id, audio_file, caption=test_markdown_string)
assert message.caption_markdown == test_markdown_string
assert message.caption == test_string
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_audio_default_parse_mode_2(self, default_bot, chat_id, audio_file):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_audio(
chat_id, audio_file, caption=test_markdown_string, parse_mode=None
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_audio_default_parse_mode_3(self, default_bot, chat_id, audio_file):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_audio(
chat_id, audio_file, caption=test_markdown_string, parse_mode="HTML"
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_audio_default_protect_content(self, default_bot, chat_id, audio):
protected_audio = await default_bot.send_audio(chat_id, audio)
assert protected_audio.has_protected_content
unprotected = await default_bot.send_audio(chat_id, audio, protect_content=False)
assert not unprotected.has_protected_content
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_audio_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("audio") == expected and data.get("thumb") == expected
else:
test_flag = isinstance(data.get("audio"), InputFile) and isinstance(
data.get("thumb"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_audio(chat_id, file, thumb=file)
assert test_flag
monkeypatch.delattr(bot, "_post")
finally:
bot._local_mode = False
def test_thumb_property_deprecation_warning(self, recwarn):
audio = Audio(self.audio_file_id, self.audio_file_unique_id, self.duration, thumb=object())
assert audio.thumb is audio.thumbnail
check_thumb_deprecation_warnings_for_args_and_attrs(recwarn, __file__)
def test_de_json(self, bot, audio):
json_dict = {
@@ -268,7 +105,7 @@ class TestAudio:
"file_name": self.file_name,
"mime_type": self.mime_type,
"file_size": self.file_size,
"thumb": audio.thumb.to_dict(),
"thumbnail": audio.thumbnail.to_dict(),
}
json_audio = Audio.de_json(json_dict, bot)
assert json_audio.api_kwargs == {}
@@ -281,7 +118,7 @@ class TestAudio:
assert json_audio.file_name == self.file_name
assert json_audio.mime_type == self.mime_type
assert json_audio.file_size == self.file_size
assert json_audio.thumb == audio.thumb
assert json_audio.thumbnail == audio.thumbnail
def test_to_dict(self, audio):
audio_dict = audio.to_dict()
@@ -294,33 +131,6 @@ class TestAudio:
assert audio_dict["file_size"] == audio.file_size
assert audio_dict["file_name"] == audio.file_name
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file(self, bot, chat_id):
audio_file = open(os.devnull, "rb")
with pytest.raises(TelegramError):
await bot.send_audio(chat_id=chat_id, audio=audio_file)
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file_id(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_audio(chat_id=chat_id, audio="")
async def test_error_send_without_required_args(self, bot, chat_id):
with pytest.raises(TypeError):
await bot.send_audio(chat_id=chat_id)
async def test_get_file_instance_method(self, monkeypatch, audio):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == audio.file_id
assert check_shortcut_signature(Audio.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(audio.get_file, audio.get_bot(), "get_file")
assert await check_defaults_handling(audio.get_file, audio.get_bot())
monkeypatch.setattr(audio._bot, "get_file", make_assertion)
assert await audio.get_file()
def test_equality(self, audio):
a = Audio(audio.file_id, audio.file_unique_id, audio.duration)
b = Audio("", audio.file_unique_id, audio.duration)
@@ -340,3 +150,196 @@ class TestAudio:
assert a != e
assert hash(a) != hash(e)
async def test_send_with_audio(self, monkeypatch, bot, chat_id, audio):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.json_parameters["audio"] == audio.file_id
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_audio(audio=audio, chat_id=chat_id)
async def test_send_audio_custom_filename(self, bot, chat_id, audio_file, monkeypatch):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return list(request_data.multipart_data.values())[0][0] == "custom_filename"
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_audio(chat_id, audio_file, filename="custom_filename")
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_audio_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("audio") == expected and data.get("thumbnail") == expected
else:
test_flag = isinstance(data.get("audio"), InputFile) and isinstance(
data.get("thumbnail"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_audio(chat_id, file, thumbnail=file)
assert test_flag
finally:
bot._local_mode = False
async def test_send_audio_with_local_files_throws_error_with_different_thumb_and_thumbnail(
self, bot, chat_id
):
file = data_file("telegram.jpg")
different_file = data_file("telegram_no_standard_header.jpg")
with pytest.raises(ValueError, match="different entities as 'thumb' and 'thumbnail'"):
await bot.send_audio(chat_id, file, thumbnail=file, thumb=different_file)
async def test_get_file_instance_method(self, monkeypatch, audio):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == audio.file_id
assert check_shortcut_signature(Audio.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(audio.get_file, audio.get_bot(), "get_file")
assert await check_defaults_handling(audio.get_file, audio.get_bot())
monkeypatch.setattr(audio._bot, "get_file", make_assertion)
assert await audio.get_file()
class TestAudioWithRequest(TestAudioBase):
async def test_send_all_args(self, bot, chat_id, audio_file, thumb_file):
message = await bot.send_audio(
chat_id,
audio=audio_file,
caption=self.caption,
duration=self.duration,
performer=self.performer,
title=self.title,
disable_notification=False,
protect_content=True,
parse_mode="Markdown",
thumbnail=thumb_file,
)
assert message.caption == self.caption.replace("*", "")
assert isinstance(message.audio, Audio)
assert isinstance(message.audio.file_id, str)
assert isinstance(message.audio.file_unique_id, str)
assert message.audio.file_unique_id is not None
assert message.audio.file_id is not None
assert message.audio.duration == self.duration
assert message.audio.performer == self.performer
assert message.audio.title == self.title
assert message.audio.file_name == self.file_name
assert message.audio.mime_type == self.mime_type
assert message.audio.file_size == self.file_size
assert message.audio.thumbnail.file_size == self.thumb_file_size
assert message.audio.thumbnail.width == self.thumb_width
assert message.audio.thumbnail.height == self.thumb_height
assert message.has_protected_content
async def test_get_and_download(self, bot, chat_id, audio):
path = Path("telegram.mp3")
if path.is_file():
path.unlink()
new_file = await bot.get_file(audio.file_id)
assert new_file.file_size == self.file_size
assert new_file.file_unique_id == audio.file_unique_id
assert str(new_file.file_path).startswith("https://")
await new_file.download_to_drive("telegram.mp3")
assert path.is_file()
async def test_send_mp3_url_file(self, bot, chat_id, audio):
message = await bot.send_audio(
chat_id=chat_id, audio=self.audio_file_url, caption=self.caption
)
assert message.caption == self.caption
assert isinstance(message.audio, Audio)
assert isinstance(message.audio.file_id, str)
assert isinstance(message.audio.file_unique_id, str)
assert message.audio.file_unique_id is not None
assert message.audio.file_id is not None
assert message.audio.duration == audio.duration
assert message.audio.mime_type == audio.mime_type
assert message.audio.file_size == audio.file_size
async def test_send_audio_caption_entities(self, bot, chat_id, audio):
test_string = "Italic Bold Code"
entities = [
MessageEntity(MessageEntity.ITALIC, 0, 6),
MessageEntity(MessageEntity.ITALIC, 7, 4),
MessageEntity(MessageEntity.ITALIC, 12, 4),
]
message = await bot.send_audio(
chat_id, audio, caption=test_string, caption_entities=entities
)
assert message.caption == test_string
assert message.caption_entities == tuple(entities)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_audio_default_parse_mode_1(self, default_bot, chat_id, audio_file):
test_string = "Italic Bold Code"
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_audio(chat_id, audio_file, caption=test_markdown_string)
assert message.caption_markdown == test_markdown_string
assert message.caption == test_string
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_audio_default_parse_mode_2(self, default_bot, chat_id, audio_file):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_audio(
chat_id, audio_file, caption=test_markdown_string, parse_mode=None
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_audio_default_parse_mode_3(self, default_bot, chat_id, audio_file):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_audio(
chat_id, audio_file, caption=test_markdown_string, parse_mode="HTML"
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_audio_default_protect_content(self, default_bot, chat_id, audio):
tasks = asyncio.gather(
default_bot.send_audio(chat_id, audio),
default_bot.send_audio(chat_id, audio, protect_content=False),
)
protected, unprotected = await tasks
assert protected.has_protected_content
assert not unprotected.has_protected_content
async def test_resend(self, bot, chat_id, audio):
message = await bot.send_audio(chat_id=chat_id, audio=audio.file_id)
assert message.audio == audio
async def test_error_send_empty_file(self, bot, chat_id):
audio_file = open(os.devnull, "rb")
with pytest.raises(TelegramError):
await bot.send_audio(chat_id=chat_id, audio=audio_file)
async def test_error_send_empty_file_id(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_audio(chat_id=chat_id, audio="")
async def test_error_send_without_required_args(self, bot, chat_id):
with pytest.raises(TypeError):
await bot.send_audio(chat_id=chat_id)
@@ -17,6 +17,7 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import os
from pathlib import Path
@@ -30,17 +31,18 @@ from tests.auxil.bot_method_checks import (
check_shortcut_call,
check_shortcut_signature,
)
from tests.conftest import data_file, expect_bad_request
from tests.auxil.files import data_file
from tests.auxil.networking import expect_bad_request
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="function")
def chatphoto_file():
f = data_file("telegram.jpg").open("rb")
yield f
f.close()
with data_file("telegram.jpg").open("rb") as f:
yield f
@pytest.fixture(scope="function")
@pytest.fixture(scope="module")
async def chat_photo(bot, super_group_id):
async def func():
return (await bot.get_chat(super_group_id, read_timeout=50)).photo
@@ -50,61 +52,20 @@ async def chat_photo(bot, super_group_id):
)
class TestChatPhoto:
class TestChatPhotoBase:
chatphoto_small_file_id = "smallCgADAQADngIAAuyVeEez0xRovKi9VAI"
chatphoto_big_file_id = "bigCgADAQADngIAAuyVeEez0xRovKi9VAI"
chatphoto_small_file_unique_id = "smalladc3145fd2e84d95b64d68eaa22aa33e"
chatphoto_big_file_unique_id = "bigadc3145fd2e84d95b64d68eaa22aa33e"
chatphoto_file_url = "https://python-telegram-bot.org/static/testfiles/telegram.jpg"
def test_slot_behaviour(self, chat_photo, mro_slots):
class TestChatPhotoWithoutRequest(TestChatPhotoBase):
def test_slot_behaviour(self, chat_photo):
for attr in chat_photo.__slots__:
assert getattr(chat_photo, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(chat_photo)) == len(set(mro_slots(chat_photo))), "duplicate slot"
@pytest.mark.flaky(3, 1)
async def test_send_all_args(
self, bot, super_group_id, chatphoto_file, chat_photo, thumb_file
):
async def func():
assert await bot.set_chat_photo(super_group_id, chatphoto_file)
await expect_bad_request(
func, "Type of file mismatch", "Telegram did not accept the file."
)
@pytest.mark.flaky(3, 1)
async def test_get_and_download(self, bot, chat_photo):
jpg_file = Path("telegram.jpg")
if jpg_file.is_file():
jpg_file.unlink()
new_file = await bot.get_file(chat_photo.small_file_id)
assert new_file.file_unique_id == chat_photo.small_file_unique_id
assert new_file.file_path.startswith("https://")
await new_file.download_to_drive(jpg_file)
assert jpg_file.is_file()
new_file = await bot.get_file(chat_photo.big_file_id)
assert new_file.file_unique_id == chat_photo.big_file_unique_id
assert new_file.file_path.startswith("https://")
await new_file.download_to_drive(jpg_file)
assert jpg_file.is_file()
async def test_send_with_chat_photo(self, monkeypatch, bot, super_group_id, chat_photo):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.parameters["photo"] == chat_photo.to_dict()
monkeypatch.setattr(bot.request, "post", make_assertion)
message = await bot.set_chat_photo(photo=chat_photo, chat_id=super_group_id)
assert message
def test_de_json(self, bot, chat_photo):
json_dict = {
"small_file_id": self.chatphoto_small_file_id,
@@ -128,46 +89,6 @@ class TestChatPhoto:
assert chat_photo_dict["small_file_unique_id"] == chat_photo.small_file_unique_id
assert chat_photo_dict["big_file_unique_id"] == chat_photo.big_file_unique_id
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file(self, bot, super_group_id):
chatphoto_file = open(os.devnull, "rb")
with pytest.raises(TelegramError):
await bot.set_chat_photo(chat_id=super_group_id, photo=chatphoto_file)
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file_id(self, bot, super_group_id):
with pytest.raises(TelegramError):
await bot.set_chat_photo(chat_id=super_group_id, photo="")
async def test_error_send_without_required_args(self, bot, super_group_id):
with pytest.raises(TypeError):
await bot.set_chat_photo(chat_id=super_group_id)
async def test_get_small_file_instance_method(self, monkeypatch, chat_photo):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == chat_photo.small_file_id
assert check_shortcut_signature(ChatPhoto.get_small_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(
chat_photo.get_small_file, chat_photo.get_bot(), "get_file"
)
assert await check_defaults_handling(chat_photo.get_small_file, chat_photo.get_bot())
monkeypatch.setattr(chat_photo.get_bot(), "get_file", make_assertion)
assert await chat_photo.get_small_file()
async def test_get_big_file_instance_method(self, monkeypatch, chat_photo):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == chat_photo.big_file_id
assert check_shortcut_signature(ChatPhoto.get_big_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(chat_photo.get_big_file, chat_photo.get_bot(), "get_file")
assert await check_defaults_handling(chat_photo.get_big_file, chat_photo.get_bot())
monkeypatch.setattr(chat_photo.get_bot(), "get_file", make_assertion)
assert await chat_photo.get_big_file()
def test_equality(self):
a = ChatPhoto(
self.chatphoto_small_file_id,
@@ -199,3 +120,80 @@ class TestChatPhoto:
assert a != e
assert hash(a) != hash(e)
async def test_send_with_chat_photo(self, monkeypatch, bot, super_group_id, chat_photo):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.parameters["photo"] == chat_photo.to_dict()
monkeypatch.setattr(bot.request, "post", make_assertion)
message = await bot.set_chat_photo(photo=chat_photo, chat_id=super_group_id)
assert message
async def test_get_small_file_instance_method(self, monkeypatch, chat_photo):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == chat_photo.small_file_id
assert check_shortcut_signature(ChatPhoto.get_small_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(
chat_photo.get_small_file, chat_photo.get_bot(), "get_file"
)
assert await check_defaults_handling(chat_photo.get_small_file, chat_photo.get_bot())
monkeypatch.setattr(chat_photo.get_bot(), "get_file", make_assertion)
assert await chat_photo.get_small_file()
async def test_get_big_file_instance_method(self, monkeypatch, chat_photo):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == chat_photo.big_file_id
assert check_shortcut_signature(ChatPhoto.get_big_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(chat_photo.get_big_file, chat_photo.get_bot(), "get_file")
assert await check_defaults_handling(chat_photo.get_big_file, chat_photo.get_bot())
monkeypatch.setattr(chat_photo.get_bot(), "get_file", make_assertion)
assert await chat_photo.get_big_file()
class TestChatPhotoWithRequest:
async def test_get_and_download(self, bot, chat_photo):
jpg_file = Path("telegram.jpg")
if jpg_file.is_file():
jpg_file.unlink()
tasks = {bot.get_file(chat_photo.small_file_id), bot.get_file(chat_photo.big_file_id)}
asserts = []
for task in asyncio.as_completed(tasks):
file = await task
if file.file_unique_id == chat_photo.small_file_unique_id:
asserts.append("small")
elif file.file_unique_id == chat_photo.big_file_unique_id:
asserts.append("big")
assert file.file_path.startswith("https://")
await file.download_to_drive(jpg_file)
assert jpg_file.is_file()
assert "small" in asserts and "big" in asserts
async def test_send_all_args(self, bot, super_group_id, chatphoto_file):
async def func():
assert await bot.set_chat_photo(super_group_id, chatphoto_file)
await expect_bad_request(
func, "Type of file mismatch", "Telegram did not accept the file."
)
async def test_error_send_empty_file(self, bot, super_group_id):
chatphoto_file = open(os.devnull, "rb")
with pytest.raises(TelegramError):
await bot.set_chat_photo(chat_id=super_group_id, photo=chatphoto_file)
async def test_error_send_empty_file_id(self, bot, super_group_id):
with pytest.raises(TelegramError):
await bot.set_chat_photo(chat_id=super_group_id, photo="")
async def test_error_send_without_required_args(self, bot, super_group_id):
with pytest.raises(TypeError):
await bot.set_chat_photo(chat_id=super_group_id)
@@ -17,30 +17,35 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import pytest
from telegram import Contact, Voice
from telegram.error import BadRequest
from telegram.request import RequestData
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def contact():
return Contact(
TestContact.phone_number,
TestContact.first_name,
TestContact.last_name,
TestContact.user_id,
TestContactBase.phone_number,
TestContactBase.first_name,
TestContactBase.last_name,
TestContactBase.user_id,
)
class TestContact:
class TestContactBase:
phone_number = "+11234567890"
first_name = "Leandro"
last_name = "Toledo"
user_id = 23
def test_slot_behaviour(self, contact, mro_slots):
class TestContactWithoutRequest(TestContactBase):
def test_slot_behaviour(self, contact):
for attr in contact.__slots__:
assert getattr(contact, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(contact)) == len(set(mro_slots(contact))), "duplicate slot"
@@ -68,6 +73,48 @@ class TestContact:
assert contact.last_name == self.last_name
assert contact.user_id == self.user_id
def test_to_dict(self, contact):
contact_dict = contact.to_dict()
assert isinstance(contact_dict, dict)
assert contact_dict["phone_number"] == contact.phone_number
assert contact_dict["first_name"] == contact.first_name
assert contact_dict["last_name"] == contact.last_name
assert contact_dict["user_id"] == contact.user_id
def test_equality(self):
a = Contact(self.phone_number, self.first_name)
b = Contact(self.phone_number, self.first_name)
c = Contact(self.phone_number, "")
d = Contact("", self.first_name)
e = Voice("", "unique_id", 0)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
async def test_send_contact_without_required(self, bot, chat_id):
with pytest.raises(ValueError, match="Either contact or phone_number and first_name"):
await bot.send_contact(chat_id=chat_id)
async def test_send_mutually_exclusive(self, bot, chat_id, contact):
with pytest.raises(ValueError, match="Not both"):
await bot.send_contact(
chat_id=chat_id,
contact=contact,
phone_number=contact.phone_number,
first_name=contact.first_name,
)
async def test_send_with_contact(self, monkeypatch, bot, chat_id, contact):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
data = request_data.json_parameters
@@ -77,10 +124,10 @@ class TestContact:
return phone and first and last
monkeypatch.setattr(bot.request, "post", make_assertion)
message = await bot.send_contact(contact=contact, chat_id=chat_id)
assert message
assert await bot.send_contact(contact=contact, chat_id=chat_id)
@pytest.mark.flaky(3, 1)
class TestContactWithRequest(TestContactBase):
@pytest.mark.parametrize(
"default_bot,custom",
[
@@ -114,54 +161,12 @@ class TestContact:
chat_id, contact=contact, reply_to_message_id=reply_to_message.message_id
)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_contact_default_protect_content(self, chat_id, default_bot, contact):
protected = await default_bot.send_contact(chat_id, contact=contact)
assert protected.has_protected_content
unprotected = await default_bot.send_contact(
chat_id, contact=contact, protect_content=False
tasks = asyncio.gather(
default_bot.send_contact(chat_id, contact=contact),
default_bot.send_contact(chat_id, contact=contact, protect_content=False),
)
protected, unprotected = await tasks
assert protected.has_protected_content
assert not unprotected.has_protected_content
async def test_send_contact_without_required(self, bot, chat_id):
with pytest.raises(ValueError, match="Either contact or phone_number and first_name"):
await bot.send_contact(chat_id=chat_id)
async def test_send_mutually_exclusive(self, bot, chat_id, contact):
with pytest.raises(ValueError, match="Not both"):
await bot.send_contact(
chat_id=chat_id,
contact=contact,
phone_number=contact.phone_number,
first_name=contact.first_name,
)
def test_to_dict(self, contact):
contact_dict = contact.to_dict()
assert isinstance(contact_dict, dict)
assert contact_dict["phone_number"] == contact.phone_number
assert contact_dict["first_name"] == contact.first_name
assert contact_dict["last_name"] == contact.last_name
assert contact_dict["user_id"] == contact.user_id
def test_equality(self):
a = Contact(self.phone_number, self.first_name)
b = Contact(self.phone_number, self.first_name)
c = Contact(self.phone_number, "")
d = Contact("", self.first_name)
e = Voice("", "unique_id", 0)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import os
from pathlib import Path
@@ -30,23 +31,24 @@ from tests.auxil.bot_method_checks import (
check_shortcut_call,
check_shortcut_signature,
)
from tests.conftest import data_file
from tests.auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs
from tests.auxil.files import data_file
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="function")
def document_file():
f = data_file("telegram.png").open("rb")
yield f
f.close()
with data_file("telegram.png").open("rb") as f:
yield f
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
async def document(bot, chat_id):
with data_file("telegram.png").open("rb") as f:
return (await bot.send_document(chat_id, document=f, read_timeout=50)).document
class TestDocument:
class TestDocumentBase:
caption = "DocumentTest - *Caption*"
document_file_url = "https://python-telegram-bot.org/static/testfiles/telegram.gif"
file_size = 12948
@@ -58,7 +60,9 @@ class TestDocument:
document_file_id = "5a3128a4d2a04750b5b58397f3b5e812"
document_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e"
def test_slot_behaviour(self, document, mro_slots):
class TestDocumentWithoutRequest(TestDocumentBase):
def test_slot_behaviour(self, document):
for attr in document.__slots__:
assert getattr(document, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(document)) == len(set(mro_slots(document))), "duplicate slot"
@@ -74,75 +78,63 @@ class TestDocument:
assert document.file_size == self.file_size
assert document.mime_type == self.mime_type
assert document.file_name == self.file_name
assert document.thumb.file_size == self.thumb_file_size
assert document.thumb.width == self.thumb_width
assert document.thumb.height == self.thumb_height
assert document.thumbnail.file_size == self.thumb_file_size
assert document.thumbnail.width == self.thumb_width
assert document.thumbnail.height == self.thumb_height
@pytest.mark.flaky(3, 1)
async def test_send_all_args(self, bot, chat_id, document_file, document, thumb_file):
message = await bot.send_document(
chat_id,
document=document_file,
caption=self.caption,
disable_notification=False,
protect_content=True,
filename="telegram_custom.png",
parse_mode="Markdown",
thumb=thumb_file,
)
def test_thumb_property_deprecation_warning(self, recwarn):
document = Document(file_id="file_id", file_unique_id="file_unique_id", thumb=object())
assert document.thumb is document.thumbnail
check_thumb_deprecation_warnings_for_args_and_attrs(recwarn, __file__)
assert isinstance(message.document, Document)
assert isinstance(message.document.file_id, str)
assert message.document.file_id != ""
assert isinstance(message.document.file_unique_id, str)
assert message.document.file_unique_id != ""
assert isinstance(message.document.thumb, PhotoSize)
assert message.document.file_name == "telegram_custom.png"
assert message.document.mime_type == document.mime_type
assert message.document.file_size == document.file_size
assert message.caption == self.caption.replace("*", "")
assert message.document.thumb.width == self.thumb_width
assert message.document.thumb.height == self.thumb_height
assert message.has_protected_content
def test_de_json(self, bot, document):
json_dict = {
"file_id": self.document_file_id,
"file_unique_id": self.document_file_unique_id,
"thumbnail": document.thumbnail.to_dict(),
"file_name": self.file_name,
"mime_type": self.mime_type,
"file_size": self.file_size,
}
test_document = Document.de_json(json_dict, bot)
assert test_document.api_kwargs == {}
@pytest.mark.flaky(3, 1)
async def test_get_and_download(self, bot, document):
path = Path("telegram.png")
if path.is_file():
path.unlink()
assert test_document.file_id == self.document_file_id
assert test_document.file_unique_id == self.document_file_unique_id
assert test_document.thumbnail == document.thumbnail
assert test_document.file_name == self.file_name
assert test_document.mime_type == self.mime_type
assert test_document.file_size == self.file_size
new_file = await bot.get_file(document.file_id)
def test_to_dict(self, document):
document_dict = document.to_dict()
assert new_file.file_size == document.file_size
assert new_file.file_id == document.file_id
assert new_file.file_unique_id == document.file_unique_id
assert new_file.file_path.startswith("https://")
assert isinstance(document_dict, dict)
assert document_dict["file_id"] == document.file_id
assert document_dict["file_unique_id"] == document.file_unique_id
assert document_dict["file_name"] == document.file_name
assert document_dict["mime_type"] == document.mime_type
assert document_dict["file_size"] == document.file_size
await new_file.download_to_drive("telegram.png")
def test_equality(self, document):
a = Document(document.file_id, document.file_unique_id)
b = Document("", document.file_unique_id)
d = Document("", "")
e = Voice(document.file_id, document.file_unique_id, 0)
assert path.is_file()
assert a == b
assert hash(a) == hash(b)
assert a is not b
@pytest.mark.flaky(3, 1)
async def test_send_url_gif_file(self, bot, chat_id):
message = await bot.send_document(chat_id, self.document_file_url)
assert a != d
assert hash(a) != hash(d)
document = message.document
assert a != e
assert hash(a) != hash(e)
assert isinstance(document, Document)
assert isinstance(document.file_id, str)
assert document.file_id != ""
assert isinstance(message.document.file_unique_id, str)
assert message.document.file_unique_id != ""
assert isinstance(document.thumb, PhotoSize)
assert document.file_name == "telegram.gif"
assert document.mime_type == "image/gif"
assert document.file_size == 3878
@pytest.mark.flaky(3, 1)
async def test_send_resend(self, bot, chat_id, document):
message = await bot.send_document(chat_id=chat_id, document=document.file_id)
assert message.document == document
async def test_error_send_without_required_args(self, bot, chat_id):
with pytest.raises(TypeError):
await bot.send_document(chat_id=chat_id)
@pytest.mark.parametrize("disable_content_type_detection", [True, False, None])
async def test_send_with_document(
@@ -165,7 +157,133 @@ class TestDocument:
assert message
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_document_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = (
data.get("document") == expected and data.get("thumbnail") == expected
)
else:
test_flag = isinstance(data.get("document"), InputFile) and isinstance(
data.get("thumbnail"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_document(chat_id, file, thumbnail=file)
assert test_flag
finally:
bot._local_mode = False
async def test_send_document_with_local_files_throws_error_with_different_thumb_and_thumbnail(
self, bot, chat_id
):
file = data_file("telegram.jpg")
different_file = data_file("telegram_no_standard_header.jpg")
with pytest.raises(ValueError, match="different entities as 'thumb' and 'thumbnail'"):
await bot.send_document(chat_id, file, thumbnail=file, thumb=different_file)
async def test_get_file_instance_method(self, monkeypatch, document):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == document.file_id
assert check_shortcut_signature(Document.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(document.get_file, document.get_bot(), "get_file")
assert await check_defaults_handling(document.get_file, document.get_bot())
monkeypatch.setattr(document.get_bot(), "get_file", make_assertion)
assert await document.get_file()
class TestDocumentWithRequest(TestDocumentBase):
async def test_error_send_empty_file(self, bot, chat_id):
with open(os.devnull, "rb") as f:
with pytest.raises(TelegramError):
await bot.send_document(chat_id=chat_id, document=f)
async def test_error_send_empty_file_id(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_document(chat_id=chat_id, document="")
async def test_get_and_download(self, bot, document, chat_id):
path = Path("telegram.png")
if path.is_file():
path.unlink()
new_file = await bot.get_file(document.file_id)
assert new_file.file_size == document.file_size
assert new_file.file_unique_id == document.file_unique_id
assert new_file.file_path.startswith("https://")
await new_file.download_to_drive("telegram.png")
assert path.is_file()
async def test_send_resend(self, bot, chat_id, document):
message = await bot.send_document(chat_id=chat_id, document=document.file_id)
assert message.document == document
async def test_send_all_args(self, bot, chat_id, document_file, document, thumb_file):
message = await bot.send_document(
chat_id,
document=document_file,
caption=self.caption,
disable_notification=False,
protect_content=True,
filename="telegram_custom.png",
parse_mode="Markdown",
thumbnail=thumb_file,
)
assert isinstance(message.document, Document)
assert isinstance(message.document.file_id, str)
assert message.document.file_id != ""
assert isinstance(message.document.file_unique_id, str)
assert message.document.file_unique_id != ""
assert isinstance(message.document.thumbnail, PhotoSize)
assert message.document.file_name == "telegram_custom.png"
assert message.document.mime_type == document.mime_type
assert message.document.file_size == document.file_size
assert message.caption == self.caption.replace("*", "")
assert message.document.thumbnail.width == self.thumb_width
assert message.document.thumbnail.height == self.thumb_height
assert message.has_protected_content
async def test_send_url_gif_file(self, bot, chat_id):
message = await bot.send_document(chat_id, self.document_file_url)
document = message.document
assert isinstance(document, Document)
assert isinstance(document.file_id, str)
assert document.file_id != ""
assert isinstance(message.document.file_unique_id, str)
assert message.document.file_unique_id != ""
assert isinstance(document.thumbnail, PhotoSize)
assert document.file_name == "telegram.gif"
assert document.mime_type == "image/gif"
assert document.file_size == 3878
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_document_default_protect_content(self, chat_id, default_bot, document):
tasks = asyncio.gather(
default_bot.send_document(chat_id, document),
default_bot.send_document(chat_id, document, protect_content=False),
)
protected, unprotected = await tasks
assert protected.has_protected_content
assert not unprotected.has_protected_content
async def test_send_document_caption_entities(self, bot, chat_id, document):
test_string = "Italic Bold Code"
entities = [
@@ -180,7 +298,6 @@ class TestDocument:
assert message.caption == test_string
assert message.caption_entities == tuple(entities)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_document_default_parse_mode_1(self, default_bot, chat_id, document):
test_string = "Italic Bold Code"
@@ -190,7 +307,6 @@ class TestDocument:
assert message.caption_markdown == test_markdown_string
assert message.caption == test_string
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_document_default_parse_mode_2(self, default_bot, chat_id, document):
test_markdown_string = "_Italic_ *Bold* `Code`"
@@ -201,7 +317,6 @@ class TestDocument:
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_document_default_parse_mode_3(self, default_bot, chat_id, document):
test_markdown_string = "_Italic_ *Bold* `Code`"
@@ -212,7 +327,6 @@ class TestDocument:
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize(
"default_bot,custom",
[
@@ -245,106 +359,3 @@ class TestDocument:
await default_bot.send_document(
chat_id, document, reply_to_message_id=reply_to_message.message_id
)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_document_default_protect_content(self, chat_id, default_bot, document):
protected = await default_bot.send_document(chat_id, document)
assert protected.has_protected_content
unprotected = await default_bot.send_document(chat_id, document, protect_content=False)
assert not unprotected.has_protected_content
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_document_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("document") == expected and data.get("thumb") == expected
else:
test_flag = isinstance(data.get("document"), InputFile) and isinstance(
data.get("thumb"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_document(chat_id, file, thumb=file)
assert test_flag
finally:
bot._local_mode = False
def test_de_json(self, bot, document):
json_dict = {
"file_id": self.document_file_id,
"file_unique_id": self.document_file_unique_id,
"thumb": document.thumb.to_dict(),
"file_name": self.file_name,
"mime_type": self.mime_type,
"file_size": self.file_size,
}
test_document = Document.de_json(json_dict, bot)
assert test_document.api_kwargs == {}
assert test_document.file_id == self.document_file_id
assert test_document.file_unique_id == self.document_file_unique_id
assert test_document.thumb == document.thumb
assert test_document.file_name == self.file_name
assert test_document.mime_type == self.mime_type
assert test_document.file_size == self.file_size
def test_to_dict(self, document):
document_dict = document.to_dict()
assert isinstance(document_dict, dict)
assert document_dict["file_id"] == document.file_id
assert document_dict["file_unique_id"] == document.file_unique_id
assert document_dict["file_name"] == document.file_name
assert document_dict["mime_type"] == document.mime_type
assert document_dict["file_size"] == document.file_size
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file(self, bot, chat_id):
with open(os.devnull, "rb") as f:
with pytest.raises(TelegramError):
await bot.send_document(chat_id=chat_id, document=f)
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file_id(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_document(chat_id=chat_id, document="")
async def test_error_send_without_required_args(self, bot, chat_id):
with pytest.raises(TypeError):
await bot.send_document(chat_id=chat_id)
async def test_get_file_instance_method(self, monkeypatch, document):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == document.file_id
assert check_shortcut_signature(Document.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(document.get_file, document.get_bot(), "get_file")
assert await check_defaults_handling(document.get_file, document.get_bot())
monkeypatch.setattr(document.get_bot(), "get_file", make_assertion)
assert await document.get_file()
def test_equality(self, document):
a = Document(document.file_id, document.file_unique_id)
b = Document("", document.file_unique_id)
d = Document("", "")
e = Voice(document.file_id, document.file_unique_id, 0)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
+109 -100
View File
@@ -24,23 +24,24 @@ import pytest
from telegram import File, FileCredentials, Voice
from telegram.error import TelegramError
from tests.conftest import data_file
from tests.auxil.files import data_file
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def file(bot):
file = File(
TestFile.file_id,
TestFile.file_unique_id,
file_path=TestFile.file_path,
file_size=TestFile.file_size,
TestFileBase.file_id,
TestFileBase.file_unique_id,
file_path=TestFileBase.file_path,
file_size=TestFileBase.file_size,
)
file.set_bot(bot)
file._unfreeze()
return file
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def encrypted_file(bot):
# check https://github.com/python-telegram-bot/python-telegram-bot/wiki/\
# PTB-test-writing-knowledge-base#how-to-generate-encrypted-passport-files
@@ -49,13 +50,18 @@ def encrypted_file(bot):
"Oq3G4sX+bKZthoyms1YlPqvWou9esb+z0Bi/KqQUG8s=",
"Pt7fKPgYWKA/7a8E64Ea1X8C+Wf7Ky1tF4ANBl63vl4=",
)
ef = File(TestFile.file_id, TestFile.file_unique_id, TestFile.file_size, TestFile.file_path)
ef = File(
TestFileBase.file_id,
TestFileBase.file_unique_id,
TestFileBase.file_size,
TestFileBase.file_path,
)
ef.set_bot(bot)
ef.set_credentials(fc)
return ef
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def encrypted_local_file(bot):
# check encrypted_file() for the source of the fc values
fc = FileCredentials(
@@ -63,9 +69,9 @@ def encrypted_local_file(bot):
"Pt7fKPgYWKA/7a8E64Ea1X8C+Wf7Ky1tF4ANBl63vl4=",
)
ef = File(
TestFile.file_id,
TestFile.file_unique_id,
TestFile.file_size,
TestFileBase.file_id,
TestFileBase.file_unique_id,
TestFileBase.file_size,
file_path=str(data_file("image_encrypted.jpg")),
)
ef.set_bot(bot)
@@ -73,19 +79,19 @@ def encrypted_local_file(bot):
return ef
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def local_file(bot):
file = File(
TestFile.file_id,
TestFile.file_unique_id,
TestFileBase.file_id,
TestFileBase.file_unique_id,
file_path=str(data_file("local_file.txt")),
file_size=TestFile.file_size,
file_size=TestFileBase.file_size,
)
file.set_bot(bot)
return file
class TestFile:
class TestFileBase:
file_id = "NOTVALIDDOESNOTMATTER"
file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e"
file_path = (
@@ -94,7 +100,9 @@ class TestFile:
file_size = 28232
file_content = "Saint-Saëns".encode() # Intentionally contains unicode chars.
def test_slot_behaviour(self, file, mro_slots):
class TestFileWithoutRequest(TestFileBase):
def test_slot_behaviour(self, file):
for attr in file.__slots__:
assert getattr(file, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(file)) == len(set(mro_slots(file))), "duplicate slot"
@@ -123,10 +131,25 @@ class TestFile:
assert file_dict["file_path"] == file.file_path
assert file_dict["file_size"] == file.file_size
@pytest.mark.flaky(3, 1)
async def test_error_get_empty_file_id(self, bot):
with pytest.raises(TelegramError):
await bot.get_file(file_id="")
def test_equality(self, bot):
a = File(self.file_id, self.file_unique_id, bot)
b = File("", self.file_unique_id, bot)
c = File(self.file_id, self.file_unique_id, None)
d = File("", "", bot)
e = Voice(self.file_id, self.file_unique_id, 0)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
async def test_download(self, monkeypatch, file):
async def test(*args, **kwargs):
@@ -140,9 +163,6 @@ class TestFile:
finally:
out_file.unlink()
async def test_download_local_file(self, local_file):
assert await local_file.download_to_drive() == Path(local_file.file_path)
@pytest.mark.parametrize(
"custom_path_type", [str, Path], ids=["str custom_path", "pathlib.Path custom_path"]
)
@@ -161,20 +181,6 @@ class TestFile:
os.close(file_handle)
custom_path.unlink()
@pytest.mark.parametrize(
"custom_path_type", [str, Path], ids=["str custom_path", "pathlib.Path custom_path"]
)
async def test_download_custom_path_local_file(self, local_file, custom_path_type):
file_handle, custom_path = mkstemp()
custom_path = Path(custom_path)
try:
out_file = await local_file.download_to_drive(custom_path_type(custom_path))
assert out_file == custom_path
assert out_file.read_bytes() == self.file_content
finally:
os.close(file_handle)
custom_path.unlink()
async def test_download_no_filename(self, monkeypatch, file):
async def test(*args, **kwargs):
return self.file_content
@@ -200,12 +206,6 @@ class TestFile:
custom_fobj.seek(0)
assert custom_fobj.read() == self.file_content
async def test_download_file_obj_local_file(self, local_file):
with TemporaryFile() as custom_fobj:
await local_file.download_to_memory(out=custom_fobj)
custom_fobj.seek(0)
assert custom_fobj.read() == self.file_content
async def test_download_bytearray(self, monkeypatch, file):
async def test(*args, **kwargs):
return self.file_content
@@ -223,18 +223,6 @@ class TestFile:
assert buf2[len(buf) :] == buf
assert buf2[: len(buf)] == buf
async def test_download_bytearray_local_file(self, local_file):
# Check that a download to a newly allocated bytearray works.
buf = await local_file.download_as_bytearray()
assert buf == bytearray(self.file_content)
# Check that a download to a given bytearray works (extends the bytearray).
buf2 = buf[:]
buf3 = await local_file.download_as_bytearray(buf=buf2)
assert buf3 is buf2
assert buf2[len(buf) :] == buf
assert buf2[: len(buf)] == buf
async def test_download_encrypted(self, monkeypatch, bot, encrypted_file):
async def test(*args, **kwargs):
return data_file("image_encrypted.jpg").read_bytes()
@@ -257,29 +245,6 @@ class TestFile:
custom_fobj.seek(0)
assert custom_fobj.read() == data_file("image_decrypted.jpg").read_bytes()
async def test_download_local_file_encrypted(self, encrypted_local_file):
out_file = await encrypted_local_file.download_to_drive()
try:
assert out_file.read_bytes() == data_file("image_decrypted.jpg").read_bytes()
finally:
out_file.unlink()
@pytest.mark.parametrize(
"custom_path_type", [str, Path], ids=["str custom_path", "pathlib.Path custom_path"]
)
async def test_download_custom_path_local_file_encrypted(
self, encrypted_local_file, custom_path_type
):
file_handle, custom_path = mkstemp()
custom_path = Path(custom_path)
try:
out_file = await encrypted_local_file.download_to_drive(custom_path_type(custom_path))
assert out_file == custom_path
assert out_file.read_bytes() == data_file("image_decrypted.jpg").read_bytes()
finally:
os.close(file_handle)
custom_path.unlink()
async def test_download_file_obj_local_file_encrypted(self, monkeypatch, encrypted_local_file):
async def test(*args, **kwargs):
return data_file("image_encrypted.jpg").read_bytes()
@@ -307,6 +272,70 @@ class TestFile:
assert buf2[len(buf) :] == buf
assert buf2[: len(buf)] == buf
class TestFileWithRequest(TestFileBase):
async def test_error_get_empty_file_id(self, bot):
with pytest.raises(TelegramError):
await bot.get_file(file_id="")
async def test_download_local_file(self, local_file):
assert await local_file.download_to_drive() == Path(local_file.file_path)
@pytest.mark.parametrize(
"custom_path_type", [str, Path], ids=["str custom_path", "pathlib.Path custom_path"]
)
async def test_download_custom_path_local_file(self, local_file, custom_path_type):
file_handle, custom_path = mkstemp()
custom_path = Path(custom_path)
try:
out_file = await local_file.download_to_drive(custom_path_type(custom_path))
assert out_file == custom_path
assert out_file.read_bytes() == self.file_content
finally:
os.close(file_handle)
custom_path.unlink()
async def test_download_file_obj_local_file(self, local_file):
with TemporaryFile() as custom_fobj:
await local_file.download_to_memory(out=custom_fobj)
custom_fobj.seek(0)
assert custom_fobj.read() == self.file_content
@pytest.mark.parametrize(
"custom_path_type", [str, Path], ids=["str custom_path", "pathlib.Path custom_path"]
)
async def test_download_custom_path_local_file_encrypted(
self, encrypted_local_file, custom_path_type
):
file_handle, custom_path = mkstemp()
custom_path = Path(custom_path)
try:
out_file = await encrypted_local_file.download_to_drive(custom_path_type(custom_path))
assert out_file == custom_path
assert out_file.read_bytes() == data_file("image_decrypted.jpg").read_bytes()
finally:
os.close(file_handle)
custom_path.unlink()
async def test_download_local_file_encrypted(self, encrypted_local_file):
out_file = await encrypted_local_file.download_to_drive()
try:
assert out_file.read_bytes() == data_file("image_decrypted.jpg").read_bytes()
finally:
out_file.unlink()
async def test_download_bytearray_local_file(self, local_file):
# Check that a download to a newly allocated bytearray works.
buf = await local_file.download_as_bytearray()
assert buf == bytearray(self.file_content)
# Check that a download to a given bytearray works (extends the bytearray).
buf2 = buf[:]
buf3 = await local_file.download_as_bytearray(buf=buf2)
assert buf3 is buf2
assert buf2[len(buf) :] == buf
assert buf2[: len(buf)] == buf
async def test_download_bytearray_local_file_encrypted(self, encrypted_local_file):
# Check that a download to a newly allocated bytearray works.
buf = await encrypted_local_file.download_as_bytearray()
@@ -318,23 +347,3 @@ class TestFile:
assert buf3 is buf2
assert buf2[len(buf) :] == buf
assert buf2[: len(buf)] == buf
def test_equality(self, bot):
a = File(self.file_id, self.file_unique_id, bot)
b = File("", self.file_unique_id, bot)
c = File(self.file_id, self.file_unique_id, None)
d = File("", "", bot)
e = Voice(self.file_id, self.file_unique_id, 0)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
@@ -23,16 +23,17 @@ from io import BytesIO
import pytest
from telegram import InputFile
from tests.conftest import data_file
from tests.auxil.files import data_file
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def png_file():
return data_file("game.png")
class TestInputFile:
def test_slot_behaviour(self, mro_slots):
class TestInputFileWithoutRequest:
def test_slot_behaviour(self):
inst = InputFile(BytesIO(b"blah"), filename="tg.jpg")
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -65,7 +66,7 @@ class TestInputFile:
assert input_file.attach_name is None
assert input_file.attach_uri is None
def test_mimetypes(self, caplog):
def test_mimetypes(self):
# Only test a few to make sure logic works okay
assert InputFile(data_file("telegram.jpg").open("rb")).mimetype == "image/jpeg"
# For some reason python can guess the type on macOS
@@ -139,6 +140,8 @@ class TestInputFile:
== "blah.jpg"
)
class TestInputFileWithRequest:
async def test_send_bytes(self, bot, chat_id):
# We test this here and not at the respective test modules because it's not worth
# duplicating the test for the different methods
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import copy
from collections.abc import Sequence
@@ -36,9 +37,15 @@ from telegram.constants import ParseMode
# noinspection PyUnresolvedReferences
from telegram.error import BadRequest
from telegram.request import RequestData
from tests.conftest import data_file, expect_bad_request
from tests._files.test_animation import animation, animation_file # noqa: F401
from tests.auxil.files import data_file
from tests.auxil.networking import expect_bad_request
from tests.auxil.slots import mro_slots
from .test_animation import animation, animation_file # noqa: F401
# noinspection PyUnresolvedReferences
from tests.test_forum import emoji_id, real_topic # noqa: F401
from ..auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs
# noinspection PyUnresolvedReferences
from .test_audio import audio, audio_file # noqa: F401
@@ -46,9 +53,6 @@ from .test_audio import audio, audio_file # noqa: F401
# noinspection PyUnresolvedReferences
from .test_document import document, document_file # noqa: F401
# noinspection PyUnresolvedReferences
from .test_forum import emoji_id, real_topic # noqa: F401
# noinspection PyUnresolvedReferences
from .test_photo import _photo, photo, photo_file, thumb # noqa: F401
@@ -56,86 +60,75 @@ from .test_photo import _photo, photo, photo_file, thumb # noqa: F401
from .test_video import video, video_file # noqa: F401
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def input_media_video(class_thumb_file):
return InputMediaVideo(
media=TestInputMediaVideo.media,
caption=TestInputMediaVideo.caption,
width=TestInputMediaVideo.width,
height=TestInputMediaVideo.height,
duration=TestInputMediaVideo.duration,
parse_mode=TestInputMediaVideo.parse_mode,
caption_entities=TestInputMediaVideo.caption_entities,
thumb=class_thumb_file,
supports_streaming=TestInputMediaVideo.supports_streaming,
has_spoiler=TestInputMediaVideo.has_spoiler,
media=TestInputMediaVideoBase.media,
caption=TestInputMediaVideoBase.caption,
width=TestInputMediaVideoBase.width,
height=TestInputMediaVideoBase.height,
duration=TestInputMediaVideoBase.duration,
parse_mode=TestInputMediaVideoBase.parse_mode,
caption_entities=TestInputMediaVideoBase.caption_entities,
thumbnail=class_thumb_file,
supports_streaming=TestInputMediaVideoBase.supports_streaming,
has_spoiler=TestInputMediaVideoBase.has_spoiler,
)
@pytest.fixture(scope="class")
def input_media_photo(class_thumb_file):
@pytest.fixture(scope="module")
def input_media_photo():
return InputMediaPhoto(
media=TestInputMediaPhoto.media,
caption=TestInputMediaPhoto.caption,
parse_mode=TestInputMediaPhoto.parse_mode,
caption_entities=TestInputMediaPhoto.caption_entities,
has_spoiler=TestInputMediaPhoto.has_spoiler,
media=TestInputMediaPhotoBase.media,
caption=TestInputMediaPhotoBase.caption,
parse_mode=TestInputMediaPhotoBase.parse_mode,
caption_entities=TestInputMediaPhotoBase.caption_entities,
has_spoiler=TestInputMediaPhotoBase.has_spoiler,
)
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def input_media_animation(class_thumb_file):
return InputMediaAnimation(
media=TestInputMediaAnimation.media,
caption=TestInputMediaAnimation.caption,
parse_mode=TestInputMediaAnimation.parse_mode,
caption_entities=TestInputMediaAnimation.caption_entities,
width=TestInputMediaAnimation.width,
height=TestInputMediaAnimation.height,
thumb=class_thumb_file,
duration=TestInputMediaAnimation.duration,
has_spoiler=TestInputMediaAnimation.has_spoiler,
media=TestInputMediaAnimationBase.media,
caption=TestInputMediaAnimationBase.caption,
parse_mode=TestInputMediaAnimationBase.parse_mode,
caption_entities=TestInputMediaAnimationBase.caption_entities,
width=TestInputMediaAnimationBase.width,
height=TestInputMediaAnimationBase.height,
thumbnail=class_thumb_file,
duration=TestInputMediaAnimationBase.duration,
has_spoiler=TestInputMediaAnimationBase.has_spoiler,
)
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def input_media_audio(class_thumb_file):
return InputMediaAudio(
media=TestInputMediaAudio.media,
caption=TestInputMediaAudio.caption,
duration=TestInputMediaAudio.duration,
performer=TestInputMediaAudio.performer,
title=TestInputMediaAudio.title,
thumb=class_thumb_file,
parse_mode=TestInputMediaAudio.parse_mode,
caption_entities=TestInputMediaAudio.caption_entities,
media=TestInputMediaAudioBase.media,
caption=TestInputMediaAudioBase.caption,
duration=TestInputMediaAudioBase.duration,
performer=TestInputMediaAudioBase.performer,
title=TestInputMediaAudioBase.title,
thumbnail=class_thumb_file,
parse_mode=TestInputMediaAudioBase.parse_mode,
caption_entities=TestInputMediaAudioBase.caption_entities,
)
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def input_media_document(class_thumb_file):
return InputMediaDocument(
media=TestInputMediaDocument.media,
caption=TestInputMediaDocument.caption,
thumb=class_thumb_file,
parse_mode=TestInputMediaDocument.parse_mode,
caption_entities=TestInputMediaDocument.caption_entities,
disable_content_type_detection=TestInputMediaDocument.disable_content_type_detection,
media=TestInputMediaDocumentBase.media,
caption=TestInputMediaDocumentBase.caption,
thumbnail=class_thumb_file,
parse_mode=TestInputMediaDocumentBase.parse_mode,
caption_entities=TestInputMediaDocumentBase.caption_entities,
disable_content_type_detection=TestInputMediaDocumentBase.disable_content_type_detection,
)
class CustomSequence(Sequence):
def __init__(self, items):
self.items = items
def __getitem__(self, index):
return self.items[index]
def __len__(self):
return len(self.items)
class TestInputMediaVideo:
class TestInputMediaVideoBase:
type_ = "video"
media = "NOTAREALFILEID"
caption = "My Caption"
@@ -147,7 +140,9 @@ class TestInputMediaVideo:
caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)]
has_spoiler = True
def test_slot_behaviour(self, input_media_video, mro_slots):
class TestInputMediaVideoWithoutRequest(TestInputMediaVideoBase):
def test_slot_behaviour(self, input_media_video):
inst = input_media_video
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -163,9 +158,15 @@ class TestInputMediaVideo:
assert input_media_video.parse_mode == self.parse_mode
assert input_media_video.caption_entities == tuple(self.caption_entities)
assert input_media_video.supports_streaming == self.supports_streaming
assert isinstance(input_media_video.thumb, InputFile)
assert isinstance(input_media_video.thumbnail, InputFile)
assert input_media_video.thumb is input_media_video.thumbnail
assert input_media_video.has_spoiler == self.has_spoiler
def test_thumb_property_deprecation_warning(self, recwarn):
input_media_video = InputMediaVideo(self.media, thumb=object())
assert input_media_video.thumb is input_media_video.thumbnail
check_thumb_deprecation_warnings_for_args_and_attrs(recwarn, __file__)
def test_caption_entities_always_tuple(self):
input_media_video = InputMediaVideo(self.media)
assert input_media_video.caption_entities == ()
@@ -204,13 +205,21 @@ class TestInputMediaVideo:
def test_with_local_files(self):
input_media_video = InputMediaVideo(
data_file("telegram.mp4"), thumb=data_file("telegram.jpg")
data_file("telegram.mp4"), thumbnail=data_file("telegram.jpg")
)
assert input_media_video.media == data_file("telegram.mp4").as_uri()
assert input_media_video.thumb == data_file("telegram.jpg").as_uri()
assert input_media_video.thumbnail == data_file("telegram.jpg").as_uri()
def test_with_local_files_throws_exception_with_different_thumb_and_thumbnail(self):
with pytest.raises(ValueError):
InputMediaVideo(
data_file("telegram.mp4"),
thumbnail=data_file("telegram.jpg"),
thumb=data_file("telegram_no_standard_header.jpg"),
)
class TestInputMediaPhoto:
class TestInputMediaPhotoBase:
type_ = "photo"
media = "NOTAREALFILEID"
caption = "My Caption"
@@ -218,7 +227,9 @@ class TestInputMediaPhoto:
caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)]
has_spoiler = True
def test_slot_behaviour(self, input_media_photo, mro_slots):
class TestInputMediaPhotoWithoutRequest(TestInputMediaPhotoBase):
def test_slot_behaviour(self, input_media_photo):
inst = input_media_photo
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -266,7 +277,7 @@ class TestInputMediaPhoto:
assert input_media_photo.media == data_file("telegram.mp4").as_uri()
class TestInputMediaAnimation:
class TestInputMediaAnimationBase:
type_ = "animation"
media = "NOTAREALFILEID"
caption = "My Caption"
@@ -277,7 +288,9 @@ class TestInputMediaAnimation:
duration = 1
has_spoiler = True
def test_slot_behaviour(self, input_media_animation, mro_slots):
class TestInputMediaAnimationWithoutRequest(TestInputMediaAnimationBase):
def test_slot_behaviour(self, input_media_animation):
inst = input_media_animation
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -289,9 +302,15 @@ class TestInputMediaAnimation:
assert input_media_animation.caption == self.caption
assert input_media_animation.parse_mode == self.parse_mode
assert input_media_animation.caption_entities == tuple(self.caption_entities)
assert isinstance(input_media_animation.thumb, InputFile)
assert isinstance(input_media_animation.thumbnail, InputFile)
assert input_media_animation.thumb is input_media_animation.thumbnail
assert input_media_animation.has_spoiler == self.has_spoiler
def test_thumb_property_deprecation_warning(self, recwarn):
input_media_animation = InputMediaAnimation(self.media, thumb=object())
assert input_media_animation.thumb is input_media_animation.thumbnail
check_thumb_deprecation_warnings_for_args_and_attrs(recwarn, __file__)
def test_caption_entities_always_tuple(self):
input_media_animation = InputMediaAnimation(self.media)
assert input_media_animation.caption_entities == ()
@@ -326,13 +345,21 @@ class TestInputMediaAnimation:
def test_with_local_files(self):
input_media_animation = InputMediaAnimation(
data_file("telegram.mp4"), thumb=data_file("telegram.jpg")
data_file("telegram.mp4"), thumbnail=data_file("telegram.jpg")
)
assert input_media_animation.media == data_file("telegram.mp4").as_uri()
assert input_media_animation.thumb == data_file("telegram.jpg").as_uri()
assert input_media_animation.thumbnail == data_file("telegram.jpg").as_uri()
def test_with_local_files_throws_exception_with_different_thumb_and_thumbnail(self):
with pytest.raises(ValueError):
InputMediaAnimation(
data_file("telegram.mp4"),
thumbnail=data_file("telegram.jpg"),
thumb=data_file("telegram_no_standard_header.jpg"),
)
class TestInputMediaAudio:
class TestInputMediaAudioBase:
type_ = "audio"
media = "NOTAREALFILEID"
caption = "My Caption"
@@ -342,7 +369,9 @@ class TestInputMediaAudio:
parse_mode = "HTML"
caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)]
def test_slot_behaviour(self, input_media_audio, mro_slots):
class TestInputMediaAudioWithoutRequest(TestInputMediaAudioBase):
def test_slot_behaviour(self, input_media_audio):
inst = input_media_audio
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -357,7 +386,13 @@ class TestInputMediaAudio:
assert input_media_audio.title == self.title
assert input_media_audio.parse_mode == self.parse_mode
assert input_media_audio.caption_entities == tuple(self.caption_entities)
assert isinstance(input_media_audio.thumb, InputFile)
assert isinstance(input_media_audio.thumbnail, InputFile)
assert input_media_audio.thumb is input_media_audio.thumbnail
def test_thumb_property_deprecation_warning(self, recwarn):
input_media_audio = InputMediaAudio(self.media, thumb=object())
assert input_media_audio.thumb is input_media_audio.thumbnail
check_thumb_deprecation_warnings_for_args_and_attrs(recwarn, __file__)
def test_caption_entities_always_tuple(self):
input_media_audio = InputMediaAudio(self.media)
@@ -395,13 +430,21 @@ class TestInputMediaAudio:
def test_with_local_files(self):
input_media_audio = InputMediaAudio(
data_file("telegram.mp4"), thumb=data_file("telegram.jpg")
data_file("telegram.mp4"), thumbnail=data_file("telegram.jpg")
)
assert input_media_audio.media == data_file("telegram.mp4").as_uri()
assert input_media_audio.thumb == data_file("telegram.jpg").as_uri()
assert input_media_audio.thumbnail == data_file("telegram.jpg").as_uri()
def test_with_local_files_throws_exception_with_different_thumb_and_thumbnail(self):
with pytest.raises(ValueError):
InputMediaAudio(
data_file("telegram.mp4"),
thumbnail=data_file("telegram.jpg"),
thumb=data_file("telegram_no_standard_header.jpg"),
)
class TestInputMediaDocument:
class TestInputMediaDocumentBase:
type_ = "document"
media = "NOTAREALFILEID"
caption = "My Caption"
@@ -409,7 +452,9 @@ class TestInputMediaDocument:
caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)]
disable_content_type_detection = True
def test_slot_behaviour(self, input_media_document, mro_slots):
class TestInputMediaDocumentWithoutRequest(TestInputMediaDocumentBase):
def test_slot_behaviour(self, input_media_document):
inst = input_media_document
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -425,7 +470,13 @@ class TestInputMediaDocument:
input_media_document.disable_content_type_detection
== self.disable_content_type_detection
)
assert isinstance(input_media_document.thumb, InputFile)
assert isinstance(input_media_document.thumbnail, InputFile)
assert input_media_document.thumb is input_media_document.thumbnail
def test_thumb_property_deprecation_warning(self, recwarn):
input_media_document = InputMediaDocument(self.media, thumb=object())
assert input_media_document.thumb is input_media_document.thumbnail
check_thumb_deprecation_warnings_for_args_and_attrs(recwarn, __file__)
def test_caption_entities_always_tuple(self):
input_media_document = InputMediaDocument(self.media)
@@ -461,13 +512,21 @@ class TestInputMediaDocument:
def test_with_local_files(self):
input_media_document = InputMediaDocument(
data_file("telegram.mp4"), thumb=data_file("telegram.jpg")
data_file("telegram.mp4"), thumbnail=data_file("telegram.jpg")
)
assert input_media_document.media == data_file("telegram.mp4").as_uri()
assert input_media_document.thumb == data_file("telegram.jpg").as_uri()
assert input_media_document.thumbnail == data_file("telegram.jpg").as_uri()
def test_with_local_files_throws_exception_with_different_thumb_and_thumbnail(self):
with pytest.raises(ValueError):
InputMediaDocument(
data_file("telegram.mp4"),
thumbnail=data_file("telegram.jpg"),
thumb=data_file("telegram_no_standard_header.jpg"),
)
@pytest.fixture(scope="function") # noqa: F811
@pytest.fixture(scope="module") # noqa: F811
def media_group(photo, thumb): # noqa: F811
return [
InputMediaPhoto(photo, caption="*photo* 1", parse_mode="Markdown"),
@@ -478,12 +537,12 @@ def media_group(photo, thumb): # noqa: F811
]
@pytest.fixture(scope="function") # noqa: F811
@pytest.fixture(scope="module") # noqa: F811
def media_group_no_caption_args(photo, thumb): # noqa: F811
return [InputMediaPhoto(photo), InputMediaPhoto(thumb), InputMediaPhoto(photo)]
@pytest.fixture(scope="function") # noqa: F811
@pytest.fixture(scope="module") # noqa: F811
def media_group_no_caption_only_caption_entities(photo, thumb): # noqa: F811
return [
InputMediaPhoto(photo, caption_entities=[MessageEntity(MessageEntity.BOLD, 0, 5)]),
@@ -491,7 +550,7 @@ def media_group_no_caption_only_caption_entities(photo, thumb): # noqa: F811
]
@pytest.fixture(scope="function") # noqa: F811
@pytest.fixture(scope="module") # noqa: F811
def media_group_no_caption_only_parse_mode(photo, thumb): # noqa: F811
return [
InputMediaPhoto(photo, parse_mode="Markdown"),
@@ -499,32 +558,7 @@ def media_group_no_caption_only_parse_mode(photo, thumb): # noqa: F811
]
class TestSendMediaGroup:
@pytest.mark.flaky(3, 1)
async def test_send_media_group_photo(self, bot, chat_id, media_group):
messages = await bot.send_media_group(chat_id, media_group)
assert isinstance(messages, tuple)
assert len(messages) == 3
assert all(isinstance(mes, Message) for mes in messages)
assert all(mes.media_group_id == messages[0].media_group_id for mes in messages)
assert all(mes.caption == f"photo {idx+1}" for idx, mes in enumerate(messages))
assert all(
mes.caption_entities == (MessageEntity(MessageEntity.BOLD, 0, 5),) for mes in messages
)
async def test_send_media_group_with_message_thread_id(
self, bot, real_topic, forum_group_id, media_group # noqa: F811
):
messages = await bot.send_media_group(
forum_group_id,
media_group,
message_thread_id=real_topic.message_thread_id,
)
assert isinstance(messages, tuple)
assert len(messages) == 3
assert all(isinstance(mes, Message) for mes in messages)
assert all(i.message_thread_id == real_topic.message_thread_id for i in messages)
class TestSendMediaGroupWithoutRequest:
async def test_send_media_group_throws_error_with_group_caption_and_individual_captions(
self,
bot,
@@ -544,6 +578,147 @@ class TestSendMediaGroup:
):
await bot.send_media_group(chat_id, group, caption="foo")
async def test_send_media_group_custom_filename(
self,
bot,
chat_id,
photo_file, # noqa: F811
animation_file, # noqa: F811
audio_file, # noqa: F811
video_file, # noqa: F811
monkeypatch,
):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
result = all(
field_tuple[0] == "custom_filename"
for field_tuple in request_data.multipart_data.values()
)
if result is True:
raise Exception("Test was successful")
monkeypatch.setattr(bot.request, "post", make_assertion)
media = [
InputMediaAnimation(animation_file, filename="custom_filename"),
InputMediaAudio(audio_file, filename="custom_filename"),
InputMediaPhoto(photo_file, filename="custom_filename"),
InputMediaVideo(video_file, filename="custom_filename"),
]
with pytest.raises(Exception, match="Test was successful"):
await bot.send_media_group(chat_id, media)
async def test_send_media_group_with_thumbs(
self, bot, chat_id, video_file, photo_file, monkeypatch # noqa: F811
):
async def make_assertion(method, url, request_data: RequestData, *args, **kwargs):
nonlocal input_video
files = request_data.multipart_data
video_check = files[input_video.media.attach_name] == input_video.media.field_tuple
thumb_check = (
files[input_video.thumbnail.attach_name] == input_video.thumbnail.field_tuple
)
result = video_check and thumb_check
raise Exception(f"Test was {'successful' if result else 'failing'}")
monkeypatch.setattr(bot.request, "_request_wrapper", make_assertion)
input_video = InputMediaVideo(video_file, thumbnail=photo_file)
with pytest.raises(Exception, match="Test was successful"):
await bot.send_media_group(chat_id, [input_video, input_video])
async def test_edit_message_media_with_thumb(
self, bot, chat_id, video_file, photo_file, monkeypatch # noqa: F811
):
async def make_assertion(
method: str, url: str, request_data: RequestData = None, *args, **kwargs
):
files = request_data.multipart_data
video_check = files[input_video.media.attach_name] == input_video.media.field_tuple
thumb_check = (
files[input_video.thumbnail.attach_name] == input_video.thumbnail.field_tuple
)
result = video_check and thumb_check
raise Exception(f"Test was {'successful' if result else 'failing'}")
monkeypatch.setattr(bot.request, "_request_wrapper", make_assertion)
input_video = InputMediaVideo(video_file, thumbnail=photo_file)
with pytest.raises(Exception, match="Test was successful"):
await bot.edit_message_media(chat_id=chat_id, message_id=123, media=input_video)
class CustomSequence(Sequence):
def __init__(self, items):
self.items = items
def __getitem__(self, index):
return self.items[index]
def __len__(self):
return len(self.items)
class TestSendMediaGroupWithRequest:
async def test_send_media_group_photo(self, bot, chat_id, media_group):
messages = await bot.send_media_group(chat_id, media_group)
assert isinstance(messages, tuple)
assert len(messages) == 3
assert all(isinstance(mes, Message) for mes in messages)
assert all(mes.media_group_id == messages[0].media_group_id for mes in messages)
assert all(mes.caption == f"photo {idx+1}" for idx, mes in enumerate(messages))
assert all(
mes.caption_entities == (MessageEntity(MessageEntity.BOLD, 0, 5),) for mes in messages
)
async def test_send_media_group_new_files(
self, bot, chat_id, video_file, photo_file # noqa: F811
):
async def func():
return await bot.send_media_group(
chat_id,
[
InputMediaVideo(video_file),
InputMediaPhoto(photo_file),
InputMediaPhoto(data_file("telegram.jpg").read_bytes()),
],
)
messages = await expect_bad_request(
func, "Type of file mismatch", "Telegram did not accept the file."
)
assert isinstance(messages, tuple)
assert len(messages) == 3
assert all(isinstance(mes, Message) for mes in messages)
assert all(mes.media_group_id == messages[0].media_group_id for mes in messages)
@pytest.mark.parametrize("sequence_type", [list, tuple, CustomSequence])
@pytest.mark.parametrize("bot_class", ["raw_bot", "ext_bot"])
async def test_send_media_group_different_sequences(
self, bot, chat_id, media_group, sequence_type, bot_class, raw_bot
):
"""Test that send_media_group accepts different sequence types. This test ensures that
Bot._insert_defaults works for arbitrary sequence types."""
bot = bot if bot_class == "ext_bot" else raw_bot
messages = await bot.send_media_group(chat_id, sequence_type(media_group))
assert isinstance(messages, tuple)
assert len(messages) == 3
assert all(isinstance(mes, Message) for mes in messages)
assert all(mes.media_group_id == messages[0].media_group_id for mes in messages)
async def test_send_media_group_with_message_thread_id(
self, bot, real_topic, forum_group_id, media_group # noqa: F811
):
messages = await bot.send_media_group(
forum_group_id,
media_group,
message_thread_id=real_topic.message_thread_id,
)
assert isinstance(messages, tuple)
assert len(messages) == 3
assert all(isinstance(mes, Message) for mes in messages)
assert all(i.message_thread_id == real_topic.message_thread_id for i in messages)
@pytest.mark.parametrize(
"caption, parse_mode, caption_entities",
[
@@ -553,7 +728,6 @@ class TestSendMediaGroup:
("photo 1", None, [MessageEntity(MessageEntity.BOLD, 0, 5)]),
],
)
@pytest.mark.flaky(3, 1)
async def test_send_media_group_with_group_caption(
self,
bot,
@@ -598,16 +772,15 @@ class TestSendMediaGroup:
assert all(mes.caption is None for mes in other_messages)
assert not any(mes.caption_entities for mes in other_messages)
@pytest.mark.flaky(3, 1)
async def test_send_media_group_all_args(self, bot, raw_bot, chat_id, media_group):
ext_bot = bot
for bot in (ext_bot, raw_bot):
# We need to test 1) below both the bot and raw_bot and setting this up with
# pytest.parametrize appears to be difficult ...
m1 = await bot.send_message(chat_id, text="test")
# We need to test 1) below both the bot and raw_bot and setting this up with
# pytest.parametrize appears to be difficult ...
aws = {b.send_message(chat_id, text="test") for b in (ext_bot, raw_bot)}
for msg_task in asyncio.as_completed(aws):
m1 = await msg_task
copied_media_group = copy.copy(media_group)
messages = await bot.send_media_group(
messages = await m1.get_bot().send_media_group(
chat_id,
media_group,
disable_notification=True,
@@ -633,7 +806,6 @@ class TestSendMediaGroup:
)
assert all(mes.has_protected_content for mes in messages)
@pytest.mark.flaky(3, 1)
async def test_send_media_group_with_spoiler(
self, bot, chat_id, photo_file, video_file # noqa: F811
):
@@ -649,97 +821,36 @@ class TestSendMediaGroup:
assert all(mes.media_group_id == messages[0].media_group_id for mes in messages)
assert all(mes.has_media_spoiler for mes in messages)
@pytest.mark.flaky(3, 1)
async def test_send_media_group_custom_filename(
self,
bot,
chat_id,
photo_file, # noqa: F811
animation_file, # noqa: F811
audio_file, # noqa: F811
video_file, # noqa: F811
monkeypatch,
):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
result = all(
field_tuple[0] == "custom_filename"
for field_tuple in request_data.multipart_data.values()
async def test_edit_message_media(self, bot, raw_bot, chat_id, media_group):
ext_bot = bot
# We need to test 1) below both the bot and raw_bot and setting this up with
# pytest.parametrize appears to be difficult ...
aws = {b.send_media_group(chat_id, media_group) for b in (ext_bot, raw_bot)}
for msg_task in asyncio.as_completed(aws):
messages = await msg_task
cid = messages[-1].chat.id
mid = messages[-1].message_id
copied_media = copy.copy(media_group[0])
new_message = (
await messages[-1]
.get_bot()
.edit_message_media(chat_id=cid, message_id=mid, media=media_group[0])
)
if result is True:
raise Exception("Test was successful")
assert isinstance(new_message, Message)
monkeypatch.setattr(bot.request, "post", make_assertion)
# 1)
# make sure that the media was not modified
assert media_group[0].parse_mode == copied_media.parse_mode
media = [
InputMediaAnimation(animation_file, filename="custom_filename"),
InputMediaAudio(audio_file, filename="custom_filename"),
InputMediaPhoto(photo_file, filename="custom_filename"),
InputMediaVideo(video_file, filename="custom_filename"),
]
with pytest.raises(Exception, match="Test was successful"):
await bot.send_media_group(chat_id, media)
async def test_send_media_group_with_thumbs(
self, bot, chat_id, video_file, photo_file, monkeypatch # noqa: F811
):
async def make_assertion(method, url, request_data: RequestData, *args, **kwargs):
files = request_data.multipart_data
video_check = files[input_video.media.attach_name] == input_video.media.field_tuple
thumb_check = files[input_video.thumb.attach_name] == input_video.thumb.field_tuple
result = video_check and thumb_check
raise Exception(f"Test was {'successful' if result else 'failing'}")
monkeypatch.setattr(bot.request, "_request_wrapper", make_assertion)
input_video = InputMediaVideo(video_file, thumb=photo_file)
with pytest.raises(Exception, match="Test was successful"):
await bot.send_media_group(chat_id, [input_video, input_video])
@pytest.mark.flaky(3, 1) # noqa: F811
async def test_send_media_group_new_files(
self,
bot,
chat_id,
video_file, # noqa: F811
photo_file, # noqa: F811
animation_file, # noqa: F811
):
async def func():
return await bot.send_media_group(
chat_id,
[
InputMediaVideo(video_file),
InputMediaPhoto(photo_file),
InputMediaPhoto(data_file("telegram.jpg").read_bytes()),
],
)
messages = await expect_bad_request(
func, "Type of file mismatch", "Telegram did not accept the file."
async def test_edit_message_media_new_file(self, bot, chat_id, media_group, thumb_file):
messages = await bot.send_media_group(chat_id, media_group)
cid = messages[-1].chat.id
mid = messages[-1].message_id
new_message = await bot.edit_message_media(
chat_id=cid, message_id=mid, media=InputMediaPhoto(thumb_file)
)
assert isinstance(new_message, Message)
assert isinstance(messages, tuple)
assert len(messages) == 3
assert all(isinstance(mes, Message) for mes in messages)
assert all(mes.media_group_id == messages[0].media_group_id for mes in messages)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("sequence_type", [list, tuple, CustomSequence])
@pytest.mark.parametrize("bot_class", ["raw_bot", "ext_bot"])
async def test_send_media_group_different_sequences(
self, bot, chat_id, media_group, sequence_type, bot_class, raw_bot
):
"""Test that send_media_group accepts different sequence types. This test ensures that
Bot._insert_defaults works for arbitrary sequence types."""
bot = bot if bot_class == "ext_bot" else raw_bot
messages = await bot.send_media_group(chat_id, sequence_type(media_group))
assert isinstance(messages, tuple)
assert len(messages) == 3
assert all(isinstance(mes, Message) for mes in messages)
assert all(mes.media_group_id == messages[0].media_group_id for mes in messages)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize(
"default_bot,custom",
[
@@ -773,19 +884,18 @@ class TestSendMediaGroup:
chat_id, media_group, reply_to_message_id=reply_to_message.message_id
)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_media_group_default_protect_content(
self, chat_id, media_group, default_bot
):
protected = await default_bot.send_media_group(chat_id, media_group)
assert all(msg.has_protected_content for msg in protected)
unprotected = await default_bot.send_media_group(
chat_id, media_group, protect_content=False
tasks = asyncio.gather(
default_bot.send_media_group(chat_id, media_group),
default_bot.send_media_group(chat_id, media_group, protect_content=False),
)
protected, unprotected = await tasks
assert all(msg.has_protected_content for msg in protected)
assert not all(msg.has_protected_content for msg in unprotected)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": ParseMode.HTML}], indirect=True)
async def test_send_media_group_default_parse_mode(
self, chat_id, media_group_no_caption_args, default_bot
@@ -797,19 +907,21 @@ class TestSendMediaGroup:
# make sure no parse_mode was set as a side effect
assert not any(item.parse_mode for item in media_group_no_caption_args)
overridden_markdown_v2 = await default_bot.send_media_group(
chat_id,
media_group_no_caption_args.copy(),
caption="*photo* 1",
parse_mode=ParseMode.MARKDOWN_V2,
)
overridden_none = await default_bot.send_media_group(
chat_id,
media_group_no_caption_args.copy(),
caption="<b>photo</b> 1",
parse_mode=None,
tasks = asyncio.gather(
default_bot.send_media_group(
chat_id,
media_group_no_caption_args.copy(),
caption="*photo* 1",
parse_mode=ParseMode.MARKDOWN_V2,
),
default_bot.send_media_group(
chat_id,
media_group_no_caption_args.copy(),
caption="<b>photo</b> 1",
parse_mode=None,
),
)
overridden_markdown_v2, overridden_none = await tasks
# Make sure first message got the caption, which will lead to Telegram
# displaying its caption as group caption
@@ -830,53 +942,6 @@ class TestSendMediaGroup:
assert all(mes.caption is None for mes in other_messages)
assert not any(mes.caption_entities for mes in other_messages)
@pytest.mark.flaky(3, 1)
async def test_edit_message_media(self, bot, raw_bot, chat_id, media_group):
ext_bot = bot
for bot in (ext_bot, raw_bot):
# We need to test 1) below both the bot and raw_bot and setting this up with
# pytest.parametrize appears to be difficult ...
messages = await bot.send_media_group(chat_id, media_group)
cid = messages[-1].chat.id
mid = messages[-1].message_id
copied_media = copy.copy(media_group[0])
new_message = await bot.edit_message_media(
chat_id=cid, message_id=mid, media=media_group[0]
)
assert isinstance(new_message, Message)
# 1)
# make sure that the media was not modified
assert media_group[0].parse_mode == copied_media.parse_mode
@pytest.mark.flaky(3, 1)
async def test_edit_message_media_new_file(self, bot, chat_id, media_group, thumb_file):
messages = await bot.send_media_group(chat_id, media_group)
cid = messages[-1].chat.id
mid = messages[-1].message_id
new_message = await bot.edit_message_media(
chat_id=cid, message_id=mid, media=InputMediaPhoto(thumb_file)
)
assert isinstance(new_message, Message)
async def test_edit_message_media_with_thumb(
self, bot, chat_id, video_file, photo_file, monkeypatch # noqa: F811
):
async def make_assertion(
method: str, url: str, request_data: RequestData = None, *args, **kwargs
):
files = request_data.multipart_data
video_check = files[input_video.media.attach_name] == input_video.media.field_tuple
thumb_check = files[input_video.thumb.attach_name] == input_video.thumb.field_tuple
result = video_check and thumb_check
raise Exception(f"Test was {'successful' if result else 'failing'}")
monkeypatch.setattr(bot.request, "_request_wrapper", make_assertion)
input_video = InputMediaVideo(video_file, thumb=photo_file)
with pytest.raises(Exception, match="Test was successful"):
await bot.edit_message_media(chat_id=chat_id, message_id=123, media=input_video)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize(
"default_bot", [{"parse_mode": ParseMode.HTML}], indirect=True, ids=["HTML-Bot"]
)
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import InputSticker, MaskPosition
from telegram._files.inputfile import InputFile
from tests._files.test_sticker import video_sticker_file # noqa: F401
from tests.auxil.files import data_file
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def input_sticker():
return InputSticker(
sticker=TestInputStickerBase.sticker,
emoji_list=TestInputStickerBase.emoji_list,
mask_position=TestInputStickerBase.mask_position,
keywords=TestInputStickerBase.keywords,
)
class TestInputStickerBase:
sticker = "fake_file_id"
emoji_list = ("👍", "👎")
mask_position = MaskPosition("forehead", 0.5, 0.5, 0.5)
keywords = ("thumbsup", "thumbsdown")
class TestInputStickerNoRequest(TestInputStickerBase):
def test_slot_behaviour(self, input_sticker):
inst = input_sticker
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
def test_expected_values(self, input_sticker):
assert input_sticker.sticker == self.sticker and isinstance(input_sticker.sticker, str)
assert input_sticker.emoji_list == self.emoji_list
assert input_sticker.mask_position == self.mask_position
assert input_sticker.keywords == self.keywords
def test_attributes_tuple(self, input_sticker):
assert isinstance(input_sticker.keywords, tuple)
assert isinstance(input_sticker.emoji_list, tuple)
a = InputSticker("sticker", ["emoji"])
assert isinstance(a.emoji_list, tuple) and a.keywords == ()
def test_to_dict(self, input_sticker):
input_sticker_dict = input_sticker.to_dict()
assert isinstance(input_sticker_dict, dict)
assert input_sticker_dict["sticker"] == input_sticker.sticker
assert input_sticker_dict["emoji_list"] == list(input_sticker.emoji_list)
assert input_sticker_dict["mask_position"] == input_sticker.mask_position.to_dict()
assert input_sticker_dict["keywords"] == list(input_sticker.keywords)
def test_with_sticker_input_types(self, video_sticker_file): # noqa: F811
sticker = InputSticker(sticker=video_sticker_file, emoji_list=["👍"])
assert isinstance(sticker.sticker, InputFile)
sticker = InputSticker(data_file("telegram_video_sticker.webm"), ["👍"])
assert sticker.sticker == data_file("telegram_video_sticker.webm").as_uri()
@@ -16,26 +16,29 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import pytest
from telegram import Location
from telegram.error import BadRequest
from telegram.request import RequestData
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def location():
return Location(
latitude=TestLocation.latitude,
longitude=TestLocation.longitude,
horizontal_accuracy=TestLocation.horizontal_accuracy,
live_period=TestLocation.live_period,
heading=TestLocation.live_period,
proximity_alert_radius=TestLocation.proximity_alert_radius,
latitude=TestLocationBase.latitude,
longitude=TestLocationBase.longitude,
horizontal_accuracy=TestLocationBase.horizontal_accuracy,
live_period=TestLocationBase.live_period,
heading=TestLocationBase.live_period,
proximity_alert_radius=TestLocationBase.proximity_alert_radius,
)
class TestLocation:
class TestLocationBase:
latitude = -23.691288
longitude = -46.788279
horizontal_accuracy = 999
@@ -43,19 +46,21 @@ class TestLocation:
heading = 90
proximity_alert_radius = 50
def test_slot_behaviour(self, location, mro_slots):
class TestLocationWithoutRequest(TestLocationBase):
def test_slot_behaviour(self, location):
for attr in location.__slots__:
assert getattr(location, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(location)) == len(set(mro_slots(location))), "duplicate slot"
def test_de_json(self, bot):
json_dict = {
"latitude": TestLocation.latitude,
"longitude": TestLocation.longitude,
"horizontal_accuracy": TestLocation.horizontal_accuracy,
"live_period": TestLocation.live_period,
"heading": TestLocation.heading,
"proximity_alert_radius": TestLocation.proximity_alert_radius,
"latitude": self.latitude,
"longitude": self.longitude,
"horizontal_accuracy": self.horizontal_accuracy,
"live_period": self.live_period,
"heading": self.heading,
"proximity_alert_radius": self.proximity_alert_radius,
}
location = Location.de_json(json_dict, bot)
assert location.api_kwargs == {}
@@ -67,7 +72,139 @@ class TestLocation:
assert location.heading == self.heading
assert location.proximity_alert_radius == self.proximity_alert_radius
@pytest.mark.flaky(3, 1)
def test_to_dict(self, location):
location_dict = location.to_dict()
assert location_dict["latitude"] == location.latitude
assert location_dict["longitude"] == location.longitude
assert location_dict["horizontal_accuracy"] == location.horizontal_accuracy
assert location_dict["live_period"] == location.live_period
assert location["heading"] == location.heading
assert location["proximity_alert_radius"] == location.proximity_alert_radius
def test_equality(self):
a = Location(self.longitude, self.latitude)
b = Location(self.longitude, self.latitude)
d = Location(0, self.latitude)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != d
assert hash(a) != hash(d)
async def test_send_location_without_required(self, bot, chat_id):
with pytest.raises(ValueError, match="Either location or latitude and longitude"):
await bot.send_location(chat_id=chat_id)
async def test_edit_location_without_required(self, bot):
with pytest.raises(ValueError, match="Either location or latitude and longitude"):
await bot.edit_message_live_location(chat_id=2, message_id=3)
async def test_send_location_with_all_args(self, bot, location):
with pytest.raises(ValueError, match="Not both"):
await bot.send_location(chat_id=1, latitude=2.5, longitude=4.6, location=location)
async def test_edit_location_with_all_args(self, bot, location):
with pytest.raises(ValueError, match="Not both"):
await bot.edit_message_live_location(
chat_id=1, message_id=7, latitude=2.5, longitude=4.6, location=location
)
# TODO: Needs improvement with in inline sent live location.
async def test_edit_live_inline_message(self, monkeypatch, bot, location):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
data = request_data.json_parameters
lat = data["latitude"] == str(location.latitude)
lon = data["longitude"] == str(location.longitude)
id_ = data["inline_message_id"] == "1234"
ha = data["horizontal_accuracy"] == "50"
heading = data["heading"] == "90"
prox_alert = data["proximity_alert_radius"] == "1000"
return lat and lon and id_ and ha and heading and prox_alert
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.edit_message_live_location(
inline_message_id=1234,
location=location,
horizontal_accuracy=50,
heading=90,
proximity_alert_radius=1000,
)
# TODO: Needs improvement with in inline sent live location.
async def test_stop_live_inline_message(self, monkeypatch, bot):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
id_ = request_data.json_parameters["inline_message_id"] == "1234"
return id_
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.stop_message_live_location(inline_message_id=1234)
async def test_send_with_location(self, monkeypatch, bot, chat_id, location):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
lat = request_data.json_parameters["latitude"] == str(location.latitude)
lon = request_data.json_parameters["longitude"] == str(location.longitude)
return lat and lon
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_location(location=location, chat_id=chat_id)
async def test_edit_live_location_with_location(self, monkeypatch, bot, location):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
lat = request_data.json_parameters["latitude"] == str(location.latitude)
lon = request_data.json_parameters["longitude"] == str(location.longitude)
return lat and lon
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.edit_message_live_location(None, None, location=location)
class TestLocationWithRequest:
@pytest.mark.parametrize(
"default_bot,custom",
[
({"allow_sending_without_reply": True}, None),
({"allow_sending_without_reply": False}, None),
({"allow_sending_without_reply": False}, True),
],
indirect=["default_bot"],
)
async def test_send_location_default_allow_sending_without_reply(
self, default_bot, chat_id, location, custom
):
reply_to_message = await default_bot.send_message(chat_id, "test")
await reply_to_message.delete()
if custom is not None:
message = await default_bot.send_location(
chat_id,
location=location,
allow_sending_without_reply=custom,
reply_to_message_id=reply_to_message.message_id,
)
assert message.reply_to_message is None
elif default_bot.defaults.allow_sending_without_reply:
message = await default_bot.send_location(
chat_id, location=location, reply_to_message_id=reply_to_message.message_id
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
await default_bot.send_location(
chat_id, location=location, reply_to_message_id=reply_to_message.message_id
)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_location_default_protect_content(self, chat_id, default_bot, location):
tasks = asyncio.gather(
default_bot.send_location(chat_id, location=location),
default_bot.send_location(chat_id, location=location, protect_content=False),
)
protected, unprotected = await tasks
assert protected.has_protected_content
assert not unprotected.has_protected_content
@pytest.mark.xfail
async def test_send_live_location(self, bot, chat_id):
message = await bot.send_location(
@@ -110,135 +247,3 @@ class TestLocation:
await bot.edit_message_live_location(
message.chat_id, message.message_id, latitude=52.223880, longitude=5.164306
)
# TODO: Needs improvement with in inline sent live location.
async def test_edit_live_inline_message(self, monkeypatch, bot, location):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
data = request_data.json_parameters
lat = data["latitude"] == str(location.latitude)
lon = data["longitude"] == str(location.longitude)
id_ = data["inline_message_id"] == "1234"
ha = data["horizontal_accuracy"] == "50"
heading = data["heading"] == "90"
prox_alert = data["proximity_alert_radius"] == "1000"
return lat and lon and id_ and ha and heading and prox_alert
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.edit_message_live_location(
inline_message_id=1234,
location=location,
horizontal_accuracy=50,
heading=90,
proximity_alert_radius=1000,
)
# TODO: Needs improvement with in inline sent live location.
async def test_stop_live_inline_message(self, monkeypatch, bot):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
id_ = request_data.json_parameters["inline_message_id"] == "1234"
return id_
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.stop_message_live_location(inline_message_id=1234)
async def test_send_with_location(self, monkeypatch, bot, chat_id, location):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
lat = request_data.json_parameters["latitude"] == str(location.latitude)
lon = request_data.json_parameters["longitude"] == str(location.longitude)
return lat and lon
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_location(location=location, chat_id=chat_id)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize(
"default_bot,custom",
[
({"allow_sending_without_reply": True}, None),
({"allow_sending_without_reply": False}, None),
({"allow_sending_without_reply": False}, True),
],
indirect=["default_bot"],
)
async def test_send_location_default_allow_sending_without_reply(
self, default_bot, chat_id, location, custom
):
reply_to_message = await default_bot.send_message(chat_id, "test")
await reply_to_message.delete()
if custom is not None:
message = await default_bot.send_location(
chat_id,
location=location,
allow_sending_without_reply=custom,
reply_to_message_id=reply_to_message.message_id,
)
assert message.reply_to_message is None
elif default_bot.defaults.allow_sending_without_reply:
message = await default_bot.send_location(
chat_id, location=location, reply_to_message_id=reply_to_message.message_id
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
await default_bot.send_location(
chat_id, location=location, reply_to_message_id=reply_to_message.message_id
)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_location_default_protect_content(self, chat_id, default_bot, location):
protected = await default_bot.send_location(chat_id, location=location)
assert protected.has_protected_content
unprotected = await default_bot.send_location(
chat_id, location=location, protect_content=False
)
assert not unprotected.has_protected_content
async def test_edit_live_location_with_location(self, monkeypatch, bot, location):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
lat = request_data.json_parameters["latitude"] == str(location.latitude)
lon = request_data.json_parameters["longitude"] == str(location.longitude)
return lat and lon
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.edit_message_live_location(None, None, location=location)
async def test_send_location_without_required(self, bot, chat_id):
with pytest.raises(ValueError, match="Either location or latitude and longitude"):
await bot.send_location(chat_id=chat_id)
async def test_edit_location_without_required(self, bot):
with pytest.raises(ValueError, match="Either location or latitude and longitude"):
await bot.edit_message_live_location(chat_id=2, message_id=3)
async def test_send_location_with_all_args(self, bot, location):
with pytest.raises(ValueError, match="Not both"):
await bot.send_location(chat_id=1, latitude=2.5, longitude=4.6, location=location)
async def test_edit_location_with_all_args(self, bot, location):
with pytest.raises(ValueError, match="Not both"):
await bot.edit_message_live_location(
chat_id=1, message_id=7, latitude=2.5, longitude=4.6, location=location
)
def test_to_dict(self, location):
location_dict = location.to_dict()
assert location_dict["latitude"] == location.latitude
assert location_dict["longitude"] == location.longitude
assert location_dict["horizontal_accuracy"] == location.horizontal_accuracy
assert location_dict["live_period"] == location.live_period
assert location["heading"] == location.heading
assert location["proximity_alert_radius"] == location.proximity_alert_radius
def test_equality(self):
a = Location(self.longitude, self.latitude)
b = Location(self.longitude, self.latitude)
d = Location(0, self.latitude)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != d
assert hash(a) != hash(d)
+326 -358
View File
@@ -15,6 +15,7 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import os
from io import BytesIO
from pathlib import Path
@@ -30,39 +31,39 @@ from tests.auxil.bot_method_checks import (
check_shortcut_call,
check_shortcut_signature,
)
from tests.conftest import data_file, expect_bad_request
from tests.auxil.files import data_file
from tests.auxil.networking import expect_bad_request
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="function")
def photo_file():
f = data_file("telegram.jpg").open("rb")
yield f
f.close()
with data_file("telegram.jpg").open("rb") as f:
yield f
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
async def _photo(bot, chat_id):
async def func():
with data_file("telegram.jpg").open("rb") as f:
photo = (await bot.send_photo(chat_id, photo=f, read_timeout=50)).photo
return photo
return (await bot.send_photo(chat_id, photo=f, read_timeout=50)).photo
return await expect_bad_request(
func, "Type of file mismatch", "Telegram did not accept the file."
)
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def thumb(_photo):
return _photo[0]
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def photo(_photo):
return _photo[-1]
class TestPhoto:
class TestPhotoBase:
width = 800
height = 800
caption = "<b>PhotoTest</b> - *Caption*"
@@ -71,7 +72,9 @@ class TestPhoto:
# so we accept three different sizes here. Shouldn't be too much
file_size = [29176, 27662]
def test_slot_behaviour(self, photo, mro_slots):
class TestPhotoWithoutRequest(TestPhotoBase):
def test_slot_behaviour(self, photo):
for attr in photo.__slots__:
assert getattr(photo, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(photo)) == len(set(mro_slots(photo))), "duplicate slot"
@@ -98,328 +101,6 @@ class TestPhoto:
assert thumb.height == 90
assert thumb.file_size == 1477
@pytest.mark.flaky(3, 1)
async def test_send_photo_all_args(self, bot, chat_id, photo_file, thumb, photo):
message = await bot.send_photo(
chat_id,
photo_file,
caption=self.caption,
disable_notification=False,
protect_content=True,
parse_mode="Markdown",
has_spoiler=True,
)
assert isinstance(message.photo[-2], PhotoSize)
assert isinstance(message.photo[-2].file_id, str)
assert isinstance(message.photo[-2].file_unique_id, str)
assert message.photo[-2].file_id != ""
assert message.photo[-2].file_unique_id != ""
assert isinstance(message.photo[-1], PhotoSize)
assert isinstance(message.photo[-1].file_id, str)
assert isinstance(message.photo[-1].file_unique_id, str)
assert message.photo[-1].file_id != ""
assert message.photo[-1].file_unique_id != ""
assert message.caption == TestPhoto.caption.replace("*", "")
assert message.has_protected_content
assert message.has_media_spoiler
@pytest.mark.flaky(3, 1)
async def test_send_photo_custom_filename(self, bot, chat_id, photo_file, monkeypatch):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return list(request_data.multipart_data.values())[0][0] == "custom_filename"
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_photo(chat_id, photo_file, filename="custom_filename")
@pytest.mark.flaky(3, 1)
async def test_send_photo_parse_mode_markdown(self, bot, chat_id, photo_file, thumb, photo):
message = await bot.send_photo(
chat_id, photo_file, caption=self.caption, parse_mode="Markdown"
)
assert isinstance(message.photo[-2], PhotoSize)
assert isinstance(message.photo[-2].file_id, str)
assert isinstance(message.photo[-2].file_unique_id, str)
assert message.photo[-2].file_id != ""
assert message.photo[-2].file_unique_id != ""
assert isinstance(message.photo[-1], PhotoSize)
assert isinstance(message.photo[-1].file_id, str)
assert isinstance(message.photo[-1].file_unique_id, str)
assert message.photo[-1].file_id != ""
assert message.photo[-1].file_unique_id != ""
assert message.caption == TestPhoto.caption.replace("*", "")
assert len(message.caption_entities) == 1
@pytest.mark.flaky(3, 1)
async def test_send_photo_parse_mode_html(self, bot, chat_id, photo_file, thumb, photo):
message = await bot.send_photo(
chat_id, photo_file, caption=self.caption, parse_mode="HTML"
)
assert isinstance(message.photo[-2], PhotoSize)
assert isinstance(message.photo[-2].file_id, str)
assert isinstance(message.photo[-2].file_unique_id, str)
assert message.photo[-2].file_id != ""
assert message.photo[-2].file_unique_id != ""
assert isinstance(message.photo[-1], PhotoSize)
assert isinstance(message.photo[-1].file_id, str)
assert isinstance(message.photo[-1].file_unique_id, str)
assert message.photo[-1].file_id != ""
assert message.photo[-1].file_unique_id != ""
assert message.caption == TestPhoto.caption.replace("<b>", "").replace("</b>", "")
assert len(message.caption_entities) == 1
@pytest.mark.flaky(3, 1)
async def test_send_photo_caption_entities(self, bot, chat_id, photo_file, thumb, photo):
test_string = "Italic Bold Code"
entities = [
MessageEntity(MessageEntity.ITALIC, 0, 6),
MessageEntity(MessageEntity.ITALIC, 7, 4),
MessageEntity(MessageEntity.ITALIC, 12, 4),
]
message = await bot.send_photo(
chat_id, photo_file, caption=test_string, caption_entities=entities
)
assert message.caption == test_string
assert message.caption_entities == tuple(entities)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_photo_default_parse_mode_1(
self, default_bot, chat_id, photo_file, thumb, photo
):
test_string = "Italic Bold Code"
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_photo(chat_id, photo_file, caption=test_markdown_string)
assert message.caption_markdown == test_markdown_string
assert message.caption == test_string
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_photo_default_parse_mode_2(
self, default_bot, chat_id, photo_file, thumb, photo
):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_photo(
chat_id, photo_file, caption=test_markdown_string, parse_mode=None
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_photo_default_parse_mode_3(
self, default_bot, chat_id, photo_file, thumb, photo
):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_photo(
chat_id, photo_file, caption=test_markdown_string, parse_mode="HTML"
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_photo_default_protect_content(self, chat_id, default_bot, photo):
protected = await default_bot.send_photo(chat_id, photo)
assert protected.has_protected_content
unprotected = await default_bot.send_photo(chat_id, photo, protect_content=False)
assert not unprotected.has_protected_content
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_photo_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("photo") == expected
else:
test_flag = isinstance(data.get("photo"), InputFile)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_photo(chat_id, file)
assert test_flag
finally:
bot._local_mode = False
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize(
"default_bot,custom",
[
({"allow_sending_without_reply": True}, None),
({"allow_sending_without_reply": False}, None),
({"allow_sending_without_reply": False}, True),
],
indirect=["default_bot"],
)
async def test_send_photo_default_allow_sending_without_reply(
self, default_bot, chat_id, photo_file, thumb, photo, custom
):
reply_to_message = await default_bot.send_message(chat_id, "test")
await reply_to_message.delete()
if custom is not None:
message = await default_bot.send_photo(
chat_id,
photo_file,
allow_sending_without_reply=custom,
reply_to_message_id=reply_to_message.message_id,
)
assert message.reply_to_message is None
elif default_bot.defaults.allow_sending_without_reply:
message = await default_bot.send_photo(
chat_id, photo_file, reply_to_message_id=reply_to_message.message_id
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
await default_bot.send_photo(
chat_id, photo_file, reply_to_message_id=reply_to_message.message_id
)
@pytest.mark.flaky(3, 1)
async def test_get_and_download(self, bot, photo):
path = Path("telegram.jpg")
if path.is_file():
path.unlink()
new_file = await bot.getFile(photo.file_id)
assert new_file.file_size == photo.file_size
assert new_file.file_unique_id == photo.file_unique_id
assert new_file.file_path.startswith("https://") is True
await new_file.download_to_drive("telegram.jpg")
assert path.is_file()
@pytest.mark.flaky(3, 1)
async def test_send_url_jpg_file(self, bot, chat_id, thumb, photo):
message = await bot.send_photo(chat_id, photo=self.photo_file_url)
assert isinstance(message.photo[-2], PhotoSize)
assert isinstance(message.photo[-2].file_id, str)
assert isinstance(message.photo[-2].file_unique_id, str)
assert message.photo[-2].file_id != ""
assert message.photo[-2].file_unique_id != ""
assert isinstance(message.photo[-1], PhotoSize)
assert isinstance(message.photo[-1].file_id, str)
assert isinstance(message.photo[-1].file_unique_id, str)
assert message.photo[-1].file_id != ""
assert message.photo[-1].file_unique_id != ""
@pytest.mark.flaky(3, 1)
async def test_send_url_png_file(self, bot, chat_id):
message = await bot.send_photo(
photo="http://dummyimage.com/600x400/000/fff.png&text=telegram", chat_id=chat_id
)
photo = message.photo[-1]
assert isinstance(photo, PhotoSize)
assert isinstance(photo.file_id, str)
assert isinstance(photo.file_unique_id, str)
assert photo.file_id != ""
assert photo.file_unique_id != ""
@pytest.mark.flaky(3, 1)
async def test_send_url_gif_file(self, bot, chat_id):
message = await bot.send_photo(
photo="http://dummyimage.com/600x400/000/fff.png&text=telegram", chat_id=chat_id
)
photo = message.photo[-1]
assert isinstance(photo, PhotoSize)
assert isinstance(photo.file_id, str)
assert isinstance(photo.file_unique_id, str)
assert photo.file_id != ""
assert photo.file_unique_id != ""
@pytest.mark.flaky(3, 1)
async def test_send_file_unicode_filename(self, bot, chat_id):
"""
Regression test for https://github.com/python-telegram-bot/python-telegram-bot/issues/1202
"""
with data_file("测试.png").open("rb") as f:
message = await bot.send_photo(photo=f, chat_id=chat_id)
photo = message.photo[-1]
assert isinstance(photo, PhotoSize)
assert isinstance(photo.file_id, str)
assert isinstance(photo.file_unique_id, str)
assert photo.file_id != ""
assert photo.file_unique_id != ""
@pytest.mark.flaky(3, 1)
async def test_send_bytesio_jpg_file(self, bot, chat_id):
filepath = data_file("telegram_no_standard_header.jpg")
# raw image bytes
raw_bytes = BytesIO(filepath.read_bytes())
input_file = InputFile(raw_bytes)
assert input_file.mimetype == "application/octet-stream"
# raw image bytes with name info
raw_bytes = BytesIO(filepath.read_bytes())
raw_bytes.name = str(filepath)
input_file = InputFile(raw_bytes)
assert input_file.mimetype == "image/jpeg"
# send raw photo
raw_bytes = BytesIO(filepath.read_bytes())
message = await bot.send_photo(chat_id, photo=raw_bytes)
photo = message.photo[-1]
assert isinstance(photo.file_id, str)
assert isinstance(photo.file_unique_id, str)
assert photo.file_id != ""
assert photo.file_unique_id != ""
assert isinstance(photo, PhotoSize)
assert photo.width == 1280
assert photo.height == 720
assert photo.file_size == 33372
async def test_send_with_photosize(self, monkeypatch, bot, chat_id, photo):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.json_parameters["photo"] == photo.file_id
monkeypatch.setattr(bot.request, "post", make_assertion)
message = await bot.send_photo(photo=photo, chat_id=chat_id)
assert message
@pytest.mark.flaky(3, 1)
async def test_resend(self, bot, chat_id, photo, thumb):
message = await bot.send_photo(chat_id=chat_id, photo=photo.file_id)
assert isinstance(message.photo[-2], PhotoSize)
assert isinstance(message.photo[-2].file_id, str)
assert isinstance(message.photo[-2].file_unique_id, str)
assert message.photo[-2].file_id != ""
assert message.photo[-2].file_unique_id != ""
assert isinstance(message.photo[-1], PhotoSize)
assert isinstance(message.photo[-1].file_id, str)
assert isinstance(message.photo[-1].file_unique_id, str)
assert message.photo[-1].file_id != ""
assert message.photo[-1].file_unique_id != ""
def test_de_json(self, bot, photo):
json_dict = {
"file_id": photo.file_id,
@@ -447,31 +128,6 @@ class TestPhoto:
assert photo_dict["height"] == photo.height
assert photo_dict["file_size"] == photo.file_size
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_photo(chat_id=chat_id, photo=open(os.devnull, "rb"))
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file_id(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_photo(chat_id=chat_id, photo="")
async def test_error_without_required_args(self, bot, chat_id):
with pytest.raises(TypeError):
await bot.send_photo(chat_id=chat_id)
async def test_get_file_instance_method(self, monkeypatch, photo):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == photo.file_id
assert check_shortcut_signature(PhotoSize.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(photo.get_file, photo.get_bot(), "get_file")
assert await check_defaults_handling(photo.get_file, photo.get_bot())
monkeypatch.setattr(photo.get_bot(), "get_file", make_assertion)
assert await photo.get_file()
def test_equality(self, photo):
a = PhotoSize(photo.file_id, photo.file_unique_id, self.width, self.height)
b = PhotoSize("", photo.file_unique_id, self.width, self.height)
@@ -499,3 +155,315 @@ class TestPhoto:
assert a != e
assert hash(a) != hash(e)
async def test_error_without_required_args(self, bot, chat_id):
with pytest.raises(TypeError):
await bot.send_photo(chat_id=chat_id)
async def test_send_photo_custom_filename(self, bot, chat_id, photo_file, monkeypatch):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return list(request_data.multipart_data.values())[0][0] == "custom_filename"
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_photo(chat_id, photo_file, filename="custom_filename")
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_photo_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("photo") == expected
else:
test_flag = isinstance(data.get("photo"), InputFile)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_photo(chat_id, file)
assert test_flag
finally:
bot._local_mode = False
async def test_send_with_photosize(self, monkeypatch, bot, chat_id, photo):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.json_parameters["photo"] == photo.file_id
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_photo(photo=photo, chat_id=chat_id)
async def test_get_file_instance_method(self, monkeypatch, photo):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == photo.file_id
assert check_shortcut_signature(PhotoSize.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(photo.get_file, photo.get_bot(), "get_file")
assert await check_defaults_handling(photo.get_file, photo.get_bot())
monkeypatch.setattr(photo.get_bot(), "get_file", make_assertion)
assert await photo.get_file()
class TestPhotoWithRequest(TestPhotoBase):
async def test_send_photo_all_args(self, bot, chat_id, photo_file):
message = await bot.send_photo(
chat_id,
photo_file,
caption=self.caption,
disable_notification=False,
protect_content=True,
parse_mode="Markdown",
has_spoiler=True,
)
assert isinstance(message.photo[-2], PhotoSize)
assert isinstance(message.photo[-2].file_id, str)
assert isinstance(message.photo[-2].file_unique_id, str)
assert message.photo[-2].file_id != ""
assert message.photo[-2].file_unique_id != ""
assert isinstance(message.photo[-1], PhotoSize)
assert isinstance(message.photo[-1].file_id, str)
assert isinstance(message.photo[-1].file_unique_id, str)
assert message.photo[-1].file_id != ""
assert message.photo[-1].file_unique_id != ""
assert message.caption == self.caption.replace("*", "")
assert message.has_protected_content
assert message.has_media_spoiler
async def test_send_photo_parse_mode_markdown(self, bot, chat_id, photo_file):
message = await bot.send_photo(
chat_id, photo_file, caption=self.caption, parse_mode="Markdown"
)
assert isinstance(message.photo[-2], PhotoSize)
assert isinstance(message.photo[-2].file_id, str)
assert isinstance(message.photo[-2].file_unique_id, str)
assert message.photo[-2].file_id != ""
assert message.photo[-2].file_unique_id != ""
assert isinstance(message.photo[-1], PhotoSize)
assert isinstance(message.photo[-1].file_id, str)
assert isinstance(message.photo[-1].file_unique_id, str)
assert message.photo[-1].file_id != ""
assert message.photo[-1].file_unique_id != ""
assert message.caption == self.caption.replace("*", "")
assert len(message.caption_entities) == 1
async def test_send_photo_parse_mode_html(self, bot, chat_id, photo_file):
message = await bot.send_photo(
chat_id, photo_file, caption=self.caption, parse_mode="HTML"
)
assert isinstance(message.photo[-2], PhotoSize)
assert isinstance(message.photo[-2].file_id, str)
assert isinstance(message.photo[-2].file_unique_id, str)
assert message.photo[-2].file_id != ""
assert message.photo[-2].file_unique_id != ""
assert isinstance(message.photo[-1], PhotoSize)
assert isinstance(message.photo[-1].file_id, str)
assert isinstance(message.photo[-1].file_unique_id, str)
assert message.photo[-1].file_id != ""
assert message.photo[-1].file_unique_id != ""
assert message.caption == self.caption.replace("<b>", "").replace("</b>", "")
assert len(message.caption_entities) == 1
async def test_send_photo_caption_entities(self, bot, chat_id, photo_file):
test_string = "Italic Bold Code"
entities = [
MessageEntity(MessageEntity.ITALIC, 0, 6),
MessageEntity(MessageEntity.ITALIC, 7, 4),
MessageEntity(MessageEntity.ITALIC, 12, 4),
]
message = await bot.send_photo(
chat_id, photo_file, caption=test_string, caption_entities=entities
)
assert message.caption == test_string
assert message.caption_entities == tuple(entities)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_photo_default_parse_mode_1(self, default_bot, chat_id, photo_file):
test_string = "Italic Bold Code"
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_photo(chat_id, photo_file, caption=test_markdown_string)
assert message.caption_markdown == test_markdown_string
assert message.caption == test_string
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_photo_default_parse_mode_2(self, default_bot, chat_id, photo_file):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_photo(
chat_id, photo_file, caption=test_markdown_string, parse_mode=None
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_photo_default_parse_mode_3(self, default_bot, chat_id, photo_file):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_photo(
chat_id, photo_file, caption=test_markdown_string, parse_mode="HTML"
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_photo_default_protect_content(self, chat_id, default_bot, photo):
tasks = asyncio.gather(
default_bot.send_photo(chat_id, photo),
default_bot.send_photo(chat_id, photo, protect_content=False),
)
protected, unprotected = await tasks
assert protected.has_protected_content
assert not unprotected.has_protected_content
@pytest.mark.parametrize(
"default_bot,custom",
[
({"allow_sending_without_reply": True}, None),
({"allow_sending_without_reply": False}, None),
({"allow_sending_without_reply": False}, True),
],
indirect=["default_bot"],
)
async def test_send_photo_default_allow_sending_without_reply(
self, default_bot, chat_id, photo_file, custom
):
reply_to_message = await default_bot.send_message(chat_id, "test")
await reply_to_message.delete()
if custom is not None:
message = await default_bot.send_photo(
chat_id,
photo_file,
allow_sending_without_reply=custom,
reply_to_message_id=reply_to_message.message_id,
)
assert message.reply_to_message is None
elif default_bot.defaults.allow_sending_without_reply:
message = await default_bot.send_photo(
chat_id, photo_file, reply_to_message_id=reply_to_message.message_id
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
await default_bot.send_photo(
chat_id, photo_file, reply_to_message_id=reply_to_message.message_id
)
async def test_get_and_download(self, bot, photo):
path = Path("telegram.jpg")
if path.is_file():
path.unlink()
new_file = await bot.getFile(photo.file_id)
assert new_file.file_size == photo.file_size
assert new_file.file_unique_id == photo.file_unique_id
assert new_file.file_path.startswith("https://") is True
await new_file.download_to_drive("telegram.jpg")
assert path.is_file()
async def test_send_url_jpg_file(self, bot, chat_id):
message = await bot.send_photo(chat_id, photo=self.photo_file_url)
assert isinstance(message.photo[-2], PhotoSize)
assert isinstance(message.photo[-2].file_id, str)
assert isinstance(message.photo[-2].file_unique_id, str)
assert message.photo[-2].file_id != ""
assert message.photo[-2].file_unique_id != ""
assert isinstance(message.photo[-1], PhotoSize)
assert isinstance(message.photo[-1].file_id, str)
assert isinstance(message.photo[-1].file_unique_id, str)
assert message.photo[-1].file_id != ""
assert message.photo[-1].file_unique_id != ""
async def test_send_url_png_file(self, bot, chat_id):
message = await bot.send_photo(
photo="http://dummyimage.com/600x400/000/fff.png&text=telegram", chat_id=chat_id
)
photo = message.photo[-1]
assert isinstance(photo, PhotoSize)
assert isinstance(photo.file_id, str)
assert isinstance(photo.file_unique_id, str)
assert photo.file_id != ""
assert photo.file_unique_id != ""
async def test_send_file_unicode_filename(self, bot, chat_id):
"""
Regression test for https://github.com/python-telegram-bot/python-telegram-bot/issues/1202
"""
with data_file("测试.png").open("rb") as f:
message = await bot.send_photo(photo=f, chat_id=chat_id)
photo = message.photo[-1]
assert isinstance(photo, PhotoSize)
assert isinstance(photo.file_id, str)
assert isinstance(photo.file_unique_id, str)
assert photo.file_id != ""
assert photo.file_unique_id != ""
async def test_send_bytesio_jpg_file(self, bot, chat_id):
filepath = data_file("telegram_no_standard_header.jpg")
# raw image bytes
raw_bytes = BytesIO(filepath.read_bytes())
input_file = InputFile(raw_bytes)
assert input_file.mimetype == "application/octet-stream"
# raw image bytes with name info
raw_bytes = BytesIO(filepath.read_bytes())
raw_bytes.name = str(filepath)
input_file = InputFile(raw_bytes)
assert input_file.mimetype == "image/jpeg"
# send raw photo
raw_bytes = BytesIO(filepath.read_bytes())
message = await bot.send_photo(chat_id, photo=raw_bytes)
photo = message.photo[-1]
assert isinstance(photo.file_id, str)
assert isinstance(photo.file_unique_id, str)
assert photo.file_id != ""
assert photo.file_unique_id != ""
assert isinstance(photo, PhotoSize)
assert photo.width == 1280
assert photo.height == 720
assert photo.file_size == 33372
async def test_resend(self, bot, chat_id, photo):
message = await bot.send_photo(chat_id=chat_id, photo=photo.file_id)
assert isinstance(message.photo[-2], PhotoSize)
assert isinstance(message.photo[-2].file_id, str)
assert isinstance(message.photo[-2].file_unique_id, str)
assert message.photo[-2].file_id != ""
assert message.photo[-2].file_unique_id != ""
assert isinstance(message.photo[-1], PhotoSize)
assert isinstance(message.photo[-1].file_id, str)
assert isinstance(message.photo[-1].file_unique_id, str)
assert message.photo[-1].file_id != ""
assert message.photo[-1].file_unique_id != ""
async def test_error_send_empty_file(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_photo(chat_id=chat_id, photo=open(os.devnull, "rb"))
async def test_error_send_empty_file_id(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_photo(chat_id=chat_id, photo="")
File diff suppressed because it is too large Load Diff
@@ -16,27 +16,30 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import pytest
from telegram import Location, Venue
from telegram.error import BadRequest
from telegram.request import RequestData
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def venue():
return Venue(
TestVenue.location,
TestVenue.title,
TestVenue.address,
foursquare_id=TestVenue.foursquare_id,
foursquare_type=TestVenue.foursquare_type,
google_place_id=TestVenue.google_place_id,
google_place_type=TestVenue.google_place_type,
TestVenueBase.location,
TestVenueBase.title,
TestVenueBase.address,
foursquare_id=TestVenueBase.foursquare_id,
foursquare_type=TestVenueBase.foursquare_type,
google_place_id=TestVenueBase.google_place_id,
google_place_type=TestVenueBase.google_place_type,
)
class TestVenue:
class TestVenueBase:
location = Location(longitude=-46.788279, latitude=-23.691288)
title = "title"
address = "address"
@@ -45,20 +48,22 @@ class TestVenue:
google_place_id = "google place id"
google_place_type = "google place type"
def test_slot_behaviour(self, venue, mro_slots):
class TestVenueWithoutRequest(TestVenueBase):
def test_slot_behaviour(self, venue):
for attr in venue.__slots__:
assert getattr(venue, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(venue)) == len(set(mro_slots(venue))), "duplicate slot"
def test_de_json(self, bot):
json_dict = {
"location": TestVenue.location.to_dict(),
"title": TestVenue.title,
"address": TestVenue.address,
"foursquare_id": TestVenue.foursquare_id,
"foursquare_type": TestVenue.foursquare_type,
"google_place_id": TestVenue.google_place_id,
"google_place_type": TestVenue.google_place_type,
"location": self.location.to_dict(),
"title": self.title,
"address": self.address,
"foursquare_id": self.foursquare_id,
"foursquare_type": self.foursquare_type,
"google_place_id": self.google_place_id,
"google_place_type": self.google_place_type,
}
venue = Venue.de_json(json_dict, bot)
assert venue.api_kwargs == {}
@@ -71,6 +76,53 @@ class TestVenue:
assert venue.google_place_id == self.google_place_id
assert venue.google_place_type == self.google_place_type
def test_to_dict(self, venue):
venue_dict = venue.to_dict()
assert isinstance(venue_dict, dict)
assert venue_dict["location"] == venue.location.to_dict()
assert venue_dict["title"] == venue.title
assert venue_dict["address"] == venue.address
assert venue_dict["foursquare_id"] == venue.foursquare_id
assert venue_dict["foursquare_type"] == venue.foursquare_type
assert venue_dict["google_place_id"] == venue.google_place_id
assert venue_dict["google_place_type"] == venue.google_place_type
def test_equality(self):
a = Venue(Location(0, 0), self.title, self.address)
b = Venue(Location(0, 0), self.title, self.address)
c = Venue(Location(0, 0), self.title, "")
d = Venue(Location(0, 1), self.title, self.address)
d2 = Venue(Location(0, 0), "", self.address)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != d2
assert hash(a) != hash(d2)
async def test_send_venue_without_required(self, bot, chat_id):
with pytest.raises(ValueError, match="Either venue or latitude, longitude, address and"):
await bot.send_venue(chat_id=chat_id)
async def test_send_venue_mutually_exclusive(self, bot, chat_id, venue):
with pytest.raises(ValueError, match="Not both"):
await bot.send_venue(
chat_id=chat_id,
latitude=1,
longitude=1,
address="address",
title="title",
venue=venue,
)
async def test_send_with_venue(self, monkeypatch, bot, chat_id, venue):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
data = request_data.json_parameters
@@ -89,7 +141,8 @@ class TestVenue:
message = await bot.send_venue(chat_id, venue=venue)
assert message
@pytest.mark.flaky(3, 1)
class TestVenueWithRequest(TestVenueBase):
@pytest.mark.parametrize(
"default_bot,custom",
[
@@ -123,57 +176,12 @@ class TestVenue:
chat_id, venue=venue, reply_to_message_id=reply_to_message.message_id
)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_venue_default_protect_content(self, default_bot, chat_id, venue):
protected = await default_bot.send_venue(chat_id, venue=venue)
tasks = asyncio.gather(
default_bot.send_venue(chat_id, venue=venue),
default_bot.send_venue(chat_id, venue=venue, protect_content=False),
)
protected, unprotected = await tasks
assert protected.has_protected_content
unprotected = await default_bot.send_venue(chat_id, venue=venue, protect_content=False)
assert not unprotected.has_protected_content
async def test_send_venue_without_required(self, bot, chat_id):
with pytest.raises(ValueError, match="Either venue or latitude, longitude, address and"):
await bot.send_venue(chat_id=chat_id)
async def test_send_venue_mutually_exclusive(self, bot, chat_id, venue):
with pytest.raises(ValueError, match="Not both"):
await bot.send_venue(
chat_id=chat_id,
latitude=1,
longitude=1,
address="address",
title="title",
venue=venue,
)
def test_to_dict(self, venue):
venue_dict = venue.to_dict()
assert isinstance(venue_dict, dict)
assert venue_dict["location"] == venue.location.to_dict()
assert venue_dict["title"] == venue.title
assert venue_dict["address"] == venue.address
assert venue_dict["foursquare_id"] == venue.foursquare_id
assert venue_dict["foursquare_type"] == venue.foursquare_type
assert venue_dict["google_place_id"] == venue.google_place_id
assert venue_dict["google_place_type"] == venue.google_place_type
def test_equality(self):
a = Venue(Location(0, 0), self.title, self.address)
b = Venue(Location(0, 0), self.title, self.address)
c = Venue(Location(0, 0), self.title, "")
d = Venue(Location(0, 1), self.title, self.address)
d2 = Venue(Location(0, 0), "", self.address)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != d2
assert hash(a) != hash(d2)
+264 -253
View File
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import os
from pathlib import Path
@@ -30,23 +31,24 @@ from tests.auxil.bot_method_checks import (
check_shortcut_call,
check_shortcut_signature,
)
from tests.conftest import data_file
from tests.auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs
from tests.auxil.files import data_file
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="function")
def video_file():
f = data_file("telegram.mp4").open("rb")
yield f
f.close()
with data_file("telegram.mp4").open("rb") as f:
yield f
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
async def video(bot, chat_id):
with data_file("telegram.mp4").open("rb") as f:
return (await bot.send_video(chat_id, video=f, read_timeout=50)).video
class TestVideo:
class TestVideoBase:
width = 360
height = 640
duration = 5
@@ -54,18 +56,17 @@ class TestVideo:
mime_type = "video/mp4"
supports_streaming = True
file_name = "telegram.mp4"
thumb_width = 180
thumb_height = 320
thumb_file_size = 1767
caption = "<b>VideoTest</b> - *Caption*"
video_file_url = "https://python-telegram-bot.org/static/testfiles/telegram.mp4"
video_file_id = "5a3128a4d2a04750b5b58397f3b5e812"
video_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e"
def test_slot_behaviour(self, video, mro_slots):
class TestVideoWithoutRequest(TestVideoBase):
def test_slot_behaviour(self, video):
for attr in video.__slots__:
assert getattr(video, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(video)) == len(set(mro_slots(video))), "duplicate slot"
@@ -78,11 +79,11 @@ class TestVideo:
assert video.file_id != ""
assert video.file_unique_id != ""
assert isinstance(video.thumb, PhotoSize)
assert isinstance(video.thumb.file_id, str)
assert isinstance(video.thumb.file_unique_id, str)
assert video.thumb.file_id != ""
assert video.thumb.file_unique_id != ""
assert isinstance(video.thumbnail, PhotoSize)
assert isinstance(video.thumbnail.file_id, str)
assert isinstance(video.thumbnail.file_unique_id, str)
assert video.thumbnail.file_id != ""
assert video.thumbnail.file_unique_id != ""
def test_expected_values(self, video):
assert video.width == self.width
@@ -91,220 +92,17 @@ class TestVideo:
assert video.file_size == self.file_size
assert video.mime_type == self.mime_type
@pytest.mark.flaky(3, 1)
async def test_send_all_args(self, bot, chat_id, video_file, video, thumb_file):
message = await bot.send_video(
chat_id,
video_file,
duration=self.duration,
caption=self.caption,
supports_streaming=self.supports_streaming,
disable_notification=False,
protect_content=True,
width=video.width,
height=video.height,
parse_mode="Markdown",
thumb=thumb_file,
has_spoiler=True,
def test_thumb_property_deprecation_warning(self, recwarn):
video = Video(
self.video_file_id,
self.video_file_unique_id,
self.width,
self.height,
self.duration,
thumb=object(),
)
assert isinstance(message.video, Video)
assert isinstance(message.video.file_id, str)
assert isinstance(message.video.file_unique_id, str)
assert message.video.file_id != ""
assert message.video.file_unique_id != ""
assert message.video.width == video.width
assert message.video.height == video.height
assert message.video.duration == video.duration
assert message.video.file_size == video.file_size
assert message.caption == self.caption.replace("*", "")
assert message.video.thumb.file_size == self.thumb_file_size
assert message.video.thumb.width == self.thumb_width
assert message.video.thumb.height == self.thumb_height
assert message.video.file_name == self.file_name
assert message.has_protected_content
assert message.has_media_spoiler
@pytest.mark.flaky(3, 1)
async def test_send_video_custom_filename(self, bot, chat_id, video_file, monkeypatch):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return list(request_data.multipart_data.values())[0][0] == "custom_filename"
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_video(chat_id, video_file, filename="custom_filename")
@pytest.mark.flaky(3, 1)
async def test_get_and_download(self, bot, video):
path = Path("telegram.mp4")
if path.is_file():
path.unlink()
new_file = await bot.get_file(video.file_id)
assert new_file.file_size == self.file_size
assert new_file.file_id == video.file_id
assert new_file.file_unique_id == video.file_unique_id
assert new_file.file_path.startswith("https://")
await new_file.download_to_drive("telegram.mp4")
assert path.is_file()
@pytest.mark.flaky(3, 1)
async def test_send_mp4_file_url(self, bot, chat_id, video):
message = await bot.send_video(chat_id, self.video_file_url, caption=self.caption)
assert isinstance(message.video, Video)
assert isinstance(message.video.file_id, str)
assert isinstance(message.video.file_unique_id, str)
assert message.video.file_id != ""
assert message.video.file_unique_id != ""
assert message.video.width == video.width
assert message.video.height == video.height
assert message.video.duration == video.duration
assert message.video.file_size == video.file_size
assert isinstance(message.video.thumb, PhotoSize)
assert isinstance(message.video.thumb.file_id, str)
assert isinstance(message.video.thumb.file_unique_id, str)
assert message.video.thumb.file_id != ""
assert message.video.thumb.file_unique_id != ""
assert message.video.thumb.width == 51 # This seems odd that it's not self.thumb_width
assert message.video.thumb.height == 90 # Ditto
assert message.video.thumb.file_size == 645 # same
assert message.caption == self.caption
@pytest.mark.flaky(3, 1)
async def test_send_video_caption_entities(self, bot, chat_id, video):
test_string = "Italic Bold Code"
entities = [
MessageEntity(MessageEntity.ITALIC, 0, 6),
MessageEntity(MessageEntity.ITALIC, 7, 4),
MessageEntity(MessageEntity.ITALIC, 12, 4),
]
message = await bot.send_video(
chat_id, video, caption=test_string, caption_entities=entities
)
assert message.caption == test_string
assert message.caption_entities == tuple(entities)
@pytest.mark.flaky(3, 1)
async def test_resend(self, bot, chat_id, video):
message = await bot.send_video(chat_id, video.file_id)
assert message.video == video
async def test_send_with_video(self, monkeypatch, bot, chat_id, video):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.json_parameters["video"] == video.file_id
monkeypatch.setattr(bot.request, "post", make_assertion)
message = await bot.send_video(chat_id, video=video)
assert message
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_video_default_parse_mode_1(self, default_bot, chat_id, video):
test_string = "Italic Bold Code"
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_video(chat_id, video, caption=test_markdown_string)
assert message.caption_markdown == test_markdown_string
assert message.caption == test_string
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_video_default_parse_mode_2(self, default_bot, chat_id, video):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_video(
chat_id, video, caption=test_markdown_string, parse_mode=None
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_video_default_parse_mode_3(self, default_bot, chat_id, video):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_video(
chat_id, video, caption=test_markdown_string, parse_mode="HTML"
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_video_default_protect_content(self, chat_id, default_bot, video):
protected = await default_bot.send_video(chat_id, video)
assert protected.has_protected_content
unprotected = await default_bot.send_video(chat_id, video, protect_content=False)
assert not unprotected.has_protected_content
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_video_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("video") == expected and data.get("thumb") == expected
else:
test_flag = isinstance(data.get("video"), InputFile) and isinstance(
data.get("thumb"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_video(chat_id, file, thumb=file)
assert test_flag
finally:
bot._local_mode = False
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize(
"default_bot,custom",
[
({"allow_sending_without_reply": True}, None),
({"allow_sending_without_reply": False}, None),
({"allow_sending_without_reply": False}, True),
],
indirect=["default_bot"],
)
async def test_send_video_default_allow_sending_without_reply(
self, default_bot, chat_id, video, custom
):
reply_to_message = await default_bot.send_message(chat_id, "test")
await reply_to_message.delete()
if custom is not None:
message = await default_bot.send_video(
chat_id,
video,
allow_sending_without_reply=custom,
reply_to_message_id=reply_to_message.message_id,
)
assert message.reply_to_message is None
elif default_bot.defaults.allow_sending_without_reply:
message = await default_bot.send_video(
chat_id, video, reply_to_message_id=reply_to_message.message_id
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
await default_bot.send_video(
chat_id, video, reply_to_message_id=reply_to_message.message_id
)
assert video.thumb is video.thumbnail
check_thumb_deprecation_warnings_for_args_and_attrs(recwarn, __file__)
def test_de_json(self, bot):
json_dict = {
@@ -342,31 +140,6 @@ class TestVideo:
assert video_dict["file_size"] == video.file_size
assert video_dict["file_name"] == video.file_name
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_video(chat_id, open(os.devnull, "rb"))
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file_id(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_video(chat_id, "")
async def test_error_without_required_args(self, bot, chat_id):
with pytest.raises(TypeError):
await bot.send_video(chat_id=chat_id)
async def test_get_file_instance_method(self, monkeypatch, video):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == video.file_id
assert check_shortcut_signature(Video.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(video.get_file, video.get_bot(), "get_file")
assert await check_defaults_handling(video.get_file, video.get_bot())
monkeypatch.setattr(video.get_bot(), "get_file", make_assertion)
assert await video.get_file()
def test_equality(self, video):
a = Video(video.file_id, video.file_unique_id, self.width, self.height, self.duration)
b = Video("", video.file_unique_id, self.width, self.height, self.duration)
@@ -386,3 +159,241 @@ class TestVideo:
assert a != e
assert hash(a) != hash(e)
async def test_error_without_required_args(self, bot, chat_id):
with pytest.raises(TypeError):
await bot.send_video(chat_id=chat_id)
async def test_send_with_video(self, monkeypatch, bot, chat_id, video):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.json_parameters["video"] == video.file_id
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_video(chat_id, video=video)
async def test_send_video_custom_filename(self, bot, chat_id, video_file, monkeypatch):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return list(request_data.multipart_data.values())[0][0] == "custom_filename"
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_video(chat_id, video_file, filename="custom_filename")
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_video_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
bot._local_mode = local_mode
# For just test that the correct paths are passed as we have no local bot API set up
test_flag = False
file = data_file("telegram.jpg")
expected = file.as_uri()
async def make_assertion(_, data, *args, **kwargs):
nonlocal test_flag
if local_mode:
test_flag = data.get("video") == expected and data.get("thumbnail") == expected
else:
test_flag = isinstance(data.get("video"), InputFile) and isinstance(
data.get("thumbnail"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_video(chat_id, file, thumbnail=file)
assert test_flag
finally:
bot._local_mode = False
async def test_send_video_with_local_files_throws_exception_with_different_thumb_and_thumbnail(
self, bot, chat_id
):
file = data_file("telegram.jpg")
different_file = data_file("telegram_no_standard_header.jpg")
with pytest.raises(ValueError, match="different entities as 'thumb' and 'thumbnail'"):
await bot.send_video(chat_id, file, thumbnail=file, thumb=different_file)
async def test_get_file_instance_method(self, monkeypatch, video):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == video.file_id
assert check_shortcut_signature(Video.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(video.get_file, video.get_bot(), "get_file")
assert await check_defaults_handling(video.get_file, video.get_bot())
monkeypatch.setattr(video.get_bot(), "get_file", make_assertion)
assert await video.get_file()
class TestVideoWithRequest(TestVideoBase):
async def test_send_all_args(self, bot, chat_id, video_file, video, thumb_file):
message = await bot.send_video(
chat_id,
video_file,
duration=self.duration,
caption=self.caption,
supports_streaming=self.supports_streaming,
disable_notification=False,
protect_content=True,
width=video.width,
height=video.height,
parse_mode="Markdown",
thumbnail=thumb_file,
has_spoiler=True,
)
assert isinstance(message.video, Video)
assert isinstance(message.video.file_id, str)
assert isinstance(message.video.file_unique_id, str)
assert message.video.file_id != ""
assert message.video.file_unique_id != ""
assert message.video.width == video.width
assert message.video.height == video.height
assert message.video.duration == video.duration
assert message.video.file_size == video.file_size
assert message.caption == self.caption.replace("*", "")
assert message.video.thumbnail.file_size == self.thumb_file_size
assert message.video.thumbnail.width == self.thumb_width
assert message.video.thumbnail.height == self.thumb_height
assert message.video.file_name == self.file_name
assert message.has_protected_content
assert message.has_media_spoiler
async def test_get_and_download(self, bot, video, chat_id):
path = Path("telegram.mp4")
if path.is_file():
path.unlink()
new_file = await bot.get_file(video.file_id)
assert new_file.file_size == self.file_size
assert new_file.file_unique_id == video.file_unique_id
assert new_file.file_path.startswith("https://")
await new_file.download_to_drive("telegram.mp4")
assert path.is_file()
async def test_send_mp4_file_url(self, bot, chat_id, video):
message = await bot.send_video(chat_id, self.video_file_url, caption=self.caption)
assert isinstance(message.video, Video)
assert isinstance(message.video.file_id, str)
assert isinstance(message.video.file_unique_id, str)
assert message.video.file_id != ""
assert message.video.file_unique_id != ""
assert message.video.width == video.width
assert message.video.height == video.height
assert message.video.duration == video.duration
assert message.video.file_size == video.file_size
assert isinstance(message.video.thumbnail, PhotoSize)
assert isinstance(message.video.thumbnail.file_id, str)
assert isinstance(message.video.thumbnail.file_unique_id, str)
assert message.video.thumbnail.file_id != ""
assert message.video.thumbnail.file_unique_id != ""
assert message.video.thumbnail.width == 51 # This seems odd that it's not self.thumb_width
assert message.video.thumbnail.height == 90 # Ditto
assert message.video.thumbnail.file_size == 645 # same
assert message.caption == self.caption
async def test_send_video_caption_entities(self, bot, chat_id, video):
test_string = "Italic Bold Code"
entities = [
MessageEntity(MessageEntity.ITALIC, 0, 6),
MessageEntity(MessageEntity.ITALIC, 7, 4),
MessageEntity(MessageEntity.ITALIC, 12, 4),
]
message = await bot.send_video(
chat_id, video, caption=test_string, caption_entities=entities
)
assert message.caption == test_string
assert message.caption_entities == tuple(entities)
async def test_resend(self, bot, chat_id, video):
message = await bot.send_video(chat_id, video.file_id)
assert message.video == video
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_video_default_parse_mode_1(self, default_bot, chat_id, video):
test_string = "Italic Bold Code"
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_video(chat_id, video, caption=test_markdown_string)
assert message.caption_markdown == test_markdown_string
assert message.caption == test_string
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_video_default_parse_mode_2(self, default_bot, chat_id, video):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_video(
chat_id, video, caption=test_markdown_string, parse_mode=None
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_video_default_parse_mode_3(self, default_bot, chat_id, video):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_video(
chat_id, video, caption=test_markdown_string, parse_mode="HTML"
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_video_default_protect_content(self, chat_id, default_bot, video):
tasks = asyncio.gather(
default_bot.send_video(chat_id, video),
default_bot.send_video(chat_id, video, protect_content=False),
)
protected, unprotected = await tasks
assert protected.has_protected_content
assert not unprotected.has_protected_content
@pytest.mark.parametrize(
"default_bot,custom",
[
({"allow_sending_without_reply": True}, None),
({"allow_sending_without_reply": False}, None),
({"allow_sending_without_reply": False}, True),
],
indirect=["default_bot"],
)
async def test_send_video_default_allow_sending_without_reply(
self, default_bot, chat_id, video, custom
):
reply_to_message = await default_bot.send_message(chat_id, "test")
await reply_to_message.delete()
if custom is not None:
message = await default_bot.send_video(
chat_id,
video,
allow_sending_without_reply=custom,
reply_to_message_id=reply_to_message.message_id,
)
assert message.reply_to_message is None
elif default_bot.defaults.allow_sending_without_reply:
message = await default_bot.send_video(
chat_id, video, reply_to_message_id=reply_to_message.message_id
)
assert message.reply_to_message is None
else:
with pytest.raises(BadRequest, match="message not found"):
await default_bot.send_video(
chat_id, video, reply_to_message_id=reply_to_message.message_id
)
async def test_error_send_empty_file(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_video(chat_id, open(os.devnull, "rb"))
async def test_error_send_empty_file_id(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_video(chat_id, "")
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import os
from pathlib import Path
@@ -29,36 +30,37 @@ from tests.auxil.bot_method_checks import (
check_shortcut_call,
check_shortcut_signature,
)
from tests.conftest import data_file
from tests.auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs
from tests.auxil.files import data_file
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="function")
def video_note_file():
f = data_file("telegram2.mp4").open("rb")
yield f
f.close()
with data_file("telegram2.mp4").open("rb") as f:
yield f
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
async def video_note(bot, chat_id):
with data_file("telegram2.mp4").open("rb") as f:
return (await bot.send_video_note(chat_id, video_note=f, read_timeout=50)).video_note
class TestVideoNote:
class TestVideoNoteBase:
length = 240
duration = 3
file_size = 132084
thumb_width = 240
thumb_height = 240
thumb_file_size = 11547
caption = "VideoNoteTest - Caption"
videonote_file_id = "5a3128a4d2a04750b5b58397f3b5e812"
videonote_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e"
def test_slot_behaviour(self, video_note, mro_slots):
class TestVideoNoteWithoutRequest(TestVideoNoteBase):
def test_slot_behaviour(self, video_note):
for attr in video_note.__slots__:
assert getattr(video_note, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(video_note)) == len(set(mro_slots(video_note))), "duplicate slot"
@@ -71,84 +73,23 @@ class TestVideoNote:
assert video_note.file_id != ""
assert video_note.file_unique_id != ""
assert isinstance(video_note.thumb, PhotoSize)
assert isinstance(video_note.thumb.file_id, str)
assert isinstance(video_note.thumb.file_unique_id, str)
assert video_note.thumb.file_id != ""
assert video_note.thumb.file_unique_id != ""
assert isinstance(video_note.thumbnail, PhotoSize)
assert isinstance(video_note.thumbnail.file_id, str)
assert isinstance(video_note.thumbnail.file_unique_id, str)
assert video_note.thumbnail.file_id != ""
assert video_note.thumbnail.file_unique_id != ""
def test_expected_values(self, video_note):
assert video_note.length == self.length
assert video_note.duration == self.duration
assert video_note.file_size == self.file_size
@pytest.mark.flaky(3, 1)
async def test_send_all_args(self, bot, chat_id, video_note_file, video_note, thumb_file):
message = await bot.send_video_note(
chat_id,
video_note_file,
duration=self.duration,
length=self.length,
disable_notification=False,
protect_content=True,
thumb=thumb_file,
def test_thumb_property_deprecation_warning(self, recwarn):
video_note = VideoNote(
file_id="id", file_unique_id="unique_id", length=1, duration=1, thumb=object()
)
assert isinstance(message.video_note, VideoNote)
assert isinstance(message.video_note.file_id, str)
assert isinstance(message.video_note.file_unique_id, str)
assert message.video_note.file_id != ""
assert message.video_note.file_unique_id != ""
assert message.video_note.length == video_note.length
assert message.video_note.duration == video_note.duration
assert message.video_note.file_size == video_note.file_size
assert message.video_note.thumb.file_size == self.thumb_file_size
assert message.video_note.thumb.width == self.thumb_width
assert message.video_note.thumb.height == self.thumb_height
assert message.has_protected_content
@pytest.mark.flaky(3, 1)
async def test_send_video_note_custom_filename(
self, bot, chat_id, video_note_file, monkeypatch
):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return list(request_data.multipart_data.values())[0][0] == "custom_filename"
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_video_note(chat_id, video_note_file, filename="custom_filename")
@pytest.mark.flaky(3, 1)
async def test_get_and_download(self, bot, video_note):
path = Path("telegram2.mp4")
if path.is_file():
path.unlink()
new_file = await bot.get_file(video_note.file_id)
assert new_file.file_size == self.file_size
assert new_file.file_id == video_note.file_id
assert new_file.file_unique_id == video_note.file_unique_id
assert new_file.file_path.startswith("https://")
await new_file.download_to_drive("telegram2.mp4")
assert path.is_file()
@pytest.mark.flaky(3, 1)
async def test_resend(self, bot, chat_id, video_note):
message = await bot.send_video_note(chat_id, video_note.file_id)
assert message.video_note == video_note
async def test_send_with_video_note(self, monkeypatch, bot, chat_id, video_note):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.json_parameters["video_note"] == video_note.file_id
monkeypatch.setattr(bot.request, "post", make_assertion)
message = await bot.send_video_note(chat_id, video_note=video_note)
assert message
assert video_note.thumb is video_note.thumbnail
check_thumb_deprecation_warnings_for_args_and_attrs(recwarn, __file__)
def test_de_json(self, bot):
json_dict = {
@@ -177,6 +118,47 @@ class TestVideoNote:
assert video_note_dict["duration"] == video_note.duration
assert video_note_dict["file_size"] == video_note.file_size
def test_equality(self, video_note):
a = VideoNote(video_note.file_id, video_note.file_unique_id, self.length, self.duration)
b = VideoNote("", video_note.file_unique_id, self.length, self.duration)
c = VideoNote(video_note.file_id, video_note.file_unique_id, 0, 0)
d = VideoNote("", "", self.length, self.duration)
e = Voice(video_note.file_id, video_note.file_unique_id, self.duration)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
async def test_error_without_required_args(self, bot, chat_id):
with pytest.raises(TypeError):
await bot.send_video_note(chat_id=chat_id)
async def test_send_with_video_note(self, monkeypatch, bot, chat_id, video_note):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.json_parameters["video_note"] == video_note.file_id
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_video_note(chat_id, video_note=video_note)
async def test_send_video_note_custom_filename(
self, bot, chat_id, video_note_file, monkeypatch
):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return list(request_data.multipart_data.values())[0][0] == "custom_filename"
monkeypatch.setattr(bot.request, "post", make_assertion)
assert await bot.send_video_note(chat_id, video_note_file, filename="custom_filename")
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_video_note_local_files(self, monkeypatch, bot, chat_id, local_mode):
try:
@@ -190,20 +172,85 @@ class TestVideoNote:
nonlocal test_flag
if local_mode:
test_flag = (
data.get("video_note") == expected and data.get("thumb") == expected
data.get("video_note") == expected and data.get("thumbnail") == expected
)
else:
test_flag = isinstance(data.get("video_note"), InputFile) and isinstance(
data.get("thumb"), InputFile
data.get("thumbnail"), InputFile
)
monkeypatch.setattr(bot, "_post", make_assertion)
await bot.send_video_note(chat_id, file, thumb=file)
await bot.send_video_note(chat_id, file, thumbnail=file)
assert test_flag
finally:
bot._local_mode = False
@pytest.mark.flaky(3, 1)
async def test_send_videonote_local_files_throws_exception_with_different_thumb_and_thumbnail(
self, bot, chat_id
):
file = data_file("telegram.jpg")
different_file = data_file("telegram_no_standard_header.jpg")
with pytest.raises(ValueError, match="different entities as 'thumb' and 'thumbnail'"):
await bot.send_video_note(chat_id, file, thumbnail=file, thumb=different_file)
async def test_get_file_instance_method(self, monkeypatch, video_note):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == video_note.file_id
assert check_shortcut_signature(VideoNote.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(video_note.get_file, video_note.get_bot(), "get_file")
assert await check_defaults_handling(video_note.get_file, video_note.get_bot())
monkeypatch.setattr(video_note.get_bot(), "get_file", make_assertion)
assert await video_note.get_file()
class TestVideoNoteWithRequest(TestVideoNoteBase):
async def test_send_all_args(self, bot, chat_id, video_note_file, video_note, thumb_file):
message = await bot.send_video_note(
chat_id,
video_note_file,
duration=self.duration,
length=self.length,
disable_notification=False,
protect_content=True,
thumbnail=thumb_file,
)
assert isinstance(message.video_note, VideoNote)
assert isinstance(message.video_note.file_id, str)
assert isinstance(message.video_note.file_unique_id, str)
assert message.video_note.file_id != ""
assert message.video_note.file_unique_id != ""
assert message.video_note.length == video_note.length
assert message.video_note.duration == video_note.duration
assert message.video_note.file_size == video_note.file_size
assert message.video_note.thumbnail.file_size == self.thumb_file_size
assert message.video_note.thumbnail.width == self.thumb_width
assert message.video_note.thumbnail.height == self.thumb_height
assert message.has_protected_content
async def test_get_and_download(self, bot, video_note, chat_id):
path = Path("telegram2.mp4")
if path.is_file():
path.unlink()
new_file = await bot.get_file(video_note.file_id)
assert new_file.file_size == self.file_size
assert new_file.file_unique_id == video_note.file_unique_id
assert new_file.file_path.startswith("https://")
await new_file.download_to_drive("telegram2.mp4")
assert path.is_file()
async def test_resend(self, bot, chat_id, video_note):
message = await bot.send_video_note(chat_id, video_note.file_id)
assert message.video_note == video_note
@pytest.mark.parametrize(
"default_bot,custom",
[
@@ -237,55 +284,20 @@ class TestVideoNote:
chat_id, video_note, reply_to_message_id=reply_to_message.message_id
)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_video_note_default_protect_content(self, chat_id, default_bot, video_note):
protected = await default_bot.send_video_note(chat_id, video_note)
tasks = asyncio.gather(
default_bot.send_video_note(chat_id, video_note),
default_bot.send_video_note(chat_id, video_note, protect_content=False),
)
protected, unprotected = await tasks
assert protected.has_protected_content
unprotected = await default_bot.send_video_note(chat_id, video_note, protect_content=False)
assert not unprotected.has_protected_content
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_video_note(chat_id, open(os.devnull, "rb"))
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file_id(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.send_video_note(chat_id, "")
async def test_error_without_required_args(self, bot, chat_id):
with pytest.raises(TypeError):
await bot.send_video_note(chat_id=chat_id)
async def test_get_file_instance_method(self, monkeypatch, video_note):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == video_note.file_id
assert check_shortcut_signature(VideoNote.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(video_note.get_file, video_note.get_bot(), "get_file")
assert await check_defaults_handling(video_note.get_file, video_note.get_bot())
monkeypatch.setattr(video_note.get_bot(), "get_file", make_assertion)
assert await video_note.get_file()
def test_equality(self, video_note):
a = VideoNote(video_note.file_id, video_note.file_unique_id, self.length, self.duration)
b = VideoNote("", video_note.file_unique_id, self.length, self.duration)
c = VideoNote(video_note.file_id, video_note.file_unique_id, 0, 0)
d = VideoNote("", "", self.length, self.duration)
e = Voice(video_note.file_id, video_note.file_unique_id, self.duration)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
+180 -189
View File
@@ -16,6 +16,7 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import os
from pathlib import Path
@@ -30,34 +31,34 @@ from tests.auxil.bot_method_checks import (
check_shortcut_call,
check_shortcut_signature,
)
from tests.conftest import data_file
from tests.auxil.files import data_file
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="function")
def voice_file():
f = data_file("telegram.ogg").open("rb")
yield f
f.close()
with data_file("telegram.ogg").open("rb") as f:
yield f
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
async def voice(bot, chat_id):
with data_file("telegram.ogg").open("rb") as f:
return (await bot.send_voice(chat_id, voice=f, read_timeout=50)).voice
class TestVoice:
class TestVoiceBase:
duration = 3
mime_type = "audio/ogg"
file_size = 9199
caption = "Test *voice*"
voice_file_url = "https://python-telegram-bot.org/static/testfiles/telegram.ogg"
voice_file_id = "5a3128a4d2a04750b5b58397f3b5e812"
voice_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e"
def test_slot_behaviour(self, voice, mro_slots):
class TestVoiceWithoutRequest(TestVoiceBase):
def test_slot_behaviour(self, voice):
for attr in voice.__slots__:
assert getattr(voice, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(voice)) == len(set(mro_slots(voice))), "duplicate slot"
@@ -75,30 +76,57 @@ class TestVoice:
assert voice.mime_type == self.mime_type
assert voice.file_size == self.file_size
@pytest.mark.flaky(3, 1)
async def test_send_all_args(self, bot, chat_id, voice_file, voice):
message = await bot.send_voice(
chat_id,
voice_file,
duration=self.duration,
caption=self.caption,
disable_notification=False,
protect_content=True,
parse_mode="Markdown",
)
def test_de_json(self, bot):
json_dict = {
"file_id": self.voice_file_id,
"file_unique_id": self.voice_file_unique_id,
"duration": self.duration,
"mime_type": self.mime_type,
"file_size": self.file_size,
}
json_voice = Voice.de_json(json_dict, bot)
assert json_voice.api_kwargs == {}
assert isinstance(message.voice, Voice)
assert isinstance(message.voice.file_id, str)
assert isinstance(message.voice.file_unique_id, str)
assert message.voice.file_id != ""
assert message.voice.file_unique_id != ""
assert message.voice.duration == voice.duration
assert message.voice.mime_type == voice.mime_type
assert message.voice.file_size == voice.file_size
assert message.caption == self.caption.replace("*", "")
assert message.has_protected_content
assert json_voice.file_id == self.voice_file_id
assert json_voice.file_unique_id == self.voice_file_unique_id
assert json_voice.duration == self.duration
assert json_voice.mime_type == self.mime_type
assert json_voice.file_size == self.file_size
def test_to_dict(self, voice):
voice_dict = voice.to_dict()
assert isinstance(voice_dict, dict)
assert voice_dict["file_id"] == voice.file_id
assert voice_dict["file_unique_id"] == voice.file_unique_id
assert voice_dict["duration"] == voice.duration
assert voice_dict["mime_type"] == voice.mime_type
assert voice_dict["file_size"] == voice.file_size
def test_equality(self, voice):
a = Voice(voice.file_id, voice.file_unique_id, self.duration)
b = Voice("", voice.file_unique_id, self.duration)
c = Voice(voice.file_id, voice.file_unique_id, 0)
d = Voice("", "", self.duration)
e = Audio(voice.file_id, voice.file_unique_id, self.duration)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
async def test_error_without_required_args(self, bot, chat_id):
with pytest.raises(TypeError):
await bot.sendVoice(chat_id)
@pytest.mark.flaky(3, 1)
async def test_send_voice_custom_filename(self, bot, chat_id, voice_file, monkeypatch):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return list(request_data.multipart_data.values())[0][0] == "custom_filename"
@@ -107,104 +135,12 @@ class TestVoice:
assert await bot.send_voice(chat_id, voice_file, filename="custom_filename")
@pytest.mark.flaky(3, 1)
async def test_get_and_download(self, bot, voice):
path = Path("telegram.ogg")
if path.is_file():
path.unlink()
new_file = await bot.get_file(voice.file_id)
assert new_file.file_size == voice.file_size
assert new_file.file_id == voice.file_id
assert new_file.file_unique_id == voice.file_unique_id
assert new_file.file_path.startswith("https://")
await new_file.download_to_drive("telegram.ogg")
assert path.is_file()
@pytest.mark.flaky(3, 1)
async def test_send_ogg_url_file(self, bot, chat_id, voice):
message = await bot.sendVoice(chat_id, self.voice_file_url, duration=self.duration)
assert isinstance(message.voice, Voice)
assert isinstance(message.voice.file_id, str)
assert isinstance(message.voice.file_unique_id, str)
assert message.voice.file_id != ""
assert message.voice.file_unique_id != ""
assert message.voice.duration == voice.duration
assert message.voice.mime_type == voice.mime_type
assert message.voice.file_size == voice.file_size
@pytest.mark.flaky(3, 1)
async def test_resend(self, bot, chat_id, voice):
message = await bot.sendVoice(chat_id, voice.file_id)
assert message.voice == voice
async def test_send_with_voice(self, monkeypatch, bot, chat_id, voice):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.json_parameters["voice"] == voice.file_id
monkeypatch.setattr(bot.request, "post", make_assertion)
message = await bot.send_voice(chat_id, voice=voice)
assert message
@pytest.mark.flaky(3, 1)
async def test_send_voice_caption_entities(self, bot, chat_id, voice_file):
test_string = "Italic Bold Code"
entities = [
MessageEntity(MessageEntity.ITALIC, 0, 6),
MessageEntity(MessageEntity.ITALIC, 7, 4),
MessageEntity(MessageEntity.ITALIC, 12, 4),
]
message = await bot.send_voice(
chat_id, voice_file, caption=test_string, caption_entities=entities
)
assert message.caption == test_string
assert message.caption_entities == tuple(entities)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_voice_default_parse_mode_1(self, default_bot, chat_id, voice):
test_string = "Italic Bold Code"
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_voice(chat_id, voice, caption=test_markdown_string)
assert message.caption_markdown == test_markdown_string
assert message.caption == test_string
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_voice_default_parse_mode_2(self, default_bot, chat_id, voice):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_voice(
chat_id, voice, caption=test_markdown_string, parse_mode=None
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_voice_default_parse_mode_3(self, default_bot, chat_id, voice):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_voice(
chat_id, voice, caption=test_markdown_string, parse_mode="HTML"
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.flaky(3, 1)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_voice_default_protect_content(self, chat_id, default_bot, voice):
protected = await default_bot.send_voice(chat_id, voice)
assert protected.has_protected_content
unprotected = await default_bot.send_voice(chat_id, voice, protect_content=False)
assert not unprotected.has_protected_content
assert await bot.send_voice(chat_id, voice=voice)
@pytest.mark.parametrize("local_mode", [True, False])
async def test_send_voice_local_files(self, monkeypatch, bot, chat_id, local_mode):
@@ -228,7 +164,126 @@ class TestVoice:
finally:
bot._local_mode = False
@pytest.mark.flaky(3, 1)
async def test_get_file_instance_method(self, monkeypatch, voice):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == voice.file_id
assert check_shortcut_signature(Voice.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(voice.get_file, voice.get_bot(), "get_file")
assert await check_defaults_handling(voice.get_file, voice.get_bot())
monkeypatch.setattr(voice.get_bot(), "get_file", make_assertion)
assert await voice.get_file()
class TestVoiceWithRequest(TestVoiceBase):
async def test_send_all_args(self, bot, chat_id, voice_file, voice):
message = await bot.send_voice(
chat_id,
voice_file,
duration=self.duration,
caption=self.caption,
disable_notification=False,
protect_content=True,
parse_mode="Markdown",
)
assert isinstance(message.voice, Voice)
assert isinstance(message.voice.file_id, str)
assert isinstance(message.voice.file_unique_id, str)
assert message.voice.file_id != ""
assert message.voice.file_unique_id != ""
assert message.voice.duration == voice.duration
assert message.voice.mime_type == voice.mime_type
assert message.voice.file_size == voice.file_size
assert message.caption == self.caption.replace("*", "")
assert message.has_protected_content
async def test_get_and_download(self, bot, voice, chat_id):
path = Path("telegram.ogg")
if path.is_file():
path.unlink()
new_file = await bot.get_file(voice.file_id)
assert new_file.file_size == voice.file_size
assert new_file.file_unique_id == voice.file_unique_id
assert new_file.file_path.startswith("https://")
await new_file.download_to_drive("telegram.ogg")
assert path.is_file()
async def test_send_ogg_url_file(self, bot, chat_id, voice):
message = await bot.sendVoice(chat_id, self.voice_file_url, duration=self.duration)
assert isinstance(message.voice, Voice)
assert isinstance(message.voice.file_id, str)
assert isinstance(message.voice.file_unique_id, str)
assert message.voice.file_id != ""
assert message.voice.file_unique_id != ""
assert message.voice.duration == voice.duration
assert message.voice.mime_type == voice.mime_type
assert message.voice.file_size == voice.file_size
async def test_resend(self, bot, chat_id, voice):
message = await bot.sendVoice(chat_id, voice.file_id)
assert message.voice == voice
async def test_send_voice_caption_entities(self, bot, chat_id, voice_file):
test_string = "Italic Bold Code"
entities = [
MessageEntity(MessageEntity.ITALIC, 0, 6),
MessageEntity(MessageEntity.ITALIC, 7, 4),
MessageEntity(MessageEntity.ITALIC, 12, 4),
]
message = await bot.send_voice(
chat_id, voice_file, caption=test_string, caption_entities=entities
)
assert message.caption == test_string
assert message.caption_entities == tuple(entities)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_voice_default_parse_mode_1(self, default_bot, chat_id, voice):
test_string = "Italic Bold Code"
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_voice(chat_id, voice, caption=test_markdown_string)
assert message.caption_markdown == test_markdown_string
assert message.caption == test_string
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_voice_default_parse_mode_2(self, default_bot, chat_id, voice):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_voice(
chat_id, voice, caption=test_markdown_string, parse_mode=None
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True)
async def test_send_voice_default_parse_mode_3(self, default_bot, chat_id, voice):
test_markdown_string = "_Italic_ *Bold* `Code`"
message = await default_bot.send_voice(
chat_id, voice, caption=test_markdown_string, parse_mode="HTML"
)
assert message.caption == test_markdown_string
assert message.caption_markdown == escape_markdown(test_markdown_string)
@pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True)
async def test_send_voice_default_protect_content(self, chat_id, default_bot, voice):
tasks = asyncio.gather(
default_bot.send_voice(chat_id, voice),
default_bot.send_voice(chat_id, voice, protect_content=False),
)
protected, unprotected = await tasks
assert protected.has_protected_content
assert not unprotected.has_protected_content
@pytest.mark.parametrize(
"default_bot,custom",
[
@@ -262,74 +317,10 @@ class TestVoice:
chat_id, voice, reply_to_message_id=reply_to_message.message_id
)
def test_de_json(self, bot):
json_dict = {
"file_id": self.voice_file_id,
"file_unique_id": self.voice_file_unique_id,
"duration": self.duration,
"mime_type": self.mime_type,
"file_size": self.file_size,
}
json_voice = Voice.de_json(json_dict, bot)
assert json_voice.api_kwargs == {}
assert json_voice.file_id == self.voice_file_id
assert json_voice.file_unique_id == self.voice_file_unique_id
assert json_voice.duration == self.duration
assert json_voice.mime_type == self.mime_type
assert json_voice.file_size == self.file_size
def test_to_dict(self, voice):
voice_dict = voice.to_dict()
assert isinstance(voice_dict, dict)
assert voice_dict["file_id"] == voice.file_id
assert voice_dict["file_unique_id"] == voice.file_unique_id
assert voice_dict["duration"] == voice.duration
assert voice_dict["mime_type"] == voice.mime_type
assert voice_dict["file_size"] == voice.file_size
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.sendVoice(chat_id, open(os.devnull, "rb"))
@pytest.mark.flaky(3, 1)
async def test_error_send_empty_file_id(self, bot, chat_id):
with pytest.raises(TelegramError):
await bot.sendVoice(chat_id, "")
async def test_error_without_required_args(self, bot, chat_id):
with pytest.raises(TypeError):
await bot.sendVoice(chat_id)
async def test_get_file_instance_method(self, monkeypatch, voice):
async def make_assertion(*_, **kwargs):
return kwargs["file_id"] == voice.file_id
assert check_shortcut_signature(Voice.get_file, Bot.get_file, ["file_id"], [])
assert await check_shortcut_call(voice.get_file, voice.get_bot(), "get_file")
assert await check_defaults_handling(voice.get_file, voice.get_bot())
monkeypatch.setattr(voice.get_bot(), "get_file", make_assertion)
assert await voice.get_file()
def test_equality(self, voice):
a = Voice(voice.file_id, voice.file_unique_id, self.duration)
b = Voice("", voice.file_unique_id, self.duration)
c = Voice(voice.file_id, voice.file_unique_id, 0)
d = Voice("", "", self.duration)
e = Audio(voice.file_id, voice.file_unique_id, self.duration)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
@@ -20,23 +20,24 @@
import pytest
from telegram import Animation, Game, MessageEntity, PhotoSize
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="function")
@pytest.fixture(scope="module")
def game():
game = Game(
TestGame.title,
TestGame.description,
TestGame.photo,
text=TestGame.text,
text_entities=TestGame.text_entities,
animation=TestGame.animation,
TestGameBase.title,
TestGameBase.description,
TestGameBase.photo,
text=TestGameBase.text,
text_entities=TestGameBase.text_entities,
animation=TestGameBase.animation,
)
game._unfreeze()
return game
class TestGame:
class TestGameBase:
title = "Python-telegram-bot Test Game"
description = "description"
photo = [PhotoSize("Blah", "ElseBlah", 640, 360, file_size=0)]
@@ -47,7 +48,9 @@ class TestGame:
text_entities = [MessageEntity(13, 17, MessageEntity.URL)]
animation = Animation("blah", "unique_id", 320, 180, 1)
def test_slot_behaviour(self, game, mro_slots):
class TestGameWithoutRequest(TestGameBase):
def test_slot_behaviour(self, game):
for attr in game.__slots__:
assert getattr(game, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(game)) == len(set(mro_slots(game))), "duplicate slot"
@@ -95,20 +98,6 @@ class TestGame:
assert game_dict["text_entities"] == [game.text_entities[0].to_dict()]
assert game_dict["animation"] == game.animation.to_dict()
def test_parse_entity(self, game):
entity = MessageEntity(type=MessageEntity.URL, offset=13, length=17)
game.text_entities = [entity]
assert game.parse_text_entity(entity) == "http://google.com"
def test_parse_entities(self, game):
entity = MessageEntity(type=MessageEntity.URL, offset=13, length=17)
entity_2 = MessageEntity(type=MessageEntity.BOLD, offset=13, length=1)
game.text_entities = [entity_2, entity]
assert game.parse_text_entities(MessageEntity.URL) == {entity: "http://google.com"}
assert game.parse_text_entities() == {entity: "http://google.com", entity_2: "h"}
def test_equality(self):
a = Game("title", "description", [PhotoSize("Blah", "unique_id", 640, 360, file_size=0)])
b = Game(
@@ -133,3 +122,17 @@ class TestGame:
assert a != d
assert hash(a) != hash(d)
def test_parse_entity(self, game):
entity = MessageEntity(type=MessageEntity.URL, offset=13, length=17)
game.text_entities = [entity]
assert game.parse_text_entity(entity) == "http://google.com"
def test_parse_entities(self, game):
entity = MessageEntity(type=MessageEntity.URL, offset=13, length=17)
entity_2 = MessageEntity(type=MessageEntity.BOLD, offset=13, length=1)
game.text_entities = [entity_2, entity]
assert game.parse_text_entities(MessageEntity.URL) == {entity: "http://google.com"}
assert game.parse_text_entities() == {entity: "http://google.com", entity_2: "h"}
@@ -20,27 +20,34 @@
import pytest
from telegram import GameHighScore, User
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def game_highscore():
return GameHighScore(
TestGameHighScore.position, TestGameHighScore.user, TestGameHighScore.score
TestGameHighScoreBase.position, TestGameHighScoreBase.user, TestGameHighScoreBase.score
)
class TestGameHighScore:
class TestGameHighScoreBase:
position = 12
user = User(2, "test user", False)
score = 42
def test_slot_behaviour(self, game_highscore, mro_slots):
class TestGameHighScoreWithoutRequest(TestGameHighScoreBase):
def test_slot_behaviour(self, game_highscore):
for attr in game_highscore.__slots__:
assert getattr(game_highscore, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(game_highscore)) == len(set(mro_slots(game_highscore))), "same slot"
def test_de_json(self, bot):
json_dict = {"position": self.position, "user": self.user.to_dict(), "score": self.score}
json_dict = {
"position": self.position,
"user": self.user.to_dict(),
"score": self.score,
}
highscore = GameHighScore.de_json(json_dict, bot)
assert highscore.api_kwargs == {}
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
@@ -20,24 +20,25 @@
import pytest
from telegram import CallbackGame, InlineKeyboardButton, LoginUrl, WebAppInfo
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def inline_keyboard_button():
return InlineKeyboardButton(
TestInlineKeyboardButton.text,
url=TestInlineKeyboardButton.url,
callback_data=TestInlineKeyboardButton.callback_data,
switch_inline_query=TestInlineKeyboardButton.switch_inline_query,
switch_inline_query_current_chat=TestInlineKeyboardButton.switch_inline_query_current_chat,
callback_game=TestInlineKeyboardButton.callback_game,
pay=TestInlineKeyboardButton.pay,
login_url=TestInlineKeyboardButton.login_url,
web_app=TestInlineKeyboardButton.web_app,
TestInlineKeyboardButtonBase.text,
url=TestInlineKeyboardButtonBase.url,
callback_data=TestInlineKeyboardButtonBase.callback_data,
switch_inline_query=TestInlineKeyboardButtonBase.switch_inline_query,
switch_inline_query_current_chat=TestInlineKeyboardButtonBase.switch_inline_query_current_chat, # noqa: E501
callback_game=TestInlineKeyboardButtonBase.callback_game,
pay=TestInlineKeyboardButtonBase.pay,
login_url=TestInlineKeyboardButtonBase.login_url,
web_app=TestInlineKeyboardButtonBase.web_app,
)
class TestInlineKeyboardButton:
class TestInlineKeyboardButtonBase:
text = "text"
url = "url"
callback_data = "callback data"
@@ -48,7 +49,9 @@ class TestInlineKeyboardButton:
login_url = LoginUrl("http://google.com")
web_app = WebAppInfo(url="https://example.com")
def test_slot_behaviour(self, inline_keyboard_button, mro_slots):
class TestInlineKeyboardButtonWithoutRequest(TestInlineKeyboardButtonBase):
def test_slot_behaviour(self, inline_keyboard_button):
inst = inline_keyboard_button
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -26,14 +26,15 @@ from telegram import (
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
)
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def inline_keyboard_markup():
return InlineKeyboardMarkup(TestInlineKeyboardMarkup.inline_keyboard)
return InlineKeyboardMarkup(TestInlineKeyboardMarkupBase.inline_keyboard)
class TestInlineKeyboardMarkup:
class TestInlineKeyboardMarkupBase:
inline_keyboard = [
[
InlineKeyboardButton(text="button1", callback_data="data1"),
@@ -41,104 +42,14 @@ class TestInlineKeyboardMarkup:
]
]
def test_slot_behaviour(self, inline_keyboard_markup, mro_slots):
class TestInlineKeyboardMarkupWithoutRequest(TestInlineKeyboardMarkupBase):
def test_slot_behaviour(self, inline_keyboard_markup):
inst = inline_keyboard_markup
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
@pytest.mark.flaky(3, 1)
async def test_send_message_with_inline_keyboard_markup(
self, bot, chat_id, inline_keyboard_markup
):
message = await bot.send_message(
chat_id, "Testing InlineKeyboardMarkup", reply_markup=inline_keyboard_markup
)
assert message.text == "Testing InlineKeyboardMarkup"
def test_from_button(self):
inline_keyboard_markup = InlineKeyboardMarkup.from_button(
InlineKeyboardButton(text="button1", callback_data="data1")
).inline_keyboard
assert len(inline_keyboard_markup) == 1
assert len(inline_keyboard_markup[0]) == 1
def test_from_row(self):
inline_keyboard_markup = InlineKeyboardMarkup.from_row(
[
InlineKeyboardButton(text="button1", callback_data="data1"),
InlineKeyboardButton(text="button1", callback_data="data1"),
]
).inline_keyboard
assert len(inline_keyboard_markup) == 1
assert len(inline_keyboard_markup[0]) == 2
def test_from_column(self):
inline_keyboard_markup = InlineKeyboardMarkup.from_column(
[
InlineKeyboardButton(text="button1", callback_data="data1"),
InlineKeyboardButton(text="button1", callback_data="data1"),
]
).inline_keyboard
assert len(inline_keyboard_markup) == 2
assert len(inline_keyboard_markup[0]) == 1
assert len(inline_keyboard_markup[1]) == 1
def test_expected_values(self, inline_keyboard_markup):
assert inline_keyboard_markup.inline_keyboard == tuple(
tuple(row) for row in self.inline_keyboard
)
def test_wrong_keyboard_inputs(self):
with pytest.raises(ValueError):
InlineKeyboardMarkup(
[[InlineKeyboardButton("b1", "1")], InlineKeyboardButton("b2", "2")]
)
with pytest.raises(ValueError):
InlineKeyboardMarkup("strings_are_not_allowed")
with pytest.raises(ValueError):
InlineKeyboardMarkup(["strings_are_not_allowed_in_the_rows_either"])
with pytest.raises(ValueError):
InlineKeyboardMarkup(InlineKeyboardButton("b1", "1"))
with pytest.raises(ValueError):
InlineKeyboardMarkup([[[InlineKeyboardButton("only_2d_array_is_allowed", "1")]]])
async def test_expected_values_empty_switch(self, inline_keyboard_markup, bot, monkeypatch):
async def make_assertion(
url,
data,
reply_to_message_id=None,
disable_notification=None,
reply_markup=None,
timeout=None,
**kwargs,
):
if reply_markup is not None:
markups = (
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
ForceReply,
ReplyKeyboardRemove,
)
if isinstance(reply_markup, markups):
data["reply_markup"] = reply_markup.to_dict()
else:
data["reply_markup"] = reply_markup
assert bool("'switch_inline_query': ''" in str(data["reply_markup"]))
assert bool("'switch_inline_query_current_chat': ''" in str(data["reply_markup"]))
inline_keyboard_markup.inline_keyboard[0][0]._unfreeze()
inline_keyboard_markup.inline_keyboard[0][0].callback_data = None
inline_keyboard_markup.inline_keyboard[0][0].switch_inline_query = ""
inline_keyboard_markup.inline_keyboard[0][1]._unfreeze()
inline_keyboard_markup.inline_keyboard[0][1].callback_data = None
inline_keyboard_markup.inline_keyboard[0][1].switch_inline_query_current_chat = ""
monkeypatch.setattr(bot, "_send_message", make_assertion)
await bot.send_message(123, "test", reply_markup=inline_keyboard_markup)
def test_to_dict(self, inline_keyboard_markup):
inline_keyboard_markup_dict = inline_keyboard_markup.to_dict()
@@ -233,3 +144,96 @@ class TestInlineKeyboardMarkup:
assert a != g
assert hash(a) != hash(g)
def test_from_button(self):
inline_keyboard_markup = InlineKeyboardMarkup.from_button(
InlineKeyboardButton(text="button1", callback_data="data1")
).inline_keyboard
assert len(inline_keyboard_markup) == 1
assert len(inline_keyboard_markup[0]) == 1
def test_from_row(self):
inline_keyboard_markup = InlineKeyboardMarkup.from_row(
[
InlineKeyboardButton(text="button1", callback_data="data1"),
InlineKeyboardButton(text="button1", callback_data="data1"),
]
).inline_keyboard
assert len(inline_keyboard_markup) == 1
assert len(inline_keyboard_markup[0]) == 2
def test_from_column(self):
inline_keyboard_markup = InlineKeyboardMarkup.from_column(
[
InlineKeyboardButton(text="button1", callback_data="data1"),
InlineKeyboardButton(text="button1", callback_data="data1"),
]
).inline_keyboard
assert len(inline_keyboard_markup) == 2
assert len(inline_keyboard_markup[0]) == 1
assert len(inline_keyboard_markup[1]) == 1
def test_expected_values(self, inline_keyboard_markup):
assert inline_keyboard_markup.inline_keyboard == tuple(
tuple(row) for row in self.inline_keyboard
)
def test_wrong_keyboard_inputs(self):
with pytest.raises(ValueError):
InlineKeyboardMarkup(
[[InlineKeyboardButton("b1", "1")], InlineKeyboardButton("b2", "2")]
)
with pytest.raises(ValueError):
InlineKeyboardMarkup("strings_are_not_allowed")
with pytest.raises(ValueError):
InlineKeyboardMarkup(["strings_are_not_allowed_in_the_rows_either"])
with pytest.raises(ValueError):
InlineKeyboardMarkup(InlineKeyboardButton("b1", "1"))
with pytest.raises(ValueError):
InlineKeyboardMarkup([[[InlineKeyboardButton("only_2d_array_is_allowed", "1")]]])
async def test_expected_values_empty_switch(self, inline_keyboard_markup, bot, monkeypatch):
async def make_assertion(
url,
data,
reply_to_message_id=None,
disable_notification=None,
reply_markup=None,
timeout=None,
**kwargs,
):
if reply_markup is not None:
markups = (
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
ForceReply,
ReplyKeyboardRemove,
)
if isinstance(reply_markup, markups):
data["reply_markup"] = reply_markup.to_dict()
else:
data["reply_markup"] = reply_markup
assert bool("'switch_inline_query': ''" in str(data["reply_markup"]))
assert bool("'switch_inline_query_current_chat': ''" in str(data["reply_markup"]))
inline_keyboard_markup.inline_keyboard[0][0]._unfreeze()
inline_keyboard_markup.inline_keyboard[0][0].callback_data = None
inline_keyboard_markup.inline_keyboard[0][0].switch_inline_query = ""
inline_keyboard_markup.inline_keyboard[0][1]._unfreeze()
inline_keyboard_markup.inline_keyboard[0][1].callback_data = None
inline_keyboard_markup.inline_keyboard[0][1].switch_inline_query_current_chat = ""
monkeypatch.setattr(bot, "_send_message", make_assertion)
await bot.send_message(123, "test", reply_markup=inline_keyboard_markup)
class TestInlineKeyborardMarkupWithRequest(TestInlineKeyboardMarkupBase):
async def test_send_message_with_inline_keyboard_markup(
self, bot, chat_id, inline_keyboard_markup
):
message = await bot.send_message(
chat_id, "Testing InlineKeyboardMarkup", reply_markup=inline_keyboard_markup
)
assert message.text == "Testing InlineKeyboardMarkup"
@@ -25,29 +25,32 @@ from tests.auxil.bot_method_checks import (
check_shortcut_call,
check_shortcut_signature,
)
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def inline_query(bot):
ilq = InlineQuery(
TestInlineQuery.id_,
TestInlineQuery.from_user,
TestInlineQuery.query,
TestInlineQuery.offset,
location=TestInlineQuery.location,
TestInlineQueryBase.id_,
TestInlineQueryBase.from_user,
TestInlineQueryBase.query,
TestInlineQueryBase.offset,
location=TestInlineQueryBase.location,
)
ilq.set_bot(bot)
return ilq
class TestInlineQuery:
class TestInlineQueryBase:
id_ = 1234
from_user = User(1, "First name", False)
query = "query text"
offset = "offset"
location = Location(8.8, 53.1)
def test_slot_behaviour(self, inline_query, mro_slots):
class TestInlineQueryWithoutRequest(TestInlineQueryBase):
def test_slot_behaviour(self, inline_query):
for attr in inline_query.__slots__:
assert getattr(inline_query, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(inline_query)) == len(set(mro_slots(inline_query))), "duplicate slot"
@@ -79,34 +82,6 @@ class TestInlineQuery:
assert inline_query_dict["query"] == inline_query.query
assert inline_query_dict["offset"] == inline_query.offset
async def test_answer(self, monkeypatch, inline_query):
async def make_assertion(*_, **kwargs):
return kwargs["inline_query_id"] == inline_query.id
assert check_shortcut_signature(
InlineQuery.answer, Bot.answer_inline_query, ["inline_query_id"], ["auto_pagination"]
)
assert await check_shortcut_call(
inline_query.answer, inline_query.get_bot(), "answer_inline_query"
)
assert await check_defaults_handling(inline_query.answer, inline_query.get_bot())
monkeypatch.setattr(inline_query.get_bot(), "answer_inline_query", make_assertion)
assert await inline_query.answer(results=[])
async def test_answer_error(self, inline_query):
with pytest.raises(ValueError, match="mutually exclusive"):
await inline_query.answer(results=[], auto_pagination=True, current_offset="foobar")
async def test_answer_auto_pagination(self, monkeypatch, inline_query):
async def make_assertion(*_, **kwargs):
inline_query_id_matches = kwargs["inline_query_id"] == inline_query.id
offset_matches = kwargs.get("current_offset") == inline_query.offset
return offset_matches and inline_query_id_matches
monkeypatch.setattr(inline_query.get_bot(), "answer_inline_query", make_assertion)
assert await inline_query.answer(results=[], auto_pagination=True)
def test_equality(self):
a = InlineQuery(self.id_, User(1, "", False), "", "")
b = InlineQuery(self.id_, User(1, "", False), "", "")
@@ -126,3 +101,31 @@ class TestInlineQuery:
assert a != e
assert hash(a) != hash(e)
async def test_answer_error(self, inline_query):
with pytest.raises(ValueError, match="mutually exclusive"):
await inline_query.answer(results=[], auto_pagination=True, current_offset="foobar")
async def test_answer(self, monkeypatch, inline_query):
async def make_assertion(*_, **kwargs):
return kwargs["inline_query_id"] == inline_query.id
assert check_shortcut_signature(
InlineQuery.answer, Bot.answer_inline_query, ["inline_query_id"], ["auto_pagination"]
)
assert await check_shortcut_call(
inline_query.answer, inline_query.get_bot(), "answer_inline_query"
)
assert await check_defaults_handling(inline_query.answer, inline_query.get_bot())
monkeypatch.setattr(inline_query.get_bot(), "answer_inline_query", make_assertion)
assert await inline_query.answer(results=[])
async def test_answer_auto_pagination(self, monkeypatch, inline_query):
async def make_assertion(*_, **kwargs):
inline_query_id_matches = kwargs["inline_query_id"] == inline_query.id
offset_matches = kwargs.get("current_offset") == inline_query.offset
return offset_matches and inline_query_id_matches
monkeypatch.setattr(inline_query.get_bot(), "answer_inline_query", make_assertion)
assert await inline_query.answer(results=[], auto_pagination=True)
@@ -34,6 +34,7 @@ from telegram import (
User,
)
from telegram.ext import CallbackContext, InlineQueryHandler, JobQueue
from tests.auxil.slots import mro_slots
message = Message(1, None, Chat(1, ""), from_user=User(1, "", False), text="Text")
@@ -87,7 +88,7 @@ def inline_query(bot):
class TestInlineQueryHandler:
test_flag = False
def test_slot_behaviour(self, mro_slots):
def test_slot_behaviour(self):
handler = InlineQueryHandler(self.callback)
for attr in handler.__slots__:
assert getattr(handler, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -0,0 +1,252 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import (
InlineKeyboardButton,
InlineKeyboardMarkup,
InlineQueryResultArticle,
InlineQueryResultAudio,
InputTextMessageContent,
)
from tests.auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def inline_query_result_article():
return InlineQueryResultArticle(
TestInlineQueryResultArticleBase.id_,
TestInlineQueryResultArticleBase.title,
input_message_content=TestInlineQueryResultArticleBase.input_message_content,
reply_markup=TestInlineQueryResultArticleBase.reply_markup,
url=TestInlineQueryResultArticleBase.url,
hide_url=TestInlineQueryResultArticleBase.hide_url,
description=TestInlineQueryResultArticleBase.description,
thumbnail_url=TestInlineQueryResultArticleBase.thumbnail_url,
thumbnail_height=TestInlineQueryResultArticleBase.thumbnail_height,
thumbnail_width=TestInlineQueryResultArticleBase.thumbnail_width,
)
class TestInlineQueryResultArticleBase:
id_ = "id"
type_ = "article"
title = "title"
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
url = "url"
hide_url = True
description = "description"
thumbnail_url = "thumb url"
thumbnail_height = 10
thumbnail_width = 15
class TestInlineQueryResultArticleWithoutRequest(TestInlineQueryResultArticleBase):
def test_slot_behaviour(self, inline_query_result_article, recwarn):
inst = inline_query_result_article
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
def test_expected_values(self, inline_query_result_article):
assert inline_query_result_article.type == self.type_
assert inline_query_result_article.id == self.id_
assert inline_query_result_article.title == self.title
assert (
inline_query_result_article.input_message_content.to_dict()
== self.input_message_content.to_dict()
)
assert inline_query_result_article.reply_markup.to_dict() == self.reply_markup.to_dict()
assert inline_query_result_article.url == self.url
assert inline_query_result_article.hide_url == self.hide_url
assert inline_query_result_article.description == self.description
assert inline_query_result_article.thumbnail_url == self.thumbnail_url
assert inline_query_result_article.thumbnail_height == self.thumbnail_height
assert inline_query_result_article.thumbnail_width == self.thumbnail_width
def test_thumb_url_property_deprecation_warning(self, recwarn):
inline_query_result_article = InlineQueryResultArticle(
TestInlineQueryResultArticleBase.id_,
TestInlineQueryResultArticleBase.title,
input_message_content=TestInlineQueryResultArticleBase.input_message_content,
reply_markup=TestInlineQueryResultArticleBase.reply_markup,
url=TestInlineQueryResultArticleBase.url,
hide_url=TestInlineQueryResultArticleBase.hide_url,
description=TestInlineQueryResultArticleBase.description,
thumb_url=TestInlineQueryResultArticleBase.thumbnail_url, # deprecated arg
thumbnail_height=TestInlineQueryResultArticleBase.thumbnail_height,
thumbnail_width=TestInlineQueryResultArticleBase.thumbnail_width,
)
assert inline_query_result_article.thumb_url == inline_query_result_article.thumbnail_url
check_thumb_deprecation_warnings_for_args_and_attrs(
recwarn, __file__, deprecated_name="thumb_url", new_name="thumbnail_url"
)
def test_thumb_height_property_deprecation_warning(self, recwarn):
inline_query_result_article = InlineQueryResultArticle(
TestInlineQueryResultArticleBase.id_,
TestInlineQueryResultArticleBase.title,
input_message_content=TestInlineQueryResultArticleBase.input_message_content,
reply_markup=TestInlineQueryResultArticleBase.reply_markup,
url=TestInlineQueryResultArticleBase.url,
hide_url=TestInlineQueryResultArticleBase.hide_url,
description=TestInlineQueryResultArticleBase.description,
thumbnail_url=TestInlineQueryResultArticleBase.thumbnail_url,
thumb_height=TestInlineQueryResultArticleBase.thumbnail_height, # deprecated arg
thumbnail_width=TestInlineQueryResultArticleBase.thumbnail_width,
)
assert (
inline_query_result_article.thumb_height
== inline_query_result_article.thumbnail_height
)
check_thumb_deprecation_warnings_for_args_and_attrs(
recwarn, __file__, deprecated_name="thumb_height", new_name="thumbnail_height"
)
def test_thumb_width_property_deprecation_warning(self, recwarn):
inline_query_result_article = InlineQueryResultArticle(
TestInlineQueryResultArticleBase.id_,
TestInlineQueryResultArticleBase.title,
input_message_content=TestInlineQueryResultArticleBase.input_message_content,
reply_markup=TestInlineQueryResultArticleBase.reply_markup,
url=TestInlineQueryResultArticleBase.url,
hide_url=TestInlineQueryResultArticleBase.hide_url,
description=TestInlineQueryResultArticleBase.description,
thumbnail_url=TestInlineQueryResultArticleBase.thumbnail_url,
thumbnail_height=TestInlineQueryResultArticleBase.thumbnail_height,
thumb_width=TestInlineQueryResultArticleBase.thumbnail_width, # deprecated arg
)
assert (
inline_query_result_article.thumb_width == inline_query_result_article.thumbnail_width
)
check_thumb_deprecation_warnings_for_args_and_attrs(
recwarn, __file__, deprecated_name="thumb_width", new_name="thumbnail_width"
)
def test_throws_value_error_with_different_deprecated_and_new_arg_thumb_url(self):
with pytest.raises(
ValueError,
match="different entities as 'thumb_url' and 'thumbnail_url'",
):
InlineQueryResultArticle(
TestInlineQueryResultArticleBase.id_,
TestInlineQueryResultArticleBase.title,
input_message_content=TestInlineQueryResultArticleBase.input_message_content,
reply_markup=TestInlineQueryResultArticleBase.reply_markup,
url=TestInlineQueryResultArticleBase.url,
hide_url=TestInlineQueryResultArticleBase.hide_url,
description=TestInlineQueryResultArticleBase.description,
thumbnail_url=TestInlineQueryResultArticleBase.thumbnail_url,
thumb_url="some other url",
thumbnail_height=TestInlineQueryResultArticleBase.thumbnail_height,
thumbnail_width=TestInlineQueryResultArticleBase.thumbnail_width,
)
def test_throws_value_error_with_different_deprecated_and_new_arg_thumb_height(self):
with pytest.raises(
ValueError,
match="different entities as 'thumb_height' and 'thumbnail_height'",
):
InlineQueryResultArticle(
TestInlineQueryResultArticleBase.id_,
TestInlineQueryResultArticleBase.title,
input_message_content=TestInlineQueryResultArticleBase.input_message_content,
reply_markup=TestInlineQueryResultArticleBase.reply_markup,
url=TestInlineQueryResultArticleBase.url,
hide_url=TestInlineQueryResultArticleBase.hide_url,
description=TestInlineQueryResultArticleBase.description,
thumbnail_height=TestInlineQueryResultArticleBase.thumbnail_height,
thumb_height=TestInlineQueryResultArticleBase.thumbnail_height + 1,
thumbnail_width=TestInlineQueryResultArticleBase.thumbnail_width,
)
def test_throws_value_error_with_different_deprecated_and_new_arg_thumb_width(self):
with pytest.raises(
ValueError,
match="different entities as 'thumb_width' and 'thumbnail_width'",
):
InlineQueryResultArticle(
TestInlineQueryResultArticleBase.id_,
TestInlineQueryResultArticleBase.title,
input_message_content=TestInlineQueryResultArticleBase.input_message_content,
reply_markup=TestInlineQueryResultArticleBase.reply_markup,
url=TestInlineQueryResultArticleBase.url,
hide_url=TestInlineQueryResultArticleBase.hide_url,
description=TestInlineQueryResultArticleBase.description,
thumbnail_height=TestInlineQueryResultArticleBase.thumbnail_height,
thumbnail_width=TestInlineQueryResultArticleBase.thumbnail_width,
thumb_width=TestInlineQueryResultArticleBase.thumbnail_width + 1,
)
def test_to_dict(self, inline_query_result_article):
inline_query_result_article_dict = inline_query_result_article.to_dict()
assert isinstance(inline_query_result_article_dict, dict)
assert inline_query_result_article_dict["type"] == inline_query_result_article.type
assert inline_query_result_article_dict["id"] == inline_query_result_article.id
assert inline_query_result_article_dict["title"] == inline_query_result_article.title
assert (
inline_query_result_article_dict["input_message_content"]
== inline_query_result_article.input_message_content.to_dict()
)
assert (
inline_query_result_article_dict["reply_markup"]
== inline_query_result_article.reply_markup.to_dict()
)
assert inline_query_result_article_dict["url"] == inline_query_result_article.url
assert inline_query_result_article_dict["hide_url"] == inline_query_result_article.hide_url
assert (
inline_query_result_article_dict["description"]
== inline_query_result_article.description
)
assert (
inline_query_result_article_dict["thumbnail_url"]
== inline_query_result_article.thumbnail_url
)
assert (
inline_query_result_article_dict["thumbnail_height"]
== inline_query_result_article.thumbnail_height
)
assert (
inline_query_result_article_dict["thumbnail_width"]
== inline_query_result_article.thumbnail_width
)
def test_equality(self):
a = InlineQueryResultArticle(self.id_, self.title, self.input_message_content)
b = InlineQueryResultArticle(self.id_, self.title, self.input_message_content)
c = InlineQueryResultArticle(self.id_, "", self.input_message_content)
d = InlineQueryResultArticle("", self.title, self.input_message_content)
e = InlineQueryResultAudio(self.id_, "", "")
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
@@ -27,25 +27,26 @@ from telegram import (
InputTextMessageContent,
MessageEntity,
)
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def inline_query_result_audio():
return InlineQueryResultAudio(
TestInlineQueryResultAudio.id_,
TestInlineQueryResultAudio.audio_url,
TestInlineQueryResultAudio.title,
performer=TestInlineQueryResultAudio.performer,
audio_duration=TestInlineQueryResultAudio.audio_duration,
caption=TestInlineQueryResultAudio.caption,
parse_mode=TestInlineQueryResultAudio.parse_mode,
caption_entities=TestInlineQueryResultAudio.caption_entities,
input_message_content=TestInlineQueryResultAudio.input_message_content,
reply_markup=TestInlineQueryResultAudio.reply_markup,
TestInlineQueryResultAudioBase.id_,
TestInlineQueryResultAudioBase.audio_url,
TestInlineQueryResultAudioBase.title,
performer=TestInlineQueryResultAudioBase.performer,
audio_duration=TestInlineQueryResultAudioBase.audio_duration,
caption=TestInlineQueryResultAudioBase.caption,
parse_mode=TestInlineQueryResultAudioBase.parse_mode,
caption_entities=TestInlineQueryResultAudioBase.caption_entities,
input_message_content=TestInlineQueryResultAudioBase.input_message_content,
reply_markup=TestInlineQueryResultAudioBase.reply_markup,
)
class TestInlineQueryResultAudio:
class TestInlineQueryResultAudioBase:
id_ = "id"
type_ = "audio"
audio_url = "audio url"
@@ -58,7 +59,9 @@ class TestInlineQueryResultAudio:
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
def test_slot_behaviour(self, inline_query_result_audio, mro_slots):
class TestInlineQueryResultAudioWithoutRequest(TestInlineQueryResultAudioBase):
def test_slot_behaviour(self, inline_query_result_audio):
inst = inline_query_result_audio
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -27,22 +27,23 @@ from telegram import (
InputTextMessageContent,
MessageEntity,
)
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def inline_query_result_cached_audio():
return InlineQueryResultCachedAudio(
TestInlineQueryResultCachedAudio.id_,
TestInlineQueryResultCachedAudio.audio_file_id,
caption=TestInlineQueryResultCachedAudio.caption,
parse_mode=TestInlineQueryResultCachedAudio.parse_mode,
caption_entities=TestInlineQueryResultCachedAudio.caption_entities,
input_message_content=TestInlineQueryResultCachedAudio.input_message_content,
reply_markup=TestInlineQueryResultCachedAudio.reply_markup,
TestInlineQueryResultCachedAudioBase.id_,
TestInlineQueryResultCachedAudioBase.audio_file_id,
caption=TestInlineQueryResultCachedAudioBase.caption,
parse_mode=TestInlineQueryResultCachedAudioBase.parse_mode,
caption_entities=TestInlineQueryResultCachedAudioBase.caption_entities,
input_message_content=TestInlineQueryResultCachedAudioBase.input_message_content,
reply_markup=TestInlineQueryResultCachedAudioBase.reply_markup,
)
class TestInlineQueryResultCachedAudio:
class TestInlineQueryResultCachedAudioBase:
id_ = "id"
type_ = "audio"
audio_file_id = "audio file id"
@@ -52,7 +53,9 @@ class TestInlineQueryResultCachedAudio:
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
def test_slot_behaviour(self, inline_query_result_cached_audio, mro_slots):
class TestInlineQueryResultCachedAudioWithoutRequest(TestInlineQueryResultCachedAudioBase):
def test_slot_behaviour(self, inline_query_result_cached_audio):
inst = inline_query_result_cached_audio
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -27,24 +27,25 @@ from telegram import (
InputTextMessageContent,
MessageEntity,
)
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def inline_query_result_cached_document():
return InlineQueryResultCachedDocument(
TestInlineQueryResultCachedDocument.id_,
TestInlineQueryResultCachedDocument.title,
TestInlineQueryResultCachedDocument.document_file_id,
caption=TestInlineQueryResultCachedDocument.caption,
parse_mode=TestInlineQueryResultCachedDocument.parse_mode,
caption_entities=TestInlineQueryResultCachedDocument.caption_entities,
description=TestInlineQueryResultCachedDocument.description,
input_message_content=TestInlineQueryResultCachedDocument.input_message_content,
reply_markup=TestInlineQueryResultCachedDocument.reply_markup,
TestInlineQueryResultCachedDocumentBase.id_,
TestInlineQueryResultCachedDocumentBase.title,
TestInlineQueryResultCachedDocumentBase.document_file_id,
caption=TestInlineQueryResultCachedDocumentBase.caption,
parse_mode=TestInlineQueryResultCachedDocumentBase.parse_mode,
caption_entities=TestInlineQueryResultCachedDocumentBase.caption_entities,
description=TestInlineQueryResultCachedDocumentBase.description,
input_message_content=TestInlineQueryResultCachedDocumentBase.input_message_content,
reply_markup=TestInlineQueryResultCachedDocumentBase.reply_markup,
)
class TestInlineQueryResultCachedDocument:
class TestInlineQueryResultCachedDocumentBase:
id_ = "id"
type_ = "document"
document_file_id = "document file id"
@@ -56,7 +57,9 @@ class TestInlineQueryResultCachedDocument:
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
def test_slot_behaviour(self, inline_query_result_cached_document, mro_slots):
class TestInlineQueryResultCachedDocumentWithoutRequest(TestInlineQueryResultCachedDocumentBase):
def test_slot_behaviour(self, inline_query_result_cached_document):
inst = inline_query_result_cached_document
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -26,23 +26,24 @@ from telegram import (
InputTextMessageContent,
MessageEntity,
)
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def inline_query_result_cached_gif():
return InlineQueryResultCachedGif(
TestInlineQueryResultCachedGif.id_,
TestInlineQueryResultCachedGif.gif_file_id,
title=TestInlineQueryResultCachedGif.title,
caption=TestInlineQueryResultCachedGif.caption,
parse_mode=TestInlineQueryResultCachedGif.parse_mode,
caption_entities=TestInlineQueryResultCachedGif.caption_entities,
input_message_content=TestInlineQueryResultCachedGif.input_message_content,
reply_markup=TestInlineQueryResultCachedGif.reply_markup,
TestInlineQueryResultCachedGifBase.id_,
TestInlineQueryResultCachedGifBase.gif_file_id,
title=TestInlineQueryResultCachedGifBase.title,
caption=TestInlineQueryResultCachedGifBase.caption,
parse_mode=TestInlineQueryResultCachedGifBase.parse_mode,
caption_entities=TestInlineQueryResultCachedGifBase.caption_entities,
input_message_content=TestInlineQueryResultCachedGifBase.input_message_content,
reply_markup=TestInlineQueryResultCachedGifBase.reply_markup,
)
class TestInlineQueryResultCachedGif:
class TestInlineQueryResultCachedGifBase:
id_ = "id"
type_ = "gif"
gif_file_id = "gif file id"
@@ -53,7 +54,9 @@ class TestInlineQueryResultCachedGif:
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
def test_slot_behaviour(self, inline_query_result_cached_gif, mro_slots):
class TestInlineQueryResultCachedGifWithoutRequest(TestInlineQueryResultCachedGifBase):
def test_slot_behaviour(self, inline_query_result_cached_gif):
inst = inline_query_result_cached_gif
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -26,23 +26,24 @@ from telegram import (
InputTextMessageContent,
MessageEntity,
)
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def inline_query_result_cached_mpeg4_gif():
return InlineQueryResultCachedMpeg4Gif(
TestInlineQueryResultCachedMpeg4Gif.id_,
TestInlineQueryResultCachedMpeg4Gif.mpeg4_file_id,
title=TestInlineQueryResultCachedMpeg4Gif.title,
caption=TestInlineQueryResultCachedMpeg4Gif.caption,
parse_mode=TestInlineQueryResultCachedMpeg4Gif.parse_mode,
caption_entities=TestInlineQueryResultCachedMpeg4Gif.caption_entities,
input_message_content=TestInlineQueryResultCachedMpeg4Gif.input_message_content,
reply_markup=TestInlineQueryResultCachedMpeg4Gif.reply_markup,
TestInlineQueryResultCachedMpeg4GifBase.id_,
TestInlineQueryResultCachedMpeg4GifBase.mpeg4_file_id,
title=TestInlineQueryResultCachedMpeg4GifBase.title,
caption=TestInlineQueryResultCachedMpeg4GifBase.caption,
parse_mode=TestInlineQueryResultCachedMpeg4GifBase.parse_mode,
caption_entities=TestInlineQueryResultCachedMpeg4GifBase.caption_entities,
input_message_content=TestInlineQueryResultCachedMpeg4GifBase.input_message_content,
reply_markup=TestInlineQueryResultCachedMpeg4GifBase.reply_markup,
)
class TestInlineQueryResultCachedMpeg4Gif:
class TestInlineQueryResultCachedMpeg4GifBase:
id_ = "id"
type_ = "mpeg4_gif"
mpeg4_file_id = "mpeg4 file id"
@@ -53,7 +54,9 @@ class TestInlineQueryResultCachedMpeg4Gif:
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
def test_slot_behaviour(self, inline_query_result_cached_mpeg4_gif, mro_slots):
class TestInlineQueryResultCachedMpeg4GifWithoutRequest(TestInlineQueryResultCachedMpeg4GifBase):
def test_slot_behaviour(self, inline_query_result_cached_mpeg4_gif):
inst = inline_query_result_cached_mpeg4_gif
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -26,24 +26,25 @@ from telegram import (
InputTextMessageContent,
MessageEntity,
)
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def inline_query_result_cached_photo():
return InlineQueryResultCachedPhoto(
TestInlineQueryResultCachedPhoto.id_,
TestInlineQueryResultCachedPhoto.photo_file_id,
title=TestInlineQueryResultCachedPhoto.title,
description=TestInlineQueryResultCachedPhoto.description,
caption=TestInlineQueryResultCachedPhoto.caption,
parse_mode=TestInlineQueryResultCachedPhoto.parse_mode,
caption_entities=TestInlineQueryResultCachedPhoto.caption_entities,
input_message_content=TestInlineQueryResultCachedPhoto.input_message_content,
reply_markup=TestInlineQueryResultCachedPhoto.reply_markup,
TestInlineQueryResultCachedPhotoBase.id_,
TestInlineQueryResultCachedPhotoBase.photo_file_id,
title=TestInlineQueryResultCachedPhotoBase.title,
description=TestInlineQueryResultCachedPhotoBase.description,
caption=TestInlineQueryResultCachedPhotoBase.caption,
parse_mode=TestInlineQueryResultCachedPhotoBase.parse_mode,
caption_entities=TestInlineQueryResultCachedPhotoBase.caption_entities,
input_message_content=TestInlineQueryResultCachedPhotoBase.input_message_content,
reply_markup=TestInlineQueryResultCachedPhotoBase.reply_markup,
)
class TestInlineQueryResultCachedPhoto:
class TestInlineQueryResultCachedPhotoBase:
id_ = "id"
type_ = "photo"
photo_file_id = "photo file id"
@@ -55,7 +56,9 @@ class TestInlineQueryResultCachedPhoto:
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
def test_slot_behaviour(self, inline_query_result_cached_photo, mro_slots):
class TestInlineQueryResultCachedPhotoWithoutRequest(TestInlineQueryResultCachedPhotoBase):
def test_slot_behaviour(self, inline_query_result_cached_photo):
inst = inline_query_result_cached_photo
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -25,26 +25,29 @@ from telegram import (
InlineQueryResultCachedVoice,
InputTextMessageContent,
)
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def inline_query_result_cached_sticker():
return InlineQueryResultCachedSticker(
TestInlineQueryResultCachedSticker.id_,
TestInlineQueryResultCachedSticker.sticker_file_id,
input_message_content=TestInlineQueryResultCachedSticker.input_message_content,
reply_markup=TestInlineQueryResultCachedSticker.reply_markup,
TestInlineQueryResultCachedStickerBase.id_,
TestInlineQueryResultCachedStickerBase.sticker_file_id,
input_message_content=TestInlineQueryResultCachedStickerBase.input_message_content,
reply_markup=TestInlineQueryResultCachedStickerBase.reply_markup,
)
class TestInlineQueryResultCachedSticker:
class TestInlineQueryResultCachedStickerBase:
id_ = "id"
type_ = "sticker"
sticker_file_id = "sticker file id"
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
def test_slot_behaviour(self, inline_query_result_cached_sticker, mro_slots):
class TestInlineQueryResultCachedStickerWithoutRequest(TestInlineQueryResultCachedStickerBase):
def test_slot_behaviour(self, inline_query_result_cached_sticker):
inst = inline_query_result_cached_sticker
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -26,24 +26,25 @@ from telegram import (
InputTextMessageContent,
MessageEntity,
)
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def inline_query_result_cached_video():
return InlineQueryResultCachedVideo(
TestInlineQueryResultCachedVideo.id_,
TestInlineQueryResultCachedVideo.video_file_id,
TestInlineQueryResultCachedVideo.title,
caption=TestInlineQueryResultCachedVideo.caption,
parse_mode=TestInlineQueryResultCachedVideo.parse_mode,
caption_entities=TestInlineQueryResultCachedVideo.caption_entities,
description=TestInlineQueryResultCachedVideo.description,
input_message_content=TestInlineQueryResultCachedVideo.input_message_content,
reply_markup=TestInlineQueryResultCachedVideo.reply_markup,
TestInlineQueryResultCachedVideoBase.id_,
TestInlineQueryResultCachedVideoBase.video_file_id,
TestInlineQueryResultCachedVideoBase.title,
caption=TestInlineQueryResultCachedVideoBase.caption,
parse_mode=TestInlineQueryResultCachedVideoBase.parse_mode,
caption_entities=TestInlineQueryResultCachedVideoBase.caption_entities,
description=TestInlineQueryResultCachedVideoBase.description,
input_message_content=TestInlineQueryResultCachedVideoBase.input_message_content,
reply_markup=TestInlineQueryResultCachedVideoBase.reply_markup,
)
class TestInlineQueryResultCachedVideo:
class TestInlineQueryResultCachedVideoBase:
id_ = "id"
type_ = "video"
video_file_id = "video file id"
@@ -55,7 +56,9 @@ class TestInlineQueryResultCachedVideo:
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
def test_slot_behaviour(self, inline_query_result_cached_video, mro_slots):
class TestInlineQueryResultCachedVideoWithoutRequest(TestInlineQueryResultCachedVideoBase):
def test_slot_behaviour(self, inline_query_result_cached_video):
inst = inline_query_result_cached_video
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -26,23 +26,24 @@ from telegram import (
InputTextMessageContent,
MessageEntity,
)
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def inline_query_result_cached_voice():
return InlineQueryResultCachedVoice(
TestInlineQueryResultCachedVoice.id_,
TestInlineQueryResultCachedVoice.voice_file_id,
TestInlineQueryResultCachedVoice.title,
caption=TestInlineQueryResultCachedVoice.caption,
parse_mode=TestInlineQueryResultCachedVoice.parse_mode,
caption_entities=TestInlineQueryResultCachedVoice.caption_entities,
input_message_content=TestInlineQueryResultCachedVoice.input_message_content,
reply_markup=TestInlineQueryResultCachedVoice.reply_markup,
TestInlineQueryResultCachedVoiceBase.id_,
TestInlineQueryResultCachedVoiceBase.voice_file_id,
TestInlineQueryResultCachedVoiceBase.title,
caption=TestInlineQueryResultCachedVoiceBase.caption,
parse_mode=TestInlineQueryResultCachedVoiceBase.parse_mode,
caption_entities=TestInlineQueryResultCachedVoiceBase.caption_entities,
input_message_content=TestInlineQueryResultCachedVoiceBase.input_message_content,
reply_markup=TestInlineQueryResultCachedVoiceBase.reply_markup,
)
class TestInlineQueryResultCachedVoice:
class TestInlineQueryResultCachedVoiceBase:
id_ = "id"
type_ = "voice"
voice_file_id = "voice file id"
@@ -53,7 +54,9 @@ class TestInlineQueryResultCachedVoice:
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
def test_slot_behaviour(self, inline_query_result_cached_voice, mro_slots):
class TestInlineQueryResultCachedVoiceWithoutRequest(TestInlineQueryResultCachedVoiceBase):
def test_slot_behaviour(self, inline_query_result_cached_voice):
inst = inline_query_result_cached_voice
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
@@ -0,0 +1,248 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import (
InlineKeyboardButton,
InlineKeyboardMarkup,
InlineQueryResultContact,
InlineQueryResultVoice,
InputTextMessageContent,
)
from tests.auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def inline_query_result_contact():
return InlineQueryResultContact(
TestInlineQueryResultContactBase.id_,
TestInlineQueryResultContactBase.phone_number,
TestInlineQueryResultContactBase.first_name,
last_name=TestInlineQueryResultContactBase.last_name,
thumbnail_url=TestInlineQueryResultContactBase.thumbnail_url,
thumbnail_width=TestInlineQueryResultContactBase.thumbnail_width,
thumbnail_height=TestInlineQueryResultContactBase.thumbnail_height,
input_message_content=TestInlineQueryResultContactBase.input_message_content,
reply_markup=TestInlineQueryResultContactBase.reply_markup,
)
class TestInlineQueryResultContactBase:
id_ = "id"
type_ = "contact"
phone_number = "phone_number"
first_name = "first_name"
last_name = "last_name"
thumbnail_url = "thumb url"
thumbnail_width = 10
thumbnail_height = 15
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
class TestInlineQueryResultContactWithoutRequest(TestInlineQueryResultContactBase):
def test_slot_behaviour(self, inline_query_result_contact):
inst = inline_query_result_contact
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
def test_expected_values(self, inline_query_result_contact):
assert inline_query_result_contact.id == self.id_
assert inline_query_result_contact.type == self.type_
assert inline_query_result_contact.phone_number == self.phone_number
assert inline_query_result_contact.first_name == self.first_name
assert inline_query_result_contact.last_name == self.last_name
assert inline_query_result_contact.thumbnail_url == self.thumbnail_url
assert inline_query_result_contact.thumbnail_width == self.thumbnail_width
assert inline_query_result_contact.thumbnail_height == self.thumbnail_height
assert (
inline_query_result_contact.input_message_content.to_dict()
== self.input_message_content.to_dict()
)
assert inline_query_result_contact.reply_markup.to_dict() == self.reply_markup.to_dict()
def test_thumb_url_property_deprecation_warning(self, recwarn):
inline_query_result_contact = InlineQueryResultContact(
TestInlineQueryResultContactBase.id_,
TestInlineQueryResultContactBase.phone_number,
TestInlineQueryResultContactBase.first_name,
last_name=TestInlineQueryResultContactBase.last_name,
input_message_content=TestInlineQueryResultContactBase.input_message_content,
reply_markup=TestInlineQueryResultContactBase.reply_markup,
thumb_url=TestInlineQueryResultContactBase.thumbnail_url, # deprecated arg
thumbnail_height=TestInlineQueryResultContactBase.thumbnail_height,
thumbnail_width=TestInlineQueryResultContactBase.thumbnail_width,
)
assert inline_query_result_contact.thumb_url == inline_query_result_contact.thumbnail_url
check_thumb_deprecation_warnings_for_args_and_attrs(
recwarn, __file__, deprecated_name="thumb_url", new_name="thumbnail_url"
)
def test_thumb_height_property_deprecation_warning(self, recwarn):
inline_query_result_contact = InlineQueryResultContact(
TestInlineQueryResultContactBase.id_,
TestInlineQueryResultContactBase.phone_number,
TestInlineQueryResultContactBase.first_name,
last_name=TestInlineQueryResultContactBase.last_name,
input_message_content=TestInlineQueryResultContactBase.input_message_content,
reply_markup=TestInlineQueryResultContactBase.reply_markup,
thumbnail_url=TestInlineQueryResultContactBase.thumbnail_url,
thumb_height=TestInlineQueryResultContactBase.thumbnail_height, # deprecated arg
thumbnail_width=TestInlineQueryResultContactBase.thumbnail_width,
)
assert (
inline_query_result_contact.thumb_height
== inline_query_result_contact.thumbnail_height
)
check_thumb_deprecation_warnings_for_args_and_attrs(
recwarn, __file__, deprecated_name="thumb_height", new_name="thumbnail_height"
)
def test_thumb_width_property_deprecation_warning(self, recwarn):
inline_query_result_contact = InlineQueryResultContact(
TestInlineQueryResultContactBase.id_,
TestInlineQueryResultContactBase.phone_number,
TestInlineQueryResultContactBase.first_name,
last_name=TestInlineQueryResultContactBase.last_name,
input_message_content=TestInlineQueryResultContactBase.input_message_content,
reply_markup=TestInlineQueryResultContactBase.reply_markup,
thumbnail_url=TestInlineQueryResultContactBase.thumbnail_url,
thumbnail_height=TestInlineQueryResultContactBase.thumbnail_height,
thumb_width=TestInlineQueryResultContactBase.thumbnail_width, # deprecated arg
)
assert (
inline_query_result_contact.thumb_width == inline_query_result_contact.thumbnail_width
)
check_thumb_deprecation_warnings_for_args_and_attrs(
recwarn, __file__, deprecated_name="thumb_width", new_name="thumbnail_width"
)
def test_throws_value_error_with_different_deprecated_and_new_arg_thumb_url(self):
with pytest.raises(
ValueError,
match="different entities as 'thumb_url' and 'thumbnail_url'",
):
InlineQueryResultContact(
TestInlineQueryResultContactBase.id_,
TestInlineQueryResultContactBase.phone_number,
TestInlineQueryResultContactBase.first_name,
last_name=TestInlineQueryResultContactBase.last_name,
input_message_content=TestInlineQueryResultContactBase.input_message_content,
reply_markup=TestInlineQueryResultContactBase.reply_markup,
thumbnail_url=TestInlineQueryResultContactBase.thumbnail_url,
thumb_url="some other url",
thumbnail_height=TestInlineQueryResultContactBase.thumbnail_height,
thumbnail_width=TestInlineQueryResultContactBase.thumbnail_width,
)
def test_throws_value_error_with_different_deprecated_and_new_arg_thumb_height(self):
with pytest.raises(
ValueError,
match="different entities as 'thumb_height' and 'thumbnail_height'",
):
InlineQueryResultContact(
TestInlineQueryResultContactBase.id_,
TestInlineQueryResultContactBase.phone_number,
TestInlineQueryResultContactBase.first_name,
last_name=TestInlineQueryResultContactBase.last_name,
input_message_content=TestInlineQueryResultContactBase.input_message_content,
reply_markup=TestInlineQueryResultContactBase.reply_markup,
thumbnail_url=TestInlineQueryResultContactBase.thumbnail_url,
thumbnail_height=TestInlineQueryResultContactBase.thumbnail_height,
thumb_height=TestInlineQueryResultContactBase.thumbnail_height + 1,
thumbnail_width=TestInlineQueryResultContactBase.thumbnail_width,
)
def test_throws_value_error_with_different_deprecated_and_new_arg_thumb_width(self):
with pytest.raises(
ValueError,
match="different entities as 'thumb_width' and 'thumbnail_width'",
):
InlineQueryResultContact(
TestInlineQueryResultContactBase.id_,
TestInlineQueryResultContactBase.phone_number,
TestInlineQueryResultContactBase.first_name,
last_name=TestInlineQueryResultContactBase.last_name,
input_message_content=TestInlineQueryResultContactBase.input_message_content,
reply_markup=TestInlineQueryResultContactBase.reply_markup,
thumbnail_url=TestInlineQueryResultContactBase.thumbnail_url,
thumbnail_height=TestInlineQueryResultContactBase.thumbnail_height,
thumbnail_width=TestInlineQueryResultContactBase.thumbnail_width,
thumb_width=TestInlineQueryResultContactBase.thumbnail_width + 1,
)
def test_to_dict(self, inline_query_result_contact):
inline_query_result_contact_dict = inline_query_result_contact.to_dict()
assert isinstance(inline_query_result_contact_dict, dict)
assert inline_query_result_contact_dict["id"] == inline_query_result_contact.id
assert inline_query_result_contact_dict["type"] == inline_query_result_contact.type
assert (
inline_query_result_contact_dict["phone_number"]
== inline_query_result_contact.phone_number
)
assert (
inline_query_result_contact_dict["first_name"]
== inline_query_result_contact.first_name
)
assert (
inline_query_result_contact_dict["last_name"] == inline_query_result_contact.last_name
)
assert (
inline_query_result_contact_dict["thumbnail_url"]
== inline_query_result_contact.thumbnail_url
)
assert (
inline_query_result_contact_dict["thumbnail_width"]
== inline_query_result_contact.thumbnail_width
)
assert (
inline_query_result_contact_dict["thumbnail_height"]
== inline_query_result_contact.thumbnail_height
)
assert (
inline_query_result_contact_dict["input_message_content"]
== inline_query_result_contact.input_message_content.to_dict()
)
assert (
inline_query_result_contact_dict["reply_markup"]
== inline_query_result_contact.reply_markup.to_dict()
)
def test_equality(self):
a = InlineQueryResultContact(self.id_, self.phone_number, self.first_name)
b = InlineQueryResultContact(self.id_, self.phone_number, self.first_name)
c = InlineQueryResultContact(self.id_, "", self.first_name)
d = InlineQueryResultContact("", self.phone_number, self.first_name)
e = InlineQueryResultVoice(self.id_, "", "")
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
@@ -0,0 +1,300 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import (
InlineKeyboardButton,
InlineKeyboardMarkup,
InlineQueryResultDocument,
InlineQueryResultVoice,
InputTextMessageContent,
MessageEntity,
)
from tests.auxil.deprecations import check_thumb_deprecation_warnings_for_args_and_attrs
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def inline_query_result_document():
return InlineQueryResultDocument(
TestInlineQueryResultDocumentBase.id_,
TestInlineQueryResultDocumentBase.document_url,
TestInlineQueryResultDocumentBase.title,
TestInlineQueryResultDocumentBase.mime_type,
caption=TestInlineQueryResultDocumentBase.caption,
parse_mode=TestInlineQueryResultDocumentBase.parse_mode,
caption_entities=TestInlineQueryResultDocumentBase.caption_entities,
description=TestInlineQueryResultDocumentBase.description,
thumbnail_url=TestInlineQueryResultDocumentBase.thumbnail_url,
thumbnail_width=TestInlineQueryResultDocumentBase.thumbnail_width,
thumbnail_height=TestInlineQueryResultDocumentBase.thumbnail_height,
input_message_content=TestInlineQueryResultDocumentBase.input_message_content,
reply_markup=TestInlineQueryResultDocumentBase.reply_markup,
)
class TestInlineQueryResultDocumentBase:
id_ = "id"
type_ = "document"
document_url = "document url"
title = "title"
caption = "caption"
parse_mode = "Markdown"
caption_entities = [MessageEntity(MessageEntity.ITALIC, 0, 7)]
mime_type = "mime type"
description = "description"
thumbnail_url = "thumb url"
thumbnail_width = 10
thumbnail_height = 15
input_message_content = InputTextMessageContent("input_message_content")
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
class TestInlineQueryResultDocumentWithoutRequest(TestInlineQueryResultDocumentBase):
def test_slot_behaviour(self, inline_query_result_document):
inst = inline_query_result_document
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
def test_expected_values(self, inline_query_result_document):
assert inline_query_result_document.id == self.id_
assert inline_query_result_document.type == self.type_
assert inline_query_result_document.document_url == self.document_url
assert inline_query_result_document.title == self.title
assert inline_query_result_document.caption == self.caption
assert inline_query_result_document.parse_mode == self.parse_mode
assert inline_query_result_document.caption_entities == tuple(self.caption_entities)
assert inline_query_result_document.mime_type == self.mime_type
assert inline_query_result_document.description == self.description
assert inline_query_result_document.thumbnail_url == self.thumbnail_url
assert inline_query_result_document.thumbnail_width == self.thumbnail_width
assert inline_query_result_document.thumbnail_height == self.thumbnail_height
assert (
inline_query_result_document.input_message_content.to_dict()
== self.input_message_content.to_dict()
)
assert inline_query_result_document.reply_markup.to_dict() == self.reply_markup.to_dict()
def test_thumb_url_property_deprecation_warning(self, recwarn):
inline_query_result_document = InlineQueryResultDocument(
TestInlineQueryResultDocumentBase.id_,
TestInlineQueryResultDocumentBase.document_url,
TestInlineQueryResultDocumentBase.title,
TestInlineQueryResultDocumentBase.mime_type,
caption=TestInlineQueryResultDocumentBase.caption,
parse_mode=TestInlineQueryResultDocumentBase.parse_mode,
caption_entities=TestInlineQueryResultDocumentBase.caption_entities,
description=TestInlineQueryResultDocumentBase.description,
input_message_content=TestInlineQueryResultDocumentBase.input_message_content,
reply_markup=TestInlineQueryResultDocumentBase.reply_markup,
thumb_url=TestInlineQueryResultDocumentBase.thumbnail_url, # deprecated arg
thumbnail_height=TestInlineQueryResultDocumentBase.thumbnail_height,
thumbnail_width=TestInlineQueryResultDocumentBase.thumbnail_width,
)
assert inline_query_result_document.thumb_url == inline_query_result_document.thumbnail_url
check_thumb_deprecation_warnings_for_args_and_attrs(
recwarn, __file__, deprecated_name="thumb_url", new_name="thumbnail_url"
)
def test_thumb_height_property_deprecation_warning(self, recwarn):
inline_query_result_document = InlineQueryResultDocument(
TestInlineQueryResultDocumentBase.id_,
TestInlineQueryResultDocumentBase.document_url,
TestInlineQueryResultDocumentBase.title,
TestInlineQueryResultDocumentBase.mime_type,
caption=TestInlineQueryResultDocumentBase.caption,
parse_mode=TestInlineQueryResultDocumentBase.parse_mode,
caption_entities=TestInlineQueryResultDocumentBase.caption_entities,
description=TestInlineQueryResultDocumentBase.description,
input_message_content=TestInlineQueryResultDocumentBase.input_message_content,
reply_markup=TestInlineQueryResultDocumentBase.reply_markup,
thumbnail_url=TestInlineQueryResultDocumentBase.thumbnail_url,
thumb_height=TestInlineQueryResultDocumentBase.thumbnail_height, # deprecated arg
thumbnail_width=TestInlineQueryResultDocumentBase.thumbnail_width,
)
assert (
inline_query_result_document.thumb_height
== inline_query_result_document.thumbnail_height
)
check_thumb_deprecation_warnings_for_args_and_attrs(
recwarn, __file__, deprecated_name="thumb_height", new_name="thumbnail_height"
)
def test_thumb_width_property_deprecation_warning(self, recwarn):
inline_query_result_document = InlineQueryResultDocument(
TestInlineQueryResultDocumentBase.id_,
TestInlineQueryResultDocumentBase.document_url,
TestInlineQueryResultDocumentBase.title,
TestInlineQueryResultDocumentBase.mime_type,
caption=TestInlineQueryResultDocumentBase.caption,
parse_mode=TestInlineQueryResultDocumentBase.parse_mode,
caption_entities=TestInlineQueryResultDocumentBase.caption_entities,
description=TestInlineQueryResultDocumentBase.description,
input_message_content=TestInlineQueryResultDocumentBase.input_message_content,
reply_markup=TestInlineQueryResultDocumentBase.reply_markup,
thumbnail_url=TestInlineQueryResultDocumentBase.thumbnail_url,
thumbnail_height=TestInlineQueryResultDocumentBase.thumbnail_height,
thumb_width=TestInlineQueryResultDocumentBase.thumbnail_width, # deprecated arg
)
assert (
inline_query_result_document.thumb_width
== inline_query_result_document.thumbnail_width
)
check_thumb_deprecation_warnings_for_args_and_attrs(
recwarn, __file__, deprecated_name="thumb_width", new_name="thumbnail_width"
)
def test_throws_value_error_with_different_deprecated_and_new_arg_thumb_url(self):
with pytest.raises(
ValueError,
match="different entities as 'thumb_url' and 'thumbnail_url'",
):
InlineQueryResultDocument(
TestInlineQueryResultDocumentBase.id_,
TestInlineQueryResultDocumentBase.document_url,
TestInlineQueryResultDocumentBase.title,
TestInlineQueryResultDocumentBase.mime_type,
caption=TestInlineQueryResultDocumentBase.caption,
parse_mode=TestInlineQueryResultDocumentBase.parse_mode,
caption_entities=TestInlineQueryResultDocumentBase.caption_entities,
description=TestInlineQueryResultDocumentBase.description,
input_message_content=TestInlineQueryResultDocumentBase.input_message_content,
reply_markup=TestInlineQueryResultDocumentBase.reply_markup,
thumbnail_url=TestInlineQueryResultDocumentBase.thumbnail_url,
thumb_url="some other url",
thumbnail_height=TestInlineQueryResultDocumentBase.thumbnail_height,
thumbnail_width=TestInlineQueryResultDocumentBase.thumbnail_width,
)
def test_throws_value_error_with_different_deprecated_and_new_arg_thumb_height(self):
with pytest.raises(
ValueError,
match="different entities as 'thumb_height' and 'thumbnail_height'",
):
InlineQueryResultDocument(
TestInlineQueryResultDocumentBase.id_,
TestInlineQueryResultDocumentBase.document_url,
TestInlineQueryResultDocumentBase.title,
TestInlineQueryResultDocumentBase.mime_type,
caption=TestInlineQueryResultDocumentBase.caption,
parse_mode=TestInlineQueryResultDocumentBase.parse_mode,
caption_entities=TestInlineQueryResultDocumentBase.caption_entities,
description=TestInlineQueryResultDocumentBase.description,
input_message_content=TestInlineQueryResultDocumentBase.input_message_content,
reply_markup=TestInlineQueryResultDocumentBase.reply_markup,
thumbnail_url=TestInlineQueryResultDocumentBase.thumbnail_url,
thumbnail_height=TestInlineQueryResultDocumentBase.thumbnail_height,
thumb_height=TestInlineQueryResultDocumentBase.thumbnail_height + 1,
thumbnail_width=TestInlineQueryResultDocumentBase.thumbnail_width,
)
def test_throws_value_error_with_different_deprecated_and_new_arg_thumb_width(self):
with pytest.raises(
ValueError,
match="different entities as 'thumb_width' and 'thumbnail_width'",
):
InlineQueryResultDocument(
TestInlineQueryResultDocumentBase.id_,
TestInlineQueryResultDocumentBase.document_url,
TestInlineQueryResultDocumentBase.title,
TestInlineQueryResultDocumentBase.mime_type,
caption=TestInlineQueryResultDocumentBase.caption,
parse_mode=TestInlineQueryResultDocumentBase.parse_mode,
caption_entities=TestInlineQueryResultDocumentBase.caption_entities,
description=TestInlineQueryResultDocumentBase.description,
input_message_content=TestInlineQueryResultDocumentBase.input_message_content,
reply_markup=TestInlineQueryResultDocumentBase.reply_markup,
thumbnail_url=TestInlineQueryResultDocumentBase.thumbnail_url,
thumbnail_height=TestInlineQueryResultDocumentBase.thumbnail_height,
thumbnail_width=TestInlineQueryResultDocumentBase.thumbnail_width,
thumb_width=TestInlineQueryResultDocumentBase.thumbnail_width + 1,
)
def test_caption_entities_always_tuple(self):
result = InlineQueryResultDocument(self.id_, self.document_url, self.title, self.mime_type)
assert result.caption_entities == ()
def test_to_dict(self, inline_query_result_document):
inline_query_result_document_dict = inline_query_result_document.to_dict()
assert isinstance(inline_query_result_document_dict, dict)
assert inline_query_result_document_dict["id"] == inline_query_result_document.id
assert inline_query_result_document_dict["type"] == inline_query_result_document.type
assert (
inline_query_result_document_dict["document_url"]
== inline_query_result_document.document_url
)
assert inline_query_result_document_dict["title"] == inline_query_result_document.title
assert inline_query_result_document_dict["caption"] == inline_query_result_document.caption
assert (
inline_query_result_document_dict["parse_mode"]
== inline_query_result_document.parse_mode
)
assert inline_query_result_document_dict["caption_entities"] == [
ce.to_dict() for ce in inline_query_result_document.caption_entities
]
assert (
inline_query_result_document_dict["mime_type"]
== inline_query_result_document.mime_type
)
assert (
inline_query_result_document_dict["description"]
== inline_query_result_document.description
)
assert (
inline_query_result_document_dict["thumbnail_url"]
== inline_query_result_document.thumbnail_url
)
assert (
inline_query_result_document_dict["thumbnail_width"]
== inline_query_result_document.thumbnail_width
)
assert (
inline_query_result_document_dict["thumbnail_height"]
== inline_query_result_document.thumbnail_height
)
assert (
inline_query_result_document_dict["input_message_content"]
== inline_query_result_document.input_message_content.to_dict()
)
assert (
inline_query_result_document_dict["reply_markup"]
== inline_query_result_document.reply_markup.to_dict()
)
def test_equality(self):
a = InlineQueryResultDocument(self.id_, self.document_url, self.title, self.mime_type)
b = InlineQueryResultDocument(self.id_, self.document_url, self.title, self.mime_type)
c = InlineQueryResultDocument(self.id_, "", self.title, self.mime_type)
d = InlineQueryResultDocument("", self.document_url, self.title, self.mime_type)
e = InlineQueryResultVoice(self.id_, "", "")
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
@@ -24,24 +24,27 @@ from telegram import (
InlineQueryResultGame,
InlineQueryResultVoice,
)
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="class")
@pytest.fixture(scope="module")
def inline_query_result_game():
return InlineQueryResultGame(
TestInlineQueryResultGame.id_,
TestInlineQueryResultGame.game_short_name,
reply_markup=TestInlineQueryResultGame.reply_markup,
TestInlineQueryResultGameBase.id_,
TestInlineQueryResultGameBase.game_short_name,
reply_markup=TestInlineQueryResultGameBase.reply_markup,
)
class TestInlineQueryResultGame:
class TestInlineQueryResultGameBase:
id_ = "id"
type_ = "game"
game_short_name = "game short name"
reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]])
def test_slot_behaviour(self, inline_query_result_game, mro_slots):
class TestInlineQueryResultGameWithoutRequest(TestInlineQueryResultGameBase):
def test_slot_behaviour(self, inline_query_result_game):
inst = inline_query_result_game
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"

Some files were not shown because too many files have changed in this diff Show More