Compare commits

...

24 Commits

Author SHA1 Message Date
Bibo-Joshi 1e4f31f1bb Bump Version to v21.11.1 (#4696) 2025-03-01 12:45:22 +01:00
Bibo-Joshi 3464f24129 Fix ReadTheDocs Build (#4695) 2025-03-01 12:28:05 +01:00
Bibo-Joshi 9323caf2b8 Bump Version to v21.11 (#4694) 2025-03-01 12:03:34 +01:00
Bibo-Joshi 77c25931a9 Stabilize Linkcheck Test (#4693) 2025-03-01 11:31:41 +01:00
Poolitzer b75948ede4 Full Support for Bot API 8.3 (#4676)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
Co-authored-by: Abdelrahman Elkheir <90580077+aelkheir@users.noreply.github.com>
2025-03-01 11:13:46 +01:00
Bibo-Joshi b0d22acedb Add Bootstrapping Logic to Application.run_* (#4673) 2025-03-01 11:12:59 +01:00
Bibo-Joshi 35a48f82f4 Documentation Improvements (#4641)
Co-authored-by: Poolitzer <github@poolitzer.eu>
2025-02-27 20:18:15 +01:00
Bibo-Joshi 5d73132838 Make provider_token Argument Optional (#4689) 2025-02-26 20:57:57 +01:00
Bibo-Joshi 7c23087d08 Remove Deprecated InlineQueryResultArticle.hide_url (#4640) 2025-02-17 17:49:31 +01:00
vavasik800 2d5f4a68bb Fix a Bug in edit_user_star_subscription (#4681) 2025-02-15 16:21:33 +01:00
Bibo-Joshi f9f1533c40 Refactor Tests for TelegramObject Classes with Subclasses (#4654) 2025-02-06 12:46:33 +01:00
Bibo-Joshi dfb0ae3747 Use Fine Grained Permissions for GitHub Actions Workflows (#4668) 2025-02-02 10:24:46 +01:00
dependabot[bot] 64006aa7ae Bump actions/setup-python from 5.3.0 to 5.4.0 (#4665)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
2025-02-02 09:52:35 +01:00
dependabot[bot] 69ddc47a6e Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 (#4666)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-02 09:34:10 +01:00
Bibo-Joshi a2150b3751 Accept datetime.timedelta Input in Bot Method Parameters (#4651) 2025-02-02 09:31:18 +01:00
dependabot[bot] 79acc1ae53 Bump actions/stale from 9.0.0 to 9.1.0 (#4667)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-02 09:22:47 +01:00
dependabot[bot] 4cdb1a0cf7 Bump astral-sh/setup-uv from 5.1.0 to 5.2.2 (#4664)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-02 09:09:55 +01:00
dependabot[bot] 6319f4bae1 Bump codecov/test-results-action from 1.0.1 to 1.0.2 (#4663)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-02 09:09:13 +01:00
Harshil d7e063dbad Overhaul Admonition Insertion in Documentation (#4462)
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
2025-01-31 19:23:09 +01:00
Bibo-Joshi 5dd7b8f1e2 Extend Customization Support for Bot.base_(file_)url (#4632) 2025-01-23 06:01:27 +01:00
Bibo-Joshi 61b87ba318 Support allow_paid_broadcast in AIORateLimiter (#4627) 2025-01-23 05:59:39 +01:00
Bibo-Joshi dd592cdd7c Simplify Handling of Empty Data in TelegramObject.de_json and Friends (#4617) 2025-01-14 17:12:55 +01:00
Bibo-Joshi f57dd52100 Add BaseUpdateProcessor.current_concurrent_updates (#4626) 2025-01-14 17:00:20 +01:00
pre-commit-ci[bot] 16605c54d7 Bump pre-commit Hooks to Latest Versions (#4643)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
2025-01-07 18:11:35 +01:00
169 changed files with 5142 additions and 3233 deletions
+3 -1
View File
@@ -4,6 +4,8 @@ on:
pull_request:
types: [opened, reopened]
permissions: {}
jobs:
process-dependabot-prs:
permissions:
@@ -16,7 +18,7 @@ jobs:
- name: Fetch Dependabot metadata
id: dependabot-metadata
uses: dependabot/fetch-metadata@dbb049abf0d677abbd7f7eee0375145b417fdd34 # v2.2.0
uses: dependabot/fetch-metadata@d7267f607e9d3fb96fc2fbe83e0af444713e90b7 # v2.3.0
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
@@ -1,17 +1,23 @@
name: Test Documentation Build
name: Test Admonitions Generation
on:
pull_request:
paths:
- telegram/**
- docs/**
- .github/workflows/docs-admonitions.yml
push:
branches:
- master
permissions: {}
jobs:
test-sphinx-build:
name: test-sphinx-build
test-admonitions:
name: Test Admonitions Generation
runs-on: ${{matrix.os}}
permissions:
# for uploading artifacts
actions: write
strategy:
matrix:
python-version: ['3.10']
@@ -22,7 +28,7 @@ jobs:
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
@@ -32,17 +38,4 @@ jobs:
python -W ignore -m pip install --upgrade pip
python -W ignore -m pip install -r requirements-dev-all.txt
- name: Test autogeneration of admonitions
run: pytest -v --tb=short tests/docs/admonition_inserter.py
- name: Build docs
run: sphinx-build docs/source docs/build/html -W --keep-going -j auto
- name: Upload docs
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: HTML Docs
retention-days: 7
path: |
# Exclude the .doctrees folder and .buildinfo file from the artifact
# since they are not needed and add to the size
docs/build/html/*
!docs/build/html/.doctrees
!docs/build/html/.buildinfo
run: pytest -v --tb=short tests/docs/admonition_inserter.py
+10 -1
View File
@@ -7,6 +7,8 @@ on:
paths:
- .github/workflows/docs-linkcheck.yml
permissions: {}
jobs:
test-sphinx-build:
name: test-sphinx-linkcheck
@@ -21,7 +23,7 @@ jobs:
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
@@ -30,3 +32,10 @@ jobs:
python -W ignore -m pip install -r requirements-dev-all.txt
- name: Check Links
run: sphinx-build docs/source docs/build/html -W --keep-going -j auto -b linkcheck
- name: Upload linkcheck output
# Run also if the previous steps failed
if: always()
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: linkcheck-output
path: docs/build/html/output.*
+4 -2
View File
@@ -6,6 +6,8 @@ on:
- master
pull_request:
permissions: {}
jobs:
zizmor:
name: Security Analysis with zizmor
@@ -19,13 +21,13 @@ jobs:
with:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@887a942a15af3a7626099df99e897a18d9e5ab3a # v5.1.0
uses: astral-sh/setup-uv@4db96194c378173c656ce18a155ffc14a9fc4355 # v5.2.2
- name: Run zizmor
run: uvx zizmor --persona=pedantic --format sarif . > results.sarif
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
with:
sarif_file: results.sarif
category: zizmor
+2
View File
@@ -4,6 +4,8 @@ on:
pull_request:
types: [opened]
permissions: {}
jobs:
pre-commit-ci:
permissions:
+6
View File
@@ -4,9 +4,15 @@ on:
schedule:
- cron: '8 4 * * *'
permissions: {}
jobs:
lock:
runs-on: ubuntu-latest
permissions:
# For locking the threads
issues: write
pull-requests: write
steps:
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
with:
+9 -1
View File
@@ -4,19 +4,24 @@ on:
# manually trigger the workflow
workflow_dispatch:
permissions: {}
jobs:
build:
name: Build Distribution
runs-on: ubuntu-latest
outputs:
TAG: ${{ steps.get_tag.outputs.TAG }}
permissions:
# for uploading artifacts
actions: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: "3.x"
- name: Install pypa/build
@@ -46,6 +51,7 @@ jobs:
url: https://pypi.org/p/python-telegram-bot
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing
actions: read # for downloading artifacts
steps:
- name: Download all the dists
@@ -64,6 +70,7 @@ jobs:
permissions:
id-token: write # IMPORTANT: mandatory for sigstore
actions: write # for up/downloading artifacts
steps:
- name: Download all the dists
@@ -100,6 +107,7 @@ jobs:
permissions:
contents: write # IMPORTANT: mandatory for making GitHub Releases
actions: read # for downloading artifacts
steps:
- name: Download all the dists
+9 -1
View File
@@ -4,19 +4,24 @@ on:
# manually trigger the workflow
workflow_dispatch:
permissions: {}
jobs:
build:
name: Build Distribution
runs-on: ubuntu-latest
outputs:
TAG: ${{ steps.get_tag.outputs.TAG }}
permissions:
# for uploading artifacts
actions: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: "3.x"
- name: Install pypa/build
@@ -46,6 +51,7 @@ jobs:
url: https://test.pypi.org/p/python-telegram-bot
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing
actions: read # for downloading artifacts
steps:
- name: Download all the dists
@@ -66,6 +72,7 @@ jobs:
permissions:
id-token: write # IMPORTANT: mandatory for sigstore
actions: write # for up/downloading artifacts
steps:
- name: Download all the dists
@@ -102,6 +109,7 @@ jobs:
permissions:
contents: write # IMPORTANT: mandatory for making GitHub Releases
actions: read # for downloading artifacts
steps:
- name: Download all the dists
+6 -1
View File
@@ -3,11 +3,16 @@ on:
schedule:
- cron: '42 2 * * *'
permissions: {}
jobs:
stale:
runs-on: ubuntu-latest
permissions:
# For adding labels and closing
issues: write
steps:
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
with:
# PRs never get stale
days-before-stale: 3
+3 -1
View File
@@ -11,6 +11,8 @@ on:
# Run monday and friday morning at 03:07 - odd time to spread load on GitHub Actions
- cron: '7 3 * * 1,5'
permissions: {}
jobs:
check-conformity:
name: check-conformity
@@ -25,7 +27,7 @@ jobs:
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
+2
View File
@@ -9,6 +9,8 @@ on:
branches:
- master
permissions: {}
jobs:
test-type-completeness:
name: test-type-completeness
@@ -4,6 +4,8 @@ on:
# Run first friday of the month at 03:17 - odd time to spread load on GitHub Actions
- cron: '17 3 1-7 * 5'
permissions: {}
jobs:
test-type-completeness:
name: test-type-completeness
+4 -2
View File
@@ -14,6 +14,8 @@ on:
# Run monday and friday morning at 03:07 - odd time to spread load on GitHub Actions
- cron: '7 3 * * 1,5'
permissions: {}
jobs:
pytest:
name: pytest
@@ -28,7 +30,7 @@ jobs:
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
@@ -97,7 +99,7 @@ jobs:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload test results to Codecov
uses: codecov/test-results-action@9739113ad922ea0a9abb4b2c0f8bf6a4aa8ef820 # v1.0.1
uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2
if: ${{ !cancelled() }}
with:
files: .test_report_no_optionals_junit.xml,.test_report_optionals_junit.xml
+1
View File
@@ -67,6 +67,7 @@ docs/_build/
# PyBuilder
target/
.idea/
.run/
# Sublime Text 2
*.sublime*
+6 -6
View File
@@ -7,7 +7,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.5.6'
rev: 'v0.8.6'
hooks:
- id: ruff
name: ruff
@@ -18,18 +18,18 @@ repos:
- cachetools>=5.3.3,<5.5.0
- aiolimiter~=1.1,<1.3
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.4.2
rev: 24.10.0
hooks:
- id: black
args:
- --diff
- --check
- repo: https://github.com/PyCQA/flake8
rev: 7.1.0
rev: 7.1.1
hooks:
- id: flake8
- repo: https://github.com/PyCQA/pylint
rev: v3.3.2
rev: v3.3.3
hooks:
- id: pylint
files: ^(?!(tests|docs)).*\.py$
@@ -41,7 +41,7 @@ repos:
- aiolimiter~=1.1,<1.3
- . # this basically does `pip install -e .`
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.1
rev: v1.14.1
hooks:
- id: mypy
name: mypy-ptb
@@ -68,7 +68,7 @@ repos:
- cachetools>=5.3.3,<5.5.0
- . # this basically does `pip install -e .`
- repo: https://github.com/asottile/pyupgrade
rev: v3.16.0
rev: v3.19.1
hooks:
- id: pyupgrade
args:
+1 -1
View File
@@ -18,7 +18,7 @@ python:
install:
- method: pip
path: .
- requirements: docs/requirements-docs.txt
- requirements: requirements-dev-all.txt
build:
os: ubuntu-22.04
+1 -1
View File
@@ -117,7 +117,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `Rahiel Kasim <https://github.com/rahiel>`_
- `Riko Naka <https://github.com/rikonaka>`_
- `Rizlas <https://github.com/rizlas>`_
- `Snehashish Biswas <https://github.com/Snehashish06>`_
- Snehashish Biswas
- `Sahil Sharma <https://github.com/sahilsharma811>`_
- `Sam Mosleh <https://github.com/sam-mosleh>`_
- `Sascha <https://github.com/saschalalala>`_
+61 -1
View File
@@ -4,6 +4,66 @@
Changelog
=========
Version 21.11.1
===============
*Released 2025-03-01*
This is the technical changelog for version 21.11. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
Documentation Improvements
--------------------------
- Fix ReadTheDocs Build (:pr:`4695`)
Version 21.11
=============
*Released 2025-03-01*
This is the technical changelog for version 21.11. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
Major Changes and New Features
------------------------------
- Full Support for Bot API 8.3 (:pr:`4676` closes :issue:`4677`, :pr:`4682` by `aelkheir <https://github.com/aelkheir>`_, :pr:`4690` by `aelkheir <https://github.com/aelkheir>`_, :pr:`4691` by `aelkheir <https://github.com/aelkheir>`_)
- Make ``provider_token`` Argument Optional (:pr:`4689`)
- Remove Deprecated ``InlineQueryResultArticle.hide_url`` (:pr:`4640` closes :issue:`4638`)
- Accept ``datetime.timedelta`` Input in ``Bot`` Method Parameters (:pr:`4651`)
- Extend Customization Support for ``Bot.base_(file_)url`` (:pr:`4632` closes :issue:`3355`)
- Support ``allow_paid_broadcast`` in ``AIORateLimiter`` (:pr:`4627` closes :issue:`4578`)
- Add ``BaseUpdateProcessor.current_concurrent_updates`` (:pr:`4626` closes :issue:`3984`)
Minor Changes and Bug Fixes
---------------------------
- Add Bootstrapping Logic to ``Application.run_*`` (:pr:`4673` closes :issue:`4657`)
- Fix a Bug in ``edit_user_star_subscription`` (:pr:`4681` by `vavasik800 <https://github.com/vavasik800>`_)
- Simplify Handling of Empty Data in ``TelegramObject.de_json`` and Friends (:pr:`4617` closes :issue:`4614`)
Documentation Improvements
--------------------------
- Documentation Improvements (:pr:`4641`)
- Overhaul Admonition Insertion in Documentation (:pr:`4462` closes :issue:`4414`)
Internal Changes
----------------
- Stabilize Linkcheck Test (:pr:`4693`)
- Bump ``pre-commit`` Hooks to Latest Versions (:pr:`4643`)
- Refactor Tests for ``TelegramObject`` Classes with Subclasses (:pr:`4654` closes :issue:`4652`)
- Use Fine Grained Permissions for GitHub Actions Workflows (:pr:`4668`)
Dependency Updates
------------------
- Bump ``actions/setup-python`` from 5.3.0 to 5.4.0 (:pr:`4665`)
- Bump ``dependabot/fetch-metadata`` from 2.2.0 to 2.3.0 (:pr:`4666`)
- Bump ``actions/stale`` from 9.0.0 to 9.1.0 (:pr:`4667`)
- Bump ``astral-sh/setup-uv`` from 5.1.0 to 5.2.2 (:pr:`4664`)
- Bump ``codecov/test-results-action`` from 1.0.1 to 1.0.2 (:pr:`4663`)
Version 21.10
=============
@@ -86,7 +146,7 @@ Major Changes
Documentation Improvements
--------------------------
- Documentation Improvements (:pr:`4565` by `Snehashish06 <https://github.com/Snehashish06>`_, :pr:`4573`)
- Documentation Improvements (:pr:`4565` by Snehashish06, :pr:`4573`)
Version 21.7
============
+2 -2
View File
@@ -11,7 +11,7 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-8.2-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-8.3-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API version
@@ -81,7 +81,7 @@ After installing_ the library, be sure to check out the section on `working with
Telegram API support
~~~~~~~~~~~~~~~~~~~~
All types and methods of the Telegram Bot API **8.2** are natively supported by this library.
All types and methods of the Telegram Bot API **8.3** are natively supported by this library.
In addition, Bot API functionality not yet natively included can still be used as described `in our wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Bot-API-Forward-Compatibility>`_.
Notable Features
+201 -226
View File
@@ -16,18 +16,55 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import collections.abc
import contextlib
import inspect
import re
import typing
from collections import defaultdict
from collections.abc import Iterator
from typing import Any, Union
from socket import socket
from types import FunctionType
from typing import Union
from apscheduler.job import Job as APSJob
import telegram
import telegram._utils.defaultvalue
import telegram._utils.types
import telegram.ext
import telegram.ext._utils.types
from tests.auxil.slots import mro_slots
# Define the namespace for type resolution. This helps dealing with the internal imports that
# we do in many places
# The .copy() is important to avoid modifying the original namespace
TG_NAMESPACE = vars(telegram).copy()
TG_NAMESPACE.update(vars(telegram._utils.types))
TG_NAMESPACE.update(vars(telegram._utils.defaultvalue))
TG_NAMESPACE.update(vars(telegram.ext))
TG_NAMESPACE.update(vars(telegram.ext._utils.types))
TG_NAMESPACE.update(vars(telegram.ext._applicationbuilder))
TG_NAMESPACE.update({"socket": socket, "APSJob": APSJob})
def _iter_own_public_methods(cls: type) -> Iterator[tuple[str, type]]:
class PublicMethod(typing.NamedTuple):
name: str
method: FunctionType
def _is_inherited_method(cls: type, method_name: str) -> bool:
"""Checks if a method is inherited from a parent class.
Inheritance is not considered if the parent class is private.
Recurses through all direcot or indirect parent classes.
"""
# The [1:] slice is used to exclude the class itself from the MRO.
for base in cls.__mro__[1:]:
if method_name in base.__dict__ and not base.__name__.startswith("_"):
return True
return False
def _iter_own_public_methods(cls: type) -> Iterator[PublicMethod]:
"""Iterates over methods of a class that are not protected/private,
not camelCase and not inherited from the parent class.
@@ -35,13 +72,15 @@ def _iter_own_public_methods(cls: type) -> Iterator[tuple[str, type]]:
This function is defined outside the class because it is used to create class constants.
"""
return (
m
for m in inspect.getmembers(cls, predicate=inspect.isfunction) # not .ismethod
if not m[0].startswith("_")
and m[0].islower() # to avoid camelCase methods
and m[0] in cls.__dict__ # method is not inherited from parent class
)
# Use .isfunction() instead of .ismethod() because we want to include static methods.
for m in inspect.getmembers(cls, predicate=inspect.isfunction):
if (
not m[0].startswith("_")
and m[0].islower() # to avoid camelCase methods
and not _is_inherited_method(cls, m[0])
):
yield PublicMethod(m[0], m[1])
class AdmonitionInserter:
@@ -58,18 +97,12 @@ class AdmonitionInserter:
start and end markers.
"""
FORWARD_REF_SKIP_PATTERN = re.compile(r"^ForwardRef\('DefaultValue\[\w+]'\)$")
"""A pattern that will be used to skip known ForwardRef's that need not be resolved
to a Telegram class, e.g.:
ForwardRef('DefaultValue[None]')
ForwardRef('DefaultValue[DVValueType]')
"""
METHOD_NAMES_FOR_BOT_AND_APPBUILDER: typing.ClassVar[dict[type, str]] = {
cls: tuple(m[0] for m in _iter_own_public_methods(cls)) # m[0] means we take only names
for cls in (telegram.Bot, telegram.ext.ApplicationBuilder)
METHOD_NAMES_FOR_BOT_APP_APPBUILDER: typing.ClassVar[dict[type, str]] = {
cls: tuple(m.name for m in _iter_own_public_methods(cls))
for cls in (telegram.Bot, telegram.ext.ApplicationBuilder, telegram.ext.Application)
}
"""A dictionary mapping Bot and ApplicationBuilder classes to their relevant methods that will
"""A dictionary mapping Bot, Application & ApplicationBuilder classes to their relevant methods
that will
be mentioned in 'Returned in' and 'Use in' admonitions in other classes' docstrings.
Methods must be public, not aliases, not inherited from TelegramObject.
"""
@@ -83,13 +116,20 @@ class AdmonitionInserter:
"""Dictionary with admonitions. Contains sub-dictionaries, one per admonition type.
Each sub-dictionary matches bot methods (for "Shortcuts") or telegram classes (for other
admonition types) to texts of admonitions, e.g.:
```
{
"use_in": {<class 'telegram._chatinvitelink.ChatInviteLink'>:
<"Use in" admonition for ChatInviteLink>, ...},
"available_in": {<class 'telegram._chatinvitelink.ChatInviteLink'>:
<"Available in" admonition">, ...},
"returned_in": {...}
"use_in": {
<class 'telegram._chatinvitelink.ChatInviteLink'>:
<"Use in" admonition for ChatInviteLink>,
...
},
"available_in": {
<class 'telegram._chatinvitelink.ChatInviteLink'>:
<"Available in" admonition">,
...
},
"returned_in": {...}
}
```
"""
@@ -128,34 +168,6 @@ class AdmonitionInserter:
# i.e. {telegram._files.sticker.Sticker: {":attr:`telegram.Message.sticker`", ...}}
attrs_for_class = defaultdict(set)
# The following regex is supposed to capture a class name in a line like this:
# media (:obj:`str` | :class:`telegram.InputFile`): Audio file to send.
#
# Note that even if such typing description spans over multiple lines but each line ends
# with a backslash (otherwise Sphinx will throw an error)
# (e.g. EncryptedPassportElement.data), then Sphinx will combine these lines into a single
# line automatically, and it will contain no backslash (only some extra many whitespaces
# from the indentation).
attr_docstr_pattern = re.compile(
r"^\s*(?P<attr_name>[a-z_]+)" # Any number of spaces, named group for attribute
r"\s?\(" # Optional whitespace, opening parenthesis
r".*" # Any number of characters (that could denote a built-in type)
r":(class|obj):`.+`" # Marker of a classref, class name in backticks
r".*\):" # Any number of characters, closing parenthesis, colon.
# The ^ colon above along with parenthesis is important because it makes sure that
# the class is mentioned in the attribute description, not in free text.
r".*$", # Any number of characters, end of string (end of line)
re.VERBOSE,
)
# for properties: there is no attr name in docstring. Just check if there's a class name.
prop_docstring_pattern = re.compile(r":(class|obj):`.+`.*:")
# pattern for iterating over potentially many class names in docstring for one attribute.
# Tilde is optional (sometimes it is in the docstring, sometimes not).
single_class_name_pattern = re.compile(r":(class|obj):`~?(?P<class_name>[\w.]*)`")
classes_to_inspect = inspect.getmembers(telegram, inspect.isclass) + inspect.getmembers(
telegram.ext, inspect.isclass
)
@@ -166,40 +178,31 @@ class AdmonitionInserter:
# docstrings.
name_of_inspected_class_in_docstr = self._generate_class_name_for_link(inspected_class)
# Parsing part of the docstring with attributes (parsing of properties follows later)
docstring_lines = inspect.getdoc(inspected_class).splitlines()
lines_with_attrs = []
for idx, line in enumerate(docstring_lines):
if line.strip() == "Attributes:":
lines_with_attrs = docstring_lines[idx + 1 :]
break
# Writing to dictionary: matching the class found in the type hint
# and its subclasses to the attribute of the class being inspected.
# The class in the attribute typehint (or its subclass) is the key,
# ReST link to attribute of the class currently being inspected is the value.
for line in lines_with_attrs:
if not (line_match := attr_docstr_pattern.match(line)):
continue
target_attr = line_match.group("attr_name")
# a typing description of one attribute can contain multiple classes
for match in single_class_name_pattern.finditer(line):
name_of_class_in_attr = match.group("class_name")
# Writing to dictionary: matching the class found in the docstring
# and its subclasses to the attribute of the class being inspected.
# The class in the attribute docstring (or its subclass) is the key,
# ReST link to attribute of the class currently being inspected is the value.
try:
self._resolve_arg_and_add_link(
arg=name_of_class_in_attr,
dict_of_methods_for_class=attrs_for_class,
link=f":attr:`{name_of_inspected_class_in_docstr}.{target_attr}`",
)
except NotImplementedError as e:
raise NotImplementedError(
"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. {e!s}"
) from e
# best effort - args of __init__ means not all attributes are covered, but there is no
# other way to get type hints of all attributes, other than doing ast parsing maybe.
# (Docstring parsing was discontinued with the closing of #4414)
type_hints = typing.get_type_hints(inspected_class.__init__, localns=TG_NAMESPACE)
class_attrs = [slot for slot in mro_slots(inspected_class) if not slot.startswith("_")]
for target_attr in class_attrs:
try:
self._resolve_arg_and_add_link(
dict_of_methods_for_class=attrs_for_class,
link=f":attr:`{name_of_inspected_class_in_docstr}.{target_attr}`",
type_hints={target_attr: type_hints.get(target_attr)},
resolve_nested_type_vars=False,
)
except NotImplementedError as e:
raise NotImplementedError(
"Error generating Sphinx 'Available in' admonition "
f"(admonition_inserter.py). Class {inspected_class} present in "
f"attribute {target_attr} of class {name_of_inspected_class_in_docstr}"
f" could not be resolved. {e!s}"
) from e
# Properties need to be parsed separately because they act like attributes but not
# listed as attributes.
@@ -210,39 +213,29 @@ class AdmonitionInserter:
if prop_name not in inspected_class.__dict__:
continue
# 1. Can't use typing.get_type_hints because double-quoted type hints
# (like "Application") will throw a NameError
# 2. Can't use inspect.signature because return annotations of properties can be
# hard to parse (like "(self) -> BD").
# 3. fget is used to access the actual function under the property wrapper
docstring = inspect.getdoc(getattr(inspected_class, prop_name).fget)
if docstring is None:
continue
# fget is used to access the actual function under the property wrapper
type_hints = typing.get_type_hints(
getattr(inspected_class, prop_name).fget, localns=TG_NAMESPACE
)
first_line = docstring.splitlines()[0]
if not prop_docstring_pattern.match(first_line):
continue
for match in single_class_name_pattern.finditer(first_line):
name_of_class_in_prop = match.group("class_name")
# Writing to dictionary: matching the class found in the docstring and its
# subclasses to the property of the class being inspected.
# The class in the property docstring (or its subclass) is the key,
# ReST link to property of the class currently being inspected is the value.
try:
self._resolve_arg_and_add_link(
arg=name_of_class_in_prop,
dict_of_methods_for_class=attrs_for_class,
link=f":attr:`{name_of_inspected_class_in_docstr}.{prop_name}`",
)
except NotImplementedError as e:
raise NotImplementedError(
"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. {e!s}"
) from e
# Writing to dictionary: matching the class found in the docstring and its
# subclasses to the property of the class being inspected.
# The class in the property docstring (or its subclass) is the key,
# ReST link to property of the class currently being inspected is the value.
try:
self._resolve_arg_and_add_link(
dict_of_methods_for_class=attrs_for_class,
link=f":attr:`{name_of_inspected_class_in_docstr}.{prop_name}`",
type_hints={prop_name: type_hints.get("return")},
resolve_nested_type_vars=False,
)
except NotImplementedError as e:
raise NotImplementedError(
"Error generating Sphinx 'Available in' admonition "
f"(admonition_inserter.py). Class {inspected_class} present in "
f"property {prop_name} of class {name_of_inspected_class_in_docstr}"
f" could not be resolved. {e!s}"
) from e
return self._generate_admonitions(attrs_for_class, admonition_type="available_in")
@@ -250,29 +243,28 @@ class AdmonitionInserter:
"""Creates a dictionary with 'Returned in' admonitions for classes that are returned
in Bot's and ApplicationBuilder's methods.
"""
# Generate a mapping of classes to ReST links to Bot methods which return it,
# i.e. {<class 'telegram._message.Message'>: {:meth:`telegram.Bot.send_message`, ...}}
methods_for_class = defaultdict(set)
for cls, method_names in self.METHOD_NAMES_FOR_BOT_AND_APPBUILDER.items():
for cls, method_names in self.METHOD_NAMES_FOR_BOT_APP_APPBUILDER.items():
for method_name in method_names:
sig = inspect.signature(getattr(cls, method_name))
ret_annot = sig.return_annotation
method_link = self._generate_link_to_method(method_name, cls)
arg = getattr(cls, method_name)
ret_type_hint = typing.get_type_hints(arg, localns=TG_NAMESPACE)
try:
self._resolve_arg_and_add_link(
arg=ret_annot,
dict_of_methods_for_class=methods_for_class,
link=method_link,
type_hints={"return": ret_type_hint.get("return")},
resolve_nested_type_vars=False,
)
except NotImplementedError as e:
raise NotImplementedError(
"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}. {e!s}"
f"Couldn't resolve type hint in return annotation {ret_type_hint}. {e!s}"
) from e
return self._generate_admonitions(methods_for_class, admonition_type="returned_in")
@@ -299,8 +291,13 @@ class AdmonitionInserter:
# inspect methods of all telegram classes for return statements that indicate
# that this given method is a shortcut for a Bot method
for _class_name, cls in inspect.getmembers(telegram, predicate=inspect.isclass):
# no need to inspect Bot's own methods, as Bot can't have shortcuts in Bot
if not cls.__module__.startswith("telegram"):
# For some reason inspect.getmembers() also yields some classes that are
# imported in the namespace but not part of the telegram module.
continue
if cls is telegram.Bot:
# no need to inspect Bot's own methods, as Bot can't have shortcuts in Bot
continue
for method_name, method in _iter_own_public_methods(cls):
@@ -310,9 +307,7 @@ class AdmonitionInserter:
continue
bot_method = getattr(telegram.Bot, bot_method_match.group())
link_to_shortcut_method = self._generate_link_to_method(method_name, cls)
shortcuts_for_bot_method[bot_method].add(link_to_shortcut_method)
return self._generate_admonitions(shortcuts_for_bot_method, admonition_type="shortcuts")
@@ -327,26 +322,24 @@ class AdmonitionInserter:
# {:meth:`telegram.Bot.answer_inline_query`, ...}}
methods_for_class = defaultdict(set)
for cls, method_names in self.METHOD_NAMES_FOR_BOT_AND_APPBUILDER.items():
for cls, method_names in self.METHOD_NAMES_FOR_BOT_APP_APPBUILDER.items():
for method_name in method_names:
method_link = self._generate_link_to_method(method_name, cls)
sig = inspect.signature(getattr(cls, method_name))
parameters = sig.parameters
for param in parameters.values():
try:
self._resolve_arg_and_add_link(
arg=param.annotation,
dict_of_methods_for_class=methods_for_class,
link=method_link,
)
except NotImplementedError as e:
raise NotImplementedError(
"Error generating Sphinx 'Use in' admonition "
f"(admonition_inserter.py). {cls}, method {method_name}, parameter "
f"{param}: Couldn't resolve type hint {param.annotation}. {e!s}"
) from e
arg = getattr(cls, method_name)
param_type_hints = typing.get_type_hints(arg, localns=TG_NAMESPACE)
param_type_hints.pop("return", None)
try:
self._resolve_arg_and_add_link(
dict_of_methods_for_class=methods_for_class,
link=method_link,
type_hints=param_type_hints,
)
except NotImplementedError as e:
raise NotImplementedError(
"Error generating Sphinx 'Use in' admonition "
f"(admonition_inserter.py). {cls}, method {method_name}, parameter "
) from e
return self._generate_admonitions(methods_for_class, admonition_type="use_in")
@@ -362,7 +355,7 @@ class AdmonitionInserter:
for idx, value in list(enumerate(lines)):
if value.startswith(
(
".. seealso:",
# ".. seealso:",
# The docstring contains heading "Examples:", but Sphinx will have it converted
# to ".. admonition: Examples":
".. admonition:: Examples",
@@ -435,12 +428,12 @@ class AdmonitionInserter:
return admonition_for_class
@staticmethod
def _generate_class_name_for_link(cls: type) -> str:
def _generate_class_name_for_link(cls_: type) -> str:
"""Generates class name that can be used in a ReST link."""
# Check for potential presence of ".ext.", we will need to keep it.
ext = ".ext" if ".ext." in str(cls) else ""
return f"telegram{ext}.{cls.__name__}"
ext = ".ext" if ".ext." in str(cls_) else ""
return f"telegram{ext}.{cls_.__name__}"
def _generate_link_to_method(self, method_name: str, cls: type) -> str:
"""Generates a ReST link to a method of a telegram class."""
@@ -448,19 +441,22 @@ class AdmonitionInserter:
return f":meth:`{self._generate_class_name_for_link(cls)}.{method_name}`"
@staticmethod
def _iter_subclasses(cls: type) -> Iterator:
def _iter_subclasses(cls_: type) -> Iterator:
if not hasattr(cls_, "__subclasses__") or cls_ is telegram.TelegramObject:
return iter([])
return (
# exclude private classes
c
for c in cls.__subclasses__()
for c in cls_.__subclasses__()
if not str(c).split(".")[-1].startswith("_")
)
def _resolve_arg_and_add_link(
self,
arg: Any,
dict_of_methods_for_class: defaultdict,
link: str,
type_hints: dict[str, type],
resolve_nested_type_vars: bool = True,
) -> None:
"""A helper method. Tries to resolve the arg into a valid class. In case of success,
adds the link (to a method, attribute, or property) for that class' and its subclasses'
@@ -468,7 +464,9 @@ class AdmonitionInserter:
**Modifies dictionary in place.**
"""
for cls in self._resolve_arg(arg):
type_hints.pop("self", None)
for cls in self._resolve_arg(type_hints, resolve_nested_type_vars):
# When trying to resolve an argument from args or return annotation,
# the method _resolve_arg returns None if nothing could be resolved.
# Also, if class was resolved correctly, "telegram" will definitely be in its str().
@@ -480,88 +478,67 @@ class AdmonitionInserter:
for subclass in self._iter_subclasses(cls):
dict_of_methods_for_class[subclass].add(link)
def _resolve_arg(self, arg: Any) -> Iterator[Union[type, None]]:
def _resolve_arg(
self,
type_hints: dict[str, type],
resolve_nested_type_vars: bool,
) -> list[type]:
"""Analyzes an argument of a method and recursively yields classes that the argument
or its sub-arguments (in cases like Union[...]) belong to, if they can be resolved to
telegram or telegram.ext classes.
Args:
type_hints: A dictionary of argument names and their types.
resolve_nested_type_vars: If True, nested type variables (like Application[BT, …])
will be resolved to their actual classes. If False, only the outermost type
variable will be resolved. *Only* affects ptb classes, not built-in types.
Useful for checking the return type of methods, where nested type variables
are not really useful.
Raises `NotImplementedError`.
"""
origin = typing.get_origin(arg)
def _is_ptb_class(cls: type) -> bool:
if not hasattr(cls, "__module__"):
return False
return cls.__module__.startswith("telegram")
if (
origin in (collections.abc.Callable, typing.IO)
or arg is None
# no other check available (by type or origin) for these:
or str(type(arg)) in ("<class 'typing._SpecialForm'>", "<class 'ellipsis'>")
):
pass
# will be edited in place
telegram_classes = set()
# RECURSIVE CALLS
# for cases like Union[Sequence....
elif origin in (
Union,
collections.abc.Coroutine,
collections.abc.Sequence,
):
for sub_arg in typing.get_args(arg):
yield from self._resolve_arg(sub_arg)
def recurse_type(type_, is_recursed_from_ptb_class: bool):
next_is_recursed_from_ptb_class = is_recursed_from_ptb_class or _is_ptb_class(type_)
elif isinstance(arg, typing.TypeVar):
# gets access to the "bound=..." parameter
yield from self._resolve_arg(arg.__bound__)
# END RECURSIVE CALLS
if hasattr(type_, "__origin__"): # For generic types like Union, List, etc.
# Make sure it's not a telegram.ext generic type (e.g. ContextTypes[...])
org = typing.get_origin(type_)
if "telegram.ext" in str(org):
telegram_classes.add(org)
elif isinstance(arg, typing.ForwardRef):
m = self.FORWARD_REF_PATTERN.match(str(arg))
# We're sure it's a ForwardRef, so, unless it belongs to known exceptions,
# the class must be resolved.
# If it isn't resolved, we'll have the program throw an exception to be sure.
try:
cls = self._resolve_class(m.group("class_name"))
except AttributeError as exc:
# skip known ForwardRef's that need not be resolved to a Telegram class
if self.FORWARD_REF_SKIP_PATTERN.match(str(arg)):
pass
else:
raise NotImplementedError(f"Could not process ForwardRef: {arg}") from exc
else:
yield cls
args = typing.get_args(type_)
for arg in args:
recurse_type(arg, next_is_recursed_from_ptb_class)
elif isinstance(type_, typing.TypeVar) and (
resolve_nested_type_vars or not is_recursed_from_ptb_class
):
# gets access to the "bound=..." parameter
recurse_type(type_.__bound__, next_is_recursed_from_ptb_class)
elif inspect.isclass(type_) and "telegram" in inspect.getmodule(type_).__name__:
telegram_classes.add(type_)
elif isinstance(type_, typing.ForwardRef):
# Resolving ForwardRef is not easy. https://peps.python.org/pep-0749/ will
# hopefully make it better by introducing typing.resolve_forward_ref() in py3.14
# but that's not there yet
# So for now we fall back to a best effort approach of guessing if the class is
# available in tg or tg.ext
with contextlib.suppress(AttributeError):
telegram_classes.add(self._resolve_class(type_.__forward_arg__))
# For custom generics like telegram.ext._application.Application[~BT, ~CCT, ~UD...].
# This must come before the check for isinstance(type) because GenericAlias can also be
# recognized as type if it belongs to <class 'types.GenericAlias'>.
elif str(type(arg)) in (
"<class 'typing._GenericAlias'>",
"<class 'types.GenericAlias'>",
"<class 'typing._LiteralGenericAlias'>",
):
if "telegram" in str(arg):
# get_origin() of telegram.ext._application.Application[~BT, ~CCT, ~UD...]
# will produce <class 'telegram.ext._application.Application'>
yield origin
for type_hint in type_hints.values():
if type_hint is not None:
recurse_type(type_hint, False)
elif isinstance(arg, type):
if "telegram" in str(arg):
yield arg
# For some reason "InlineQueryResult", "InputMedia" & some others are currently not
# recognized as ForwardRefs and are identified as plain strings.
elif isinstance(arg, str):
# args like "ApplicationBuilder[BT, CCT, UD, CD, BD, JQ]" can be recognized as strings.
# Remove whatever is in the square brackets because it doesn't need to be parsed.
arg = re.sub(r"\[.+]", "", arg)
cls = self._resolve_class(arg)
# Here we don't want an exception to be thrown since we're not sure it's ForwardRef
if cls is not None:
yield cls
else:
raise NotImplementedError(
f"Cannot process argument {arg} of type {type(arg)} (origin {origin})"
)
return list(telegram_classes)
@staticmethod
def _resolve_class(name: str) -> Union[type, None]:
@@ -581,16 +558,14 @@ class AdmonitionInserter:
f"telegram.ext.{name}",
f"telegram.ext.filters.{name}",
):
try:
return eval(option)
# NameError will be raised if trying to eval just name and it doesn't work, e.g.
# "Name 'ApplicationBuilder' is not defined".
# AttributeError will be raised if trying to e.g. eval f"telegram.{name}" when the
# class denoted by `name` actually belongs to `telegram.ext`:
# "module 'telegram' has no attribute 'ApplicationBuilder'".
# If neither option works, this is not a PTB class.
except (NameError, AttributeError):
continue
with contextlib.suppress(NameError, AttributeError):
return eval(option)
return None
+1 -1
View File
@@ -61,5 +61,5 @@
}
.admonition.returned-in > ul, .admonition.available-in > ul, .admonition.use-in > ul, .admonition.shortcuts > ul {
max-height: 200px;
overflow-y: scroll;
overflow-y: auto;
}
+5
View File
@@ -111,6 +111,11 @@ linkcheck_ignore = [
# Anchors are apparently inserted by GitHub dynamically, so let's skip checking them
"https://github.com/python-telegram-bot/python-telegram-bot/tree/master/examples#",
r"https://github\.com/python-telegram-bot/python-telegram-bot/wiki/[\w\-_,]+\#",
# The LGPL license link regularly causes network errors for some reason
re.escape("https://www.gnu.org/licenses/lgpl-3.0.html"),
# The doc-fixes branch may not always exist - doesn't matter, we only link to it from the
# contributing guide
re.escape("https://docs.python-telegram-bot.org/en/doc-fixes"),
]
linkcheck_allowed_redirects = {
# Redirects to the default version are okay
+1
View File
@@ -27,6 +27,7 @@ Your bot can accept payments from Telegram users. Please see the `introduction t
telegram.successfulpayment
telegram.transactionpartner
telegram.transactionpartneraffiliateprogram
telegram.transactionpartnerchat
telegram.transactionpartnerfragment
telegram.transactionpartnerother
telegram.transactionpartnertelegramads
@@ -0,0 +1,7 @@
TransactionPartnerChat
======================
.. autoclass:: telegram.TransactionPartnerChat
:members:
:show-inheritance:
:inherited-members: TransactionPartner
+5 -1
View File
@@ -96,4 +96,8 @@
.. |allow_paid_broadcast| replace:: Pass True to allow up to :tg-const:`telegram.constants.FloodLimit.PAID_MESSAGES_PER_SECOND` messages per second, ignoring `broadcasting limits <https://core.telegram.org/bots/faq#how-can-i-message-all-of-my-bot-39s-subscribers-at-once>`__ for a fee of 0.1 Telegram Stars per message. The relevant Stars will be withdrawn from the bot's balance.
.. |tz-naive-dtms| replace:: For timezone naive :obj:`datetime.datetime` objects, the default timezone of the bot will be used, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is used.
.. |tz-naive-dtms| replace:: For timezone naive :obj:`datetime.datetime` objects, the default timezone of the bot will be used, which is UTC unless :attr:`telegram.ext.Defaults.tzinfo` is used.
.. |org-verify| replace:: `on behalf of the organization <https://telegram.org/verify#third-party-verification>`__
.. |time-period-input| replace:: :class:`datetime.timedelta` objects are accepted in addition to plain :obj:`int` values.
+8 -2
View File
@@ -61,9 +61,9 @@ async def start_with_shipping_callback(update: Update, context: ContextTypes.DEF
title,
description,
payload,
PAYMENT_PROVIDER_TOKEN,
currency,
prices,
provider_token=PAYMENT_PROVIDER_TOKEN,
need_name=True,
need_phone_number=True,
need_email=True,
@@ -90,7 +90,13 @@ async def start_without_shipping_callback(
# optionally pass need_name=True, need_phone_number=True,
# need_email=True, need_shipping_address=True, is_flexible=True
await context.bot.send_invoice(
chat_id, title, description, payload, PAYMENT_PROVIDER_TOKEN, currency, prices
chat_id,
title,
description,
payload,
currency,
prices,
provider_token=PAYMENT_PROVIDER_TOKEN,
)
+1 -1
View File
@@ -10,7 +10,7 @@ description = "We have made you a wrapper you can't refuse"
readme = "README.rst"
requires-python = ">=3.9"
license = "LGPL-3.0-only"
license-files = { paths = ["LICENSE", "LICENSE.dual", "LICENSE.lesser"] }
license-files = ["LICENSE", "LICENSE.dual", "LICENSE.lesser"]
authors = [
{ name = "Leandro Toledo", email = "devs@python-telegram-bot.org" }
]
+2
View File
@@ -238,6 +238,7 @@ __all__ = (
"TextQuote",
"TransactionPartner",
"TransactionPartnerAffiliateProgram",
"TransactionPartnerChat",
"TransactionPartnerFragment",
"TransactionPartnerOther",
"TransactionPartnerTelegramAds",
@@ -275,6 +276,7 @@ from telegram._payment.stars.startransactions import StarTransaction, StarTransa
from telegram._payment.stars.transactionpartner import (
TransactionPartner,
TransactionPartnerAffiliateProgram,
TransactionPartnerChat,
TransactionPartnerFragment,
TransactionPartnerOther,
TransactionPartnerTelegramAds,
+247 -93
View File
@@ -96,7 +96,15 @@ 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.strings import to_camel_case
from telegram._utils.types import CorrectOptionID, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.types import (
BaseUrl,
CorrectOptionID,
FileInput,
JSONDict,
ODVInput,
ReplyMarkup,
TimePeriod,
)
from telegram._utils.warnings import warn
from telegram._webhookinfo import WebhookInfo
from telegram.constants import InlineQueryLimit, ReactionEmoji
@@ -126,6 +134,35 @@ if TYPE_CHECKING:
BT = TypeVar("BT", bound="Bot")
# Even though we document only {token} as supported insertion, we are a bit more flexible
# internally and support additional variants. At the very least, we don't want the insertion
# to be case sensitive.
_SUPPORTED_INSERTIONS = {"token", "TOKEN", "bot_token", "BOT_TOKEN", "bot-token", "BOT-TOKEN"}
_INSERTION_STRINGS = {f"{{{insertion}}}" for insertion in _SUPPORTED_INSERTIONS}
class _TokenDict(dict):
__slots__ = ("token",)
# small helper to make .format_map work without knowing which exact insertion name is used
def __init__(self, token: str):
self.token = token
super().__init__()
def __missing__(self, key: str) -> str:
if key in _SUPPORTED_INSERTIONS:
return self.token
raise KeyError(f"Base URL string contains unsupported insertion: {key}")
def _parse_base_url(value: BaseUrl, token: str) -> str:
if callable(value):
return value(token)
if any(insertion in value for insertion in _INSERTION_STRINGS):
return value.format_map(_TokenDict(token))
return value + token
class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
"""This object represents a Telegram Bot.
@@ -193,8 +230,40 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
Args:
token (:obj:`str`): Bot's unique authentication token.
base_url (:obj:`str`, optional): Telegram Bot API service URL.
base_url (:obj:`str` | Callable[[:obj:`str`], :obj:`str`], optional): Telegram Bot API
service URL. If the string contains ``{token}``, it will be replaced with the bot's
token. If a callable is passed, it will be called with the bot's token as the only
argument and must return the base URL. Otherwise, the token will be appended to the
string. Defaults to ``"https://api.telegram.org/bot"``.
Tip:
Customizing the base URL can be used to run a bot against
:wiki:`Local Bot API Server <Local-Bot-API-Server>` or using Telegrams
`test environment \
<https://core.telegram.org/bots/features#dedicated-test-environment>`_.
Example:
``"https://api.telegram.org/bot{token}/test"``
.. versionchanged:: 21.11
Supports callable input and string formatting.
base_file_url (:obj:`str`, optional): Telegram Bot API file URL.
If the string contains ``{token}``, it will be replaced with the bot's
token. If a callable is passed, it will be called with the bot's token as the only
argument and must return the base URL. Otherwise, the token will be appended to the
string. Defaults to ``"https://api.telegram.org/bot"``.
Tip:
Customizing the base URL can be used to run a bot against
:wiki:`Local Bot API Server <Local-Bot-API-Server>` or using Telegrams
`test environment \
<https://core.telegram.org/bots/features#dedicated-test-environment>`_.
Example:
``"https://api.telegram.org/file/bot{token}/test"``
.. versionchanged:: 21.11
Supports callable input and string formatting.
request (:class:`telegram.request.BaseRequest`, optional): Pre initialized
:class:`telegram.request.BaseRequest` instances. Will be used for all bot methods
*except* for :meth:`get_updates`. If not passed, an instance of
@@ -239,8 +308,8 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
def __init__(
self,
token: str,
base_url: str = "https://api.telegram.org/bot",
base_file_url: str = "https://api.telegram.org/file/bot",
base_url: BaseUrl = "https://api.telegram.org/bot",
base_file_url: BaseUrl = "https://api.telegram.org/file/bot",
request: Optional[BaseRequest] = None,
get_updates_request: Optional[BaseRequest] = None,
private_key: Optional[bytes] = None,
@@ -252,8 +321,11 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
raise InvalidToken("You must pass the token you received from https://t.me/Botfather!")
self._token: str = token
self._base_url: str = base_url + self._token
self._base_file_url: str = base_file_url + self._token
self._base_url: str = _parse_base_url(base_url, self._token)
self._base_file_url: str = _parse_base_url(base_file_url, self._token)
self._LOGGER.debug("Set Bot API URL: %s", self._base_url)
self._LOGGER.debug("Set Bot API File URL: %s", self._base_file_url)
self._local_mode: bool = local_mode
self._bot_user: Optional[User] = None
self._private_key: Optional[bytes] = None
@@ -264,7 +336,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
HTTPXRequest() if request is None else request,
)
# this section is about issuing a warning when using HTTP/2 and connect to a self hosted
# this section is about issuing a warning when using HTTP/2 and connect to a self-hosted
# bot api instance, which currently only supports HTTP/1.1. Checking if a custom base url
# is set is the best way to do that.
@@ -273,14 +345,14 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
if (
isinstance(self._request[0], HTTPXRequest)
and self._request[0].http_version == "2"
and not base_url.startswith("https://api.telegram.org/bot")
and not self.base_url.startswith("https://api.telegram.org/bot")
):
warning_string = "get_updates_request"
if (
isinstance(self._request[1], HTTPXRequest)
and self._request[1].http_version == "2"
and not base_url.startswith("https://api.telegram.org/bot")
and not self.base_url.startswith("https://api.telegram.org/bot")
):
if warning_string:
warning_string += " and request"
@@ -901,7 +973,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
api_kwargs=api_kwargs,
)
self._bot_user = User.de_json(result, self)
return self._bot_user # type: ignore[return-value]
return self._bot_user
async def send_message(
self,
@@ -1146,6 +1218,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
video_start_timestamp: Optional[int] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1170,6 +1243,10 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
original message was sent (or channel username in the format ``@channelusername``).
message_id (:obj:`int`): Message identifier in the chat specified in
:paramref:`from_chat_id`.
video_start_timestamp (:obj:`int`, optional): New start timestamp for the
forwarded video in the message
.. versionadded:: 21.11
disable_notification (:obj:`bool`, optional): |disable_notification|
protect_content (:obj:`bool`, optional): |protect_content|
@@ -1188,6 +1265,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
"chat_id": chat_id,
"from_chat_id": from_chat_id,
"message_id": message_id,
"video_start_timestamp": video_start_timestamp,
}
return await self._send_message(
@@ -1421,7 +1499,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
self,
chat_id: Union[int, str],
audio: Union[FileInput, "Audio"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
performer: Optional[str] = None,
title: Optional[str] = None,
caption: Optional[str] = None,
@@ -1483,7 +1561,11 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
.. versionchanged:: 20.0
|sequenceargs|
duration (:obj:`int`, optional): Duration of sent audio in seconds.
duration (:obj:`int` | :class:`datetime.timedelta`, optional): Duration of sent audio
in seconds.
.. versionchanged:: 21.11
|time-period-input|
performer (:obj:`str`, optional): Performer.
title (:obj:`str`, optional): Track name.
disable_notification (:obj:`bool`, optional): |disable_notification|
@@ -1861,7 +1943,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
self,
chat_id: Union[int, str],
video: Union[FileInput, "Video"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
caption: Optional[str] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
@@ -1879,6 +1961,8 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
message_effect_id: Optional[str] = None,
allow_paid_broadcast: Optional[bool] = None,
show_caption_above_media: Optional[bool] = None,
cover: Optional[FileInput] = None,
start_timestamp: Optional[int] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@@ -1919,9 +2003,20 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
duration (:obj:`int`, optional): Duration of sent video in seconds.
duration (:obj:`int` | :class:`datetime.timedelta`, optional): Duration of sent video
in seconds.
.. versionchanged:: 21.11
|time-period-input|
width (:obj:`int`, optional): Video width.
height (:obj:`int`, optional): Video height.
cover (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): Cover for the video in the message. |fileinputnopath|
.. versionadded:: 21.11
start_timestamp (:obj:`int`, optional): Start timestamp for the video in the message.
.. versionadded:: 21.11
caption (:obj:`str`, optional): Video caption (may also be used when resending videos
by file_id), 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH`
characters after entities parsing.
@@ -2008,6 +2103,8 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
"width": width,
"height": height,
"supports_streaming": supports_streaming,
"cover": self._parse_file_input(cover, attach=True) if cover else None,
"start_timestamp": start_timestamp,
"thumbnail": self._parse_file_input(thumbnail, attach=True) if thumbnail else None,
"has_spoiler": has_spoiler,
"show_caption_above_media": show_caption_above_media,
@@ -2040,7 +2137,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
self,
chat_id: Union[int, str],
video_note: Union[FileInput, "VideoNote"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
length: Optional[int] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
@@ -2092,7 +2189,11 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
duration (:obj:`int`, optional): Duration of sent video in seconds.
duration (:obj:`int` | :class:`datetime.timedelta`, optional): Duration of sent video
in seconds.
.. versionchanged:: 21.11
|time-period-input|
length (:obj:`int`, optional): Video width and height, i.e. diameter of the video
message.
disable_notification (:obj:`bool`, optional): |disable_notification|
@@ -2188,7 +2289,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
self,
chat_id: Union[int, str],
animation: Union[FileInput, "Animation"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
width: Optional[int] = None,
height: Optional[int] = None,
caption: Optional[str] = None,
@@ -2240,7 +2341,11 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
duration (:obj:`int`, optional): Duration of sent animation in seconds.
duration (:obj:`int` | :class:`datetime.timedelta`, optional): Duration of sent
animation in seconds.
.. versionchanged:: 21.11
|time-period-input|
width (:obj:`int`, optional): Animation width.
height (:obj:`int`, optional): Animation height.
caption (:obj:`str`, optional): Animation caption (may also be used when resending
@@ -2359,7 +2464,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
self,
chat_id: Union[int, str],
voice: Union[FileInput, "Voice"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
caption: Optional[str] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
@@ -2420,7 +2525,11 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
.. versionchanged:: 20.0
|sequenceargs|
duration (:obj:`int`, optional): Duration of the voice message in seconds.
duration (:obj:`int` | :class:`datetime.timedelta`, optional): Duration of the voice
message in seconds.
.. versionchanged:: 21.11
|time-period-input|
disable_notification (:obj:`bool`, optional): |disable_notification|
protect_content (:obj:`bool`, optional): |protect_content|
@@ -2692,7 +2801,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
longitude: Optional[float] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
live_period: Optional[int] = None,
live_period: Optional[TimePeriod] = None,
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
@@ -2725,12 +2834,16 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
horizontal_accuracy (:obj:`int`, optional): The radius of uncertainty for the location,
measured in meters;
0-:tg-const:`telegram.constants.LocationLimit.HORIZONTAL_ACCURACY`.
live_period (:obj:`int`, optional): Period in seconds for which the location will be
live_period (:obj:`int` | :class:`datetime.timedelta`, optional): Period in seconds for
which the location will be
updated, should be between
:tg-const:`telegram.constants.LocationLimit.MIN_LIVE_PERIOD` and
:tg-const:`telegram.constants.LocationLimit.MAX_LIVE_PERIOD`, or
:tg-const:`telegram.constants.LocationLimit.LIVE_PERIOD_FOREVER` for live
locations that can be edited indefinitely.
.. versionchanged:: 21.11
|time-period-input|
heading (:obj:`int`, optional): For live locations, a direction in which the user is
moving, in degrees. Must be between
:tg-const:`telegram.constants.LocationLimit.MIN_HEADING` and
@@ -2848,7 +2961,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
live_period: Optional[int] = None,
live_period: Optional[TimePeriod] = None,
business_connection_id: Optional[str] = None,
*,
location: Optional[Location] = None,
@@ -2888,7 +3001,8 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
if specified.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): An object for a new
inline keyboard.
live_period (:obj:`int`, optional): New period in seconds during which the location
live_period (:obj:`int` | :class:`datetime.timedelta`, optional): New period in seconds
during which the location
can be updated, starting from the message send date. If
:tg-const:`telegram.constants.LocationLimit.LIVE_PERIOD_FOREVER` is specified,
then the location can be updated forever. Otherwise, the new value must not exceed
@@ -2897,6 +3011,9 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
remains unchanged
.. versionadded:: 21.2.
.. versionchanged:: 21.11
|time-period-input|
business_connection_id (:obj:`str`, optional): |business_id_str_edit|
.. versionadded:: 21.4
@@ -3552,7 +3669,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
results: Union[
Sequence["InlineQueryResult"], Callable[[int], Optional[Sequence["InlineQueryResult"]]]
],
cache_time: Optional[int] = None,
cache_time: Optional[TimePeriod] = None,
is_personal: Optional[bool] = None,
next_offset: Optional[str] = None,
button: Optional[InlineQueryResultsButton] = None,
@@ -3588,8 +3705,12 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
a callable that accepts the current page index starting from 0. It must return
either a list of :class:`telegram.InlineQueryResult` instances or :obj:`None` if
there are no more results.
cache_time (:obj:`int`, optional): The maximum amount of time in seconds that the
cache_time (:obj:`int` | :class:`datetime.timedelta`, optional): The maximum amount of
time in seconds that the
result of the inline query may be cached on the server. Defaults to ``300``.
.. versionchanged:: 21.11
|time-period-input|
is_personal (:obj:`bool`, optional): Pass :obj:`True`, if results may be cached on
the server side only for the user that sent the query. By default,
results may be returned to any user who sends the same query.
@@ -3689,7 +3810,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
"allow_group_chats": allow_group_chats,
"allow_channel_chats": allow_channel_chats,
}
return PreparedInlineMessage.de_json( # type: ignore[return-value]
return PreparedInlineMessage.de_json(
await self._post(
"savePreparedInlineMessage",
data,
@@ -3744,7 +3865,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
api_kwargs=api_kwargs,
)
return UserProfilePhotos.de_json(result, self) # type: ignore[return-value]
return UserProfilePhotos.de_json(result, self)
async def get_file(
self,
@@ -3809,7 +3930,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
if file_path and not is_local_file(file_path):
result["file_path"] = f"{self._base_file_url}/{file_path}"
return File.de_json(result, self) # type: ignore[return-value]
return File.de_json(result, self)
async def ban_chat_member(
self,
@@ -4005,7 +4126,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
text: Optional[str] = None,
show_alert: Optional[bool] = None,
url: Optional[str] = None,
cache_time: Optional[int] = None,
cache_time: Optional[TimePeriod] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -4036,9 +4157,13 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
opens your game - note that this will only work if the query comes from a callback
game button. Otherwise, you may use links like t.me/your_bot?start=XXXX that open
your bot with a parameter.
cache_time (:obj:`int`, optional): The maximum amount of time in seconds that the
cache_time (:obj:`int` | :class:`datetime.timedelta`, optional): The maximum amount of
time in seconds that the
result of the callback query may be cached client-side. Defaults to 0.
.. versionchanged:: 21.11
|time-period-input|
Returns:
:obj:`bool` On success, :obj:`True` is returned.
@@ -4386,7 +4511,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
self,
offset: Optional[int] = None,
limit: Optional[int] = None,
timeout: Optional[int] = None, # noqa: ASYNC109
timeout: Optional[int] = None,
allowed_updates: Optional[Sequence[str]] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -4523,8 +4648,11 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
"""
Use this method to specify a url and receive incoming updates via an outgoing webhook.
Whenever there is an update for the bot, Telegram will send an HTTPS POST request to the
specified url, containing An Update. In case of an unsuccessful request,
Telegram will give up after a reasonable amount of attempts.
specified url, containing An Update. In case of an unsuccessful request
(a request with response
`HTTP status code <https://en.wikipedia.org/wiki/List_of_HTTP_status_codes>`_different
from ``2XY``),
Telegram will repeat the request and give up after a reasonable amount of attempts.
If you'd like to make sure that the Webhook was set by you, you can specify secret data in
the parameter :paramref:`secret_token`. If specified, the request will contain a header
@@ -4729,7 +4857,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
api_kwargs=api_kwargs,
)
return ChatFullInfo.de_json(result, self) # type: ignore[return-value]
return ChatFullInfo.de_json(result, self)
async def get_chat_administrators(
self,
@@ -4842,7 +4970,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
return ChatMember.de_json(result, self) # type: ignore[return-value]
return ChatMember.de_json(result, self)
async def set_chat_sticker_set(
self,
@@ -4937,7 +5065,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
return WebhookInfo.de_json(result, self) # type: ignore[return-value]
return WebhookInfo.de_json(result, self)
async def set_game_score(
self,
@@ -5069,9 +5197,9 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
title: str,
description: str,
payload: str,
provider_token: Optional[str], # This arg is now optional as of Bot API 7.4
currency: str,
prices: Sequence["LabeledPrice"],
provider_token: Optional[str] = None,
start_parameter: Optional[str] = None,
photo_url: Optional[str] = None,
photo_size: Optional[int] = None,
@@ -5124,13 +5252,13 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
:tg-const:`telegram.Invoice.MIN_PAYLOAD_LENGTH`-
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be
displayed to the user, use it for your internal processes.
provider_token (:obj:`str`): Payments provider token, obtained via
provider_token (:obj:`str`, optional): Payments provider token, obtained via
`@BotFather <https://t.me/BotFather>`_. Pass an empty string for payments in
|tg_stars|.
.. deprecated:: 21.3
As of Bot API 7.4, this parameter is now optional and future versions of the
library will make it optional as well.
.. versionchanged:: 21.11
Bot API 7.4 made this parameter is optional and this is now reflected in the
function signature.
currency (:obj:`str`): Three-letter ISO 4217 currency code, see `more on currencies
<https://core.telegram.org/bots/payments#supported-currencies>`_. Pass ``XTR`` for
@@ -5444,7 +5572,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
api_kwargs=api_kwargs,
)
return SentWebAppMessage.de_json(api_result, self) # type: ignore[return-value]
return SentWebAppMessage.de_json(api_result, self)
async def restrict_chat_member(
self,
@@ -5858,7 +5986,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
api_kwargs=api_kwargs,
)
return ChatInviteLink.de_json(result, self) # type: ignore[return-value]
return ChatInviteLink.de_json(result, self)
async def edit_chat_invite_link(
self,
@@ -5937,7 +6065,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
api_kwargs=api_kwargs,
)
return ChatInviteLink.de_json(result, self) # type: ignore[return-value]
return ChatInviteLink.de_json(result, self)
async def revoke_chat_invite_link(
self,
@@ -5984,7 +6112,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
api_kwargs=api_kwargs,
)
return ChatInviteLink.de_json(result, self) # type: ignore[return-value]
return ChatInviteLink.de_json(result, self)
async def approve_chat_join_request(
self,
@@ -6456,7 +6584,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
return StickerSet.de_json(result, self) # type: ignore[return-value]
return StickerSet.de_json(result, self)
async def get_custom_emoji_stickers(
self,
@@ -6559,7 +6687,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
return File.de_json(result, self) # type: ignore[return-value]
return File.de_json(result, self)
async def add_sticker_to_set(
self,
@@ -6856,7 +6984,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
:tg-const:`telegram.constants.StickerFormat.STATIC` for a
``.WEBP`` or ``.PNG`` image, :tg-const:`telegram.constants.StickerFormat.ANIMATED`
for a ``.TGS`` animation, :tg-const:`telegram.constants.StickerFormat.VIDEO` for a
WEBM video.
``.WEBM`` video.
.. versionadded:: 21.1
@@ -6870,7 +6998,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
:tg-const:`telegram.constants.StickerSetLimit.MAX_ANIMATED_THUMBNAIL_SIZE`
kilobytes in size; see
`the docs <https://core.telegram.org/stickers#animation-requirements>`_ for
animated sticker technical requirements, or a **.WEBM** video with the thumbnail up
animated sticker technical requirements, or a ``.WEBM`` video with the thumbnail up
to :tg-const:`telegram.constants.StickerSetLimit.MAX_ANIMATED_THUMBNAIL_SIZE`
kilobytes in size; see
`this <https://core.telegram.org/stickers#video-requirements>`_ for video sticker
@@ -7190,7 +7318,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
reply_markup: Optional[ReplyMarkup] = None,
explanation: Optional[str] = None,
explanation_parse_mode: ODVInput[str] = DEFAULT_NONE,
open_period: Optional[int] = None,
open_period: Optional[TimePeriod] = None,
close_date: Optional[Union[int, dtm.datetime]] = None,
explanation_entities: Optional[Sequence["MessageEntity"]] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
@@ -7253,10 +7381,14 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
.. versionchanged:: 20.0
|sequenceargs|
open_period (:obj:`int`, optional): Amount of time in seconds the poll will be active
open_period (:obj:`int` | :class:`datetime.timedelta`, optional): Amount of time in
seconds the poll will be active
after creation, :tg-const:`telegram.Poll.MIN_OPEN_PERIOD`-
:tg-const:`telegram.Poll.MAX_OPEN_PERIOD`. Can't be used together with
:paramref:`close_date`.
.. versionchanged:: 21.11
|time-period-input|
close_date (:obj:`int` | :obj:`datetime.datetime`, optional): Point in time (Unix
timestamp) when the poll will be automatically closed. Must be at least
:tg-const:`telegram.Poll.MIN_OPEN_PERIOD` and no more than
@@ -7416,7 +7548,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
return Poll.de_json(result, self) # type: ignore[return-value]
return Poll.de_json(result, self)
async def send_dice(
self,
@@ -7572,7 +7704,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
api_kwargs=api_kwargs,
)
return ChatAdministratorRights.de_json(result, self) # type: ignore[return-value]
return ChatAdministratorRights.de_json(result, self)
async def set_my_default_administrator_rights(
self,
@@ -7862,6 +7994,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
allow_paid_broadcast: Optional[bool] = None,
video_start_timestamp: Optional[int] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@@ -7881,6 +8014,10 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
from_chat_id (:obj:`int` | :obj:`str`): Unique identifier for the chat where the
original message was sent (or channel username in the format ``@channelusername``).
message_id (:obj:`int`): Message identifier in the chat specified in from_chat_id.
video_start_timestamp (:obj:`int`, optional): New start timestamp for the
copied video in the message
.. versionadded:: 21.11
caption (:obj:`str`, optional): New caption for media,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after
entities parsing. If not specified, the original caption is kept.
@@ -7971,6 +8108,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"reply_parameters": reply_parameters,
"show_caption_above_media": show_caption_above_media,
"allow_paid_broadcast": allow_paid_broadcast,
"video_start_timestamp": video_start_timestamp,
}
result = await self._post(
@@ -7982,7 +8120,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
return MessageId.de_json(result, self) # type: ignore[return-value]
return MessageId.de_json(result, self)
async def copy_messages(
self,
@@ -8133,16 +8271,16 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
return MenuButton.de_json(result, bot=self) # type: ignore[return-value]
return MenuButton.de_json(result, bot=self)
async def create_invoice_link(
self,
title: str,
description: str,
payload: str,
provider_token: Optional[str], # This arg is now optional as of Bot API 7.4
currency: str,
prices: Sequence["LabeledPrice"],
provider_token: Optional[str] = None,
max_tip_amount: Optional[int] = None,
suggested_tip_amounts: Optional[Sequence[int]] = None,
provider_data: Optional[Union[str, object]] = None,
@@ -8157,7 +8295,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
send_phone_number_to_provider: Optional[bool] = None,
send_email_to_provider: Optional[bool] = None,
is_flexible: Optional[bool] = None,
subscription_period: Optional[Union[int, dtm.timedelta]] = None,
subscription_period: Optional[TimePeriod] = None,
business_connection_id: Optional[str] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -8184,13 +8322,13 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
:tg-const:`telegram.Invoice.MIN_PAYLOAD_LENGTH`-
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be
displayed to the user, use it for your internal processes.
provider_token (:obj:`str`): Payments provider token, obtained via
provider_token (:obj:`str`, optional): Payments provider token, obtained via
`@BotFather <https://t.me/BotFather>`_. Pass an empty string for payments in
|tg_stars|.
.. deprecated:: 21.3
As of Bot API 7.4, this parameter is now optional and future versions of the
library will make it optional as well.
.. versionchanged:: 21.11
Bot API 7.4 made this parameter is optional and this is now reflected in the
function signature.
currency (:obj:`str`): Three-letter ISO 4217 currency code, see `more on currencies
<https://core.telegram.org/bots/payments#supported-currencies>`_. Pass ``XTR`` for
@@ -8278,11 +8416,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"is_flexible": is_flexible,
"send_phone_number_to_provider": send_phone_number_to_provider,
"send_email_to_provider": send_email_to_provider,
"subscription_period": (
subscription_period.total_seconds()
if isinstance(subscription_period, dtm.timedelta)
else subscription_period
),
"subscription_period": subscription_period,
"business_connection_id": business_connection_id,
}
@@ -8384,7 +8518,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
return ForumTopic.de_json(result, self) # type: ignore[return-value]
return ForumTopic.de_json(result, self)
async def edit_forum_topic(
self,
@@ -8972,7 +9106,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""
data = {"language_code": language_code}
return BotDescription.de_json( # type: ignore[return-value]
return BotDescription.de_json(
await self._post(
"getMyDescription",
data,
@@ -9011,7 +9145,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""
data = {"language_code": language_code}
return BotShortDescription.de_json( # type: ignore[return-value]
return BotShortDescription.de_json(
await self._post(
"getMyShortDescription",
data,
@@ -9097,7 +9231,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""
data = {"language_code": language_code}
return BotName.de_json( # type: ignore[return-value]
return BotName.de_json(
await self._post(
"getMyName",
data,
@@ -9139,7 +9273,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"chat_id": chat_id, "user_id": user_id}
return UserChatBoosts.de_json( # type: ignore[return-value]
return UserChatBoosts.de_json(
await self._post(
"getUserChatBoosts",
data,
@@ -9166,7 +9300,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
api_kwargs: Optional[JSONDict] = None,
) -> bool:
"""
Use this method to change the chosen reactions on a message. Service messages can't be
Use this method to change the chosen reactions on a message. Service messages of some types
can't be
reacted to. Automatically forwarded messages from a channel to its discussion group have
the same available reactions as messages in the channel. Bots can't use paid reactions.
@@ -9263,7 +9398,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"business_connection_id": business_connection_id}
return BusinessConnection.de_json( # type: ignore[return-value]
return BusinessConnection.de_json(
await self._post(
"getBusinessConnection",
data,
@@ -9402,7 +9537,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
data: JSONDict = {"offset": offset, "limit": limit}
return StarTransactions.de_json( # type: ignore[return-value]
return StarTransactions.de_json(
await self._post(
"getStarTransactions",
data,
@@ -9453,7 +9588,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"is_canceled": is_canceled,
}
return await self._post(
"editUserStartSubscription",
"editUserStarSubscription",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -9573,7 +9708,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
async def create_chat_subscription_invite_link(
self,
chat_id: Union[str, int],
subscription_period: int,
subscription_period: TimePeriod,
subscription_price: int,
name: Optional[str] = None,
*,
@@ -9594,9 +9729,13 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
subscription_period (:obj:`int`): The number of seconds the subscription will be
subscription_period (:obj:`int` | :class:`datetime.timedelta`): The number of seconds
the subscription will be
active for before the next payment. Currently, it must always be
:tg-const:`telegram.constants.ChatSubscriptionLimit.SUBSCRIPTION_PERIOD` (30 days).
.. versionchanged:: 21.11
|time-period-input|
subscription_price (:obj:`int`): The number of Telegram Stars a user must pay initially
and after each subsequent subscription period to be a member of the chat;
:tg-const:`telegram.constants.ChatSubscriptionLimit.MIN_PRICE`-
@@ -9628,7 +9767,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
api_kwargs=api_kwargs,
)
return ChatInviteLink.de_json(result, self) # type: ignore[return-value]
return ChatInviteLink.de_json(result, self)
async def edit_chat_subscription_invite_link(
self,
@@ -9681,7 +9820,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
api_kwargs=api_kwargs,
)
return ChatInviteLink.de_json(result, self) # type: ignore[return-value]
return ChatInviteLink.de_json(result, self)
async def get_available_gifts(
self,
@@ -9692,7 +9831,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> Gifts:
"""Returns the list of gifts that can be sent by the bot to users.
"""Returns the list of gifts that can be sent by the bot to users and channel chats.
Requires no parameters.
.. versionadded:: 21.8
@@ -9703,7 +9842,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
Raises:
:class:`telegram.error.TelegramError`
"""
return Gifts.de_json( # type: ignore[return-value]
return Gifts.de_json(
await self._post(
"getAvailableGifts",
read_timeout=read_timeout,
@@ -9716,12 +9855,13 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
async def send_gift(
self,
user_id: int,
gift_id: Union[str, Gift],
user_id: Optional[int] = None,
gift_id: Union[str, Gift] = None, # type: ignore
text: Optional[str] = None,
text_parse_mode: ODVInput[str] = DEFAULT_NONE,
text_entities: Optional[Sequence["MessageEntity"]] = None,
pay_for_upgrade: Optional[bool] = None,
chat_id: Optional[Union[str, int]] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -9729,15 +9869,23 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> bool:
"""Sends a gift to the given user.
The gift can't be converted to Telegram Stars by the user
"""Sends a gift to the given user or channel chat.
The gift can't be converted to Telegram Stars by the receiver.
.. versionadded:: 21.8
Args:
user_id (:obj:`int`): Unique identifier of the target user that will receive the gift
user_id (:obj:`int`, optional): Required if :paramref:`chat_id` is not specified.
Unique identifier of the target user that will receive the gift.
.. versionchanged:: 21.11
Now optional.
gift_id (:obj:`str` | :class:`~telegram.Gift`): Identifier of the gift or a
:class:`~telegram.Gift` object
chat_id (:obj:`int` | :obj:`str`, optional): Required if :paramref:`user_id`
is not specified. |chat_id_channel| It will receive the gift.
.. versionadded:: 21.11
text (:obj:`str`, optional): Text that will be shown along with the gift;
0- :tg-const:`telegram.constants.GiftLimit.MAX_TEXT_LENGTH` characters
text_parse_mode (:obj:`str`, optional): Mode for parsing entities.
@@ -9764,6 +9912,11 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
Raises:
:class:`telegram.error.TelegramError`
"""
# TODO: Remove when stability policy allows, tags: deprecated 21.11
# also we should raise a deprecation warnung if anything is passed by
# position since it will be moved, not sure how
if gift_id is None:
raise TypeError("Missing required argument `gift_id`.")
data: JSONDict = {
"user_id": user_id,
"gift_id": gift_id.id if isinstance(gift_id, Gift) else gift_id,
@@ -9771,6 +9924,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"text_parse_mode": text_parse_mode,
"text_entities": text_entities,
"pay_for_upgrade": pay_for_upgrade,
"chat_id": chat_id,
}
return await self._post(
"sendGift",
@@ -9793,7 +9947,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> bool:
"""Verifies a chat on behalf of the organization which is represented by the bot.
"""Verifies a chat |org-verify| which is represented by the bot.
.. versionadded:: 21.10
@@ -9835,7 +9989,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> bool:
"""Verifies a user on behalf of the organization which is represented by the bot.
"""Verifies a user |org-verify| which is represented by the bot.
.. versionadded:: 21.10
@@ -9876,8 +10030,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> bool:
"""Removes verification from a chat that is currently verified on behalf of the
organization represented by the bot.
"""Removes verification from a chat that is currently verified |org-verify|
represented by the bot.
@@ -9915,8 +10069,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> bool:
"""Removes verification from a user who is currently verified on behalf of the
organization represented by the bot.
"""Removes verification from a user who is currently verified |org-verify|
represented by the bot.
+1 -6
View File
@@ -84,9 +84,7 @@ class BotCommandScope(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["BotCommandScope"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BotCommandScope":
"""Converts JSON data to the appropriate :class:`BotCommandScope` object, i.e. takes
care of selecting the correct subclass.
@@ -104,9 +102,6 @@ class BotCommandScope(TelegramObject):
"""
data = cls._parse_data(data)
if not data:
return None
_class_mapping: dict[str, type[BotCommandScope]] = {
cls.DEFAULT: BotCommandScopeDefault,
cls.ALL_PRIVATE_CHATS: BotCommandScopeAllPrivateChats,
+12 -37
View File
@@ -27,7 +27,7 @@ from telegram._files.location import Location
from telegram._files.sticker import Sticker
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
@@ -106,20 +106,15 @@ class BusinessConnection(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["BusinessConnection"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BusinessConnection":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
data["user"] = User.de_json(data.get("user"), bot)
data["user"] = de_json_optional(data.get("user"), User, bot)
return super().de_json(data=data, bot=bot)
@@ -177,16 +172,11 @@ class BusinessMessagesDeleted(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["BusinessMessagesDeleted"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BusinessMessagesDeleted":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
return super().de_json(data=data, bot=bot)
@@ -236,16 +226,11 @@ class BusinessIntro(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["BusinessIntro"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BusinessIntro":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["sticker"] = Sticker.de_json(data.get("sticker"), bot)
data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot)
return super().de_json(data=data, bot=bot)
@@ -290,16 +275,11 @@ class BusinessLocation(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["BusinessLocation"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BusinessLocation":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["location"] = Location.de_json(data.get("location"), bot)
data["location"] = de_json_optional(data.get("location"), Location, bot)
return super().de_json(data=data, bot=bot)
@@ -439,17 +419,12 @@ class BusinessOpeningHours(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["BusinessOpeningHours"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BusinessOpeningHours":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["opening_hours"] = BusinessOpeningHoursInterval.de_list(
data.get("opening_hours"), bot
data["opening_hours"] = de_list_optional(
data.get("opening_hours"), BusinessOpeningHoursInterval, bot
)
return super().de_json(data=data, bot=bot)
+9 -11
View File
@@ -26,8 +26,9 @@ from telegram._files.location import Location
from telegram._message import MaybeInaccessibleMessage, Message
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput, ReplyMarkup
from telegram._utils.types import JSONDict, ODVInput, ReplyMarkup, TimePeriod
if TYPE_CHECKING:
from telegram import (
@@ -149,17 +150,12 @@ class CallbackQuery(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["CallbackQuery"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "CallbackQuery":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["message"] = Message.de_json(data.get("message"), bot)
data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
data["message"] = de_json_optional(data.get("message"), Message, bot)
return super().de_json(data=data, bot=bot)
@@ -168,7 +164,7 @@ class CallbackQuery(TelegramObject):
text: Optional[str] = None,
show_alert: Optional[bool] = None,
url: Optional[str] = None,
cache_time: Optional[int] = None,
cache_time: Optional[TimePeriod] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -475,7 +471,7 @@ class CallbackQuery(TelegramObject):
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
live_period: Optional[int] = None,
live_period: Optional[TimePeriod] = None,
*,
location: Optional[Location] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -835,6 +831,7 @@ class CallbackQuery(TelegramObject):
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
allow_paid_broadcast: Optional[bool] = None,
video_start_timestamp: Optional[int] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@@ -868,6 +865,7 @@ class CallbackQuery(TelegramObject):
chat_id=chat_id,
caption=caption,
parse_mode=parse_mode,
video_start_timestamp=video_start_timestamp,
caption_entities=caption_entities,
disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id,
+39 -12
View File
@@ -31,7 +31,14 @@ from telegram._reaction import ReactionType
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import CorrectOptionID, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.types import (
CorrectOptionID,
FileInput,
JSONDict,
ODVInput,
ReplyMarkup,
TimePeriod,
)
from telegram.helpers import escape_markdown
from telegram.helpers import mention_html as helpers_mention_html
from telegram.helpers import mention_markdown as helpers_mention_markdown
@@ -1339,7 +1346,7 @@ class _ChatBase(TelegramObject):
async def send_audio(
self,
audio: Union[FileInput, "Audio"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
performer: Optional[str] = None,
title: Optional[str] = None,
caption: Optional[str] = None,
@@ -1569,9 +1576,9 @@ class _ChatBase(TelegramObject):
title: str,
description: str,
payload: str,
provider_token: Optional[str],
currency: str,
prices: Sequence["LabeledPrice"],
provider_token: Optional[str] = None,
start_parameter: Optional[str] = None,
photo_url: Optional[str] = None,
photo_size: Optional[int] = None,
@@ -1668,7 +1675,7 @@ class _ChatBase(TelegramObject):
longitude: Optional[float] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
live_period: Optional[int] = None,
live_period: Optional[TimePeriod] = None,
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
@@ -1727,7 +1734,7 @@ class _ChatBase(TelegramObject):
async def send_animation(
self,
animation: Union[FileInput, "Animation"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
width: Optional[int] = None,
height: Optional[int] = None,
caption: Optional[str] = None,
@@ -1915,7 +1922,7 @@ class _ChatBase(TelegramObject):
async def send_video(
self,
video: Union[FileInput, "Video"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
caption: Optional[str] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
@@ -1933,6 +1940,8 @@ class _ChatBase(TelegramObject):
message_effect_id: Optional[str] = None,
allow_paid_broadcast: Optional[bool] = None,
show_caption_above_media: Optional[bool] = None,
cover: Optional[FileInput] = None,
start_timestamp: Optional[int] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1971,6 +1980,8 @@ class _ChatBase(TelegramObject):
parse_mode=parse_mode,
supports_streaming=supports_streaming,
thumbnail=thumbnail,
cover=cover,
start_timestamp=start_timestamp,
api_kwargs=api_kwargs,
allow_sending_without_reply=allow_sending_without_reply,
caption_entities=caption_entities,
@@ -1987,7 +1998,7 @@ class _ChatBase(TelegramObject):
async def send_video_note(
self,
video_note: Union[FileInput, "VideoNote"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
length: Optional[int] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
@@ -2045,7 +2056,7 @@ class _ChatBase(TelegramObject):
async def send_voice(
self,
voice: Union[FileInput, "Voice"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
caption: Optional[str] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
@@ -2115,7 +2126,7 @@ class _ChatBase(TelegramObject):
reply_markup: Optional[ReplyMarkup] = None,
explanation: Optional[str] = None,
explanation_parse_mode: ODVInput[str] = DEFAULT_NONE,
open_period: Optional[int] = None,
open_period: Optional[TimePeriod] = None,
close_date: Optional[Union[int, dtm.datetime]] = None,
explanation_entities: Optional[Sequence["MessageEntity"]] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
@@ -2192,6 +2203,7 @@ class _ChatBase(TelegramObject):
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
allow_paid_broadcast: Optional[bool] = None,
video_start_timestamp: Optional[int] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2218,6 +2230,7 @@ class _ChatBase(TelegramObject):
from_chat_id=from_chat_id,
message_id=message_id,
caption=caption,
video_start_timestamp=video_start_timestamp,
parse_mode=parse_mode,
caption_entities=caption_entities,
disable_notification=disable_notification,
@@ -2250,6 +2263,7 @@ class _ChatBase(TelegramObject):
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
allow_paid_broadcast: Optional[bool] = None,
video_start_timestamp: Optional[int] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2276,6 +2290,7 @@ class _ChatBase(TelegramObject):
chat_id=chat_id,
message_id=message_id,
caption=caption,
video_start_timestamp=video_start_timestamp,
parse_mode=parse_mode,
caption_entities=caption_entities,
disable_notification=disable_notification,
@@ -2391,6 +2406,7 @@ class _ChatBase(TelegramObject):
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
video_start_timestamp: Optional[int] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2416,6 +2432,7 @@ class _ChatBase(TelegramObject):
chat_id=self.id,
from_chat_id=from_chat_id,
message_id=message_id,
video_start_timestamp=video_start_timestamp,
disable_notification=disable_notification,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -2433,6 +2450,7 @@ class _ChatBase(TelegramObject):
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
video_start_timestamp: Optional[int] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2459,6 +2477,7 @@ class _ChatBase(TelegramObject):
from_chat_id=self.id,
chat_id=chat_id,
message_id=message_id,
video_start_timestamp=video_start_timestamp,
disable_notification=disable_notification,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -2708,7 +2727,7 @@ class _ChatBase(TelegramObject):
async def create_subscription_invite_link(
self,
subscription_period: int,
subscription_period: TimePeriod,
subscription_price: int,
name: Optional[str] = None,
*,
@@ -3455,18 +3474,25 @@ class _ChatBase(TelegramObject):
await bot.send_gift(user_id=update.effective_chat.id, *args, **kwargs )
or::
await bot.send_gift(chat_id=update.effective_chat.id, *args, **kwargs )
For the documentation of the arguments, please see :meth:`telegram.Bot.send_gift`.
Caution:
Can only work, if the chat is a private chat, see :attr:`type`.
Will only work if the chat is a private or channel chat, see :attr:`type`.
.. versionadded:: 21.8
.. versionchanged:: 21.11
Added support for channel chats.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().send_gift(
user_id=self.id,
gift_id=gift_id,
text=text,
text_parse_mode=text_parse_mode,
@@ -3477,6 +3503,7 @@ class _ChatBase(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
**{"chat_id" if self.type == Chat.CHANNEL else "user_id": self.id},
)
async def verify(
+9 -24
View File
@@ -24,7 +24,7 @@ from telegram import constants
from telegram._files.document import Document
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -79,15 +79,10 @@ class BackgroundFill(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["BackgroundFill"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BackgroundFill":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
_class_mapping: dict[str, type[BackgroundFill]] = {
cls.SOLID: BackgroundFillSolid,
cls.GRADIENT: BackgroundFillGradient,
@@ -270,15 +265,10 @@ class BackgroundType(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["BackgroundType"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "BackgroundType":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
_class_mapping: dict[str, type[BackgroundType]] = {
cls.FILL: BackgroundTypeFill,
cls.WALLPAPER: BackgroundTypeWallpaper,
@@ -290,10 +280,10 @@ class BackgroundType(TelegramObject):
return _class_mapping[data.pop("type")].de_json(data=data, bot=bot)
if "fill" in data:
data["fill"] = BackgroundFill.de_json(data.get("fill"), bot)
data["fill"] = de_json_optional(data.get("fill"), BackgroundFill, bot)
if "document" in data:
data["document"] = Document.de_json(data.get("document"), bot)
data["document"] = de_json_optional(data.get("document"), Document, bot)
return super().de_json(data=data, bot=bot)
@@ -398,8 +388,8 @@ class BackgroundTypeWallpaper(BackgroundType):
class BackgroundTypePattern(BackgroundType):
"""
The background is a `PNG` or `TGV` (gzipped subset of `SVG` with `MIME` type
`"application/x-tgwallpattern"`) pattern to be combined with the background fill
The background is a ``.PNG`` or ``.TGV`` (gzipped subset of ``SVG`` with ``MIME`` type
``"application/x-tgwallpattern"``) pattern to be combined with the background fill
chosen by the user.
Objects of this class are comparable in terms of equality. Two objects of this class are
@@ -533,15 +523,10 @@ class ChatBackground(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatBackground"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatBackground":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["type"] = BackgroundType.de_json(data.get("type"), bot)
data["type"] = de_json_optional(data.get("type"), BackgroundType, bot)
return super().de_json(data=data, bot=bot)
+16 -41
View File
@@ -26,7 +26,7 @@ from telegram._chat import Chat
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
@@ -110,15 +110,10 @@ class ChatBoostSource(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatBoostSource"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatBoostSource":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
_class_mapping: dict[str, type[ChatBoostSource]] = {
cls.PREMIUM: ChatBoostSourcePremium,
cls.GIFT_CODE: ChatBoostSourceGiftCode,
@@ -129,7 +124,7 @@ class ChatBoostSource(TelegramObject):
return _class_mapping[data.pop("source")].de_json(data=data, bot=bot)
if "user" in data:
data["user"] = User.de_json(data.get("user"), bot)
data["user"] = de_json_optional(data.get("user"), User, bot)
return super().de_json(data=data, bot=bot)
@@ -290,19 +285,14 @@ class ChatBoost(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatBoost"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatBoost":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["source"] = ChatBoostSource.de_json(data.get("source"), bot)
data["source"] = de_json_optional(data.get("source"), ChatBoostSource, bot)
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["add_date"] = from_timestamp(data["add_date"], tzinfo=loc_tzinfo)
data["expiration_date"] = from_timestamp(data["expiration_date"], tzinfo=loc_tzinfo)
data["add_date"] = from_timestamp(data.get("add_date"), tzinfo=loc_tzinfo)
data["expiration_date"] = from_timestamp(data.get("expiration_date"), tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)
@@ -342,17 +332,12 @@ class ChatBoostUpdated(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatBoostUpdated"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatBoostUpdated":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["boost"] = ChatBoost.de_json(data.get("boost"), bot)
data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
data["boost"] = de_json_optional(data.get("boost"), ChatBoost, bot)
return super().de_json(data=data, bot=bot)
@@ -401,19 +386,14 @@ class ChatBoostRemoved(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatBoostRemoved"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatBoostRemoved":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["source"] = ChatBoostSource.de_json(data.get("source"), bot)
data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
data["source"] = de_json_optional(data.get("source"), ChatBoostSource, bot)
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["remove_date"] = from_timestamp(data["remove_date"], tzinfo=loc_tzinfo)
data["remove_date"] = from_timestamp(data.get("remove_date"), tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)
@@ -450,15 +430,10 @@ class UserChatBoosts(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["UserChatBoosts"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "UserChatBoosts":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["boosts"] = ChatBoost.de_list(data.get("boosts"), bot)
data["boosts"] = de_list_optional(data.get("boosts"), ChatBoost, bot)
return super().de_json(data=data, bot=bot)
+26 -18
View File
@@ -28,7 +28,7 @@ from telegram._chatlocation import ChatLocation
from telegram._chatpermissions import ChatPermissions
from telegram._files.chatphoto import ChatPhoto
from telegram._reaction import ReactionType
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
@@ -200,6 +200,9 @@ class ChatFullInfo(_ChatBase):
sent or forwarded to the channel chat. The field is available only for channel chats.
.. versionadded:: 21.4
can_send_gift (:obj:`bool`, optional): :obj:`True`, if gifts can be sent to the chat.
.. versionadded:: 21.11
Attributes:
id (:obj:`int`): Unique identifier for this chat.
@@ -354,6 +357,9 @@ class ChatFullInfo(_ChatBase):
sent or forwarded to the channel chat. The field is available only for channel chats.
.. versionadded:: 21.4
can_send_gift (:obj:`bool`): Optional. :obj:`True`, if gifts can be sent to the chat.
.. versionadded:: 21.11
.. _accent colors: https://core.telegram.org/bots/api#accent-colors
.. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups
@@ -369,6 +375,7 @@ class ChatFullInfo(_ChatBase):
"business_intro",
"business_location",
"business_opening_hours",
"can_send_gift",
"can_send_paid_media",
"can_set_sticker_set",
"custom_emoji_sticker_set_name",
@@ -445,6 +452,7 @@ class ChatFullInfo(_ChatBase):
linked_chat_id: Optional[int] = None,
location: Optional[ChatLocation] = None,
can_send_paid_media: Optional[bool] = None,
can_send_gift: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -510,17 +518,13 @@ class ChatFullInfo(_ChatBase):
self.business_location: Optional[BusinessLocation] = business_location
self.business_opening_hours: Optional[BusinessOpeningHours] = business_opening_hours
self.can_send_paid_media: Optional[bool] = can_send_paid_media
self.can_send_gift: Optional[bool] = can_send_gift
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatFullInfo"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatFullInfo":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
@@ -528,7 +532,7 @@ class ChatFullInfo(_ChatBase):
data.get("emoji_status_expiration_date"), tzinfo=loc_tzinfo
)
data["photo"] = ChatPhoto.de_json(data.get("photo"), bot)
data["photo"] = de_json_optional(data.get("photo"), ChatPhoto, bot)
from telegram import ( # pylint: disable=import-outside-toplevel
BusinessIntro,
@@ -537,16 +541,20 @@ class ChatFullInfo(_ChatBase):
Message,
)
data["pinned_message"] = Message.de_json(data.get("pinned_message"), bot)
data["permissions"] = ChatPermissions.de_json(data.get("permissions"), bot)
data["location"] = ChatLocation.de_json(data.get("location"), bot)
data["available_reactions"] = ReactionType.de_list(data.get("available_reactions"), bot)
data["birthdate"] = Birthdate.de_json(data.get("birthdate"), bot)
data["personal_chat"] = Chat.de_json(data.get("personal_chat"), bot)
data["business_intro"] = BusinessIntro.de_json(data.get("business_intro"), bot)
data["business_location"] = BusinessLocation.de_json(data.get("business_location"), bot)
data["business_opening_hours"] = BusinessOpeningHours.de_json(
data.get("business_opening_hours"), bot
data["pinned_message"] = de_json_optional(data.get("pinned_message"), Message, bot)
data["permissions"] = de_json_optional(data.get("permissions"), ChatPermissions, bot)
data["location"] = de_json_optional(data.get("location"), ChatLocation, bot)
data["available_reactions"] = de_list_optional(
data.get("available_reactions"), ReactionType, bot
)
data["birthdate"] = de_json_optional(data.get("birthdate"), Birthdate, bot)
data["personal_chat"] = de_json_optional(data.get("personal_chat"), Chat, bot)
data["business_intro"] = de_json_optional(data.get("business_intro"), BusinessIntro, bot)
data["business_location"] = de_json_optional(
data.get("business_location"), BusinessLocation, bot
)
data["business_opening_hours"] = de_json_optional(
data.get("business_opening_hours"), BusinessOpeningHours, bot
)
return super().de_json(data=data, bot=bot)
+3 -7
View File
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
@@ -177,19 +178,14 @@ class ChatInviteLink(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatInviteLink"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatInviteLink":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["creator"] = User.de_json(data.get("creator"), bot)
data["creator"] = de_json_optional(data.get("creator"), User, bot)
data["expire_date"] = from_timestamp(data.get("expire_date", None), tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)
+5 -9
View File
@@ -24,6 +24,7 @@ from telegram._chat import Chat
from telegram._chatinvitelink import ChatInviteLink
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
@@ -129,22 +130,17 @@ class ChatJoinRequest(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatJoinRequest"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatJoinRequest":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
data["invite_link"] = ChatInviteLink.de_json(data.get("invite_link"), bot)
data["invite_link"] = de_json_optional(data.get("invite_link"), ChatInviteLink, bot)
return super().de_json(data=data, bot=bot)
+3 -7
View File
@@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._files.location import Location
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -68,16 +69,11 @@ class ChatLocation(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatLocation"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatLocation":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["location"] = Location.de_json(data.get("location"), bot)
data["location"] = de_json_optional(data.get("location"), Location, bot)
return super().de_json(data=data, bot=bot)
+6 -9
View File
@@ -24,6 +24,8 @@ from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
@@ -98,22 +100,17 @@ class ChatMember(TelegramObject):
super().__init__(api_kwargs=api_kwargs)
# Required by all subclasses
self.user: User = user
self.status: str = status
self.status: str = enum.get_member(constants.ChatMemberStatus, status, status)
self._id_attrs = (self.user, self.status)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatMember"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatMember":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
_class_mapping: dict[str, type[ChatMember]] = {
cls.OWNER: ChatMemberOwner,
cls.ADMINISTRATOR: ChatMemberAdministrator,
@@ -126,12 +123,12 @@ class ChatMember(TelegramObject):
if cls is ChatMember and data.get("status") in _class_mapping:
return _class_mapping[data.pop("status")].de_json(data=data, bot=bot)
data["user"] = User.de_json(data.get("user"), bot)
data["user"] = de_json_optional(data.get("user"), User, bot)
if "until_date" in data:
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["until_date"] = from_timestamp(data["until_date"], tzinfo=loc_tzinfo)
data["until_date"] = from_timestamp(data.get("until_date"), tzinfo=loc_tzinfo)
# This is a deprecated field that TG still returns for backwards compatibility
# Let's filter it out to speed up the de-json process
+7 -11
View File
@@ -25,6 +25,7 @@ from telegram._chatinvitelink import ChatInviteLink
from telegram._chatmember import ChatMember
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
@@ -141,24 +142,19 @@ class ChatMemberUpdated(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatMemberUpdated"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatMemberUpdated":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
data["old_chat_member"] = ChatMember.de_json(data.get("old_chat_member"), bot)
data["new_chat_member"] = ChatMember.de_json(data.get("new_chat_member"), bot)
data["invite_link"] = ChatInviteLink.de_json(data.get("invite_link"), bot)
data["old_chat_member"] = de_json_optional(data.get("old_chat_member"), ChatMember, bot)
data["new_chat_member"] = de_json_optional(data.get("new_chat_member"), ChatMember, bot)
data["invite_link"] = de_json_optional(data.get("invite_link"), ChatInviteLink, bot)
return super().de_json(data=data, bot=bot)
+1 -6
View File
@@ -231,15 +231,10 @@ class ChatPermissions(TelegramObject):
return cls(*(14 * (False,)))
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatPermissions"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatPermissions":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
# Let's filter it out to speed up the de-json process
+4 -8
View File
@@ -24,6 +24,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._files.location import Location
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -92,18 +93,13 @@ class ChosenInlineResult(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChosenInlineResult"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChosenInlineResult":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Required
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
# Optionals
data["location"] = Location.de_json(data.get("location"), bot)
data["location"] = de_json_optional(data.get("location"), Location, bot)
return super().de_json(data=data, bot=bot)
+4 -6
View File
@@ -21,6 +21,7 @@ from typing import TYPE_CHECKING, Optional, TypeVar
from telegram._files._basemedium import _BaseMedium
from telegram._files.photosize import PhotoSize
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -82,17 +83,14 @@ class _BaseThumbedMedium(_BaseMedium):
@classmethod
def de_json(
cls: type[ThumbedMT_co], data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional[ThumbedMT_co]:
cls: type[ThumbedMT_co], data: JSONDict, bot: Optional["Bot"] = None
) -> ThumbedMT_co:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# In case this wasn't already done by the subclass
if not isinstance(data.get("thumbnail"), PhotoSize):
data["thumbnail"] = PhotoSize.de_json(data.get("thumbnail"), bot)
data["thumbnail"] = de_json_optional(data.get("thumbnail"), PhotoSize, bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
+51 -1
View File
@@ -214,6 +214,13 @@ class InputPaidMediaVideo(InputPaidMedia):
Lastly you can pass an existing :class:`telegram.Video` object to send.
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
cover (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): Cover for the video in the message. |fileinputnopath|
.. versionchanged:: 21.11
start_timestamp (:obj:`int`, optional): Start timestamp for the video in the message
.. versionchanged:: 21.11
width (:obj:`int`, optional): Video width.
height (:obj:`int`, optional): Video height.
duration (:obj:`int`, optional): Video duration in seconds.
@@ -225,6 +232,13 @@ class InputPaidMediaVideo(InputPaidMedia):
:tg-const:`telegram.constants.InputPaidMediaType.VIDEO`.
media (:obj:`str` | :class:`telegram.InputFile`): Video to send.
thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
cover (:class:`telegram.InputFile`): Optional. Cover for the video in the message.
|fileinputnopath|
.. versionchanged:: 21.11
start_timestamp (:obj:`int`): Optional. Start timestamp for the video in the message
.. versionchanged:: 21.11
width (:obj:`int`): Optional. Video width.
height (:obj:`int`): Optional. Video height.
duration (:obj:`int`): Optional. Video duration in seconds.
@@ -232,7 +246,15 @@ class InputPaidMediaVideo(InputPaidMedia):
suitable for streaming.
"""
__slots__ = ("duration", "height", "supports_streaming", "thumbnail", "width")
__slots__ = (
"cover",
"duration",
"height",
"start_timestamp",
"supports_streaming",
"thumbnail",
"width",
)
def __init__(
self,
@@ -242,6 +264,8 @@ class InputPaidMediaVideo(InputPaidMedia):
height: Optional[int] = None,
duration: Optional[int] = None,
supports_streaming: Optional[bool] = None,
cover: Optional[FileInput] = None,
start_timestamp: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -264,6 +288,10 @@ class InputPaidMediaVideo(InputPaidMedia):
self.height: Optional[int] = height
self.duration: Optional[int] = duration
self.supports_streaming: Optional[bool] = supports_streaming
self.cover: Optional[Union[InputFile, str]] = (
parse_file_input(cover, attach=True, local_mode=True) if cover else None
)
self.start_timestamp: Optional[int] = start_timestamp
class InputMediaAnimation(InputMedia):
@@ -536,6 +564,13 @@ class InputMediaVideo(InputMedia):
optional): |thumbdocstringnopath|
.. versionadded:: 20.2
cover (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): Cover for the video in the message. |fileinputnopath|
.. versionchanged:: 21.11
start_timestamp (:obj:`int`, optional): Start timestamp for the video in the message
.. versionchanged:: 21.11
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
.. versionadded:: 21.3
@@ -568,13 +603,22 @@ class InputMediaVideo(InputMedia):
show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med|
.. versionadded:: 21.3
cover (:class:`telegram.InputFile`): Optional. Cover for the video in the message.
|fileinputnopath|
.. versionchanged:: 21.11
start_timestamp (:obj:`int`): Optional. Start timestamp for the video in the message
.. versionchanged:: 21.11
"""
__slots__ = (
"cover",
"duration",
"has_spoiler",
"height",
"show_caption_above_media",
"start_timestamp",
"supports_streaming",
"thumbnail",
"width",
@@ -594,6 +638,8 @@ class InputMediaVideo(InputMedia):
has_spoiler: Optional[bool] = None,
thumbnail: Optional[FileInput] = None,
show_caption_above_media: Optional[bool] = None,
cover: Optional[FileInput] = None,
start_timestamp: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -625,6 +671,10 @@ class InputMediaVideo(InputMedia):
self.supports_streaming: Optional[bool] = supports_streaming
self.has_spoiler: Optional[bool] = has_spoiler
self.show_caption_above_media: Optional[bool] = show_caption_above_media
self.cover: Optional[Union[InputFile, str]] = (
parse_file_input(cover, attach=True, local_mode=True) if cover else None
)
self.start_timestamp: Optional[int] = start_timestamp
class InputMediaAudio(InputMedia):
+4 -4
View File
@@ -61,8 +61,8 @@ class InputSticker(TelegramObject):
format (:obj:`str`): Format of the added sticker, must be one of
:tg-const:`telegram.constants.StickerFormat.STATIC` for a
``.WEBP`` or ``.PNG`` image, :tg-const:`telegram.constants.StickerFormat.ANIMATED`
for a ``.TGS`` animation, :tg-const:`telegram.constants.StickerFormat.VIDEO` for a WEBM
video.
for a ``.TGS`` animation, :tg-const:`telegram.constants.StickerFormat.VIDEO` for a
``.WEBM`` video.
.. versionadded:: 21.1
@@ -84,8 +84,8 @@ class InputSticker(TelegramObject):
format (:obj:`str`): Format of the added sticker, must be one of
:tg-const:`telegram.constants.StickerFormat.STATIC` for a
``.WEBP`` or ``.PNG`` image, :tg-const:`telegram.constants.StickerFormat.ANIMATED`
for a ``.TGS`` animation, :tg-const:`telegram.constants.StickerFormat.VIDEO` for a WEBM
video.
for a ``.TGS`` animation, :tg-const:`telegram.constants.StickerFormat.VIDEO` for a
``.WEBM`` video.
.. versionadded:: 21.1
"""
+9 -15
View File
@@ -26,7 +26,7 @@ from telegram._files.file import File
from telegram._files.photosize import PhotoSize
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -194,16 +194,13 @@ class Sticker(_BaseThumbedMedium):
""":const:`telegram.constants.StickerType.CUSTOM_EMOJI`"""
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Sticker"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Sticker":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["thumbnail"] = PhotoSize.de_json(data.get("thumbnail"), bot)
data["mask_position"] = MaskPosition.de_json(data.get("mask_position"), bot)
data["premium_animation"] = File.de_json(data.get("premium_animation"), bot)
data["thumbnail"] = de_json_optional(data.get("thumbnail"), PhotoSize, bot)
data["mask_position"] = de_json_optional(data.get("mask_position"), MaskPosition, bot)
data["premium_animation"] = de_json_optional(data.get("premium_animation"), File, bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
@@ -306,15 +303,12 @@ class StickerSet(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["StickerSet"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "StickerSet":
"""See :meth:`telegram.TelegramObject.de_json`."""
if not data:
return None
data = cls._parse_data(data)
data["thumbnail"] = PhotoSize.de_json(data.get("thumbnail"), bot)
data["stickers"] = Sticker.de_list(data.get("stickers"), bot)
data["thumbnail"] = de_json_optional(data.get("thumbnail"), PhotoSize, bot)
data["stickers"] = de_list_optional(data.get("stickers"), Sticker, bot)
api_kwargs = {}
# These are deprecated fields that TG still returns for backwards compatibility
+3 -5
View File
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._files.location import Location
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -103,13 +104,10 @@ class Venue(TelegramObject):
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Venue"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Venue":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["location"] = Location.de_json(data.get("location"), bot)
data["location"] = de_json_optional(data.get("location"), Location, bot)
return super().de_json(data=data, bot=bot)
+42 -2
View File
@@ -17,12 +17,17 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Video."""
from typing import Optional
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from telegram._files._basethumbedmedium import _BaseThumbedMedium
from telegram._files.photosize import PhotoSize
from telegram._utils.argumentparsing import de_list_optional, parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class Video(_BaseThumbedMedium):
"""This object represents a video file.
@@ -48,6 +53,13 @@ class Video(_BaseThumbedMedium):
thumbnail (:class:`telegram.PhotoSize`, optional): Video thumbnail.
.. versionadded:: 20.2
cover (Sequence[:class:`telegram.PhotoSize`], optional): Available sizes of the cover of
the video in the message.
.. versionadded:: 21.11
start_timestamp (:obj:`int`, optional): Timestamp in seconds from which the video
will play in the message
.. versionadded:: 21.11
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
@@ -64,9 +76,24 @@ class Video(_BaseThumbedMedium):
thumbnail (:class:`telegram.PhotoSize`): Optional. Video thumbnail.
.. versionadded:: 20.2
cover (tuple[:class:`telegram.PhotoSize`]): Optional, Available sizes of the cover of
the video in the message.
.. versionadded:: 21.11
start_timestamp (:obj:`int`): Optional, Timestamp in seconds from which the video
will play in the message
.. versionadded:: 21.11
"""
__slots__ = ("duration", "file_name", "height", "mime_type", "width")
__slots__ = (
"cover",
"duration",
"file_name",
"height",
"mime_type",
"start_timestamp",
"width",
)
def __init__(
self,
@@ -79,6 +106,8 @@ class Video(_BaseThumbedMedium):
file_size: Optional[int] = None,
file_name: Optional[str] = None,
thumbnail: Optional[PhotoSize] = None,
cover: Optional[Sequence[PhotoSize]] = None,
start_timestamp: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -97,3 +126,14 @@ class Video(_BaseThumbedMedium):
# Optional
self.mime_type: Optional[str] = mime_type
self.file_name: Optional[str] = file_name
self.cover: Optional[Sequence[PhotoSize]] = parse_sequence_arg(cover)
self.start_timestamp: Optional[int] = start_timestamp
@classmethod
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Video":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
data["cover"] = de_list_optional(data.get("cover"), PhotoSize, bot)
return super().de_json(data=data, bot=bot)
+5 -8
View File
@@ -24,7 +24,7 @@ from telegram._files.animation import Animation
from telegram._files.photosize import PhotoSize
from telegram._messageentity import MessageEntity
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.strings import TextEncoding
from telegram._utils.types import JSONDict
@@ -124,16 +124,13 @@ class Game(TelegramObject):
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Game"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Game":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
data["text_entities"] = MessageEntity.de_list(data.get("text_entities"), bot)
data["animation"] = Animation.de_json(data.get("animation"), bot)
data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot)
data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot)
data["animation"] = de_json_optional(data.get("animation"), Animation, bot)
return super().de_json(data=data, bot=bot)
+3 -7
View File
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -61,15 +62,10 @@ class GameHighScore(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["GameHighScore"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "GameHighScore":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["user"] = User.de_json(data.get("user"), bot)
data["user"] = de_json_optional(data.get("user"), User, bot)
return super().de_json(data=data, bot=bot)
+5 -11
View File
@@ -23,7 +23,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._files.sticker import Sticker
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -99,14 +99,11 @@ class Gift(TelegramObject):
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Gift"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Gift":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["sticker"] = Sticker.de_json(data.get("sticker"), bot)
data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot)
return super().de_json(data=data, bot=bot)
@@ -142,12 +139,9 @@ class Gifts(TelegramObject):
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Gifts"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Gifts":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["gifts"] = Gift.de_list(data.get("gifts"), bot)
data["gifts"] = de_list_optional(data.get("gifts"), Gift, bot)
return super().de_json(data=data, bot=bot)
+8 -23
View File
@@ -24,7 +24,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._chat import Chat
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
@@ -137,19 +137,14 @@ class Giveaway(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["Giveaway"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Giveaway":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["chats"] = tuple(Chat.de_list(data.get("chats"), bot))
data["chats"] = de_list_optional(data.get("chats"), Chat, bot)
data["winners_selection_date"] = from_timestamp(
data.get("winners_selection_date"), tzinfo=loc_tzinfo
)
@@ -299,20 +294,15 @@ class GiveawayWinners(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["GiveawayWinners"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "GiveawayWinners":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["winners"] = tuple(User.de_list(data.get("winners"), bot))
data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
data["winners"] = de_list_optional(data.get("winners"), User, bot)
data["winners_selection_date"] = from_timestamp(
data.get("winners_selection_date"), tzinfo=loc_tzinfo
)
@@ -376,18 +366,13 @@ class GiveawayCompleted(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["GiveawayCompleted"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "GiveawayCompleted":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
# Unfortunately, this needs to be here due to cyclic imports
from telegram._message import Message # pylint: disable=import-outside-toplevel
data["giveaway_message"] = Message.de_json(data.get("giveaway_message"), bot)
data["giveaway_message"] = de_json_optional(data.get("giveaway_message"), Message, bot)
return super().de_json(data=data, bot=bot)
+8 -12
View File
@@ -26,6 +26,7 @@ from telegram._games.callbackgame import CallbackGame
from telegram._loginurl import LoginUrl
from telegram._switchinlinequerychosenchat import SwitchInlineQueryChosenChat
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
from telegram._webappinfo import WebAppInfo
@@ -296,22 +297,17 @@ class InlineKeyboardButton(TelegramObject):
)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["InlineKeyboardButton"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "InlineKeyboardButton":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["login_url"] = LoginUrl.de_json(data.get("login_url"), bot)
data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot)
data["callback_game"] = CallbackGame.de_json(data.get("callback_game"), bot)
data["switch_inline_query_chosen_chat"] = SwitchInlineQueryChosenChat.de_json(
data.get("switch_inline_query_chosen_chat"), bot
data["login_url"] = de_json_optional(data.get("login_url"), LoginUrl, bot)
data["web_app"] = de_json_optional(data.get("web_app"), WebAppInfo, bot)
data["callback_game"] = de_json_optional(data.get("callback_game"), CallbackGame, bot)
data["switch_inline_query_chosen_chat"] = de_json_optional(
data.get("switch_inline_query_chosen_chat"), SwitchInlineQueryChosenChat, bot
)
data["copy_text"] = CopyTextButton.de_json(data.get("copy_text"), bot)
data["copy_text"] = de_json_optional(data.get("copy_text"), CopyTextButton, bot)
return super().de_json(data=data, bot=bot)
+1 -5
View File
@@ -91,12 +91,8 @@ class InlineKeyboardMarkup(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["InlineKeyboardMarkup"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "InlineKeyboardMarkup":
"""See :meth:`telegram.TelegramObject.de_json`."""
if not data:
return None
keyboard = []
for row in data["inline_keyboard"]:
+6 -10
View File
@@ -27,8 +27,9 @@ from telegram._files.location import Location
from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
from telegram._utils.types import JSONDict, ODVInput, TimePeriod
if TYPE_CHECKING:
from telegram import Bot, InlineQueryResult
@@ -126,17 +127,12 @@ class InlineQuery(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["InlineQuery"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "InlineQuery":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["location"] = Location.de_json(data.get("location"), bot)
data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
data["location"] = de_json_optional(data.get("location"), Location, bot)
return super().de_json(data=data, bot=bot)
@@ -145,7 +141,7 @@ class InlineQuery(TelegramObject):
results: Union[
Sequence["InlineQueryResult"], Callable[[int], Optional[Sequence["InlineQueryResult"]]]
],
cache_time: Optional[int] = None,
cache_time: Optional[TimePeriod] = None,
is_personal: Optional[bool] = None,
next_offset: Optional[str] = None,
button: Optional[InlineQueryResultsButton] = None,
+5 -25
View File
@@ -23,9 +23,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._inline.inlinequeryresult import InlineQueryResult
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
from telegram.constants import InlineQueryResultType
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import InputMessageContent
@@ -40,6 +38,9 @@ class InlineQueryResultArticle(InlineQueryResult):
.. versionchanged:: 20.5
Removed the deprecated arguments and attributes ``thumb_*``.
.. versionchanged:: 21.11
Removed the deprecated argument and attribute ``hide_url``.
Args:
id (:obj:`str`): Unique identifier for this result,
:tg-const:`telegram.InlineQueryResult.MIN_ID_LENGTH`-
@@ -50,12 +51,9 @@ class InlineQueryResultArticle(InlineQueryResult):
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message.
url (:obj:`str`, optional): URL of the result.
hide_url (:obj:`bool`, optional): Pass :obj:`True`, if you don't want the URL to be shown
in the message.
.. deprecated:: 21.10
This attribute will be removed in future PTB versions. Pass an empty string as URL
instead.
Tip:
Pass an empty string as URL if you don't want the URL to be shown in the message.
description (:obj:`str`, optional): Short description of the result.
thumbnail_url (:obj:`str`, optional): Url of the thumbnail for the result.
@@ -78,12 +76,6 @@ class InlineQueryResultArticle(InlineQueryResult):
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
url (:obj:`str`): Optional. URL of the result.
hide_url (:obj:`bool`): Optional. Pass :obj:`True`, if you don't want the URL to be shown
in the message.
.. deprecated:: 21.10
This attribute will be removed in future PTB versions. Pass an empty string as URL
instead.
description (:obj:`str`): Optional. Short description of the result.
thumbnail_url (:obj:`str`): Optional. Url of the thumbnail for the result.
@@ -99,7 +91,6 @@ class InlineQueryResultArticle(InlineQueryResult):
__slots__ = (
"description",
"hide_url",
"input_message_content",
"reply_markup",
"thumbnail_height",
@@ -116,7 +107,6 @@ class InlineQueryResultArticle(InlineQueryResult):
input_message_content: "InputMessageContent",
reply_markup: Optional[InlineKeyboardMarkup] = None,
url: Optional[str] = None,
hide_url: Optional[bool] = None,
description: Optional[str] = None,
thumbnail_url: Optional[str] = None,
thumbnail_width: Optional[int] = None,
@@ -133,16 +123,6 @@ class InlineQueryResultArticle(InlineQueryResult):
# Optional
self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup
self.url: Optional[str] = url
if hide_url is not None:
warn(
PTBDeprecationWarning(
"21.10",
"The argument `hide_url` will be removed in future PTB"
"versions. Pass an empty string as URL instead.",
),
stacklevel=2,
)
self.hide_url: Optional[bool] = hide_url
self.description: Optional[str] = description
self.thumbnail_url: Optional[str] = thumbnail_url
self.thumbnail_width: Optional[int] = thumbnail_width
+2 -2
View File
@@ -47,7 +47,7 @@ class InlineQueryResultGif(InlineQueryResult):
id (:obj:`str`): Unique identifier for this result,
:tg-const:`telegram.InlineQueryResult.MIN_ID_LENGTH`-
:tg-const:`telegram.InlineQueryResult.MAX_ID_LENGTH` Bytes.
gif_url (:obj:`str`): A valid URL for the GIF file. File size must not exceed 1MB.
gif_url (:obj:`str`): A valid URL for the GIF file.
gif_width (:obj:`int`, optional): Width of the GIF.
gif_height (:obj:`int`, optional): Height of the GIF.
gif_duration (:obj:`int`, optional): Duration of the GIF in seconds.
@@ -86,7 +86,7 @@ class InlineQueryResultGif(InlineQueryResult):
id (:obj:`str`): Unique identifier for this result,
:tg-const:`telegram.InlineQueryResult.MIN_ID_LENGTH`-
:tg-const:`telegram.InlineQueryResult.MAX_ID_LENGTH` Bytes.
gif_url (:obj:`str`): A valid URL for the GIF file. File size must not exceed 1MB.
gif_url (:obj:`str`): A valid URL for the GIF file.
gif_width (:obj:`int`): Optional. Width of the GIF.
gif_height (:obj:`int`): Optional. Height of the GIF.
gif_duration (:obj:`int`): Optional. Duration of the GIF in seconds.
@@ -48,7 +48,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
id (:obj:`str`): Unique identifier for this result,
:tg-const:`telegram.InlineQueryResult.MIN_ID_LENGTH`-
:tg-const:`telegram.InlineQueryResult.MAX_ID_LENGTH` Bytes.
mpeg4_url (:obj:`str`): A valid URL for the MP4 file. File size must not exceed 1MB.
mpeg4_url (:obj:`str`): A valid URL for the MP4 file.
mpeg4_width (:obj:`int`, optional): Video width.
mpeg4_height (:obj:`int`, optional): Video height.
mpeg4_duration (:obj:`int`, optional): Video duration in seconds.
@@ -88,7 +88,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
id (:obj:`str`): Unique identifier for this result,
:tg-const:`telegram.InlineQueryResult.MIN_ID_LENGTH`-
:tg-const:`telegram.InlineQueryResult.MAX_ID_LENGTH` Bytes.
mpeg4_url (:obj:`str`): A valid URL for the MP4 file. File size must not exceed 1MB.
mpeg4_url (:obj:`str`): A valid URL for the MP4 file.
mpeg4_width (:obj:`int`): Optional. Video width.
mpeg4_height (:obj:`int`): Optional. Video height.
mpeg4_duration (:obj:`int`): Optional. Video duration in seconds.
+3 -6
View File
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
from telegram._webappinfo import WebAppInfo
@@ -97,14 +98,10 @@ class InlineQueryResultsButton(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["InlineQueryResultsButton"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "InlineQueryResultsButton":
"""See :meth:`telegram.TelegramObject.de_json`."""
if not data:
return None
data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot)
data["web_app"] = de_json_optional(data.get("web_app"), WebAppInfo, bot)
return super().de_json(data=data, bot=bot)
+12 -16
View File
@@ -22,7 +22,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._inline.inputmessagecontent import InputMessageContent
from telegram._payment.labeledprice import LabeledPrice
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_list_optional, parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -35,9 +35,11 @@ class InputInvoiceMessageContent(InputMessageContent):
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`title`, :attr:`description`, :attr:`payload`,
:attr:`provider_token`, :attr:`currency` and :attr:`prices` are equal.
:attr:`currency` and :attr:`prices` are equal.
.. versionadded:: 13.5
.. versionchanged:: 21.11
:attr:`provider_token` is no longer considered for equality comparison.
Args:
title (:obj:`str`): Product name. :tg-const:`telegram.Invoice.MIN_TITLE_LENGTH`-
@@ -49,13 +51,13 @@ class InputInvoiceMessageContent(InputMessageContent):
:tg-const:`telegram.Invoice.MIN_PAYLOAD_LENGTH`-
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be displayed
to the user, use it for your internal processes.
provider_token (:obj:`str`): Payment provider token, obtained via
provider_token (:obj:`str`, optional): Payment provider token, obtained via
`@Botfather <https://t.me/Botfather>`_. Pass an empty string for payments in
|tg_stars|.
.. deprecated:: 21.3
As of Bot API 7.4, this parameter is now optional and future versions of the
library will make it optional as well.
.. versionchanged:: 21.11
Bot API 7.4 made this parameter is optional and this is now reflected in the
class signature.
currency (:obj:`str`): Three-letter ISO 4217 currency code, see more on
`currencies <https://core.telegram.org/bots/payments#supported-currencies>`_.
Pass ``XTR`` for payments in |tg_stars|.
@@ -199,9 +201,9 @@ class InputInvoiceMessageContent(InputMessageContent):
title: str,
description: str,
payload: str,
provider_token: Optional[str], # This arg is now optional since Bot API 7.4
currency: str,
prices: Sequence[LabeledPrice],
provider_token: Optional[str] = None,
max_tip_amount: Optional[int] = None,
suggested_tip_amounts: Optional[Sequence[int]] = None,
provider_data: Optional[str] = None,
@@ -225,10 +227,10 @@ class InputInvoiceMessageContent(InputMessageContent):
self.title: str = title
self.description: str = description
self.payload: str = payload
self.provider_token: Optional[str] = provider_token
self.currency: str = currency
self.prices: tuple[LabeledPrice, ...] = parse_sequence_arg(prices)
# Optionals
self.provider_token: Optional[str] = provider_token
self.max_tip_amount: Optional[int] = max_tip_amount
self.suggested_tip_amounts: tuple[int, ...] = parse_sequence_arg(suggested_tip_amounts)
self.provider_data: Optional[str] = provider_data
@@ -248,21 +250,15 @@ class InputInvoiceMessageContent(InputMessageContent):
self.title,
self.description,
self.payload,
self.provider_token,
self.currency,
self.prices,
)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["InputInvoiceMessageContent"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "InputInvoiceMessageContent":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["prices"] = LabeledPrice.de_list(data.get("prices"), bot)
data["prices"] = de_list_optional(data.get("prices"), LabeledPrice, bot)
return super().de_json(data=data, bot=bot)
+1 -6
View File
@@ -67,15 +67,10 @@ class PreparedInlineMessage(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PreparedInlineMessage"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PreparedInlineMessage":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["expiration_date"] = from_timestamp(data.get("expiration_date"), tzinfo=loc_tzinfo)
+12 -10
View File
@@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._keyboardbuttonpolltype import KeyboardButtonPollType
from telegram._keyboardbuttonrequest import KeyboardButtonRequestChat, KeyboardButtonRequestUsers
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
from telegram._webappinfo import WebAppInfo
@@ -168,19 +169,20 @@ class KeyboardButton(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["KeyboardButton"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "KeyboardButton":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["request_poll"] = KeyboardButtonPollType.de_json(data.get("request_poll"), bot)
data["request_users"] = KeyboardButtonRequestUsers.de_json(data.get("request_users"), bot)
data["request_chat"] = KeyboardButtonRequestChat.de_json(data.get("request_chat"), bot)
data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot)
data["request_poll"] = de_json_optional(
data.get("request_poll"), KeyboardButtonPollType, bot
)
data["request_users"] = de_json_optional(
data.get("request_users"), KeyboardButtonRequestUsers, bot
)
data["request_chat"] = de_json_optional(
data.get("request_chat"), KeyboardButtonRequestChat, bot
)
data["web_app"] = de_json_optional(data.get("web_app"), WebAppInfo, bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
+6 -10
View File
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._chatadministratorrights import ChatAdministratorRights
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -257,20 +258,15 @@ class KeyboardButtonRequestChat(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["KeyboardButtonRequestChat"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "KeyboardButtonRequestChat":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["user_administrator_rights"] = ChatAdministratorRights.de_json(
data.get("user_administrator_rights"), bot
data["user_administrator_rights"] = de_json_optional(
data.get("user_administrator_rights"), ChatAdministratorRights, bot
)
data["bot_administrator_rights"] = ChatAdministratorRights.de_json(
data.get("bot_administrator_rights"), bot
data["bot_administrator_rights"] = de_json_optional(
data.get("bot_administrator_rights"), ChatAdministratorRights, bot
)
return super().de_json(data=data, bot=bot)
+4 -16
View File
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
from telegram._webappinfo import WebAppInfo
@@ -69,9 +70,7 @@ class MenuButton(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["MenuButton"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "MenuButton":
"""Converts JSON data to the appropriate :class:`MenuButton` object, i.e. takes
care of selecting the correct subclass.
@@ -89,12 +88,6 @@ class MenuButton(TelegramObject):
"""
data = cls._parse_data(data)
if data is None:
return None
if not data and cls is MenuButton:
return None
_class_mapping: dict[str, type[MenuButton]] = {
cls.COMMANDS: MenuButtonCommands,
cls.WEB_APP: MenuButtonWebApp,
@@ -172,16 +165,11 @@ class MenuButtonWebApp(MenuButton):
self._id_attrs = (self.type, self.text, self.web_app)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["MenuButtonWebApp"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "MenuButtonWebApp":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot)
data["web_app"] = de_json_optional(data.get("web_app"), WebAppInfo, bot)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
+121 -88
View File
@@ -65,7 +65,7 @@ from telegram._shared import ChatShared, UsersShared
from telegram._story import Story
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.entities import parse_message_entities, parse_message_entity
@@ -77,6 +77,7 @@ from telegram._utils.types import (
MarkdownVersion,
ODVInput,
ReplyMarkup,
TimePeriod,
)
from telegram._utils.warnings import warn
from telegram._videochat import (
@@ -191,9 +192,6 @@ class MaybeInaccessibleMessage(TelegramObject):
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
if cls is MaybeInaccessibleMessage:
if data["date"] == 0:
return InaccessibleMessage.de_json(data=data, bot=bot)
@@ -206,9 +204,9 @@ class MaybeInaccessibleMessage(TelegramObject):
if data["date"] == 0:
data["date"] = ZERO_DATE
else:
data["date"] = from_timestamp(data["date"], tzinfo=loc_tzinfo)
data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs)
@@ -1251,83 +1249,100 @@ class Message(MaybeInaccessibleMessage):
return None
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Message"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Message":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["sender_chat"] = Chat.de_json(data.get("sender_chat"), bot)
data["entities"] = MessageEntity.de_list(data.get("entities"), bot)
data["caption_entities"] = MessageEntity.de_list(data.get("caption_entities"), bot)
data["reply_to_message"] = Message.de_json(data.get("reply_to_message"), bot)
data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
data["sender_chat"] = de_json_optional(data.get("sender_chat"), Chat, bot)
data["entities"] = de_list_optional(data.get("entities"), MessageEntity, bot)
data["caption_entities"] = de_list_optional(
data.get("caption_entities"), MessageEntity, bot
)
data["reply_to_message"] = de_json_optional(data.get("reply_to_message"), Message, bot)
data["edit_date"] = from_timestamp(data.get("edit_date"), tzinfo=loc_tzinfo)
data["audio"] = Audio.de_json(data.get("audio"), bot)
data["document"] = Document.de_json(data.get("document"), bot)
data["animation"] = Animation.de_json(data.get("animation"), bot)
data["game"] = Game.de_json(data.get("game"), bot)
data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
data["sticker"] = Sticker.de_json(data.get("sticker"), bot)
data["story"] = Story.de_json(data.get("story"), bot)
data["video"] = Video.de_json(data.get("video"), bot)
data["voice"] = Voice.de_json(data.get("voice"), bot)
data["video_note"] = VideoNote.de_json(data.get("video_note"), bot)
data["contact"] = Contact.de_json(data.get("contact"), bot)
data["location"] = Location.de_json(data.get("location"), bot)
data["venue"] = Venue.de_json(data.get("venue"), bot)
data["new_chat_members"] = User.de_list(data.get("new_chat_members"), bot)
data["left_chat_member"] = User.de_json(data.get("left_chat_member"), bot)
data["new_chat_photo"] = PhotoSize.de_list(data.get("new_chat_photo"), bot)
data["message_auto_delete_timer_changed"] = MessageAutoDeleteTimerChanged.de_json(
data.get("message_auto_delete_timer_changed"), bot
data["audio"] = de_json_optional(data.get("audio"), Audio, bot)
data["document"] = de_json_optional(data.get("document"), Document, bot)
data["animation"] = de_json_optional(data.get("animation"), Animation, bot)
data["game"] = de_json_optional(data.get("game"), Game, bot)
data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot)
data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot)
data["story"] = de_json_optional(data.get("story"), Story, bot)
data["video"] = de_json_optional(data.get("video"), Video, bot)
data["voice"] = de_json_optional(data.get("voice"), Voice, bot)
data["video_note"] = de_json_optional(data.get("video_note"), VideoNote, bot)
data["contact"] = de_json_optional(data.get("contact"), Contact, bot)
data["location"] = de_json_optional(data.get("location"), Location, bot)
data["venue"] = de_json_optional(data.get("venue"), Venue, bot)
data["new_chat_members"] = de_list_optional(data.get("new_chat_members"), User, bot)
data["left_chat_member"] = de_json_optional(data.get("left_chat_member"), User, bot)
data["new_chat_photo"] = de_list_optional(data.get("new_chat_photo"), PhotoSize, bot)
data["message_auto_delete_timer_changed"] = de_json_optional(
data.get("message_auto_delete_timer_changed"), MessageAutoDeleteTimerChanged, bot
)
data["pinned_message"] = MaybeInaccessibleMessage.de_json(data.get("pinned_message"), bot)
data["invoice"] = Invoice.de_json(data.get("invoice"), bot)
data["successful_payment"] = SuccessfulPayment.de_json(data.get("successful_payment"), bot)
data["passport_data"] = PassportData.de_json(data.get("passport_data"), bot)
data["poll"] = Poll.de_json(data.get("poll"), bot)
data["dice"] = Dice.de_json(data.get("dice"), bot)
data["via_bot"] = User.de_json(data.get("via_bot"), bot)
data["proximity_alert_triggered"] = ProximityAlertTriggered.de_json(
data.get("proximity_alert_triggered"), bot
data["pinned_message"] = de_json_optional(
data.get("pinned_message"), MaybeInaccessibleMessage, bot
)
data["reply_markup"] = InlineKeyboardMarkup.de_json(data.get("reply_markup"), bot)
data["video_chat_scheduled"] = VideoChatScheduled.de_json(
data.get("video_chat_scheduled"), bot
data["invoice"] = de_json_optional(data.get("invoice"), Invoice, bot)
data["successful_payment"] = de_json_optional(
data.get("successful_payment"), SuccessfulPayment, bot
)
data["video_chat_started"] = VideoChatStarted.de_json(data.get("video_chat_started"), bot)
data["video_chat_ended"] = VideoChatEnded.de_json(data.get("video_chat_ended"), bot)
data["video_chat_participants_invited"] = VideoChatParticipantsInvited.de_json(
data.get("video_chat_participants_invited"), bot
data["passport_data"] = de_json_optional(data.get("passport_data"), PassportData, bot)
data["poll"] = de_json_optional(data.get("poll"), Poll, bot)
data["dice"] = de_json_optional(data.get("dice"), Dice, bot)
data["via_bot"] = de_json_optional(data.get("via_bot"), User, bot)
data["proximity_alert_triggered"] = de_json_optional(
data.get("proximity_alert_triggered"), ProximityAlertTriggered, bot
)
data["web_app_data"] = WebAppData.de_json(data.get("web_app_data"), bot)
data["forum_topic_closed"] = ForumTopicClosed.de_json(data.get("forum_topic_closed"), bot)
data["forum_topic_created"] = ForumTopicCreated.de_json(
data.get("forum_topic_created"), bot
data["reply_markup"] = de_json_optional(
data.get("reply_markup"), InlineKeyboardMarkup, bot
)
data["forum_topic_reopened"] = ForumTopicReopened.de_json(
data.get("forum_topic_reopened"), bot
data["video_chat_scheduled"] = de_json_optional(
data.get("video_chat_scheduled"), VideoChatScheduled, bot
)
data["forum_topic_edited"] = ForumTopicEdited.de_json(data.get("forum_topic_edited"), bot)
data["general_forum_topic_hidden"] = GeneralForumTopicHidden.de_json(
data.get("general_forum_topic_hidden"), bot
data["video_chat_started"] = de_json_optional(
data.get("video_chat_started"), VideoChatStarted, bot
)
data["general_forum_topic_unhidden"] = GeneralForumTopicUnhidden.de_json(
data.get("general_forum_topic_unhidden"), bot
data["video_chat_ended"] = de_json_optional(
data.get("video_chat_ended"), VideoChatEnded, bot
)
data["write_access_allowed"] = WriteAccessAllowed.de_json(
data.get("write_access_allowed"), bot
data["video_chat_participants_invited"] = de_json_optional(
data.get("video_chat_participants_invited"), VideoChatParticipantsInvited, bot
)
data["web_app_data"] = de_json_optional(data.get("web_app_data"), WebAppData, bot)
data["forum_topic_closed"] = de_json_optional(
data.get("forum_topic_closed"), ForumTopicClosed, bot
)
data["forum_topic_created"] = de_json_optional(
data.get("forum_topic_created"), ForumTopicCreated, bot
)
data["forum_topic_reopened"] = de_json_optional(
data.get("forum_topic_reopened"), ForumTopicReopened, bot
)
data["forum_topic_edited"] = de_json_optional(
data.get("forum_topic_edited"), ForumTopicEdited, bot
)
data["general_forum_topic_hidden"] = de_json_optional(
data.get("general_forum_topic_hidden"), GeneralForumTopicHidden, bot
)
data["general_forum_topic_unhidden"] = de_json_optional(
data.get("general_forum_topic_unhidden"), GeneralForumTopicUnhidden, bot
)
data["write_access_allowed"] = de_json_optional(
data.get("write_access_allowed"), WriteAccessAllowed, bot
)
data["users_shared"] = de_json_optional(data.get("users_shared"), UsersShared, bot)
data["chat_shared"] = de_json_optional(data.get("chat_shared"), ChatShared, bot)
data["chat_background_set"] = de_json_optional(
data.get("chat_background_set"), ChatBackground, bot
)
data["paid_media"] = de_json_optional(data.get("paid_media"), PaidMediaInfo, bot)
data["refunded_payment"] = de_json_optional(
data.get("refunded_payment"), RefundedPayment, bot
)
data["users_shared"] = UsersShared.de_json(data.get("users_shared"), bot)
data["chat_shared"] = ChatShared.de_json(data.get("chat_shared"), bot)
data["chat_background_set"] = ChatBackground.de_json(data.get("chat_background_set"), bot)
data["paid_media"] = PaidMediaInfo.de_json(data.get("paid_media"), bot)
data["refunded_payment"] = RefundedPayment.de_json(data.get("refunded_payment"), bot)
# Unfortunately, this needs to be here due to cyclic imports
from telegram._giveaway import ( # pylint: disable=import-outside-toplevel
@@ -1344,19 +1359,27 @@ class Message(MaybeInaccessibleMessage):
TextQuote,
)
data["giveaway"] = Giveaway.de_json(data.get("giveaway"), bot)
data["giveaway_completed"] = GiveawayCompleted.de_json(data.get("giveaway_completed"), bot)
data["giveaway_created"] = GiveawayCreated.de_json(data.get("giveaway_created"), bot)
data["giveaway_winners"] = GiveawayWinners.de_json(data.get("giveaway_winners"), bot)
data["link_preview_options"] = LinkPreviewOptions.de_json(
data.get("link_preview_options"), bot
data["giveaway"] = de_json_optional(data.get("giveaway"), Giveaway, bot)
data["giveaway_completed"] = de_json_optional(
data.get("giveaway_completed"), GiveawayCompleted, bot
)
data["external_reply"] = ExternalReplyInfo.de_json(data.get("external_reply"), bot)
data["quote"] = TextQuote.de_json(data.get("quote"), bot)
data["forward_origin"] = MessageOrigin.de_json(data.get("forward_origin"), bot)
data["reply_to_story"] = Story.de_json(data.get("reply_to_story"), bot)
data["boost_added"] = ChatBoostAdded.de_json(data.get("boost_added"), bot)
data["sender_business_bot"] = User.de_json(data.get("sender_business_bot"), bot)
data["giveaway_created"] = de_json_optional(
data.get("giveaway_created"), GiveawayCreated, bot
)
data["giveaway_winners"] = de_json_optional(
data.get("giveaway_winners"), GiveawayWinners, bot
)
data["link_preview_options"] = de_json_optional(
data.get("link_preview_options"), LinkPreviewOptions, bot
)
data["external_reply"] = de_json_optional(
data.get("external_reply"), ExternalReplyInfo, bot
)
data["quote"] = de_json_optional(data.get("quote"), TextQuote, bot)
data["forward_origin"] = de_json_optional(data.get("forward_origin"), MessageOrigin, bot)
data["reply_to_story"] = de_json_optional(data.get("reply_to_story"), Story, bot)
data["boost_added"] = de_json_optional(data.get("boost_added"), ChatBoostAdded, bot)
data["sender_business_bot"] = de_json_optional(data.get("sender_business_bot"), User, bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
@@ -2210,7 +2233,7 @@ class Message(MaybeInaccessibleMessage):
async def reply_audio(
self,
audio: Union[FileInput, "Audio"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
performer: Optional[str] = None,
title: Optional[str] = None,
caption: Optional[str] = None,
@@ -2384,7 +2407,7 @@ class Message(MaybeInaccessibleMessage):
async def reply_animation(
self,
animation: Union[FileInput, "Animation"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
width: Optional[int] = None,
height: Optional[int] = None,
caption: Optional[str] = None,
@@ -2552,7 +2575,7 @@ class Message(MaybeInaccessibleMessage):
async def reply_video(
self,
video: Union[FileInput, "Video"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
caption: Optional[str] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
@@ -2569,6 +2592,8 @@ class Message(MaybeInaccessibleMessage):
message_effect_id: Optional[str] = None,
allow_paid_broadcast: Optional[bool] = None,
show_caption_above_media: Optional[bool] = None,
cover: Optional[FileInput] = None,
start_timestamp: Optional[int] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -2638,6 +2663,8 @@ class Message(MaybeInaccessibleMessage):
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
thumbnail=thumbnail,
cover=cover,
start_timestamp=start_timestamp,
business_connection_id=self.business_connection_id,
message_effect_id=message_effect_id,
allow_paid_broadcast=allow_paid_broadcast,
@@ -2647,7 +2674,7 @@ class Message(MaybeInaccessibleMessage):
async def reply_video_note(
self,
video_note: Union[FileInput, "VideoNote"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
length: Optional[int] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
@@ -2728,7 +2755,7 @@ class Message(MaybeInaccessibleMessage):
async def reply_voice(
self,
voice: Union[FileInput, "Voice"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
caption: Optional[str] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
@@ -2814,7 +2841,7 @@ class Message(MaybeInaccessibleMessage):
longitude: Optional[float] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
live_period: Optional[int] = None,
live_period: Optional[TimePeriod] = None,
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
@@ -3076,7 +3103,7 @@ class Message(MaybeInaccessibleMessage):
reply_markup: Optional[ReplyMarkup] = None,
explanation: Optional[str] = None,
explanation_parse_mode: ODVInput[str] = DEFAULT_NONE,
open_period: Optional[int] = None,
open_period: Optional[TimePeriod] = None,
close_date: Optional[Union[int, dtm.datetime]] = None,
explanation_entities: Optional[Sequence["MessageEntity"]] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
@@ -3359,9 +3386,9 @@ class Message(MaybeInaccessibleMessage):
title: str,
description: str,
payload: str,
provider_token: Optional[str],
currency: str,
prices: Sequence["LabeledPrice"],
provider_token: Optional[str] = None,
start_parameter: Optional[str] = None,
photo_url: Optional[str] = None,
photo_size: Optional[int] = None,
@@ -3483,6 +3510,7 @@ class Message(MaybeInaccessibleMessage):
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
video_start_timestamp: Optional[int] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -3517,6 +3545,7 @@ class Message(MaybeInaccessibleMessage):
chat_id=chat_id,
from_chat_id=self.chat_id,
message_id=self.message_id,
video_start_timestamp=video_start_timestamp,
disable_notification=disable_notification,
protect_content=protect_content,
message_thread_id=message_thread_id,
@@ -3540,6 +3569,7 @@ class Message(MaybeInaccessibleMessage):
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
allow_paid_broadcast: Optional[bool] = None,
video_start_timestamp: Optional[int] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -3570,6 +3600,7 @@ class Message(MaybeInaccessibleMessage):
from_chat_id=self.chat_id,
message_id=self.message_id,
caption=caption,
video_start_timestamp=video_start_timestamp,
parse_mode=parse_mode,
caption_entities=caption_entities,
disable_notification=disable_notification,
@@ -3602,6 +3633,7 @@ class Message(MaybeInaccessibleMessage):
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
allow_paid_broadcast: Optional[bool] = None,
video_start_timestamp: Optional[int] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -3652,6 +3684,7 @@ class Message(MaybeInaccessibleMessage):
from_chat_id=from_chat_id,
message_id=message_id,
caption=caption,
video_start_timestamp=video_start_timestamp,
parse_mode=parse_mode,
caption_entities=caption_entities,
disable_notification=disable_notification,
@@ -3958,7 +3991,7 @@ class Message(MaybeInaccessibleMessage):
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
live_period: Optional[int] = None,
live_period: Optional[TimePeriod] = None,
*,
location: Optional[Location] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
+3 -7
View File
@@ -27,6 +27,7 @@ from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.strings import TextEncoding
from telegram._utils.types import JSONDict
@@ -137,16 +138,11 @@ class MessageEntity(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["MessageEntity"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "MessageEntity":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["user"] = User.de_json(data.get("user"), bot)
data["user"] = de_json_optional(data.get("user"), User, bot)
return super().de_json(data=data, bot=bot)
+5 -9
View File
@@ -25,6 +25,7 @@ from telegram._chat import Chat
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
@@ -94,17 +95,12 @@ class MessageOrigin(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["MessageOrigin"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "MessageOrigin":
"""Converts JSON data to the appropriate :class:`MessageOrigin` object, i.e. takes
care of selecting the correct subclass.
"""
data = cls._parse_data(data)
if not data:
return None
_class_mapping: dict[str, type[MessageOrigin]] = {
cls.USER: MessageOriginUser,
cls.HIDDEN_USER: MessageOriginHiddenUser,
@@ -118,13 +114,13 @@ class MessageOrigin(TelegramObject):
data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
if "sender_user" in data:
data["sender_user"] = User.de_json(data.get("sender_user"), bot)
data["sender_user"] = de_json_optional(data.get("sender_user"), User, bot)
if "sender_chat" in data:
data["sender_chat"] = Chat.de_json(data.get("sender_chat"), bot)
data["sender_chat"] = de_json_optional(data.get("sender_chat"), Chat, bot)
if "chat" in data:
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
return super().de_json(data=data, bot=bot)
+10 -20
View File
@@ -25,7 +25,7 @@ from telegram._chat import Chat
from telegram._reaction import ReactionCount, ReactionType
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
@@ -86,21 +86,16 @@ class MessageReactionCountUpdated(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["MessageReactionCountUpdated"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "MessageReactionCountUpdated":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["reactions"] = ReactionCount.de_list(data.get("reactions"), bot)
data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
data["reactions"] = de_list_optional(data.get("reactions"), ReactionCount, bot)
return super().de_json(data=data, bot=bot)
@@ -187,23 +182,18 @@ class MessageReactionUpdated(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["MessageReactionUpdated"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "MessageReactionUpdated":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["date"] = from_timestamp(data.get("date"), tzinfo=loc_tzinfo)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["old_reaction"] = ReactionType.de_list(data.get("old_reaction"), bot)
data["new_reaction"] = ReactionType.de_list(data.get("new_reaction"), bot)
data["user"] = User.de_json(data.get("user"), bot)
data["actor_chat"] = Chat.de_json(data.get("actor_chat"), bot)
data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
data["old_reaction"] = de_list_optional(data.get("old_reaction"), ReactionType, bot)
data["new_reaction"] = de_list_optional(data.get("new_reaction"), ReactionType, bot)
data["user"] = de_json_optional(data.get("user"), User, bot)
data["actor_chat"] = de_json_optional(data.get("actor_chat"), Chat, bot)
return super().de_json(data=data, bot=bot)
+9 -37
View File
@@ -27,7 +27,7 @@ from telegram._files.video import Video
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -75,9 +75,7 @@ class PaidMedia(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PaidMedia"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PaidMedia":
"""Converts JSON data to the appropriate :class:`PaidMedia` object, i.e. takes
care of selecting the correct subclass.
@@ -91,12 +89,6 @@ class PaidMedia(TelegramObject):
"""
data = cls._parse_data(data)
if data is None:
return None
if not data and cls is PaidMedia:
return None
_class_mapping: dict[str, type[PaidMedia]] = {
cls.PREVIEW: PaidMediaPreview,
cls.PHOTO: PaidMediaPhoto,
@@ -185,15 +177,10 @@ class PaidMediaPhoto(PaidMedia):
self._id_attrs = (self.type, self.photo)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PaidMediaPhoto"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PaidMediaPhoto":
data = cls._parse_data(data)
if not data:
return None
data["photo"] = PhotoSize.de_list(data.get("photo"), bot=bot)
data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
@@ -231,15 +218,10 @@ class PaidMediaVideo(PaidMedia):
self._id_attrs = (self.type, self.video)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PaidMediaVideo"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PaidMediaVideo":
data = cls._parse_data(data)
if not data:
return None
data["video"] = Video.de_json(data.get("video"), bot=bot)
data["video"] = de_json_optional(data.get("video"), Video, bot)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
@@ -280,15 +262,10 @@ class PaidMediaInfo(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PaidMediaInfo"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PaidMediaInfo":
data = cls._parse_data(data)
if not data:
return None
data["paid_media"] = PaidMedia.de_list(data.get("paid_media"), bot=bot)
data["paid_media"] = de_list_optional(data.get("paid_media"), PaidMedia, bot)
return super().de_json(data=data, bot=bot)
@@ -329,13 +306,8 @@ class PaidMediaPurchased(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PaidMediaPurchased"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PaidMediaPurchased":
data = cls._parse_data(data)
if not data:
return None
data["from_user"] = User.de_json(data=data.pop("from"), bot=bot)
return super().de_json(data=data, bot=bot)
+27 -40
View File
@@ -39,7 +39,7 @@ except ImportError:
CRYPTO_INSTALLED = False
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.strings import TextEncoding
from telegram._utils.types import JSONDict
from telegram.error import PassportDecryptionError
@@ -207,7 +207,7 @@ class EncryptedCredentials(TelegramObject):
decrypt_json(self.decrypted_secret, b64decode(self.hash), b64decode(self.data)),
self.get_bot(),
)
return self._decrypted_data # type: ignore[return-value]
return self._decrypted_data
class Credentials(TelegramObject):
@@ -234,16 +234,11 @@ class Credentials(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["Credentials"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Credentials":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["secure_data"] = SecureData.de_json(data.get("secure_data"), bot=bot)
data["secure_data"] = de_json_optional(data.get("secure_data"), SecureData, bot)
return super().de_json(data=data, bot=bot)
@@ -346,30 +341,27 @@ class SecureData(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["SecureData"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SecureData":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["temporary_registration"] = SecureValue.de_json(
data.get("temporary_registration"), bot=bot
data["temporary_registration"] = de_json_optional(
data.get("temporary_registration"), SecureValue, bot
)
data["passport_registration"] = SecureValue.de_json(
data.get("passport_registration"), bot=bot
data["passport_registration"] = de_json_optional(
data.get("passport_registration"), SecureValue, bot
)
data["rental_agreement"] = SecureValue.de_json(data.get("rental_agreement"), bot=bot)
data["bank_statement"] = SecureValue.de_json(data.get("bank_statement"), bot=bot)
data["utility_bill"] = SecureValue.de_json(data.get("utility_bill"), bot=bot)
data["address"] = SecureValue.de_json(data.get("address"), bot=bot)
data["identity_card"] = SecureValue.de_json(data.get("identity_card"), bot=bot)
data["driver_license"] = SecureValue.de_json(data.get("driver_license"), bot=bot)
data["internal_passport"] = SecureValue.de_json(data.get("internal_passport"), bot=bot)
data["passport"] = SecureValue.de_json(data.get("passport"), bot=bot)
data["personal_details"] = SecureValue.de_json(data.get("personal_details"), bot=bot)
data["rental_agreement"] = de_json_optional(data.get("rental_agreement"), SecureValue, bot)
data["bank_statement"] = de_json_optional(data.get("bank_statement"), SecureValue, bot)
data["utility_bill"] = de_json_optional(data.get("utility_bill"), SecureValue, bot)
data["address"] = de_json_optional(data.get("address"), SecureValue, bot)
data["identity_card"] = de_json_optional(data.get("identity_card"), SecureValue, bot)
data["driver_license"] = de_json_optional(data.get("driver_license"), SecureValue, bot)
data["internal_passport"] = de_json_optional(
data.get("internal_passport"), SecureValue, bot
)
data["passport"] = de_json_optional(data.get("passport"), SecureValue, bot)
data["personal_details"] = de_json_optional(data.get("personal_details"), SecureValue, bot)
return super().de_json(data=data, bot=bot)
@@ -454,21 +446,16 @@ class SecureValue(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["SecureValue"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SecureValue":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["data"] = DataCredentials.de_json(data.get("data"), bot=bot)
data["front_side"] = FileCredentials.de_json(data.get("front_side"), bot=bot)
data["reverse_side"] = FileCredentials.de_json(data.get("reverse_side"), bot=bot)
data["selfie"] = FileCredentials.de_json(data.get("selfie"), bot=bot)
data["files"] = FileCredentials.de_list(data.get("files"), bot=bot)
data["translation"] = FileCredentials.de_list(data.get("translation"), bot=bot)
data["data"] = de_json_optional(data.get("data"), DataCredentials, bot)
data["front_side"] = de_json_optional(data.get("front_side"), FileCredentials, bot)
data["reverse_side"] = de_json_optional(data.get("reverse_side"), FileCredentials, bot)
data["selfie"] = de_json_optional(data.get("selfie"), FileCredentials, bot)
data["files"] = de_list_optional(data.get("files"), FileCredentials, bot)
data["translation"] = de_list_optional(data.get("translation"), FileCredentials, bot)
return super().de_json(data=data, bot=bot)
+25 -25
View File
@@ -25,7 +25,13 @@ from telegram._passport.credentials import decrypt_json
from telegram._passport.data import IdDocumentData, PersonalDetails, ResidentialAddress
from telegram._passport.passportfile import PassportFile
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import (
de_json_decrypted_optional,
de_json_optional,
de_list_decrypted_optional,
de_list_optional,
parse_sequence_arg,
)
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -194,27 +200,22 @@ class EncryptedPassportElement(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["EncryptedPassportElement"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "EncryptedPassportElement":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["files"] = PassportFile.de_list(data.get("files"), bot) or None
data["front_side"] = PassportFile.de_json(data.get("front_side"), bot)
data["reverse_side"] = PassportFile.de_json(data.get("reverse_side"), bot)
data["selfie"] = PassportFile.de_json(data.get("selfie"), bot)
data["translation"] = PassportFile.de_list(data.get("translation"), bot) or None
data["files"] = de_list_optional(data.get("files"), PassportFile, bot) or None
data["front_side"] = de_json_optional(data.get("front_side"), PassportFile, bot)
data["reverse_side"] = de_json_optional(data.get("reverse_side"), PassportFile, bot)
data["selfie"] = de_json_optional(data.get("selfie"), PassportFile, bot)
data["translation"] = de_list_optional(data.get("translation"), PassportFile, bot) or None
return super().de_json(data=data, bot=bot)
@classmethod
def de_json_decrypted(
cls, data: Optional[JSONDict], bot: Optional["Bot"], credentials: "Credentials"
) -> Optional["EncryptedPassportElement"]:
cls, data: JSONDict, bot: Optional["Bot"], credentials: "Credentials"
) -> "EncryptedPassportElement":
"""Variant of :meth:`telegram.TelegramObject.de_json` that also takes into account
passport credentials.
@@ -234,8 +235,6 @@ class EncryptedPassportElement(TelegramObject):
:class:`telegram.EncryptedPassportElement`:
"""
if not data:
return None
if data["type"] not in ("phone_number", "email"):
secure_data = getattr(credentials.secure_data, data["type"])
@@ -261,20 +260,21 @@ class EncryptedPassportElement(TelegramObject):
data["data"] = ResidentialAddress.de_json(data["data"], bot=bot)
data["files"] = (
PassportFile.de_list_decrypted(data.get("files"), bot, secure_data.files) or None
de_list_decrypted_optional(data.get("files"), PassportFile, bot, secure_data.files)
or None
)
data["front_side"] = PassportFile.de_json_decrypted(
data.get("front_side"), bot, secure_data.front_side
data["front_side"] = de_json_decrypted_optional(
data.get("front_side"), PassportFile, bot, secure_data.front_side
)
data["reverse_side"] = PassportFile.de_json_decrypted(
data.get("reverse_side"), bot, secure_data.reverse_side
data["reverse_side"] = de_json_decrypted_optional(
data.get("reverse_side"), PassportFile, bot, secure_data.reverse_side
)
data["selfie"] = PassportFile.de_json_decrypted(
data.get("selfie"), bot, secure_data.selfie
data["selfie"] = de_json_decrypted_optional(
data.get("selfie"), PassportFile, bot, secure_data.selfie
)
data["translation"] = (
PassportFile.de_list_decrypted(
data.get("translation"), bot, secure_data.translation
de_list_decrypted_optional(
data.get("translation"), PassportFile, bot, secure_data.translation
)
or None
)
+4 -9
View File
@@ -23,7 +23,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._passport.credentials import EncryptedCredentials
from telegram._passport.encryptedpassportelement import EncryptedPassportElement
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -82,17 +82,12 @@ class PassportData(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PassportData"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PassportData":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["data"] = EncryptedPassportElement.de_list(data.get("data"), bot)
data["credentials"] = EncryptedCredentials.de_json(data.get("credentials"), bot)
data["data"] = de_list_optional(data.get("data"), EncryptedPassportElement, bot)
data["credentials"] = de_json_optional(data.get("credentials"), EncryptedCredentials, bot)
return super().de_json(data=data, bot=bot)
+4 -11
View File
@@ -118,8 +118,8 @@ class PassportFile(TelegramObject):
@classmethod
def de_json_decrypted(
cls, data: Optional[JSONDict], bot: Optional["Bot"], credentials: "FileCredentials"
) -> Optional["PassportFile"]:
cls, data: JSONDict, bot: Optional["Bot"], credentials: "FileCredentials"
) -> "PassportFile":
"""Variant of :meth:`telegram.TelegramObject.de_json` that also takes into account
passport credentials.
@@ -141,9 +141,6 @@ class PassportFile(TelegramObject):
"""
data = cls._parse_data(data)
if not data:
return None
data["credentials"] = credentials
return super().de_json(data=data, bot=bot)
@@ -151,10 +148,10 @@ class PassportFile(TelegramObject):
@classmethod
def de_list_decrypted(
cls,
data: Optional[list[JSONDict]],
data: list[JSONDict],
bot: Optional["Bot"],
credentials: list["FileCredentials"],
) -> tuple[Optional["PassportFile"], ...]:
) -> tuple["PassportFile", ...]:
"""Variant of :meth:`telegram.TelegramObject.de_list` that also takes into account
passport credentials.
@@ -179,16 +176,12 @@ class PassportFile(TelegramObject):
tuple[:class:`telegram.PassportFile`]:
"""
if not data:
return ()
return tuple(
obj
for obj in (
cls.de_json_decrypted(passport_file, bot, credentials[i])
for i, passport_file in enumerate(data)
)
if obj is not None
)
async def get_file(
+5 -7
View File
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._payment.shippingaddress import ShippingAddress
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -71,15 +72,12 @@ class OrderInfo(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["OrderInfo"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "OrderInfo":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return cls()
data["shipping_address"] = ShippingAddress.de_json(data.get("shipping_address"), bot)
data["shipping_address"] = de_json_optional(
data.get("shipping_address"), ShippingAddress, bot
)
return super().de_json(data=data, bot=bot)
+4 -8
View File
@@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._payment.orderinfo import OrderInfo
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
@@ -110,17 +111,12 @@ class PreCheckoutQuery(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PreCheckoutQuery"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PreCheckoutQuery":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["order_info"] = OrderInfo.de_json(data.get("order_info"), bot)
data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
data["order_info"] = de_json_optional(data.get("order_info"), OrderInfo, bot)
return super().de_json(data=data, bot=bot)
+6 -8
View File
@@ -24,6 +24,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._payment.shippingaddress import ShippingAddress
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
@@ -78,17 +79,14 @@ class ShippingQuery(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ShippingQuery"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ShippingQuery":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["from_user"] = User.de_json(data.pop("from", None), bot)
data["shipping_address"] = ShippingAddress.de_json(data.get("shipping_address"), bot)
data["from_user"] = de_json_optional(data.pop("from", None), User, bot)
data["shipping_address"] = de_json_optional(
data.get("shipping_address"), ShippingAddress, bot
)
return super().de_json(data=data, bot=bot)
+4 -8
View File
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._chat import Chat
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -105,16 +106,11 @@ class AffiliateInfo(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["AffiliateInfo"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "AffiliateInfo":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["affiliate_user"] = User.de_json(data.get("affiliate_user"), bot)
data["affiliate_chat"] = Chat.de_json(data.get("affiliate_chat"), bot)
data["affiliate_user"] = de_json_optional(data.get("affiliate_user"), User, bot)
data["affiliate_chat"] = de_json_optional(data.get("affiliate_chat"), Chat, bot)
return super().de_json(data=data, bot=bot)
@@ -68,9 +68,7 @@ class RevenueWithdrawalState(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["RevenueWithdrawalState"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "RevenueWithdrawalState":
"""Converts JSON data to the appropriate :class:`RevenueWithdrawalState` object, i.e. takes
care of selecting the correct subclass.
@@ -84,9 +82,6 @@ class RevenueWithdrawalState(TelegramObject):
"""
data = cls._parse_data(data)
if (cls is RevenueWithdrawalState and not data) or data is None:
return None
_class_mapping: dict[str, type[RevenueWithdrawalState]] = {
cls.PENDING: RevenueWithdrawalStatePending,
cls.SUCCEEDED: RevenueWithdrawalStateSucceeded,
@@ -156,14 +151,11 @@ class RevenueWithdrawalStateSucceeded(RevenueWithdrawalState):
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["RevenueWithdrawalStateSucceeded"]:
cls, data: JSONDict, bot: Optional["Bot"] = None
) -> "RevenueWithdrawalStateSucceeded":
"""See :meth:`telegram.RevenueWithdrawalState.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
+9 -16
View File
@@ -24,7 +24,7 @@ from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
@@ -36,6 +36,9 @@ if TYPE_CHECKING:
class StarTransaction(TelegramObject):
"""Describes a Telegram Star transaction.
Note that if the buyer initiates a chargeback with the payment provider from whom they
acquired Stars (e.g., Apple, Google) following this transaction, the refunded Stars will be
deducted from the bot's balance. This is outside of Telegram's control.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id`, :attr:`source`, and :attr:`receiver` are equal.
@@ -112,21 +115,16 @@ class StarTransaction(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["StarTransaction"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "StarTransaction":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
data["source"] = TransactionPartner.de_json(data.get("source"), bot)
data["receiver"] = TransactionPartner.de_json(data.get("receiver"), bot)
data["source"] = de_json_optional(data.get("source"), TransactionPartner, bot)
data["receiver"] = de_json_optional(data.get("receiver"), TransactionPartner, bot)
return super().de_json(data=data, bot=bot)
@@ -159,14 +157,9 @@ class StarTransactions(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["StarTransactions"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "StarTransactions":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
data["transactions"] = StarTransaction.de_list(data.get("transactions"), bot)
data["transactions"] = de_list_optional(data.get("transactions"), StarTransaction, bot)
return super().de_json(data=data, bot=bot)
+77 -30
View File
@@ -23,12 +23,13 @@ from collections.abc import Sequence
from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._chat import Chat
from telegram._gifts import Gift
from telegram._paidmedia import PaidMedia
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.types import JSONDict
from .affiliateinfo import AffiliateInfo
@@ -43,6 +44,7 @@ class TransactionPartner(TelegramObject):
transactions. Currently, it can be one of:
* :class:`TransactionPartnerUser`
* :class:`TransactionPartnerChat`
* :class:`TransactionPartnerAffiliateProgram`
* :class:`TransactionPartnerFragment`
* :class:`TransactionPartnerTelegramAds`
@@ -54,6 +56,9 @@ class TransactionPartner(TelegramObject):
.. versionadded:: 21.4
..versionchanged:: 21.11
Added :class:`TransactionPartnerChat`
Args:
type (:obj:`str`): The type of the transaction partner.
@@ -68,6 +73,11 @@ class TransactionPartner(TelegramObject):
.. versionadded:: 21.9
"""
CHAT: Final[str] = constants.TransactionPartnerType.CHAT
""":const:`telegram.constants.TransactionPartnerType.CHAT`
.. versionadded:: 21.11
"""
FRAGMENT: Final[str] = constants.TransactionPartnerType.FRAGMENT
""":const:`telegram.constants.TransactionPartnerType.FRAGMENT`"""
OTHER: Final[str] = constants.TransactionPartnerType.OTHER
@@ -87,9 +97,7 @@ class TransactionPartner(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartner"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "TransactionPartner":
"""Converts JSON data to the appropriate :class:`TransactionPartner` object, i.e. takes
care of selecting the correct subclass.
@@ -103,11 +111,9 @@ class TransactionPartner(TelegramObject):
"""
data = cls._parse_data(data)
if (cls is TransactionPartner and not data) or data is None:
return None
_class_mapping: dict[str, type[TransactionPartner]] = {
cls.AFFILIATE_PROGRAM: TransactionPartnerAffiliateProgram,
cls.CHAT: TransactionPartnerChat,
cls.FRAGMENT: TransactionPartnerFragment,
cls.USER: TransactionPartnerUser,
cls.TELEGRAM_ADS: TransactionPartnerTelegramAds,
@@ -166,15 +172,66 @@ class TransactionPartnerAffiliateProgram(TransactionPartner):
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartnerAffiliateProgram"]:
cls, data: JSONDict, bot: Optional["Bot"] = None
) -> "TransactionPartnerAffiliateProgram":
"""See :meth:`telegram.TransactionPartner.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["sponsor_user"] = de_json_optional(data.get("sponsor_user"), User, bot)
data["sponsor_user"] = User.de_json(data.get("sponsor_user"), bot)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
class TransactionPartnerChat(TransactionPartner):
"""Describes a transaction with a chat.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`chat` are equal.
.. versionadded:: 21.11
Args:
chat (:class:`telegram.Chat`): Information about the chat.
gift (:class:`telegram.Gift`, optional): The gift sent to the chat by the bot.
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.CHAT`.
chat (:class:`telegram.Chat`): Information about the chat.
gift (:class:`telegram.Gift`): Optional. The gift sent to the user by the bot.
"""
__slots__ = (
"chat",
"gift",
)
def __init__(
self,
chat: Chat,
gift: Optional[Gift] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(type=TransactionPartner.CHAT, api_kwargs=api_kwargs)
with self._unfrozen():
self.chat: Chat = chat
self.gift: Optional[Gift] = gift
self._id_attrs = (
self.type,
self.chat,
)
@classmethod
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "TransactionPartnerChat":
"""See :meth:`telegram.TransactionPartner.de_json`."""
data = cls._parse_data(data)
data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
data["gift"] = de_json_optional(data.get("gift"), Gift, bot)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
@@ -209,17 +266,12 @@ class TransactionPartnerFragment(TransactionPartner):
self.withdrawal_state: Optional[RevenueWithdrawalState] = withdrawal_state
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartnerFragment"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "TransactionPartnerFragment":
"""See :meth:`telegram.TransactionPartner.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
data["withdrawal_state"] = RevenueWithdrawalState.de_json(
data.get("withdrawal_state"), bot
data["withdrawal_state"] = de_json_optional(
data.get("withdrawal_state"), RevenueWithdrawalState, bot
)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
@@ -320,24 +372,19 @@ class TransactionPartnerUser(TransactionPartner):
)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartnerUser"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "TransactionPartnerUser":
"""See :meth:`telegram.TransactionPartner.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["user"] = User.de_json(data.get("user"), bot)
data["affiliate"] = AffiliateInfo.de_json(data.get("affiliate"), bot)
data["paid_media"] = PaidMedia.de_list(data.get("paid_media"), bot=bot)
data["user"] = de_json_optional(data.get("user"), User, bot)
data["affiliate"] = de_json_optional(data.get("affiliate"), AffiliateInfo, bot)
data["paid_media"] = de_list_optional(data.get("paid_media"), PaidMedia, bot)
data["subscription_period"] = (
dtm.timedelta(seconds=sp)
if (sp := data.get("subscription_period")) is not None
else None
)
data["gift"] = Gift.de_json(data.get("gift"), bot)
data["gift"] = de_json_optional(data.get("gift"), Gift, bot)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
+6 -7
View File
@@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._payment.orderinfo import OrderInfo
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
@@ -32,6 +33,9 @@ if TYPE_CHECKING:
class SuccessfulPayment(TelegramObject):
"""This object contains basic information about a successful payment.
Note that if the buyer initiates a chargeback with the relevant payment provider following
this transaction, the funds may be debited from your balance. This is outside of
Telegram's control.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`telegram_payment_charge_id` and
@@ -138,16 +142,11 @@ class SuccessfulPayment(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["SuccessfulPayment"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SuccessfulPayment":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["order_info"] = OrderInfo.de_json(data.get("order_info"), bot)
data["order_info"] = de_json_optional(data.get("order_info"), OrderInfo, bot)
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
+16 -30
View File
@@ -27,7 +27,7 @@ from telegram._messageentity import MessageEntity
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.entities import parse_message_entities, parse_message_entity
@@ -91,16 +91,11 @@ class InputPollOption(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["InputPollOption"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "InputPollOption":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["text_entities"] = MessageEntity.de_list(data.get("text_entities"), bot)
data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot)
return super().de_json(data=data, bot=bot)
@@ -157,16 +152,11 @@ class PollOption(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PollOption"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PollOption":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["text_entities"] = MessageEntity.de_list(data.get("text_entities"), bot)
data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot)
return super().de_json(data=data, bot=bot)
@@ -306,17 +296,12 @@ class PollAnswer(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PollAnswer"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PollAnswer":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["user"] = User.de_json(data.get("user"), bot)
data["voter_chat"] = Chat.de_json(data.get("voter_chat"), bot)
data["user"] = de_json_optional(data.get("user"), User, bot)
data["voter_chat"] = de_json_optional(data.get("voter_chat"), Chat, bot)
return super().de_json(data=data, bot=bot)
@@ -474,20 +459,21 @@ class Poll(TelegramObject):
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Poll"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Poll":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["options"] = [PollOption.de_json(option, bot) for option in data["options"]]
data["explanation_entities"] = MessageEntity.de_list(data.get("explanation_entities"), bot)
data["options"] = de_list_optional(data.get("options"), PollOption, bot)
data["explanation_entities"] = de_list_optional(
data.get("explanation_entities"), MessageEntity, bot
)
data["close_date"] = from_timestamp(data.get("close_date"), tzinfo=loc_tzinfo)
data["question_entities"] = MessageEntity.de_list(data.get("question_entities"), bot)
data["question_entities"] = de_list_optional(
data.get("question_entities"), MessageEntity, bot
)
return super().de_json(data=data, bot=bot)
+4 -8
View File
@@ -21,6 +21,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -67,16 +68,11 @@ class ProximityAlertTriggered(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ProximityAlertTriggered"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ProximityAlertTriggered":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["traveler"] = User.de_json(data.get("traveler"), bot)
data["watcher"] = User.de_json(data.get("watcher"), bot)
data["traveler"] = de_json_optional(data.get("traveler"), User, bot)
data["watcher"] = de_json_optional(data.get("watcher"), User, bot)
return super().de_json(data=data, bot=bot)
+4 -16
View File
@@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, Final, Literal, Optional, Union
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -77,18 +78,10 @@ class ReactionType(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ReactionType"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ReactionType":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
if not data and cls is ReactionType:
return None
_class_mapping: dict[str, type[ReactionType]] = {
cls.EMOJI: ReactionTypeEmoji,
cls.CUSTOM_EMOJI: ReactionTypeCustomEmoji,
@@ -230,15 +223,10 @@ class ReactionCount(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ReactionCount"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ReactionCount":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["type"] = ReactionType.de_json(data.get("type"), bot)
data["type"] = de_json_optional(data.get("type"), ReactionType, bot)
return super().de_json(data=data, bot=bot)
+33 -44
View File
@@ -43,7 +43,7 @@ from telegram._payment.invoice import Invoice
from telegram._poll import Poll
from telegram._story import Story
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import JSONDict, ODVInput
@@ -248,39 +248,36 @@ class ExternalReplyInfo(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ExternalReplyInfo"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ExternalReplyInfo":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
data["origin"] = MessageOrigin.de_json(data.get("origin"), bot)
data["chat"] = Chat.de_json(data.get("chat"), bot)
data["link_preview_options"] = LinkPreviewOptions.de_json(
data.get("link_preview_options"), bot
data["origin"] = de_json_optional(data.get("origin"), MessageOrigin, bot)
data["chat"] = de_json_optional(data.get("chat"), Chat, bot)
data["link_preview_options"] = de_json_optional(
data.get("link_preview_options"), LinkPreviewOptions, bot
)
data["animation"] = Animation.de_json(data.get("animation"), bot)
data["audio"] = Audio.de_json(data.get("audio"), bot)
data["document"] = Document.de_json(data.get("document"), bot)
data["photo"] = tuple(PhotoSize.de_list(data.get("photo"), bot))
data["sticker"] = Sticker.de_json(data.get("sticker"), bot)
data["story"] = Story.de_json(data.get("story"), bot)
data["video"] = Video.de_json(data.get("video"), bot)
data["video_note"] = VideoNote.de_json(data.get("video_note"), bot)
data["voice"] = Voice.de_json(data.get("voice"), bot)
data["contact"] = Contact.de_json(data.get("contact"), bot)
data["dice"] = Dice.de_json(data.get("dice"), bot)
data["game"] = Game.de_json(data.get("game"), bot)
data["giveaway"] = Giveaway.de_json(data.get("giveaway"), bot)
data["giveaway_winners"] = GiveawayWinners.de_json(data.get("giveaway_winners"), bot)
data["invoice"] = Invoice.de_json(data.get("invoice"), bot)
data["location"] = Location.de_json(data.get("location"), bot)
data["poll"] = Poll.de_json(data.get("poll"), bot)
data["venue"] = Venue.de_json(data.get("venue"), bot)
data["paid_media"] = PaidMediaInfo.de_json(data.get("paid_media"), bot)
data["animation"] = de_json_optional(data.get("animation"), Animation, bot)
data["audio"] = de_json_optional(data.get("audio"), Audio, bot)
data["document"] = de_json_optional(data.get("document"), Document, bot)
data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot)
data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot)
data["story"] = de_json_optional(data.get("story"), Story, bot)
data["video"] = de_json_optional(data.get("video"), Video, bot)
data["video_note"] = de_json_optional(data.get("video_note"), VideoNote, bot)
data["voice"] = de_json_optional(data.get("voice"), Voice, bot)
data["contact"] = de_json_optional(data.get("contact"), Contact, bot)
data["dice"] = de_json_optional(data.get("dice"), Dice, bot)
data["game"] = de_json_optional(data.get("game"), Game, bot)
data["giveaway"] = de_json_optional(data.get("giveaway"), Giveaway, bot)
data["giveaway_winners"] = de_json_optional(
data.get("giveaway_winners"), GiveawayWinners, bot
)
data["invoice"] = de_json_optional(data.get("invoice"), Invoice, bot)
data["location"] = de_json_optional(data.get("location"), Location, bot)
data["poll"] = de_json_optional(data.get("poll"), Poll, bot)
data["venue"] = de_json_optional(data.get("venue"), Venue, bot)
data["paid_media"] = de_json_optional(data.get("paid_media"), PaidMediaInfo, bot)
return super().de_json(data=data, bot=bot)
@@ -350,16 +347,11 @@ class TextQuote(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TextQuote"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "TextQuote":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
data["entities"] = tuple(MessageEntity.de_list(data.get("entities"), bot))
data["entities"] = de_list_optional(data.get("entities"), MessageEntity, bot)
return super().de_json(data=data, bot=bot)
@@ -458,15 +450,12 @@ class ReplyParameters(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ReplyParameters"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ReplyParameters":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
data["quote_entities"] = tuple(MessageEntity.de_list(data.get("quote_entities"), bot))
data["quote_entities"] = tuple(
de_list_optional(data.get("quote_entities"), MessageEntity, bot)
)
return super().de_json(data=data, bot=bot)
+7 -22
View File
@@ -22,7 +22,7 @@ from typing import TYPE_CHECKING, Optional
from telegram._files.photosize import PhotoSize
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.argumentparsing import de_list_optional, parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -84,16 +84,11 @@ class UsersShared(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["UsersShared"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "UsersShared":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["users"] = SharedUser.de_list(data.get("users"), bot)
data["users"] = de_list_optional(data.get("users"), SharedUser, bot)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
@@ -175,16 +170,11 @@ class ChatShared(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["ChatShared"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChatShared":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot)
return super().de_json(data=data, bot=bot)
@@ -255,14 +245,9 @@ class SharedUser(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["SharedUser"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "SharedUser":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["photo"] = PhotoSize.de_list(data.get("photo"), bot)
data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot)
return super().de_json(data=data, bot=bot)
+1 -4
View File
@@ -71,12 +71,9 @@ class Story(TelegramObject):
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Story"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Story":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["chat"] = Chat.de_json(data.get("chat", {}), bot)
return super().de_json(data=data, bot=bot)
+7 -15
View File
@@ -377,23 +377,20 @@ class TelegramObject:
return result
@staticmethod
def _parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
def _parse_data(data: JSONDict) -> 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()
return data.copy()
@classmethod
def _de_json(
cls: type[Tele_co],
data: Optional[JSONDict],
data: JSONDict,
bot: Optional["Bot"],
api_kwargs: Optional[JSONDict] = None,
) -> Optional[Tele_co]:
if data is None:
return None
) -> Tele_co:
# try-except is significantly faster in case we already have a correct argument set
try:
obj = cls(**data, api_kwargs=api_kwargs)
@@ -417,9 +414,7 @@ class TelegramObject:
return obj
@classmethod
def de_json(
cls: type[Tele_co], data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional[Tele_co]:
def de_json(cls: type[Tele_co], data: JSONDict, bot: Optional["Bot"] = None) -> Tele_co:
"""Converts JSON data to a Telegram object.
Args:
@@ -438,7 +433,7 @@ class TelegramObject:
@classmethod
def de_list(
cls: type[Tele_co], data: Optional[list[JSONDict]], bot: Optional["Bot"] = None
cls: type[Tele_co], data: list[JSONDict], bot: Optional["Bot"] = None
) -> tuple[Tele_co, ...]:
"""Converts a list of JSON objects to a tuple of Telegram objects.
@@ -459,10 +454,7 @@ class TelegramObject:
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)
return tuple(cls.de_json(d, bot) for d in data)
@contextmanager
def _unfrozen(self: Tele_co) -> Iterator[Tele_co]:
+43 -33
View File
@@ -35,6 +35,7 @@ from telegram._payment.precheckoutquery import PreCheckoutQuery
from telegram._payment.shippingquery import ShippingQuery
from telegram._poll import Poll, PollAnswer
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
@@ -757,47 +758,56 @@ class Update(TelegramObject):
return message
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: Optional["Bot"] = None) -> Optional["Update"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Update":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["message"] = Message.de_json(data.get("message"), bot)
data["edited_message"] = Message.de_json(data.get("edited_message"), bot)
data["inline_query"] = InlineQuery.de_json(data.get("inline_query"), bot)
data["chosen_inline_result"] = ChosenInlineResult.de_json(
data.get("chosen_inline_result"), bot
data["message"] = de_json_optional(data.get("message"), Message, bot)
data["edited_message"] = de_json_optional(data.get("edited_message"), Message, bot)
data["inline_query"] = de_json_optional(data.get("inline_query"), InlineQuery, bot)
data["chosen_inline_result"] = de_json_optional(
data.get("chosen_inline_result"), ChosenInlineResult, bot
)
data["callback_query"] = CallbackQuery.de_json(data.get("callback_query"), bot)
data["shipping_query"] = ShippingQuery.de_json(data.get("shipping_query"), bot)
data["pre_checkout_query"] = PreCheckoutQuery.de_json(data.get("pre_checkout_query"), bot)
data["channel_post"] = Message.de_json(data.get("channel_post"), bot)
data["edited_channel_post"] = Message.de_json(data.get("edited_channel_post"), bot)
data["poll"] = Poll.de_json(data.get("poll"), bot)
data["poll_answer"] = PollAnswer.de_json(data.get("poll_answer"), bot)
data["my_chat_member"] = ChatMemberUpdated.de_json(data.get("my_chat_member"), bot)
data["chat_member"] = ChatMemberUpdated.de_json(data.get("chat_member"), bot)
data["chat_join_request"] = ChatJoinRequest.de_json(data.get("chat_join_request"), bot)
data["chat_boost"] = ChatBoostUpdated.de_json(data.get("chat_boost"), bot)
data["removed_chat_boost"] = ChatBoostRemoved.de_json(data.get("removed_chat_boost"), bot)
data["message_reaction"] = MessageReactionUpdated.de_json(
data.get("message_reaction"), bot
data["callback_query"] = de_json_optional(data.get("callback_query"), CallbackQuery, bot)
data["shipping_query"] = de_json_optional(data.get("shipping_query"), ShippingQuery, bot)
data["pre_checkout_query"] = de_json_optional(
data.get("pre_checkout_query"), PreCheckoutQuery, bot
)
data["message_reaction_count"] = MessageReactionCountUpdated.de_json(
data.get("message_reaction_count"), bot
data["channel_post"] = de_json_optional(data.get("channel_post"), Message, bot)
data["edited_channel_post"] = de_json_optional(
data.get("edited_channel_post"), Message, bot
)
data["business_connection"] = BusinessConnection.de_json(
data.get("business_connection"), bot
data["poll"] = de_json_optional(data.get("poll"), Poll, bot)
data["poll_answer"] = de_json_optional(data.get("poll_answer"), PollAnswer, bot)
data["my_chat_member"] = de_json_optional(
data.get("my_chat_member"), ChatMemberUpdated, bot
)
data["business_message"] = Message.de_json(data.get("business_message"), bot)
data["edited_business_message"] = Message.de_json(data.get("edited_business_message"), bot)
data["deleted_business_messages"] = BusinessMessagesDeleted.de_json(
data.get("deleted_business_messages"), bot
data["chat_member"] = de_json_optional(data.get("chat_member"), ChatMemberUpdated, bot)
data["chat_join_request"] = de_json_optional(
data.get("chat_join_request"), ChatJoinRequest, bot
)
data["purchased_paid_media"] = PaidMediaPurchased.de_json(
data.get("purchased_paid_media"), bot
data["chat_boost"] = de_json_optional(data.get("chat_boost"), ChatBoostUpdated, bot)
data["removed_chat_boost"] = de_json_optional(
data.get("removed_chat_boost"), ChatBoostRemoved, bot
)
data["message_reaction"] = de_json_optional(
data.get("message_reaction"), MessageReactionUpdated, bot
)
data["message_reaction_count"] = de_json_optional(
data.get("message_reaction_count"), MessageReactionCountUpdated, bot
)
data["business_connection"] = de_json_optional(
data.get("business_connection"), BusinessConnection, bot
)
data["business_message"] = de_json_optional(data.get("business_message"), Message, bot)
data["edited_business_message"] = de_json_optional(
data.get("edited_business_message"), Message, bot
)
data["deleted_business_messages"] = de_json_optional(
data.get("deleted_business_messages"), BusinessMessagesDeleted, bot
)
data["purchased_paid_media"] = de_json_optional(
data.get("purchased_paid_media"), PaidMediaPurchased, bot
)
return super().de_json(data=data, bot=bot)
+30 -10
View File
@@ -26,7 +26,14 @@ from telegram._inline.inlinekeyboardbutton import InlineKeyboardButton
from telegram._menubutton import MenuButton
from telegram._telegramobject import TelegramObject
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.types import CorrectOptionID, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.types import (
CorrectOptionID,
FileInput,
JSONDict,
ODVInput,
ReplyMarkup,
TimePeriod,
)
from telegram.helpers import mention_html as helpers_mention_html
from telegram.helpers import mention_markdown as helpers_mention_markdown
@@ -668,7 +675,7 @@ class User(TelegramObject):
async def send_audio(
self,
audio: Union[FileInput, "Audio"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
performer: Optional[str] = None,
title: Optional[str] = None,
caption: Optional[str] = None,
@@ -1011,9 +1018,9 @@ class User(TelegramObject):
title: str,
description: str,
payload: str,
provider_token: Optional[str],
currency: str,
prices: Sequence["LabeledPrice"],
provider_token: Optional[str] = None,
start_parameter: Optional[str] = None,
photo_url: Optional[str] = None,
photo_size: Optional[int] = None,
@@ -1113,7 +1120,7 @@ class User(TelegramObject):
longitude: Optional[float] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
live_period: Optional[int] = None,
live_period: Optional[TimePeriod] = None,
horizontal_accuracy: Optional[float] = None,
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
@@ -1175,7 +1182,7 @@ class User(TelegramObject):
async def send_animation(
self,
animation: Union[FileInput, "Animation"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
width: Optional[int] = None,
height: Optional[int] = None,
caption: Optional[str] = None,
@@ -1303,7 +1310,7 @@ class User(TelegramObject):
async def send_video(
self,
video: Union[FileInput, "Video"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
caption: Optional[str] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
@@ -1321,6 +1328,8 @@ class User(TelegramObject):
message_effect_id: Optional[str] = None,
allow_paid_broadcast: Optional[bool] = None,
show_caption_above_media: Optional[bool] = None,
cover: Optional[FileInput] = None,
start_timestamp: Optional[int] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1362,6 +1371,8 @@ class User(TelegramObject):
parse_mode=parse_mode,
supports_streaming=supports_streaming,
thumbnail=thumbnail,
cover=cover,
start_timestamp=start_timestamp,
api_kwargs=api_kwargs,
allow_sending_without_reply=allow_sending_without_reply,
caption_entities=caption_entities,
@@ -1447,7 +1458,7 @@ class User(TelegramObject):
async def send_video_note(
self,
video_note: Union[FileInput, "VideoNote"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
length: Optional[int] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
@@ -1508,7 +1519,7 @@ class User(TelegramObject):
async def send_voice(
self,
voice: Union[FileInput, "Voice"],
duration: Optional[int] = None,
duration: Optional[TimePeriod] = None,
caption: Optional[str] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
reply_markup: Optional[ReplyMarkup] = None,
@@ -1581,7 +1592,7 @@ class User(TelegramObject):
reply_markup: Optional[ReplyMarkup] = None,
explanation: Optional[str] = None,
explanation_parse_mode: ODVInput[str] = DEFAULT_NONE,
open_period: Optional[int] = None,
open_period: Optional[TimePeriod] = None,
close_date: Optional[Union[int, dtm.datetime]] = None,
explanation_entities: Optional[Sequence["MessageEntity"]] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
@@ -1663,7 +1674,7 @@ class User(TelegramObject):
) -> bool:
"""Shortcut for::
await bot.send_gift( user_id=update.effective_user.id, *args, **kwargs )
await bot.send_gift(user_id=update.effective_user.id, *args, **kwargs )
For the documentation of the arguments, please see :meth:`telegram.Bot.send_gift`.
@@ -1673,6 +1684,7 @@ class User(TelegramObject):
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().send_gift(
chat_id=None,
user_id=self.id,
gift_id=gift_id,
text=text,
@@ -1700,6 +1712,7 @@ class User(TelegramObject):
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
allow_paid_broadcast: Optional[bool] = None,
video_start_timestamp: Optional[int] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1727,6 +1740,7 @@ class User(TelegramObject):
from_chat_id=from_chat_id,
message_id=message_id,
caption=caption,
video_start_timestamp=video_start_timestamp,
parse_mode=parse_mode,
caption_entities=caption_entities,
disable_notification=disable_notification,
@@ -1759,6 +1773,7 @@ class User(TelegramObject):
reply_parameters: Optional["ReplyParameters"] = None,
show_caption_above_media: Optional[bool] = None,
allow_paid_broadcast: Optional[bool] = None,
video_start_timestamp: Optional[int] = None,
*,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
@@ -1786,6 +1801,7 @@ class User(TelegramObject):
chat_id=chat_id,
message_id=message_id,
caption=caption,
video_start_timestamp=video_start_timestamp,
parse_mode=parse_mode,
caption_entities=caption_entities,
disable_notification=disable_notification,
@@ -1901,6 +1917,7 @@ class User(TelegramObject):
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
video_start_timestamp: Optional[int] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1926,6 +1943,7 @@ class User(TelegramObject):
chat_id=self.id,
from_chat_id=from_chat_id,
message_id=message_id,
video_start_timestamp=video_start_timestamp,
disable_notification=disable_notification,
read_timeout=read_timeout,
write_timeout=write_timeout,
@@ -1943,6 +1961,7 @@ class User(TelegramObject):
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: Optional[int] = None,
video_start_timestamp: Optional[int] = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -1969,6 +1988,7 @@ class User(TelegramObject):
from_chat_id=self.id,
chat_id=chat_id,
message_id=message_id,
video_start_timestamp=video_start_timestamp,
disable_notification=disable_notification,
read_timeout=read_timeout,
write_timeout=write_timeout,
+1 -6
View File
@@ -71,15 +71,10 @@ class UserProfilePhotos(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["UserProfilePhotos"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "UserProfilePhotos":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["photos"] = [PhotoSize.de_list(photo, bot) for photo in data["photos"]]
return super().de_json(data=data, bot=bot)
+80 -2
View File
@@ -24,10 +24,16 @@ Warning:
the changelog.
"""
from collections.abc import Sequence
from typing import Optional, TypeVar
from typing import TYPE_CHECKING, Optional, Protocol, TypeVar
from telegram._linkpreviewoptions import LinkPreviewOptions
from telegram._utils.types import ODVInput
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict, ODVInput
if TYPE_CHECKING:
from typing import type_check_only
from telegram import Bot, FileCredentials
T = TypeVar("T")
@@ -60,3 +66,75 @@ def parse_lpo_and_dwpp(
link_preview_options = LinkPreviewOptions(is_disabled=disable_web_page_preview)
return link_preview_options
Tele_co = TypeVar("Tele_co", bound=TelegramObject, covariant=True)
TeleCrypto_co = TypeVar("TeleCrypto_co", bound="HasDecryptMethod", covariant=True)
if TYPE_CHECKING:
@type_check_only
class HasDecryptMethod(Protocol):
__slots__ = ()
@classmethod
def de_json_decrypted(
cls: type[TeleCrypto_co],
data: JSONDict,
bot: Optional["Bot"],
credentials: list["FileCredentials"],
) -> TeleCrypto_co: ...
@classmethod
def de_list_decrypted(
cls: type[TeleCrypto_co],
data: list[JSONDict],
bot: Optional["Bot"],
credentials: list["FileCredentials"],
) -> tuple[TeleCrypto_co, ...]: ...
def de_json_optional(
data: Optional[JSONDict], cls: type[Tele_co], bot: Optional["Bot"]
) -> Optional[Tele_co]:
"""Wrapper around TO.de_json that returns None if data is None."""
if data is None:
return None
return cls.de_json(data, bot)
def de_json_decrypted_optional(
data: Optional[JSONDict],
cls: type[TeleCrypto_co],
bot: Optional["Bot"],
credentials: list["FileCredentials"],
) -> Optional[TeleCrypto_co]:
"""Wrapper around TO.de_json_decrypted that returns None if data is None."""
if data is None:
return None
return cls.de_json_decrypted(data, bot, credentials)
def de_list_optional(
data: Optional[list[JSONDict]], cls: type[Tele_co], bot: Optional["Bot"]
) -> tuple[Tele_co, ...]:
"""Wrapper around TO.de_list that returns an empty list if data is None."""
if data is None:
return ()
return cls.de_list(data, bot)
def de_list_decrypted_optional(
data: Optional[list[JSONDict]],
cls: type[TeleCrypto_co],
bot: Optional["Bot"],
credentials: list["FileCredentials"],
) -> tuple[TeleCrypto_co, ...]:
"""Wrapper around TO.de_list_decrypted that returns an empty list if data is None."""
if data is None:
return ()
return cls.de_list_decrypted(data, bot, credentials)
+6 -1
View File
@@ -23,9 +23,10 @@ Warning:
user. Changes to this module are not considered breaking changes and may not be documented in
the changelog.
"""
import datetime as dtm
from collections.abc import Collection
from pathlib import Path
from typing import IO, TYPE_CHECKING, Any, Literal, Optional, TypeVar, Union
from typing import IO, TYPE_CHECKING, Any, Callable, Literal, Optional, TypeVar, Union
if TYPE_CHECKING:
from telegram import (
@@ -91,3 +92,7 @@ SocketOpt = Union[
tuple[int, int, Union[bytes, bytearray]],
tuple[int, int, None, int],
]
BaseUrl = Union[str, Callable[[str], str]]
TimePeriod = Union[int, dtm.timedelta]
+1 -1
View File
@@ -51,6 +51,6 @@ class Version(NamedTuple):
__version_info__: Final[Version] = Version(
major=21, minor=10, micro=0, releaselevel="final", serial=0
major=21, minor=11, micro=1, releaselevel="final", serial=0
)
__version__: Final[str] = str(__version_info__)
+4 -12
View File
@@ -126,14 +126,11 @@ class VideoChatParticipantsInvited(TelegramObject):
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["VideoChatParticipantsInvited"]:
cls, data: JSONDict, bot: Optional["Bot"] = None
) -> "VideoChatParticipantsInvited":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["users"] = User.de_list(data.get("users", []), bot)
return super().de_json(data=data, bot=bot)
@@ -178,18 +175,13 @@ class VideoChatScheduled(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["VideoChatScheduled"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "VideoChatScheduled":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["start_date"] = from_timestamp(data["start_date"], tzinfo=loc_tzinfo)
data["start_date"] = from_timestamp(data.get("start_date"), tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)
+1 -6
View File
@@ -166,15 +166,10 @@ class WebhookInfo(TelegramObject):
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["WebhookInfo"]:
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "WebhookInfo":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
+13 -5
View File
@@ -155,7 +155,7 @@ class _AccentColor(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=8, minor=2)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=8, minor=3)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@@ -1236,9 +1236,12 @@ class GiftLimit(IntEnum):
__slots__ = ()
MAX_TEXT_LENGTH = 255
MAX_TEXT_LENGTH = 128
""":obj:`int`: Maximum number of characters in a :obj:`str` passed as the
:paramref:`~telegram.Bot.send_gift.text` parameter of :meth:`~telegram.Bot.send_gift`.
.. versionchanged:: 21.11
Updated Value to 128 based on Bot API 8.3
"""
@@ -2618,13 +2621,13 @@ class StickerSetLimit(IntEnum):
:meth:`telegram.Bot.add_sticker_to_set`.
"""
MAX_STATIC_THUMBNAIL_SIZE = 128
""":obj:`int`: Maximum size of the thumbnail if it is a **.WEBP** or **.PNG** in kilobytes,
""":obj:`int`: Maximum size of the thumbnail if it is a ``.WEBP`` or ``.PNG`` in kilobytes,
as given in :meth:`telegram.Bot.set_sticker_set_thumbnail`."""
MAX_ANIMATED_THUMBNAIL_SIZE = 32
""":obj:`int`: Maximum size of the thumbnail if it is a **.TGS** or **.WEBM** in kilobytes,
""":obj:`int`: Maximum size of the thumbnail if it is a ``.TGS`` or ``.WEBM`` in kilobytes,
as given in :meth:`telegram.Bot.set_sticker_set_thumbnail`."""
STATIC_THUMB_DIMENSIONS = 100
""":obj:`int`: Exact height and width of the thumbnail if it is a **.WEBP** or **.PNG** in
""":obj:`int`: Exact height and width of the thumbnail if it is a ``.WEBP`` or ``.PNG`` in
pixels, as given in :meth:`telegram.Bot.set_sticker_set_thumbnail`."""
@@ -2659,6 +2662,11 @@ class TransactionPartnerType(StringEnum):
.. versionadded:: 21.9
"""
CHAT = "chat"
""":obj:`str`: Transaction with a chat.
.. versionadded:: 21.11
"""
FRAGMENT = "fragment"
""":obj:`str`: Withdrawal transaction with Fragment."""
OTHER = "other"
+42 -20
View File
@@ -32,6 +32,7 @@ try:
except ImportError:
AIO_LIMITER_AVAILABLE = False
from telegram import constants
from telegram._utils.logging import get_logger
from telegram._utils.types import JSONDict
from telegram.error import RetryAfter
@@ -86,7 +87,8 @@ class AIORateLimiter(BaseRateLimiter[int]):
* A :exc:`~telegram.error.RetryAfter` exception will halt *all* requests for
:attr:`~telegram.error.RetryAfter.retry_after` + 0.1 seconds. This may be stricter than
necessary in some cases, e.g. the bot may hit a rate limit in one group but might still
be allowed to send messages in another group.
be allowed to send messages in another group or with
:paramref:`~telegram.Bot.send_message.allow_paid_broadcast` set to :obj:`True`.
Tip:
With `Bot API 7.1 <https://core.telegram.org/bots/api-changelog#october-31-2024>`_
@@ -96,10 +98,10 @@ class AIORateLimiter(BaseRateLimiter[int]):
:tg-const:`telegram.constants.FloodLimit.PAID_MESSAGES_PER_SECOND` messages per second by
paying a fee in Telegram Stars.
.. caution::
This class currently doesn't take the
:paramref:`~telegram.Bot.send_message.allow_paid_broadcast` parameter into account.
This means that the rate limiting is applied just like for any other message.
.. versionchanged:: 21.11
This class automatically takes the
:paramref:`~telegram.Bot.send_message.allow_paid_broadcast` parameter into account and
throttles the requests accordingly.
Note:
This class is to be understood as minimal effort reference implementation.
@@ -114,16 +116,17 @@ class AIORateLimiter(BaseRateLimiter[int]):
Args:
overall_max_rate (:obj:`float`): The maximum number of requests allowed for the entire bot
per :paramref:`overall_time_period`. When set to 0, no rate limiting will be applied.
Defaults to ``30``.
Defaults to :tg-const:`telegram.constants.FloodLimit.MESSAGES_PER_SECOND`.
overall_time_period (:obj:`float`): The time period (in seconds) during which the
:paramref:`overall_max_rate` is enforced. When set to 0, no rate limiting will be
applied. Defaults to 1.
applied. Defaults to ``1``.
group_max_rate (:obj:`float`): The maximum number of requests allowed for requests related
to groups and channels per :paramref:`group_time_period`. When set to 0, no rate
limiting will be applied. Defaults to 20.
limiting will be applied. Defaults to
:tg-const:`telegram.constants.FloodLimit.MESSAGES_PER_MINUTE_PER_GROUP`.
group_time_period (:obj:`float`): The time period (in seconds) during which the
:paramref:`group_max_rate` is enforced. When set to 0, no rate limiting will be
applied. Defaults to 60.
applied. Defaults to ``60``.
max_retries (:obj:`int`): The maximum number of retries to be made in case of a
:exc:`~telegram.error.RetryAfter` exception.
If set to 0, no retries will be made. Defaults to ``0``.
@@ -131,6 +134,7 @@ class AIORateLimiter(BaseRateLimiter[int]):
"""
__slots__ = (
"_apb_limiter",
"_base_limiter",
"_group_limiters",
"_group_max_rate",
@@ -141,9 +145,9 @@ class AIORateLimiter(BaseRateLimiter[int]):
def __init__(
self,
overall_max_rate: float = 30,
overall_max_rate: float = constants.FloodLimit.MESSAGES_PER_SECOND,
overall_time_period: float = 1,
group_max_rate: float = 20,
group_max_rate: float = constants.FloodLimit.MESSAGES_PER_MINUTE_PER_GROUP,
group_time_period: float = 60,
max_retries: int = 0,
) -> None:
@@ -167,6 +171,9 @@ class AIORateLimiter(BaseRateLimiter[int]):
self._group_time_period = 0
self._group_limiters: dict[Union[str, int], AsyncLimiter] = {}
self._apb_limiter: AsyncLimiter = AsyncLimiter(
max_rate=constants.FloodLimit.PAID_MESSAGES_PER_SECOND, time_period=1
)
self._max_retries: int = max_retries
self._retry_after_event = asyncio.Event()
self._retry_after_event.set()
@@ -201,21 +208,30 @@ class AIORateLimiter(BaseRateLimiter[int]):
self,
chat: bool,
group: Union[str, int, bool],
allow_paid_broadcast: bool,
callback: Callable[..., Coroutine[Any, Any, Union[bool, JSONDict, list[JSONDict]]]],
args: Any,
kwargs: dict[str, Any],
) -> Union[bool, JSONDict, list[JSONDict]]:
base_context = self._base_limiter if (chat and self._base_limiter) else null_context()
group_context = (
self._get_group_limiter(group) if group and self._group_max_rate else null_context()
)
async with group_context, base_context:
async def inner() -> Union[bool, JSONDict, list[JSONDict]]:
# In case a retry_after was hit, we wait with processing the request
await self._retry_after_event.wait()
return await callback(*args, **kwargs)
if allow_paid_broadcast:
async with self._apb_limiter:
return await inner()
else:
base_context = self._base_limiter if (chat and self._base_limiter) else null_context()
group_context = (
self._get_group_limiter(group)
if group and self._group_max_rate
else null_context()
)
async with group_context, base_context:
return await inner()
# mypy doesn't understand that the last run of the for loop raises an exception
async def process_request(
self,
@@ -242,12 +258,13 @@ class AIORateLimiter(BaseRateLimiter[int]):
group: Union[int, str, bool] = False
chat: bool = False
chat_id = data.get("chat_id")
allow_paid_broadcast = data.get("allow_paid_broadcast", False)
if chat_id is not None:
chat = True
# In case user passes integer chat id as string
with contextlib.suppress(ValueError, TypeError):
chat_id = int(chat_id)
chat_id = int(chat_id) # type: ignore[arg-type]
if (isinstance(chat_id, int) and chat_id < 0) or isinstance(chat_id, str):
# string chat_id only works for channels and supergroups
@@ -257,7 +274,12 @@ class AIORateLimiter(BaseRateLimiter[int]):
for i in range(max_retries + 1):
try:
return await self._run_request(
chat=chat, group=group, callback=callback, args=args, kwargs=kwargs
chat=chat,
group=group,
allow_paid_broadcast=allow_paid_broadcast,
callback=callback,
args=args,
kwargs=kwargs,
)
except RetryAfter as exc:
if i == max_retries:
+33 -15
View File
@@ -50,6 +50,7 @@ from telegram.ext._contexttypes import ContextTypes
from telegram.ext._extbot import ExtBot
from telegram.ext._handlers.basehandler import BaseHandler
from telegram.ext._updater import Updater
from telegram.ext._utils.networkloop import network_retry_loop
from telegram.ext._utils.stack import was_called_by
from telegram.ext._utils.trackingdict import TrackingDict
from telegram.ext._utils.types import BD, BT, CCT, CD, JQ, RT, UD, ConversationKey, HandlerCallback
@@ -235,7 +236,7 @@ class Application(
"""
__slots__ = (
( # noqa: RUF005
(
"__create_task_tasks",
"__update_fetcher_task",
"__update_persistence_event",
@@ -270,9 +271,7 @@ class Application(
# Allowing '__weakref__' creation here since we need it for the JobQueue
# Currently the __weakref__ slot is already created
# in the AsyncContextManager base class for pythons < 3.13
+ ("__weakref__",)
if sys.version_info >= (3, 13)
else ()
+ (("__weakref__",) if sys.version_info >= (3, 13) else ())
)
def __init__(
@@ -741,7 +740,7 @@ class Application(
self,
poll_interval: float = 0.0,
timeout: int = 10,
bootstrap_retries: int = -1,
bootstrap_retries: int = 0,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -782,13 +781,19 @@ class Application(
Telegram in seconds. Default is ``0.0``.
timeout (:obj:`int`, optional): Passed to
:paramref:`telegram.Bot.get_updates.timeout`. Default is ``10`` seconds.
bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the
:class:`telegram.ext.Updater` will retry on failures on the Telegram server.
bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase
(calling :meth:`initialize` and the boostrapping of
:meth:`telegram.ext.Updater.start_polling`)
will retry on failures on the Telegram server.
* < 0 - retry indefinitely (default)
* 0 - no retries
* < 0 - retry indefinitely
* 0 - no retries (default)
* > 0 - retry up to X times
.. versionchanged:: 21.11
The default value will be changed to from ``-1`` to ``0``. Indefinite retries
during bootstrapping are not recommended.
read_timeout (:obj:`float`, optional): Value to pass to
:paramref:`telegram.Bot.get_updates.read_timeout`. Defaults to
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
@@ -878,8 +883,9 @@ class Application(
drop_pending_updates=drop_pending_updates,
error_callback=error_callback, # if there is an error in fetching updates
),
close_loop=close_loop,
stop_signals=stop_signals,
bootstrap_retries=bootstrap_retries,
close_loop=close_loop,
)
def run_webhook(
@@ -948,8 +954,10 @@ class Application(
url_path (:obj:`str`, optional): Path inside url. Defaults to `` '' ``
cert (:class:`pathlib.Path` | :obj:`str`, optional): Path to the SSL certificate file.
key (:class:`pathlib.Path` | :obj:`str`, optional): Path to the SSL key file.
bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the
:class:`telegram.ext.Updater` will retry on failures on the Telegram server.
bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase
(calling :meth:`initialize` and the boostrapping of
:meth:`telegram.ext.Updater.start_polling`)
will retry on failures on the Telegram server.
* < 0 - retry indefinitely
* 0 - no retries (default)
@@ -1035,18 +1043,28 @@ class Application(
secret_token=secret_token,
unix=unix,
),
close_loop=close_loop,
stop_signals=stop_signals,
bootstrap_retries=bootstrap_retries,
close_loop=close_loop,
)
async def _bootstrap_initialize(self, max_retries: int) -> None:
await network_retry_loop(
action_cb=self.initialize,
description="Bootstrap Initialize Application",
max_retries=max_retries,
interval=1,
)
def __run(
self,
updater_coroutine: Coroutine,
stop_signals: ODVInput[Sequence[int]],
bootstrap_retries: int,
close_loop: bool = True,
) -> None:
# Calling get_event_loop() should still be okay even in py3.10+ as long as there is a
# running event loop or we are in the main thread, which are the intended use cases.
# running event loop, or we are in the main thread, which are the intended use cases.
# See the docs of get_event_loop() and get_running_loop() for more info
loop = asyncio.get_event_loop()
@@ -1066,7 +1084,7 @@ class Application(
)
try:
loop.run_until_complete(self.initialize())
loop.run_until_complete(self._bootstrap_initialize(max_retries=bootstrap_retries))
if self.post_init:
loop.run_until_complete(self.post_init(self))
if self.__stop_running_marker.is_set():

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