Compare commits

...

35 Commits

Author SHA1 Message Date
Bibo-Joshi 9ebd48903b Bump Version to v22.0 (#4719) 2025-03-15 09:53:21 +01:00
Bibo-Joshi 1cf000c806 Remove Functionality Deprecated in v20.x (#4671) 2025-03-15 09:21:37 +01:00
Bibo-Joshi 150328799a Bump Bibo-Joshi/chango from 0.3.2 to 0.4.0 (#4712) 2025-03-09 16:02:24 +01:00
dependabot[bot] 6d7134608f Bump sphinx from 8.1.3 to 8.2.3 (#4710)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2025-03-08 19:55:21 +01:00
dependabot[bot] 8266870ed7 Bump pytest from 8.3.4 to 8.3.5 (#4709)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2025-03-08 19:55:08 +01:00
dependabot[bot] 519dee7e0c Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4 (#4701)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2025-03-02 10:57:12 +01:00
dependabot[bot] 753f3727aa Bump Bibo-Joshi/chango from 0.3.1 to 0.3.2 (#4700)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2025-03-02 10:56:42 +01:00
dependabot[bot] 892a66d0e8 Bump astral-sh/setup-uv from 5.2.2 to 5.3.1 (#4699)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2025-03-02 10:56:11 +01:00
dependabot[bot] d5e5874f96 Bump srvaroa/labeler from 1.12.0 to 1.13.0 (#4698)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2025-03-02 10:54:56 +01:00
dependabot[bot] a5dacab8a9 Bump github/codeql-action from 3.28.8 to 3.28.10 (#4697)
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2025-03-02 10:54:18 +01:00
Bibo-Joshi 76d8eaf0f2 Add chango As Changelog Management Tool (#4672) 2025-03-01 18:35:16 +01:00
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
206 changed files with 5718 additions and 4260 deletions
@@ -0,0 +1,5 @@
<b>We've just released {tag}</b>.
Thank you to everyone who contributed to this release.
As usual, upgrade using <code>pip install -U python-telegram-bot</code>.
The release notes can be found <a href="https://docs.python-telegram-bot.org/en/{tag}/changelog.html">here</a>.
+30
View File
@@ -0,0 +1,30 @@
name: Create Chango Fragment
on:
pull_request:
types:
- opened
- reopened
- synchronize
permissions: {}
jobs:
create-chango-fragment:
permissions:
# Give the default GITHUB_TOKEN write permission to commit and push the
# added or changed files to the repository.
contents: write
name: create-chango-fragment
runs-on: ubuntu-latest
steps:
# Create the new fragment
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Bibo-Joshi/chango@9d6bd9d7612eca5fab2c5161687011be59baaf19 # v0.4.0
with:
github-token: ${{ secrets.CHANGO_PAT }}
query-issue-types: true
+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,20 +1,26 @@
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']
python-version: ['3.12']
os: [ubuntu-latest]
fail-fast: False
steps:
@@ -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
+11 -2
View File
@@ -7,13 +7,15 @@ on:
paths:
- .github/workflows/docs-linkcheck.yml
permissions: {}
jobs:
test-sphinx-build:
name: test-sphinx-linkcheck
runs-on: ${{matrix.os}}
strategy:
matrix:
python-version: ['3.10']
python-version: ['3.12']
os: [ubuntu-latest]
fail-fast: False
steps:
@@ -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@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v5.3.1
- 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@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
with:
sarif_file: results.sarif
category: zizmor
+3 -1
View File
@@ -4,6 +4,8 @@ on:
pull_request:
types: [opened]
permissions: {}
jobs:
pre-commit-ci:
permissions:
@@ -11,7 +13,7 @@ jobs:
pull-requests: write # for srvaroa/labeler to add labels in PR
runs-on: ubuntu-latest
steps:
- uses: srvaroa/labeler@fe4b1c73bb8abf2f14a44a6912a8b4fee835d631 # v1.12.0
- uses: srvaroa/labeler@0a20eccb8c94a1ee0bed5f16859aece1c45c3e55 # v1.13.0
# Config file at .github/labeler.yml
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+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:
+38 -5
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
@@ -54,7 +60,7 @@ jobs:
name: python-package-distributions
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
compute-signatures:
name: Compute SHA1 Sums and Sign with Sigstore
@@ -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
@@ -111,13 +119,14 @@ jobs:
env:
GITHUB_TOKEN: ${{ github.token }}
TAG: ${{ needs.build.outputs.TAG }}
# Create a tag and a GitHub Release. The description can be changed later, as for now
# we don't define it through this workflow.
# Create a tag and a GitHub Release. The description is filled by the static template, we
# just insert the correct tag in the template.
run: >-
sed "s/{tag}/$TAG/g" .github/workflows/assets/release_template.html |
gh release create
"$TAG"
--repo '${{ github.repository }}'
--generate-notes
--notes-file -
- name: Upload artifact signatures to GitHub Release
env:
GITHUB_TOKEN: ${{ github.token }}
@@ -129,3 +138,27 @@ jobs:
gh release upload
"$TAG" dist/**
--repo '${{ github.repository }}'
telegram-channel:
name: Publish to Telegram Channel
needs:
- github-release
runs-on: ubuntu-latest
environment:
name: release_pypi
permissions: {}
steps:
- name: Publish to Telegram Channel
env:
TAG: ${{ needs.build.outputs.TAG }}
# This secret is configured only for the `pypi-release` branch
BOT_TOKEN: ${{ secrets.CHANNEL_BOT_TOKEN }}
run: >-
sed "s/{tag}/$TAG/g" .github/workflows/assets/release_template.html |
curl
-X POST "https://api.telegram.org/bot$BOT_TOKEN/sendMessage"
-d "chat_id=@pythontelegrambotchannel"
-d "parse_mode=HTML"
--data-urlencode "text@-"
+14 -5
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
@@ -54,7 +60,7 @@ jobs:
name: python-package-distributions
path: dist/
- name: Publish to Test PyPI
uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
with:
repository-url: https://test.pypi.org/legacy/
@@ -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
@@ -113,14 +121,15 @@ jobs:
env:
GITHUB_TOKEN: ${{ github.token }}
TAG: ${{ needs.build.outputs.TAG }}
# Create a GitHub Release *draft*. The description can be changed later, as for now
# we don't define it through this workflow.
# Create a tag and a GitHub Release *draft*. The description is filled by the static
# template, we just insert the correct tag in the template.
run: >-
sed "s/{tag}/$TAG/g" .github/workflows/assets/release_template.html |
gh release create
"$TAG"
--repo '${{ github.repository }}'
--generate-notes
--draft
--notes-file -
- name: Upload artifact signatures to GitHub Release
env:
GITHUB_TOKEN: ${{ github.token }}
+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>`_
+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
@@ -0,0 +1,19 @@
breaking = """This release removes all functionality that was deprecated in v20.x. This is in line with our :ref:`stability policy <stability-policy>`.
This includes the following changes:
- Removed ``filters.CHAT`` (all messages have an associated chat) and ``filters.StatusUpdate.USER_SHARED`` (use ``filters.StatusUpdate.USERS_SHARED`` instead).
- Removed ``Defaults.disable_web_page_preview`` and ``Defaults.quote``. Use ``Defaults.link_preview_options`` and ``Defaults.do_quote`` instead.
- Removed ``ApplicationBuilder.(get_updates_)proxy_url`` and ``HTTPXRequest.proxy_url``. Use ``ApplicationBuilder.(get_updates_)proxy`` and ``HTTPXRequest.proxy`` instead.
- Removed the ``*_timeout`` arguments of ``Application.run_polling`` and ``Updater.start_webhook``. Instead, specify the values via ``ApplicationBuilder.get_updates_*_timeout``.
- Removed ``constants.InlineQueryLimit.MIN_SWITCH_PM_TEXT_LENGTH``. Use ``constants.InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH`` instead.
- Removed the argument ``quote`` of ``Message.reply_*``. Use ``do_quote`` instead.
- Removed the superfluous ``EncryptedPassportElement.credentials`` without replacement.
- Changed attribute value of ``PassportFile.file_date`` from :obj:`int` to :class:`datetime.datetime`. Make sure to adjust your code accordingly.
- Changed the attribute value of ``PassportElementErrors.file_hashes`` from :obj:`list` to :obj:`tuple`. Make sure to adjust your code accordingly.
- Make ``BaseRequest.read_timeout`` an abstract property. If you subclass ``BaseRequest``, you need to implement this property.
- The default value for ``write_timeout`` now defaults to ``DEFAULT_NONE`` also for bot methods that send media. Previously, it was ``20``. If you subclass ``BaseRequest``, make sure to use your desired write timeout if ``RequestData.multipart_data`` is set.
"""
[[pull_requests]]
uid = "4671"
author_uid = "Bibo-Joshi"
closes_threads = ["4659"]
@@ -0,0 +1,5 @@
documentation = "Add `chango <https://chango.readthedocs.io/stable/>`_ As Changelog Management Tool"
[[pull_requests]]
uid = "4672"
author_uid = "Bibo-Joshi"
closes_threads = ["4321"]
@@ -0,0 +1,5 @@
internal = "Bump github/codeql-action from 3.28.8 to 3.28.10"
[[pull_requests]]
uid = "4697"
author_uid = "dependabot[bot]"
closes_threads = []
@@ -0,0 +1,5 @@
internal = "Bump srvaroa/labeler from 1.12.0 to 1.13.0"
[[pull_requests]]
uid = "4698"
author_uid = "dependabot[bot]"
closes_threads = []
@@ -0,0 +1,5 @@
internal = "Bump astral-sh/setup-uv from 5.2.2 to 5.3.1"
[[pull_requests]]
uid = "4699"
author_uid = "dependabot[bot]"
closes_threads = []
@@ -0,0 +1,5 @@
internal = "Bump Bibo-Joshi/chango from 0.3.1 to 0.3.2"
[[pull_requests]]
uid = "4700"
author_uid = "dependabot[bot]"
closes_threads = []
@@ -0,0 +1,5 @@
internal = "Bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4"
[[pull_requests]]
uid = "4701"
author_uid = "dependabot[bot]"
closes_threads = []
@@ -0,0 +1,5 @@
internal = "Bump pytest from 8.3.4 to 8.3.5"
[[pull_requests]]
uid = "4709"
author_uid = "dependabot[bot]"
closes_threads = []
@@ -0,0 +1,5 @@
internal = "Bump sphinx from 8.1.3 to 8.2.3"
[[pull_requests]]
uid = "4710"
author_uid = "dependabot[bot]"
closes_threads = []
@@ -0,0 +1,5 @@
internal = "Bump Bibo-Joshi/chango from 0.3.2 to 0.4.0"
[[pull_requests]]
uid = "4712"
author_uid = "Bibo-Joshi"
closes_threads = []
@@ -0,0 +1,5 @@
internal = "Bump Version to v22.0"
[[pull_requests]]
uid = "4719"
author_uid = "Bibo-Joshi"
closes_threads = []
+59 -5
View File
@@ -1,8 +1,62 @@
.. _ptb-changelog:
Version 21.11.1
===============
=========
Changelog
=========
*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 +140,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
============
+78
View File
@@ -0,0 +1,78 @@
# noqa: INP001
# pylint: disable=import-error
"""Configuration for the chango changelog tool"""
from collections.abc import Collection
from typing import Optional
from chango.concrete import DirectoryChanGo, DirectoryVersionScanner, HeaderVersionHistory
from chango.concrete.sections import GitHubSectionChangeNote, Section, SectionVersionNote
version_scanner = DirectoryVersionScanner(base_directory=".", unreleased_directory="unreleased")
class ChangoSectionChangeNote(
GitHubSectionChangeNote.with_sections( # type: ignore[misc]
[
Section(uid="highlights", title="Highlights", sort_order=0),
Section(uid="breaking", title="Breaking Changes", sort_order=1),
Section(uid="security", title="Security Changes", sort_order=2),
Section(uid="deprecations", title="Deprecations", sort_order=3),
Section(uid="features", title="New Features", sort_order=4),
Section(uid="bugfixes", title="Bug Fixes", sort_order=5),
Section(uid="dependencies", title="Dependencies", sort_order=6),
Section(uid="other", title="Other Changes", sort_order=7),
Section(uid="documentation", title="Documentation", sort_order=8),
Section(uid="internal", title="Internal Changes", sort_order=9),
]
)
):
"""Custom change note type for PTB. Mainly overrides get_sections to map labels to sections"""
OWNER = "python-telegram-bot"
REPOSITORY = "python-telegram-bot"
@classmethod
def get_sections(
cls,
labels: Collection[str],
issue_types: Optional[Collection[str]],
) -> set[str]:
"""Override get_sections to have customized auto-detection of relevant sections based on
the pull request and linked issues. Certainly not perfect in all cases, but should be a
good start for most PRs.
"""
combined_labels = set(labels) | (set(issue_types or []))
mapping = {
"🐛 bug": "bugfixes",
"💡 feature": "features",
"🧹 chore": "internal",
"⚙️ bot-api": "features",
"⚙️ documentation": "documentation",
"⚙️ tests": "internal",
"⚙️ ci-cd": "internal",
"⚙️ security": "security",
"⚙️ examples": "documentation",
"⚙️ type-hinting": "other",
"🛠 refactor": "internal",
"🛠 breaking": "breaking",
"⚙️ dependencies": "dependencies",
"🔗 github-actions": "internal",
"🛠 code-quality": "internal",
}
# we want to return *all* from the mapping that are in the combined_labels
# removing superfluous sections from the fragment is a tad easier than adding them
found = {section for label, section in mapping.items() if label in combined_labels}
# if we have not found any sections, we default to "other"
return found or {"other"}
chango_instance = DirectoryChanGo(
change_note_type=ChangoSectionChangeNote,
version_note_type=SectionVersionNote,
version_history_type=HeaderVersionHistory,
scanner=version_scanner,
)
View File
+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
+9 -9
View File
@@ -47,29 +47,29 @@ keyword_args = [
"",
]
media_write_timeout_deprecation_methods = [
"send_photo",
media_write_timeout_change_methods = [
"add_sticker_to_set",
"create_new_sticker_set",
"send_animation",
"send_audio",
"send_document",
"send_media_group",
"send_photo",
"send_sticker",
"send_video",
"send_video_note",
"send_animation",
"send_voice",
"send_media_group",
"set_chat_photo",
"upload_sticker_file",
"add_sticker_to_set",
"create_new_sticker_set",
]
media_write_timeout_deprecation = [
media_write_timeout_change = [
" write_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to "
" :paramref:`telegram.request.BaseRequest.post.write_timeout`. By default, ``20`` "
" seconds are used as write timeout."
"",
"",
" .. deprecated:: 20.7",
" In future versions, the default value will be changed to "
" .. versionchanged:: 22.0",
" The default value changed to "
" :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.",
"",
"",
+4 -4
View File
@@ -32,8 +32,8 @@ from docs.auxil.kwargs_insertion import (
find_insert_pos_for_kwargs,
get_updates_read_timeout_addition,
keyword_args,
media_write_timeout_deprecation,
media_write_timeout_deprecation_methods,
media_write_timeout_change,
media_write_timeout_change_methods,
)
from docs.auxil.link_code import LINE_NUMBERS
@@ -116,9 +116,9 @@ def autodoc_process_docstring(
if (
"post.write_timeout`. Defaults to" in to_insert
and method_name in media_write_timeout_deprecation_methods
and method_name in media_write_timeout_change_methods
):
effective_insert: list[str] = media_write_timeout_deprecation
effective_insert: list[str] = media_write_timeout_change
elif get_updates and to_insert.lstrip().startswith("read_timeout"):
effective_insert = [to_insert, *get_updates_read_timeout_addition]
else:
+2 -1
View File
@@ -1,4 +1,5 @@
sphinx==8.1.3
chango~=0.4.0
sphinx==8.2.3
furo==2024.8.6
furo-sphinx-search @ git+https://github.com/harshil21/furo-sphinx-search@v0.2.0.1
sphinx-paramlinks==0.6.0
+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;
}
+9 -1
View File
@@ -1 +1,9 @@
.. include:: ../../CHANGES.rst
.. _ptb-changelog:
=========
Changelog
=========
.. chango::
.. include:: ../../changes/LEGACY.rst
+14
View File
@@ -8,6 +8,11 @@ from pathlib import Path
# documentation root, use os.path.abspath to make it absolute, like shown here.
from sphinx.application import Sphinx
if sys.version_info < (3, 12):
# Due to dependency on chango
raise RuntimeError("This documentation needs at least Python 3.12")
sys.path.insert(0, str(Path("../..").resolve().absolute()))
# -- General configuration ------------------------------------------------
@@ -36,6 +41,7 @@ needs_sphinx = "8.1.3"
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"chango.sphinx_ext",
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
"sphinx.ext.intersphinx",
@@ -52,6 +58,9 @@ extensions = [
if os.environ.get("READTHEDOCS", "") == "True":
extensions.append("sphinx_build_compatibility.extension")
# Configuration for the chango sphinx directive
chango_pyproject_toml_path = Path(__file__).parent.parent.parent
# For shorter links to Wiki in docstrings
extlinks = {
"wiki": ("https://github.com/python-telegram-bot/python-telegram-bot/wiki/%s", "%s"),
@@ -111,6 +120,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
+2
View File
@@ -1,3 +1,5 @@
.. _stability-policy:
Stability Policy
================
+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,
)
+6 -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" }
]
@@ -105,6 +105,11 @@ search-paths = ["telegram"]
[tool.hatch.build]
packages = ["telegram"]
# CHANGO
[tool.chango]
sys_path = "changes"
chango_instance = { name= "chango_instance", module = "config" }
# BLACK:
[tool.black]
line-length = 99
+1 -1
View File
@@ -4,7 +4,7 @@
build
# For the test suite
pytest==8.3.4
pytest==8.3.5
# needed because pytest doesn't come with native support for coroutines as tests
pytest-asyncio==0.21.2
+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,
+249 -107
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
@@ -104,7 +112,7 @@ from telegram.error import EndPointNotFound, InvalidToken
from telegram.request import BaseRequest, RequestData
from telegram.request._httpxrequest import HTTPXRequest
from telegram.request._requestparameter import RequestParameter
from telegram.warnings import PTBDeprecationWarning, PTBUserWarning
from telegram.warnings import PTBUserWarning
if TYPE_CHECKING:
from telegram import (
@@ -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,
@@ -4456,19 +4581,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
if not isinstance(read_timeout, DefaultValue):
arg_read_timeout: float = read_timeout or 0
else:
try:
arg_read_timeout = self._request[0].read_timeout or 0
except NotImplementedError:
arg_read_timeout = 2
self._warn(
PTBDeprecationWarning(
"20.7",
f"The class {self._request[0].__class__.__name__} does not override "
"the property `read_timeout`. Overriding this property will be mandatory "
"in future versions. Using 2 seconds as fallback.",
),
stacklevel=2,
)
arg_read_timeout = self._request[0].read_timeout or 0
# Ideally we'd use an aggressive read timeout for the polling. However,
# * Short polling should return within 2 seconds.
@@ -4523,8 +4636,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 +4845,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 +4958,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 +5053,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 +5185,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 +5240,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 +5560,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 +5974,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 +6053,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 +6100,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 +6572,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 +6675,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 +6972,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 +6986,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 +7306,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 +7369,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 +7536,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 +7692,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 +7982,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 +8002,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 +8096,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 +8108,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 +8259,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 +8283,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 +8310,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 +8404,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 +8506,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 +9094,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 +9133,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 +9219,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 +9261,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 +9288,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 +9386,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 +9525,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 +9576,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 +9696,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 +9717,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 +9755,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 +9808,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 +9819,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 +9830,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 +9843,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 +9857,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 +9900,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 +9912,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 +9935,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 +9977,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 +10018,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 +10057,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"]:
+12 -20
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
@@ -61,6 +62,12 @@ class InlineQuery(TelegramObject):
``auto_pagination``. Use a named argument for those,
and notice that some positional arguments changed position as a result.
.. versionchanged:: 22.0
Removed constants ``MIN_START_PARAMETER_LENGTH`` and ``MAX_START_PARAMETER_LENGTH``.
Use :attr:`telegram.constants.InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH` and
:attr:`telegram.constants.InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH`
instead.
Args:
id (:obj:`str`): Unique identifier for this query.
from_user (:class:`telegram.User`): Sender.
@@ -126,17 +133,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 +147,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,
@@ -206,16 +208,6 @@ class InlineQuery(TelegramObject):
.. versionadded:: 13.2
"""
MIN_SWITCH_PM_TEXT_LENGTH: Final[int] = constants.InlineQueryLimit.MIN_SWITCH_PM_TEXT_LENGTH
""":const:`telegram.constants.InlineQueryLimit.MIN_SWITCH_PM_TEXT_LENGTH`
.. versionadded:: 20.0
"""
MAX_SWITCH_PM_TEXT_LENGTH: Final[int] = constants.InlineQueryLimit.MAX_SWITCH_PM_TEXT_LENGTH
""":const:`telegram.constants.InlineQueryLimit.MAX_SWITCH_PM_TEXT_LENGTH`
.. versionadded:: 20.0
"""
MAX_OFFSET_LENGTH: Final[int] = constants.InlineQueryLimit.MAX_OFFSET_LENGTH
""":const:`telegram.constants.InlineQueryLimit.MAX_OFFSET_LENGTH`
+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.
+11 -13
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
@@ -46,9 +47,10 @@ class InlineQueryResultsButton(TelegramObject):
inside the Web App.
start_parameter (:obj:`str`, optional): Deep-linking parameter for the
:guilabel:`/start` message sent to the bot when user presses the switch button.
:tg-const:`telegram.InlineQuery.MIN_SWITCH_PM_TEXT_LENGTH`-
:tg-const:`telegram.InlineQuery.MAX_SWITCH_PM_TEXT_LENGTH` characters,
only ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed.
:tg-const:`telegram.constants.InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH`
-
:tg-const:`telegram.constants.InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH`
characters, only ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed.
Example:
An inline bot that sends YouTube videos can ask the user to connect the bot to
@@ -66,10 +68,10 @@ class InlineQueryResultsButton(TelegramObject):
user presses the button. The Web App will be able to switch back to the inline mode
using the method ``web_app_switch_inline_query`` inside the Web App.
start_parameter (:obj:`str`): Optional. Deep-linking parameter for the
:guilabel:`/start` message sent to the bot when user presses the switch button.
:tg-const:`telegram.InlineQuery.MIN_SWITCH_PM_TEXT_LENGTH`-
:tg-const:`telegram.InlineQuery.MAX_SWITCH_PM_TEXT_LENGTH` characters,
only ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed.
:tg-const:`telegram.constants.InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH`
-
:tg-const:`telegram.constants.InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH`
characters, only ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed.
"""
@@ -97,14 +99,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]
+217 -244
View File
File diff suppressed because it is too large Load Diff
+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 -29
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:
@@ -158,10 +164,6 @@ class EncryptedPassportElement(TelegramObject):
reverse_side: Optional[PassportFile] = None,
selfie: Optional[PassportFile] = None,
translation: Optional[Sequence[PassportFile]] = None,
# TODO: Remove the credentials argument in 22.0 or later
credentials: Optional[ # pylint: disable=unused-argument # noqa: ARG002
"Credentials"
] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -194,27 +196,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 +231,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 +256,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)
+26 -59
View File
@@ -19,12 +19,12 @@
# pylint: disable=redefined-builtin
"""This module contains the classes that represent Telegram PassportElementError."""
from collections.abc import Sequence
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
from telegram._utils.warnings import warn
from telegram.warnings import PTBDeprecationWarning
class PassportElementError(TelegramObject):
@@ -168,23 +168,30 @@ class PassportElementErrorFiles(PassportElementError):
type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
``"utility_bill"``, ``"bank_statement"``, ``"rental_agreement"``,
``"passport_registration"``, ``"temporary_registration"``.
file_hashes (list[:obj:`str`]): List of base64-encoded file hashes.
file_hashes (Sequence[:obj:`str`]): List of base64-encoded file hashes.
.. versionchanged:: 22.0
|sequenceargs|
message (:obj:`str`): Error message.
Attributes:
type (:obj:`str`): The section of the user's Telegram Passport which has the issue, one of
``"utility_bill"``, ``"bank_statement"``, ``"rental_agreement"``,
``"passport_registration"``, ``"temporary_registration"``.
file_hashes (tuple[:obj:`str`]): List of base64-encoded file hashes.
.. versionchanged:: 22.0
|tupleclassattrs|
message (:obj:`str`): Error message.
"""
__slots__ = ("_file_hashes",)
__slots__ = ("file_hashes",)
def __init__(
self,
type: str,
file_hashes: list[str],
file_hashes: Sequence[str],
message: str,
*,
api_kwargs: Optional[JSONDict] = None,
@@ -192,32 +199,9 @@ class PassportElementErrorFiles(PassportElementError):
# Required
super().__init__("files", type, message, api_kwargs=api_kwargs)
with self._unfrozen():
self._file_hashes: list[str] = file_hashes
self.file_hashes: tuple[str, ...] = parse_sequence_arg(file_hashes)
self._id_attrs = (self.source, self.type, self.message, *tuple(file_hashes))
def to_dict(self, recursive: bool = True) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict` for details."""
data = super().to_dict(recursive)
data["file_hashes"] = self._file_hashes
return data
@property
def file_hashes(self) -> list[str]:
"""List of base64-encoded file hashes.
.. deprecated:: 20.6
This attribute will return a tuple instead of a list in future major versions.
"""
warn(
PTBDeprecationWarning(
"20.6",
"The attribute `file_hashes` will return a tuple instead of a list in future major"
" versions.",
),
stacklevel=2,
)
return self._file_hashes
self._id_attrs = (self.source, self.type, self.message, self.file_hashes)
class PassportElementErrorFrontSide(PassportElementError):
@@ -386,7 +370,10 @@ class PassportElementErrorTranslationFiles(PassportElementError):
one of ``"passport"``, ``"driver_license"``, ``"identity_card"``,
``"internal_passport"``, ``"utility_bill"``, ``"bank_statement"``,
``"rental_agreement"``, ``"passport_registration"``, ``"temporary_registration"``.
file_hashes (list[:obj:`str`]): List of base64-encoded file hashes.
file_hashes (Sequence[:obj:`str`]): List of base64-encoded file hashes.
.. versionchanged:: 22.0
|sequenceargs|
message (:obj:`str`): Error message.
Attributes:
@@ -394,16 +381,20 @@ class PassportElementErrorTranslationFiles(PassportElementError):
one of ``"passport"``, ``"driver_license"``, ``"identity_card"``,
``"internal_passport"``, ``"utility_bill"``, ``"bank_statement"``,
``"rental_agreement"``, ``"passport_registration"``, ``"temporary_registration"``.
file_hashes (tuple[:obj:`str`]): List of base64-encoded file hashes.
.. versionchanged:: 22.0
|tupleclassattrs|
message (:obj:`str`): Error message.
"""
__slots__ = ("_file_hashes",)
__slots__ = ("file_hashes",)
def __init__(
self,
type: str,
file_hashes: list[str],
file_hashes: Sequence[str],
message: str,
*,
api_kwargs: Optional[JSONDict] = None,
@@ -411,33 +402,9 @@ class PassportElementErrorTranslationFiles(PassportElementError):
# Required
super().__init__("translation_files", type, message, api_kwargs=api_kwargs)
with self._unfrozen():
self._file_hashes: list[str] = file_hashes
self.file_hashes: tuple[str, ...] = parse_sequence_arg(file_hashes)
self._id_attrs = (self.source, self.type, self.message, *tuple(file_hashes))
def to_dict(self, recursive: bool = True) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict` for details."""
data = super().to_dict(recursive)
data["file_hashes"] = self._file_hashes
return data
@property
def file_hashes(self) -> list[str]:
"""List of base64-encoded file hashes.
.. deprecated:: 20.6
This attribute will return a tuple instead of a list in future major versions.
"""
warn(
PTBDeprecationWarning(
"20.6",
"The attribute `file_hashes` will return a tuple instead of a list in future major"
" versions. See the stability policy:"
" https://docs.python-telegram-bot.org/en/stable/stability_policy.html",
),
stacklevel=2,
)
return self._file_hashes
self._id_attrs = (self.source, self.type, self.message, self.file_hashes)
class PassportElementErrorUnspecified(PassportElementError):
+26 -40
View File
@@ -18,13 +18,13 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Encrypted PassportFile."""
import datetime as dtm
from typing import TYPE_CHECKING, Optional
from telegram._telegramobject import TelegramObject
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
from telegram._utils.warnings import warn
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import Bot, File, FileCredentials
@@ -45,11 +45,11 @@ class PassportFile(TelegramObject):
is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file.
file_size (:obj:`int`): File size in bytes.
file_date (:obj:`int`): Unix time when the file was uploaded.
file_date (:class:`datetime.datetime`): Time when the file was uploaded.
.. deprecated:: 20.6
This argument will only accept a datetime instead of an integer in future
major versions.
.. versionchanged:: 22.0
Accepts only :class:`datetime.datetime` instead of :obj:`int`.
|datetime_localization|
Attributes:
file_id (:obj:`str`): Identifier for this file, which can be used to download
@@ -58,11 +58,16 @@ class PassportFile(TelegramObject):
is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file.
file_size (:obj:`int`): File size in bytes.
file_date (:class:`datetime.datetime`): Time when the file was uploaded.
.. versionchanged:: 22.0
Returns :class:`datetime.datetime` instead of :obj:`int`.
|datetime_localization|
"""
__slots__ = (
"_credentials",
"_file_date",
"file_date",
"file_id",
"file_size",
"file_unique_id",
@@ -72,7 +77,7 @@ class PassportFile(TelegramObject):
self,
file_id: str,
file_unique_id: str,
file_date: int,
file_date: dtm.datetime,
file_size: int,
credentials: Optional["FileCredentials"] = None,
*,
@@ -84,7 +89,7 @@ class PassportFile(TelegramObject):
self.file_id: str = file_id
self.file_unique_id: str = file_unique_id
self.file_size: int = file_size
self._file_date: int = file_date
self.file_date: dtm.datetime = file_date
# Optionals
self._credentials: Optional[FileCredentials] = credentials
@@ -93,33 +98,21 @@ class PassportFile(TelegramObject):
self._freeze()
def to_dict(self, recursive: bool = True) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict` for details."""
data = super().to_dict(recursive)
data["file_date"] = self._file_date
return data
@classmethod
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "PassportFile":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
@property
def file_date(self) -> int:
""":obj:`int`: Unix time when the file was uploaded.
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["file_date"] = from_timestamp(data.get("file_date"), tzinfo=loc_tzinfo)
.. deprecated:: 20.6
This attribute will return a datetime instead of a integer in future major versions.
"""
warn(
PTBDeprecationWarning(
"20.6",
"The attribute `file_date` will return a datetime instead of an integer in future"
" major versions.",
),
stacklevel=2,
)
return self._file_date
return super().de_json(data=data, bot=bot)
@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 +134,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 +141,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 +169,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)

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