Compare commits

...

23 Commits

Author SHA1 Message Date
Hinrich Mahler 9ef8826f33 Bump Version to v20.6 2023-10-03 15:39:57 +02:00
Bibo-Joshi 61b70efb4c Drop Backward Compatibility Layer Introduced in #3853 (API 6.8) (#3873) 2023-10-03 15:15:17 +02:00
Bibo-Joshi 63977ea353 Documentation Improvements (#3910)
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2023-10-03 15:11:37 +02:00
Harshil ae57d3b7c3 API 6.9 (#3898)
Co-authored-by: poolitzer <github@poolitzer.eu>
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
2023-10-03 14:32:57 +02:00
Harshil 8d76087bed Update pre-commit Dependencies (#3916) 2023-10-03 14:17:42 +02:00
Harshil 0e90deafb5 Add Support Python 3.12 (#3915) 2023-10-03 14:01:06 +02:00
Bibo-Joshi 39d45124df Add Rich Equality Comparison to WriteAccessAllowed (#3911) 2023-10-02 20:21:51 +02:00
dependabot[bot] 895403a0b5 Bump actions/checkout from 3 to 4 (#3914)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 20:20:28 +02:00
Bibo-Joshi 8cb177cb2c Move Bot API Tests to Separate Workflow File (#3912)
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2023-10-02 19:51:34 +02:00
Bibo-Joshi eaf802e07d Fix Failing file_size Tests (#3906) 2023-10-01 11:30:56 +02:00
Dmitry K 1ef242a17e Add __repr__ Methods Added in #3826 to Sphinx Documentation (#3901) 2023-09-26 19:18:12 +02:00
dependabot[bot] 7adb4fa2db Update httpx requirement from ~=0.24.1 to ~=0.25.0 (#3891)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 08:52:10 +02:00
dependabot[bot] 5c5ee598a2 Bump furo from 2023.8.19 to 2023.9.10 (#3890)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-22 19:44:33 +02:00
dependabot[bot] a4ae6f2097 Bump sphinx from 7.2.5 to 7.2.6 (#3892)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2023-09-22 18:53:07 +02:00
dependabot[bot] fc5a56c15b Update tornado requirement from ~=6.2 to ~=6.3.3 (#3675)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2023-09-22 18:47:50 +02:00
Bibo-Joshi 74112bfd06 Set Threshold for DeepSource's PY-R1000 to High (#3888) 2023-09-22 18:39:26 +02:00
Harshil ab90cd7359 One-Time Code Formatting Improvement via --preview Flag of black (#3882) 2023-09-22 18:19:21 +02:00
Harshil 5b0f1697f1 Move Dunder Methods to the Top of Class Bodies (#3883)
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
2023-09-15 22:19:45 +02:00
Dmitry K 9c7298c17a Add String Representation for Selected Classes (#3826) 2023-09-15 21:35:45 +02:00
Harshil 39abf838fa Verify Type Hints for Bot Method & Telegram Class Parameters (#3868) 2023-09-15 21:33:42 +02:00
Harshil 04b44f4595 Remove Superfluous Defaults.__ne__ (#3884) 2023-09-11 21:12:20 +02:00
dependabot[bot] a0decdac28 Bump pytest from 7.4.0 to 7.4.2 (#3881)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-10 12:30:55 +02:00
pre-commit-ci[bot] f77f4b0cf7 pre-commit autoupdate (#3876)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2023-09-09 23:12:30 +02:00
113 changed files with 1889 additions and 1003 deletions
+1
View File
@@ -17,3 +17,4 @@ enabled = true
runtime_version = "3.x.x"
max_line_length = 99
skip_doc_coverage = ["module", "magic", "init", "nonpublic"]
cyclomatic_complexity_threshold = "high"
+3 -3
View File
@@ -16,9 +16,9 @@ jobs:
- name: Fetch Dependabot metadata
id: dependabot-metadata
uses: dependabot/fetch-metadata@v1.6.1
uses: dependabot/fetch-metadata@v1.6.0
- uses: actions/checkout@v3.5.2
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
@@ -35,4 +35,4 @@ jobs:
with:
message: 'Update version number in other files'
committer_name: GitHub Actions
committer_email: 41898282+github-actions[bot]@users.noreply.github.com
committer_email: 41898282+github-actions[bot]@users.noreply.github.com
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
os: [ubuntu-latest]
fail-fast: False
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
os: [ubuntu-latest]
fail-fast: False
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
+47
View File
@@ -0,0 +1,47 @@
name: Bot API Tests
on:
pull_request:
branches:
- master
push:
branches:
- master
schedule:
# Run monday and friday morning at 03:07 - odd time to spread load on GitHub Actions
- cron: '7 3 * * 1,5'
jobs:
check-conformity:
name: check-conformity
runs-on: ${{matrix.os}}
strategy:
matrix:
python-version: [3.11]
os: [ubuntu-latest]
fail-fast: False
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -W ignore -m pip install --upgrade pip
python -W ignore -m pip install -r requirements.txt
python -W ignore -m pip install -r requirements-opts.txt
python -W ignore -m pip install -r requirements-dev.txt
- name: Compare to official api
run: |
pytest -v tests/test_official.py --junit-xml=.test_report_official.xml
exit $?
env:
TEST_OFFICIAL: "true"
shell: bash --noprofile --norc {0}
- name: Test Summary
id: test_summary
uses: test-summary/action@v2.1
if: always() # always run, even if tests fail
with:
paths: .test_report_official.xml
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
name: test-type-completeness
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: git fetch --depth=1 # https://github.com/actions/checkout/issues/329#issuecomment-674881489
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
@@ -16,11 +16,11 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12.0-rc.1']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
os: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: False
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
@@ -92,38 +92,3 @@ jobs:
env_vars: OS,PYTHON
name: ${{ matrix.os }}-${{ matrix.python-version }}
fail_ci_if_error: true
test_official:
name: test-official
runs-on: ${{matrix.os}}
strategy:
matrix:
python-version: [3.11]
os: [ubuntu-latest]
fail-fast: False
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -W ignore -m pip install --upgrade pip
python -W ignore -m pip install -r requirements.txt
python -W ignore -m pip install -r requirements-opts.txt
python -W ignore -m pip install -r requirements-dev.txt
- name: Compare to official api
run: |
pytest -v tests/test_official.py --junit-xml=.test_report_official.xml
exit $?
env:
TEST_OFFICIAL: "true"
shell: bash --noprofile --norc {0}
- name: Test Summary
id: test_summary
uses: test-summary/action@v2.1
if: always() # always run, even if tests fail
with:
paths: .test_report_official.xml
+9 -9
View File
@@ -6,7 +6,7 @@ ci:
repos:
- repo: https://github.com/psf/black
rev: 23.7.0
rev: 23.9.1
hooks:
- id: black
args:
@@ -17,7 +17,7 @@ repos:
hooks:
- id: flake8
- repo: https://github.com/PyCQA/pylint
rev: v3.0.0a6
rev: v3.0.0
hooks:
- id: pylint
files: ^(telegram|examples)/.*\.py$
@@ -29,13 +29,13 @@ repos:
additional_dependencies:
- httpx~=0.24.1
- tornado~=6.2
- tornado~=6.3.3
- APScheduler~=3.10.4
- cachetools~=5.3.1
- aiolimiter~=1.1.0
- . # this basically does `pip install -e .`
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.4.1
rev: v1.5.1
hooks:
- id: mypy
name: mypy-ptb
@@ -45,7 +45,7 @@ repos:
- types-cryptography
- types-cachetools
- httpx~=0.24.1
- tornado~=6.2
- tornado~=6.3.3
- APScheduler~=3.10.4
- cachetools~=5.3.1
- aiolimiter~=1.1.0
@@ -57,12 +57,12 @@ repos:
- --no-strict-optional
- --follow-imports=silent
additional_dependencies:
- tornado~=6.2
- tornado~=6.3.3
- APScheduler~=3.10.4
- cachetools~=5.3.1
- . # this basically does `pip install -e .`
- repo: https://github.com/asottile/pyupgrade
rev: v3.10.1
rev: v3.13.0
hooks:
- id: pyupgrade
files: ^(telegram|examples|tests|docs)/.*\.py$
@@ -77,14 +77,14 @@ repos:
- --diff
- --check
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.0.281'
rev: 'v0.0.292'
hooks:
- id: ruff
name: ruff
files: ^(telegram|examples|tests)/.*\.py$
additional_dependencies:
- httpx~=0.24.1
- tornado~=6.2
- tornado~=6.3.3
- APScheduler~=3.10.4
- cachetools~=5.3.1
- aiolimiter~=1.1.0
+50
View File
@@ -4,6 +4,56 @@
Changelog
=========
Version 20.6
============
*Released 2023-10-03*
This is the technical changelog for version 20.6. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`__.
Major Changes
-------------
- Drop Backward Compatibility Layer Introduced in :pr:`3853` (API 6.8) (:pr:`3873`)
- Full Support for Bot API 6.9 (:pr:`3898`)
New Features
------------
- Add Rich Equality Comparison to ``WriteAccessAllowed`` (:pr:`3911` closes :issue:`3909`)
- Add ``__repr__`` Methods Added in :pr:`3826` closes :issue:`3770` to Sphinx Documentation (:pr:`3901` closes :issue:`3889`)
- Add String Representation for Selected Classes (:pr:`3826` closes :issue:`3770`)
Minor Changes
-------------
- Add Support Python 3.12 (:pr:`3915`)
- Documentation Improvements (:pr:`3910`)
Internal Changes
----------------
- Verify Type Hints for Bot Method & Telegram Class Parameters (:pr:`3868`)
- Move Bot API Tests to Separate Workflow File (:pr:`3912`)
- Fix Failing ``file_size`` Tests (:pr:`3906`)
- Set Threshold for DeepSources PY-R1000 to High (:pr:`3888`)
- One-Time Code Formatting Improvement via ``--preview`` Flag of ``black`` (:pr:`3882`)
- Move Dunder Methods to the Top of Class Bodies (:pr:`3883`)
- Remove Superfluous ``Defaults.__ne__`` (:pr:`3884`)
Dependency Updates
------------------
- ``pre-commit`` autoupdate (:pr:`3876`)
- Update ``pre-commit`` Dependencies (:pr:`3916`)
- Bump ``actions/checkout`` from 3 to 4 (:pr:`3914`)
- Update ``httpx`` requirement from ~=0.24.1 to ~=0.25.0 (:pr:`3891`)
- Bump ``furo`` from 2023.8.19 to 2023.9.10 (:pr:`3890`)
- Bump ``sphinx`` from 7.2.5 to 7.2.6 (:pr:`3892`)
- Update ``tornado`` requirement from ~=6.2 to ~=6.3.3 (:pr:`3675`)
- Bump ``pytest`` from 7.4.0 to 7.4.2 (:pr:`3881`)
Version 20.5
============
*Released 2023-09-03*
+3 -3
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.8-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.9-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.8** are supported.
All types and methods of the Telegram Bot API **6.9** are supported.
Installing
==========
@@ -152,7 +152,7 @@ PTB can be installed with optional dependencies:
* ``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.1.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[webhooks]"`` installs the `tornado~=6.3.3 <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.1 <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.4 <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``.
+2 -2
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.8-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.9-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.8** are supported.
All types and methods of the Telegram Bot API **6.9** are supported.
Installing
==========
+4 -4
View File
@@ -194,7 +194,7 @@ class AdmonitionInserter:
)
except NotImplementedError as e:
raise NotImplementedError(
f"Error generating Sphinx 'Available in' admonition "
"Error generating Sphinx 'Available in' admonition "
f"(admonition_inserter.py). Class {name_of_class_in_attr} present in "
f"attribute {target_attr} of class {name_of_inspected_class_in_docstr}"
f" could not be resolved. {str(e)}"
@@ -237,7 +237,7 @@ class AdmonitionInserter:
)
except NotImplementedError as e:
raise NotImplementedError(
f"Error generating Sphinx 'Available in' admonition "
"Error generating Sphinx 'Available in' admonition "
f"(admonition_inserter.py). Class {name_of_class_in_prop} present in "
f"property {prop_name} of class {name_of_inspected_class_in_docstr}"
f" could not be resolved. {str(e)}"
@@ -269,7 +269,7 @@ class AdmonitionInserter:
)
except NotImplementedError as e:
raise NotImplementedError(
f"Error generating Sphinx 'Returned in' admonition "
"Error generating Sphinx 'Returned in' admonition "
f"(admonition_inserter.py). {cls}, method {method_name}. "
f"Couldn't resolve type hint in return annotation {ret_annot}. {str(e)}"
)
@@ -342,7 +342,7 @@ class AdmonitionInserter:
)
except NotImplementedError as e:
raise NotImplementedError(
f"Error generating Sphinx 'Use in' admonition "
"Error generating Sphinx 'Use in' admonition "
f"(admonition_inserter.py). {cls}, method {method_name}, parameter "
f"{param}: Couldn't resolve type hint {param.annotation}. {str(e)}"
)
+34 -18
View File
@@ -18,25 +18,41 @@
import inspect
keyword_args = [
":keyword _sphinx_paramlinks_telegram.Bot.{method}.read_timeout: Value to pass to "
":paramref:`telegram.request.BaseRequest.post.read_timeout`. Defaults to {read_timeout}.",
(
":keyword _sphinx_paramlinks_telegram.Bot.{method}.read_timeout: Value to pass to "
":paramref:`telegram.request.BaseRequest.post.read_timeout`. Defaults to {read_timeout}."
),
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.read_timeout: {read_timeout_type}, optional",
":keyword _sphinx_paramlinks_telegram.Bot.{method}.write_timeout: Value to pass to "
":paramref:`telegram.request.BaseRequest.post.write_timeout`. Defaults to {write_timeout}.",
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.write_timeout: :obj:`float` | :obj:`None`, "
"optional",
":keyword _sphinx_paramlinks_telegram.Bot.{method}.connect_timeout: Value to pass to "
":paramref:`telegram.request.BaseRequest.post.connect_timeout`. Defaults to "
":attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.",
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.connect_timeout: :obj:`float` | "
":obj:`None`, optional",
":keyword _sphinx_paramlinks_telegram.Bot.{method}.pool_timeout: Value to pass to "
":paramref:`telegram.request.BaseRequest.post.pool_timeout`. Defaults to "
":attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.",
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.pool_timeout: :obj:`float` | :obj:`None`, "
"optional",
":keyword _sphinx_paramlinks_telegram.Bot.{method}.api_kwargs: Arbitrary keyword arguments "
"to be passed to the Telegram API.",
(
":keyword _sphinx_paramlinks_telegram.Bot.{method}.write_timeout: Value to pass to "
":paramref:`telegram.request.BaseRequest.post.write_timeout`. Defaults to {write_timeout}."
),
(
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.write_timeout: :obj:`float` |"
" :obj:`None`, optional"
),
(
":keyword _sphinx_paramlinks_telegram.Bot.{method}.connect_timeout: Value to pass to "
":paramref:`telegram.request.BaseRequest.post.connect_timeout`. Defaults to "
":attr:`~telegram.request.BaseRequest.DEFAULT_NONE`."
),
(
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.connect_timeout: :obj:`float` | "
":obj:`None`, optional"
),
(
":keyword _sphinx_paramlinks_telegram.Bot.{method}.pool_timeout: Value to pass to "
":paramref:`telegram.request.BaseRequest.post.pool_timeout`. Defaults to "
":attr:`~telegram.request.BaseRequest.DEFAULT_NONE`."
),
(
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.pool_timeout: :obj:`float` |"
" :obj:`None`, optional"
),
(
":keyword _sphinx_paramlinks_telegram.Bot.{method}.api_kwargs: Arbitrary keyword arguments"
" to be passed to the Telegram API."
),
":kwtype _sphinx_paramlinks_telegram.Bot.{method}.api_kwargs: :obj:`dict`, optional",
"",
]
+2 -2
View File
@@ -1,6 +1,6 @@
sphinx==7.2.5
sphinx==7.2.6
sphinx-pypi-upload
furo==2023.8.19
furo==2023.9.10
git+https://github.com/harshil21/furo-sphinx-search@v0.2.0.1
sphinx-paramlinks==0.6.0
sphinxcontrib-mermaid==0.9.2
+62 -52
View File
@@ -21,9 +21,9 @@ author = "Leandro Toledo"
# built documents.
#
# The short X.Y version.
version = "20.5" # telegram.__version__[:3]
version = "20.6" # telegram.__version__[:3]
# The full version, including alpha/beta/rc tags.
release = "20.5" # telegram.__version__
release = "20.6" # telegram.__version__
# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = "6.1.3"
@@ -96,7 +96,9 @@ linkcheck_ignore = [
]
linkcheck_allowed_redirects = {
# Redirects to the default version are okay
r"https://docs\.python-telegram-bot\.org/.*": r"https://docs\.python-telegram-bot\.org/en/[\w\d\.]+/.*",
r"https://docs\.python-telegram-bot\.org/.*": (
r"https://docs\.python-telegram-bot\.org/en/[\w\d\.]+/.*"
),
# pre-commit.ci always redirects to the latest run
re.escape(
"https://results.pre-commit.ci/latest/github/python-telegram-bot/python-telegram-bot/master"
@@ -131,71 +133,79 @@ html_theme_options = {
"admonition-title-font-size": "0.95rem",
"admonition-font-size": "0.92rem",
},
"announcement": "PTB has undergone significant changes in v20. Please read the documentation "
"carefully and also check out the transition guide in the "
'<a href="https://github.com/python-telegram-bot/python-telegram-bot/wiki/'
'Transition-guide-to-Version-20.0">wiki</a>.',
"announcement": (
"PTB has undergone significant changes in v20. Please read the documentation "
"carefully and also check out the transition guide in the "
'<a href="https://github.com/python-telegram-bot/python-telegram-bot/wiki/'
'Transition-guide-to-Version-20.0">wiki</a>.'
),
"footer_icons": [
{
# Telegram channel logo
"name": "Telegram Channel",
"url": "https://t.me/pythontelegrambotchannel/",
# Following svg is from https://react-icons.github.io/react-icons/search?q=telegram
"html": '<svg stroke="currentColor" fill="currentColor" stroke-width="0" '
'viewBox="0 0 16 16" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">'
'<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8.287 5.906c-.778.324-2.334.994'
"-4.666 2.01-.378.15-.577.298-.595.442-.03.243.275.339.69.47l.175.055c.408.133."
"958.288 1.243.294.26.006.549-.1.868-.32 2.179-1.471 3.304-2.214 3.374-2.23.0"
"5-.012.12-.026.166.016.047.041.042.12.037.141-.03.129-1.227 1.241-1.846 1.81"
"7-.193.18-.33.307-.358.336a8.154 8.154 0 0 1-.188.186c-.38.366-.664.64.015 1.08"
"8.327.216.589.393.85.571.284.194.568.387.936.629.093.06.183.125.27.187.331.23"
"6.63.448.997.414.214-.02.435-.22.547-.82.265-1.417.786-4.486.906-5.751a1.426 "
"1.426 0 0 0-.013-.315.337.337 0 0 0-.114-.217.526.526 0 0 0-.31-.093c-.3.005-.7"
'63.166-2.984 1.09z"></path></svg>',
"html": (
'<svg stroke="currentColor" fill="currentColor" stroke-width="0" '
'viewBox="0 0 16 16" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">'
'<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8.287 5.906c-.778.324-2.334.994'
"-4.666 2.01-.378.15-.577.298-.595.442-.03.243.275.339.69.47l.175.055c.408.133."
"958.288 1.243.294.26.006.549-.1.868-.32 2.179-1.471 3.304-2.214 3.374-2.23.0"
"5-.012.12-.026.166.016.047.041.042.12.037.141-.03.129-1.227 1.241-1.846 1.81"
"7-.193.18-.33.307-.358.336a8.154 8.154 0 0 1-.188.186c-.38.366-.664.64.015 1.08"
"8.327.216.589.393.85.571.284.194.568.387.936.629.093.06.183.125.27.187.331.23"
"6.63.448.997.414.214-.02.435-.22.547-.82.265-1.417.786-4.486.906-5.751a1.426 "
"1.426 0 0 0-.013-.315.337.337 0 0 0-.114-.217.526.526 0 0 0-.31-.093c-.3.005-.7"
'63.166-2.984 1.09z"></path></svg>'
),
"class": "",
},
{ # Github logo
"name": "GitHub",
"url": "https://github.com/python-telegram-bot/python-telegram-bot/",
"html": '<svg stroke="currentColor" fill="currentColor" stroke-width="0" '
'viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 '
"2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.4"
"9-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23"
".82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 "
"0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.2"
"7 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.5"
"1.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 "
'1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z">'
"</path></svg>",
"html": (
'<svg stroke="currentColor" fill="currentColor" stroke-width="0" '
'viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 '
"2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.4"
"9-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23"
".82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 "
"0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.2"
"7 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.5"
"1.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 "
'1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z">'
"</path></svg>"
),
"class": "",
},
{ # PTB website logo - globe
"name": "python-telegram-bot website",
"url": "https://python-telegram-bot.org/",
"html": '<svg stroke="currentColor" fill="currentColor" stroke-width="0" '
'viewBox="0 0 16 16" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">'
'<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 '
"1.855-.143.268-.276.56-.395.872.705.157 1.472.257 2.282.287V1.077zM4.249 3.53"
"9c.142-.384.304-.744.481-1.078a6.7 6.7 0 0 1 .597-.933A7.01 7.01 0 0 0 3.051 "
"3.05c.362.184.763.349 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9.124 "
"9.124 0 0 1-1.565-.667A6.964 6.964 0 0 0 1.018 7.5h2.49zm1.4-2.741a12.344 "
"12.344 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332zM8.5 5.09V7.5h"
"2.99a12.342 12.342 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.03"
"5.987.176 1.914.399 2.741A13.612 13.612 0 0 1 7.5 10.91V8.5H4.51zm3.99 0v2.409"
"c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741H8.5zm-3.282 3.696c.12.31"
"2.252.604.395.872.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.28"
"2.287zm.11 2.276a6.696 6.696 0 0 1-.598-.933 8.853 8.853 0 0 1-.481-1.079 8.38 "
"8.38 0 0 0-1.198.49 7.01 7.01 0 0 0 2.276 1.522zm-1.383-2.964A13.36 13.36 0 0 1"
" 3.508 8.5h-2.49a6.963 6.963 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667zm"
"6.728 2.964a7.009 7.009 0 0 0 2.275-1.521 8.376 8.376 0 0 0-1.197-.49 8.853 "
"8.853 0 0 1-.481 1.078 6.688 6.688 0 0 1-.597.933zM8.5 11.909v3.014c.67-.204 "
"1.335-.82 1.887-1.855.143-.268.276-.56.395-.872A12.63 12.63 0 0 0 8.5 11.91zm"
"3.555-.401c.57.185 1.095.409 1.565.667A6.963 6.963 0 0 0 14.982 8.5h-2.49a1"
"3.36 13.36 0 0 1-.437 3.008zM14.982 7.5a6.963 6.963 0 0 0-1.362-3.675c-.47.25"
"8-.995.482-1.565.667.248.92.4 1.938.437 3.008h2.49zM11.27 2.461c.177.334.339.6"
"94.482 1.078a8.368 8.368 0 0 0 1.196-.49 7.01 7.01 0 0 0-2.275-1.52c.218.283.4"
"18.597.597.932zm-.488 1.343a7.765 7.765 0 0 0-.395-.872C9.835 1.897 9.17 1.282 "
'8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z"></path></svg>',
"html": (
'<svg stroke="currentColor" fill="currentColor" stroke-width="0" '
'viewBox="0 0 16 16" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">'
'<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 '
"1.855-.143.268-.276.56-.395.872.705.157 1.472.257 2.282.287V1.077zM4.249 3.53"
"9c.142-.384.304-.744.481-1.078a6.7 6.7 0 0 1 .597-.933A7.01 7.01 0 0 0 3.051 "
"3.05c.362.184.763.349 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9.124 "
"9.124 0 0 1-1.565-.667A6.964 6.964 0 0 0 1.018 7.5h2.49zm1.4-2.741a12.344 "
"12.344 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332zM8.5 5.09V7.5h"
"2.99a12.342 12.342 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.03"
"5.987.176 1.914.399 2.741A13.612 13.612 0 0 1 7.5 10.91V8.5H4.51zm3.99 0v2.409"
"c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741H8.5zm-3.282 3.696c.12.31"
"2.252.604.395.872.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.28"
"2.287zm.11 2.276a6.696 6.696 0 0 1-.598-.933 8.853 8.853 0 0 1-.481-1.079 8.38 "
"8.38 0 0 0-1.198.49 7.01 7.01 0 0 0 2.276 1.522zm-1.383-2.964A13.36 13.36 0 0 1"
" 3.508 8.5h-2.49a6.963 6.963 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667zm"
"6.728 2.964a7.009 7.009 0 0 0 2.275-1.521 8.376 8.376 0 0 0-1.197-.49 8.853 "
"8.853 0 0 1-.481 1.078 6.688 6.688 0 0 1-.597.933zM8.5 11.909v3.014c.67-.204 "
"1.335-.82 1.887-1.855.143-.268.276-.56.395-.872A12.63 12.63 0 0 0 8.5 11.91zm"
"3.555-.401c.57.185 1.095.409 1.565.667A6.963 6.963 0 0 0 14.982 8.5h-2.49a1"
"3.36 13.36 0 0 1-.437 3.008zM14.982 7.5a6.963 6.963 0 0 0-1.362-3.675c-.47.25"
"8-.995.482-1.565.667.248.92.4 1.938.437 3.008h2.49zM11.27 2.461c.177.334.339.6"
"94.482 1.078a8.368 8.368 0 0 0 1.196-.49 7.01 7.01 0 0 0-2.275-1.52c.218.283.4"
"18.597.597.932zm-.488 1.343a7.765 7.765 0 0 0-.395-.872C9.835 1.897 9.17 1.282 "
'8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z"></path></svg>'
),
"class": "",
},
],
+1 -1
View File
@@ -4,4 +4,4 @@ Bot
.. autoclass:: telegram.Bot
:members:
:show-inheritance:
:special-members: __reduce__, __deepcopy__
:special-members: __repr__, __reduce__, __deepcopy__
+1
View File
@@ -4,3 +4,4 @@ Application
.. autoclass:: telegram.ext.Application
:members:
:show-inheritance:
:special-members: __repr__
+1
View File
@@ -4,3 +4,4 @@ BaseHandler
.. autoclass:: telegram.ext.BaseHandler
:members:
:show-inheritance:
:special-members: __repr__
@@ -4,3 +4,4 @@ ConversationHandler
.. autoclass:: telegram.ext.ConversationHandler
:members:
:show-inheritance:
:special-members: __repr__
+1
View File
@@ -4,3 +4,4 @@ ExtBot
.. autoclass:: telegram.ext.ExtBot
:show-inheritance:
:members: insert_callback_data, defaults, rate_limiter, initialize, shutdown, callback_data_cache
:special-members: __repr__
+1 -1
View File
@@ -4,4 +4,4 @@ Job
.. autoclass:: telegram.ext.Job
:members:
:show-inheritance:
:special-members: __call__
:special-members: __call__, __repr__
+1
View File
@@ -4,3 +4,4 @@ JobQueue
.. autoclass:: telegram.ext.JobQueue
:members:
:show-inheritance:
:special-members: __repr__
+1
View File
@@ -4,3 +4,4 @@ Updater
.. autoclass:: telegram.ext.Updater
:members:
:show-inheritance:
:special-members: __repr__
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""This example showcases how PTBs "arbitrary callback data" feature can be used.
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
+2 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -115,8 +115,7 @@ async def count_click(update: Update, context: CustomContext) -> None:
async def print_users(update: Update, context: CustomContext) -> None:
"""Show which users have been using this bot."""
await update.message.reply_text(
"The following user IDs have used this bot: "
f'{", ".join(map(str, context.bot_user_ids))}'
f"The following user IDs have used this bot: {', '.join(map(str, context.bot_user_ids))}"
)
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
+2 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""Bot that explains Telegram's "Deep Linking Parameters" functionality.
@@ -57,8 +57,7 @@ async def deep_linked_level_1(update: Update, context: ContextTypes.DEFAULT_TYPE
bot = context.bot
url = helpers.create_deep_linked_url(bot.username, SO_COOL)
text = (
"Awesome, you just accessed hidden functionality! "
"Now let's get back to the private chat."
"Awesome, you just accessed hidden functionality! Now let's get back to the private chat."
)
keyboard = InlineKeyboardMarkup.from_button(
InlineKeyboardButton(text="Continue here!", url=url)
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""This is a very simple example on how one could implement a custom error handler."""
@@ -40,7 +40,7 @@ async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> N
# You might need to add some logic to deal with messages longer than the 4096 character limit.
update_str = update.to_dict() if isinstance(update, Update) else str(update)
message = (
f"An exception was raised while handling an update\n"
"An exception was raised while handling an update\n"
f"<pre>update = {html.escape(json.dumps(update_str, indent=2, ensure_ascii=False))}"
"</pre>\n\n"
f"<pre>context.chat_data = {html.escape(str(context.chat_data))}</pre>\n\n"
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""Simple inline keyboard bot with multiple CallbackQueryHandlers.
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""Basic example for a bot that can receive payment from user."""
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -59,7 +59,7 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
if context.user_data:
reply_text += (
f" You already told me your {', '.join(context.user_data.keys())}. Why don't you "
f"tell me something more about yourself? Or change anything I already know."
"tell me something more about yourself? Or change anything I already know."
)
else:
reply_text += (
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
-1
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python
# pylint: disable=import-error
"""Simple Bot to reply to Telegram messages.
This is built on the API wrapper, see echobot.py to see the same example built
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument, import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
+5 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# pylint: disable=unused-argument,import-error
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
@@ -45,8 +45,10 @@ async def web_app_data(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
# (see webappbot.html)
data = json.loads(update.effective_message.web_app_data.data)
await update.message.reply_html(
text=f"You selected the color with the HEX value <code>{data['hex']}</code>. The "
f"corresponding RGB value is <code>{tuple(data['rgb'].values())}</code>.",
text=(
f"You selected the color with the HEX value <code>{data['hex']}</code>. The "
f"corresponding RGB value is <code>{tuple(data['rgb'].values())}</code>."
),
reply_markup=ReplyKeyboardRemove(),
)
+2 -3
View File
@@ -12,10 +12,9 @@ target-version = "py38"
show-fixes = true
ignore = ["PLR2004", "PLR0911", "PLR0912", "PLR0913", "PLR0915", "PERF203"]
select = ["E", "F", "I", "PL", "UP", "RUF", "PTH", "C4", "B", "PIE", "SIM", "RET", "RSE",
"G", "ISC", "PT", "ASYNC", "TCH", "CPY", "SLOT", "PERF", "PYI"]
"G", "ISC", "PT", "ASYNC", "TCH", "SLOT", "PERF", "PYI", "FLY", "AIR"]
# Add "FURB" after it's out of preview
[tool.ruff.per-file-ignores]
"tests/*.py" = ["B018"]
"**/__init__.py" = ["CPY001"]
"examples/**.py" = ["CPY001"]
"tests/**.py" = ["RUF012"]
+1 -1
View File
@@ -1,7 +1,7 @@
pre-commit # needed for pre-commit hooks in the git commit command
# For the test suite
pytest==7.4.0
pytest==7.4.2
pytest-asyncio==0.21.1 # needed because pytest doesn't come with native support for coroutines as tests
pytest-xdist==3.3.1 # xdist runs tests in parallel
flaky # Used for flaky tests (flaky decorator)
+1 -1
View File
@@ -16,7 +16,7 @@ cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1 # passport
aiolimiter~=1.1.0 # rate-limiter!ext
# tornado is rather stable, but let's not allow the next mayor release without prior testing
tornado~=6.2 # webhooks!ext
tornado~=6.3.3 # webhooks!ext
# Cachetools and APS don't have a strict stability policy.
# Let's be cautious for now.
+1 -1
View File
@@ -6,4 +6,4 @@
# versions and only increase the lower bound if necessary
# httpx has no stable release yet, so let's be cautious for now
httpx ~= 0.24.1
httpx ~= 0.25.0
+1 -1
View File
@@ -19,7 +19,7 @@ exclude = setup.py, setup-raw.py docs/source/conf.py
disable = duplicate-code,too-many-arguments,too-many-public-methods,too-few-public-methods,
broad-except,too-many-instance-attributes,fixme,missing-function-docstring,
missing-class-docstring,too-many-locals,too-many-lines,too-many-branches,
too-many-statements
too-many-statements, cyclic-import
enable=useless-suppression ; Warns about unused pylint ignores
exclude-protected=_unfrozen
+211 -181
View File
@@ -69,7 +69,6 @@ from telegram._files.contact import Contact
from telegram._files.document import Document
from telegram._files.file import File
from telegram._files.inputmedia import InputMedia
from telegram._files.inputsticker import InputSticker
from telegram._files.location import Location
from telegram._files.photosize import PhotoSize
from telegram._files.sticker import MaskPosition, Sticker, StickerSet
@@ -79,13 +78,10 @@ from telegram._files.videonote import VideoNote
from telegram._files.voice import Voice
from telegram._forumtopic import ForumTopic
from telegram._games.gamehighscore import GameHighScore
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton
from telegram._menubutton import MenuButton
from telegram._message import Message
from telegram._messageid import MessageId
from telegram._passport.passportelementerrors import PassportElementError
from telegram._payment.shippingoption import ShippingOption
from telegram._poll import Poll
from telegram._sentwebappmessage import SentWebAppMessage
from telegram._telegramobject import TelegramObject
@@ -96,6 +92,7 @@ from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.files import is_local_file, parse_file_input
from telegram._utils.logging import get_logger
from telegram._utils.repr import build_repr_with_selected_attrs
from telegram._utils.types import (
CorrectOptionID,
DVInput,
@@ -115,14 +112,18 @@ from telegram.warnings import PTBUserWarning
if TYPE_CHECKING:
from telegram import (
InlineKeyboardMarkup,
InlineQueryResult,
InputFile,
InputMediaAudio,
InputMediaDocument,
InputMediaPhoto,
InputMediaVideo,
InputSticker,
LabeledPrice,
MessageEntity,
PassportElementError,
ShippingOption,
)
BT = TypeVar("BT", bound="Bot")
@@ -290,8 +291,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
if warning_string:
self._warn(
f"You set the HTTP version for the {warning_string} HTTPXRequest instance to "
f"HTTP/2. The self hosted bot api instances only support HTTP/1.1. You should "
f"either run a HTTP proxy in front of it which supports HTTP/2 or use HTTP/1.1.",
"HTTP/2. The self hosted bot api instances only support HTTP/1.1. You should "
"either run a HTTP proxy in front of it which supports HTTP/2 or use HTTP/1.1.",
PTBUserWarning,
stacklevel=2,
)
@@ -308,6 +309,65 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
self._freeze()
async def __aenter__(self: BT) -> BT:
try:
await self.initialize()
return self
except Exception as exc:
await self.shutdown()
raise exc
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
# Make sure not to return `True` so that exceptions are not suppressed
# https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
await self.shutdown()
def __reduce__(self) -> NoReturn:
"""Customizes how :func:`copy.deepcopy` processes objects of this type. Bots can not
be pickled and this method will always raise an exception.
.. versionadded:: 20.0
Raises:
:exc:`pickle.PicklingError`
"""
raise pickle.PicklingError("Bot objects cannot be pickled!")
def __deepcopy__(self, memodict: Dict[int, object]) -> NoReturn:
"""Customizes how :func:`copy.deepcopy` processes objects of this type. Bots can not
be deepcopied and this method will always raise an exception.
.. versionadded:: 20.0
Raises:
:exc:`TypeError`
"""
raise TypeError("Bot objects cannot be deepcopied!")
def __eq__(self, other: object) -> bool:
if isinstance(other, self.__class__):
return self.bot == other.bot
return False
def __hash__(self) -> int:
return hash((self.__class__, self.bot))
def __repr__(self) -> str:
"""Give a string representation of the bot in the form ``Bot[token=...]``.
As this class doesn't implement :meth:`object.__str__`, the default implementation
will be used, which is equivalent to :meth:`__repr__`.
Returns:
:obj:`str`
"""
return build_repr_with_selected_attrs(self, token=self.token)
@property
def token(self) -> str:
""":obj:`str`: Bot's unique authentication token.
@@ -353,6 +413,93 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"""
return self._private_key
@property
def request(self) -> BaseRequest:
"""The :class:`~telegram.request.BaseRequest` object used by this bot.
Warning:
Requests to the Bot API are made by the various methods of this class. This attribute
should *not* be used manually.
"""
return self._request[1]
@property
def bot(self) -> User:
""":class:`telegram.User`: User instance for the bot as returned by :meth:`get_me`.
Warning:
This value is the cached return value of :meth:`get_me`. If the bots profile is
changed during runtime, this value won't reflect the changes until :meth:`get_me` is
called again.
.. seealso:: :meth:`initialize`
"""
if self._bot_user is None:
raise RuntimeError(
f"{self.__class__.__name__} is not properly initialized. Call "
f"`{self.__class__.__name__}.initialize` before accessing this property."
)
return self._bot_user
@property
def id(self) -> int:
""":obj:`int`: Unique identifier for this bot. Shortcut for the corresponding attribute of
:attr:`bot`.
"""
return self.bot.id
@property
def first_name(self) -> str:
""":obj:`str`: Bot's first name. Shortcut for the corresponding attribute of
:attr:`bot`.
"""
return self.bot.first_name
@property
def last_name(self) -> str:
""":obj:`str`: Optional. Bot's last name. Shortcut for the corresponding attribute of
:attr:`bot`.
"""
return self.bot.last_name # type: ignore
@property
def username(self) -> str:
""":obj:`str`: Bot's username. Shortcut for the corresponding attribute of
:attr:`bot`.
"""
return self.bot.username # type: ignore
@property
def link(self) -> str:
""":obj:`str`: Convenience property. Returns the t.me link of the bot."""
return f"https://t.me/{self.username}"
@property
def can_join_groups(self) -> bool:
""":obj:`bool`: Bot's :attr:`telegram.User.can_join_groups` attribute. Shortcut for the
corresponding attribute of :attr:`bot`.
"""
return self.bot.can_join_groups # type: ignore
@property
def can_read_all_group_messages(self) -> bool:
""":obj:`bool`: Bot's :attr:`telegram.User.can_read_all_group_messages` attribute.
Shortcut for the corresponding attribute of :attr:`bot`.
"""
return self.bot.can_read_all_group_messages # type: ignore
@property
def supports_inline_queries(self) -> bool:
""":obj:`bool`: Bot's :attr:`telegram.User.supports_inline_queries` attribute.
Shortcut for the corresponding attribute of :attr:`bot`.
"""
return self.bot.supports_inline_queries # type: ignore
@property
def name(self) -> str:
""":obj:`str`: Bot's @username. Shortcut for the corresponding attribute of :attr:`bot`."""
return f"@{self.username}"
@classmethod
def _warn(
cls, message: str, category: Type[Warning] = PTBUserWarning, stacklevel: int = 0
@@ -362,28 +509,6 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"""
warn(message=message, category=category, stacklevel=stacklevel + 1)
def __reduce__(self) -> NoReturn:
"""Customizes how :func:`copy.deepcopy` processes objects of this type. Bots can not
be pickled and this method will always raise an exception.
.. versionadded:: 20.0
Raises:
:exc:`pickle.PicklingError`
"""
raise pickle.PicklingError("Bot objects cannot be pickled!")
def __deepcopy__(self, memodict: Dict[int, object]) -> NoReturn:
"""Customizes how :func:`copy.deepcopy` processes objects of this type. Bots can not
be deepcopied and this method will always raise an exception.
.. versionadded:: 20.0
Raises:
:exc:`TypeError`
"""
raise TypeError("Bot objects cannot be deepcopied!")
# TODO: After https://youtrack.jetbrains.com/issue/PY-50952 is fixed, we can revisit this and
# consider adding Paramspec from typing_extensions to properly fix this. Currently a workaround
def _log(func: Any): # type: ignore[no-untyped-def] # skipcq: PY-D0003
@@ -621,111 +746,6 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
await asyncio.gather(self._request[0].shutdown(), self._request[1].shutdown())
self._initialized = False
async def __aenter__(self: BT) -> BT:
try:
await self.initialize()
return self
except Exception as exc:
await self.shutdown()
raise exc
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
# Make sure not to return `True` so that exceptions are not suppressed
# https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
await self.shutdown()
@property
def request(self) -> BaseRequest:
"""The :class:`~telegram.request.BaseRequest` object used by this bot.
Warning:
Requests to the Bot API are made by the various methods of this class. This attribute
should *not* be used manually.
"""
return self._request[1]
@property
def bot(self) -> User:
""":class:`telegram.User`: User instance for the bot as returned by :meth:`get_me`.
Warning:
This value is the cached return value of :meth:`get_me`. If the bots profile is
changed during runtime, this value won't reflect the changes until :meth:`get_me` is
called again.
.. seealso:: :meth:`initialize`
"""
if self._bot_user is None:
raise RuntimeError(
f"{self.__class__.__name__} is not properly initialized. Call "
f"`{self.__class__.__name__}.initialize` before accessing this property."
)
return self._bot_user
@property
def id(self) -> int: # pylint: disable=invalid-name
""":obj:`int`: Unique identifier for this bot. Shortcut for the corresponding attribute of
:attr:`bot`.
"""
return self.bot.id
@property
def first_name(self) -> str:
""":obj:`str`: Bot's first name. Shortcut for the corresponding attribute of
:attr:`bot`.
"""
return self.bot.first_name
@property
def last_name(self) -> str:
""":obj:`str`: Optional. Bot's last name. Shortcut for the corresponding attribute of
:attr:`bot`.
"""
return self.bot.last_name # type: ignore
@property
def username(self) -> str:
""":obj:`str`: Bot's username. Shortcut for the corresponding attribute of
:attr:`bot`.
"""
return self.bot.username # type: ignore
@property
def link(self) -> str:
""":obj:`str`: Convenience property. Returns the t.me link of the bot."""
return f"https://t.me/{self.username}"
@property
def can_join_groups(self) -> bool:
""":obj:`bool`: Bot's :attr:`telegram.User.can_join_groups` attribute. Shortcut for the
corresponding attribute of :attr:`bot`.
"""
return self.bot.can_join_groups # type: ignore
@property
def can_read_all_group_messages(self) -> bool:
""":obj:`bool`: Bot's :attr:`telegram.User.can_read_all_group_messages` attribute.
Shortcut for the corresponding attribute of :attr:`bot`.
"""
return self.bot.can_read_all_group_messages # type: ignore
@property
def supports_inline_queries(self) -> bool:
""":obj:`bool`: Bot's :attr:`telegram.User.supports_inline_queries` attribute.
Shortcut for the corresponding attribute of :attr:`bot`.
"""
return self.bot.supports_inline_queries # type: ignore
@property
def name(self) -> str:
""":obj:`str`: Bot's @username. Shortcut for the corresponding attribute of :attr:`bot`."""
return f"@{self.username}"
@_log
async def get_me(
self,
@@ -2154,7 +2174,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
inline_message_id: Optional[str] = None,
latitude: Optional[float] = None,
longitude: Optional[float] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
@@ -2247,7 +2267,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
chat_id: Optional[Union[str, int]] = None,
message_id: Optional[int] = None,
inline_message_id: Optional[str] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2521,11 +2541,11 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
@_log
async def send_game(
self,
chat_id: Union[int, str],
chat_id: int,
game_short_name: str,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
@@ -2539,7 +2559,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"""Use this method to send a game.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat.
chat_id (:obj:`int`): Unique identifier for the target chat.
game_short_name (:obj:`str`): Short name of the game, serves as the unique identifier
for the game. Set up your games via `@BotFather <https://t.me/BotFather>`_.
disable_notification (:obj:`bool`, optional): |disable_notification|
@@ -2826,7 +2846,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
@_log
async def get_user_profile_photos(
self,
user_id: Union[str, int],
user_id: int,
offset: Optional[int] = None,
limit: Optional[int] = None,
*,
@@ -2938,7 +2958,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
async def ban_chat_member(
self,
chat_id: Union[str, int],
user_id: Union[str, int],
user_id: int,
until_date: Optional[Union[int, datetime]] = None,
revoke_messages: Optional[bool] = None,
*,
@@ -3046,7 +3066,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
async def unban_chat_member(
self,
chat_id: Union[str, int],
user_id: Union[str, int],
user_id: int,
only_if_banned: Optional[bool] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -3203,7 +3223,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
inline_message_id: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
entities: Optional[Sequence["MessageEntity"]] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -3279,7 +3299,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
message_id: Optional[int] = None,
inline_message_id: Optional[str] = None,
caption: Optional[str] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
*,
@@ -3349,7 +3369,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
chat_id: Optional[Union[str, int]] = None,
message_id: Optional[int] = None,
inline_message_id: Optional[str] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -3876,7 +3896,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
async def get_chat_member(
self,
chat_id: Union[str, int],
user_id: Union[str, int],
user_id: int,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -4011,9 +4031,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
@_log
async def set_game_score(
self,
user_id: Union[int, str],
user_id: int,
score: int,
chat_id: Optional[Union[str, int]] = None,
chat_id: Optional[int] = None,
message_id: Optional[int] = None,
inline_message_id: Optional[str] = None,
force: Optional[bool] = None,
@@ -4037,7 +4057,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
decrease. This can be useful when fixing mistakes or banning cheaters.
disable_edit_message (:obj:`bool`, optional): Pass :obj:`True`, if the game message
should not be automatically edited to include the current scoreboard.
chat_id (:obj:`int` | :obj:`str`, optional): Required if :paramref:`inline_message_id`
chat_id (:obj:`int`, optional): Required if :paramref:`inline_message_id`
is not specified. Unique identifier for the target chat.
message_id (:obj:`int`, optional): Required if :paramref:`inline_message_id` is not
specified. Identifier of the sent message.
@@ -4076,8 +4096,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
@_log
async def get_game_high_scores(
self,
user_id: Union[int, str],
chat_id: Optional[Union[str, int]] = None,
user_id: int,
chat_id: Optional[int] = None,
message_id: Optional[int] = None,
inline_message_id: Optional[str] = None,
*,
@@ -4101,7 +4121,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
Args:
user_id (:obj:`int`): Target user id.
chat_id (:obj:`int` | :obj:`str`, optional): Required if :paramref:`inline_message_id`
chat_id (:obj:`int`, optional): Required if :paramref:`inline_message_id`
is not specified. Unique identifier for the target chat.
message_id (:obj:`int`, optional): Required if :paramref:`inline_message_id` is not
specified. Identifier of the sent message.
@@ -4156,7 +4176,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
is_flexible: Optional[bool] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
provider_data: Optional[Union[str, object]] = None,
send_phone_number_to_provider: Optional[bool] = None,
send_email_to_provider: Optional[bool] = None,
@@ -4317,11 +4337,11 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
)
@_log
async def answer_shipping_query( # pylint: disable=invalid-name
async def answer_shipping_query(
self,
shipping_query_id: str,
ok: bool,
shipping_options: Optional[Sequence[ShippingOption]] = None,
shipping_options: Optional[Sequence["ShippingOption"]] = None,
error_message: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -4376,7 +4396,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
)
@_log
async def answer_pre_checkout_query( # pylint: disable=invalid-name
async def answer_pre_checkout_query(
self,
pre_checkout_query_id: str,
ok: bool,
@@ -4483,7 +4503,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
async def restrict_chat_member(
self,
chat_id: Union[str, int],
user_id: Union[str, int],
user_id: int,
permissions: ChatPermissions,
until_date: Optional[Union[int, datetime]] = None,
use_independent_chat_permissions: Optional[bool] = None,
@@ -4557,7 +4577,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
async def promote_chat_member(
self,
chat_id: Union[str, int],
user_id: Union[str, int],
user_id: int,
can_change_info: Optional[bool] = None,
can_post_messages: Optional[bool] = None,
can_edit_messages: Optional[bool] = None,
@@ -4570,6 +4590,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
can_manage_chat: Optional[bool] = None,
can_manage_video_chats: Optional[bool] = None,
can_manage_topics: Optional[bool] = None,
can_post_stories: Optional[bool] = None,
can_edit_stories: Optional[bool] = None,
can_delete_stories: Optional[bool] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -4591,10 +4614,10 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
user_id (:obj:`int`): Unique identifier of the target user.
is_anonymous (:obj:`bool`, optional): Pass :obj:`True`, if the administrator's presence
in the chat is hidden.
can_manage_chat (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
access the chat event log, chat statistics, message statistics in channels, see
channel members, see anonymous administrators in supergroups and ignore slow mode.
Implied by any other administrator privilege.
can_manage_chat (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
access the chat event log, chat statistics, boost list in channels, see channel
members, report spam messages, see anonymous administrators in supergroups and
ignore slow mode. Implied by any other administrator privilege.
.. versionadded:: 13.4
@@ -4606,7 +4629,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
can_change_info (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
change chat title, photo and other settings.
can_post_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
create channel posts, channels only.
post messages in the channel, or access channel statistics; channels only.
can_edit_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
edit messages of other users and can pin messages, channels only.
can_delete_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
@@ -4614,7 +4637,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
can_invite_users (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
invite new users to the chat.
can_restrict_members (:obj:`bool`, optional): Pass :obj:`True`, if the administrator
can restrict, ban or unban chat members.
can restrict, ban or unban chat members, or access supergroup statistics.
can_pin_messages (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
pin messages, supergroups only.
can_promote_members (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
@@ -4625,6 +4648,18 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
allowed to create, rename, close, and reopen forum topics; supergroups only.
.. versionadded:: 20.0
can_post_stories (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
post stories in the channel; channels only.
.. versionadded:: 20.6
can_edit_stories (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
edit stories posted by other users; channels only.
.. versionadded:: 20.6
can_delete_stories (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
delete stories posted by other users; channels only.
.. versionadded:: 20.6
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
@@ -4648,6 +4683,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"can_manage_chat": can_manage_chat,
"can_manage_video_chats": can_manage_video_chats,
"can_manage_topics": can_manage_topics,
"can_post_stories": can_post_stories,
"can_edit_stories": can_edit_stories,
"can_delete_stories": can_delete_stories,
}
return await self._post(
@@ -4723,7 +4761,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
async def set_chat_administrator_custom_title(
self,
chat_id: Union[int, str],
user_id: Union[int, str],
user_id: int,
custom_title: str,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -5478,7 +5516,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
@_log
async def upload_sticker_file(
self,
user_id: Union[str, int],
user_id: int,
sticker: Optional[FileInput],
sticker_format: Optional[str],
*,
@@ -5538,9 +5576,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
@_log
async def add_sticker_to_set(
self,
user_id: Union[str, int],
user_id: int,
name: str,
sticker: Optional[InputSticker],
sticker: Optional["InputSticker"],
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -5636,10 +5674,10 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
@_log
async def create_new_sticker_set(
self,
user_id: Union[str, int],
user_id: int,
name: str,
title: str,
stickers: Optional[Sequence[InputSticker]],
stickers: Optional[Sequence["InputSticker"]],
sticker_format: Optional[str],
sticker_type: Optional[str] = None,
needs_repainting: Optional[bool] = None,
@@ -5807,7 +5845,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
async def set_sticker_set_thumbnail(
self,
name: str,
user_id: Union[str, int],
user_id: int,
thumbnail: Optional[FileInput] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -6079,8 +6117,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
@_log
async def set_passport_data_errors(
self,
user_id: Union[str, int],
errors: Sequence[PassportElementError],
user_id: int,
errors: Sequence["PassportElementError"],
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -6262,7 +6300,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
self,
chat_id: Union[int, str],
message_id: int,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -7843,14 +7881,6 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
return data
def __eq__(self, other: object) -> bool:
if isinstance(other, self.__class__):
return self.bot == other.bot
return False
def __hash__(self) -> int:
return hash((self.__class__, self.bot))
# camelCase aliases
getMe = get_me
"""Alias for :meth:`get_me`"""
+3 -3
View File
@@ -127,7 +127,7 @@ class CallbackQuery(TelegramObject):
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.id: str = id # pylint: disable=invalid-name
self.id: str = id
self.from_user: User = from_user
self.chat_instance: str = chat_instance
# Optionals
@@ -531,7 +531,7 @@ class CallbackQuery(TelegramObject):
async def set_game_score(
self,
user_id: Union[int, str],
user_id: int,
score: int,
force: Optional[bool] = None,
disable_edit_message: Optional[bool] = None,
@@ -589,7 +589,7 @@ class CallbackQuery(TelegramObject):
async def get_game_high_scores(
self,
user_id: Union[int, str],
user_id: int,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
+15 -6
View File
@@ -374,7 +374,7 @@ class Chat(TelegramObject):
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.id: int = id # pylint: disable=invalid-name
self.id: int = id
self.type: str = enum.get_member(constants.ChatType, type, type)
# Optionals
self.title: Optional[str] = title
@@ -677,7 +677,7 @@ class Chat(TelegramObject):
async def get_member(
self,
user_id: Union[str, int],
user_id: int,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -707,7 +707,7 @@ class Chat(TelegramObject):
async def ban_member(
self,
user_id: Union[str, int],
user_id: int,
revoke_messages: Optional[bool] = None,
until_date: Optional[Union[int, datetime]] = None,
*,
@@ -877,7 +877,7 @@ class Chat(TelegramObject):
async def unban_member(
self,
user_id: Union[str, int],
user_id: int,
only_if_banned: Optional[bool] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -909,7 +909,7 @@ class Chat(TelegramObject):
async def promote_member(
self,
user_id: Union[str, int],
user_id: int,
can_change_info: Optional[bool] = None,
can_post_messages: Optional[bool] = None,
can_edit_messages: Optional[bool] = None,
@@ -922,6 +922,9 @@ class Chat(TelegramObject):
can_manage_chat: Optional[bool] = None,
can_manage_video_chats: Optional[bool] = None,
can_manage_topics: Optional[bool] = None,
can_post_stories: Optional[bool] = None,
can_edit_stories: Optional[bool] = None,
can_delete_stories: Optional[bool] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -941,6 +944,9 @@ class Chat(TelegramObject):
The argument ``can_manage_voice_chats`` was renamed to
:paramref:`~telegram.Bot.promote_chat_member.can_manage_video_chats` in accordance to
Bot API 6.0.
.. versionchanged:: 20.6
The arguments `can_post_stories`, `can_edit_stories` and `can_delete_stories` were
added.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
@@ -966,11 +972,14 @@ class Chat(TelegramObject):
can_manage_chat=can_manage_chat,
can_manage_video_chats=can_manage_video_chats,
can_manage_topics=can_manage_topics,
can_post_stories=can_post_stories,
can_edit_stories=can_edit_stories,
can_delete_stories=can_delete_stories,
)
async def restrict_member(
self,
user_id: Union[str, int],
user_id: int,
permissions: ChatPermissions,
until_date: Optional[Union[int, datetime]] = None,
use_independent_chat_permissions: Optional[bool] = None,
+55 -16
View File
@@ -31,26 +31,31 @@ class ChatAdministratorRights(TelegramObject):
:attr:`can_delete_messages`, :attr:`can_manage_video_chats`, :attr:`can_restrict_members`,
:attr:`can_promote_members`, :attr:`can_change_info`, :attr:`can_invite_users`,
:attr:`can_post_messages`, :attr:`can_edit_messages`, :attr:`can_pin_messages`,
:attr:`can_manage_topics` are equal.
:attr:`can_manage_topics`, :attr:`can_post_stories`, :attr:`can_delete_stories`, and
:attr:`can_edit_stories` are equal.
.. versionadded:: 20.0
.. versionchanged:: 20.0
:attr:`can_manage_topics` is considered as well when comparing objects of
this type in terms of equality.
.. versionadded:: 20.0
.. versionchanged:: 20.6
:attr:`can_post_stories`, :attr:`can_edit_stories`, and :attr:`can_delete_stories` are
considered as well when comparing objects of this type in terms of equality.
Args:
is_anonymous (:obj:`bool`): :obj:`True`, if the user's presence in the chat is hidden.
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator can access the chat event
log, chat statistics, message statistics in channels, see channel members, see
anonymous administrators in supergroups and ignore slow mode. Implied by any other
administrator privilege.
log, chat statistics, boost list in channels, see channel members, report spam
messages, see anonymous administrators in supergroups and ignore slow mode.
Implied by any other administrator privilege.
can_delete_messages (:obj:`bool`): :obj:`True`, if the administrator can delete messages of
other users.
can_manage_video_chats (:obj:`bool`): :obj:`True`, if the administrator can manage video
chats.
can_restrict_members (:obj:`bool`): :obj:`True`, if the administrator can restrict, ban or
unban chat members.
unban chat members, or access supergroup statistics.
can_promote_members (:obj:`bool`): :obj:`True`, if the administrator can add new
administrators with a subset of their own privileges or demote administrators
that they have promoted, directly or indirectly (promoted by administrators that
@@ -60,11 +65,23 @@ class ChatAdministratorRights(TelegramObject):
can_invite_users (:obj:`bool`): :obj:`True`, if the user is allowed to invite new users to
the chat.
can_post_messages (:obj:`bool`, optional): :obj:`True`, if the administrator can post
messages in the channel; channels only.
messages in the channel, or access channel statistics; channels only.
can_edit_messages (:obj:`bool`, optional): :obj:`True`, if the administrator can edit
messages of other users.
can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to pin
messages; groups and supergroups only.
can_post_stories (:obj:`bool`, optional): :obj:`True`, if the administrator can post
stories in the channel; channels only.
.. versionadded:: 20.6
can_edit_stories (:obj:`bool`, optional): :obj:`True`, if the administrator can edit
stories posted by other users; channels only.
.. versionadded:: 20.6
can_delete_stories (:obj:`bool`, optional): :obj:`True`, if the administrator can delete
stories posted by other users; channels only.
.. versionadded:: 20.6
can_manage_topics (:obj:`bool`, optional): :obj:`True`, if the user is allowed
to create, rename, close, and reopen forum topics; supergroups only.
@@ -73,15 +90,15 @@ class ChatAdministratorRights(TelegramObject):
Attributes:
is_anonymous (:obj:`bool`): :obj:`True`, if the user's presence in the chat is hidden.
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator can access the chat event
log, chat statistics, message statistics in channels, see channel members, see
anonymous administrators in supergroups and ignore slow mode. Implied by any other
administrator privilege.
log, chat statistics, boost list in channels, see channel members, report spam
messages, see anonymous administrators in supergroups and ignore slow mode.
Implied by any other administrator privilege.
can_delete_messages (:obj:`bool`): :obj:`True`, if the administrator can delete messages of
other users.
can_manage_video_chats (:obj:`bool`): :obj:`True`, if the administrator can manage video
chats.
can_restrict_members (:obj:`bool`): :obj:`True`, if the administrator can restrict, ban or
unban chat members.
unban chat members, or access supergroup statistics.
can_promote_members (:obj:`bool`): :obj:`True`, if the administrator can add new
administrators with a subset of their own privileges or demote administrators that he
has promoted, directly or indirectly (promoted by administrators that were appointed by
@@ -91,11 +108,23 @@ class ChatAdministratorRights(TelegramObject):
can_invite_users (:obj:`bool`): :obj:`True`, if the user is allowed to invite new users to
the chat.
can_post_messages (:obj:`bool`): Optional. :obj:`True`, if the administrator can post
messages in the channel; channels only.
messages in the channel, or access channel statistics; channels only.
can_edit_messages (:obj:`bool`): Optional. :obj:`True`, if the administrator can edit
messages of other users.
can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to pin
messages; groups and supergroups only.
can_post_stories (:obj:`bool`): Optional. :obj:`True`, if the administrator can post
stories in the channel; channels only.
.. versionadded:: 20.6
can_edit_stories (:obj:`bool`): Optional. :obj:`True`, if the administrator can edit
stories posted by other users; channels only.
.. versionadded:: 20.6
can_delete_stories (:obj:`bool`): Optional. :obj:`True`, if the administrator can delete
stories posted by other users; channels only.
.. versionadded:: 20.6
can_manage_topics (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
to create, rename, close, and reopen forum topics; supergroups only.
@@ -115,6 +144,9 @@ class ChatAdministratorRights(TelegramObject):
"can_edit_messages",
"can_pin_messages",
"can_manage_topics",
"can_post_stories",
"can_edit_stories",
"can_delete_stories",
)
def __init__(
@@ -131,6 +163,9 @@ class ChatAdministratorRights(TelegramObject):
can_edit_messages: Optional[bool] = None,
can_pin_messages: Optional[bool] = None,
can_manage_topics: Optional[bool] = None,
can_post_stories: Optional[bool] = None,
can_edit_stories: Optional[bool] = None,
can_delete_stories: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
@@ -148,6 +183,9 @@ class ChatAdministratorRights(TelegramObject):
self.can_post_messages: Optional[bool] = can_post_messages
self.can_edit_messages: Optional[bool] = can_edit_messages
self.can_pin_messages: Optional[bool] = can_pin_messages
self.can_post_stories: Optional[bool] = can_post_stories
self.can_edit_stories: Optional[bool] = can_edit_stories
self.can_delete_stories: Optional[bool] = can_delete_stories
self.can_manage_topics: Optional[bool] = can_manage_topics
self._id_attrs = (
@@ -163,6 +201,9 @@ class ChatAdministratorRights(TelegramObject):
self.can_edit_messages,
self.can_pin_messages,
self.can_manage_topics,
self.can_post_stories,
self.can_edit_stories,
self.can_delete_stories,
)
self._freeze()
@@ -176,7 +217,7 @@ class ChatAdministratorRights(TelegramObject):
.. versionadded:: 20.0
"""
return cls(True, True, True, True, True, True, True, True, True, True, True, True)
return cls(*(True,) * len(cls.__slots__))
@classmethod
def no_rights(cls) -> "ChatAdministratorRights":
@@ -186,6 +227,4 @@ class ChatAdministratorRights(TelegramObject):
.. versionadded:: 20.0
"""
return cls(
False, False, False, False, False, False, False, False, False, False, False, False
)
return cls(*(False,) * len(cls.__slots__))
+1 -1
View File
@@ -63,7 +63,7 @@ class ChatJoinRequest(TelegramObject):
request. This number may have more than 32 significant bits and some programming
languages may have difficulty/silent defects in interpreting it. But it has at most 52
significant bits, so a 64-bit integer or double-precision float type are safe for
storing this identifier. The bot can use this identifier for 24 hours to send messages
storing this identifier. The bot can use this identifier for 5 minutes to send messages
until the join request is processed, assuming no other administrator contacted the
user.
+42 -7
View File
@@ -218,12 +218,25 @@ class ChatMemberAdministrator(ChatMember):
can_invite_users (:obj:`bool`): :obj:`True`, if the user can invite
new users to the chat.
can_post_messages (:obj:`bool`, optional): :obj:`True`, if the
administrator can post in the channel, channels only.
administrator can post messages in the channel, or access channel statistics; channels
only.
can_edit_messages (:obj:`bool`, optional): :obj:`True`, if the
administrator can edit messages of other users and can pin
messages; channels only.
can_pin_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed
to pin messages; groups and supergroups only.
can_post_stories (:obj:`bool`, optional): :obj:`True`, if the administrator can post
stories in the channel; channels only.
.. versionadded:: 20.6
can_edit_stories (:obj:`bool`, optional): :obj:`True`, if the administrator can edit
stories posted by other users; channels only.
.. versionadded:: 20.6
can_delete_stories (:obj:`bool`, optional): :obj:`True`, if the administrator can delete
stories posted by other users; channels only.
.. versionadded:: 20.6
can_manage_topics (:obj:`bool`, optional): :obj:`True`, if the user is allowed
to create, rename, close, and reopen forum topics; supergroups only.
@@ -238,10 +251,10 @@ class ChatMemberAdministrator(ChatMember):
is allowed to edit administrator privileges of that user.
is_anonymous (:obj:`bool`): :obj:`True`, if the user's
presence in the chat is hidden.
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator
can access the chat event log, chat statistics, message statistics in
channels, see channel members, see anonymous administrators in supergroups
and ignore slow mode. Implied by any other administrator privilege.
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator can access the chat event
log, chat statistics, boost list in channels, see channel members, report spam
messages, see anonymous administrators in supergroups and ignore slow mode.
Implied by any other administrator privilege.
can_delete_messages (:obj:`bool`): :obj:`True`, if the
administrator can delete messages of other users.
can_manage_video_chats (:obj:`bool`): :obj:`True`, if the
@@ -249,7 +262,7 @@ class ChatMemberAdministrator(ChatMember):
.. versionadded:: 20.0
can_restrict_members (:obj:`bool`): :obj:`True`, if the
administrator can restrict, ban or unban chat members.
administrator can restrict, ban or unban chat members, or access supergroup statistics.
can_promote_members (:obj:`bool`): :obj:`True`, if the administrator can add new
administrators with a subset of their own privileges or demote administrators
that they have promoted, directly or indirectly (promoted by administrators that
@@ -259,12 +272,25 @@ class ChatMemberAdministrator(ChatMember):
can_invite_users (:obj:`bool`): :obj:`True`, if the user can invite
new users to the chat.
can_post_messages (:obj:`bool`): Optional. :obj:`True`, if the
administrator can post in the channel, channels only.
administrator can post messages in the channel or access channel statistics;
channels only.
can_edit_messages (:obj:`bool`): Optional. :obj:`True`, if the
administrator can edit messages of other users and can pin
messages; channels only.
can_pin_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
to pin messages; groups and supergroups only.
can_post_stories (:obj:`bool`): Optional. :obj:`True`, if the administrator can post
stories in the channel; channels only.
.. versionadded:: 20.6
can_edit_stories (:obj:`bool`): Optional. :obj:`True`, if the administrator can edit
stories posted by other users; channels only.
.. versionadded:: 20.6
can_delete_stories (:obj:`bool`): Optional. :obj:`True`, if the administrator can delete
stories posted by other users; channels only.
.. versionadded:: 20.6
can_manage_topics (:obj:`bool`): Optional. :obj:`True`, if the user is allowed
to create, rename, close, and reopen forum topics; supergroups only
@@ -287,6 +313,9 @@ class ChatMemberAdministrator(ChatMember):
"can_pin_messages",
"can_manage_topics",
"custom_title",
"can_post_stories",
"can_edit_stories",
"can_delete_stories",
)
def __init__(
@@ -306,6 +335,9 @@ class ChatMemberAdministrator(ChatMember):
can_pin_messages: Optional[bool] = None,
can_manage_topics: Optional[bool] = None,
custom_title: Optional[str] = None,
can_post_stories: Optional[bool] = None,
can_edit_stories: Optional[bool] = None,
can_delete_stories: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -325,6 +357,9 @@ class ChatMemberAdministrator(ChatMember):
self.can_pin_messages: Optional[bool] = can_pin_messages
self.can_manage_topics: Optional[bool] = can_manage_topics
self.custom_title: Optional[str] = custom_title
self.can_post_stories: Optional[bool] = can_post_stories
self.can_edit_stories: Optional[bool] = can_edit_stories
self.can_delete_stories: Optional[bool] = can_delete_stories
class ChatMemberMember(ChatMember):
+1 -1
View File
@@ -111,7 +111,7 @@ class InlineQuery(TelegramObject):
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.id: str = id # pylint: disable=invalid-name
self.id: str = id
self.from_user: User = from_user
self.query: str = query
self.offset: str = offset
+1 -1
View File
@@ -60,7 +60,7 @@ class InlineQueryResult(TelegramObject):
# Required
self.type: str = type
self.id: str = str(id) # pylint: disable=invalid-name
self.id: str = str(id)
self._id_attrs = (self.id,)
+1 -1
View File
@@ -42,7 +42,7 @@ class InlineQueryResultsButton(TelegramObject):
`Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the
user presses the button. The Web App will be able to switch back to the inline mode
using the method
`switchInlineQuery <https://core.telegram.org/bots/webapps#initializing-web-apps>`_
`switchInlineQuery <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_
inside the Web App.
start_parameter (:obj:`str`, optional): Deep-linking parameter for the
:guilabel:`/start` message sent to the bot when user presses the switch button.
+2 -2
View File
@@ -86,7 +86,7 @@ class LoginUrl(TelegramObject):
def __init__(
self,
url: str,
forward_text: Optional[bool] = None,
forward_text: Optional[str] = None,
bot_username: Optional[str] = None,
request_write_access: Optional[bool] = None,
*,
@@ -96,7 +96,7 @@ class LoginUrl(TelegramObject):
# Required
self.url: str = url
# Optional
self.forward_text: Optional[bool] = forward_text
self.forward_text: Optional[str] = forward_text
self.bot_username: Optional[str] = bot_username
self.request_write_access: Optional[bool] = request_write_access
+13 -10
View File
@@ -330,7 +330,10 @@ class Message(TelegramObject):
.. versionadded:: 20.0
write_access_allowed (:class:`telegram.WriteAccessAllowed`, optional): Service message:
the user allowed the bot added to the attachment menu to write messages.
the user allowed the bot to write messages after adding it to the attachment or side
menu, launching a Web App from a link, or accepting an explicit request from a Web App
sent by the method
`requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_.
.. versionadded:: 20.0
has_media_spoiler (:obj:`bool`, optional): :obj:`True`, if the message media is covered
@@ -854,7 +857,7 @@ class Message(TelegramObject):
return self.chat.id
@property
def id(self) -> int: # pylint: disable=invalid-name
def id(self) -> int:
"""
:obj:`int`: Shortcut for :attr:`message_id`.
@@ -2504,7 +2507,7 @@ class Message(TelegramObject):
text: str,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
entities: Optional[Sequence["MessageEntity"]] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2550,7 +2553,7 @@ class Message(TelegramObject):
async def edit_caption(
self,
caption: Optional[str] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
*,
@@ -2597,7 +2600,7 @@ class Message(TelegramObject):
async def edit_media(
self,
media: "InputMedia",
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2681,7 +2684,7 @@ class Message(TelegramObject):
self,
latitude: Optional[float] = None,
longitude: Optional[float] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
@@ -2731,7 +2734,7 @@ class Message(TelegramObject):
async def stop_live_location(
self,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2771,7 +2774,7 @@ class Message(TelegramObject):
async def set_game_score(
self,
user_id: Union[int, str],
user_id: int,
score: int,
force: Optional[bool] = None,
disable_edit_message: Optional[bool] = None,
@@ -2816,7 +2819,7 @@ class Message(TelegramObject):
async def get_game_high_scores(
self,
user_id: Union[int, str],
user_id: int,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2886,7 +2889,7 @@ class Message(TelegramObject):
async def stop_poll(
self,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -18,7 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram EncryptedPassportElement."""
from base64 import b64decode
from typing import TYPE_CHECKING, Optional, Sequence, Tuple
from typing import TYPE_CHECKING, Optional, Sequence, Tuple, Union
from telegram._passport.credentials import decrypt_json
from telegram._passport.data import IdDocumentData, PersonalDetails, ResidentialAddress
@@ -54,7 +54,7 @@ class EncryptedPassportElement(TelegramObject):
data (:class:`telegram.PersonalDetails` | :class:`telegram.IdDocumentData` | \
:class:`telegram.ResidentialAddress` | :obj:`str`, optional):
Decrypted or encrypted data, available for "personal_details", "passport",
"driver_license", "identity_card", "identity_passport" and "address" types.
"driver_license", "identity_card", "internal_passport" and "address" types.
phone_number (:obj:`str`, optional): User's verified phone number, available only for
"phone_number" type.
email (:obj:`str`, optional): User's verified email address, available only for "email"
@@ -96,7 +96,7 @@ class EncryptedPassportElement(TelegramObject):
data (:class:`telegram.PersonalDetails` | :class:`telegram.IdDocumentData` | \
:class:`telegram.ResidentialAddress` | :obj:`str`):
Optional. Decrypted or encrypted data, available for "personal_details", "passport",
"driver_license", "identity_card", "identity_passport" and "address" types.
"driver_license", "identity_card", "internal_passport" and "address" types.
phone_number (:obj:`str`): Optional. User's verified phone number, available only for
"phone_number" type.
email (:obj:`str`): Optional. User's verified email address, available only for "email"
@@ -151,7 +151,7 @@ class EncryptedPassportElement(TelegramObject):
self,
type: str, # pylint: disable=redefined-builtin
hash: str, # pylint: disable=redefined-builtin
data: Optional[PersonalDetails] = None,
data: Optional[Union[PersonalDetails, IdDocumentData, ResidentialAddress]] = None,
phone_number: Optional[str] = None,
email: Optional[str] = None,
files: Optional[Sequence[PassportFile]] = None,
@@ -168,7 +168,7 @@ class EncryptedPassportElement(TelegramObject):
# Required
self.type: str = type
# Optionals
self.data: Optional[PersonalDetails] = data
self.data: Optional[Union[PersonalDetails, IdDocumentData, ResidentialAddress]] = data
self.phone_number: Optional[str] = phone_number
self.email: Optional[str] = email
self.files: Tuple[PassportFile, ...] = parse_sequence_arg(files)
+62 -9
View File
@@ -19,10 +19,12 @@
# pylint: disable=redefined-builtin
"""This module contains the classes that represent Telegram PassportElementError."""
from typing import Optional
from typing import List, Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
from telegram.warnings import PTBDeprecationWarning
class PassportElementError(TelegramObject):
@@ -173,23 +175,48 @@ class PassportElementErrorFiles(PassportElementError):
type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
``"utility_bill"``, ``"bank_statement"``, ``"rental_agreement"``,
``"passport_registration"``, ``"temporary_registration"``.
file_hashes (List[:obj:`str`]): List of base64-encoded file hashes.
message (:obj:`str`): Error message.
"""
__slots__ = ("file_hashes",)
__slots__ = ("_file_hashes",)
def __init__(
self, type: str, file_hashes: str, message: str, *, api_kwargs: Optional[JSONDict] = None
self,
type: str,
file_hashes: List[str],
message: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
# Required
super().__init__("files", type, message, api_kwargs=api_kwargs)
with self._unfrozen():
self.file_hashes: str = file_hashes
self._file_hashes: List[str] = file_hashes
self._id_attrs = (self.source, self.type, self.message, *tuple(file_hashes))
def to_dict(self, recursive: bool = True) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict` for details."""
data = super().to_dict(recursive)
data["file_hashes"] = self._file_hashes
return data
@property
def file_hashes(self) -> List[str]:
"""List of base64-encoded file hashes.
.. deprecated:: 20.6
This attribute will return a tuple instead of a list in future major versions.
"""
warn(
"The attribute `file_hashes` will return a tuple instead of a list in future major"
" versions.",
PTBDeprecationWarning,
stacklevel=2,
)
return self._file_hashes
class PassportElementErrorFrontSide(PassportElementError):
"""
@@ -365,23 +392,49 @@ class PassportElementErrorTranslationFiles(PassportElementError):
one of ``"passport"``, ``"driver_license"``, ``"identity_card"``,
``"internal_passport"``, ``"utility_bill"``, ``"bank_statement"``,
``"rental_agreement"``, ``"passport_registration"``, ``"temporary_registration"``.
file_hashes (List[:obj:`str`]): List of base64-encoded file hashes.
message (:obj:`str`): Error message.
"""
__slots__ = ("file_hashes",)
__slots__ = ("_file_hashes",)
def __init__(
self, type: str, file_hashes: str, message: str, *, api_kwargs: Optional[JSONDict] = None
self,
type: str,
file_hashes: List[str],
message: str,
*,
api_kwargs: Optional[JSONDict] = None,
):
# Required
super().__init__("translation_files", type, message, api_kwargs=api_kwargs)
with self._unfrozen():
self.file_hashes: str = file_hashes
self._file_hashes: List[str] = file_hashes
self._id_attrs = (self.source, self.type, self.message, *tuple(file_hashes))
def to_dict(self, recursive: bool = True) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict` for details."""
data = super().to_dict(recursive)
data["file_hashes"] = self._file_hashes
return data
@property
def file_hashes(self) -> List[str]:
"""List of base64-encoded file hashes.
.. deprecated:: 20.6
This attribute will return a tuple instead of a list in future major versions.
"""
warn(
"The attribute `file_hashes` will return a tuple instead of a list in future major"
" versions. See the stability policy:"
" https://docs.python-telegram-bot.org/en/stable/stability_policy.html",
PTBDeprecationWarning,
stacklevel=2,
)
return self._file_hashes
class PassportElementErrorUnspecified(PassportElementError):
"""
+29 -5
View File
@@ -23,6 +23,8 @@ from typing import TYPE_CHECKING, List, Optional, Tuple
from telegram._telegramobject import TelegramObject
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.warnings import warn
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import Bot, File, FileCredentials
@@ -45,6 +47,10 @@ class PassportFile(TelegramObject):
file_size (:obj:`int`): File size in bytes.
file_date (:obj:`int`): Unix time when the file was uploaded.
.. deprecated:: 20.6
This argument will only accept a datetime instead of an integer in future
major versions.
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
or reuse the file.
@@ -52,13 +58,10 @@ class PassportFile(TelegramObject):
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`): File size in bytes.
file_date (:obj:`int`): Unix time when the file was uploaded.
"""
__slots__ = (
"file_date",
"_file_date",
"file_id",
"file_size",
"_credentials",
@@ -81,7 +84,7 @@ class PassportFile(TelegramObject):
self.file_id: str = file_id
self.file_unique_id: str = file_unique_id
self.file_size: int = file_size
self.file_date: int = file_date
self._file_date: int = file_date
# Optionals
self._credentials: Optional[FileCredentials] = credentials
@@ -90,6 +93,27 @@ class PassportFile(TelegramObject):
self._freeze()
def to_dict(self, recursive: bool = True) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict` for details."""
data = super().to_dict(recursive)
data["file_date"] = self._file_date
return data
@property
def file_date(self) -> int:
""":obj:`int`: Unix time when the file was uploaded.
.. deprecated:: 20.6
This attribute will return a datetime instead of a integer in future major versions.
"""
warn(
"The attribute `file_date` will return a datetime instead of an integer in future"
" major versions.",
PTBDeprecationWarning,
stacklevel=2,
)
return self._file_date
@classmethod
def de_json_decrypted(
cls, data: Optional[JSONDict], bot: "Bot", credentials: "FileCredentials"
+2 -2
View File
@@ -56,7 +56,7 @@ class OrderInfo(TelegramObject):
name: Optional[str] = None,
phone_number: Optional[str] = None,
email: Optional[str] = None,
shipping_address: Optional[str] = None,
shipping_address: Optional[ShippingAddress] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -64,7 +64,7 @@ class OrderInfo(TelegramObject):
self.name: Optional[str] = name
self.phone_number: Optional[str] = phone_number
self.email: Optional[str] = email
self.shipping_address: Optional[str] = shipping_address
self.shipping_address: Optional[ShippingAddress] = shipping_address
self._id_attrs = (self.name, self.phone_number, self.email, self.shipping_address)
+2 -2
View File
@@ -95,7 +95,7 @@ class PreCheckoutQuery(TelegramObject):
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.id: str = id # pylint: disable=invalid-name
self.id: str = id
self.from_user: User = from_user
self.currency: str = currency
self.total_amount: int = total_amount
@@ -120,7 +120,7 @@ class PreCheckoutQuery(TelegramObject):
return super().de_json(data=data, bot=bot)
async def answer( # pylint: disable=invalid-name
async def answer(
self,
ok: bool,
error_message: Optional[str] = None,
+1 -1
View File
@@ -66,7 +66,7 @@ class ShippingOption(TelegramObject):
):
super().__init__(api_kwargs=api_kwargs)
self.id: str = id # pylint: disable=invalid-name
self.id: str = id
self.title: str = title
self.prices: Tuple[LabeledPrice, ...] = parse_sequence_arg(prices)
+4 -4
View File
@@ -21,7 +21,6 @@
from typing import TYPE_CHECKING, Optional, Sequence
from telegram._payment.shippingaddress import ShippingAddress
from telegram._payment.shippingoption import ShippingOption
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.defaultvalue import DEFAULT_NONE
@@ -29,6 +28,7 @@ from telegram._utils.types import JSONDict, ODVInput
if TYPE_CHECKING:
from telegram import Bot
from telegram._payment.shippingoption import ShippingOption
class ShippingQuery(TelegramObject):
@@ -67,7 +67,7 @@ class ShippingQuery(TelegramObject):
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.id: str = id # pylint: disable=invalid-name
self.id: str = id
self.from_user: User = from_user
self.invoice_payload: str = invoice_payload
self.shipping_address: ShippingAddress = shipping_address
@@ -89,10 +89,10 @@ class ShippingQuery(TelegramObject):
return super().de_json(data=data, bot=bot)
async def answer( # pylint: disable=invalid-name
async def answer(
self,
ok: bool,
shipping_options: Optional[Sequence[ShippingOption]] = None,
shipping_options: Optional[Sequence["ShippingOption"]] = None,
error_message: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
+8 -21
View File
@@ -29,8 +29,6 @@ from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import Bot
@@ -89,9 +87,11 @@ class PollAnswer(TelegramObject):
.. versionchanged:: 20.5
The order of :paramref:`option_ids` and :paramref:`user` is changed in
20.5 as the latter one became optional. We currently provide
backward compatibility for this but it will be removed in the future.
Please update your code to use the new order.
20.5 as the latter one became optional.
.. versionchanged:: 20.6
Backward compatiblity for changed order of :paramref:`option_ids` and :paramref:`user`
was removed.
Args:
poll_id (:obj:`str`): Unique poll identifier.
@@ -145,21 +145,8 @@ class PollAnswer(TelegramObject):
super().__init__(api_kwargs=api_kwargs)
self.poll_id: str = poll_id
self.voter_chat: Optional[Chat] = voter_chat
if isinstance(option_ids, User) or isinstance(user, tuple):
warn(
"From v20.5 the order of `option_ids` and `user` is changed as the latter one"
" became optional. Please update your code to use the new order.",
category=PTBDeprecationWarning,
stacklevel=2,
)
self.option_ids: Tuple[int, ...] = parse_sequence_arg(user)
self.user: Optional[User] = option_ids
else:
self.option_ids: Tuple[int, ...] = parse_sequence_arg( # type: ignore[no-redef]
option_ids
)
self.user: Optional[User] = user # type: ignore[no-redef]
self.option_ids: Tuple[int, ...] = parse_sequence_arg(option_ids)
self.user: Optional[User] = user
self._id_attrs = (
self.poll_id,
@@ -302,7 +289,7 @@ class Poll(TelegramObject):
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.id: str = id # pylint: disable=invalid-name
self.id: str = id
self.question: str = question
self.options: Tuple[PollOption, ...] = parse_sequence_arg(options)
self.total_voter_count: int = total_voter_count
+156 -156
View File
@@ -110,43 +110,53 @@ class TelegramObject:
# We don't do anything with api_kwargs here - see docstring of _apply_api_kwargs
self.api_kwargs: Mapping[str, Any] = MappingProxyType(api_kwargs or {})
def _freeze(self) -> None:
self._frozen = True
def __eq__(self, other: object) -> bool:
"""Compares this object with :paramref:`other` in terms of equality.
If this object and :paramref:`other` are `not` objects of the same class,
this comparison will fall back to Python's default implementation of :meth:`object.__eq__`.
Otherwise, both objects may be compared in terms of equality, if the corresponding
subclass of :class:`TelegramObject` has defined a set of attributes to compare and
the objects are considered to be equal, if all of these attributes are equal.
If the subclass has not defined a set of attributes to compare, a warning will be issued.
def _unfreeze(self) -> None:
self._frozen = False
Tip:
If instances of a class in the :mod:`telegram` module are comparable in terms of
equality, the documentation of the class will state the attributes that will be used
for this comparison.
@contextmanager
def _unfrozen(self: Tele_co) -> Iterator[Tele_co]:
"""Context manager to temporarily unfreeze the object. For internal use only.
Args:
other (:obj:`object`): The object to compare with.
Returns:
:obj:`bool`
Note:
with to._unfrozen() as other_to:
assert to is other_to
"""
self._unfreeze()
yield self
self._freeze()
if isinstance(other, self.__class__):
if not self._id_attrs:
warn(
f"Objects of type {self.__class__.__name__} can not be meaningfully tested for"
" equivalence.",
stacklevel=2,
)
if not other._id_attrs:
warn(
f"Objects of type {other.__class__.__name__} can not be meaningfully tested"
" for equivalence.",
stacklevel=2,
)
return self._id_attrs == other._id_attrs
return super().__eq__(other)
def _apply_api_kwargs(self, api_kwargs: JSONDict) -> None:
"""Loops through the api kwargs and for every key that exists as attribute of the
object (and is None), it moves the value from `api_kwargs` to the attribute.
*Edits `api_kwargs` in place!*
def __hash__(self) -> int:
"""Builds a hash value for this object such that the hash of two objects is equal if and
only if the objects are equal in terms of :meth:`__eq__`.
This method is currently only called in the unpickling process, i.e. not on "normal" init.
This is because
* automating this is tricky to get right: It should be called at the *end* of the __init__,
preferably only once at the end of the __init__ of the last child class. This could be
done via __init_subclass__, but it's hard to not destroy the signature of __init__ in the
process.
* calling it manually in every __init__ is tedious
* There probably is no use case for it anyway. If you manually initialize a TO subclass,
then you can pass everything as proper argument.
Returns:
:obj:`int`
"""
# we convert to list to ensure that the list doesn't change length while we loop
for key in list(api_kwargs.keys()):
if getattr(self, key, True) is None:
setattr(self, key, api_kwargs.pop(key))
if self._id_attrs:
return hash((self.__class__, self._id_attrs))
return super().__hash__()
def __setattr__(self, key: str, value: object) -> None:
"""Overrides :meth:`object.__setattr__` to prevent the overriding of attributes.
@@ -364,6 +374,122 @@ class TelegramObject:
self.set_bot(bot)
return result
@staticmethod
def _parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
"""Should be called by subclasses that override de_json to ensure that the input
is not altered. Whoever calls de_json might still want to use the original input
for something else.
"""
return None if data is None else data.copy()
@classmethod
def _de_json(
cls: Type[Tele_co],
data: Optional[JSONDict],
bot: "Bot",
api_kwargs: Optional[JSONDict] = None,
) -> Optional[Tele_co]:
if data is None:
return None
# try-except is significantly faster in case we already have a correct argument set
try:
obj = cls(**data, api_kwargs=api_kwargs)
except TypeError as exc:
if "__init__() got an unexpected keyword argument" not in str(exc):
raise exc
if cls.__INIT_PARAMS_CHECK is not cls:
signature = inspect.signature(cls)
cls.__INIT_PARAMS = set(signature.parameters.keys())
cls.__INIT_PARAMS_CHECK = cls
api_kwargs = api_kwargs or {}
existing_kwargs: JSONDict = {}
for key, value in data.items():
(existing_kwargs if key in cls.__INIT_PARAMS else api_kwargs)[key] = value
obj = cls(api_kwargs=api_kwargs, **existing_kwargs)
obj.set_bot(bot=bot)
return obj
@classmethod
def de_json(cls: Type[Tele_co], data: Optional[JSONDict], bot: "Bot") -> Optional[Tele_co]:
"""Converts JSON data to a Telegram object.
Args:
data (Dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with this object.
Returns:
The Telegram object.
"""
return cls._de_json(data=data, bot=bot)
@classmethod
def de_list(
cls: Type[Tele_co], data: Optional[List[JSONDict]], bot: "Bot"
) -> Tuple[Tele_co, ...]:
"""Converts a list of JSON objects to a tuple of Telegram objects.
.. versionchanged:: 20.0
* Returns a tuple instead of a list.
* Filters out any :obj:`None` values.
Args:
data (List[Dict[:obj:`str`, ...]]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with these objects.
Returns:
A tuple of Telegram objects.
"""
if not data:
return ()
return tuple(obj for obj in (cls.de_json(d, bot) for d in data) if obj is not None)
@contextmanager
def _unfrozen(self: Tele_co) -> Iterator[Tele_co]:
"""Context manager to temporarily unfreeze the object. For internal use only.
Note:
with to._unfrozen() as other_to:
assert to is other_to
"""
self._unfreeze()
yield self
self._freeze()
def _freeze(self) -> None:
self._frozen = True
def _unfreeze(self) -> None:
self._frozen = False
def _apply_api_kwargs(self, api_kwargs: JSONDict) -> None:
"""Loops through the api kwargs and for every key that exists as attribute of the
object (and is None), it moves the value from `api_kwargs` to the attribute.
*Edits `api_kwargs` in place!*
This method is currently only called in the unpickling process, i.e. not on "normal" init.
This is because
* automating this is tricky to get right: It should be called at the *end* of the __init__,
preferably only once at the end of the __init__ of the last child class. This could be
done via __init_subclass__, but it's hard to not destroy the signature of __init__ in the
process.
* calling it manually in every __init__ is tedious
* There probably is no use case for it anyway. If you manually initialize a TO subclass,
then you can pass everything as proper argument.
"""
# we convert to list to ensure that the list doesn't change length while we loop
for key in list(api_kwargs.keys()):
if getattr(self, key, True) is None:
setattr(self, key, api_kwargs.pop(key))
def _get_attrs_names(self, include_private: bool) -> Iterator[str]:
"""
Returns the names of the attributes of this object. This is used to determine which
@@ -423,84 +549,6 @@ class TelegramObject:
data.pop("_bot", None)
return data
@staticmethod
def _parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
"""Should be called by subclasses that override de_json to ensure that the input
is not altered. Whoever calls de_json might still want to use the original input
for something else.
"""
return None if data is None else data.copy()
@classmethod
def de_json(cls: Type[Tele_co], data: Optional[JSONDict], bot: "Bot") -> Optional[Tele_co]:
"""Converts JSON data to a Telegram object.
Args:
data (Dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with this object.
Returns:
The Telegram object.
"""
return cls._de_json(data=data, bot=bot)
@classmethod
def _de_json(
cls: Type[Tele_co],
data: Optional[JSONDict],
bot: "Bot",
api_kwargs: Optional[JSONDict] = None,
) -> Optional[Tele_co]:
if data is None:
return None
# try-except is significantly faster in case we already have a correct argument set
try:
obj = cls(**data, api_kwargs=api_kwargs)
except TypeError as exc:
if "__init__() got an unexpected keyword argument" not in str(exc):
raise exc
if cls.__INIT_PARAMS_CHECK is not cls:
signature = inspect.signature(cls)
cls.__INIT_PARAMS = set(signature.parameters.keys())
cls.__INIT_PARAMS_CHECK = cls
api_kwargs = api_kwargs or {}
existing_kwargs: JSONDict = {}
for key, value in data.items():
(existing_kwargs if key in cls.__INIT_PARAMS else api_kwargs)[key] = value
obj = cls(api_kwargs=api_kwargs, **existing_kwargs)
obj.set_bot(bot=bot)
return obj
@classmethod
def de_list(
cls: Type[Tele_co], data: Optional[List[JSONDict]], bot: "Bot"
) -> Tuple[Tele_co, ...]:
"""Converts a list of JSON objects to a tuple of Telegram objects.
.. versionchanged:: 20.0
* Returns a tuple instead of a list.
* Filters out any :obj:`None` values.
Args:
data (List[Dict[:obj:`str`, ...]]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with these objects.
Returns:
A tuple of Telegram objects.
"""
if not data:
return ()
return tuple(obj for obj in (cls.de_json(d, bot) for d in data) if obj is not None)
def to_json(self) -> str:
"""Gives a JSON representation of object.
@@ -596,51 +644,3 @@ class TelegramObject:
bot (:class:`telegram.Bot` | :obj:`None`): The bot instance.
"""
self._bot = bot
def __eq__(self, other: object) -> bool:
"""Compares this object with :paramref:`other` in terms of equality.
If this object and :paramref:`other` are `not` objects of the same class,
this comparison will fall back to Python's default implementation of :meth:`object.__eq__`.
Otherwise, both objects may be compared in terms of equality, if the corresponding
subclass of :class:`TelegramObject` has defined a set of attributes to compare and
the objects are considered to be equal, if all of these attributes are equal.
If the subclass has not defined a set of attributes to compare, a warning will be issued.
Tip:
If instances of a class in the :mod:`telegram` module are comparable in terms of
equality, the documentation of the class will state the attributes that will be used
for this comparison.
Args:
other (:obj:`object`): The object to compare with.
Returns:
:obj:`bool`
"""
if isinstance(other, self.__class__):
if not self._id_attrs:
warn(
f"Objects of type {self.__class__.__name__} can not be meaningfully tested for"
" equivalence.",
stacklevel=2,
)
if not other._id_attrs:
warn(
f"Objects of type {other.__class__.__name__} can not be meaningfully tested"
" for equivalence.",
stacklevel=2,
)
return self._id_attrs == other._id_attrs
return super().__eq__(other)
def __hash__(self) -> int:
"""Builds a hash value for this object such that the hash of two objects is equal if and
only if the objects are equal in terms of :meth:`__eq__`.
Returns:
:obj:`int`
"""
if self._id_attrs:
return hash((self.__class__, self._id_attrs))
return super().__hash__()
+1 -1
View File
@@ -153,7 +153,7 @@ class User(TelegramObject):
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.id: int = id # pylint: disable=invalid-name
self.id: int = id
self.first_name: str = first_name
self.is_bot: bool = is_bot
# Optionals
+4 -5
View File
@@ -53,7 +53,7 @@ def _localize(datetime: dtm.datetime, tzinfo: dtm.tzinfo) -> dtm.datetime:
def to_float_timestamp(
time_object: Union[int, float, dtm.timedelta, dtm.datetime, dtm.time],
time_object: Union[float, dtm.timedelta, dtm.datetime, dtm.time],
reference_timestamp: Optional[float] = None,
tzinfo: Optional[dtm.tzinfo] = None,
) -> float:
@@ -65,12 +65,11 @@ def to_float_timestamp(
to be in UTC, if ``bot`` is not passed or ``bot.defaults`` is :obj:`None`.
Args:
time_object (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta` | \
time_object (:obj:`float` | :obj:`datetime.timedelta` | \
:obj:`datetime.datetime` | :obj:`datetime.time`):
Time value to convert. The semantics of this parameter will depend on its type:
* :obj:`int` or :obj:`float` will be interpreted as "seconds from
:paramref:`reference_t`"
* :obj:`float` will be interpreted as "seconds from :paramref:`reference_t`"
* :obj:`datetime.timedelta` will be interpreted as
"time increment from :paramref:`reference_timestamp`"
* :obj:`datetime.datetime` will be interpreted as an absolute date/time value
@@ -148,7 +147,7 @@ def to_float_timestamp(
def to_timestamp(
dt_obj: Union[int, float, dtm.timedelta, dtm.datetime, dtm.time, None],
dt_obj: Union[float, dtm.timedelta, dtm.datetime, dtm.time, None],
reference_timestamp: Optional[float] = None,
tzinfo: Optional[dtm.tzinfo] = None,
) -> Optional[int]:
+8 -8
View File
@@ -88,6 +88,14 @@ class DefaultValue(Generic[DVType]):
def __bool__(self) -> bool:
return bool(self.value)
# This is mostly here for readability during debugging
def __str__(self) -> str:
return f"DefaultValue({self.value})"
# This is here to have the default instances nicely rendered in the docs
def __repr__(self) -> str:
return repr(self.value)
@overload
@staticmethod
def get_value(obj: "DefaultValue[OT]") -> OT:
@@ -112,14 +120,6 @@ class DefaultValue(Generic[DVType]):
"""
return obj.value if isinstance(obj, DefaultValue) else obj
# This is mostly here for readability during debugging
def __str__(self) -> str:
return f"DefaultValue({self.value})"
# This is here to have the default instances nicely rendered in the docs
def __repr__(self) -> str:
return repr(self.value)
DEFAULT_NONE: DefaultValue[None] = DefaultValue(None)
""":class:`DefaultValue`: Default :obj:`None`"""
+45
View File
@@ -0,0 +1,45 @@
#!/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 auxiliary functionality for building strings for __repr__ method.
Warning:
Contents of this module are intended to be used internally by the library and *not* by the
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
from typing import Any
def build_repr_with_selected_attrs(obj: object, **kwargs: Any) -> str:
"""Create ``__repr__`` string in the style ``Classname[arg1=1, arg2=2]``.
The square brackets emphasize the fact that an object cannot be instantiated
from this string.
Attributes that are to be used in the representation, are passed as kwargs.
"""
return (
f"{obj.__class__.__name__}"
# square brackets emphasize that an object cannot be instantiated with these params
f"[{', '.join(_stringify(name, value) for name, value in kwargs.items())}]"
)
def _stringify(key: str, val: Any) -> str:
return f"{key}={val.__qualname__ if callable(val) else val}"
+1 -1
View File
@@ -51,7 +51,7 @@ class Version(NamedTuple):
__version_info__: Final[Version] = Version(
major=20, minor=5, micro=0, releaselevel="final", serial=0
major=20, minor=6, micro=0, releaselevel="final", serial=0
)
__version__: Final[str] = str(__version_info__)
+2 -2
View File
@@ -39,12 +39,12 @@ class WebAppInfo(TelegramObject):
Args:
url (:obj:`str`): An HTTPS URL of a Web App to be opened with additional data as specified
in `Initializing Web Apps \
<https://core.telegram.org/bots/webapps#initializing-web-apps>`_.
<https://core.telegram.org/bots/webapps#initializing-mini-apps>`_.
Attributes:
url (:obj:`str`): An HTTPS URL of a Web App to be opened with additional data as specified
in `Initializing Web Apps \
<https://core.telegram.org/bots/webapps#initializing-web-apps>`_.
<https://core.telegram.org/bots/webapps#initializing-mini-apps>`_.
"""
__slots__ = ("url",)
+40 -5
View File
@@ -26,28 +26,63 @@ from telegram._utils.types import JSONDict
class WriteAccessAllowed(TelegramObject):
"""
This object represents a service message about a user allowing a bot to write messages after
adding the bot to the attachment menu or launching a Web App from a link.
adding it to the attachment menu, launching a Web App from a link, or accepting an explicit
request from a Web App sent by the method
`requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`web_app_name` is equal.
.. versionadded:: 20.0
.. versionchanged:: 20.6
Added custom equality comparison for objects of this class.
Args:
web_app_name (:obj:`str`, optional): Name of the Web App which was launched from a link.
web_app_name (:obj:`str`, optional): Name of the Web App, if the access was granted when
the Web App was launched from a link.
.. versionadded:: 20.3
from_request (:obj:`bool`, optional): :obj:`True`, if the access was granted after the user
accepted an explicit request from a Web App sent by the method
`requestWriteAccess <https://core.telegram.org/bots/webapps#initializing-mini-apps>`_.
.. versionadded:: 20.6
from_attachment_menu (:obj:`bool`, optional): :obj:`True`, if the access was granted when
the bot was added to the attachment or side menu.
.. versionadded:: 20.6
Attributes:
web_app_name (:obj:`str`): Optional. Name of the Web App which was launched from a link.
web_app_name (:obj:`str`): Optional. Name of the Web App, if the access was granted when
the Web App was launched from a link.
.. versionadded:: 20.3
from_request (:obj:`bool`): Optional. :obj:`True`, if the access was granted after the user
accepted an explicit request from a Web App.
.. versionadded:: 20.6
from_attachment_menu (:obj:`bool`): Optional. :obj:`True`, if the access was granted when
the bot was added to the attachment or side menu.
.. versionadded:: 20.6
"""
__slots__ = ("web_app_name",)
__slots__ = ("web_app_name", "from_request", "from_attachment_menu")
def __init__(
self, web_app_name: Optional[str] = None, *, api_kwargs: Optional[JSONDict] = None
self,
web_app_name: Optional[str] = None,
from_request: Optional[bool] = None,
from_attachment_menu: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.web_app_name: Optional[str] = web_app_name
self.from_request: Optional[bool] = from_request
self.from_attachment_menu: Optional[bool] = from_attachment_menu
self._id_attrs = (self.web_app_name,)
self._freeze()
+1 -1
View File
@@ -116,7 +116,7 @@ class _BotAPIVersion(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=6, minor=8)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=6, minor=9)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
+59 -48
View File
@@ -54,6 +54,7 @@ from typing import (
from telegram._update import Update
from telegram._utils.defaultvalue import DEFAULT_NONE, DEFAULT_TRUE, DefaultValue
from telegram._utils.logging import get_logger
from telegram._utils.repr import build_repr_with_selected_attrs
from telegram._utils.types import SCT, DVType, ODVInput
from telegram._utils.warnings import warn
from telegram.error import TelegramError
@@ -80,7 +81,6 @@ _AppType = TypeVar("_AppType", bound="Application") # pylint: disable=invalid-n
_STOP_SIGNAL = object()
_DEFAULT_0 = DefaultValue(0)
# Since python 3.12, the coroutine passed to create_task should not be an (async) generator. Remove
# this check when we drop support for python 3.11.
if sys.version_info >= (3, 12):
@@ -90,7 +90,6 @@ else:
_ErrorCoroType = Optional[_CoroType[RT]]
_LOGGER = get_logger(__name__)
@@ -345,11 +344,36 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
self.__update_persistence_lock = asyncio.Lock()
self.__create_task_tasks: Set[asyncio.Task] = set() # Used for awaiting tasks upon exit
def _check_initialized(self) -> None:
if not self._initialized:
raise RuntimeError(
"This Application was not initialized via `Application.initialize`!"
)
async def __aenter__(self: _AppType) -> _AppType: # noqa: PYI019
"""Simple context manager which initializes the App."""
try:
await self.initialize()
return self
except Exception as exc:
await self.shutdown()
raise exc
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
"""Shutdown the App from the context manager."""
# Make sure not to return `True` so that exceptions are not suppressed
# https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
await self.shutdown()
def __repr__(self) -> str:
"""Give a string representation of the application in the form ``Application[bot=...]``.
As this class doesn't implement :meth:`object.__str__`, the default implementation
will be used, which is equivalent to :meth:`__repr__`.
Returns:
:obj:`str`
"""
return build_repr_with_selected_attrs(self, bot=self.bot)
@property
def running(self) -> bool:
@@ -400,6 +424,27 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
"""
return self._update_processor
@staticmethod
def _raise_system_exit() -> NoReturn:
raise SystemExit
@staticmethod
def builder() -> "InitApplicationBuilder":
"""Convenience method. Returns a new :class:`telegram.ext.ApplicationBuilder`.
.. versionadded:: 20.0
"""
# Unfortunately this needs to be here due to cyclical imports
from telegram.ext import ApplicationBuilder # pylint: disable=import-outside-toplevel
return ApplicationBuilder()
def _check_initialized(self) -> None:
if not self._initialized:
raise RuntimeError(
"This Application was not initialized via `Application.initialize`!"
)
async def initialize(self) -> None:
"""Initializes the Application by initializing:
@@ -486,26 +531,6 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
self._initialized = False
async def __aenter__(self: _AppType) -> _AppType:
"""Simple context manager which initializes the App."""
try:
await self.initialize()
return self
except Exception as exc:
await self.shutdown()
raise exc
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
"""Shutdown the App from the context manager."""
# Make sure not to return `True` so that exceptions are not suppressed
# https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
await self.shutdown()
async def _initialize_persistence(self) -> None:
"""This method basically just loads all the data by awaiting the BP methods"""
if not self.persistence:
@@ -535,17 +560,6 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
persistent_data
)
@staticmethod
def builder() -> "InitApplicationBuilder":
"""Convenience method. Returns a new :class:`telegram.ext.ApplicationBuilder`.
.. versionadded:: 20.0
"""
# Unfortunately this needs to be here due to cyclical imports
from telegram.ext import ApplicationBuilder # pylint: disable=import-outside-toplevel
return ApplicationBuilder()
async def start(self) -> None:
"""Starts
@@ -912,10 +926,6 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
stop_signals=stop_signals,
)
@staticmethod
def _raise_system_exit() -> NoReturn:
raise SystemExit
def __run(
self,
updater_coroutine: Coroutine,
@@ -938,7 +948,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
warn(
f"Could not add signal handlers for the stop signals {stop_signals} due to "
f"exception `{exc!r}`. If your event loop does not implement `add_signal_handler`,"
f" please pass `stop_signals=None`.",
" please pass `stop_signals=None`.",
stacklevel=3,
)
@@ -1070,8 +1080,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
except Exception as exception:
if isinstance(exception, ApplicationHandlerStop):
warn(
"ApplicationHandlerStop is not supported with handlers "
"running non-blocking.",
"ApplicationHandlerStop is not supported with handlers running non-blocking.",
stacklevel=1,
)
@@ -1176,8 +1185,10 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
self.create_task(
coroutine,
update=update,
name=f"Application:{self.bot.id}:process_update_non_blocking"
f":{handler}",
name=(
f"Application:{self.bot.id}:process_update_non_blocking"
f":{handler}"
),
)
else:
any_blocking = True
@@ -1246,7 +1257,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica
if not self.persistence:
raise ValueError(
f"ConversationHandler {handler.name} "
f"can not be persistent if application has no persistence"
"can not be persistent if application has no persistence"
)
if self._initialized:
self.create_task(
+12
View File
@@ -21,6 +21,7 @@ from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar, Union
from telegram._utils.defaultvalue import DEFAULT_TRUE
from telegram._utils.repr import build_repr_with_selected_attrs
from telegram._utils.types import DVType
from telegram.ext._utils.types import CCT, HandlerCallback
@@ -95,6 +96,17 @@ class BaseHandler(Generic[UT, CCT], ABC):
self.callback: HandlerCallback[UT, CCT, RT] = callback
self.block: DVType[bool] = block
def __repr__(self) -> str:
"""Give a string representation of the handler in the form ``ClassName[callback=...]``.
As this class doesn't implement :meth:`object.__str__`, the default implementation
will be used, which is equivalent to :meth:`__repr__`.
Returns:
:obj:`str`
"""
return build_repr_with_selected_attrs(self, callback=self.callback.__qualname__)
@abstractmethod
def check_update(self, update: object) -> Optional[Union[bool, object]]:
"""
+18 -18
View File
@@ -48,6 +48,24 @@ class BaseUpdateProcessor(ABC):
raise ValueError("`max_concurrent_updates` must be a positive integer!")
self._semaphore = BoundedSemaphore(self.max_concurrent_updates)
async def __aenter__(self) -> "BaseUpdateProcessor":
"""Simple context manager which initializes the Processor."""
try:
await self.initialize()
return self
except Exception as exc:
await self.shutdown()
raise exc
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
"""Simple context manager which shuts down the Processor."""
await self.shutdown()
@property
def max_concurrent_updates(self) -> int:
""":obj:`int`: The maximum number of updates that can be processed concurrently."""
@@ -105,24 +123,6 @@ class BaseUpdateProcessor(ABC):
async with self._semaphore:
await self.do_process_update(update, coroutine)
async def __aenter__(self) -> "BaseUpdateProcessor":
"""Simple context manager which initializes the Processor."""
try:
await self.initialize()
return self
except Exception as exc:
await self.shutdown()
raise exc
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
"""Shutdown the Processor from the context manager."""
await self.shutdown()
class SimpleUpdateProcessor(BaseUpdateProcessor):
"""Instance of :class:`telegram.ext.BaseUpdateProcessor` that immediately awaits the
+9 -7
View File
@@ -227,14 +227,16 @@ class CallbackDataCache:
# Built a new nested list of buttons by replacing the callback data if needed
buttons = [
[
# We create a new button instead of replacing callback_data in case the
# same object is used elsewhere
InlineKeyboardButton(
btn.text,
callback_data=self.__put_button(btn.callback_data, keyboard_data),
(
# We create a new button instead of replacing callback_data in case the
# same object is used elsewhere
InlineKeyboardButton(
btn.text,
callback_data=self.__put_button(btn.callback_data, keyboard_data),
)
if btn.callback_data
else btn
)
if btn.callback_data
else btn
for btn in column
]
for column in reply_markup.inline_keyboard
+26 -1
View File
@@ -38,6 +38,7 @@ from typing import (
from telegram import Update
from telegram._utils.defaultvalue import DEFAULT_TRUE, DefaultValue
from telegram._utils.logging import get_logger
from telegram._utils.repr import build_repr_with_selected_attrs
from telegram._utils.types import DVType
from telegram._utils.warnings import warn
from telegram.ext._application import ApplicationHandlerStop
@@ -413,7 +414,7 @@ class ConversationHandler(BaseHandler[Update, CCT]):
):
warn(
f"Updates handled by {handler.__class__.__name__} only have information about "
f"the user, so this handler won't ever be triggered if `per_chat=True`."
"the user, so this handler won't ever be triggered if `per_chat=True`."
f"{per_faq_link}",
stacklevel=2,
)
@@ -440,6 +441,30 @@ class ConversationHandler(BaseHandler[Update, CCT]):
stacklevel=2,
)
def __repr__(self) -> str:
"""Give a string representation of the ConversationHandler in the form
``ConversationHandler[name=..., states={...}]``.
If there are more than 3 states, only the first 3 states are listed.
As this class doesn't implement :meth:`object.__str__`, the default implementation
will be used, which is equivalent to :meth:`__repr__`.
Returns:
:obj:`str`
"""
truncation_threshold = 3
states = dict(list(self.states.items())[:truncation_threshold])
states_string = str(states)
if len(self.states) > truncation_threshold:
states_string = states_string[:-1] + ", ...}"
return build_repr_with_selected_attrs(
self,
name=self.name,
states=states_string,
)
@property
def entry_points(self) -> List[BaseHandler[Update, CCT]]:
"""List[:class:`telegram.ext.BaseHandler`]: A list of :obj:`BaseHandler` objects that can
+19 -22
View File
@@ -104,6 +104,25 @@ class Defaults:
if value is not None:
self._api_defaults[kwarg] = value
def __hash__(self) -> int:
return hash(
(
self._parse_mode,
self._disable_notification,
self._disable_web_page_preview,
self._allow_sending_without_reply,
self._quote,
self._tzinfo,
self._block,
self._protect_content,
)
)
def __eq__(self, other: object) -> bool:
if isinstance(other, Defaults):
return all(getattr(self, attr) == getattr(other, attr) for attr in self.__slots__)
return False
@property
def api_defaults(self) -> Dict[str, Any]: # skip-cq: PY-D0003
return self._api_defaults
@@ -220,25 +239,3 @@ class Defaults:
raise AttributeError(
"You can't assign a new value to protect_content after initialization."
)
def __hash__(self) -> int:
return hash(
(
self._parse_mode,
self._disable_notification,
self._disable_web_page_preview,
self._allow_sending_without_reply,
self._quote,
self._tzinfo,
self._block,
self._protect_content,
)
)
def __eq__(self, other: object) -> bool:
if isinstance(other, Defaults):
return all(getattr(self, attr) == getattr(other, attr) for attr in self.__slots__)
return False
def __ne__(self, other: object) -> bool:
return not self == other
+50 -32
View File
@@ -63,17 +63,14 @@ from telegram import (
InlineKeyboardMarkup,
InlineQueryResultsButton,
InputMedia,
InputSticker,
Location,
MaskPosition,
MenuButton,
Message,
MessageId,
PassportElementError,
PhotoSize,
Poll,
SentWebAppMessage,
ShippingOption,
Sticker,
StickerSet,
Update,
@@ -88,6 +85,7 @@ from telegram import (
from telegram._utils.datetime import to_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.logging import get_logger
from telegram._utils.repr import build_repr_with_selected_attrs
from telegram._utils.types import (
CorrectOptionID,
DVInput,
@@ -108,8 +106,11 @@ if TYPE_CHECKING:
InputMediaDocument,
InputMediaPhoto,
InputMediaVideo,
InputSticker,
LabeledPrice,
MessageEntity,
PassportElementError,
ShippingOption,
)
from telegram.ext import BaseRateLimiter, Defaults
@@ -246,6 +247,17 @@ class ExtBot(Bot, Generic[RLARGS]):
self._callback_data_cache = CallbackDataCache(bot=self, maxsize=maxsize)
def __repr__(self) -> str:
"""Give a string representation of the bot in the form ``ExtBot[token=...]``.
As this class doesn't implement :meth:`object.__str__`, the default implementation
will be used, which is equivalent to :meth:`__repr__`.
Returns:
:obj:`str`
"""
return build_repr_with_selected_attrs(self, token=self.token)
@classmethod
def _warn(
cls, message: str, category: Type[Warning] = PTBUserWarning, stacklevel: int = 0
@@ -645,7 +657,7 @@ class ExtBot(Bot, Generic[RLARGS]):
self,
chat_id: Union[int, str],
message_id: int,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -733,9 +745,9 @@ class ExtBot(Bot, Generic[RLARGS]):
async def add_sticker_to_set(
self,
user_id: Union[str, int],
user_id: int,
name: str,
sticker: Optional[InputSticker],
sticker: Optional["InputSticker"],
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = 20,
@@ -845,7 +857,7 @@ class ExtBot(Bot, Generic[RLARGS]):
self,
shipping_query_id: str,
ok: bool,
shipping_options: Optional[Sequence[ShippingOption]] = None,
shipping_options: Optional[Sequence["ShippingOption"]] = None,
error_message: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -914,7 +926,7 @@ class ExtBot(Bot, Generic[RLARGS]):
async def ban_chat_member(
self,
chat_id: Union[str, int],
user_id: Union[str, int],
user_id: int,
until_date: Optional[Union[int, datetime]] = None,
revoke_messages: Optional[bool] = None,
*,
@@ -1047,10 +1059,10 @@ class ExtBot(Bot, Generic[RLARGS]):
async def create_new_sticker_set(
self,
user_id: Union[str, int],
user_id: int,
name: str,
title: str,
stickers: Optional[Sequence[InputSticker]],
stickers: Optional[Sequence["InputSticker"]],
sticker_format: Optional[str],
sticker_type: Optional[str] = None,
needs_repainting: Optional[bool] = None,
@@ -1329,7 +1341,7 @@ class ExtBot(Bot, Generic[RLARGS]):
message_id: Optional[int] = None,
inline_message_id: Optional[str] = None,
caption: Optional[str] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
*,
@@ -1362,7 +1374,7 @@ class ExtBot(Bot, Generic[RLARGS]):
inline_message_id: Optional[str] = None,
latitude: Optional[float] = None,
longitude: Optional[float] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
@@ -1399,7 +1411,7 @@ class ExtBot(Bot, Generic[RLARGS]):
chat_id: Optional[Union[str, int]] = None,
message_id: Optional[int] = None,
inline_message_id: Optional[str] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1455,7 +1467,7 @@ class ExtBot(Bot, Generic[RLARGS]):
inline_message_id: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
disable_web_page_preview: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
entities: Optional[Sequence["MessageEntity"]] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1554,7 +1566,7 @@ class ExtBot(Bot, Generic[RLARGS]):
async def get_chat_member(
self,
chat_id: Union[str, int],
user_id: Union[str, int],
user_id: int,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1655,8 +1667,8 @@ class ExtBot(Bot, Generic[RLARGS]):
async def get_game_high_scores(
self,
user_id: Union[int, str],
chat_id: Optional[Union[str, int]] = None,
user_id: int,
chat_id: Optional[int] = None,
message_id: Optional[int] = None,
inline_message_id: Optional[str] = None,
*,
@@ -1781,7 +1793,7 @@ class ExtBot(Bot, Generic[RLARGS]):
async def get_user_profile_photos(
self,
user_id: Union[str, int],
user_id: int,
offset: Optional[int] = None,
limit: Optional[int] = None,
*,
@@ -2032,7 +2044,7 @@ class ExtBot(Bot, Generic[RLARGS]):
async def promote_chat_member(
self,
chat_id: Union[str, int],
user_id: Union[str, int],
user_id: int,
can_change_info: Optional[bool] = None,
can_post_messages: Optional[bool] = None,
can_edit_messages: Optional[bool] = None,
@@ -2045,6 +2057,9 @@ class ExtBot(Bot, Generic[RLARGS]):
can_manage_chat: Optional[bool] = None,
can_manage_video_chats: Optional[bool] = None,
can_manage_topics: Optional[bool] = None,
can_post_stories: Optional[bool] = None,
can_edit_stories: Optional[bool] = None,
can_delete_stories: Optional[bool] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2068,6 +2083,9 @@ class ExtBot(Bot, Generic[RLARGS]):
can_manage_chat=can_manage_chat,
can_manage_video_chats=can_manage_video_chats,
can_manage_topics=can_manage_topics,
can_post_stories=can_post_stories,
can_edit_stories=can_edit_stories,
can_delete_stories=can_delete_stories,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
@@ -2100,7 +2118,7 @@ class ExtBot(Bot, Generic[RLARGS]):
async def restrict_chat_member(
self,
chat_id: Union[str, int],
user_id: Union[str, int],
user_id: int,
permissions: ChatPermissions,
until_date: Optional[Union[int, datetime]] = None,
use_independent_chat_permissions: Optional[bool] = None,
@@ -2397,11 +2415,11 @@ class ExtBot(Bot, Generic[RLARGS]):
async def send_game(
self,
chat_id: Union[int, str],
chat_id: int,
game_short_name: str,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
@@ -2450,7 +2468,7 @@ class ExtBot(Bot, Generic[RLARGS]):
is_flexible: Optional[bool] = None,
disable_notification: DVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
provider_data: Optional[Union[str, object]] = None,
send_phone_number_to_provider: Optional[bool] = None,
send_email_to_provider: Optional[bool] = None,
@@ -2958,7 +2976,7 @@ class ExtBot(Bot, Generic[RLARGS]):
async def set_chat_administrator_custom_title(
self,
chat_id: Union[int, str],
user_id: Union[int, str],
user_id: int,
custom_title: str,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -3115,9 +3133,9 @@ class ExtBot(Bot, Generic[RLARGS]):
async def set_game_score(
self,
user_id: Union[int, str],
user_id: int,
score: int,
chat_id: Optional[Union[str, int]] = None,
chat_id: Optional[int] = None,
message_id: Optional[int] = None,
inline_message_id: Optional[str] = None,
force: Optional[bool] = None,
@@ -3193,8 +3211,8 @@ class ExtBot(Bot, Generic[RLARGS]):
async def set_passport_data_errors(
self,
user_id: Union[str, int],
errors: Sequence[PassportElementError],
user_id: int,
errors: Sequence["PassportElementError"],
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -3238,7 +3256,7 @@ class ExtBot(Bot, Generic[RLARGS]):
async def set_sticker_set_thumbnail(
self,
name: str,
user_id: Union[str, int],
user_id: int,
thumbnail: Optional[FileInput] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -3296,7 +3314,7 @@ class ExtBot(Bot, Generic[RLARGS]):
chat_id: Optional[Union[str, int]] = None,
message_id: Optional[int] = None,
inline_message_id: Optional[str] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
reply_markup: Optional["InlineKeyboardMarkup"] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -3320,7 +3338,7 @@ class ExtBot(Bot, Generic[RLARGS]):
async def unban_chat_member(
self,
chat_id: Union[str, int],
user_id: Union[str, int],
user_id: int,
only_if_banned: Optional[bool] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -3449,7 +3467,7 @@ class ExtBot(Bot, Generic[RLARGS]):
async def upload_sticker_file(
self,
user_id: Union[str, int],
user_id: int,
sticker: Optional[FileInput],
sticker_format: Optional[str],
*,
+95 -65
View File
@@ -31,6 +31,7 @@ try:
except ImportError:
APS_AVAILABLE = False
from telegram._utils.repr import build_repr_with_selected_attrs
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
from telegram.ext._extbot import ExtBot
@@ -97,6 +98,27 @@ class JobQueue(Generic[CCT]):
timezone=pytz.utc, executors={"default": self._executor}
)
def __repr__(self) -> str:
"""Give a string representation of the JobQueue in the form ``JobQueue[application=...]``.
As this class doesn't implement :meth:`object.__str__`, the default implementation
will be used, which is equivalent to :meth:`__repr__`.
Returns:
:obj:`str`
"""
return build_repr_with_selected_attrs(self, application=self.application)
@property
def application(self) -> "Application[Any, CCT, Any, Any, Any, JobQueue[CCT]]":
"""The application this JobQueue is associated with."""
if self._application is None:
raise RuntimeError("No application was set for this JobQueue.")
application = self._application()
if application is not None:
return application
raise RuntimeError("The application instance is no longer alive.")
def _tz_now(self) -> datetime.datetime:
return datetime.datetime.now(self.scheduler.timezone)
@@ -107,14 +129,14 @@ class JobQueue(Generic[CCT]):
@overload
def _parse_time_input(
self,
time: Union[float, int, datetime.timedelta, datetime.datetime, datetime.time],
time: Union[float, datetime.timedelta, datetime.datetime, datetime.time],
shift_day: bool = False,
) -> datetime.datetime:
...
def _parse_time_input(
self,
time: Union[float, int, datetime.timedelta, datetime.datetime, datetime.time, None],
time: Union[float, datetime.timedelta, datetime.datetime, datetime.time, None],
shift_day: bool = False,
) -> Optional[datetime.datetime]:
if time is None:
@@ -150,16 +172,6 @@ class JobQueue(Generic[CCT]):
executors={"default": self._executor},
)
@property
def application(self) -> "Application[Any, CCT, Any, Any, Any, JobQueue[CCT]]":
"""The application this JobQueue is associated with."""
if self._application is None:
raise RuntimeError("No application was set for this JobQueue.")
application = self._application()
if application is not None:
return application
raise RuntimeError("The application instance is no longer alive.")
@staticmethod
async def job_callback(job_queue: "JobQueue[CCT]", job: "Job[CCT]") -> None:
"""This method is used as a callback for the APScheduler jobs.
@@ -766,6 +778,40 @@ class Job(Generic[CCT]):
self._job = cast("APSJob", None) # skipcq: PTC-W0052
def __getattr__(self, item: str) -> object:
try:
return getattr(self.job, item)
except AttributeError as exc:
raise AttributeError(
f"Neither 'telegram.ext.Job' nor 'apscheduler.job.Job' has attribute '{item}'"
) from exc
def __eq__(self, other: object) -> bool:
if isinstance(other, self.__class__):
return self.id == other.id
return False
def __hash__(self) -> int:
return hash(self.id)
def __repr__(self) -> str:
"""Give a string representation of the job in the form
``Job[id=..., name=..., callback=..., trigger=...]``.
As this class doesn't implement :meth:`object.__str__`, the default implementation
will be used, which is equivalent to :meth:`__repr__`.
Returns:
:obj:`str`
"""
return build_repr_with_selected_attrs(
self,
id=self.job.id,
name=self.name,
callback=self.callback.__name__,
trigger=self.job.trigger,
)
@property
def job(self) -> "APSJob":
""":class:`apscheduler.job.Job`: The APS Job this job is a wrapper for.
@@ -775,46 +821,6 @@ class Job(Generic[CCT]):
"""
return self._job
async def run(
self, application: "Application[Any, CCT, Any, Any, Any, JobQueue[CCT]]"
) -> None:
"""Executes the callback function independently of the jobs schedule. Also calls
:meth:`telegram.ext.Application.update_persistence`.
.. versionchanged:: 20.0
Calls :meth:`telegram.ext.Application.update_persistence`.
Args:
application (:class:`telegram.ext.Application`): The application this job is associated
with.
"""
# We shield the task such that the job isn't cancelled mid-run
await asyncio.shield(self._run(application))
async def _run(
self, application: "Application[Any, CCT, Any, Any, Any, JobQueue[CCT]]"
) -> None:
try:
context = application.context_types.context.from_job(self, application)
await context.refresh_data()
await self.callback(context)
except Exception as exc:
await application.create_task(
application.process_error(None, exc, job=self),
name=f"Job:{self.id}:run:process_error",
)
finally:
# This is internal logic of application - let's keep it private for now
application._mark_for_persistence_update(job=self) # pylint: disable=protected-access
def schedule_removal(self) -> None:
"""
Schedules this job for removal from the :class:`JobQueue`. It will be removed without
executing its callback function again.
"""
self.job.remove()
self._removed = True
@property
def removed(self) -> bool:
""":obj:`bool`: Whether this job is due to be removed."""
@@ -867,18 +873,42 @@ class Job(Generic[CCT]):
ext_job._job = aps_job # pylint: disable=protected-access
return ext_job
def __getattr__(self, item: str) -> object:
async def run(
self, application: "Application[Any, CCT, Any, Any, Any, JobQueue[CCT]]"
) -> None:
"""Executes the callback function independently of the jobs schedule. Also calls
:meth:`telegram.ext.Application.update_persistence`.
.. versionchanged:: 20.0
Calls :meth:`telegram.ext.Application.update_persistence`.
Args:
application (:class:`telegram.ext.Application`): The application this job is associated
with.
"""
# We shield the task such that the job isn't cancelled mid-run
await asyncio.shield(self._run(application))
async def _run(
self, application: "Application[Any, CCT, Any, Any, Any, JobQueue[CCT]]"
) -> None:
try:
return getattr(self.job, item)
except AttributeError as exc:
raise AttributeError(
f"Neither 'telegram.ext.Job' nor 'apscheduler.job.Job' has attribute '{item}'"
) from exc
context = application.context_types.context.from_job(self, application)
await context.refresh_data()
await self.callback(context)
except Exception as exc:
await application.create_task(
application.process_error(None, exc, job=self),
name=f"Job:{self.id}:run:process_error",
)
finally:
# This is internal logic of application - let's keep it private for now
application._mark_for_persistence_update(job=self) # pylint: disable=protected-access
def __eq__(self, other: object) -> bool:
if isinstance(other, self.__class__):
return self.id == other.id
return False
def __hash__(self) -> int:
return hash(self.id)
def schedule_removal(self) -> None:
"""
Schedules this job for removal from the :class:`JobQueue`. It will be removed without
executing its callback function again.
"""
self.job.remove()
self._removed = True
+32 -20
View File
@@ -37,6 +37,7 @@ from typing import (
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.logging import get_logger
from telegram._utils.repr import build_repr_with_selected_attrs
from telegram._utils.types import ODVInput
from telegram.error import InvalidToken, RetryAfter, TelegramError, TimedOut
@@ -124,6 +125,37 @@ class Updater(AsyncContextManager["Updater"]):
self.__polling_task: Optional[asyncio.Task] = None
self.__polling_cleanup_cb: Optional[Callable[[], Coroutine[Any, Any, None]]] = None
async def __aenter__(self: _UpdaterType) -> _UpdaterType: # noqa: PYI019
"""Simple context manager which initializes the Updater."""
try:
await self.initialize()
return self
except Exception as exc:
await self.shutdown()
raise exc
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
"""Shutdown the Updater from the context manager."""
# Make sure not to return `True` so that exceptions are not suppressed
# https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
await self.shutdown()
def __repr__(self) -> str:
"""Give a string representation of the updater in the form ``Updater[bot=...]``.
As this class doesn't implement :meth:`object.__str__`, the default implementation
will be used, which is equivalent to :meth:`__repr__`.
Returns:
:obj:`str`
"""
return build_repr_with_selected_attrs(self, bot=self.bot)
@property
def running(self) -> bool:
return self._running
@@ -163,26 +195,6 @@ class Updater(AsyncContextManager["Updater"]):
self._initialized = False
_LOGGER.debug("Shut down of Updater complete")
async def __aenter__(self: _UpdaterType) -> _UpdaterType:
"""Simple context manager which initializes the Updater."""
try:
await self.initialize()
return self
except Exception as exc:
await self.shutdown()
raise exc
async def __aexit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
"""Shutdown the Updater from the context manager."""
# Make sure not to return `True` so that exceptions are not suppressed
# https://docs.python.org/3/reference/datamodel.html?#object.__aexit__
await self.shutdown()
async def start_polling(
self,
poll_interval: float = 0.0,
+8 -8
View File
@@ -54,6 +54,14 @@ class TrackingDict(UserDict, Generic[_KT, _VT]):
super().__init__()
self._write_access_keys: Set[_KT] = set()
def __setitem__(self, key: _KT, value: _VT) -> None:
self.__track_write(key)
super().__setitem__(key, value)
def __delitem__(self, key: _KT) -> None:
self.__track_write(key)
super().__delitem__(key)
def __track_write(self, key: Union[_KT, Set[_KT]]) -> None:
if isinstance(key, set):
self._write_access_keys |= key
@@ -83,14 +91,6 @@ class TrackingDict(UserDict, Generic[_KT, _VT]):
# Override methods to track access
def __setitem__(self, key: _KT, value: _VT) -> None:
self.__track_write(key)
super().__setitem__(key, value)
def __delitem__(self, key: _KT) -> None:
self.__track_write(key)
super().__delitem__(key)
def update_no_track(self, mapping: Mapping[_KT, _VT]) -> None:
"""Like ``update``, but doesn't count towards write access."""
for key, value in mapping.items():
+34 -34
View File
@@ -182,6 +182,39 @@ class BaseFilter:
self._name = self.__class__.__name__ if name is None else name
self._data_filter = data_filter
def __and__(self, other: "BaseFilter") -> "BaseFilter":
return _MergedFilter(self, and_filter=other)
def __or__(self, other: "BaseFilter") -> "BaseFilter":
return _MergedFilter(self, or_filter=other)
def __xor__(self, other: "BaseFilter") -> "BaseFilter":
return _XORFilter(self, other)
def __invert__(self) -> "BaseFilter":
return _InvertedFilter(self)
def __repr__(self) -> str:
return self.name
@property
def data_filter(self) -> bool:
""":obj:`bool`: Whether this filter is a data filter."""
return self._data_filter
@data_filter.setter
def data_filter(self, value: bool) -> None:
self._data_filter = value
@property
def name(self) -> str:
""":obj:`str`: Name for this filter."""
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
def check_update( # skipcq: PYL-R0201
self, update: Update
) -> Optional[Union[bool, FilterDataDict]]:
@@ -205,39 +238,6 @@ class BaseFilter:
return True
return False
def __and__(self, other: "BaseFilter") -> "BaseFilter":
return _MergedFilter(self, and_filter=other)
def __or__(self, other: "BaseFilter") -> "BaseFilter":
return _MergedFilter(self, or_filter=other)
def __xor__(self, other: "BaseFilter") -> "BaseFilter":
return _XORFilter(self, other)
def __invert__(self) -> "BaseFilter":
return _InvertedFilter(self)
@property
def data_filter(self) -> bool:
""":obj:`bool`: Whether this filter is a data filter."""
return self._data_filter
@data_filter.setter
def data_filter(self, value: bool) -> None:
self._data_filter = value
@property
def name(self) -> str:
""":obj:`str`: Name for this filter."""
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
def __repr__(self) -> str:
return self.name
class MessageFilter(BaseFilter):
"""Base class for all Message Filters. In contrast to :class:`UpdateFilter`, the object passed
@@ -763,7 +763,7 @@ class _ChatUserBaseFilter(MessageFilter, ABC):
def name(self) -> str:
return (
f"filters.{self.__class__.__name__}("
f'{", ".join(str(s) for s in (self.usernames or self.chat_ids))})'
f"{', '.join(str(s) for s in (self.usernames or self.chat_ids))})"
)
@name.setter
+1 -2
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python
#
#! /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>
+1 -1
View File
@@ -72,7 +72,7 @@ complete and correct. To run it, export an environment variable first:
$ export TEST_OFFICIAL=true
and then run ``pytest tests/test_official.py``.
and then run ``pytest tests/test_official.py``. Note: You need py 3.10+ to run this test.
We also have another marker, ``@pytest.mark.dev``, which you can add to tests that you want to run selectively.
Use as follows:
+3 -1
View File
@@ -99,7 +99,9 @@ class TestPhotoWithoutRequest(TestPhotoBase):
assert photo.file_size in self.file_size
assert thumb.width == 90
assert thumb.height == 90
assert thumb.file_size == 1477
# File sizes don't seem to be consistent, so we use the values that we have observed
# so far
assert thumb.file_size in [1475, 1477]
def test_de_json(self, bot, photo):
json_dict = {
+1 -1
View File
@@ -103,7 +103,7 @@ class TestStickerBase:
file_size = 39518
thumb_width = 319
thumb_height = 320
thumb_file_size = 21472
thumb_file_size = 21448
type = Sticker.REGULAR
custom_emoji_id = "ThisIsSuchACustomEmojiID"
needs_repainting = True
+6 -2
View File
@@ -36,12 +36,16 @@ def inline_keyboard_button():
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
switch_inline_query_current_chat=(
TestInlineKeyboardButtonBase.switch_inline_query_current_chat
),
callback_game=TestInlineKeyboardButtonBase.callback_game,
pay=TestInlineKeyboardButtonBase.pay,
login_url=TestInlineKeyboardButtonBase.login_url,
web_app=TestInlineKeyboardButtonBase.web_app,
switch_inline_query_chosen_chat=TestInlineKeyboardButtonBase.switch_inline_query_chosen_chat, # noqa: E501
switch_inline_query_chosen_chat=(
TestInlineKeyboardButtonBase.switch_inline_query_chosen_chat
),
)
@@ -43,7 +43,9 @@ def input_invoice_message_content():
need_phone_number=TestInputInvoiceMessageContentBase.need_phone_number,
need_email=TestInputInvoiceMessageContentBase.need_email,
need_shipping_address=TestInputInvoiceMessageContentBase.need_shipping_address,
send_phone_number_to_provider=TestInputInvoiceMessageContentBase.send_phone_number_to_provider, # noqa: E501
send_phone_number_to_provider=(
TestInputInvoiceMessageContentBase.send_phone_number_to_provider
),
send_email_to_provider=TestInputInvoiceMessageContentBase.send_email_to_provider,
is_flexible=TestInputInvoiceMessageContentBase.is_flexible,
)
+5 -1
View File
@@ -468,7 +468,11 @@ class TestPassportWithoutRequest(TestPassportBase):
assert passport_data.decrypted_data
async def test_wrong_key(self, bot):
short_key = b"-----BEGIN RSA PRIVATE KEY-----\r\nMIIBOQIBAAJBAKU+OZ2jJm7sCA/ec4gngNZhXYPu+DZ/TAwSMl0W7vAPXAsLplBk\r\nO8l6IBHx8N0ZC4Bc65mO3b2G8YAzqndyqH8CAwEAAQJAWOx3jQFzeVXDsOaBPdAk\r\nYTncXVeIc6tlfUl9mOLyinSbRNCy1XicOiOZFgH1rRKOGIC1235QmqxFvdecySoY\r\nwQIhAOFeGgeX9CrEPuSsd9+kqUcA2avCwqdQgSdy2qggRFyJAiEAu7QHT8JQSkHU\r\nDELfzrzc24AhjyG0z1DpGZArM8COascCIDK42SboXj3Z2UXiQ0CEcMzYNiVgOisq\r\nBUd5pBi+2mPxAiAM5Z7G/Sv1HjbKrOGh29o0/sXPhtpckEuj5QMC6E0gywIgFY6S\r\nNjwrAA+cMmsgY0O2fAzEKkDc5YiFsiXaGaSS4eA=\r\n-----END RSA PRIVATE KEY-----"
short_key = (
b"-----BEGIN RSA PRIVATE"
b" KEY-----\r\nMIIBOQIBAAJBAKU+OZ2jJm7sCA/ec4gngNZhXYPu+DZ/TAwSMl0W7vAPXAsLplBk\r\nO8l6IBHx8N0ZC4Bc65mO3b2G8YAzqndyqH8CAwEAAQJAWOx3jQFzeVXDsOaBPdAk\r\nYTncXVeIc6tlfUl9mOLyinSbRNCy1XicOiOZFgH1rRKOGIC1235QmqxFvdecySoY\r\nwQIhAOFeGgeX9CrEPuSsd9+kqUcA2avCwqdQgSdy2qggRFyJAiEAu7QHT8JQSkHU\r\nDELfzrzc24AhjyG0z1DpGZArM8COascCIDK42SboXj3Z2UXiQ0CEcMzYNiVgOisq\r\nBUd5pBi+2mPxAiAM5Z7G/Sv1HjbKrOGh29o0/sXPhtpckEuj5QMC6E0gywIgFY6S\r\nNjwrAA+cMmsgY0O2fAzEKkDc5YiFsiXaGaSS4eA=\r\n-----END"
b" RSA PRIVATE KEY-----"
)
async with make_bot(token=bot.token, private_key=short_key) as b:
passport_data = PassportData.de_json(RAW_PASSPORT_DATA, bot=b)
with pytest.raises(PassportDecryptionError):
@@ -19,6 +19,7 @@
import pytest
from telegram import PassportElementErrorFiles, PassportElementErrorSelfie
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.slots import mro_slots
@@ -58,11 +59,11 @@ class TestPassportElementErrorFilesWithoutRequest(TestPassportElementErrorFilesB
assert isinstance(passport_element_error_files_dict, dict)
assert passport_element_error_files_dict["source"] == passport_element_error_files.source
assert passport_element_error_files_dict["type"] == passport_element_error_files.type
assert passport_element_error_files_dict["message"] == passport_element_error_files.message
assert (
passport_element_error_files_dict["file_hashes"]
== passport_element_error_files.file_hashes
)
assert passport_element_error_files_dict["message"] == passport_element_error_files.message
def test_equality(self):
a = PassportElementErrorFiles(self.type_, self.file_hashes, self.message)
@@ -87,3 +88,13 @@ class TestPassportElementErrorFilesWithoutRequest(TestPassportElementErrorFilesB
assert a != f
assert hash(a) != hash(f)
def test_file_hashes_deprecated(self, passport_element_error_files, recwarn):
passport_element_error_files.file_hashes
assert len(recwarn) == 1
assert (
"The attribute `file_hashes` will return a tuple instead of a list in future major"
" versions." in str(recwarn[0].message)
)
assert recwarn[0].category is PTBDeprecationWarning
assert recwarn[0].filename == __file__
@@ -19,6 +19,7 @@
import pytest
from telegram import PassportElementErrorSelfie, PassportElementErrorTranslationFiles
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.slots import mro_slots
@@ -68,14 +69,14 @@ class TestPassportElementErrorTranslationFilesWithoutRequest(
passport_element_error_translation_files_dict["type"]
== passport_element_error_translation_files.type
)
assert (
passport_element_error_translation_files_dict["file_hashes"]
== passport_element_error_translation_files.file_hashes
)
assert (
passport_element_error_translation_files_dict["message"]
== passport_element_error_translation_files.message
)
assert (
passport_element_error_translation_files_dict["file_hashes"]
== passport_element_error_translation_files.file_hashes
)
def test_equality(self):
a = PassportElementErrorTranslationFiles(self.type_, self.file_hashes, self.message)
@@ -100,3 +101,13 @@ class TestPassportElementErrorTranslationFilesWithoutRequest(
assert a != f
assert hash(a) != hash(f)
def test_file_hashes_deprecated(self, passport_element_error_translation_files, recwarn):
passport_element_error_translation_files.file_hashes
assert len(recwarn) == 1
assert (
"The attribute `file_hashes` will return a tuple instead of a list in future major"
" versions." in str(recwarn[0].message)
)
assert recwarn[0].category is PTBDeprecationWarning
assert recwarn[0].filename == __file__
+11
View File
@@ -19,6 +19,7 @@
import pytest
from telegram import Bot, File, PassportElementError, PassportFile
from telegram.warnings import PTBDeprecationWarning
from tests.auxil.bot_method_checks import (
check_defaults_handling,
check_shortcut_call,
@@ -88,6 +89,16 @@ class TestPassportFileWithoutRequest(TestPassportFileBase):
assert a != e
assert hash(a) != hash(e)
def test_file_date_deprecated(self, passport_file, recwarn):
passport_file.file_date
assert len(recwarn) == 1
assert (
"The attribute `file_date` will return a datetime instead of an integer in future"
" major versions." in str(recwarn[0].message)
)
assert recwarn[0].category is PTBDeprecationWarning
assert recwarn[0].filename == __file__
async def test_get_file_instance_method(self, monkeypatch, passport_file):
async def make_assertion(*_, **kwargs):
result = kwargs["file_id"] == passport_file.file_id
+4 -2
View File
@@ -301,8 +301,10 @@ class TestInvoiceWithRequest(TestInvoiceBase):
suggested_tip_amounts=self.suggested_tip_amounts,
start_parameter=self.start_parameter,
provider_data=self.provider_data,
photo_url="https://raw.githubusercontent.com/"
"python-telegram-bot/logos/master/logo/png/ptb-logo_240.png",
photo_url=(
"https://raw.githubusercontent.com/"
"python-telegram-bot/logos/master/logo/png/ptb-logo_240.png"
),
photo_size=240,
photo_width=240,
photo_height=240,
+1
View File
@@ -29,3 +29,4 @@ def env_var_2_bool(env_var: object) -> bool:
GITHUB_ACTION = os.getenv("GITHUB_ACTION", "")
TEST_WITH_OPT_DEPS = env_var_2_bool(os.getenv("TEST_WITH_OPT_DEPS", "true"))
RUN_TEST_OFFICIAL = env_var_2_bool(os.getenv("TEST_OFFICIAL"))
+11 -1
View File
@@ -18,6 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
import asyncio
import datetime
import logging
import sys
from typing import Dict, List
from uuid import uuid4
@@ -40,7 +41,7 @@ from telegram.ext.filters import MessageFilter, UpdateFilter
from tests.auxil.build_messages import DATE
from tests.auxil.ci_bots import BOT_INFO_PROVIDER
from tests.auxil.constants import PRIVATE_KEY
from tests.auxil.envvars import TEST_WITH_OPT_DEPS
from tests.auxil.envvars import RUN_TEST_OFFICIAL, TEST_WITH_OPT_DEPS
from tests.auxil.files import data_file
from tests.auxil.networking import NonchalantHttpxRequest
from tests.auxil.pytest_classes import PytestApplication, PytestBot, make_bot
@@ -50,6 +51,15 @@ if TEST_WITH_OPT_DEPS:
import pytz
# Don't collect `test_official.py` on Python 3.10- since it uses newer features like X | Y syntax.
# Docs: https://docs.pytest.org/en/7.1.x/example/pythoncollection.html#customizing-test-collection
collect_ignore = []
if sys.version_info < (3, 10):
if RUN_TEST_OFFICIAL:
logging.warning("Skipping test_official.py since it requires Python 3.10+")
collect_ignore.append("test_official.py")
# This is here instead of in setup.cfg due to https://github.com/pytest-dev/pytest/issues/8343
def pytest_runtestloop(session: pytest.Session):
session.add_marker(
+3
View File
@@ -201,6 +201,9 @@ class TestApplication:
assert isinstance(app.chat_data[1], dict)
assert isinstance(app.user_data[1], dict)
async def test_repr(self, app):
assert repr(app) == f"PytestApplication[bot={app.bot!r}]"
def test_job_queue(self, one_time_bot, app, recwarn):
expected_warning = (
"No `JobQueue` set up. To use `JobQueue`, you must install PTB via "

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