From 73d54f4e20e67f52adb2f487a4a4d8a7d7953786 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 22 Mar 2026 02:01:41 +0000 Subject: [PATCH 01/39] Update Ruff to v0.15.7 (#5177) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- .../5177.fg9Y3ZSboi94mttv3eas2L.toml | 5 ++ pyproject.toml | 2 +- uv.lock | 46 +++++++++---------- 4 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 changes/unreleased/5177.fg9Y3ZSboi94mttv3eas2L.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 81aba867ac4..dc27de1adcd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.15.6' + rev: 'v0.15.7' hooks: # Run the linter: - id: ruff-check diff --git a/changes/unreleased/5177.fg9Y3ZSboi94mttv3eas2L.toml b/changes/unreleased/5177.fg9Y3ZSboi94mttv3eas2L.toml new file mode 100644 index 00000000000..c89b0dd3f90 --- /dev/null +++ b/changes/unreleased/5177.fg9Y3ZSboi94mttv3eas2L.toml @@ -0,0 +1,5 @@ +internal = "Update Ruff to v0.15.7" +[[pull_requests]] +uid = "5177" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index 2c9909e04ec..f6f1349e511 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,7 @@ docs = [ ] linting = [ "prek", - "ruff==0.15.6", + "ruff==0.15.7", "mypy==1.19.1", "pylint==4.0.5" ] diff --git a/uv.lock b/uv.lock index 1dcaef13739..9e430d530ea 100644 --- a/uv.lock +++ b/uv.lock @@ -1631,7 +1631,7 @@ all = [ { name = "pytest-cov" }, { name = "pytest-xdist", specifier = "==3.8.0" }, { name = "pytz" }, - { name = "ruff", specifier = "==0.15.6" }, + { name = "ruff", specifier = "==0.15.7" }, { name = "sphinx", marker = "python_full_version >= '3.12'", specifier = "==9.1.0" }, { name = "sphinx-build-compatibility", git = "https://github.com/readthedocs/sphinx-build-compatibility.git?rev=58aabc5f207c6c2421f23d3578adc0b14af57047" }, { name = "sphinx-copybutton", specifier = "==0.5.2" }, @@ -1655,7 +1655,7 @@ linting = [ { name = "mypy", specifier = "==1.19.1" }, { name = "prek" }, { name = "pylint", specifier = "==4.0.5" }, - { name = "ruff", specifier = "==0.15.6" }, + { name = "ruff", specifier = "==0.15.7" }, ] tests = [ { name = "beautifulsoup4" }, @@ -1781,27 +1781,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/df/f8629c19c5318601d3121e230f74cbee7a3732339c52b21daa2b82ef9c7d/ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4", size = 4597916, upload-time = "2026-03-12T23:05:47.51Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/2f/4e03a7e5ce99b517e98d3b4951f411de2b0fa8348d39cf446671adcce9a2/ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff", size = 10508953, upload-time = "2026-03-12T23:05:17.246Z" }, - { url = "https://files.pythonhosted.org/packages/70/60/55bcdc3e9f80bcf39edf0cd272da6fa511a3d94d5a0dd9e0adf76ceebdb4/ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3", size = 10942257, upload-time = "2026-03-12T23:05:23.076Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f9/005c29bd1726c0f492bfa215e95154cf480574140cb5f867c797c18c790b/ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb", size = 10322683, upload-time = "2026-03-12T23:05:33.738Z" }, - { url = "https://files.pythonhosted.org/packages/5f/74/2f861f5fd7cbb2146bddb5501450300ce41562da36d21868c69b7a828169/ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8", size = 10660986, upload-time = "2026-03-12T23:05:53.245Z" }, - { url = "https://files.pythonhosted.org/packages/c1/a1/309f2364a424eccb763cdafc49df843c282609f47fe53aa83f38272389e0/ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e", size = 10332177, upload-time = "2026-03-12T23:05:56.145Z" }, - { url = "https://files.pythonhosted.org/packages/30/41/7ebf1d32658b4bab20f8ac80972fb19cd4e2c6b78552be263a680edc55ac/ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15", size = 11170783, upload-time = "2026-03-12T23:06:01.742Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/6d488f6adca047df82cd62c304638bcb00821c36bd4881cfca221561fdfc/ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9", size = 12044201, upload-time = "2026-03-12T23:05:28.697Z" }, - { url = "https://files.pythonhosted.org/packages/71/68/e6f125df4af7e6d0b498f8d373274794bc5156b324e8ab4bf5c1b4fc0ec7/ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab", size = 11421561, upload-time = "2026-03-12T23:05:31.236Z" }, - { url = "https://files.pythonhosted.org/packages/f1/9f/f85ef5fd01a52e0b472b26dc1b4bd228b8f6f0435975442ffa4741278703/ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e", size = 11310928, upload-time = "2026-03-12T23:05:45.288Z" }, - { url = "https://files.pythonhosted.org/packages/8c/26/b75f8c421f5654304b89471ed384ae8c7f42b4dff58fa6ce1626d7f2b59a/ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c", size = 11235186, upload-time = "2026-03-12T23:05:50.677Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d4/d5a6d065962ff7a68a86c9b4f5500f7d101a0792078de636526c0edd40da/ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512", size = 10635231, upload-time = "2026-03-12T23:05:37.044Z" }, - { url = "https://files.pythonhosted.org/packages/d6/56/7c3acf3d50910375349016cf33de24be021532042afbed87942858992491/ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0", size = 10340357, upload-time = "2026-03-12T23:06:04.748Z" }, - { url = "https://files.pythonhosted.org/packages/06/54/6faa39e9c1033ff6a3b6e76b5df536931cd30caf64988e112bbf91ef5ce5/ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb", size = 10860583, upload-time = "2026-03-12T23:05:58.978Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/509a201b843b4dfb0b32acdedf68d951d3377988cae43949ba4c4133a96a/ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0", size = 11410976, upload-time = "2026-03-12T23:05:39.955Z" }, - { url = "https://files.pythonhosted.org/packages/6c/25/3fc9114abf979a41673ce877c08016f8e660ad6cf508c3957f537d2e9fa9/ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c", size = 10616872, upload-time = "2026-03-12T23:05:42.451Z" }, - { url = "https://files.pythonhosted.org/packages/89/7a/09ece68445ceac348df06e08bf75db72d0e8427765b96c9c0ffabc1be1d9/ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406", size = 11787271, upload-time = "2026-03-12T23:05:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" }, +version = "0.15.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, + { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, + { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, + { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, + { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, ] [[package]] From 26203b447433869bfb1f2e0c31fb0279dc7b9f99 Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Date: Wed, 25 Mar 2026 17:18:15 -0700 Subject: [PATCH 02/39] Migrate from deprecated test-results-action to codecov-action (#5183) --- .github/workflows/unit_tests.yml | 3 ++- AUTHORS.rst | 1 + changes/unreleased/5183.Enoyw5yoT4S9uRxn7v5ud5.toml | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 changes/unreleased/5183.Enoyw5yoT4S9uRxn7v5ud5.toml diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 16965b643d8..df2449a2244 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -94,8 +94,9 @@ jobs: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} - name: Upload test results to Codecov - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 if: ${{ !cancelled() }} with: files: .test_report_no_optionals_junit.xml,.test_report_optionals_junit.xml token: ${{ secrets.CODECOV_TOKEN }} + report_type: test_results diff --git a/AUTHORS.rst b/AUTHORS.rst index 8851cee1668..299f88ad3ee 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -79,6 +79,7 @@ The following wonderful people contributed directly or indirectly to this projec - `kennethcheo `_ - `Kirill Vasin `_ - `Kjwon15 `_ +- `Krishna Chaitanya Balusu `_ - `Li-aung Yip `_ - `locobott `_ - `Loo Zheng Yuan `_ diff --git a/changes/unreleased/5183.Enoyw5yoT4S9uRxn7v5ud5.toml b/changes/unreleased/5183.Enoyw5yoT4S9uRxn7v5ud5.toml new file mode 100644 index 00000000000..f5eb4f4671c --- /dev/null +++ b/changes/unreleased/5183.Enoyw5yoT4S9uRxn7v5ud5.toml @@ -0,0 +1,5 @@ +internal = "Migrate from deprecated test-results-action to codecov-action" +[[pull_requests]] +uid = "5183" +author_uids = ["Krishnachaitanyakc"] +closes_threads = ["5158"] From 1e82e6260b6db1900abc2cd96ce0f949d437cbc8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:46:04 +0000 Subject: [PATCH 03/39] Update Ruff to v0.15.8 (#5184) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- .../5184.YKdeCfWDDXk8v3DJx8nTeg.toml | 5 ++ pyproject.toml | 2 +- uv.lock | 46 +++++++++---------- 4 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 changes/unreleased/5184.YKdeCfWDDXk8v3DJx8nTeg.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dc27de1adcd..d203ca6257c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.15.7' + rev: 'v0.15.8' hooks: # Run the linter: - id: ruff-check diff --git a/changes/unreleased/5184.YKdeCfWDDXk8v3DJx8nTeg.toml b/changes/unreleased/5184.YKdeCfWDDXk8v3DJx8nTeg.toml new file mode 100644 index 00000000000..fe9d3496f80 --- /dev/null +++ b/changes/unreleased/5184.YKdeCfWDDXk8v3DJx8nTeg.toml @@ -0,0 +1,5 @@ +internal = "Update Ruff to v0.15.8" +[[pull_requests]] +uid = "5184" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index f6f1349e511..8e37fef4d7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,7 @@ docs = [ ] linting = [ "prek", - "ruff==0.15.7", + "ruff==0.15.8", "mypy==1.19.1", "pylint==4.0.5" ] diff --git a/uv.lock b/uv.lock index 9e430d530ea..aac46f030e9 100644 --- a/uv.lock +++ b/uv.lock @@ -1631,7 +1631,7 @@ all = [ { name = "pytest-cov" }, { name = "pytest-xdist", specifier = "==3.8.0" }, { name = "pytz" }, - { name = "ruff", specifier = "==0.15.7" }, + { name = "ruff", specifier = "==0.15.8" }, { name = "sphinx", marker = "python_full_version >= '3.12'", specifier = "==9.1.0" }, { name = "sphinx-build-compatibility", git = "https://github.com/readthedocs/sphinx-build-compatibility.git?rev=58aabc5f207c6c2421f23d3578adc0b14af57047" }, { name = "sphinx-copybutton", specifier = "==0.5.2" }, @@ -1655,7 +1655,7 @@ linting = [ { name = "mypy", specifier = "==1.19.1" }, { name = "prek" }, { name = "pylint", specifier = "==4.0.5" }, - { name = "ruff", specifier = "==0.15.7" }, + { name = "ruff", specifier = "==0.15.8" }, ] tests = [ { name = "beautifulsoup4" }, @@ -1781,27 +1781,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, - { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, - { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, - { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, - { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, - { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, - { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, - { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, - { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, - { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, - { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, - { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, - { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, +version = "0.15.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, + { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, + { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, + { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, + { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, + { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, + { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, + { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, + { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, + { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, + { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, ] [[package]] From 3ef646edbf555b95194784036e3fc3268a5b8e85 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 01:53:30 +0000 Subject: [PATCH 04/39] Update dependency cryptography to v46.0.6 [SECURITY] (#5185) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../5185.VTFw6hNJ4A9mGayu2rduUt.toml | 5 + uv.lock | 102 +++++++++--------- 2 files changed, 56 insertions(+), 51 deletions(-) create mode 100644 changes/unreleased/5185.VTFw6hNJ4A9mGayu2rduUt.toml diff --git a/changes/unreleased/5185.VTFw6hNJ4A9mGayu2rduUt.toml b/changes/unreleased/5185.VTFw6hNJ4A9mGayu2rduUt.toml new file mode 100644 index 00000000000..4b2a625b5e8 --- /dev/null +++ b/changes/unreleased/5185.VTFw6hNJ4A9mGayu2rduUt.toml @@ -0,0 +1,5 @@ +dependencies = "Update dependency cryptography to v46.0.6 [SECURITY]" +[[pull_requests]] +uid = "5185" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/uv.lock b/uv.lock index aac46f030e9..b390c0f8afd 100644 --- a/uv.lock +++ b/uv.lock @@ -495,62 +495,62 @@ toml = [ [[package]] name = "cryptography" -version = "46.0.5" +version = "46.0.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, - { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, - { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, - { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, - { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, - { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, - { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, - { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, - { url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, - { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, - { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, - { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, - { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, - { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, - { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, - { url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" }, - { url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" }, - { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, - { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, - { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, - { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, - { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, - { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, - { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, - { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, - { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, - { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, - { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, - { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, - { url = "https://files.pythonhosted.org/packages/eb/dd/2d9fdb07cebdf3d51179730afb7d5e576153c6744c3ff8fded23030c204e/cryptography-46.0.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c", size = 3476964, upload-time = "2026-02-10T19:18:20.687Z" }, - { url = "https://files.pythonhosted.org/packages/e9/6f/6cc6cc9955caa6eaf83660b0da2b077c7fe8ff9950a3c5e45d605038d439/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a", size = 4218321, upload-time = "2026-02-10T19:18:22.349Z" }, - { url = "https://files.pythonhosted.org/packages/3e/5d/c4da701939eeee699566a6c1367427ab91a8b7088cc2328c09dbee940415/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356", size = 4381786, upload-time = "2026-02-10T19:18:24.529Z" }, - { url = "https://files.pythonhosted.org/packages/ac/97/a538654732974a94ff96c1db621fa464f455c02d4bb7d2652f4edc21d600/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da", size = 4217990, upload-time = "2026-02-10T19:18:25.957Z" }, - { url = "https://files.pythonhosted.org/packages/ae/11/7e500d2dd3ba891197b9efd2da5454b74336d64a7cc419aa7327ab74e5f6/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257", size = 4381252, upload-time = "2026-02-10T19:18:27.496Z" }, - { url = "https://files.pythonhosted.org/packages/bc/58/6b3d24e6b9bc474a2dcdee65dfd1f008867015408a271562e4b690561a4d/cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7", size = 3407605, upload-time = "2026-02-10T19:18:29.233Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" }, + { url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" }, + { url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" }, + { url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" }, + { url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" }, + { url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" }, + { url = "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", size = 7117147, upload-time = "2026-03-25T23:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", size = 4266221, upload-time = "2026-03-25T23:33:49.874Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", size = 4408952, upload-time = "2026-03-25T23:33:52.128Z" }, + { url = "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", size = 4270141, upload-time = "2026-03-25T23:33:54.11Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", size = 4904178, upload-time = "2026-03-25T23:33:55.725Z" }, + { url = "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", size = 4441812, upload-time = "2026-03-25T23:33:57.364Z" }, + { url = "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", size = 3963923, upload-time = "2026-03-25T23:33:59.361Z" }, + { url = "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", size = 4269695, upload-time = "2026-03-25T23:34:00.909Z" }, + { url = "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", size = 4869785, upload-time = "2026-03-25T23:34:02.796Z" }, + { url = "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", size = 4441404, upload-time = "2026-03-25T23:34:04.35Z" }, + { url = "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", size = 4397549, upload-time = "2026-03-25T23:34:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", size = 4651874, upload-time = "2026-03-25T23:34:07.916Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", size = 3001511, upload-time = "2026-03-25T23:34:09.892Z" }, + { url = "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", size = 3471692, upload-time = "2026-03-25T23:34:11.613Z" }, + { url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" }, + { url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" }, + { url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" }, + { url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" }, + { url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" }, + { url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" }, + { url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" }, + { url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" }, + { url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" }, + { url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" }, + { url = "https://files.pythonhosted.org/packages/2e/84/7ccff00ced5bac74b775ce0beb7d1be4e8637536b522b5df9b73ada42da2/cryptography-46.0.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:2ea0f37e9a9cf0df2952893ad145fd9627d326a59daec9b0802480fa3bcd2ead", size = 3475444, upload-time = "2026-03-25T23:34:38.944Z" }, + { url = "https://files.pythonhosted.org/packages/bc/1f/4c926f50df7749f000f20eede0c896769509895e2648db5da0ed55db711d/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a3e84d5ec9ba01f8fd03802b2147ba77f0c8f2617b2aff254cedd551844209c8", size = 4218227, upload-time = "2026-03-25T23:34:40.871Z" }, + { url = "https://files.pythonhosted.org/packages/c6/65/707be3ffbd5f786028665c3223e86e11c4cda86023adbc56bd72b1b6bab5/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:12f0fa16cc247b13c43d56d7b35287ff1569b5b1f4c5e87e92cc4fcc00cd10c0", size = 4381399, upload-time = "2026-03-25T23:34:42.609Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6d/73557ed0ef7d73d04d9aba745d2c8e95218213687ee5e76b7d236a5030fc/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:50575a76e2951fe7dbd1f56d181f8c5ceeeb075e9ff88e7ad997d2f42af06e7b", size = 4217595, upload-time = "2026-03-25T23:34:44.205Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c5/e1594c4eec66a567c3ac4400008108a415808be2ce13dcb9a9045c92f1a0/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:90e5f0a7b3be5f40c3a0a0eafb32c681d8d2c181fc2a1bdabe9b3f611d9f6b1a", size = 4380912, upload-time = "2026-03-25T23:34:46.328Z" }, + { url = "https://files.pythonhosted.org/packages/1a/89/843b53614b47f97fe1abc13f9a86efa5ec9e275292c457af1d4a60dc80e0/cryptography-46.0.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6728c49e3b2c180ef26f8e9f0a883a2c585638db64cf265b49c9ba10652d430e", size = 3409955, upload-time = "2026-03-25T23:34:48.465Z" }, ] [[package]] From 5f406fb32f88af61f8a71741d9f1e75bec323eab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 01:18:16 +0000 Subject: [PATCH 05/39] Update astral-sh/setup-uv action to v7.6.0 (#5191) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/copilot-setup-steps.yml | 2 +- .github/workflows/gha_security.yml | 2 +- changes/unreleased/5191.B6JK8KhgoqqawRrrWaSCDH.toml | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changes/unreleased/5191.B6JK8KhgoqqawRrrWaSCDH.toml diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index cba6d41dc3c..6985b84be0a 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -31,7 +31,7 @@ jobs: persist-credentials: false - name: Install uv - uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 with: # Install a specific version of uv. version: "0.10.10" diff --git a/.github/workflows/gha_security.yml b/.github/workflows/gha_security.yml index 5807b28be53..e74c6ab7823 100644 --- a/.github/workflows/gha_security.yml +++ b/.github/workflows/gha_security.yml @@ -21,7 +21,7 @@ jobs: with: persist-credentials: false - name: Install the latest version of uv - uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 - name: Run zizmor run: uvx zizmor --persona=pedantic --format sarif . > results.sarif env: diff --git a/changes/unreleased/5191.B6JK8KhgoqqawRrrWaSCDH.toml b/changes/unreleased/5191.B6JK8KhgoqqawRrrWaSCDH.toml new file mode 100644 index 00000000000..af1b24d818d --- /dev/null +++ b/changes/unreleased/5191.B6JK8KhgoqqawRrrWaSCDH.toml @@ -0,0 +1,5 @@ +internal = "Update astral-sh/setup-uv action to v7.6.0" +[[pull_requests]] +uid = "5191" +author_uids = ["renovate[bot]"] +closes_threads = [] From bc5f3de3c33b76c85fae97cef163ddf50f4da1d2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 06:06:22 +0000 Subject: [PATCH 06/39] Update dependency astral-sh/uv to v0.11.2 (#5192) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/copilot-setup-steps.yml | 2 +- changes/unreleased/5192.VYDBGMwXeTVyvXESmEnfFa.toml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/unreleased/5192.VYDBGMwXeTVyvXESmEnfFa.toml diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 6985b84be0a..6f6eb5c4397 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -34,7 +34,7 @@ jobs: uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 with: # Install a specific version of uv. - version: "0.10.10" + version: "0.11.2" # Install 3.13: python-version: 3.13 diff --git a/changes/unreleased/5192.VYDBGMwXeTVyvXESmEnfFa.toml b/changes/unreleased/5192.VYDBGMwXeTVyvXESmEnfFa.toml new file mode 100644 index 00000000000..c8585ba7989 --- /dev/null +++ b/changes/unreleased/5192.VYDBGMwXeTVyvXESmEnfFa.toml @@ -0,0 +1,5 @@ +internal = "Update dependency astral-sh/uv to v0.11.2" +[[pull_requests]] +uid = "5192" +author_uids = ["renovate[bot]"] +closes_threads = [] From 6827d3da752700df0b02810194033000fb7839ca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 06:06:24 +0000 Subject: [PATCH 07/39] Update dependency mypy to v1.20.0 (#5193) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../5193.GqEK23Erwr49GQ79uJho38.toml | 5 ++ pyproject.toml | 2 +- uv.lock | 84 +++++++++++-------- 3 files changed, 54 insertions(+), 37 deletions(-) create mode 100644 changes/unreleased/5193.GqEK23Erwr49GQ79uJho38.toml diff --git a/changes/unreleased/5193.GqEK23Erwr49GQ79uJho38.toml b/changes/unreleased/5193.GqEK23Erwr49GQ79uJho38.toml new file mode 100644 index 00000000000..26e3560e0a5 --- /dev/null +++ b/changes/unreleased/5193.GqEK23Erwr49GQ79uJho38.toml @@ -0,0 +1,5 @@ +internal = "Update dependency mypy to v1.20.0" +[[pull_requests]] +uid = "5193" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index 8e37fef4d7a..e229b09b5bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,7 +134,7 @@ docs = [ linting = [ "prek", "ruff==0.15.8", - "mypy==1.19.1", + "mypy==1.20.0", "pylint==4.0.5" ] all = [{ include-group = "tests" }, { include-group = "docs" }, { include-group = "linting"}] diff --git a/uv.lock b/uv.lock index b390c0f8afd..457b29398e8 100644 --- a/uv.lock +++ b/uv.lock @@ -975,7 +975,7 @@ wheels = [ [[package]] name = "mypy" -version = "1.19.1" +version = "1.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, @@ -984,39 +984,51 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", size = 13101333, upload-time = "2025-12-15T05:03:03.28Z" }, - { url = "https://files.pythonhosted.org/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", size = 12164102, upload-time = "2025-12-15T05:02:33.611Z" }, - { url = "https://files.pythonhosted.org/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", size = 12765799, upload-time = "2025-12-15T05:03:28.44Z" }, - { url = "https://files.pythonhosted.org/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", size = 13522149, upload-time = "2025-12-15T05:02:36.011Z" }, - { url = "https://files.pythonhosted.org/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", size = 13810105, upload-time = "2025-12-15T05:02:40.327Z" }, - { url = "https://files.pythonhosted.org/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", size = 10057200, upload-time = "2025-12-15T05:02:51.012Z" }, - { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, - { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, - { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, - { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, - { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, - { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, - { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, - { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, - { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, - { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, - { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, - { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, - { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, - { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, - { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, - { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, - { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, - { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/b0089fe7fef0a994ae5ee07029ced0526082c6cfaaa4c10d40a10e33b097/mypy-1.20.0.tar.gz", hash = "sha256:eb96c84efcc33f0b5e0e04beacf00129dd963b67226b01c00b9dfc8affb464c3", size = 3815028, upload-time = "2026-03-31T16:55:14.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/a2/a965c8c3fcd4fa8b84ba0d46606181b0d0a1d50f274c67877f3e9ed4882c/mypy-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d99f515f95fd03a90875fdb2cca12ff074aa04490db4d190905851bdf8a549a8", size = 14430138, upload-time = "2026-03-31T16:52:37.843Z" }, + { url = "https://files.pythonhosted.org/packages/53/6e/043477501deeb8eabbab7f1a2f6cac62cfb631806dc1d6862a04a7f5011b/mypy-1.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bd0212976dc57a5bfeede7c219e7cd66568a32c05c9129686dd487c059c1b88a", size = 13311282, upload-time = "2026-03-31T16:55:11.021Z" }, + { url = "https://files.pythonhosted.org/packages/65/aa/bd89b247b83128197a214f29f0632ff3c14f54d4cd70d144d157bd7d7d6e/mypy-1.20.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8426d4d75d68714abc17a4292d922f6ba2cfb984b72c2278c437f6dae797865", size = 13750889, upload-time = "2026-03-31T16:52:02.909Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9d/2860be7355c45247ccc0be1501c91176318964c2a137bd4743f58ce6200e/mypy-1.20.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02cca0761c75b42a20a2757ae58713276605eb29a08dd8a6e092aa347c4115ca", size = 14619788, upload-time = "2026-03-31T16:50:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/75/7f/3ef3e360c91f3de120f205c8ce405e9caf9fc52ef14b65d37073e322c114/mypy-1.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3a49064504be59e59da664c5e149edc1f26c67c4f8e8456f6ba6aba55033018", size = 14918849, upload-time = "2026-03-31T16:51:10.478Z" }, + { url = "https://files.pythonhosted.org/packages/ae/72/af970dfe167ef788df7c5e6109d2ed0229f164432ce828bc9741a4250e64/mypy-1.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:ebea00201737ad4391142808ed16e875add5c17f676e0912b387739f84991e13", size = 10822007, upload-time = "2026-03-31T16:50:25.268Z" }, + { url = "https://files.pythonhosted.org/packages/93/94/ba9065c2ebe5421619aff684b793d953e438a8bfe31a320dd6d1e0706e81/mypy-1.20.0-cp310-cp310-win_arm64.whl", hash = "sha256:e80cf77847d0d3e6e3111b7b25db32a7f8762fd4b9a3a72ce53fe16a2863b281", size = 9756158, upload-time = "2026-03-31T16:48:36.213Z" }, + { url = "https://files.pythonhosted.org/packages/6e/1c/74cb1d9993236910286865679d1c616b136b2eae468493aa939431eda410/mypy-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4525e7010b1b38334516181c5b81e16180b8e149e6684cee5a727c78186b4e3b", size = 14343972, upload-time = "2026-03-31T16:49:04.887Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/01399515eca280386e308cf57901e68d3a52af18691941b773b3380c1df8/mypy-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a17c5d0bdcca61ce24a35beb828a2d0d323d3fcf387d7512206888c900193367", size = 13225007, upload-time = "2026-03-31T16:50:08.151Z" }, + { url = "https://files.pythonhosted.org/packages/56/ac/b4ba5094fb2d7fe9d2037cd8d18bbe02bcf68fd22ab9ff013f55e57ba095/mypy-1.20.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75ff57defcd0f1d6e006d721ccdec6c88d4f6a7816eb92f1c4890d979d9ee62", size = 13663752, upload-time = "2026-03-31T16:49:26.064Z" }, + { url = "https://files.pythonhosted.org/packages/db/a7/460678d3cf7da252d2288dad0c602294b6ec22a91932ec368cc11e44bb6e/mypy-1.20.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b503ab55a836136b619b5fc21c8803d810c5b87551af8600b72eecafb0059cb0", size = 14532265, upload-time = "2026-03-31T16:53:55.077Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3e/051cca8166cf0438ae3ea80e0e7c030d7a8ab98dffc93f80a1aa3f23c1a2/mypy-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1973868d2adbb4584a3835780b27436f06d1dc606af5be09f187aaa25be1070f", size = 14768476, upload-time = "2026-03-31T16:50:34.587Z" }, + { url = "https://files.pythonhosted.org/packages/be/66/8e02ec184f852ed5c4abb805583305db475930854e09964b55e107cdcbc4/mypy-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:2fcedb16d456106e545b2bfd7ef9d24e70b38ec252d2a629823a4d07ebcdb69e", size = 10818226, upload-time = "2026-03-31T16:53:15.624Z" }, + { url = "https://files.pythonhosted.org/packages/13/4b/383ad1924b28f41e4879a74151e7a5451123330d45652da359f9183bcd45/mypy-1.20.0-cp311-cp311-win_arm64.whl", hash = "sha256:379edf079ce44ac8d2805bcf9b3dd7340d4f97aad3a5e0ebabbf9d125b84b442", size = 9750091, upload-time = "2026-03-31T16:54:12.162Z" }, + { url = "https://files.pythonhosted.org/packages/be/dd/3afa29b58c2e57c79116ed55d700721c3c3b15955e2b6251dd165d377c0e/mypy-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:002b613ae19f4ac7d18b7e168ffe1cb9013b37c57f7411984abbd3b817b0a214", size = 14509525, upload-time = "2026-03-31T16:55:01.824Z" }, + { url = "https://files.pythonhosted.org/packages/54/eb/227b516ab8cad9f2a13c5e7a98d28cd6aa75e9c83e82776ae6c1c4c046c7/mypy-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9336b5e6712f4adaf5afc3203a99a40b379049104349d747eb3e5a3aa23ac2e", size = 13326469, upload-time = "2026-03-31T16:51:41.23Z" }, + { url = "https://files.pythonhosted.org/packages/57/d4/1ddb799860c1b5ac6117ec307b965f65deeb47044395ff01ab793248a591/mypy-1.20.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f13b3e41bce9d257eded794c0f12878af3129d80aacd8a3ee0dee51f3a978651", size = 13705953, upload-time = "2026-03-31T16:48:55.69Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b7/54a720f565a87b893182a2a393370289ae7149e4715859e10e1c05e49154/mypy-1.20.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9804c3ad27f78e54e58b32e7cb532d128b43dbfb9f3f9f06262b821a0f6bd3f5", size = 14710363, upload-time = "2026-03-31T16:53:26.948Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2a/74810274848d061f8a8ea4ac23aaad43bd3d8c1882457999c2e568341c57/mypy-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:697f102c5c1d526bdd761a69f17c6070f9892eebcb94b1a5963d679288c09e78", size = 14947005, upload-time = "2026-03-31T16:50:17.591Z" }, + { url = "https://files.pythonhosted.org/packages/77/91/21b8ba75f958bcda75690951ce6fa6b7138b03471618959529d74b8544e2/mypy-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ecd63f75fdd30327e4ad8b5704bd6d91fc6c1b2e029f8ee14705e1207212489", size = 10880616, upload-time = "2026-03-31T16:52:19.986Z" }, + { url = "https://files.pythonhosted.org/packages/8a/15/3d8198ef97c1ca03aea010cce4f1d4f3bc5d9849e8c0140111ca2ead9fdd/mypy-1.20.0-cp312-cp312-win_arm64.whl", hash = "sha256:f194db59657c58593a3c47c6dfd7bad4ef4ac12dbc94d01b3a95521f78177e33", size = 9813091, upload-time = "2026-03-31T16:53:44.385Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a7/f64ea7bd592fa431cb597418b6dec4a47f7d0c36325fec7ac67bc8402b94/mypy-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b20c8b0fd5877abdf402e79a3af987053de07e6fb208c18df6659f708b535134", size = 14485344, upload-time = "2026-03-31T16:49:16.78Z" }, + { url = "https://files.pythonhosted.org/packages/bb/72/8927d84cfc90c6abea6e96663576e2e417589347eb538749a464c4c218a0/mypy-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:367e5c993ba34d5054d11937d0485ad6dfc60ba760fa326c01090fc256adf15c", size = 13327400, upload-time = "2026-03-31T16:53:08.02Z" }, + { url = "https://files.pythonhosted.org/packages/ab/4a/11ab99f9afa41aa350178d24a7d2da17043228ea10f6456523f64b5a6cf6/mypy-1.20.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f799d9db89fc00446f03281f84a221e50018fc40113a3ba9864b132895619ebe", size = 13706384, upload-time = "2026-03-31T16:52:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/42/79/694ca73979cfb3535ebfe78733844cd5aff2e63304f59bf90585110d975a/mypy-1.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555658c611099455b2da507582ea20d2043dfdfe7f5ad0add472b1c6238b433f", size = 14700378, upload-time = "2026-03-31T16:48:45.527Z" }, + { url = "https://files.pythonhosted.org/packages/84/24/a022ccab3a46e3d2cdf2e0e260648633640eb396c7e75d5a42818a8d3971/mypy-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:efe8d70949c3023698c3fca1e94527e7e790a361ab8116f90d11221421cd8726", size = 14932170, upload-time = "2026-03-31T16:49:36.038Z" }, + { url = "https://files.pythonhosted.org/packages/d8/9b/549228d88f574d04117e736f55958bd4908f980f9f5700a07aeb85df005b/mypy-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:f49590891d2c2f8a9de15614e32e459a794bcba84693c2394291a2038bbaaa69", size = 10888526, upload-time = "2026-03-31T16:50:59.827Z" }, + { url = "https://files.pythonhosted.org/packages/91/17/15095c0e54a8bc04d22d4ff06b2139d5f142c2e87520b4e39010c4862771/mypy-1.20.0-cp313-cp313-win_arm64.whl", hash = "sha256:76a70bf840495729be47510856b978f1b0ec7d08f257ca38c9d932720bf6b43e", size = 9816456, upload-time = "2026-03-31T16:49:59.537Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0e/6ca4a84cbed9e62384bc0b2974c90395ece5ed672393e553996501625fc5/mypy-1.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0f42dfaab7ec1baff3b383ad7af562ab0de573c5f6edb44b2dab016082b89948", size = 14483331, upload-time = "2026-03-31T16:52:57.999Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c5/5fe9d8a729dd9605064691816243ae6c49fde0bd28f6e5e17f6a24203c43/mypy-1.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:31b5dbb55293c1bd27c0fc813a0d2bb5ceef9d65ac5afa2e58f829dab7921fd5", size = 13342047, upload-time = "2026-03-31T16:54:21.555Z" }, + { url = "https://files.pythonhosted.org/packages/4c/33/e18bcfa338ca4e6b2771c85d4c5203e627d0c69d9de5c1a2cf2ba13320ba/mypy-1.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49d11c6f573a5a08f77fad13faff2139f6d0730ebed2cfa9b3d2702671dd7188", size = 13719585, upload-time = "2026-03-31T16:51:53.89Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8d/93491ff7b79419edc7eabf95cb3b3f7490e2e574b2855c7c7e7394ff933f/mypy-1.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d3243c406773185144527f83be0e0aefc7bf4601b0b2b956665608bf7c98a83", size = 14685075, upload-time = "2026-03-31T16:54:04.464Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9d/d924b38a4923f8d164bf2b4ec98bf13beaf6e10a5348b4b137eadae40a6e/mypy-1.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a79c1eba7ac4209f2d850f0edd0a2f8bba88cbfdfefe6fb76a19e9d4fe5e71a2", size = 14919141, upload-time = "2026-03-31T16:54:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/59/98/1da9977016678c0b99d43afe52ed00bb3c1a0c4c995d3e6acca1a6ebb9b4/mypy-1.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:00e047c74d3ec6e71a2eb88e9ea551a2edb90c21f993aefa9e0d2a898e0bb732", size = 11050925, upload-time = "2026-03-31T16:51:30.758Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e3/ba0b7a3143e49a9c4f5967dde6ea4bf8e0b10ecbbcca69af84027160ee89/mypy-1.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:931a7630bba591593dcf6e97224a21ff80fb357e7982628d25e3c618e7f598ef", size = 10001089, upload-time = "2026-03-31T16:49:43.632Z" }, + { url = "https://files.pythonhosted.org/packages/12/28/e617e67b3be9d213cda7277913269c874eb26472489f95d09d89765ce2d8/mypy-1.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:26c8b52627b6552f47ff11adb4e1509605f094e29815323e487fc0053ebe93d1", size = 15534710, upload-time = "2026-03-31T16:52:12.506Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/3b5f2d3e45dc7169b811adce8451679d9430399d03b168f9b0489f43adaa/mypy-1.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39362cdb4ba5f916e7976fccecaab1ba3a83e35f60fa68b64e9a70e221bb2436", size = 14393013, upload-time = "2026-03-31T16:54:41.186Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/edc8b0aa145cc09c1c74f7ce2858eead9329931dcbbb26e2ad40906daa4e/mypy-1.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34506397dbf40c15dc567635d18a21d33827e9ab29014fb83d292a8f4f8953b6", size = 15047240, upload-time = "2026-03-31T16:54:31.955Z" }, + { url = "https://files.pythonhosted.org/packages/42/37/a946bb416e37a57fa752b3100fd5ede0e28df94f92366d1716555d47c454/mypy-1.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555493c44a4f5a1b58d611a43333e71a9981c6dbe26270377b6f8174126a0526", size = 15858565, upload-time = "2026-03-31T16:53:36.997Z" }, + { url = "https://files.pythonhosted.org/packages/2f/99/7690b5b5b552db1bd4ff362e4c0eb3107b98d680835e65823fbe888c8b78/mypy-1.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2721f0ce49cb74a38f00c50da67cb7d36317b5eda38877a49614dc018e91c787", size = 16087874, upload-time = "2026-03-31T16:52:48.313Z" }, + { url = "https://files.pythonhosted.org/packages/aa/76/53e893a498138066acd28192b77495c9357e5a58cc4be753182846b43315/mypy-1.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:47781555a7aa5fedcc2d16bcd72e0dc83eb272c10dd657f9fb3f9cc08e2e6abb", size = 12572380, upload-time = "2026-03-31T16:49:52.454Z" }, + { url = "https://files.pythonhosted.org/packages/76/9c/6dbdae21f01b7aacddc2c0bbf3c5557aa547827fdf271770fe1e521e7093/mypy-1.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c70380fe5d64010f79fb863b9081c7004dd65225d2277333c219d93a10dad4dd", size = 10381174, upload-time = "2026-03-31T16:51:20.179Z" }, + { url = "https://files.pythonhosted.org/packages/21/66/4d734961ce167f0fd8380769b3b7c06dbdd6ff54c2190f3f2ecd22528158/mypy-1.20.0-py3-none-any.whl", hash = "sha256:a6e0641147cbfa7e4e94efdb95c2dab1aff8cfc159ded13e07f308ddccc8c48e", size = 2636365, upload-time = "2026-03-31T16:51:44.911Z" }, ] [[package]] @@ -1622,7 +1634,7 @@ all = [ { name = "chango", marker = "python_full_version >= '3.12'", specifier = "~=0.6.0" }, { name = "flaky", specifier = ">=3.8.1" }, { name = "furo", specifier = "==2025.12.19" }, - { name = "mypy", specifier = "==1.19.1" }, + { name = "mypy", specifier = "==1.20.0" }, { name = "prek" }, { name = "pydantic", marker = "python_full_version >= '3.14'", specifier = ">=2.12.0a1" }, { name = "pylint", specifier = "==4.0.5" }, @@ -1652,7 +1664,7 @@ docs = [ { name = "sphinxcontrib-mermaid", specifier = "==2.0.1" }, ] linting = [ - { name = "mypy", specifier = "==1.19.1" }, + { name = "mypy", specifier = "==1.20.0" }, { name = "prek" }, { name = "pylint", specifier = "==4.0.5" }, { name = "ruff", specifier = "==0.15.8" }, From 98f4dae84fc0774f50a17cae60cad3ae22be11e8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 06:09:21 +0000 Subject: [PATCH 08/39] Update codecov/codecov-action action to v5.5.4 (#5190) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/unit_tests.yml | 4 ++-- changes/unreleased/5190.5mL9K6trXT6Y4vLgqxy9jE.toml | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changes/unreleased/5190.5mL9K6trXT6Y4vLgqxy9jE.toml diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index df2449a2244..55fe104e4a5 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -87,14 +87,14 @@ jobs: .test_report_optionals_junit.xml - name: Submit coverage - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5.5.4 with: env_vars: OS,PYTHON name: ${{ matrix.os }}-${{ matrix.python-version }} fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} - name: Upload test results to Codecov - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5.5.4 if: ${{ !cancelled() }} with: files: .test_report_no_optionals_junit.xml,.test_report_optionals_junit.xml diff --git a/changes/unreleased/5190.5mL9K6trXT6Y4vLgqxy9jE.toml b/changes/unreleased/5190.5mL9K6trXT6Y4vLgqxy9jE.toml new file mode 100644 index 00000000000..f7f2aa88f99 --- /dev/null +++ b/changes/unreleased/5190.5mL9K6trXT6Y4vLgqxy9jE.toml @@ -0,0 +1,5 @@ +other = "Update codecov/codecov-action action to v5.5.4" +[[pull_requests]] +uid = "5190" +author_uids = ["renovate[bot]"] +closes_threads = [] From 87ecfee99ea767106c9f777ea8b24b1f0323afb7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 06:10:51 +0000 Subject: [PATCH 09/39] Update github/codeql-action action to v4.35.1 (#5194) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/gha_security.yml | 2 +- changes/unreleased/5194.bnCTjH5QVYuyFDp4iCwJoV.toml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/unreleased/5194.bnCTjH5QVYuyFDp4iCwJoV.toml diff --git a/.github/workflows/gha_security.yml b/.github/workflows/gha_security.yml index e74c6ab7823..a4c3bc149c3 100644 --- a/.github/workflows/gha_security.yml +++ b/.github/workflows/gha_security.yml @@ -27,7 +27,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 + uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: sarif_file: results.sarif category: zizmor diff --git a/changes/unreleased/5194.bnCTjH5QVYuyFDp4iCwJoV.toml b/changes/unreleased/5194.bnCTjH5QVYuyFDp4iCwJoV.toml new file mode 100644 index 00000000000..928e99daa28 --- /dev/null +++ b/changes/unreleased/5194.bnCTjH5QVYuyFDp4iCwJoV.toml @@ -0,0 +1,5 @@ +internal = "Update github/codeql-action action to v4.35.1" +[[pull_requests]] +uid = "5194" +author_uids = ["renovate[bot]"] +closes_threads = [] From 875f4ab4e3057f7986c017112b130895c9092256 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 5 Apr 2026 05:40:40 +0000 Subject: [PATCH 10/39] Update Ruff to v0.15.9 (#5198) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- .../5198.haofa4LoE6k2WQoAUSizM8.toml | 5 ++ pyproject.toml | 2 +- uv.lock | 46 +++++++++---------- 4 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 changes/unreleased/5198.haofa4LoE6k2WQoAUSizM8.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d203ca6257c..75acc825b60 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.15.8' + rev: 'v0.15.9' hooks: # Run the linter: - id: ruff-check diff --git a/changes/unreleased/5198.haofa4LoE6k2WQoAUSizM8.toml b/changes/unreleased/5198.haofa4LoE6k2WQoAUSizM8.toml new file mode 100644 index 00000000000..4d894b19706 --- /dev/null +++ b/changes/unreleased/5198.haofa4LoE6k2WQoAUSizM8.toml @@ -0,0 +1,5 @@ +internal = "Update Ruff to v0.15.9" +[[pull_requests]] +uid = "5198" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index e229b09b5bd..e13b2c51b64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,7 @@ docs = [ ] linting = [ "prek", - "ruff==0.15.8", + "ruff==0.15.9", "mypy==1.20.0", "pylint==4.0.5" ] diff --git a/uv.lock b/uv.lock index 457b29398e8..0da0d4a4834 100644 --- a/uv.lock +++ b/uv.lock @@ -1643,7 +1643,7 @@ all = [ { name = "pytest-cov" }, { name = "pytest-xdist", specifier = "==3.8.0" }, { name = "pytz" }, - { name = "ruff", specifier = "==0.15.8" }, + { name = "ruff", specifier = "==0.15.9" }, { name = "sphinx", marker = "python_full_version >= '3.12'", specifier = "==9.1.0" }, { name = "sphinx-build-compatibility", git = "https://github.com/readthedocs/sphinx-build-compatibility.git?rev=58aabc5f207c6c2421f23d3578adc0b14af57047" }, { name = "sphinx-copybutton", specifier = "==0.5.2" }, @@ -1667,7 +1667,7 @@ linting = [ { name = "mypy", specifier = "==1.20.0" }, { name = "prek" }, { name = "pylint", specifier = "==4.0.5" }, - { name = "ruff", specifier = "==0.15.8" }, + { name = "ruff", specifier = "==0.15.9" }, ] tests = [ { name = "beautifulsoup4" }, @@ -1793,27 +1793,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, - { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, - { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, - { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, - { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, - { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, - { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, - { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, - { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, - { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, - { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, - { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, - { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, +version = "0.15.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/97/e9f1ca355108ef7194e38c812ef40ba98c7208f47b13ad78d023caa583da/ruff-0.15.9.tar.gz", hash = "sha256:29cbb1255a9797903f6dde5ba0188c707907ff44a9006eb273b5a17bfa0739a2", size = 4617361, upload-time = "2026-04-02T18:17:20.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/1f/9cdfd0ac4b9d1e5a6cf09bedabdf0b56306ab5e333c85c87281273e7b041/ruff-0.15.9-py3-none-linux_armv6l.whl", hash = "sha256:6efbe303983441c51975c243e26dff328aca11f94b70992f35b093c2e71801e1", size = 10511206, upload-time = "2026-04-02T18:16:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f6/32bfe3e9c136b35f02e489778d94384118bb80fd92c6d92e7ccd97db12ce/ruff-0.15.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4965bac6ac9ea86772f4e23587746f0b7a395eccabb823eb8bfacc3fa06069f7", size = 10923307, upload-time = "2026-04-02T18:17:08.645Z" }, + { url = "https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf05aad70ca5b5a0a4b0e080df3a6b699803916d88f006efd1f5b46302daab8", size = 10316722, upload-time = "2026-04-02T18:16:44.206Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/690d75f3fd6278fe55fff7c9eb429c92d207e14b25d1cae4064a32677029/ruff-0.15.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439a342adb8725f32f92732e2bafb6d5246bd7a5021101166b223d312e8fc59", size = 10623674, upload-time = "2026-04-02T18:16:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ec/176f6987be248fc5404199255522f57af1b4a5a1b57727e942479fec98ad/ruff-0.15.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5e6faf9d97c8edc43877c3f406f47446fc48c40e1442d58cfcdaba2acea745", size = 10351516, upload-time = "2026-04-02T18:16:57.206Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fc/51cffbd2b3f240accc380171d51446a32aa2ea43a40d4a45ada67368fbd2/ruff-0.15.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b34a9766aeec27a222373d0b055722900fbc0582b24f39661aa96f3fe6ad901", size = 11150202, upload-time = "2026-04-02T18:17:06.452Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d4/25292a6dfc125f6b6528fe6af31f5e996e19bf73ca8e3ce6eb7fa5b95885/ruff-0.15.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89dd695bc72ae76ff484ae54b7e8b0f6b50f49046e198355e44ea656e521fef9", size = 11988891, upload-time = "2026-04-02T18:17:18.575Z" }, + { url = "https://files.pythonhosted.org/packages/13/e1/1eebcb885c10e19f969dcb93d8413dfee8172578709d7ee933640f5e7147/ruff-0.15.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce187224ef1de1bd225bc9a152ac7102a6171107f026e81f317e4257052916d5", size = 11480576, upload-time = "2026-04-02T18:16:52.986Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0c7c341f68adb01c488c3b7d4b49aa8ea97409eae6462d860a79cf55f431b6", size = 11254525, upload-time = "2026-04-02T18:17:02.041Z" }, + { url = "https://files.pythonhosted.org/packages/42/aa/4bb3af8e61acd9b1281db2ab77e8b2c3c5e5599bf2a29d4a942f1c62b8d6/ruff-0.15.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:55cc15eee27dc0eebdfcb0d185a6153420efbedc15eb1d38fe5e685657b0f840", size = 11204072, upload-time = "2026-04-02T18:17:13.581Z" }, + { url = "https://files.pythonhosted.org/packages/69/48/d550dc2aa6e423ea0bcc1d0ff0699325ffe8a811e2dba156bd80750b86dc/ruff-0.15.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6537f6eed5cda688c81073d46ffdfb962a5f29ecb6f7e770b2dc920598997ed", size = 10594998, upload-time = "2026-04-02T18:16:46.369Z" }, + { url = "https://files.pythonhosted.org/packages/63/47/321167e17f5344ed5ec6b0aa2cff64efef5f9e985af8f5622cfa6536043f/ruff-0.15.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6d3fcbca7388b066139c523bda744c822258ebdcfbba7d24410c3f454cc9af71", size = 10359769, upload-time = "2026-04-02T18:17:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/67/5e/074f00b9785d1d2c6f8c22a21e023d0c2c1817838cfca4c8243200a1fa87/ruff-0.15.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:058d8e99e1bfe79d8a0def0b481c56059ee6716214f7e425d8e737e412d69677", size = 10850236, upload-time = "2026-04-02T18:16:48.749Z" }, + { url = "https://files.pythonhosted.org/packages/76/37/804c4135a2a2caf042925d30d5f68181bdbd4461fd0d7739da28305df593/ruff-0.15.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8e1ddb11dbd61d5983fa2d7d6370ef3eb210951e443cace19594c01c72abab4c", size = 11358343, upload-time = "2026-04-02T18:16:55.068Z" }, + { url = "https://files.pythonhosted.org/packages/88/3d/1364fcde8656962782aa9ea93c92d98682b1ecec2f184e625a965ad3b4a6/ruff-0.15.9-py3-none-win32.whl", hash = "sha256:bde6ff36eaf72b700f32b7196088970bf8fdb2b917b7accd8c371bfc0fd573ec", size = 10583382, upload-time = "2026-04-02T18:17:04.261Z" }, + { url = "https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl", hash = "sha256:45a70921b80e1c10cf0b734ef09421f71b5aa11d27404edc89d7e8a69505e43d", size = 11744969, upload-time = "2026-04-02T18:16:59.611Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/76704c4f312257d6dbaae3c959add2a622f63fcca9d864659ce6d8d97d3d/ruff-0.15.9-py3-none-win_arm64.whl", hash = "sha256:0694e601c028fd97dc5c6ee244675bc241aeefced7ef80cd9c6935a871078f53", size = 11005870, upload-time = "2026-04-02T18:17:15.773Z" }, ] [[package]] From fcceb91b1a1d41821bc439f95a0fd403f1a70853 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 02:35:49 +0000 Subject: [PATCH 11/39] Update dependency cryptography to v46.0.7 [SECURITY] (#5203) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../5203.jxMZ7aSe7eDKXTvfEdZGzw.toml | 5 + uv.lock | 102 +++++++++--------- 2 files changed, 56 insertions(+), 51 deletions(-) create mode 100644 changes/unreleased/5203.jxMZ7aSe7eDKXTvfEdZGzw.toml diff --git a/changes/unreleased/5203.jxMZ7aSe7eDKXTvfEdZGzw.toml b/changes/unreleased/5203.jxMZ7aSe7eDKXTvfEdZGzw.toml new file mode 100644 index 00000000000..2534d84cd65 --- /dev/null +++ b/changes/unreleased/5203.jxMZ7aSe7eDKXTvfEdZGzw.toml @@ -0,0 +1,5 @@ +dependencies = "Update dependency cryptography to v46.0.7 [SECURITY]" +[[pull_requests]] +uid = "5203" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/uv.lock b/uv.lock index 0da0d4a4834..9cca45f15f3 100644 --- a/uv.lock +++ b/uv.lock @@ -495,62 +495,62 @@ toml = [ [[package]] name = "cryptography" -version = "46.0.6" +version = "46.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" }, - { url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" }, - { url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" }, - { url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" }, - { url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" }, - { url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" }, - { url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" }, - { url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" }, - { url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" }, - { url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" }, - { url = "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", size = 7117147, upload-time = "2026-03-25T23:33:48.249Z" }, - { url = "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", size = 4266221, upload-time = "2026-03-25T23:33:49.874Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", size = 4408952, upload-time = "2026-03-25T23:33:52.128Z" }, - { url = "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", size = 4270141, upload-time = "2026-03-25T23:33:54.11Z" }, - { url = "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", size = 4904178, upload-time = "2026-03-25T23:33:55.725Z" }, - { url = "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", size = 4441812, upload-time = "2026-03-25T23:33:57.364Z" }, - { url = "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", size = 3963923, upload-time = "2026-03-25T23:33:59.361Z" }, - { url = "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", size = 4269695, upload-time = "2026-03-25T23:34:00.909Z" }, - { url = "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", size = 4869785, upload-time = "2026-03-25T23:34:02.796Z" }, - { url = "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", size = 4441404, upload-time = "2026-03-25T23:34:04.35Z" }, - { url = "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", size = 4397549, upload-time = "2026-03-25T23:34:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", size = 4651874, upload-time = "2026-03-25T23:34:07.916Z" }, - { url = "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", size = 3001511, upload-time = "2026-03-25T23:34:09.892Z" }, - { url = "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", size = 3471692, upload-time = "2026-03-25T23:34:11.613Z" }, - { url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" }, - { url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" }, - { url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" }, - { url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" }, - { url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" }, - { url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" }, - { url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" }, - { url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" }, - { url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" }, - { url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" }, - { url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" }, - { url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" }, - { url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" }, - { url = "https://files.pythonhosted.org/packages/2e/84/7ccff00ced5bac74b775ce0beb7d1be4e8637536b522b5df9b73ada42da2/cryptography-46.0.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:2ea0f37e9a9cf0df2952893ad145fd9627d326a59daec9b0802480fa3bcd2ead", size = 3475444, upload-time = "2026-03-25T23:34:38.944Z" }, - { url = "https://files.pythonhosted.org/packages/bc/1f/4c926f50df7749f000f20eede0c896769509895e2648db5da0ed55db711d/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a3e84d5ec9ba01f8fd03802b2147ba77f0c8f2617b2aff254cedd551844209c8", size = 4218227, upload-time = "2026-03-25T23:34:40.871Z" }, - { url = "https://files.pythonhosted.org/packages/c6/65/707be3ffbd5f786028665c3223e86e11c4cda86023adbc56bd72b1b6bab5/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:12f0fa16cc247b13c43d56d7b35287ff1569b5b1f4c5e87e92cc4fcc00cd10c0", size = 4381399, upload-time = "2026-03-25T23:34:42.609Z" }, - { url = "https://files.pythonhosted.org/packages/f3/6d/73557ed0ef7d73d04d9aba745d2c8e95218213687ee5e76b7d236a5030fc/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:50575a76e2951fe7dbd1f56d181f8c5ceeeb075e9ff88e7ad997d2f42af06e7b", size = 4217595, upload-time = "2026-03-25T23:34:44.205Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c5/e1594c4eec66a567c3ac4400008108a415808be2ce13dcb9a9045c92f1a0/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:90e5f0a7b3be5f40c3a0a0eafb32c681d8d2c181fc2a1bdabe9b3f611d9f6b1a", size = 4380912, upload-time = "2026-03-25T23:34:46.328Z" }, - { url = "https://files.pythonhosted.org/packages/1a/89/843b53614b47f97fe1abc13f9a86efa5ec9e275292c457af1d4a60dc80e0/cryptography-46.0.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6728c49e3b2c180ef26f8e9f0a883a2c585638db64cf265b49c9ba10652d430e", size = 3409955, upload-time = "2026-03-25T23:34:48.465Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" }, + { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, + { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, + { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, + { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, + { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" }, + { url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, + { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, + { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, + { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, + { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, + { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, + { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, + { url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" }, + { url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, + { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" }, + { url = "https://files.pythonhosted.org/packages/63/0c/dca8abb64e7ca4f6b2978769f6fea5ad06686a190cec381f0a796fdcaaba/cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f", size = 3476879, upload-time = "2026-04-08T01:57:38.664Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ea/075aac6a84b7c271578d81a2f9968acb6e273002408729f2ddff517fed4a/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15", size = 4219700, upload-time = "2026-04-08T01:57:40.625Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7b/1c55db7242b5e5612b29fc7a630e91ee7a6e3c8e7bf5406d22e206875fbd/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455", size = 4385982, upload-time = "2026-04-08T01:57:42.725Z" }, + { url = "https://files.pythonhosted.org/packages/cb/da/9870eec4b69c63ef5925bf7d8342b7e13bc2ee3d47791461c4e49ca212f4/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65", size = 4219115, upload-time = "2026-04-08T01:57:44.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/72/05aa5832b82dd341969e9a734d1812a6aadb088d9eb6f0430fc337cc5a8f/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968", size = 4385479, upload-time = "2026-04-08T01:57:46.86Z" }, + { url = "https://files.pythonhosted.org/packages/20/2a/1b016902351a523aa2bd446b50a5bc1175d7a7d1cf90fe2ef904f9b84ebc/cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4", size = 3412829, upload-time = "2026-04-08T01:57:48.874Z" }, ] [[package]] From d9dbd9481fa6fb7319e2824dfb44fa8c31e02078 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Apr 2026 00:40:39 +0000 Subject: [PATCH 12/39] Update Ruff to v0.15.10 (#5205) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- .../5205.KBqhJA9kouNAJmQYNwZHT2.toml | 5 ++ pyproject.toml | 2 +- uv.lock | 46 +++++++++---------- 4 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 changes/unreleased/5205.KBqhJA9kouNAJmQYNwZHT2.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75acc825b60..9e4f3f1ec1b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.15.9' + rev: 'v0.15.10' hooks: # Run the linter: - id: ruff-check diff --git a/changes/unreleased/5205.KBqhJA9kouNAJmQYNwZHT2.toml b/changes/unreleased/5205.KBqhJA9kouNAJmQYNwZHT2.toml new file mode 100644 index 00000000000..544c91e6df5 --- /dev/null +++ b/changes/unreleased/5205.KBqhJA9kouNAJmQYNwZHT2.toml @@ -0,0 +1,5 @@ +internal = "Update Ruff to v0.15.10" +[[pull_requests]] +uid = "5205" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index e13b2c51b64..28db45a148f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,7 @@ docs = [ ] linting = [ "prek", - "ruff==0.15.9", + "ruff==0.15.10", "mypy==1.20.0", "pylint==4.0.5" ] diff --git a/uv.lock b/uv.lock index 9cca45f15f3..08ecd0a0299 100644 --- a/uv.lock +++ b/uv.lock @@ -1643,7 +1643,7 @@ all = [ { name = "pytest-cov" }, { name = "pytest-xdist", specifier = "==3.8.0" }, { name = "pytz" }, - { name = "ruff", specifier = "==0.15.9" }, + { name = "ruff", specifier = "==0.15.10" }, { name = "sphinx", marker = "python_full_version >= '3.12'", specifier = "==9.1.0" }, { name = "sphinx-build-compatibility", git = "https://github.com/readthedocs/sphinx-build-compatibility.git?rev=58aabc5f207c6c2421f23d3578adc0b14af57047" }, { name = "sphinx-copybutton", specifier = "==0.5.2" }, @@ -1667,7 +1667,7 @@ linting = [ { name = "mypy", specifier = "==1.20.0" }, { name = "prek" }, { name = "pylint", specifier = "==4.0.5" }, - { name = "ruff", specifier = "==0.15.9" }, + { name = "ruff", specifier = "==0.15.10" }, ] tests = [ { name = "beautifulsoup4" }, @@ -1793,27 +1793,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/97/e9f1ca355108ef7194e38c812ef40ba98c7208f47b13ad78d023caa583da/ruff-0.15.9.tar.gz", hash = "sha256:29cbb1255a9797903f6dde5ba0188c707907ff44a9006eb273b5a17bfa0739a2", size = 4617361, upload-time = "2026-04-02T18:17:20.829Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/1f/9cdfd0ac4b9d1e5a6cf09bedabdf0b56306ab5e333c85c87281273e7b041/ruff-0.15.9-py3-none-linux_armv6l.whl", hash = "sha256:6efbe303983441c51975c243e26dff328aca11f94b70992f35b093c2e71801e1", size = 10511206, upload-time = "2026-04-02T18:16:41.574Z" }, - { url = "https://files.pythonhosted.org/packages/3d/f6/32bfe3e9c136b35f02e489778d94384118bb80fd92c6d92e7ccd97db12ce/ruff-0.15.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4965bac6ac9ea86772f4e23587746f0b7a395eccabb823eb8bfacc3fa06069f7", size = 10923307, upload-time = "2026-04-02T18:17:08.645Z" }, - { url = "https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf05aad70ca5b5a0a4b0e080df3a6b699803916d88f006efd1f5b46302daab8", size = 10316722, upload-time = "2026-04-02T18:16:44.206Z" }, - { url = "https://files.pythonhosted.org/packages/48/11/690d75f3fd6278fe55fff7c9eb429c92d207e14b25d1cae4064a32677029/ruff-0.15.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439a342adb8725f32f92732e2bafb6d5246bd7a5021101166b223d312e8fc59", size = 10623674, upload-time = "2026-04-02T18:16:50.951Z" }, - { url = "https://files.pythonhosted.org/packages/bd/ec/176f6987be248fc5404199255522f57af1b4a5a1b57727e942479fec98ad/ruff-0.15.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5e6faf9d97c8edc43877c3f406f47446fc48c40e1442d58cfcdaba2acea745", size = 10351516, upload-time = "2026-04-02T18:16:57.206Z" }, - { url = "https://files.pythonhosted.org/packages/b2/fc/51cffbd2b3f240accc380171d51446a32aa2ea43a40d4a45ada67368fbd2/ruff-0.15.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b34a9766aeec27a222373d0b055722900fbc0582b24f39661aa96f3fe6ad901", size = 11150202, upload-time = "2026-04-02T18:17:06.452Z" }, - { url = "https://files.pythonhosted.org/packages/d6/d4/25292a6dfc125f6b6528fe6af31f5e996e19bf73ca8e3ce6eb7fa5b95885/ruff-0.15.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89dd695bc72ae76ff484ae54b7e8b0f6b50f49046e198355e44ea656e521fef9", size = 11988891, upload-time = "2026-04-02T18:17:18.575Z" }, - { url = "https://files.pythonhosted.org/packages/13/e1/1eebcb885c10e19f969dcb93d8413dfee8172578709d7ee933640f5e7147/ruff-0.15.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce187224ef1de1bd225bc9a152ac7102a6171107f026e81f317e4257052916d5", size = 11480576, upload-time = "2026-04-02T18:16:52.986Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0c7c341f68adb01c488c3b7d4b49aa8ea97409eae6462d860a79cf55f431b6", size = 11254525, upload-time = "2026-04-02T18:17:02.041Z" }, - { url = "https://files.pythonhosted.org/packages/42/aa/4bb3af8e61acd9b1281db2ab77e8b2c3c5e5599bf2a29d4a942f1c62b8d6/ruff-0.15.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:55cc15eee27dc0eebdfcb0d185a6153420efbedc15eb1d38fe5e685657b0f840", size = 11204072, upload-time = "2026-04-02T18:17:13.581Z" }, - { url = "https://files.pythonhosted.org/packages/69/48/d550dc2aa6e423ea0bcc1d0ff0699325ffe8a811e2dba156bd80750b86dc/ruff-0.15.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6537f6eed5cda688c81073d46ffdfb962a5f29ecb6f7e770b2dc920598997ed", size = 10594998, upload-time = "2026-04-02T18:16:46.369Z" }, - { url = "https://files.pythonhosted.org/packages/63/47/321167e17f5344ed5ec6b0aa2cff64efef5f9e985af8f5622cfa6536043f/ruff-0.15.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6d3fcbca7388b066139c523bda744c822258ebdcfbba7d24410c3f454cc9af71", size = 10359769, upload-time = "2026-04-02T18:17:10.994Z" }, - { url = "https://files.pythonhosted.org/packages/67/5e/074f00b9785d1d2c6f8c22a21e023d0c2c1817838cfca4c8243200a1fa87/ruff-0.15.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:058d8e99e1bfe79d8a0def0b481c56059ee6716214f7e425d8e737e412d69677", size = 10850236, upload-time = "2026-04-02T18:16:48.749Z" }, - { url = "https://files.pythonhosted.org/packages/76/37/804c4135a2a2caf042925d30d5f68181bdbd4461fd0d7739da28305df593/ruff-0.15.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8e1ddb11dbd61d5983fa2d7d6370ef3eb210951e443cace19594c01c72abab4c", size = 11358343, upload-time = "2026-04-02T18:16:55.068Z" }, - { url = "https://files.pythonhosted.org/packages/88/3d/1364fcde8656962782aa9ea93c92d98682b1ecec2f184e625a965ad3b4a6/ruff-0.15.9-py3-none-win32.whl", hash = "sha256:bde6ff36eaf72b700f32b7196088970bf8fdb2b917b7accd8c371bfc0fd573ec", size = 10583382, upload-time = "2026-04-02T18:17:04.261Z" }, - { url = "https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl", hash = "sha256:45a70921b80e1c10cf0b734ef09421f71b5aa11d27404edc89d7e8a69505e43d", size = 11744969, upload-time = "2026-04-02T18:16:59.611Z" }, - { url = "https://files.pythonhosted.org/packages/03/36/76704c4f312257d6dbaae3c959add2a622f63fcca9d864659ce6d8d97d3d/ruff-0.15.9-py3-none-win_arm64.whl", hash = "sha256:0694e601c028fd97dc5c6ee244675bc241aeefced7ef80cd9c6935a871078f53", size = 11005870, upload-time = "2026-04-02T18:17:15.773Z" }, +version = "0.15.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/aa3f7d59a10ef6b14fe3431706f854dbf03c5976be614a9796d36326810c/ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e", size = 4631728, upload-time = "2026-04-09T14:06:09.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/00/a1c2fdc9939b2c03691edbda290afcd297f1f389196172826b03d6b6a595/ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f", size = 10563362, upload-time = "2026-04-09T14:06:21.189Z" }, + { url = "https://files.pythonhosted.org/packages/5c/15/006990029aea0bebe9d33c73c3e28c80c391ebdba408d1b08496f00d422d/ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e", size = 10951122, upload-time = "2026-04-09T14:06:02.236Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1", size = 10314005, upload-time = "2026-04-09T14:06:00.026Z" }, + { url = "https://files.pythonhosted.org/packages/da/73/c209138a5c98c0d321266372fc4e33ad43d506d7e5dd817dd89b60a8548f/ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e", size = 10643450, upload-time = "2026-04-09T14:05:42.137Z" }, + { url = "https://files.pythonhosted.org/packages/ec/76/0deec355d8ec10709653635b1f90856735302cb8e149acfdf6f82a5feb70/ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1", size = 10379597, upload-time = "2026-04-09T14:05:49.984Z" }, + { url = "https://files.pythonhosted.org/packages/dc/be/86bba8fc8798c081e28a4b3bb6d143ccad3fd5f6f024f02002b8f08a9fa3/ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef", size = 11146645, upload-time = "2026-04-09T14:06:12.246Z" }, + { url = "https://files.pythonhosted.org/packages/a8/89/140025e65911b281c57be1d385ba1d932c2366ca88ae6663685aed8d4881/ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158", size = 12030289, upload-time = "2026-04-09T14:06:04.776Z" }, + { url = "https://files.pythonhosted.org/packages/88/de/ddacca9545a5e01332567db01d44bd8cf725f2db3b3d61a80550b48308ea/ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0", size = 11496266, upload-time = "2026-04-09T14:05:55.485Z" }, + { url = "https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609", size = 11256418, upload-time = "2026-04-09T14:05:57.69Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/55de0d35aacf6cd50b6ee91ee0f291672080021896543776f4170fc5c454/ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f", size = 11288416, upload-time = "2026-04-09T14:05:44.695Z" }, + { url = "https://files.pythonhosted.org/packages/68/cf/9438b1a27426ec46a80e0a718093c7f958ef72f43eb3111862949ead3cc1/ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151", size = 10621053, upload-time = "2026-04-09T14:05:52.782Z" }, + { url = "https://files.pythonhosted.org/packages/4c/50/e29be6e2c135e9cd4cb15fbade49d6a2717e009dff3766dd080fcb82e251/ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8", size = 10378302, upload-time = "2026-04-09T14:06:14.361Z" }, + { url = "https://files.pythonhosted.org/packages/18/2f/e0b36a6f99c51bb89f3a30239bc7bf97e87a37ae80aa2d6542d6e5150364/ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07", size = 10850074, upload-time = "2026-04-09T14:06:16.581Z" }, + { url = "https://files.pythonhosted.org/packages/11/08/874da392558ce087a0f9b709dc6ec0d60cbc694c1c772dab8d5f31efe8cb/ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48", size = 11358051, upload-time = "2026-04-09T14:06:18.948Z" }, + { url = "https://files.pythonhosted.org/packages/e4/46/602938f030adfa043e67112b73821024dc79f3ab4df5474c25fa4c1d2d14/ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5", size = 10588964, upload-time = "2026-04-09T14:06:07.14Z" }, + { url = "https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed", size = 11745044, upload-time = "2026-04-09T14:05:39.473Z" }, + { url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" }, ] [[package]] From 170dca0ac4975ee0ca5267f6d6dbeb1ab3eab1e5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 04:43:49 +0000 Subject: [PATCH 13/39] Update dependency pytest to v9.0.3 [SECURITY] (#5206) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- changes/unreleased/5206.nXEjjZXrYZiWroBfBeQR3N.toml | 5 +++++ pyproject.toml | 2 +- uv.lock | 10 +++++----- 3 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 changes/unreleased/5206.nXEjjZXrYZiWroBfBeQR3N.toml diff --git a/changes/unreleased/5206.nXEjjZXrYZiWroBfBeQR3N.toml b/changes/unreleased/5206.nXEjjZXrYZiWroBfBeQR3N.toml new file mode 100644 index 00000000000..2cbdb4d446d --- /dev/null +++ b/changes/unreleased/5206.nXEjjZXrYZiWroBfBeQR3N.toml @@ -0,0 +1,5 @@ +internal = "Update dependency pytest to v9.0.3 [SECURITY]" +[[pull_requests]] +uid = "5206" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index 28db45a148f..b35c9769b9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ tests = [ # required for building the wheels for releases "build", # For the test suite - "pytest==9.0.2", + "pytest==9.0.3", # needed because pytest doesn't come with native support for coroutines as tests "pytest-asyncio==0.21.2", # xdist runs tests in parallel diff --git a/uv.lock b/uv.lock index 08ecd0a0299..320430bb0ae 100644 --- a/uv.lock +++ b/uv.lock @@ -1435,7 +1435,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.0.2" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1446,9 +1446,9 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] @@ -1638,7 +1638,7 @@ all = [ { name = "prek" }, { name = "pydantic", marker = "python_full_version >= '3.14'", specifier = ">=2.12.0a1" }, { name = "pylint", specifier = "==4.0.5" }, - { name = "pytest", specifier = "==9.0.2" }, + { name = "pytest", specifier = "==9.0.3" }, { name = "pytest-asyncio", specifier = "==0.21.2" }, { name = "pytest-cov" }, { name = "pytest-xdist", specifier = "==3.8.0" }, @@ -1673,7 +1673,7 @@ tests = [ { name = "beautifulsoup4" }, { name = "build" }, { name = "flaky", specifier = ">=3.8.1" }, - { name = "pytest", specifier = "==9.0.2" }, + { name = "pytest", specifier = "==9.0.3" }, { name = "pytest-asyncio", specifier = "==0.21.2" }, { name = "pytest-cov" }, { name = "pytest-xdist", specifier = "==3.8.0" }, From d228b339167f8c5b660717c399ff8bdde730a203 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:06:25 +0000 Subject: [PATCH 14/39] Update Mypy to v1.20.1 (#5199) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: poolitzer --- .pre-commit-config.yaml | 2 +- .../5199.4qywU9Faav8YhWivBBykyJ.toml | 5 + pyproject.toml | 2 +- src/telegram/_files/sticker.py | 2 +- src/telegram/ext/_utils/trackingdict.py | 2 +- uv.lock | 96 +++++++++---------- 6 files changed, 57 insertions(+), 52 deletions(-) create mode 100644 changes/unreleased/5199.4qywU9Faav8YhWivBBykyJ.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e4f3f1ec1b..e510d52b734 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: - . # this basically does `pip install -e .` priority: 10 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.19.1 + rev: v1.20.1 hooks: - id: mypy name: mypy-ptb diff --git a/changes/unreleased/5199.4qywU9Faav8YhWivBBykyJ.toml b/changes/unreleased/5199.4qywU9Faav8YhWivBBykyJ.toml new file mode 100644 index 00000000000..dc85c2b2c54 --- /dev/null +++ b/changes/unreleased/5199.4qywU9Faav8YhWivBBykyJ.toml @@ -0,0 +1,5 @@ +internal = "Update Mypy to v1.20.1" +[[pull_requests]] +uid = "5199" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index b35c9769b9b..e0890d3dbd5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,7 +134,7 @@ docs = [ linting = [ "prek", "ruff==0.15.10", - "mypy==1.20.0", + "mypy==1.20.1", "pylint==4.0.5" ] all = [{ include-group = "tests" }, { include-group = "docs" }, { include-group = "linting"}] diff --git a/src/telegram/_files/sticker.py b/src/telegram/_files/sticker.py index e7854b53ab9..9ab6bd09aa5 100644 --- a/src/telegram/_files/sticker.py +++ b/src/telegram/_files/sticker.py @@ -311,7 +311,7 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "StickerSet": data["thumbnail"] = de_json_optional(data.get("thumbnail"), PhotoSize, bot) data["stickers"] = de_list_optional(data.get("stickers"), Sticker, bot) - api_kwargs = {} + api_kwargs: JSONDict = {} # These are deprecated fields that TG still returns for backwards compatibility # Let's filter them out to speed up the de-json process for deprecated_field in ("contains_masks", "thumb", "is_animated", "is_video"): diff --git a/src/telegram/ext/_utils/trackingdict.py b/src/telegram/ext/_utils/trackingdict.py index bf7af4d5dab..04685efca23 100644 --- a/src/telegram/ext/_utils/trackingdict.py +++ b/src/telegram/ext/_utils/trackingdict.py @@ -109,7 +109,7 @@ def pop( # type: ignore[override] self.__track_write(key) if isinstance(default, DefaultValue): return super().pop(key) - return super().pop(key, default=default) + return super().pop(key, default) def clear(self) -> None: self.__track_write(set(super().keys())) diff --git a/uv.lock b/uv.lock index 320430bb0ae..a30a63bf1fa 100644 --- a/uv.lock +++ b/uv.lock @@ -975,7 +975,7 @@ wheels = [ [[package]] name = "mypy" -version = "1.20.0" +version = "1.20.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, @@ -984,51 +984,51 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/5c/b0089fe7fef0a994ae5ee07029ced0526082c6cfaaa4c10d40a10e33b097/mypy-1.20.0.tar.gz", hash = "sha256:eb96c84efcc33f0b5e0e04beacf00129dd963b67226b01c00b9dfc8affb464c3", size = 3815028, upload-time = "2026-03-31T16:55:14.959Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/a2/a965c8c3fcd4fa8b84ba0d46606181b0d0a1d50f274c67877f3e9ed4882c/mypy-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d99f515f95fd03a90875fdb2cca12ff074aa04490db4d190905851bdf8a549a8", size = 14430138, upload-time = "2026-03-31T16:52:37.843Z" }, - { url = "https://files.pythonhosted.org/packages/53/6e/043477501deeb8eabbab7f1a2f6cac62cfb631806dc1d6862a04a7f5011b/mypy-1.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bd0212976dc57a5bfeede7c219e7cd66568a32c05c9129686dd487c059c1b88a", size = 13311282, upload-time = "2026-03-31T16:55:11.021Z" }, - { url = "https://files.pythonhosted.org/packages/65/aa/bd89b247b83128197a214f29f0632ff3c14f54d4cd70d144d157bd7d7d6e/mypy-1.20.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8426d4d75d68714abc17a4292d922f6ba2cfb984b72c2278c437f6dae797865", size = 13750889, upload-time = "2026-03-31T16:52:02.909Z" }, - { url = "https://files.pythonhosted.org/packages/fa/9d/2860be7355c45247ccc0be1501c91176318964c2a137bd4743f58ce6200e/mypy-1.20.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02cca0761c75b42a20a2757ae58713276605eb29a08dd8a6e092aa347c4115ca", size = 14619788, upload-time = "2026-03-31T16:50:48.928Z" }, - { url = "https://files.pythonhosted.org/packages/75/7f/3ef3e360c91f3de120f205c8ce405e9caf9fc52ef14b65d37073e322c114/mypy-1.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3a49064504be59e59da664c5e149edc1f26c67c4f8e8456f6ba6aba55033018", size = 14918849, upload-time = "2026-03-31T16:51:10.478Z" }, - { url = "https://files.pythonhosted.org/packages/ae/72/af970dfe167ef788df7c5e6109d2ed0229f164432ce828bc9741a4250e64/mypy-1.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:ebea00201737ad4391142808ed16e875add5c17f676e0912b387739f84991e13", size = 10822007, upload-time = "2026-03-31T16:50:25.268Z" }, - { url = "https://files.pythonhosted.org/packages/93/94/ba9065c2ebe5421619aff684b793d953e438a8bfe31a320dd6d1e0706e81/mypy-1.20.0-cp310-cp310-win_arm64.whl", hash = "sha256:e80cf77847d0d3e6e3111b7b25db32a7f8762fd4b9a3a72ce53fe16a2863b281", size = 9756158, upload-time = "2026-03-31T16:48:36.213Z" }, - { url = "https://files.pythonhosted.org/packages/6e/1c/74cb1d9993236910286865679d1c616b136b2eae468493aa939431eda410/mypy-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4525e7010b1b38334516181c5b81e16180b8e149e6684cee5a727c78186b4e3b", size = 14343972, upload-time = "2026-03-31T16:49:04.887Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/01399515eca280386e308cf57901e68d3a52af18691941b773b3380c1df8/mypy-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a17c5d0bdcca61ce24a35beb828a2d0d323d3fcf387d7512206888c900193367", size = 13225007, upload-time = "2026-03-31T16:50:08.151Z" }, - { url = "https://files.pythonhosted.org/packages/56/ac/b4ba5094fb2d7fe9d2037cd8d18bbe02bcf68fd22ab9ff013f55e57ba095/mypy-1.20.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75ff57defcd0f1d6e006d721ccdec6c88d4f6a7816eb92f1c4890d979d9ee62", size = 13663752, upload-time = "2026-03-31T16:49:26.064Z" }, - { url = "https://files.pythonhosted.org/packages/db/a7/460678d3cf7da252d2288dad0c602294b6ec22a91932ec368cc11e44bb6e/mypy-1.20.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b503ab55a836136b619b5fc21c8803d810c5b87551af8600b72eecafb0059cb0", size = 14532265, upload-time = "2026-03-31T16:53:55.077Z" }, - { url = "https://files.pythonhosted.org/packages/a3/3e/051cca8166cf0438ae3ea80e0e7c030d7a8ab98dffc93f80a1aa3f23c1a2/mypy-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1973868d2adbb4584a3835780b27436f06d1dc606af5be09f187aaa25be1070f", size = 14768476, upload-time = "2026-03-31T16:50:34.587Z" }, - { url = "https://files.pythonhosted.org/packages/be/66/8e02ec184f852ed5c4abb805583305db475930854e09964b55e107cdcbc4/mypy-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:2fcedb16d456106e545b2bfd7ef9d24e70b38ec252d2a629823a4d07ebcdb69e", size = 10818226, upload-time = "2026-03-31T16:53:15.624Z" }, - { url = "https://files.pythonhosted.org/packages/13/4b/383ad1924b28f41e4879a74151e7a5451123330d45652da359f9183bcd45/mypy-1.20.0-cp311-cp311-win_arm64.whl", hash = "sha256:379edf079ce44ac8d2805bcf9b3dd7340d4f97aad3a5e0ebabbf9d125b84b442", size = 9750091, upload-time = "2026-03-31T16:54:12.162Z" }, - { url = "https://files.pythonhosted.org/packages/be/dd/3afa29b58c2e57c79116ed55d700721c3c3b15955e2b6251dd165d377c0e/mypy-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:002b613ae19f4ac7d18b7e168ffe1cb9013b37c57f7411984abbd3b817b0a214", size = 14509525, upload-time = "2026-03-31T16:55:01.824Z" }, - { url = "https://files.pythonhosted.org/packages/54/eb/227b516ab8cad9f2a13c5e7a98d28cd6aa75e9c83e82776ae6c1c4c046c7/mypy-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9336b5e6712f4adaf5afc3203a99a40b379049104349d747eb3e5a3aa23ac2e", size = 13326469, upload-time = "2026-03-31T16:51:41.23Z" }, - { url = "https://files.pythonhosted.org/packages/57/d4/1ddb799860c1b5ac6117ec307b965f65deeb47044395ff01ab793248a591/mypy-1.20.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f13b3e41bce9d257eded794c0f12878af3129d80aacd8a3ee0dee51f3a978651", size = 13705953, upload-time = "2026-03-31T16:48:55.69Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b7/54a720f565a87b893182a2a393370289ae7149e4715859e10e1c05e49154/mypy-1.20.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9804c3ad27f78e54e58b32e7cb532d128b43dbfb9f3f9f06262b821a0f6bd3f5", size = 14710363, upload-time = "2026-03-31T16:53:26.948Z" }, - { url = "https://files.pythonhosted.org/packages/b2/2a/74810274848d061f8a8ea4ac23aaad43bd3d8c1882457999c2e568341c57/mypy-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:697f102c5c1d526bdd761a69f17c6070f9892eebcb94b1a5963d679288c09e78", size = 14947005, upload-time = "2026-03-31T16:50:17.591Z" }, - { url = "https://files.pythonhosted.org/packages/77/91/21b8ba75f958bcda75690951ce6fa6b7138b03471618959529d74b8544e2/mypy-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ecd63f75fdd30327e4ad8b5704bd6d91fc6c1b2e029f8ee14705e1207212489", size = 10880616, upload-time = "2026-03-31T16:52:19.986Z" }, - { url = "https://files.pythonhosted.org/packages/8a/15/3d8198ef97c1ca03aea010cce4f1d4f3bc5d9849e8c0140111ca2ead9fdd/mypy-1.20.0-cp312-cp312-win_arm64.whl", hash = "sha256:f194db59657c58593a3c47c6dfd7bad4ef4ac12dbc94d01b3a95521f78177e33", size = 9813091, upload-time = "2026-03-31T16:53:44.385Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a7/f64ea7bd592fa431cb597418b6dec4a47f7d0c36325fec7ac67bc8402b94/mypy-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b20c8b0fd5877abdf402e79a3af987053de07e6fb208c18df6659f708b535134", size = 14485344, upload-time = "2026-03-31T16:49:16.78Z" }, - { url = "https://files.pythonhosted.org/packages/bb/72/8927d84cfc90c6abea6e96663576e2e417589347eb538749a464c4c218a0/mypy-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:367e5c993ba34d5054d11937d0485ad6dfc60ba760fa326c01090fc256adf15c", size = 13327400, upload-time = "2026-03-31T16:53:08.02Z" }, - { url = "https://files.pythonhosted.org/packages/ab/4a/11ab99f9afa41aa350178d24a7d2da17043228ea10f6456523f64b5a6cf6/mypy-1.20.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f799d9db89fc00446f03281f84a221e50018fc40113a3ba9864b132895619ebe", size = 13706384, upload-time = "2026-03-31T16:52:28.577Z" }, - { url = "https://files.pythonhosted.org/packages/42/79/694ca73979cfb3535ebfe78733844cd5aff2e63304f59bf90585110d975a/mypy-1.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555658c611099455b2da507582ea20d2043dfdfe7f5ad0add472b1c6238b433f", size = 14700378, upload-time = "2026-03-31T16:48:45.527Z" }, - { url = "https://files.pythonhosted.org/packages/84/24/a022ccab3a46e3d2cdf2e0e260648633640eb396c7e75d5a42818a8d3971/mypy-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:efe8d70949c3023698c3fca1e94527e7e790a361ab8116f90d11221421cd8726", size = 14932170, upload-time = "2026-03-31T16:49:36.038Z" }, - { url = "https://files.pythonhosted.org/packages/d8/9b/549228d88f574d04117e736f55958bd4908f980f9f5700a07aeb85df005b/mypy-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:f49590891d2c2f8a9de15614e32e459a794bcba84693c2394291a2038bbaaa69", size = 10888526, upload-time = "2026-03-31T16:50:59.827Z" }, - { url = "https://files.pythonhosted.org/packages/91/17/15095c0e54a8bc04d22d4ff06b2139d5f142c2e87520b4e39010c4862771/mypy-1.20.0-cp313-cp313-win_arm64.whl", hash = "sha256:76a70bf840495729be47510856b978f1b0ec7d08f257ca38c9d932720bf6b43e", size = 9816456, upload-time = "2026-03-31T16:49:59.537Z" }, - { url = "https://files.pythonhosted.org/packages/4e/0e/6ca4a84cbed9e62384bc0b2974c90395ece5ed672393e553996501625fc5/mypy-1.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0f42dfaab7ec1baff3b383ad7af562ab0de573c5f6edb44b2dab016082b89948", size = 14483331, upload-time = "2026-03-31T16:52:57.999Z" }, - { url = "https://files.pythonhosted.org/packages/7d/c5/5fe9d8a729dd9605064691816243ae6c49fde0bd28f6e5e17f6a24203c43/mypy-1.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:31b5dbb55293c1bd27c0fc813a0d2bb5ceef9d65ac5afa2e58f829dab7921fd5", size = 13342047, upload-time = "2026-03-31T16:54:21.555Z" }, - { url = "https://files.pythonhosted.org/packages/4c/33/e18bcfa338ca4e6b2771c85d4c5203e627d0c69d9de5c1a2cf2ba13320ba/mypy-1.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49d11c6f573a5a08f77fad13faff2139f6d0730ebed2cfa9b3d2702671dd7188", size = 13719585, upload-time = "2026-03-31T16:51:53.89Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8d/93491ff7b79419edc7eabf95cb3b3f7490e2e574b2855c7c7e7394ff933f/mypy-1.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d3243c406773185144527f83be0e0aefc7bf4601b0b2b956665608bf7c98a83", size = 14685075, upload-time = "2026-03-31T16:54:04.464Z" }, - { url = "https://files.pythonhosted.org/packages/b5/9d/d924b38a4923f8d164bf2b4ec98bf13beaf6e10a5348b4b137eadae40a6e/mypy-1.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a79c1eba7ac4209f2d850f0edd0a2f8bba88cbfdfefe6fb76a19e9d4fe5e71a2", size = 14919141, upload-time = "2026-03-31T16:54:51.785Z" }, - { url = "https://files.pythonhosted.org/packages/59/98/1da9977016678c0b99d43afe52ed00bb3c1a0c4c995d3e6acca1a6ebb9b4/mypy-1.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:00e047c74d3ec6e71a2eb88e9ea551a2edb90c21f993aefa9e0d2a898e0bb732", size = 11050925, upload-time = "2026-03-31T16:51:30.758Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e3/ba0b7a3143e49a9c4f5967dde6ea4bf8e0b10ecbbcca69af84027160ee89/mypy-1.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:931a7630bba591593dcf6e97224a21ff80fb357e7982628d25e3c618e7f598ef", size = 10001089, upload-time = "2026-03-31T16:49:43.632Z" }, - { url = "https://files.pythonhosted.org/packages/12/28/e617e67b3be9d213cda7277913269c874eb26472489f95d09d89765ce2d8/mypy-1.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:26c8b52627b6552f47ff11adb4e1509605f094e29815323e487fc0053ebe93d1", size = 15534710, upload-time = "2026-03-31T16:52:12.506Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0c/3b5f2d3e45dc7169b811adce8451679d9430399d03b168f9b0489f43adaa/mypy-1.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:39362cdb4ba5f916e7976fccecaab1ba3a83e35f60fa68b64e9a70e221bb2436", size = 14393013, upload-time = "2026-03-31T16:54:41.186Z" }, - { url = "https://files.pythonhosted.org/packages/a3/49/edc8b0aa145cc09c1c74f7ce2858eead9329931dcbbb26e2ad40906daa4e/mypy-1.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34506397dbf40c15dc567635d18a21d33827e9ab29014fb83d292a8f4f8953b6", size = 15047240, upload-time = "2026-03-31T16:54:31.955Z" }, - { url = "https://files.pythonhosted.org/packages/42/37/a946bb416e37a57fa752b3100fd5ede0e28df94f92366d1716555d47c454/mypy-1.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:555493c44a4f5a1b58d611a43333e71a9981c6dbe26270377b6f8174126a0526", size = 15858565, upload-time = "2026-03-31T16:53:36.997Z" }, - { url = "https://files.pythonhosted.org/packages/2f/99/7690b5b5b552db1bd4ff362e4c0eb3107b98d680835e65823fbe888c8b78/mypy-1.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2721f0ce49cb74a38f00c50da67cb7d36317b5eda38877a49614dc018e91c787", size = 16087874, upload-time = "2026-03-31T16:52:48.313Z" }, - { url = "https://files.pythonhosted.org/packages/aa/76/53e893a498138066acd28192b77495c9357e5a58cc4be753182846b43315/mypy-1.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:47781555a7aa5fedcc2d16bcd72e0dc83eb272c10dd657f9fb3f9cc08e2e6abb", size = 12572380, upload-time = "2026-03-31T16:49:52.454Z" }, - { url = "https://files.pythonhosted.org/packages/76/9c/6dbdae21f01b7aacddc2c0bbf3c5557aa547827fdf271770fe1e521e7093/mypy-1.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c70380fe5d64010f79fb863b9081c7004dd65225d2277333c219d93a10dad4dd", size = 10381174, upload-time = "2026-03-31T16:51:20.179Z" }, - { url = "https://files.pythonhosted.org/packages/21/66/4d734961ce167f0fd8380769b3b7c06dbdd6ff54c2190f3f2ecd22528158/mypy-1.20.0-py3-none-any.whl", hash = "sha256:a6e0641147cbfa7e4e94efdb95c2dab1aff8cfc159ded13e07f308ddccc8c48e", size = 2636365, upload-time = "2026-03-31T16:51:44.911Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/0b/3d/5b373635b3146264eb7a68d09e5ca11c305bbb058dfffbb47c47daf4f632/mypy-1.20.1.tar.gz", hash = "sha256:6fc3f4ecd52de81648fed1945498bf42fa2993ddfad67c9056df36ae5757f804", size = 3815892, upload-time = "2026-04-13T02:46:51.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/4b/b1fa23297c8a5c403aabaac0649549efc5a0af7095f3dd33e7482863f973/mypy-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3ba5d1e712ada9c3b6223dcbc5a31dac334ed62991e5caa17bcf5a4ddc349af0", size = 14426426, upload-time = "2026-04-13T02:46:37.828Z" }, + { url = "https://files.pythonhosted.org/packages/22/53/82923480aee5507a46df22428316e28b2b710d08506a128b2acef81ab18e/mypy-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e731284c117b0987fb1e6c5013a56f33e7faa1fce594066ab83876183ce1c66", size = 13307651, upload-time = "2026-04-13T02:46:22.676Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0c/91905b393c790440fa273f0903ee2b07cce95bb6deccac87e6eb343d077a/mypy-1.20.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8e945b872a05f4fbefabe2249c0b07b6b194e5e11a86ebee9edf855de09806c", size = 13746066, upload-time = "2026-04-13T02:45:15.345Z" }, + { url = "https://files.pythonhosted.org/packages/88/b9/8a7017270438e34544e19dd6284cad54fd65dde3c35418a2ce07a1897804/mypy-1.20.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fc88acef0dc9b15246502b418980478c1bfc9702057a0e1e7598d01a7af8937", size = 14617944, upload-time = "2026-04-13T02:45:44.954Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cf/5a61ceec3fc133e0f559d1e1f9adf4150abdbc2ad8eb831ec26fc8459196/mypy-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:14911a115c73608f155f648b978c5055d16ff974e6b1b5512d7fedf4fa8b15c6", size = 14918205, upload-time = "2026-04-13T02:45:42.653Z" }, + { url = "https://files.pythonhosted.org/packages/6f/80/afb1c665e9c426c78e4711cce04e446b645867bfb97936158886103c1648/mypy-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:76d9b4c992cca3331d9793ef197ae360ea44953cf35beb2526e95b9e074f2866", size = 10823344, upload-time = "2026-04-13T02:46:07.607Z" }, + { url = "https://files.pythonhosted.org/packages/11/68/7ad64b49b7663c88fef76a2ac689ea73e17804832ac4cb5416bcff17775b/mypy-1.20.1-cp310-cp310-win_arm64.whl", hash = "sha256:b408722f80be44845da555671a5ef3a0c63f51ca5752b0c20e992dc9c0fbd3cd", size = 9760694, upload-time = "2026-04-13T02:46:49.369Z" }, + { url = "https://files.pythonhosted.org/packages/82/0d/555ab7453cc4a4a8643b7f21c842b1a84c36b15392061ae7b052ee119320/mypy-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c01eb9bac2c6a962d00f9d23421cd2913840e65bba365167d057bd0b4171a92e", size = 14336012, upload-time = "2026-04-13T02:45:39.935Z" }, + { url = "https://files.pythonhosted.org/packages/57/26/85a28893f7db8a16ebb41d1e9dfcb4475844d06a88480b6639e32a74d6ef/mypy-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55d12ddbd8a9cac5b276878bd534fa39fff5bf543dc6ae18f25d30c8d7d27fca", size = 13224636, upload-time = "2026-04-13T02:45:49.659Z" }, + { url = "https://files.pythonhosted.org/packages/93/41/bd4cd3c2caeb6c448b669222b8cfcbdee4a03b89431527b56fca9e56b6f3/mypy-1.20.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0aa322c1468b6cdfc927a44ce130f79bb44bcd34eb4a009eb9f96571fd80955", size = 13663471, upload-time = "2026-04-13T02:46:20.276Z" }, + { url = "https://files.pythonhosted.org/packages/3e/56/7ee8c471e10402d64b6517ae10434541baca053cffd81090e4097d5609d4/mypy-1.20.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f8bc95899cf676b6e2285779a08a998cc3a7b26f1026752df9d2741df3c79e8", size = 14532344, upload-time = "2026-04-13T02:46:44.205Z" }, + { url = "https://files.pythonhosted.org/packages/b5/95/b37d1fa859a433f6156742e12f62b0bb75af658544fb6dada9363918743a/mypy-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:47c2b90191a870a04041e910277494b0d92f0711be9e524d45c074fe60c00b65", size = 14776670, upload-time = "2026-04-13T02:45:52.481Z" }, + { url = "https://files.pythonhosted.org/packages/03/77/b302e4cb0b80d2bdf6bf4fce5864bb4cbfa461f7099cea544eaf2457df78/mypy-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:9857dc8d2ec1a392ffbda518075beb00ac58859979c79f9e6bdcb7277082c2f2", size = 10816524, upload-time = "2026-04-13T02:45:37.711Z" }, + { url = "https://files.pythonhosted.org/packages/7f/21/d969d7a68eb964993ebcc6170d5ecaf0cf65830c58ac3344562e16dc42a9/mypy-1.20.1-cp311-cp311-win_arm64.whl", hash = "sha256:09d8df92bb25b6065ab91b178da843dda67b33eb819321679a6e98a907ce0e10", size = 9750419, upload-time = "2026-04-13T02:45:08.542Z" }, + { url = "https://files.pythonhosted.org/packages/69/1b/75a7c825a02781ca10bc2f2f12fba2af5202f6d6005aad8d2d1f264d8d78/mypy-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:36ee2b9c6599c230fea89bbd79f401f9f9f8e9fcf0c777827789b19b7da90f51", size = 14494077, upload-time = "2026-04-13T02:45:55.085Z" }, + { url = "https://files.pythonhosted.org/packages/b0/54/5e5a569ea5c2b4d48b729fb32aa936eeb4246e4fc3e6f5b3d36a2dfbefb9/mypy-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fba3fb0968a7b48806b0c90f38d39296f10766885a94c83bd21399de1e14eb28", size = 13319495, upload-time = "2026-04-13T02:45:29.674Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a4/a1945b19f33e91721b59deee3abb484f2fa5922adc33bb166daf5325d76d/mypy-1.20.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef1415a637cd3627d6304dfbeddbadd21079dafc2a8a753c477ce4fc0c2af54f", size = 13696948, upload-time = "2026-04-13T02:46:15.006Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c6/75e969781c2359b2f9c15b061f28ec6d67c8b61865ceda176e85c8e7f2de/mypy-1.20.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef3461b1ad5cd446e540016e90b5984657edda39f982f4cc45ca317b628f5a37", size = 14706744, upload-time = "2026-04-13T02:46:00.482Z" }, + { url = "https://files.pythonhosted.org/packages/a8/6e/b221b1de981fc4262fe3e0bf9ec272d292dfe42394a689c2d49765c144c4/mypy-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:542dd63c9e1339b6092eb25bd515f3a32a1453aee8c9521d2ddb17dacd840237", size = 14949035, upload-time = "2026-04-13T02:45:06.021Z" }, + { url = "https://files.pythonhosted.org/packages/ca/4b/298ba2de0aafc0da3ff2288da06884aae7ba6489bc247c933f87847c41b3/mypy-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:1d55c7cd8ca22e31f93af2a01160a9e95465b5878de23dba7e48116052f20a8d", size = 10883216, upload-time = "2026-04-13T02:45:47.232Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f9/5e25b8f0b8cb92f080bfed9c21d3279b2a0b6a601cdca369a039ba84789d/mypy-1.20.1-cp312-cp312-win_arm64.whl", hash = "sha256:f5b84a79070586e0d353ee07b719d9d0a4aa7c8ee90c0ea97747e98cbe193019", size = 9814299, upload-time = "2026-04-13T02:45:21.934Z" }, + { url = "https://files.pythonhosted.org/packages/21/e8/ef0991aa24c8f225df10b034f3c2681213cb54cf247623c6dec9a5744e70/mypy-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f3886c03e40afefd327bd70b3f634b39ea82e87f314edaa4d0cce4b927ddcc1", size = 14500739, upload-time = "2026-04-13T02:46:05.442Z" }, + { url = "https://files.pythonhosted.org/packages/23/73/416ebec3047636ed89fa871dc8c54bf05e9e20aa9499da59790d7adb312d/mypy-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e860eb3904f9764e83bafd70c8250bdffdc7dde6b82f486e8156348bf7ceb184", size = 13314735, upload-time = "2026-04-13T02:46:47.154Z" }, + { url = "https://files.pythonhosted.org/packages/10/1e/1505022d9c9ac2e014a384eb17638fb37bf8e9d0a833ea60605b66f8f7ba/mypy-1.20.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4b5aac6e785719da51a84f5d09e9e843d473170a9045b1ea7ea1af86225df4b", size = 13704356, upload-time = "2026-04-13T02:45:19.773Z" }, + { url = "https://files.pythonhosted.org/packages/98/91/275b01f5eba5c467a3318ec214dd865abb66e9c811231c8587287b92876a/mypy-1.20.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f37b6cd0fe2ad3a20f05ace48ca3523fc52ff86940e34937b439613b6854472e", size = 14696420, upload-time = "2026-04-13T02:45:24.205Z" }, + { url = "https://files.pythonhosted.org/packages/a1/57/b3779e134e1b7250d05f874252780d0a88c068bc054bcff99ca20a3a2986/mypy-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4bbb0f6b54ce7cc350ef4a770650d15fa70edd99ad5267e227133eda9c94218", size = 14936093, upload-time = "2026-04-13T02:45:32.087Z" }, + { url = "https://files.pythonhosted.org/packages/be/33/81b64991b0f3f278c3b55c335888794af190b2d59031a5ad1401bcb69f1e/mypy-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:c3dc20f8ec76eecd77148cdd2f1542ed496e51e185713bf488a414f862deb8f2", size = 10889659, upload-time = "2026-04-13T02:46:02.926Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fd/7adcb8053572edf5ef8f3db59599dfeeee3be9cc4c8c97e2d28f66f42ac5/mypy-1.20.1-cp313-cp313-win_arm64.whl", hash = "sha256:a9d62bbac5d6d46718e2b0330b25e6264463ed832722b8f7d4440ff1be3ca895", size = 9815515, upload-time = "2026-04-13T02:46:32.103Z" }, + { url = "https://files.pythonhosted.org/packages/40/cd/db831e84c81d57d4886d99feee14e372f64bbec6a9cb1a88a19e243f2ef5/mypy-1.20.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:12927b9c0ed794daedcf1dab055b6c613d9d5659ac511e8d936d96f19c087d12", size = 14483064, upload-time = "2026-04-13T02:45:26.901Z" }, + { url = "https://files.pythonhosted.org/packages/d5/82/74e62e7097fa67da328ac8ece8de09133448c04d20ddeaeba251a3000f01/mypy-1.20.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:752507dd481e958b2c08fc966d3806c962af5a9433b5bf8f3bdd7175c20e34fe", size = 13335694, upload-time = "2026-04-13T02:46:12.514Z" }, + { url = "https://files.pythonhosted.org/packages/74/c4/97e9a0abe4f3cdbbf4d079cb87a03b786efeccf5bf2b89fe4f96939ab2e6/mypy-1.20.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c614655b5a065e56274c6cbbe405f7cf7e96c0654db7ba39bc680238837f7b08", size = 13726365, upload-time = "2026-04-13T02:45:17.422Z" }, + { url = "https://files.pythonhosted.org/packages/d7/aa/a19d884a8d28fcd3c065776323029f204dbc774e70ec9c85eba228b680de/mypy-1.20.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c3f6221a76f34d5100c6d35b3ef6b947054123c3f8d6938a4ba00b1308aa572", size = 14693472, upload-time = "2026-04-13T02:46:41.253Z" }, + { url = "https://files.pythonhosted.org/packages/84/44/cc9324bd21cf786592b44bf3b5d224b3923c1230ec9898d508d00241d465/mypy-1.20.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4bdfc06303ac06500af71ea0cdbe995c502b3c9ba32f3f8313523c137a25d1b6", size = 14919266, upload-time = "2026-04-13T02:46:28.37Z" }, + { url = "https://files.pythonhosted.org/packages/6e/dc/779abb25a8c63e8f44bf5a336217fa92790fa17e0c40e0c725d10cb01bbd/mypy-1.20.1-cp314-cp314-win_amd64.whl", hash = "sha256:0131edd7eba289973d1ba1003d1a37c426b85cdef76650cd02da6420898a5eb3", size = 11049713, upload-time = "2026-04-13T02:45:57.673Z" }, + { url = "https://files.pythonhosted.org/packages/28/08/4172be2ad7de9119b5a92ca36abbf641afdc5cb1ef4ae0c3a8182f29674f/mypy-1.20.1-cp314-cp314-win_arm64.whl", hash = "sha256:33f02904feb2c07e1fdf7909026206396c9deeb9e6f34d466b4cfedb0aadbbe4", size = 9999819, upload-time = "2026-04-13T02:46:35.039Z" }, + { url = "https://files.pythonhosted.org/packages/2d/af/af9e46b0c8eabbce9fc04a477564170f47a1c22b308822282a59b7ff315f/mypy-1.20.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:168472149dd8cc505c98cefd21ad77e4257ed6022cd5ed2fe2999bed56977a5a", size = 15547508, upload-time = "2026-04-13T02:46:25.588Z" }, + { url = "https://files.pythonhosted.org/packages/a7/cd/39c9e4ad6ba33e069e5837d772a9e6c304b4a5452a14a975d52b36444650/mypy-1.20.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eb674600309a8f22790cca883a97c90299f948183ebb210fbef6bcee07cb1986", size = 14399557, upload-time = "2026-04-13T02:46:10.021Z" }, + { url = "https://files.pythonhosted.org/packages/83/c1/3fd71bdc118ffc502bf57559c909927bb7e011f327f7bb8e0488e98a5870/mypy-1.20.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef2b2e4cc464ba9795459f2586923abd58a0055487cbe558cb538ea6e6bc142a", size = 15045789, upload-time = "2026-04-13T02:45:10.81Z" }, + { url = "https://files.pythonhosted.org/packages/8e/73/6f07ff8b57a7d7b3e6e5bf34685d17632382395c8bb53364ec331661f83e/mypy-1.20.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee461d396dd46b3f0ed5a098dbc9b8860c81c46ad44fa071afcfbc149f167c9", size = 15850795, upload-time = "2026-04-13T02:45:03.349Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e2/f7dffec1c7767078f9e9adf0c786d1fe0ff30964a77eb213c09b8b58cb76/mypy-1.20.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e364926308b3e66f1361f81a566fc1b2f8cd47fc8525e8136d4058a65a4b4f02", size = 16088539, upload-time = "2026-04-13T02:46:17.841Z" }, + { url = "https://files.pythonhosted.org/packages/1a/76/e0dee71035316e75a69d73aec2f03c39c21c967b97e277fd0ef8fd6aec66/mypy-1.20.1-cp314-cp314t-win_amd64.whl", hash = "sha256:a0c17fbd746d38c70cbc42647cfd884f845a9708a4b160a8b4f7e70d41f4d7fa", size = 12575567, upload-time = "2026-04-13T02:45:34.795Z" }, + { url = "https://files.pythonhosted.org/packages/22/a8/7ed43c9d9c3d1468f86605e323a5d97e411a448790a00f07e779f3211a46/mypy-1.20.1-cp314-cp314t-win_arm64.whl", hash = "sha256:db2cb89654626a912efda69c0d5c1d22d948265e2069010d3dde3abf751c7d08", size = 10378823, upload-time = "2026-04-13T02:45:13.35Z" }, + { url = "https://files.pythonhosted.org/packages/d8/28/926bd972388e65a39ee98e188ccf67e81beb3aacfd5d6b310051772d974b/mypy-1.20.1-py3-none-any.whl", hash = "sha256:1aae28507f253fe82d883790d1c0a0d35798a810117c88184097fe8881052f06", size = 2636553, upload-time = "2026-04-13T02:46:30.45Z" }, ] [[package]] @@ -1634,7 +1634,7 @@ all = [ { name = "chango", marker = "python_full_version >= '3.12'", specifier = "~=0.6.0" }, { name = "flaky", specifier = ">=3.8.1" }, { name = "furo", specifier = "==2025.12.19" }, - { name = "mypy", specifier = "==1.20.0" }, + { name = "mypy", specifier = "==1.20.1" }, { name = "prek" }, { name = "pydantic", marker = "python_full_version >= '3.14'", specifier = ">=2.12.0a1" }, { name = "pylint", specifier = "==4.0.5" }, @@ -1664,7 +1664,7 @@ docs = [ { name = "sphinxcontrib-mermaid", specifier = "==2.0.1" }, ] linting = [ - { name = "mypy", specifier = "==1.20.0" }, + { name = "mypy", specifier = "==1.20.1" }, { name = "prek" }, { name = "pylint", specifier = "==4.0.5" }, { name = "ruff", specifier = "==0.15.10" }, From f34db07d702f51d0890b6ef0290277ec1d52e4c1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 19 Apr 2026 09:21:14 +0000 Subject: [PATCH 15/39] Update Ruff to v0.15.11 (#5209) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- .../5209.msPrg8bYnmr89qXR4fr5bG.toml | 5 ++ pyproject.toml | 2 +- uv.lock | 46 +++++++++---------- 4 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 changes/unreleased/5209.msPrg8bYnmr89qXR4fr5bG.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e510d52b734..be17032733b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.15.10' + rev: 'v0.15.11' hooks: # Run the linter: - id: ruff-check diff --git a/changes/unreleased/5209.msPrg8bYnmr89qXR4fr5bG.toml b/changes/unreleased/5209.msPrg8bYnmr89qXR4fr5bG.toml new file mode 100644 index 00000000000..ee623902d8f --- /dev/null +++ b/changes/unreleased/5209.msPrg8bYnmr89qXR4fr5bG.toml @@ -0,0 +1,5 @@ +internal = "Update Ruff to v0.15.11" +[[pull_requests]] +uid = "5209" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index e0890d3dbd5..8c25dba356f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,7 @@ docs = [ ] linting = [ "prek", - "ruff==0.15.10", + "ruff==0.15.11", "mypy==1.20.1", "pylint==4.0.5" ] diff --git a/uv.lock b/uv.lock index a30a63bf1fa..2306fa9df21 100644 --- a/uv.lock +++ b/uv.lock @@ -1643,7 +1643,7 @@ all = [ { name = "pytest-cov" }, { name = "pytest-xdist", specifier = "==3.8.0" }, { name = "pytz" }, - { name = "ruff", specifier = "==0.15.10" }, + { name = "ruff", specifier = "==0.15.11" }, { name = "sphinx", marker = "python_full_version >= '3.12'", specifier = "==9.1.0" }, { name = "sphinx-build-compatibility", git = "https://github.com/readthedocs/sphinx-build-compatibility.git?rev=58aabc5f207c6c2421f23d3578adc0b14af57047" }, { name = "sphinx-copybutton", specifier = "==0.5.2" }, @@ -1667,7 +1667,7 @@ linting = [ { name = "mypy", specifier = "==1.20.1" }, { name = "prek" }, { name = "pylint", specifier = "==4.0.5" }, - { name = "ruff", specifier = "==0.15.10" }, + { name = "ruff", specifier = "==0.15.11" }, ] tests = [ { name = "beautifulsoup4" }, @@ -1793,27 +1793,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/aa3f7d59a10ef6b14fe3431706f854dbf03c5976be614a9796d36326810c/ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e", size = 4631728, upload-time = "2026-04-09T14:06:09.884Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/00/a1c2fdc9939b2c03691edbda290afcd297f1f389196172826b03d6b6a595/ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f", size = 10563362, upload-time = "2026-04-09T14:06:21.189Z" }, - { url = "https://files.pythonhosted.org/packages/5c/15/006990029aea0bebe9d33c73c3e28c80c391ebdba408d1b08496f00d422d/ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e", size = 10951122, upload-time = "2026-04-09T14:06:02.236Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1", size = 10314005, upload-time = "2026-04-09T14:06:00.026Z" }, - { url = "https://files.pythonhosted.org/packages/da/73/c209138a5c98c0d321266372fc4e33ad43d506d7e5dd817dd89b60a8548f/ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e", size = 10643450, upload-time = "2026-04-09T14:05:42.137Z" }, - { url = "https://files.pythonhosted.org/packages/ec/76/0deec355d8ec10709653635b1f90856735302cb8e149acfdf6f82a5feb70/ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1", size = 10379597, upload-time = "2026-04-09T14:05:49.984Z" }, - { url = "https://files.pythonhosted.org/packages/dc/be/86bba8fc8798c081e28a4b3bb6d143ccad3fd5f6f024f02002b8f08a9fa3/ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef", size = 11146645, upload-time = "2026-04-09T14:06:12.246Z" }, - { url = "https://files.pythonhosted.org/packages/a8/89/140025e65911b281c57be1d385ba1d932c2366ca88ae6663685aed8d4881/ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158", size = 12030289, upload-time = "2026-04-09T14:06:04.776Z" }, - { url = "https://files.pythonhosted.org/packages/88/de/ddacca9545a5e01332567db01d44bd8cf725f2db3b3d61a80550b48308ea/ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0", size = 11496266, upload-time = "2026-04-09T14:05:55.485Z" }, - { url = "https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609", size = 11256418, upload-time = "2026-04-09T14:05:57.69Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/55de0d35aacf6cd50b6ee91ee0f291672080021896543776f4170fc5c454/ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f", size = 11288416, upload-time = "2026-04-09T14:05:44.695Z" }, - { url = "https://files.pythonhosted.org/packages/68/cf/9438b1a27426ec46a80e0a718093c7f958ef72f43eb3111862949ead3cc1/ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151", size = 10621053, upload-time = "2026-04-09T14:05:52.782Z" }, - { url = "https://files.pythonhosted.org/packages/4c/50/e29be6e2c135e9cd4cb15fbade49d6a2717e009dff3766dd080fcb82e251/ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8", size = 10378302, upload-time = "2026-04-09T14:06:14.361Z" }, - { url = "https://files.pythonhosted.org/packages/18/2f/e0b36a6f99c51bb89f3a30239bc7bf97e87a37ae80aa2d6542d6e5150364/ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07", size = 10850074, upload-time = "2026-04-09T14:06:16.581Z" }, - { url = "https://files.pythonhosted.org/packages/11/08/874da392558ce087a0f9b709dc6ec0d60cbc694c1c772dab8d5f31efe8cb/ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48", size = 11358051, upload-time = "2026-04-09T14:06:18.948Z" }, - { url = "https://files.pythonhosted.org/packages/e4/46/602938f030adfa043e67112b73821024dc79f3ab4df5474c25fa4c1d2d14/ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5", size = 10588964, upload-time = "2026-04-09T14:06:07.14Z" }, - { url = "https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed", size = 11745044, upload-time = "2026-04-09T14:05:39.473Z" }, - { url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" }, +version = "0.15.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264, upload-time = "2026-04-16T18:46:26.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943, upload-time = "2026-04-16T18:46:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592, upload-time = "2026-04-16T18:46:00.742Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501, upload-time = "2026-04-16T18:46:03.723Z" }, + { url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693, upload-time = "2026-04-16T18:46:41.941Z" }, + { url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177, upload-time = "2026-04-16T18:46:21.717Z" }, + { url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886, upload-time = "2026-04-16T18:46:15.086Z" }, + { url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183, upload-time = "2026-04-16T18:46:07.944Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575, upload-time = "2026-04-16T18:46:31.687Z" }, + { url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537, upload-time = "2026-04-16T18:46:36.988Z" }, + { url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813, upload-time = "2026-04-16T18:46:24.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136, upload-time = "2026-04-16T18:46:39.802Z" }, + { url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701, upload-time = "2026-04-16T18:46:10.381Z" }, + { url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887, upload-time = "2026-04-16T18:46:29.157Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316, upload-time = "2026-04-16T18:46:19.462Z" }, + { url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535, upload-time = "2026-04-16T18:46:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692, upload-time = "2026-04-16T18:46:17.268Z" }, + { url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614, upload-time = "2026-04-16T18:46:34.487Z" }, ] [[package]] From 229b61a3aef0e7c1b3f70d99a0d45a65c3d05530 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 01:57:29 +0000 Subject: [PATCH 16/39] Update Ruff to v0.15.12 (#5217) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- .../5217.5AVeFbQnudaXWyZ3TM7E5B.toml | 5 ++ pyproject.toml | 2 +- uv.lock | 46 +++++++++---------- 4 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 changes/unreleased/5217.5AVeFbQnudaXWyZ3TM7E5B.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index be17032733b..c59d716bdd2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.15.11' + rev: 'v0.15.12' hooks: # Run the linter: - id: ruff-check diff --git a/changes/unreleased/5217.5AVeFbQnudaXWyZ3TM7E5B.toml b/changes/unreleased/5217.5AVeFbQnudaXWyZ3TM7E5B.toml new file mode 100644 index 00000000000..81f26445f4b --- /dev/null +++ b/changes/unreleased/5217.5AVeFbQnudaXWyZ3TM7E5B.toml @@ -0,0 +1,5 @@ +internal = "Update Ruff to v0.15.12" +[[pull_requests]] +uid = "5217" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index 8c25dba356f..d6c495c1132 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,7 @@ docs = [ ] linting = [ "prek", - "ruff==0.15.11", + "ruff==0.15.12", "mypy==1.20.1", "pylint==4.0.5" ] diff --git a/uv.lock b/uv.lock index 2306fa9df21..7562814fd4a 100644 --- a/uv.lock +++ b/uv.lock @@ -1643,7 +1643,7 @@ all = [ { name = "pytest-cov" }, { name = "pytest-xdist", specifier = "==3.8.0" }, { name = "pytz" }, - { name = "ruff", specifier = "==0.15.11" }, + { name = "ruff", specifier = "==0.15.12" }, { name = "sphinx", marker = "python_full_version >= '3.12'", specifier = "==9.1.0" }, { name = "sphinx-build-compatibility", git = "https://github.com/readthedocs/sphinx-build-compatibility.git?rev=58aabc5f207c6c2421f23d3578adc0b14af57047" }, { name = "sphinx-copybutton", specifier = "==0.5.2" }, @@ -1667,7 +1667,7 @@ linting = [ { name = "mypy", specifier = "==1.20.1" }, { name = "prek" }, { name = "pylint", specifier = "==4.0.5" }, - { name = "ruff", specifier = "==0.15.11" }, + { name = "ruff", specifier = "==0.15.12" }, ] tests = [ { name = "beautifulsoup4" }, @@ -1793,27 +1793,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264, upload-time = "2026-04-16T18:46:26.58Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943, upload-time = "2026-04-16T18:46:05.967Z" }, - { url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592, upload-time = "2026-04-16T18:46:00.742Z" }, - { url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501, upload-time = "2026-04-16T18:46:03.723Z" }, - { url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693, upload-time = "2026-04-16T18:46:41.941Z" }, - { url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177, upload-time = "2026-04-16T18:46:21.717Z" }, - { url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886, upload-time = "2026-04-16T18:46:15.086Z" }, - { url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183, upload-time = "2026-04-16T18:46:07.944Z" }, - { url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575, upload-time = "2026-04-16T18:46:31.687Z" }, - { url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537, upload-time = "2026-04-16T18:46:36.988Z" }, - { url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813, upload-time = "2026-04-16T18:46:24.182Z" }, - { url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136, upload-time = "2026-04-16T18:46:39.802Z" }, - { url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701, upload-time = "2026-04-16T18:46:10.381Z" }, - { url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887, upload-time = "2026-04-16T18:46:29.157Z" }, - { url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316, upload-time = "2026-04-16T18:46:19.462Z" }, - { url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535, upload-time = "2026-04-16T18:46:12.47Z" }, - { url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692, upload-time = "2026-04-16T18:46:17.268Z" }, - { url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614, upload-time = "2026-04-16T18:46:34.487Z" }, +version = "0.15.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, + { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, + { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, + { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, + { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, + { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, + { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, + { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, + { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, + { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, ] [[package]] From 80d515de39226cf37e69e0cc46e1fc9a45d2f676 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 01:33:06 +0000 Subject: [PATCH 17/39] Update actions/upload-artifact action to v7.0.1 (#5220) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/docs-linkcheck.yml | 2 +- .github/workflows/release_pypi.yml | 4 ++-- .github/workflows/release_test_pypi.yml | 4 ++-- changes/unreleased/5220.iAgLEYgfuW46wtvCUx3dhM.toml | 5 +++++ 4 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 changes/unreleased/5220.iAgLEYgfuW46wtvCUx3dhM.toml diff --git a/.github/workflows/docs-linkcheck.yml b/.github/workflows/docs-linkcheck.yml index da816bfb2d7..02df1034ebd 100644 --- a/.github/workflows/docs-linkcheck.yml +++ b/.github/workflows/docs-linkcheck.yml @@ -35,7 +35,7 @@ jobs: - name: Upload linkcheck output # Run also if the previous steps failed if: always() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: linkcheck-output path: docs/build/html/output.* diff --git a/.github/workflows/release_pypi.yml b/.github/workflows/release_pypi.yml index d394e462866..a6a42d10dde 100644 --- a/.github/workflows/release_pypi.yml +++ b/.github/workflows/release_pypi.yml @@ -30,7 +30,7 @@ jobs: - name: Build a binary wheel and a source tarball run: python3 -m build - name: Store the distribution packages - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: python-package-distributions path: dist/ @@ -92,7 +92,7 @@ jobs: ./dist/*.tar.gz ./dist/*.whl - name: Store the distribution packages and signatures - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: python-package-distributions-and-signatures path: dist/ diff --git a/.github/workflows/release_test_pypi.yml b/.github/workflows/release_test_pypi.yml index d7376a50535..5a9661de79d 100644 --- a/.github/workflows/release_test_pypi.yml +++ b/.github/workflows/release_test_pypi.yml @@ -30,7 +30,7 @@ jobs: - name: Build a binary wheel and a source tarball run: python3 -m build - name: Store the distribution packages - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: python-package-distributions path: dist/ @@ -94,7 +94,7 @@ jobs: ./dist/*.tar.gz ./dist/*.whl - name: Store the distribution packages and signatures - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: python-package-distributions-and-signatures path: dist/ diff --git a/changes/unreleased/5220.iAgLEYgfuW46wtvCUx3dhM.toml b/changes/unreleased/5220.iAgLEYgfuW46wtvCUx3dhM.toml new file mode 100644 index 00000000000..81e1d3d1a1f --- /dev/null +++ b/changes/unreleased/5220.iAgLEYgfuW46wtvCUx3dhM.toml @@ -0,0 +1,5 @@ +internal = "Update actions/upload-artifact action to v7.0.1" +[[pull_requests]] +uid = "5220" +author_uids = ["renovate[bot]"] +closes_threads = [] From 818d17c44d0fdcc43756629e258e2b8c335ceed3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 01:42:44 +0000 Subject: [PATCH 18/39] Update github/codeql-action action to v4.35.2 (#5222) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/gha_security.yml | 2 +- changes/unreleased/5222.dGHpTKrbVb9VQV8pVv9Lii.toml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/unreleased/5222.dGHpTKrbVb9VQV8pVv9Lii.toml diff --git a/.github/workflows/gha_security.yml b/.github/workflows/gha_security.yml index a4c3bc149c3..113e3fcff7f 100644 --- a/.github/workflows/gha_security.yml +++ b/.github/workflows/gha_security.yml @@ -27,7 +27,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 + uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 with: sarif_file: results.sarif category: zizmor diff --git a/changes/unreleased/5222.dGHpTKrbVb9VQV8pVv9Lii.toml b/changes/unreleased/5222.dGHpTKrbVb9VQV8pVv9Lii.toml new file mode 100644 index 00000000000..b6f8325899e --- /dev/null +++ b/changes/unreleased/5222.dGHpTKrbVb9VQV8pVv9Lii.toml @@ -0,0 +1,5 @@ +internal = "Update github/codeql-action action to v4.35.2" +[[pull_requests]] +uid = "5222" +author_uids = ["renovate[bot]"] +closes_threads = [] From 69af37ded9b74498c0eba80d0a6f6ffcfc201efd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 01:48:48 +0000 Subject: [PATCH 19/39] Update Mypy to v1.20.2 (#5223) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- .../5223.6Ttu6UoyzkVQddzKrz947W.toml | 5 + pyproject.toml | 2 +- uv.lock | 111 +++++++++--------- 4 files changed, 65 insertions(+), 55 deletions(-) create mode 100644 changes/unreleased/5223.6Ttu6UoyzkVQddzKrz947W.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c59d716bdd2..10e04d2940c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: - . # this basically does `pip install -e .` priority: 10 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.20.1 + rev: v1.20.2 hooks: - id: mypy name: mypy-ptb diff --git a/changes/unreleased/5223.6Ttu6UoyzkVQddzKrz947W.toml b/changes/unreleased/5223.6Ttu6UoyzkVQddzKrz947W.toml new file mode 100644 index 00000000000..e58e4ffd443 --- /dev/null +++ b/changes/unreleased/5223.6Ttu6UoyzkVQddzKrz947W.toml @@ -0,0 +1,5 @@ +internal = "Update Mypy to v1.20.2" +[[pull_requests]] +uid = "5223" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index d6c495c1132..c5a1377164d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,7 +134,7 @@ docs = [ linting = [ "prek", "ruff==0.15.12", - "mypy==1.20.1", + "mypy==1.20.2", "pylint==4.0.5" ] all = [{ include-group = "tests" }, { include-group = "docs" }, { include-group = "linting"}] diff --git a/uv.lock b/uv.lock index 7562814fd4a..fceee73d550 100644 --- a/uv.lock +++ b/uv.lock @@ -2,7 +2,8 @@ version = 1 revision = 3 requires-python = ">=3.10" resolution-markers = [ - "python_full_version >= '3.14'", + "python_full_version >= '3.15'", + "python_full_version == '3.14.*'", "python_full_version == '3.13.*'", "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", @@ -579,7 +580,8 @@ name = "docutils" version = "0.22.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14'", + "python_full_version >= '3.15'", + "python_full_version == '3.14.*'", "python_full_version == '3.13.*'", "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", @@ -975,7 +977,7 @@ wheels = [ [[package]] name = "mypy" -version = "1.20.1" +version = "1.20.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, @@ -984,51 +986,51 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/3d/5b373635b3146264eb7a68d09e5ca11c305bbb058dfffbb47c47daf4f632/mypy-1.20.1.tar.gz", hash = "sha256:6fc3f4ecd52de81648fed1945498bf42fa2993ddfad67c9056df36ae5757f804", size = 3815892, upload-time = "2026-04-13T02:46:51.474Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/4b/b1fa23297c8a5c403aabaac0649549efc5a0af7095f3dd33e7482863f973/mypy-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3ba5d1e712ada9c3b6223dcbc5a31dac334ed62991e5caa17bcf5a4ddc349af0", size = 14426426, upload-time = "2026-04-13T02:46:37.828Z" }, - { url = "https://files.pythonhosted.org/packages/22/53/82923480aee5507a46df22428316e28b2b710d08506a128b2acef81ab18e/mypy-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e731284c117b0987fb1e6c5013a56f33e7faa1fce594066ab83876183ce1c66", size = 13307651, upload-time = "2026-04-13T02:46:22.676Z" }, - { url = "https://files.pythonhosted.org/packages/4e/0c/91905b393c790440fa273f0903ee2b07cce95bb6deccac87e6eb343d077a/mypy-1.20.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8e945b872a05f4fbefabe2249c0b07b6b194e5e11a86ebee9edf855de09806c", size = 13746066, upload-time = "2026-04-13T02:45:15.345Z" }, - { url = "https://files.pythonhosted.org/packages/88/b9/8a7017270438e34544e19dd6284cad54fd65dde3c35418a2ce07a1897804/mypy-1.20.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fc88acef0dc9b15246502b418980478c1bfc9702057a0e1e7598d01a7af8937", size = 14617944, upload-time = "2026-04-13T02:45:44.954Z" }, - { url = "https://files.pythonhosted.org/packages/0c/cf/5a61ceec3fc133e0f559d1e1f9adf4150abdbc2ad8eb831ec26fc8459196/mypy-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:14911a115c73608f155f648b978c5055d16ff974e6b1b5512d7fedf4fa8b15c6", size = 14918205, upload-time = "2026-04-13T02:45:42.653Z" }, - { url = "https://files.pythonhosted.org/packages/6f/80/afb1c665e9c426c78e4711cce04e446b645867bfb97936158886103c1648/mypy-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:76d9b4c992cca3331d9793ef197ae360ea44953cf35beb2526e95b9e074f2866", size = 10823344, upload-time = "2026-04-13T02:46:07.607Z" }, - { url = "https://files.pythonhosted.org/packages/11/68/7ad64b49b7663c88fef76a2ac689ea73e17804832ac4cb5416bcff17775b/mypy-1.20.1-cp310-cp310-win_arm64.whl", hash = "sha256:b408722f80be44845da555671a5ef3a0c63f51ca5752b0c20e992dc9c0fbd3cd", size = 9760694, upload-time = "2026-04-13T02:46:49.369Z" }, - { url = "https://files.pythonhosted.org/packages/82/0d/555ab7453cc4a4a8643b7f21c842b1a84c36b15392061ae7b052ee119320/mypy-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c01eb9bac2c6a962d00f9d23421cd2913840e65bba365167d057bd0b4171a92e", size = 14336012, upload-time = "2026-04-13T02:45:39.935Z" }, - { url = "https://files.pythonhosted.org/packages/57/26/85a28893f7db8a16ebb41d1e9dfcb4475844d06a88480b6639e32a74d6ef/mypy-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55d12ddbd8a9cac5b276878bd534fa39fff5bf543dc6ae18f25d30c8d7d27fca", size = 13224636, upload-time = "2026-04-13T02:45:49.659Z" }, - { url = "https://files.pythonhosted.org/packages/93/41/bd4cd3c2caeb6c448b669222b8cfcbdee4a03b89431527b56fca9e56b6f3/mypy-1.20.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0aa322c1468b6cdfc927a44ce130f79bb44bcd34eb4a009eb9f96571fd80955", size = 13663471, upload-time = "2026-04-13T02:46:20.276Z" }, - { url = "https://files.pythonhosted.org/packages/3e/56/7ee8c471e10402d64b6517ae10434541baca053cffd81090e4097d5609d4/mypy-1.20.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f8bc95899cf676b6e2285779a08a998cc3a7b26f1026752df9d2741df3c79e8", size = 14532344, upload-time = "2026-04-13T02:46:44.205Z" }, - { url = "https://files.pythonhosted.org/packages/b5/95/b37d1fa859a433f6156742e12f62b0bb75af658544fb6dada9363918743a/mypy-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:47c2b90191a870a04041e910277494b0d92f0711be9e524d45c074fe60c00b65", size = 14776670, upload-time = "2026-04-13T02:45:52.481Z" }, - { url = "https://files.pythonhosted.org/packages/03/77/b302e4cb0b80d2bdf6bf4fce5864bb4cbfa461f7099cea544eaf2457df78/mypy-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:9857dc8d2ec1a392ffbda518075beb00ac58859979c79f9e6bdcb7277082c2f2", size = 10816524, upload-time = "2026-04-13T02:45:37.711Z" }, - { url = "https://files.pythonhosted.org/packages/7f/21/d969d7a68eb964993ebcc6170d5ecaf0cf65830c58ac3344562e16dc42a9/mypy-1.20.1-cp311-cp311-win_arm64.whl", hash = "sha256:09d8df92bb25b6065ab91b178da843dda67b33eb819321679a6e98a907ce0e10", size = 9750419, upload-time = "2026-04-13T02:45:08.542Z" }, - { url = "https://files.pythonhosted.org/packages/69/1b/75a7c825a02781ca10bc2f2f12fba2af5202f6d6005aad8d2d1f264d8d78/mypy-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:36ee2b9c6599c230fea89bbd79f401f9f9f8e9fcf0c777827789b19b7da90f51", size = 14494077, upload-time = "2026-04-13T02:45:55.085Z" }, - { url = "https://files.pythonhosted.org/packages/b0/54/5e5a569ea5c2b4d48b729fb32aa936eeb4246e4fc3e6f5b3d36a2dfbefb9/mypy-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fba3fb0968a7b48806b0c90f38d39296f10766885a94c83bd21399de1e14eb28", size = 13319495, upload-time = "2026-04-13T02:45:29.674Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a4/a1945b19f33e91721b59deee3abb484f2fa5922adc33bb166daf5325d76d/mypy-1.20.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef1415a637cd3627d6304dfbeddbadd21079dafc2a8a753c477ce4fc0c2af54f", size = 13696948, upload-time = "2026-04-13T02:46:15.006Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c6/75e969781c2359b2f9c15b061f28ec6d67c8b61865ceda176e85c8e7f2de/mypy-1.20.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef3461b1ad5cd446e540016e90b5984657edda39f982f4cc45ca317b628f5a37", size = 14706744, upload-time = "2026-04-13T02:46:00.482Z" }, - { url = "https://files.pythonhosted.org/packages/a8/6e/b221b1de981fc4262fe3e0bf9ec272d292dfe42394a689c2d49765c144c4/mypy-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:542dd63c9e1339b6092eb25bd515f3a32a1453aee8c9521d2ddb17dacd840237", size = 14949035, upload-time = "2026-04-13T02:45:06.021Z" }, - { url = "https://files.pythonhosted.org/packages/ca/4b/298ba2de0aafc0da3ff2288da06884aae7ba6489bc247c933f87847c41b3/mypy-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:1d55c7cd8ca22e31f93af2a01160a9e95465b5878de23dba7e48116052f20a8d", size = 10883216, upload-time = "2026-04-13T02:45:47.232Z" }, - { url = "https://files.pythonhosted.org/packages/c7/f9/5e25b8f0b8cb92f080bfed9c21d3279b2a0b6a601cdca369a039ba84789d/mypy-1.20.1-cp312-cp312-win_arm64.whl", hash = "sha256:f5b84a79070586e0d353ee07b719d9d0a4aa7c8ee90c0ea97747e98cbe193019", size = 9814299, upload-time = "2026-04-13T02:45:21.934Z" }, - { url = "https://files.pythonhosted.org/packages/21/e8/ef0991aa24c8f225df10b034f3c2681213cb54cf247623c6dec9a5744e70/mypy-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f3886c03e40afefd327bd70b3f634b39ea82e87f314edaa4d0cce4b927ddcc1", size = 14500739, upload-time = "2026-04-13T02:46:05.442Z" }, - { url = "https://files.pythonhosted.org/packages/23/73/416ebec3047636ed89fa871dc8c54bf05e9e20aa9499da59790d7adb312d/mypy-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e860eb3904f9764e83bafd70c8250bdffdc7dde6b82f486e8156348bf7ceb184", size = 13314735, upload-time = "2026-04-13T02:46:47.154Z" }, - { url = "https://files.pythonhosted.org/packages/10/1e/1505022d9c9ac2e014a384eb17638fb37bf8e9d0a833ea60605b66f8f7ba/mypy-1.20.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4b5aac6e785719da51a84f5d09e9e843d473170a9045b1ea7ea1af86225df4b", size = 13704356, upload-time = "2026-04-13T02:45:19.773Z" }, - { url = "https://files.pythonhosted.org/packages/98/91/275b01f5eba5c467a3318ec214dd865abb66e9c811231c8587287b92876a/mypy-1.20.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f37b6cd0fe2ad3a20f05ace48ca3523fc52ff86940e34937b439613b6854472e", size = 14696420, upload-time = "2026-04-13T02:45:24.205Z" }, - { url = "https://files.pythonhosted.org/packages/a1/57/b3779e134e1b7250d05f874252780d0a88c068bc054bcff99ca20a3a2986/mypy-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4bbb0f6b54ce7cc350ef4a770650d15fa70edd99ad5267e227133eda9c94218", size = 14936093, upload-time = "2026-04-13T02:45:32.087Z" }, - { url = "https://files.pythonhosted.org/packages/be/33/81b64991b0f3f278c3b55c335888794af190b2d59031a5ad1401bcb69f1e/mypy-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:c3dc20f8ec76eecd77148cdd2f1542ed496e51e185713bf488a414f862deb8f2", size = 10889659, upload-time = "2026-04-13T02:46:02.926Z" }, - { url = "https://files.pythonhosted.org/packages/1b/fd/7adcb8053572edf5ef8f3db59599dfeeee3be9cc4c8c97e2d28f66f42ac5/mypy-1.20.1-cp313-cp313-win_arm64.whl", hash = "sha256:a9d62bbac5d6d46718e2b0330b25e6264463ed832722b8f7d4440ff1be3ca895", size = 9815515, upload-time = "2026-04-13T02:46:32.103Z" }, - { url = "https://files.pythonhosted.org/packages/40/cd/db831e84c81d57d4886d99feee14e372f64bbec6a9cb1a88a19e243f2ef5/mypy-1.20.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:12927b9c0ed794daedcf1dab055b6c613d9d5659ac511e8d936d96f19c087d12", size = 14483064, upload-time = "2026-04-13T02:45:26.901Z" }, - { url = "https://files.pythonhosted.org/packages/d5/82/74e62e7097fa67da328ac8ece8de09133448c04d20ddeaeba251a3000f01/mypy-1.20.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:752507dd481e958b2c08fc966d3806c962af5a9433b5bf8f3bdd7175c20e34fe", size = 13335694, upload-time = "2026-04-13T02:46:12.514Z" }, - { url = "https://files.pythonhosted.org/packages/74/c4/97e9a0abe4f3cdbbf4d079cb87a03b786efeccf5bf2b89fe4f96939ab2e6/mypy-1.20.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c614655b5a065e56274c6cbbe405f7cf7e96c0654db7ba39bc680238837f7b08", size = 13726365, upload-time = "2026-04-13T02:45:17.422Z" }, - { url = "https://files.pythonhosted.org/packages/d7/aa/a19d884a8d28fcd3c065776323029f204dbc774e70ec9c85eba228b680de/mypy-1.20.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c3f6221a76f34d5100c6d35b3ef6b947054123c3f8d6938a4ba00b1308aa572", size = 14693472, upload-time = "2026-04-13T02:46:41.253Z" }, - { url = "https://files.pythonhosted.org/packages/84/44/cc9324bd21cf786592b44bf3b5d224b3923c1230ec9898d508d00241d465/mypy-1.20.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4bdfc06303ac06500af71ea0cdbe995c502b3c9ba32f3f8313523c137a25d1b6", size = 14919266, upload-time = "2026-04-13T02:46:28.37Z" }, - { url = "https://files.pythonhosted.org/packages/6e/dc/779abb25a8c63e8f44bf5a336217fa92790fa17e0c40e0c725d10cb01bbd/mypy-1.20.1-cp314-cp314-win_amd64.whl", hash = "sha256:0131edd7eba289973d1ba1003d1a37c426b85cdef76650cd02da6420898a5eb3", size = 11049713, upload-time = "2026-04-13T02:45:57.673Z" }, - { url = "https://files.pythonhosted.org/packages/28/08/4172be2ad7de9119b5a92ca36abbf641afdc5cb1ef4ae0c3a8182f29674f/mypy-1.20.1-cp314-cp314-win_arm64.whl", hash = "sha256:33f02904feb2c07e1fdf7909026206396c9deeb9e6f34d466b4cfedb0aadbbe4", size = 9999819, upload-time = "2026-04-13T02:46:35.039Z" }, - { url = "https://files.pythonhosted.org/packages/2d/af/af9e46b0c8eabbce9fc04a477564170f47a1c22b308822282a59b7ff315f/mypy-1.20.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:168472149dd8cc505c98cefd21ad77e4257ed6022cd5ed2fe2999bed56977a5a", size = 15547508, upload-time = "2026-04-13T02:46:25.588Z" }, - { url = "https://files.pythonhosted.org/packages/a7/cd/39c9e4ad6ba33e069e5837d772a9e6c304b4a5452a14a975d52b36444650/mypy-1.20.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eb674600309a8f22790cca883a97c90299f948183ebb210fbef6bcee07cb1986", size = 14399557, upload-time = "2026-04-13T02:46:10.021Z" }, - { url = "https://files.pythonhosted.org/packages/83/c1/3fd71bdc118ffc502bf57559c909927bb7e011f327f7bb8e0488e98a5870/mypy-1.20.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef2b2e4cc464ba9795459f2586923abd58a0055487cbe558cb538ea6e6bc142a", size = 15045789, upload-time = "2026-04-13T02:45:10.81Z" }, - { url = "https://files.pythonhosted.org/packages/8e/73/6f07ff8b57a7d7b3e6e5bf34685d17632382395c8bb53364ec331661f83e/mypy-1.20.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee461d396dd46b3f0ed5a098dbc9b8860c81c46ad44fa071afcfbc149f167c9", size = 15850795, upload-time = "2026-04-13T02:45:03.349Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e2/f7dffec1c7767078f9e9adf0c786d1fe0ff30964a77eb213c09b8b58cb76/mypy-1.20.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e364926308b3e66f1361f81a566fc1b2f8cd47fc8525e8136d4058a65a4b4f02", size = 16088539, upload-time = "2026-04-13T02:46:17.841Z" }, - { url = "https://files.pythonhosted.org/packages/1a/76/e0dee71035316e75a69d73aec2f03c39c21c967b97e277fd0ef8fd6aec66/mypy-1.20.1-cp314-cp314t-win_amd64.whl", hash = "sha256:a0c17fbd746d38c70cbc42647cfd884f845a9708a4b160a8b4f7e70d41f4d7fa", size = 12575567, upload-time = "2026-04-13T02:45:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/22/a8/7ed43c9d9c3d1468f86605e323a5d97e411a448790a00f07e779f3211a46/mypy-1.20.1-cp314-cp314t-win_arm64.whl", hash = "sha256:db2cb89654626a912efda69c0d5c1d22d948265e2069010d3dde3abf751c7d08", size = 10378823, upload-time = "2026-04-13T02:45:13.35Z" }, - { url = "https://files.pythonhosted.org/packages/d8/28/926bd972388e65a39ee98e188ccf67e81beb3aacfd5d6b310051772d974b/mypy-1.20.1-py3-none-any.whl", hash = "sha256:1aae28507f253fe82d883790d1c0a0d35798a810117c88184097fe8881052f06", size = 2636553, upload-time = "2026-04-13T02:46:30.45Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/04/af/e3d4b3e9ec91a0ff9aabfdb38692952acf49bbb899c2e4c29acb3a6da3ae/mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665", size = 3817349, upload-time = "2026-04-21T17:12:28.473Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/97/ce2502df2cecf2ef997b6c6527c4a223b92feb9e7b790cdc8dcd683f3a8a/mypy-1.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cf5a4db6dca263010e2c7bff081c89383c72d187ba2cf4c44759aac970e2f0c4", size = 14457059, upload-time = "2026-04-21T17:06:14.935Z" }, + { url = "https://files.pythonhosted.org/packages/c9/34/417ee60b822cc80c0f3dc9f495ad7fd8dbb8d8b2cf4baf22d4046d25d01d/mypy-1.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b0e817b518bff7facd7f85ea05b643ad8bdcce684cf29784987b0a7c8e1f997", size = 13346816, upload-time = "2026-04-21T17:10:41.433Z" }, + { url = "https://files.pythonhosted.org/packages/4a/85/e20951978702df58379d0bcc2e8f7ccdca4e78cd7dc66dd3ddbf9b29d517/mypy-1.20.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97d7b9a485b40f8ca425460e89bf1da2814625b2da627c0dcc6aa46c92631d14", size = 13772593, upload-time = "2026-04-21T17:08:11.24Z" }, + { url = "https://files.pythonhosted.org/packages/63/a5/5441a13259ec516c56fd5de0fd96a69a9590ae6c5e5d3e5174aa84b97973/mypy-1.20.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e1c12f6d2db3d78b909b5f77513c11eb7f2dd2782b96a3ab6dffc7d44575c99", size = 14656635, upload-time = "2026-04-21T17:09:54.042Z" }, + { url = "https://files.pythonhosted.org/packages/3b/51/b89c69157c5e1f19fd125a65d991166a26906e7902f026f00feebbcfa2b9/mypy-1.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89dce27e142d25ffbc154c1819383b69f2e9234dc4ed4766f42e0e8cb264ab5c", size = 14943278, upload-time = "2026-04-21T17:09:15.599Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/6b0eeecfe96d7cce1d71c66b8e03cb304aa70ec11f1955dc1d6b46aca3c3/mypy-1.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd", size = 10851915, upload-time = "2026-04-21T17:06:03.5Z" }, + { url = "https://files.pythonhosted.org/packages/3c/36/6593dc88545d75fb96416184be5392da5e2a8e8c2802a8597913e16ae25c/mypy-1.20.2-cp310-cp310-win_arm64.whl", hash = "sha256:6e2b469efd811707bc530fd1effef0f5d6eebcb7fe376affae69025da4b979a2", size = 9786676, upload-time = "2026-04-21T17:07:02.035Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4d/9ebeae211caccbdaddde7ed5e31dfcf57faac66be9b11deb1dc6526c8078/mypy-1.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c", size = 14371307, upload-time = "2026-04-21T17:08:56.442Z" }, + { url = "https://files.pythonhosted.org/packages/95/d7/93473d34b61f04fac1aecc01368485c89c5c4af7a4b9a0cab5d77d04b63f/mypy-1.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3", size = 13258917, upload-time = "2026-04-21T17:05:50.978Z" }, + { url = "https://files.pythonhosted.org/packages/e2/30/3dd903e8bafb7b5f7bf87fcd58f8382086dea2aa19f0a7b357f21f63071b/mypy-1.20.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254", size = 13700516, upload-time = "2026-04-21T17:11:33.161Z" }, + { url = "https://files.pythonhosted.org/packages/07/05/c61a140aba4c729ac7bc99ae26fc627c78a6e08f5b9dd319244ea71a3d7e/mypy-1.20.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98", size = 14562889, upload-time = "2026-04-21T17:05:27.674Z" }, + { url = "https://files.pythonhosted.org/packages/fd/87/da78243742ffa8a36d98c3010f0d829f93d5da4e6786f1a1a6f2ad616502/mypy-1.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac", size = 14803844, upload-time = "2026-04-21T17:10:06.2Z" }, + { url = "https://files.pythonhosted.org/packages/37/52/10a1ddf91b40f843943a3c6db51e2df59c9e237f29d355e95eaab427461f/mypy-1.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67", size = 10846300, upload-time = "2026-04-21T17:12:23.886Z" }, + { url = "https://files.pythonhosted.org/packages/20/02/f9a4415b664c53bd34d6709be59da303abcae986dc4ac847b402edb6fa1e/mypy-1.20.2-cp311-cp311-win_arm64.whl", hash = "sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100", size = 9779498, upload-time = "2026-04-21T17:09:23.695Z" }, + { url = "https://files.pythonhosted.org/packages/71/4e/7560e4528db9e9b147e4c0f22660466bf30a0a1fe3d63d1b9d3b0fd354ee/mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b", size = 14539393, upload-time = "2026-04-21T17:07:12.52Z" }, + { url = "https://files.pythonhosted.org/packages/32/d9/34a5efed8124f5a9234f55ac6a4ced4201e2c5b81e1109c49ad23190ec8c/mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4", size = 13361642, upload-time = "2026-04-21T17:06:53.742Z" }, + { url = "https://files.pythonhosted.org/packages/d1/14/eb377acf78c03c92d566a1510cda8137348215b5335085ef662ab82ecd3a/mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6", size = 13740347, upload-time = "2026-04-21T17:12:04.73Z" }, + { url = "https://files.pythonhosted.org/packages/b9/94/7e4634a32b641aa1c112422eed1bbece61ee16205f674190e8b536f884de/mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066", size = 14734042, upload-time = "2026-04-21T17:07:43.16Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f3/f7e62395cb7f434541b4491a01149a4439e28ace4c0c632bbf5431e92d1f/mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102", size = 14964958, upload-time = "2026-04-21T17:11:00.665Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0d/47e3c3a0ec2a876e35aeac365df3cac7776c36bbd4ed18cc521e1b9d255b/mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9", size = 10911340, upload-time = "2026-04-21T17:10:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/d6/b2/6c852d72e0ea8b01f49da817fb52539993cde327e7d010e0103dc12d0dac/mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58", size = 9833947, upload-time = "2026-04-21T17:09:05.267Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c4/b93812d3a192c9bcf5df405bd2f30277cd0e48106a14d1023c7f6ed6e39b/mypy-1.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026", size = 14524670, upload-time = "2026-04-21T17:10:30.737Z" }, + { url = "https://files.pythonhosted.org/packages/f3/47/42c122501bff18eaf1e8f457f5c017933452d8acdc52918a9f59f6812955/mypy-1.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943", size = 13336218, upload-time = "2026-04-21T17:08:44.069Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/75bbc92f41725fbd585fb17b440b1119b576105df1013622983e18640a93/mypy-1.20.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517", size = 13724906, upload-time = "2026-04-21T17:08:01.02Z" }, + { url = "https://files.pythonhosted.org/packages/a1/32/4c49da27a606167391ff0c39aa955707a00edc500572e562f7c36c08a71f/mypy-1.20.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15", size = 14726046, upload-time = "2026-04-21T17:11:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/7f/fc/4e354a1bd70216359deb0c9c54847ee6b32ef78dfb09f5131ff99b494078/mypy-1.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee", size = 14955587, upload-time = "2026-04-21T17:12:16.033Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/c0f2056e9eb8f08c62cafd9715e4584b89132bdc832fcf85d27d07b5f3e5/mypy-1.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f", size = 10922681, upload-time = "2026-04-21T17:06:35.842Z" }, + { url = "https://files.pythonhosted.org/packages/e5/14/065e333721f05de8ef683d0aa804c23026bcc287446b61cac657b902ccac/mypy-1.20.2-cp313-cp313-win_arm64.whl", hash = "sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330", size = 9830560, upload-time = "2026-04-21T17:07:51.023Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d1/b4ec96b0ecc620a4443570c6e95c867903428cfcde4206518eafdd5880c3/mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30", size = 14524561, upload-time = "2026-04-21T17:06:27.325Z" }, + { url = "https://files.pythonhosted.org/packages/3a/63/d2c2ff4fa66bc49477d32dfa26e8a167ba803ea6a69c5efb416036909d30/mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924", size = 13363883, upload-time = "2026-04-21T17:11:11.239Z" }, + { url = "https://files.pythonhosted.org/packages/2a/56/983916806bf4eddeaaa2c9230903c3669c6718552a921154e1c5182c701f/mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb", size = 13742945, upload-time = "2026-04-21T17:08:34.181Z" }, + { url = "https://files.pythonhosted.org/packages/19/65/0cd9285ab010ee8214c83d67c6b49417c40d86ce46f1aa109457b5a9b8d7/mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc", size = 14706163, upload-time = "2026-04-21T17:05:15.51Z" }, + { url = "https://files.pythonhosted.org/packages/94/97/48ff3b297cafcc94d185243a9190836fb1b01c1b0918fff64e941e973cc9/mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558", size = 14938677, upload-time = "2026-04-21T17:05:39.562Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a1/1b4233d255bdd0b38a1f284feeb1c143ca508c19184964e22f8d837ec851/mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8", size = 11089322, upload-time = "2026-04-21T17:06:44.29Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/ce7ee2ba36aeb954ba50f18fa25d9c1188578654b97d02a66a15b6f09531/mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3", size = 10017775, upload-time = "2026-04-21T17:07:20.732Z" }, + { url = "https://files.pythonhosted.org/packages/4e/a1/9d93a7d0b5859af0ead82b4888b46df6c8797e1bc5e1e262a08518c6d48e/mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609", size = 15549002, upload-time = "2026-04-21T17:08:23.107Z" }, + { url = "https://files.pythonhosted.org/packages/00/d2/09a6a10ee1bf0008f6c144d9676f2ca6a12512151b4e0ad0ff6c4fac5337/mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2", size = 14401942, upload-time = "2026-04-21T17:07:31.837Z" }, + { url = "https://files.pythonhosted.org/packages/57/da/9594b75c3c019e805250bed3583bdf4443ff9e6ef08f97e39ae308cb06f2/mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c", size = 15041649, upload-time = "2026-04-21T17:09:34.653Z" }, + { url = "https://files.pythonhosted.org/packages/97/77/f75a65c278e6e8eba2071f7f5a90481891053ecc39878cc444634d892abe/mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744", size = 15864588, upload-time = "2026-04-21T17:11:44.936Z" }, + { url = "https://files.pythonhosted.org/packages/d7/46/1a4e1c66e96c1a3246ddf5403d122ac9b0a8d2b7e65730b9d6533ba7a6d3/mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6", size = 16093956, upload-time = "2026-04-21T17:10:17.683Z" }, + { url = "https://files.pythonhosted.org/packages/5a/2c/78a8851264dec38cd736ca5b8bc9380674df0dd0be7792f538916157716c/mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec", size = 12568661, upload-time = "2026-04-21T17:11:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/83/01/cd7318aa03493322ce275a0e14f4f52b8896335e4e79d4fb8153a7ad2b77/mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382", size = 10389240, upload-time = "2026-04-21T17:09:42.719Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/f23c163e25b11074188251b0b5a0342625fc1cdb6af604757174fa9acc9b/mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563", size = 2637314, upload-time = "2026-04-21T17:05:54.5Z" }, ] [[package]] @@ -1133,7 +1135,8 @@ name = "pydantic" version = "2.13.0b2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14'", + "python_full_version >= '3.15'", + "python_full_version == '3.14.*'", ] dependencies = [ { name = "annotated-types", marker = "python_full_version >= '3.14'" }, @@ -1273,7 +1276,8 @@ name = "pydantic-core" version = "2.42.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14'", + "python_full_version >= '3.15'", + "python_full_version == '3.14.*'", ] dependencies = [ { name = "typing-extensions", marker = "python_full_version >= '3.14'" }, @@ -1634,7 +1638,7 @@ all = [ { name = "chango", marker = "python_full_version >= '3.12'", specifier = "~=0.6.0" }, { name = "flaky", specifier = ">=3.8.1" }, { name = "furo", specifier = "==2025.12.19" }, - { name = "mypy", specifier = "==1.20.1" }, + { name = "mypy", specifier = "==1.20.2" }, { name = "prek" }, { name = "pydantic", marker = "python_full_version >= '3.14'", specifier = ">=2.12.0a1" }, { name = "pylint", specifier = "==4.0.5" }, @@ -1664,7 +1668,7 @@ docs = [ { name = "sphinxcontrib-mermaid", specifier = "==2.0.1" }, ] linting = [ - { name = "mypy", specifier = "==1.20.1" }, + { name = "mypy", specifier = "==1.20.2" }, { name = "prek" }, { name = "pylint", specifier = "==4.0.5" }, { name = "ruff", specifier = "==0.15.12" }, @@ -1928,7 +1932,8 @@ name = "sphinx" version = "9.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.14'", + "python_full_version >= '3.15'", + "python_full_version == '3.14.*'", "python_full_version == '3.13.*'", "python_full_version == '3.12.*'", ] From e79716ae358ab07cbfb3ffae1d15e43895276bc4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:08:53 +0000 Subject: [PATCH 20/39] Update jannekem/run-python-script-action action to v1.8 (#5224) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/type_completeness_monthly.yml | 2 +- changes/unreleased/5224.YVKcje5xUdXTjJJurmAVVD.toml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/unreleased/5224.YVKcje5xUdXTjJJurmAVVD.toml diff --git a/.github/workflows/type_completeness_monthly.yml b/.github/workflows/type_completeness_monthly.yml index 30a8a1c8a3b..f8711035915 100644 --- a/.github/workflows/type_completeness_monthly.yml +++ b/.github/workflows/type_completeness_monthly.yml @@ -18,7 +18,7 @@ jobs: python-version: 3.12 pyright-version: ~=1.1.367 - name: Check Output - uses: jannekem/run-python-script-action@bbfca66c612a28f3eeca0ae40e1f810265e2ea68 # v1.7 + uses: jannekem/run-python-script-action@9d8e2e0878d575fb6073277f38ce3f10ebf4f059 # v1.8 env: TYPE_COMPLETENESS: ${{ steps.pyright-type-completeness.outputs.base-completeness-score }} with: diff --git a/changes/unreleased/5224.YVKcje5xUdXTjJJurmAVVD.toml b/changes/unreleased/5224.YVKcje5xUdXTjJJurmAVVD.toml new file mode 100644 index 00000000000..9a5abdba578 --- /dev/null +++ b/changes/unreleased/5224.YVKcje5xUdXTjJJurmAVVD.toml @@ -0,0 +1,5 @@ +internal = "Update jannekem/run-python-script-action action to v1.8" +[[pull_requests]] +uid = "5224" +author_uids = ["renovate[bot]"] +closes_threads = [] From 6d9e17a5dcca7403d522eccd4945a7862941ec98 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:12:18 +0000 Subject: [PATCH 21/39] Update dependency astral-sh/uv to v0.11.8 (#5221) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/copilot-setup-steps.yml | 2 +- changes/unreleased/5221.UEBLb7KFNwZkZ5fC3CyDBD.toml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/unreleased/5221.UEBLb7KFNwZkZ5fC3CyDBD.toml diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 6f6eb5c4397..9dc97e078c1 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -34,7 +34,7 @@ jobs: uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 with: # Install a specific version of uv. - version: "0.11.2" + version: "0.11.8" # Install 3.13: python-version: 3.13 diff --git a/changes/unreleased/5221.UEBLb7KFNwZkZ5fC3CyDBD.toml b/changes/unreleased/5221.UEBLb7KFNwZkZ5fC3CyDBD.toml new file mode 100644 index 00000000000..10aa76f8085 --- /dev/null +++ b/changes/unreleased/5221.UEBLb7KFNwZkZ5fC3CyDBD.toml @@ -0,0 +1,5 @@ +internal = "Update dependency astral-sh/uv to v0.11.8" +[[pull_requests]] +uid = "5221" +author_uids = ["renovate[bot]"] +closes_threads = [] From 7db5b4314a0527e391bba18864f6357270348a8f Mon Sep 17 00:00:00 2001 From: Aleksandr Kovalko Date: Mon, 4 May 2026 14:48:38 +0200 Subject: [PATCH 22/39] Correct isinstance check variable in _MergedFilter.__init__ (#5215) --- changes/unreleased/5125.filter-fix.toml | 5 +++++ src/telegram/ext/filters.py | 2 +- tests/ext/test_filters.py | 12 ++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 changes/unreleased/5125.filter-fix.toml diff --git a/changes/unreleased/5125.filter-fix.toml b/changes/unreleased/5125.filter-fix.toml new file mode 100644 index 00000000000..b76b3683e66 --- /dev/null +++ b/changes/unreleased/5125.filter-fix.toml @@ -0,0 +1,5 @@ +bugfixes = "Fixed incorrect isinstance check in class telegram.ext.filters._MergedFilter for or_filter." +[[pull_requests]] +uid = "5125" +author_uids = ["gistrec"] +closes_threads = [] diff --git a/src/telegram/ext/filters.py b/src/telegram/ext/filters.py index d099b0c5313..62901fa7a1c 100644 --- a/src/telegram/ext/filters.py +++ b/src/telegram/ext/filters.py @@ -430,7 +430,7 @@ def __init__( ): self.data_filter = True self.or_filter = or_filter - if self.or_filter and not isinstance(self.and_filter, bool) and self.or_filter.data_filter: + if self.or_filter and not isinstance(self.or_filter, bool) and self.or_filter.data_filter: self.data_filter = True @staticmethod diff --git a/tests/ext/test_filters.py b/tests/ext/test_filters.py index a045d2a6dd7..5eccc49a6c3 100644 --- a/tests/ext/test_filters.py +++ b/tests/ext/test_filters.py @@ -276,6 +276,18 @@ def test_filters_merged_with_regex(self, update): result = (filters.COMMAND | filters.Regex(r"linked param")).check_update(update) assert result is True + def test_merged_filter_or_data_filter(self, update): + sre_type = type(re.match("", "")) + update.message.text = "deep-linked param" + update.message.entities = [] + # COMMAND doesn't match; or_filter (Regex, a data filter) should return match data + result = (filters.COMMAND | filters.Regex(r"linked param")).check_update(update) + assert result + assert isinstance(result, dict) + matches = result["matches"] + assert isinstance(matches, list) + assert all(type(res) is sre_type for res in matches) + def test_regex_complex_merges(self, update, message_origin_user): sre_type = type(re.match("", "")) update.message.text = "test it out" From 85294fb58d62fe8410d925045aa7fbba3293ee70 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Fri, 8 May 2026 16:07:39 -0400 Subject: [PATCH 23/39] Full Support for Bot API 9.6 (#5196) Co-authored-by: OuYoung <212045739+ouyooung@users.noreply.github.com> --- README.rst | 4 +- .../5196.7keq7yJhXbMb9RyShLHz4D.toml | 7 + docs/source/inclusions/bot_methods.rst | 7 + docs/source/telegram.at-tree.rst | 7 +- docs/source/telegram.ext.handlers-tree.rst | 1 + .../telegram.ext.managedbotupdatedhandler.rst | 6 + ...legram.keyboardbuttonrequestmanagedbot.rst | 6 + docs/source/telegram.managedbotcreated.rst | 6 + docs/source/telegram.managedbotupdated.rst | 6 + docs/source/telegram.polloptionadded.rst | 6 + docs/source/telegram.polloptiondeleted.rst | 6 + .../telegram.preparedkeyboardbutton.rst | 6 + src/telegram/__init__.py | 23 +- src/telegram/_bot.py | 230 ++++++++- src/telegram/_chat.py | 17 + src/telegram/_inputchecklist.py | 10 +- src/telegram/_keyboardbutton.py | 27 +- src/telegram/_keyboardbuttonrequest.py | 50 ++ src/telegram/_managedbot.py | 123 +++++ src/telegram/_message.py | 74 ++- src/telegram/_poll.py | 465 +++++++++++++++++- src/telegram/_preparedkeyboardbutton.py | 54 ++ src/telegram/_reply.py | 31 +- src/telegram/_update.py | 30 +- src/telegram/_user.py | 63 ++- src/telegram/_utils/types.py | 7 +- src/telegram/constants.py | 30 +- src/telegram/ext/__init__.py | 2 + src/telegram/ext/_extbot.py | 85 ++++ .../ext/_handlers/managedbotupdatedhandler.py | 104 ++++ src/telegram/ext/filters.py | 34 ++ tests/_files/test_audio.py | 2 +- tests/_files/test_photo.py | 2 +- tests/_files/test_video.py | 2 +- tests/_files/test_videonote.py | 2 +- tests/auxil/dummy_objects.py | 2 + tests/ext/test_filters.py | 15 + tests/ext/test_managedbotupdatedhandler.py | 177 +++++++ tests/test_bot.py | 75 ++- tests/test_constants.py | 1 + tests/test_keyboardbutton.py | 10 + tests/test_keyboardbuttonrequestmanagedbot.py | 99 ++++ tests/test_managedbot.py | 196 ++++++++ tests/test_message.py | 11 + tests/test_official/exceptions.py | 13 +- tests/test_poll.py | 365 +++++++++++++- tests/test_preparedkeyboardbutton.py | 62 +++ tests/test_reply.py | 5 + tests/test_update.py | 9 + tests/test_user.py | 20 + 50 files changed, 2532 insertions(+), 63 deletions(-) create mode 100644 changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml create mode 100644 docs/source/telegram.ext.managedbotupdatedhandler.rst create mode 100644 docs/source/telegram.keyboardbuttonrequestmanagedbot.rst create mode 100644 docs/source/telegram.managedbotcreated.rst create mode 100644 docs/source/telegram.managedbotupdated.rst create mode 100644 docs/source/telegram.polloptionadded.rst create mode 100644 docs/source/telegram.polloptiondeleted.rst create mode 100644 docs/source/telegram.preparedkeyboardbutton.rst create mode 100644 src/telegram/_managedbot.py create mode 100644 src/telegram/_preparedkeyboardbutton.py create mode 100644 src/telegram/ext/_handlers/managedbotupdatedhandler.py create mode 100644 tests/ext/test_managedbotupdatedhandler.py create mode 100644 tests/test_keyboardbuttonrequestmanagedbot.py create mode 100644 tests/test_managedbot.py create mode 100644 tests/test_preparedkeyboardbutton.py diff --git a/README.rst b/README.rst index bfdedc88a0b..84f80c9bb4f 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ :target: https://pypi.org/project/python-telegram-bot/ :alt: Supported Python versions -.. image:: https://img.shields.io/badge/Bot%20API-9.5-blue?logo=telegram +.. image:: https://img.shields.io/badge/Bot%20API-9.6-blue?logo=telegram :target: https://core.telegram.org/bots/api-changelog :alt: Supported Bot API version @@ -77,7 +77,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 **9.5** are natively supported by this library. +All types and methods of the Telegram Bot API **9.6** are natively supported by this library. In addition, Bot API functionality not yet natively included can still be used as described `in our wiki `_. Notable Features diff --git a/changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml b/changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml new file mode 100644 index 00000000000..9a1ba1501e1 --- /dev/null +++ b/changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml @@ -0,0 +1,7 @@ +features = "Full Support for Bot API 9.6" + +pull_requests = [ + { uid = "5196", author_uid = "harshil21" }, + { uid = "5202", author_uid = "ouyooung" }, + { uid = "5197", author_uid = "harshil21" }, +] diff --git a/docs/source/inclusions/bot_methods.rst b/docs/source/inclusions/bot_methods.rst index c7b178dd4e9..72f58dea7bd 100644 --- a/docs/source/inclusions/bot_methods.rst +++ b/docs/source/inclusions/bot_methods.rst @@ -496,12 +496,19 @@ - Used for getting information about gifts available for sending * - :meth:`~telegram.Bot.get_chat_gifts` - Used for getting information about gifts owned and hosted by a chat + * - :meth:`~telegram.Bot.get_managed_bot_token` + - Used for getting the token of a managed bot + * - :meth:`~telegram.Bot.replace_managed_bot_token` + - Used for replacing the token of a managed bot * - :meth:`~telegram.Bot.get_me` - Used for getting basic information about the bot * - :meth:`~telegram.Bot.get_user_gifts` - Used for getting information about gifts owned and hosted by a user * - :meth:`~telegram.Bot.save_prepared_inline_message` - Used for storing a message to be sent by a user of a Mini App + * - :meth:`~telegram.Bot.save_prepared_keyboard_button` + - Used for saving a keyboard button to be used in a Mini App + .. raw:: html diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index fb960954fd3..25d9c269838 100644 --- a/docs/source/telegram.at-tree.rst +++ b/docs/source/telegram.at-tree.rst @@ -116,11 +116,14 @@ Available Types telegram.keyboardbutton telegram.keyboardbuttonpolltype telegram.keyboardbuttonrequestchat + telegram.keyboardbuttonrequestmanagedbot telegram.keyboardbuttonrequestusers telegram.linkpreviewoptions telegram.location telegram.locationaddress telegram.loginurl + telegram.managedbotcreated + telegram.managedbotupdated telegram.maybeinaccessiblemessage telegram.menubutton telegram.menubuttoncommands @@ -151,7 +154,9 @@ Available Types telegram.photosize telegram.poll telegram.pollanswer - telegram.polloption + telegram.polloptionadded + telegram.polloptiondeleted + telegram.preparedkeyboardbutton telegram.proximityalerttriggered telegram.reactioncount telegram.reactiontype diff --git a/docs/source/telegram.ext.handlers-tree.rst b/docs/source/telegram.ext.handlers-tree.rst index 72e0d824c53..690aca1c537 100644 --- a/docs/source/telegram.ext.handlers-tree.rst +++ b/docs/source/telegram.ext.handlers-tree.rst @@ -16,6 +16,7 @@ Handlers telegram.ext.conversationhandler telegram.ext.filters telegram.ext.inlinequeryhandler + telegram.ext.managedbotupdatedhandler telegram.ext.messagehandler telegram.ext.messagereactionhandler telegram.ext.paidmediapurchasedhandler diff --git a/docs/source/telegram.ext.managedbotupdatedhandler.rst b/docs/source/telegram.ext.managedbotupdatedhandler.rst new file mode 100644 index 00000000000..c59d8080389 --- /dev/null +++ b/docs/source/telegram.ext.managedbotupdatedhandler.rst @@ -0,0 +1,6 @@ +ManagedBotUpdatedHandler +======================== + +.. autoclass:: telegram.ext.ManagedBotUpdatedHandler + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.keyboardbuttonrequestmanagedbot.rst b/docs/source/telegram.keyboardbuttonrequestmanagedbot.rst new file mode 100644 index 00000000000..2840249e15d --- /dev/null +++ b/docs/source/telegram.keyboardbuttonrequestmanagedbot.rst @@ -0,0 +1,6 @@ +KeyboardButtonRequestManagedBot +=============================== + +.. autoclass:: telegram.KeyboardButtonRequestManagedBot + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.managedbotcreated.rst b/docs/source/telegram.managedbotcreated.rst new file mode 100644 index 00000000000..e137de86830 --- /dev/null +++ b/docs/source/telegram.managedbotcreated.rst @@ -0,0 +1,6 @@ +ManagedBotCreated +================= + +.. autoclass:: telegram.ManagedBotCreated + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.managedbotupdated.rst b/docs/source/telegram.managedbotupdated.rst new file mode 100644 index 00000000000..f47bd6b60cc --- /dev/null +++ b/docs/source/telegram.managedbotupdated.rst @@ -0,0 +1,6 @@ +ManagedBotUpdated +================= + +.. autoclass:: telegram.ManagedBotUpdated + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.polloptionadded.rst b/docs/source/telegram.polloptionadded.rst new file mode 100644 index 00000000000..fa4638c38f0 --- /dev/null +++ b/docs/source/telegram.polloptionadded.rst @@ -0,0 +1,6 @@ +PollOptionAdded +=============== + +.. autoclass:: telegram.PollOptionAdded + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.polloptiondeleted.rst b/docs/source/telegram.polloptiondeleted.rst new file mode 100644 index 00000000000..74f317f5604 --- /dev/null +++ b/docs/source/telegram.polloptiondeleted.rst @@ -0,0 +1,6 @@ +PollOptionDeleted +================= + +.. autoclass:: telegram.PollOptionDeleted + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.preparedkeyboardbutton.rst b/docs/source/telegram.preparedkeyboardbutton.rst new file mode 100644 index 00000000000..7a1f40481b5 --- /dev/null +++ b/docs/source/telegram.preparedkeyboardbutton.rst @@ -0,0 +1,6 @@ +PreparedKeyboardButton +====================== + +.. autoclass:: telegram.PreparedKeyboardButton + :members: + :show-inheritance: \ No newline at end of file diff --git a/src/telegram/__init__.py b/src/telegram/__init__.py index fad336e0549..362f3253198 100644 --- a/src/telegram/__init__.py +++ b/src/telegram/__init__.py @@ -177,12 +177,15 @@ "KeyboardButton", "KeyboardButtonPollType", "KeyboardButtonRequestChat", + "KeyboardButtonRequestManagedBot", "KeyboardButtonRequestUsers", "LabeledPrice", "LinkPreviewOptions", "Location", "LocationAddress", "LoginUrl", + "ManagedBotCreated", + "ManagedBotUpdated", "MaskPosition", "MaybeInaccessibleMessage", "MenuButton", @@ -229,8 +232,11 @@ "Poll", "PollAnswer", "PollOption", + "PollOptionAdded", + "PollOptionDeleted", "PreCheckoutQuery", "PreparedInlineMessage", + "PreparedKeyboardButton", "ProximityAlertTriggered", "ReactionCount", "ReactionType", @@ -498,9 +504,14 @@ from ._inline.preparedinlinemessage import PreparedInlineMessage from ._keyboardbutton import KeyboardButton from ._keyboardbuttonpolltype import KeyboardButtonPollType -from ._keyboardbuttonrequest import KeyboardButtonRequestChat, KeyboardButtonRequestUsers +from ._keyboardbuttonrequest import ( + KeyboardButtonRequestChat, + KeyboardButtonRequestManagedBot, + KeyboardButtonRequestUsers, +) from ._linkpreviewoptions import LinkPreviewOptions from ._loginurl import LoginUrl +from ._managedbot import ManagedBotCreated, ManagedBotUpdated from ._menubutton import MenuButton, MenuButtonCommands, MenuButtonDefault, MenuButtonWebApp from ._message import InaccessibleMessage, MaybeInaccessibleMessage, Message from ._messageautodeletetimerchanged import MessageAutoDeleteTimerChanged @@ -564,7 +575,15 @@ RevenueWithdrawalStateSucceeded, ) from ._payment.successfulpayment import SuccessfulPayment -from ._poll import InputPollOption, Poll, PollAnswer, PollOption +from ._poll import ( + InputPollOption, + Poll, + PollAnswer, + PollOption, + PollOptionAdded, + PollOptionDeleted, +) +from ._preparedkeyboardbutton import PreparedKeyboardButton from ._proximityalerttriggered import ProximityAlertTriggered from ._reaction import ( ReactionCount, diff --git a/src/telegram/_bot.py b/src/telegram/_bot.py index 1050f05baf7..d9d1f83b069 100644 --- a/src/telegram/_bot.py +++ b/src/telegram/_bot.py @@ -78,6 +78,7 @@ from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton from telegram._inline.preparedinlinemessage import PreparedInlineMessage from telegram._inputchecklist import InputChecklist +from telegram._keyboardbutton import KeyboardButton from telegram._menubutton import MenuButton from telegram._message import Message from telegram._messageid import MessageId @@ -85,6 +86,7 @@ from telegram._payment.stars.staramount import StarAmount from telegram._payment.stars.startransactions import StarTransactions from telegram._poll import InputPollOption, Poll +from telegram._preparedkeyboardbutton import PreparedKeyboardButton from telegram._reaction import ReactionType, ReactionTypeCustomEmoji, ReactionTypeEmoji from telegram._reply import ReplyParameters from telegram._sentwebappmessage import SentWebAppMessage @@ -102,6 +104,7 @@ from telegram._utils.types import ( BaseUrl, CorrectOptionID, + CorrectOptionIds, FileInput, JSONDict, ODVInput, @@ -114,7 +117,7 @@ from telegram.request import BaseRequest, RequestData from telegram.request._httpxrequest import HTTPXRequest from telegram.request._requestparameter import RequestParameter -from telegram.warnings import PTBUserWarning +from telegram.warnings import PTBDeprecationWarning, PTBUserWarning if TYPE_CHECKING: from telegram import ( @@ -7597,6 +7600,8 @@ async def send_poll( is_anonymous: bool | None = None, type: str | None = None, # pylint: disable=redefined-builtin allows_multiple_answers: bool | None = None, + # tags: deprecated in NEXT.VERSION, to be removed + # replaced by `correct_option_ids` correct_option_id: CorrectOptionID | None = None, is_closed: bool | None = None, disable_notification: ODVInput[bool] = DEFAULT_NONE, @@ -7614,6 +7619,14 @@ async def send_poll( question_entities: Sequence["MessageEntity"] | None = None, message_effect_id: str | None = None, allow_paid_broadcast: bool | None = None, + allows_revoting: bool | None = None, + allow_adding_options: bool | None = None, + hide_results_until_closes: bool | None = None, + correct_option_ids: CorrectOptionIds | None = None, + description: str | None = None, + description_parse_mode: str | None = None, + description_entities: Sequence["MessageEntity"] | None = None, + shuffle_options: bool | None = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: int | None = None, @@ -7649,9 +7662,13 @@ async def send_poll( type (:obj:`str`, optional): Poll type, :tg-const:`telegram.Poll.QUIZ` or :tg-const:`telegram.Poll.REGULAR`, defaults to :tg-const:`telegram.Poll.REGULAR`. allows_multiple_answers (:obj:`bool`, optional): :obj:`True`, if the poll allows - multiple answers, ignored for polls in quiz mode, defaults to :obj:`False`. + multiple answers, defaults to :obj:`False`. correct_option_id (:obj:`int`, optional): 0-based identifier of the correct answer option, required for polls in quiz mode. + + .. deprecated:: NEXT.VERSION + Bot API 9.6 replaces this with :paramref:`correct_option_ids` instead. + explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-:tg-const:`telegram.Poll.MAX_EXPLANATION_LENGTH` characters with at most @@ -7716,6 +7733,43 @@ async def send_poll( allow_paid_broadcast (:obj:`bool`, optional): |allow_paid_broadcast| .. versionadded:: 21.7 + allows_revoting (:obj:`bool`, optional): :obj:`True`, if the poll allows to + change the chosen answer options, defaults to :obj:`False` + for quizzes and to :obj:`True` for regular polls + + .. versionadded:: NEXT.VERSION + allow_adding_options (:obj:`bool`, optional): :obj:`True`, if answer options can be + added to the poll after creation; not supported for anonymous polls and quizzes + + .. versionadded:: NEXT.VERSION + hide_results_until_closes (:obj:`bool`, optional): :obj:`True`, if poll results + must be shown only after the poll closes + + .. versionadded:: NEXT.VERSION + correct_option_ids (Sequence[:class:`int`], optional): A list of monotonically + increasing 0-based identifiers of the correct answer options, + required for polls in quiz mode. + + .. versionadded:: NEXT.VERSION + description (:obj:`str`, optional): Description of the poll to be sent, + 0-:tg-const:`telegram.Poll.MAX_DESCRIPTION_CHARACTERS` characters + after entities parsing. + + .. versionadded:: NEXT.VERSION + description_parse_mode (:obj:`str`, optional): Mode for parsing entities + in the poll description. See the constants + in :class:`telegram.constants.ParseMode` + + .. versionadded:: NEXT.VERSION + description_entities (Sequence[:class:`telegram.MessageEntity`], optional): A + JSON-serialized list of special entities that appear in the poll description, + which can be specified instead of :paramref:`description_parse_mode` + + .. versionadded:: NEXT.VERSION + shuffle_options (:obj:`bool`, optional): :obj:`True`, if the poll options must be + shown in random order + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -7744,6 +7798,19 @@ async def send_poll( :class:`telegram.error.TelegramError` """ + + if correct_option_id is not None: + warn( + PTBDeprecationWarning( + version="NEXT.VERSION", + message="Bot API 9.6 deprecated `correct_option_id` in favour of " + "`correct_option_ids`, please use that.", + ), + stacklevel=2, + ) + if correct_option_ids is None: + correct_option_ids = [correct_option_id] + data: JSONDict = { "chat_id": chat_id, "question": question, @@ -7755,8 +7822,15 @@ async def send_poll( "is_anonymous": is_anonymous, "type": type, "allows_multiple_answers": allows_multiple_answers, - "correct_option_id": correct_option_id, + "allow_adding_options": allow_adding_options, + "allows_revoting": allows_revoting, + "shuffle_options": shuffle_options, + "hide_results_until_closes": hide_results_until_closes, + "correct_option_ids": correct_option_ids, "is_closed": is_closed, + "description": description, + "description_parse_mode": description_parse_mode, + "description_entities": description_entities, "explanation": explanation, "explanation_entities": explanation_entities, "open_period": open_period, @@ -9874,14 +9948,16 @@ async def gift_premium_subscription( `formatting options `__ for more details. Entities other than :attr:`~MessageEntity.BOLD`, :attr:`~MessageEntity.ITALIC`, :attr:`~MessageEntity.UNDERLINE`, - :attr:`~MessageEntity.STRIKETHROUGH`, :attr:`~MessageEntity.SPOILER`, and - :attr:`~MessageEntity.CUSTOM_EMOJI` are ignored. + :attr:`~MessageEntity.STRIKETHROUGH`, :attr:`~MessageEntity.SPOILER` + :attr:`~MessageEntity.CUSTOM_EMOJI`, and :attr:`~MessageEntity.DATE_TIME` are + ignored. text_entities (Sequence[:class:`telegram.MessageEntity`], optional): A list of special entities that appear in the gift text. It can be specified instead of :paramref:`text_parse_mode`. Entities other than :attr:`~MessageEntity.BOLD`, :attr:`~MessageEntity.ITALIC`, :attr:`~MessageEntity.UNDERLINE`, - :attr:`~MessageEntity.STRIKETHROUGH`, :attr:`~MessageEntity.SPOILER`, and - :attr:`~MessageEntity.CUSTOM_EMOJI` are ignored. + :attr:`~MessageEntity.STRIKETHROUGH`, :attr:`~MessageEntity.SPOILER`, + :attr:`~MessageEntity.CUSTOM_EMOJI`, and :attr:`~MessageEntity.DATE_TIME` are + ignored. Returns: :obj:`bool`: On success, :obj:`True` is returned. @@ -11362,14 +11438,16 @@ async def send_gift( `formatting options `__ for more details. Entities other than :attr:`~MessageEntity.BOLD`, :attr:`~MessageEntity.ITALIC`, :attr:`~MessageEntity.UNDERLINE`, - :attr:`~MessageEntity.STRIKETHROUGH`, :attr:`~MessageEntity.SPOILER`, and - :attr:`~MessageEntity.CUSTOM_EMOJI` are ignored. + :attr:`~MessageEntity.STRIKETHROUGH`, :attr:`~MessageEntity.SPOILER`, + :attr:`~MessageEntity.CUSTOM_EMOJI`, and :attr:`~MessageEntity.DATE_TIME` are + ignored. text_entities (Sequence[:class:`telegram.MessageEntity`], optional): A list of special entities that appear in the gift text. It can be specified instead of :paramref:`text_parse_mode`. Entities other than :attr:`~MessageEntity.BOLD`, :attr:`~MessageEntity.ITALIC`, :attr:`~MessageEntity.UNDERLINE`, - :attr:`~MessageEntity.STRIKETHROUGH`, :attr:`~MessageEntity.SPOILER`, and - :attr:`~MessageEntity.CUSTOM_EMOJI` are ignored. + :attr:`~MessageEntity.STRIKETHROUGH`, :attr:`~MessageEntity.SPOILER`, + :attr:`~MessageEntity.CUSTOM_EMOJI`, and :attr:`~MessageEntity.DATE_TIME` are + ignored. pay_for_upgrade (:obj:`bool`, optional): Pass :obj:`True` to pay for the gift upgrade from the bot's balance, thereby making the upgrade free for the receiver. @@ -12081,6 +12159,130 @@ async def set_chat_member_tag( api_kwargs=api_kwargs, ) + async def get_managed_bot_token( + self, + user_id: int, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> str: + """ + Use this method to get the token of a managed bot. + + .. versionadded:: NEXT.VERSION + + Args: + user_id (:obj:`int`): User identifier of the managed bot whose token will be returned. + + Returns: + :obj:`str`: The token of the managed bot. + + Raises: + :class:`telegram.error.TelegramError` + """ + data: JSONDict = { + "user_id": user_id, + } + + return await self._post( + "getManagedBotToken", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def replace_managed_bot_token( + self, + user_id: int, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> str: + """ + Use this method to revoke the current token of a managed bot and generate a new one. + + .. versionadded:: NEXT.VERSION + + Args: + user_id (:obj:`int`): User identifier of the managed bot whose token will be replaced. + + Returns: + :obj:`str`: The new token of the managed bot. + + Raises: + :class:`telegram.error.TelegramError` + """ + data: JSONDict = { + "user_id": user_id, + } + + return await self._post( + "replaceManagedBotToken", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def save_prepared_keyboard_button( + self, + user_id: int, + button: KeyboardButton, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> PreparedKeyboardButton: + """ + Stores a keyboard button that can be used by a user within a Mini App. + + .. versionadded:: NEXT.VERSION + + Args: + user_id (:obj:`int`): Unique identifier of the target user that can use the button. + button (:obj:`telegram.KeyboardButton`): An object describing the + button to be saved. The button must be of the type + :attr:`~telegram.KeyboardButton.request_users`, + :attr:`~telegram.KeyboardButton.request_chat`, or + :attr:`~telegram.KeyboardButton.request_managed_bot`. + + Returns: + :class:`telegram.PreparedKeyboardButton` + + Raises: + :class:`telegram.error.TelegramError` + """ + data: JSONDict = { + "user_id": user_id, + "button": button, + } + + return PreparedKeyboardButton.de_json( + await self._post( + "savePreparedKeyboardButton", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ), + self, + ) + def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002 """See :meth:`telegram.TelegramObject.to_dict`.""" data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name} @@ -12421,3 +12623,9 @@ def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002 """Alias for :meth:`get_user_profile_audios`""" setChatMemberTag = set_chat_member_tag """Alias for :meth:`set_chat_member_tag`""" + getManagedBotToken = get_managed_bot_token + """Alias for :meth:`get_managed_bot_token`""" + replaceManagedBotToken = replace_managed_bot_token + """Alias for :meth:`replace_managed_bot_token`""" + savePreparedKeyboardButton = save_prepared_keyboard_button + """Alias for :meth:`save_prepared_keyboard_button`""" diff --git a/src/telegram/_chat.py b/src/telegram/_chat.py index 1230bf48277..764da868e7e 100644 --- a/src/telegram/_chat.py +++ b/src/telegram/_chat.py @@ -34,6 +34,7 @@ from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import ( CorrectOptionID, + CorrectOptionIds, FileInput, JSONDict, ODVInput, @@ -2296,6 +2297,14 @@ async def send_poll( question_entities: Sequence["MessageEntity"] | None = None, message_effect_id: str | None = None, allow_paid_broadcast: bool | None = None, + shuffle_options: bool | None = None, + allows_revoting: bool | None = None, + correct_option_ids: CorrectOptionIds | None = None, + allow_adding_options: bool | None = None, + hide_results_until_closes: bool | None = None, + description: str | None = None, + description_parse_mode: str | None = None, + description_entities: Sequence["MessageEntity"] | None = None, *, reply_to_message_id: int | None = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2323,6 +2332,9 @@ async def send_poll( type=type, # pylint=pylint, allows_multiple_answers=allows_multiple_answers, correct_option_id=correct_option_id, + allows_revoting=allows_revoting, + shuffle_options=shuffle_options, + correct_option_ids=correct_option_ids, is_closed=is_closed, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, @@ -2346,6 +2358,11 @@ async def send_poll( business_connection_id=business_connection_id, question_parse_mode=question_parse_mode, question_entities=question_entities, + description=description, + description_parse_mode=description_parse_mode, + description_entities=description_entities, + hide_results_until_closes=hide_results_until_closes, + allow_adding_options=allow_adding_options, ) async def send_copy( diff --git a/src/telegram/_inputchecklist.py b/src/telegram/_inputchecklist.py index d8fd06258ca..21ae2f491f6 100644 --- a/src/telegram/_inputchecklist.py +++ b/src/telegram/_inputchecklist.py @@ -49,8 +49,8 @@ class InputChecklistTask(TelegramObject): |parse_mode| text_entities (Sequence[:class:`telegram.MessageEntity`], optional): List of special entities that appear in the text, which can be specified instead of - parse_mode. Currently, only bold, italic, underline, strikethrough, spoiler, and - custom_emoji entities are allowed. + parse_mode. Currently, only bold, italic, underline, strikethrough, spoiler, + custom_emoji, and date_time entities are allowed. Attributes: id (:obj:`int`): @@ -66,7 +66,7 @@ class InputChecklistTask(TelegramObject): text_entities (Sequence[:class:`telegram.MessageEntity`]): Optional. List of special entities that appear in the text, which can be specified instead of parse_mode. Currently, only bold, italic, underline, strikethrough, spoiler, - and custom_emoji entities are allowed. + custom_emoji, and date_time entities are allowed. """ @@ -117,7 +117,7 @@ class InputChecklist(TelegramObject): title_entities (Sequence[:class:`telegram.MessageEntity`], optional): List of special entities that appear in the title, which can be specified instead of :paramref:`parse_mode`. Currently, only bold, italic, - underline, strikethrough, spoiler, and custom_emoji entities are allowed. + underline, strikethrough, spoiler, and custom_emoji, and date_time entities are allowed tasks (Sequence[:class:`telegram.InputChecklistTask`]): List of :tg-const:`telegram.constants.InputChecklistLimit.MIN_TASK_NUMBER`\ @@ -139,7 +139,7 @@ class InputChecklist(TelegramObject): title_entities (Sequence[:class:`telegram.MessageEntity`]): Optional. List of special entities that appear in the title, which can be specified instead of :paramref:`parse_mode`. Currently, only bold, italic, - underline, strikethrough, spoiler, and custom_emoji entities are allowed. + underline, strikethrough, spoiler, and custom_emoji, and date_time entities are allowed tasks (Sequence[:class:`telegram.InputChecklistTask`]): List of :tg-const:`telegram.constants.InputChecklistLimit.MIN_TASK_NUMBER`\ diff --git a/src/telegram/_keyboardbutton.py b/src/telegram/_keyboardbutton.py index 750062bf516..98aeeaeea35 100644 --- a/src/telegram/_keyboardbutton.py +++ b/src/telegram/_keyboardbutton.py @@ -21,7 +21,11 @@ from typing import TYPE_CHECKING from telegram._keyboardbuttonpolltype import KeyboardButtonPollType -from telegram._keyboardbuttonrequest import KeyboardButtonRequestChat, KeyboardButtonRequestUsers +from telegram._keyboardbuttonrequest import ( + KeyboardButtonRequestChat, + KeyboardButtonRequestManagedBot, + KeyboardButtonRequestUsers, +) from telegram._telegramobject import TelegramObject from telegram._utils.argumentparsing import de_json_optional from telegram._utils.types import JSONDict @@ -120,6 +124,14 @@ class KeyboardButton(TelegramObject): Premium subscription. .. versionadded:: 22.7 + request_managed_bot (:obj:`telegram.KeyboardButtonRequestManagedBot`, optional): If + specified, pressing the button will ask the user to create and share a bot that will + be managed by the current bot. Available for bots that enabled management of other bots + in the `@BotFather ` Mini App. Available in private + chats only. + + .. versionadded:: NEXT.VERSION + Attributes: text (:obj:`str`): Text of the button. If none of the fields other than :attr:`text`, @@ -167,6 +179,13 @@ class KeyboardButton(TelegramObject): Premium subscription. .. versionadded:: 22.7 + request_managed_bot (:obj:`telegram.KeyboardButtonRequestManagedBot`): Optional. If + specified, pressing the button will ask the user to create and share a bot that will + be managed by the current bot. Available for bots that enabled management of other bots + in the `@BotFather ` Mini App. Available in private + chats only. + + .. versionadded:: NEXT.VERSION """ __slots__ = ( @@ -174,6 +193,7 @@ class KeyboardButton(TelegramObject): "request_chat", "request_contact", "request_location", + "request_managed_bot", "request_poll", "request_users", "style", @@ -192,6 +212,7 @@ def __init__( request_users: KeyboardButtonRequestUsers | None = None, style: str | None = None, icon_custom_emoji_id: str | None = None, + request_managed_bot: KeyboardButtonRequestManagedBot | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -208,6 +229,7 @@ def __init__( self.request_chat: KeyboardButtonRequestChat | None = request_chat self.style: str | None = style self.icon_custom_emoji_id: str | None = icon_custom_emoji_id + self.request_managed_bot: KeyboardButtonRequestManagedBot | None = request_managed_bot self._id_attrs = ( self.text, @@ -238,6 +260,9 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "KeyboardButton": data.get("request_chat"), KeyboardButtonRequestChat, bot ) data["web_app"] = de_json_optional(data.get("web_app"), WebAppInfo, bot) + data["request_managed_bot"] = de_json_optional( + data.get("request_managed_bot"), KeyboardButtonRequestManagedBot, bot + ) api_kwargs = {} # This is a deprecated field that TG still returns for backwards compatibility diff --git a/src/telegram/_keyboardbuttonrequest.py b/src/telegram/_keyboardbuttonrequest.py index b5e0b050436..fa8e3513c1c 100644 --- a/src/telegram/_keyboardbuttonrequest.py +++ b/src/telegram/_keyboardbuttonrequest.py @@ -268,3 +268,53 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "KeyboardButtonReq ) return super().de_json(data=data, bot=bot) + + +class KeyboardButtonRequestManagedBot(TelegramObject): + """ + This object defines the parameters for the creation of a managed bot. + Information about the created bot will be shared with the bot using the update + managed_bot and a :obj:`telegram.Message` with the field + :attr:`telegram.Message.managed_bot_created`. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`request_id` is equal. + + .. versionadded:: NEXT.VERSION + + Args: + request_id (:obj:`int`): Signed 32-bit identifier of the request. Must be unique + within the message. + suggested_name (:obj:`str`, optional): Suggested name for the bot. + suggested_username (:obj:`str`, optional): Suggested username for the bot. + + Attributes: + request_id (:obj:`int`): Signed 32-bit identifier of the request. Must be unique + within the message. + suggested_name (:obj:`str`): Optional. Suggested name for the bot. + suggested_username (:obj:`str`): Optional. Suggested username for the bot. + """ + + __slots__ = ( + "request_id", + "suggested_name", + "suggested_username", + ) + + def __init__( + self, + request_id: int, + suggested_name: str | None = None, + suggested_username: str | None = None, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(api_kwargs=api_kwargs) + # Required + self.request_id: int = request_id + # Optional + self.suggested_name: str | None = suggested_name + self.suggested_username: str | None = suggested_username + + self._id_attrs = (self.request_id,) + self._freeze() diff --git a/src/telegram/_managedbot.py b/src/telegram/_managedbot.py new file mode 100644 index 00000000000..109983c656e --- /dev/null +++ b/src/telegram/_managedbot.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2026 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""This module contains objects that represent managed bots in the Telegram Bot API.""" + +from typing import TYPE_CHECKING + +from telegram._telegramobject import TelegramObject +from telegram._user import User +from telegram._utils.types import ( + JSONDict, +) + +if TYPE_CHECKING: + from telegram import Bot + + +class ManagedBotCreated(TelegramObject): + """ + This object contains information about the bot that was created to be managed by the current + bot. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`bot` is equal. + + .. versionadded:: NEXT.VERSION + + Args: + bot (:class:`telegram.User`): Information about the bot. The bot's token can be fetched + using the method :meth:`~telegram.Bot.get_managed_bot_token`. + Attributes: + bot (:class:`telegram.User`): Information about the bot. The bot's token can be fetched + using the method :meth:`~telegram.Bot.get_managed_bot_token`. + """ + + __slots__ = ("bot",) + + def __init__( + self, + bot: User, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.bot: User = bot + self._id_attrs = (self.bot,) + self._freeze() + + @classmethod + def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ManagedBotCreated": + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data=data) + + data["bot"] = User.de_json(data=data["bot"], bot=bot) + + return super().de_json(data=data, bot=bot) + + +class ManagedBotUpdated(TelegramObject): + """ + This object contains information about the creation, token update, or owner update of a bot + that is managed by the current bot. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`user` and :attr:`bot` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + user (:class:`telegram.User`): User that created the bot. + bot (:class:`telegram.User`): Information about the bot. Token of the bot can be fetched + using the method :meth:`~telegram.Bot.get_managed_bot_token`. + + Attributes: + user (:class:`telegram.User`): User that created the bot. + bot (:class:`telegram.User`): Information about the bot. Token of the bot can be fetched + using the method :meth:`~telegram.Bot.get_managed_bot_token`. + """ + + __slots__ = ("bot", "user") + + def __init__( + self, + user: User, + bot: User, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(api_kwargs=api_kwargs) + # Required + self.user: User = user + self.bot: User = bot + + self._id_attrs = ( + self.user, + self.bot, + ) + self._freeze() + + @classmethod + def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ManagedBotUpdated": + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data=data) + + data["user"] = User.de_json(data=data["user"], bot=bot) + data["bot"] = User.de_json(data=data["bot"], bot=bot) + + return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_message.py b/src/telegram/_message.py index c0d018ec406..8c6626f50a6 100644 --- a/src/telegram/_message.py +++ b/src/telegram/_message.py @@ -57,6 +57,7 @@ from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inputchecklist import InputChecklist from telegram._linkpreviewoptions import LinkPreviewOptions +from telegram._managedbot import ManagedBotCreated from telegram._messageautodeletetimerchanged import MessageAutoDeleteTimerChanged from telegram._messageentity import MessageEntity from telegram._paidmedia import PaidMediaInfo @@ -65,7 +66,7 @@ from telegram._payment.invoice import Invoice from telegram._payment.refundedpayment import RefundedPayment from telegram._payment.successfulpayment import SuccessfulPayment -from telegram._poll import Poll +from telegram._poll import Poll, PollOptionAdded, PollOptionDeleted from telegram._proximityalerttriggered import ProximityAlertTriggered from telegram._reply import ReplyParameters from telegram._shared import ChatShared, UsersShared @@ -80,6 +81,7 @@ from telegram._utils.strings import TextEncoding from telegram._utils.types import ( CorrectOptionID, + CorrectOptionIds, JSONDict, MarkdownVersion, ODVInput, @@ -689,6 +691,23 @@ class Message(MaybeInaccessibleMessage): supergroups only .. versionadded:: 22.7 + poll_option_added (:class:`telegram.PollOptionAdded`, optional): Service message: + answer option was added to a poll. + + .. versionadded:: NEXT.VERSION + poll_option_deleted (:class:`telegram.PollOptionDeleted`, optional): Service message: + answer option was deleted from a poll. + + .. versionadded:: NEXT.VERSION + reply_to_poll_option_id (:obj:`str`, optional): Persistent + identifier of the specific poll option that is being replied to. + + .. versionadded:: NEXT.VERSION + + managed_bot_created (:class:`telegram.ManagedBotCreated`, optional): Service message: user + created a bot that will be managed by the current bot. + + .. versionadded:: NEXT.VERSION Attributes: message_id (:obj:`int`): Unique message identifier inside this chat. In specific instances @@ -1105,6 +1124,22 @@ class Message(MaybeInaccessibleMessage): supergroups only .. versionadded:: 22.7 + poll_option_added (:class:`telegram.PollOptionAdded`): Optional. Service message: + answer option was added to a poll. + + .. versionadded:: NEXT.VERSION + poll_option_deleted (:class:`telegram.PollOptionDeleted`): Optional. Service message: + answer option was deleted from a poll. + + .. versionadded:: NEXT.VERSION + reply_to_poll_option_id (:obj:`str`): Optional. Persistent + identifier of the specific poll option that is being replied to. + + .. versionadded:: NEXT.VERSION + managed_bot_created (:class:`telegram.ManagedBotCreated`): Optional. Service message: user + created a bot that will be managed by the current bot. + + .. versionadded:: NEXT.VERSION .. |custom_emoji_no_md1_support| replace:: Since custom emoji entities are not supported by :attr:`~telegram.constants.ParseMode.MARKDOWN`, this method now raises a @@ -1176,6 +1211,7 @@ class Message(MaybeInaccessibleMessage): "left_chat_member", "link_preview_options", "location", + "managed_bot_created", "media_group_id", "message_auto_delete_timer_changed", "message_thread_id", @@ -1191,12 +1227,15 @@ class Message(MaybeInaccessibleMessage): "photo", "pinned_message", "poll", + "poll_option_added", + "poll_option_deleted", "proximity_alert_triggered", "quote", "refunded_payment", "reply_markup", "reply_to_checklist_task_id", "reply_to_message", + "reply_to_poll_option_id", "reply_to_story", "sender_boost_count", "sender_business_bot", @@ -1337,6 +1376,10 @@ def __init__( chat_owner_changed: ChatOwnerChanged | None = None, chat_owner_left: ChatOwnerLeft | None = None, sender_tag: str | None = None, + poll_option_added: PollOptionAdded | None = None, + poll_option_deleted: PollOptionDeleted | None = None, + reply_to_poll_option_id: str | None = None, + managed_bot_created: ManagedBotCreated | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -1467,6 +1510,10 @@ def __init__( self.chat_owner_changed: ChatOwnerChanged | None = chat_owner_changed self.chat_owner_left: ChatOwnerLeft | None = chat_owner_left self.sender_tag: str | None = sender_tag + self.poll_option_added: PollOptionAdded | None = poll_option_added + self.poll_option_deleted: PollOptionDeleted | None = poll_option_deleted + self.reply_to_poll_option_id: str | None = reply_to_poll_option_id + self.managed_bot_created: ManagedBotCreated | None = managed_bot_created self._effective_attachment = DEFAULT_NONE @@ -1687,6 +1734,15 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Message": data.get("chat_owner_changed"), ChatOwnerChanged, bot ) data["chat_owner_left"] = de_json_optional(data.get("chat_owner_left"), ChatOwnerLeft, bot) + data["poll_option_added"] = de_json_optional( + data.get("poll_option_added"), PollOptionAdded, bot + ) + data["poll_option_deleted"] = de_json_optional( + data.get("poll_option_deleted"), PollOptionDeleted, bot + ) + data["managed_bot_created"] = de_json_optional( + data.get("managed_bot_created"), ManagedBotCreated, bot + ) api_kwargs = {} # This is a deprecated field that TG still returns for backwards compatibility @@ -3473,6 +3529,14 @@ async def reply_poll( question_entities: Sequence["MessageEntity"] | None = None, message_effect_id: str | None = None, allow_paid_broadcast: bool | None = None, + shuffle_options: bool | None = None, + allows_revoting: bool | None = None, + correct_option_ids: CorrectOptionIds | None = None, + allow_adding_options: bool | None = None, + hide_results_until_closes: bool | None = None, + description: str | None = None, + description_parse_mode: str | None = None, + description_entities: Sequence["MessageEntity"] | None = None, *, reply_to_message_id: int | None = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3522,6 +3586,9 @@ async def reply_poll( type=type, allows_multiple_answers=allows_multiple_answers, correct_option_id=correct_option_id, + allows_revoting=allows_revoting, + shuffle_options=shuffle_options, + correct_option_ids=correct_option_ids, is_closed=is_closed, disable_notification=disable_notification, reply_parameters=effective_reply_parameters, @@ -3543,6 +3610,11 @@ async def reply_poll( question_entities=question_entities, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, + description=description, + description_parse_mode=description_parse_mode, + description_entities=description_entities, + hide_results_until_closes=hide_results_until_closes, + allow_adding_options=allow_adding_options, ) async def reply_dice( diff --git a/src/telegram/_poll.py b/src/telegram/_poll.py index 744edd22eff..ba02062b33a 100644 --- a/src/telegram/_poll.py +++ b/src/telegram/_poll.py @@ -42,9 +42,11 @@ from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.entities import parse_message_entities, parse_message_entity from telegram._utils.types import JSONDict, ODVInput, TimePeriod +from telegram._utils.warnings import warn +from telegram.warnings import PTBDeprecationWarning if TYPE_CHECKING: - from telegram import Bot + from telegram import Bot, MaybeInaccessibleMessage class InputPollOption(TelegramObject): @@ -118,6 +120,10 @@ class PollOption(TelegramObject): considered equal, if their :attr:`text` and :attr:`voter_count` are equal. Args: + persistent_id (:obj:`str`): Unique identifier of the option, persistent on option addition + and deletion. + + .. versionadded:: NEXT.VERSION text (:obj:`str`): Option text, :tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH` characters. @@ -127,8 +133,24 @@ class PollOption(TelegramObject): poll option texts. .. versionadded:: 21.2 + added_by_user (:class:`telegram.User`, optional): User who added the option; + omitted if the option wasn't added by a user after poll creation. + + .. versionadded:: NEXT.VERSION + added_by_chat (:class:`telegram.Chat`, optional): Chat that added the option; + omitted if the option wasn't added by a chat after poll creation. + + .. versionadded:: NEXT.VERSION + addition_date (:obj:`datetime.datetime`, optional): Point in time + when the option was added; omitted if the option existed in the original poll. + + .. versionadded:: NEXT.VERSION Attributes: + persistent_id (:obj:`str`): Unique identifier of the option, persistent on option addition + and deletion. + + .. versionadded:: NEXT.VERSION text (:obj:`str`): Option text, :tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH` characters. @@ -139,22 +161,52 @@ class PollOption(TelegramObject): This list is empty if the question does not contain entities. .. versionadded:: 21.2 + added_by_user (:class:`telegram.User`): Optional. User who added the option; + omitted if the option wasn't added by a user after poll creation. + + .. versionadded:: NEXT.VERSION + added_by_chat (:class:`telegram.Chat`): Optional. Chat that added the option; + omitted if the option wasn't added by a chat after poll creation. + + .. versionadded:: NEXT.VERSION + addition_date (:obj:`datetime.datetime`): Optional. Point in time + when the option was added; omitted if the option existed in the original poll. + .. versionadded:: NEXT.VERSION """ - __slots__ = ("text", "text_entities", "voter_count") + __slots__ = ( + "added_by_chat", + "added_by_user", + "addition_date", + "persistent_id", + "text", + "text_entities", + "voter_count", + ) def __init__( self, text: str, voter_count: int, text_entities: Sequence[MessageEntity] | None = None, + added_by_user: User | None = None, + added_by_chat: Chat | None = None, + addition_date: dtm.datetime | None = None, + # tags: required in NEXT.VERSION, bot api 9.6 + # temporarily optional to avoid breaking changes + persistent_id: str | None = None, *, api_kwargs: JSONDict | None = None, ): super().__init__(api_kwargs=api_kwargs) self.text: str = text self.voter_count: int = voter_count + self.added_by_user: User | None = added_by_user + self.added_by_chat: Chat | None = added_by_chat + self.addition_date: dtm.datetime | None = addition_date + self.persistent_id: str | None = persistent_id + self.text_entities: tuple[MessageEntity, ...] = parse_sequence_arg(text_entities) self._id_attrs = (self.text, self.voter_count) @@ -166,7 +218,13 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PollOption": """See :meth:`telegram.TelegramObject.de_json`.""" data = cls._parse_data(data) + # Get the local timezone from the bot if it has defaults + loc_tzinfo = extract_tzinfo_from_defaults(bot) + data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot) + data["added_by_user"] = de_json_optional(data.get("added_by_user"), User, bot) + data["added_by_chat"] = de_json_optional(data.get("added_by_chat"), Chat, bot) + data["addition_date"] = from_timestamp(data.get("addition_date"), tzinfo=loc_tzinfo) return super().de_json(data=data, bot=bot) @@ -248,6 +306,10 @@ class PollAnswer(TelegramObject): .. versionchanged:: 20.0 |sequenceclassargs| + option_persistent_ids (Sequence[:obj:`str`]): Persistent identifiers of the + chosen answer options. May be empty if the vote was retracted. + + .. versionadded:: NEXT.VERSION user (:class:`telegram.User`, optional): The user that changed the answer to the poll, if the voter isn't anonymous. If the voter is anonymous, this field will contain the user :tg-const:`telegram.constants.ChatID.FAKE_CHANNEL` for backwards compatibility. @@ -266,6 +328,10 @@ class PollAnswer(TelegramObject): .. versionchanged:: 20.0 |tupleclassattrs| + option_persistent_ids (tuple[:obj:`str`]): Persistent identifiers of the + chosen answer options. May be empty if the vote was retracted. + + .. versionadded:: NEXT.VERSION user (:class:`telegram.User`): Optional. The user, who changed the answer to the poll, if the voter isn't anonymous. If the voter is anonymous, this field will contain the user :tg-const:`telegram.constants.ChatID.FAKE_CHANNEL` for backwards compatibility @@ -276,10 +342,9 @@ class PollAnswer(TelegramObject): poll, if the voter is anonymous. .. versionadded:: 20.5 - """ - __slots__ = ("option_ids", "poll_id", "user", "voter_chat") + __slots__ = ("option_ids", "option_persistent_ids", "poll_id", "user", "voter_chat") def __init__( self, @@ -287,6 +352,9 @@ def __init__( option_ids: Sequence[int], user: User | None = None, voter_chat: Chat | None = None, + # tags: required in NEXT.VERSION, bot api 9.6 + # temporarily optional to avoid breaking changes + option_persistent_ids: Sequence[str] | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -295,6 +363,7 @@ def __init__( self.voter_chat: Chat | None = voter_chat self.option_ids: tuple[int, ...] = parse_sequence_arg(option_ids) self.user: User | None = user + self.option_persistent_ids: tuple[str, ...] = parse_sequence_arg(option_persistent_ids) self._id_attrs = ( self.poll_id, @@ -316,6 +385,236 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PollAnswer": return super().de_json(data=data, bot=bot) +class PollOptionAdded(TelegramObject): + """ + Describes a service message about an option added to a poll. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`option_persistent_id`, and :attr:`option_text` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + option_persistent_id (:obj:`str`): Unique identifier of the added option. + option_text (:obj:`str`): Option text. + poll_message (:class:`telegram.MaybeInaccessibleMessage`, optional): Message + containing the poll to which the option was added, if known. + Note that the Message object in this field will not contain the + :attr:`~telegram.Message.reply_to_message` field even if it itself is a reply. + option_text_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special + entities that appear in the :paramref:`option_text`. + + Attributes: + option_persistent_id (:obj:`str`): Unique identifier of the added option. + option_text (:obj:`str`): Option text. + poll_message (:class:`telegram.MaybeInaccessibleMessage`): Optional. Message + containing the poll to which the option was added, if known. + Note that the Message object in this field will not contain the + :attr:`~telegram.Message.reply_to_message` field even if it itself is a reply. + option_text_entities (tuple[:class:`telegram.MessageEntity`]): Optional. Special + entities that appear in the :paramref:`option_text`. + """ + + __slots__ = ("option_persistent_id", "option_text", "option_text_entities", "poll_message") + + def __init__( + self, + option_persistent_id: str, + option_text: str, + poll_message: "MaybeInaccessibleMessage | None" = None, + option_text_entities: Sequence[MessageEntity] | None = None, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.option_persistent_id: str = option_persistent_id + self.option_text: str = option_text + self.poll_message: MaybeInaccessibleMessage | None = poll_message + + self.option_text_entities: tuple[MessageEntity, ...] = parse_sequence_arg( + option_text_entities + ) + + self._id_attrs = (self.option_persistent_id, self.option_text) + + self._freeze() + + @classmethod + def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PollOptionAdded": + """See :meth:`telegram.TelegramObject.de_json`.""" + from telegram._message import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415 + MaybeInaccessibleMessage, + ) + + data = cls._parse_data(data) + + data["poll_message"] = de_json_optional( + data.get("poll_message"), MaybeInaccessibleMessage, bot + ) + data["option_text_entities"] = de_list_optional( + data.get("option_text_entities"), MessageEntity, bot + ) + + return super().de_json(data=data, bot=bot) + + def parse_option_text_entity(self, entity: MessageEntity) -> str: + """Returns the text in :attr:`option_text` + from a given :class:`telegram.MessageEntity` of :attr:`option_text_entities`. + + Note: + This method is present because Telegram calculates the offset and length in + UTF-16 codepoint pairs, which some versions of Python don't handle automatically. + (That is, you can't just slice ``Message.text`` with the offset and length.) + + Args: + entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must + be an entity that belongs to :attr:`option_text_entities`. + + Returns: + :obj:`str`: The text of the given entity. + """ + return parse_message_entity(self.option_text, entity) + + def parse_option_text_entities( + self, types: list[str] | None = None + ) -> dict[MessageEntity, str]: + """ + Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`. + It contains entities from this polls option text filtered by their ``type`` attribute as + the key, and the text that each entity belongs to as the value of the :obj:`dict`. + + Note: + This method should always be used instead of the :attr:`option_text_entities` + attribute, since it calculates the correct substring from the message text based on + UTF-16 codepoints. See :attr:`parse_entity` for more info. + + Args: + types (list[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the + ``type`` attribute of an entity is contained in this list, it will be returned. + Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`. + + Returns: + dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to + the text that belongs to them, calculated based on UTF-16 codepoints. + """ + return parse_message_entities(self.option_text, self.option_text_entities, types) + + +class PollOptionDeleted(TelegramObject): + """ + Describes a service message about an option deleted from a poll. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`option_persistent_id`, :attr:`option_text` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + option_persistent_id (:obj:`str`): Unique identifier of the deleted option. + option_text (:obj:`str`): Option text. + poll_message (:class:`telegram.MaybeInaccessibleMessage`, optional): Message + containing the poll to which the option was deleted, if known. + Note that the Message object in this field will not contain the + :attr:`~telegram.Message.reply_to_message` field even if it itself is a reply. + option_text_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special + entities that appear in the option_text. + + Attributes: + option_persistent_id (:obj:`str`): Unique identifier of the deleted option. + option_text (:obj:`str`): Option text. + poll_message (:class:`telegram.MaybeInaccessibleMessage`): Optional. Message + containing the poll to which the option was deleted, if known. + Note that the Message object in this field will not contain the + :attr:`~telegram.Message.reply_to_message` field even if it itself is a reply. + option_text_entities (tuple[:class:`telegram.MessageEntity`]): Optional. Special + entities that appear in the option_text. + """ + + __slots__ = ("option_persistent_id", "option_text", "option_text_entities", "poll_message") + + def __init__( + self, + option_persistent_id: str, + option_text: str, + poll_message: "MaybeInaccessibleMessage | None" = None, + option_text_entities: Sequence[MessageEntity] | None = None, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.option_persistent_id: str = option_persistent_id + self.option_text: str = option_text + self.poll_message: MaybeInaccessibleMessage | None = poll_message + + self.option_text_entities: tuple[MessageEntity, ...] = parse_sequence_arg( + option_text_entities + ) + + self._id_attrs = (self.option_persistent_id, self.option_text) + + self._freeze() + + @classmethod + def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PollOptionDeleted": + """See :meth:`telegram.TelegramObject.de_json`.""" + from telegram._message import ( # pylint: disable=import-outside-toplevel # noqa: PLC0415 + MaybeInaccessibleMessage, + ) + + data = cls._parse_data(data) + + data["poll_message"] = de_json_optional( + data.get("poll_message"), MaybeInaccessibleMessage, bot + ) + data["option_text_entities"] = de_list_optional( + data.get("option_text_entities"), MessageEntity, bot + ) + + return super().de_json(data=data, bot=bot) + + def parse_option_text_entity(self, entity: MessageEntity) -> str: + """Returns the text in :attr:`option_text` + from a given :class:`telegram.MessageEntity` of :attr:`option_text_entities`. + + Note: + This method is present because Telegram calculates the offset and length in + UTF-16 codepoint pairs, which some versions of Python don't handle automatically. + (That is, you can't just slice ``Message.text`` with the offset and length.) + + Args: + entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must + be an entity that belongs to :attr:`option_text_entities`. + + Returns: + :obj:`str`: The text of the given entity. + """ + return parse_message_entity(self.option_text, entity) + + def parse_option_text_entities( + self, types: list[str] | None = None + ) -> dict[MessageEntity, str]: + """ + Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`. + It contains entities from this polls option text filtered by their ``type`` attribute as + the key, and the text that each entity belongs to as the value of the :obj:`dict`. + + Note: + This method should always be used instead of the :attr:`option_text_entities` + attribute, since it calculates the correct substring from the message text based on + UTF-16 codepoints. See :attr:`parse_entity` for more info. + + Args: + types (list[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the + ``type`` attribute of an entity is contained in this list, it will be returned. + Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`. + + Returns: + dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to + the text that belongs to them, calculated based on UTF-16 codepoints. + """ + return parse_message_entities(self.option_text, self.option_text_entities, types) + + class Poll(TelegramObject): """ This object contains information about a poll. @@ -341,6 +640,9 @@ class Poll(TelegramObject): correct_option_id (:obj:`int`, optional): A zero based identifier of the correct answer option. Available only for closed polls in the quiz mode, which were sent (not forwarded), by the bot or to a private chat with the bot. + + .. deprecated:: NEXT.VERSION + Use :paramref:`correct_option_ids` instead. explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-:tg-const:`telegram.Poll.MAX_EXPLANATION_LENGTH` characters. @@ -368,6 +670,23 @@ class Poll(TelegramObject): in poll questions. .. versionadded:: 21.2 + allows_revoting (:obj:`bool`, optional): :obj:`True`, if the poll allows to + change the chosenanswer options. + + .. versionadded:: NEXT.VERSION + correct_option_ids (Sequence[:class:`int`], optional): Array of 0-based identifiers of + the correct answer options. Available only for polls in quiz mode which are closed or + were sent (not forwarded) by the bot or to the private chat with the bot. + + .. versionadded:: NEXT.VERSION + description (:obj:`str`, optional): Description of the poll; + for polls inside the :class:`~telegram.Message` object only. + + .. versionadded:: NEXT.VERSION + description_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special + entities like usernames, URLs, bot commands, etc. that appear in the description + + .. versionadded:: NEXT.VERSION Attributes: id (:obj:`str`): Unique poll identifier. @@ -385,6 +704,9 @@ class Poll(TelegramObject): correct_option_id (:obj:`int`): Optional. A zero based identifier of the correct answer option. Available only for closed polls in the quiz mode, which were sent (not forwarded), by the bot or to a private chat with the bot. + + .. deprecated:: NEXT.VERSION + Use :attr:`correct_option_ids` instead. explanation (:obj:`str`): Optional. Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-:tg-const:`telegram.Poll.MAX_EXPLANATION_LENGTH` characters. @@ -413,14 +735,34 @@ class Poll(TelegramObject): This list is empty if the question does not contain entities. .. versionadded:: 21.2 + allows_revoting (:obj:`bool`): Optional. :obj:`True`, if the poll + allows to change the chosenanswer options + + .. versionadded:: NEXT.VERSION + correct_option_ids (tuple[:class:`int`]): Array of 0-based identifiers of the + correct answer options. Available only for polls in quiz mode which are closed or were + sent (not forwarded) by the bot or to the private chat with the bot. + + .. versionadded:: NEXT.VERSION + description (:obj:`str`): Optional. Description of the poll; + for polls inside the Message object only + + .. versionadded:: NEXT.VERSION + description_entities (tuple[:class:`telegram.MessageEntity`]): Special + entities like usernames, URLs, bot commands, etc. that appear in the description + + .. versionadded:: NEXT.VERSION """ __slots__ = ( "_open_period", "allows_multiple_answers", + "allows_revoting", "close_date", - "correct_option_id", + "correct_option_ids", + "description", + "description_entities", "explanation", "explanation_entities", "id", @@ -443,12 +785,20 @@ def __init__( is_anonymous: bool, type: str, # pylint: disable=redefined-builtin allows_multiple_answers: bool, + # tags: deprecated NEXT.VERSION + # Removed in bot api 9.6: correct_option_id: int | None = None, explanation: str | None = None, explanation_entities: Sequence[MessageEntity] | None = None, open_period: TimePeriod | None = None, close_date: dtm.datetime | None = None, question_entities: Sequence[MessageEntity] | None = None, + # tags: required in NEXT.VERSION + # temporarily optional to avoid breaking changes + allows_revoting: bool | None = None, + correct_option_ids: Sequence[int] | None = None, + description: str | None = None, + description_entities: Sequence[MessageEntity] | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -461,7 +811,26 @@ def __init__( self.is_anonymous: bool = is_anonymous self.type: str = enum.get_member(constants.PollType, type, type) self.allows_multiple_answers: bool = allows_multiple_answers - self.correct_option_id: int | None = correct_option_id + self.allows_revoting: bool | None = allows_revoting + + # tag: deprecated NEXT.VERSION + if correct_option_id is not None: + warn( + PTBDeprecationWarning( + "NEXT.VERSION", + "The parameter `correct_option_id` is deprecated. " + "Use `correct_option_ids` instead.", + ), + stacklevel=2, + ) + if correct_option_ids is None: + correct_option_ids = [correct_option_id] + + self.correct_option_ids: tuple[int, ...] = parse_sequence_arg(correct_option_ids) + self.description: str | None = description + self.description_entities: tuple[MessageEntity, ...] = parse_sequence_arg( + description_entities + ) self.explanation: str | None = explanation self.explanation_entities: tuple[MessageEntity, ...] = parse_sequence_arg( explanation_entities @@ -494,6 +863,9 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Poll": data["question_entities"] = de_list_optional( data.get("question_entities"), MessageEntity, bot ) + data["description_entities"] = de_list_optional( + data.get("description_entities"), MessageEntity, bot + ) return super().de_json(data=data, bot=bot) @@ -598,6 +970,82 @@ def parse_question_entities(self, types: list[str] | None = None) -> dict[Messag """ return parse_message_entities(self.question, self.question_entities, types) + def parse_description_entity(self, entity: MessageEntity) -> str: + """Returns the text in :attr:`description` from a given :class:`telegram.MessageEntity` of + :attr:`description_entities`. + + .. versionadded:: NEXT.VERSION + + Note: + This method is present because Telegram calculates the offset and length in + UTF-16 codepoint pairs, which some versions of Python don't handle automatically. + (That is, you can't just slice ``Message.text`` with the offset and length.) + + Args: + entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must + be an entity that belongs to :attr:`description_entities`. + + Returns: + :obj:`str`: The text of the given entity. + + Raises: + RuntimeError: If the poll has no description. + + """ + if not self.description: + raise RuntimeError("This Poll has no 'description'.") + + return parse_message_entity(self.description, entity) + + def parse_description_entities( + self, types: list[str] | None = None + ) -> dict[MessageEntity, str]: + """ + Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`. + It contains entities from this polls description filtered by their ``type`` attribute as + the key, and the text that each entity belongs to as the value of the :obj:`dict`. + + .. versionadded:: NEXT.VERSION + + Note: + This method should always be used instead of the :attr:`description_entities` + attribute, since it calculates the correct substring from the message text based on + UTF-16 codepoints. See :attr:`parse_description_entity` for more info. + + Args: + types (list[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the + ``type`` attribute of an entity is contained in this list, it will be returned. + Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`. + Returns: + dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities + mapped to the text that belongs to them, calculated based on UTF-16 codepoints. + Raises: + RuntimeError: If the poll has no description. + """ + if not self.description: + raise RuntimeError("This Poll has no 'description'.") + + return parse_message_entities(self.description, self.description_entities, types) + + @property + def correct_option_id(self) -> int | None: + """A zero based identifier of the correct answer + option. Available only for closed polls in the quiz mode, which were sent + (not forwarded), by the bot or to a private chat with the bot. + + .. deprecated:: NEXT.VERSION + Use :attr:`correct_option_ids` instead. + """ + warn( + PTBDeprecationWarning( + "NEXT.VERSION", + "The attribute `correct_option_id` is deprecated. " + "Use `correct_option_ids` instead.", + ), + stacklevel=2, + ) + return self.correct_option_ids[0] if self.correct_option_ids else None + REGULAR: Final[str] = constants.PollType.REGULAR """:const:`telegram.constants.PollType.REGULAR`""" QUIZ: Final[str] = constants.PollType.QUIZ @@ -652,3 +1100,8 @@ def parse_question_entities(self, types: list[str] | None = None) -> dict[Messag .. versionadded:: 20.0 """ + MAX_DESCRIPTION_CHARACTERS: Final[int] = constants.PollLimit.MAX_DESCRIPTION_CHARACTERS + """:const:`telegram.constants.PollLimit.MAX_DESCRIPTION_CHARACTERS` + + .. versionadded:: NEXT.VERSION + """ diff --git a/src/telegram/_preparedkeyboardbutton.py b/src/telegram/_preparedkeyboardbutton.py new file mode 100644 index 00000000000..0a650f9469e --- /dev/null +++ b/src/telegram/_preparedkeyboardbutton.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2026 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""This module contains an object that represents a Telegram PreparedKeyboardButton.""" + +from telegram._telegramobject import TelegramObject +from telegram._utils.types import ( + JSONDict, +) + + +class PreparedKeyboardButton(TelegramObject): + """ + Describes a keyboard button to be used by a user of a Mini App. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`id` is equal. + + .. versionadded:: NEXT.VERSION + + Args: + id (:obj:`str`): Unique identifier of the keyboard button. + + Attributes: + id (:obj:`str`): Unique identifier of the keyboard button. + """ + + __slots__ = ("id",) + + def __init__( + self, + id: str, # pylint: disable=redefined-builtin + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.id: str = id + self._id_attrs = (self.id,) + self._freeze() diff --git a/src/telegram/_reply.py b/src/telegram/_reply.py index 367d5aad7a0..6f1edfadeb8 100644 --- a/src/telegram/_reply.py +++ b/src/telegram/_reply.py @@ -311,8 +311,8 @@ class TextQuote(TelegramObject): units as specified by the sender. entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities that appear - in the quote. Currently, only bold, italic, underline, strikethrough, spoiler, and - custom_emoji entities are kept in quotes. + in the quote. Currently, only bold, italic, underline, strikethrough, spoiler, + custom_emoji, and date_time entities are kept in quotes. is_manual (:obj:`bool`, optional): :obj:`True`, if the quote was chosen manually by the message sender. Otherwise, the quote was added automatically by the server. @@ -322,8 +322,8 @@ class TextQuote(TelegramObject): position (:obj:`int`): Approximate quote position in the original message in UTF-16 code units as specified by the sender. entities (tuple[:class:`telegram.MessageEntity`]): Optional. Special entities that appear - in the quote. Currently, only bold, italic, underline, strikethrough, spoiler, and - custom_emoji entities are kept in quotes. + in the quote. Currently, only bold, italic, underline, strikethrough, spoiler, + custom_emoji, and date_time entities are kept in quotes. is_manual (:obj:`bool`): Optional. :obj:`True`, if the quote was chosen manually by the message sender. Otherwise, the quote was added automatically by the server. """ @@ -394,9 +394,9 @@ class ReplyParameters(TelegramObject): used only for replies in the same chat and forum topic. quote (:obj:`str`, optional): Quoted part of the message to be replied to; 0-1024 characters after entities parsing. The quote must be an exact substring of the message - to be replied to, including bold, italic, underline, strikethrough, spoiler, and - custom_emoji entities. The message will fail to send if the quote isn't found in the - original message. + to be replied to, including bold, italic, underline, strikethrough, spoiler, + custom_emoji, and date_time entities. The message will fail to send if the quote isn't + found in the original message. quote_parse_mode (:obj:`str`, optional): Mode for parsing entities in the quote. See :wiki:`formatting options ` for more details. @@ -410,6 +410,10 @@ class ReplyParameters(TelegramObject): replied to. .. versionadded:: 22.4 + poll_option_id (:obj:`str`, optional): Persistent + identifier of the specific poll option to be replied to. + + .. versionadded:: NEXT.VERSION Attributes: message_id (:obj:`int`): Identifier of the message that will be replied to in the current @@ -422,9 +426,9 @@ class ReplyParameters(TelegramObject): used only for replies in the same chat and forum topic. quote (:obj:`str`): Optional. Quoted part of the message to be replied to; 0-1024 characters after entities parsing. The quote must be an exact substring of the message - to be replied to, including bold, italic, underline, strikethrough, spoiler, and - custom_emoji entities. The message will fail to send if the quote isn't found in the - original message. + to be replied to, including bold, italic, underline, strikethrough, spoiler, + custom_emoji, and date_time entities. The message will fail to send if the quote isn't + found in the original message. quote_parse_mode (:obj:`str`): Optional. Mode for parsing entities in the quote. See :wiki:`formatting options ` for more details. @@ -437,6 +441,10 @@ class ReplyParameters(TelegramObject): replied to. .. versionadded:: 22.4 + poll_option_id (:obj:`str`): Optional. Persistent + identifier of the specific poll option to be replied to. + + .. versionadded:: NEXT.VERSION """ __slots__ = ( @@ -444,6 +452,7 @@ class ReplyParameters(TelegramObject): "chat_id", "checklist_task_id", "message_id", + "poll_option_id", "quote", "quote_entities", "quote_parse_mode", @@ -460,6 +469,7 @@ def __init__( quote_entities: Sequence[MessageEntity] | None = None, quote_position: int | None = None, checklist_task_id: int | None = None, + poll_option_id: str | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -473,6 +483,7 @@ def __init__( self.quote_entities: tuple[MessageEntity, ...] | None = parse_sequence_arg(quote_entities) self.quote_position: int | None = quote_position self.checklist_task_id: int | None = checklist_task_id + self.poll_option_id: str | None = poll_option_id self._id_attrs = (self.message_id,) diff --git a/src/telegram/_update.py b/src/telegram/_update.py index 9e018e4cb0f..a8b8668ad17 100644 --- a/src/telegram/_update.py +++ b/src/telegram/_update.py @@ -28,6 +28,7 @@ from telegram._chatmemberupdated import ChatMemberUpdated from telegram._choseninlineresult import ChosenInlineResult from telegram._inline.inlinequery import InlineQuery +from telegram._managedbot import ManagedBotUpdated from telegram._message import Message from telegram._messagereactionupdated import MessageReactionCountUpdated, MessageReactionUpdated from telegram._paidmedia import PaidMediaPurchased @@ -163,6 +164,11 @@ class Update(TelegramObject): .. versionadded:: 21.6 + managed_bot (:class:`telegram.ManagedBotUpdated`, optional): A new bot was created to be + managed by the bot, or token or owner of a managed bot was changed. + + .. versionadded:: NEXT.VERSION + Attributes: update_id (:obj:`int`): The update's unique identifier. Update identifiers start from a @@ -275,6 +281,10 @@ class Update(TelegramObject): paid media with a non-empty payload sent by the bot in a non-channel chat. .. versionadded:: 21.6 + managed_bot (:class:`telegram.ManagedBotUpdated`): Optional. A new bot was created to be + managed by the bot, or token or owner of a managed bot was changed. + + .. versionadded:: NEXT.VERSION """ __slots__ = ( @@ -295,6 +305,7 @@ class Update(TelegramObject): "edited_channel_post", "edited_message", "inline_query", + "managed_bot", "message", "message_reaction", "message_reaction_count", @@ -403,6 +414,12 @@ class Update(TelegramObject): .. versionadded:: 21.6 """ + MANAGED_BOT: Final[str] = constants.UpdateType.MANAGED_BOT + """:const:`telegram.constants.UpdateType.MANAGED_BOT` + + .. versionadded:: NEXT.VERSION + """ + ALL_TYPES: Final[list[str]] = list(constants.UpdateType) """list[:obj:`str`]: A list of all available update types. @@ -434,6 +451,7 @@ def __init__( edited_business_message: Message | None = None, deleted_business_messages: BusinessMessagesDeleted | None = None, purchased_paid_media: PaidMediaPurchased | None = None, + managed_bot: ManagedBotUpdated | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -464,6 +482,7 @@ def __init__( self.edited_business_message: Message | None = edited_business_message self.deleted_business_messages: BusinessMessagesDeleted | None = deleted_business_messages self.purchased_paid_media: PaidMediaPurchased | None = purchased_paid_media + self.managed_bot: ManagedBotUpdated | None = managed_bot self._effective_user: User | None = None self._effective_sender: User | Chat | None = None @@ -498,6 +517,9 @@ def effective_user(self) -> "User | None": .. versionchanged:: 21.6 This property now also considers :attr:`purchased_paid_media`. + .. versionchanged:: NEXT.VERSION + This property now also considers :attr:`managed_bot`. + Example: * If :attr:`message` is present, this will give :attr:`telegram.Message.from_user`. @@ -557,6 +579,9 @@ def effective_user(self) -> "User | None": elif self.purchased_paid_media: user = self.purchased_paid_media.from_user + elif self.managed_bot: + user = self.managed_bot.user + self._effective_user = user return user @@ -627,8 +652,8 @@ def effective_chat(self) -> "Chat | None": This is the case, if :attr:`inline_query`, :attr:`chosen_inline_result`, :attr:`callback_query` from inline messages, :attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll`, - :attr:`poll_answer`, :attr:`business_connection`, or :attr:`purchased_paid_media` - is present. + :attr:`poll_answer`, :attr:`business_connection`, :attr:`purchased_paid_media`, + or :attr:`managed_bot` is present. .. versionchanged:: 21.1 This property now also considers :attr:`business_message`, @@ -807,5 +832,6 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Update": data["purchased_paid_media"] = de_json_optional( data.get("purchased_paid_media"), PaidMediaPurchased, bot ) + data["managed_bot"] = de_json_optional(data.get("managed_bot"), ManagedBotUpdated, bot) return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_user.py b/src/telegram/_user.py index 89dae50d147..821f230adb6 100644 --- a/src/telegram/_user.py +++ b/src/telegram/_user.py @@ -29,6 +29,7 @@ from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import ( CorrectOptionID, + CorrectOptionIds, JSONDict, ODVInput, TimePeriod, @@ -124,6 +125,10 @@ class User(TelegramObject): :meth:`telegram.Bot.get_me`. .. versionadded:: 22.7 + can_manage_bots (:obj:`bool`, optional): :obj:`True`, if other bots can be created to be + controlled by the bot. Returned only in :meth:`telegram.Bot.get_me`. + + .. versionadded:: NEXT.VERSION Attributes: id (:obj:`int`): Unique identifier for this user or bot. @@ -164,6 +169,10 @@ class User(TelegramObject): :meth:`telegram.Bot.get_me`. .. versionadded:: 22.7 + can_manage_bots (:obj:`bool`): Optional. :obj:`True`, if other bots can be created to be + controlled by the bot. Returned only in :meth:`telegram.Bot.get_me`. + + .. versionadded:: NEXT.VERSION .. |user_chat_id_note| replace:: This shortcuts build on the assumption that :attr:`User.id` coincides with the :attr:`Chat.id` of the private chat with the user. This has been the @@ -175,6 +184,7 @@ class User(TelegramObject): "allows_users_to_create_topics", "can_connect_to_business", "can_join_groups", + "can_manage_bots", "can_read_all_group_messages", "first_name", "has_main_web_app", @@ -205,6 +215,7 @@ def __init__( has_main_web_app: bool | None = None, has_topics_enabled: bool | None = None, allows_users_to_create_topics: bool | None = None, + can_manage_bots: bool | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -226,6 +237,7 @@ def __init__( self.has_main_web_app: bool | None = has_main_web_app self.has_topics_enabled: bool | None = has_topics_enabled self.allows_users_to_create_topics: bool | None = allows_users_to_create_topics + self.can_manage_bots: bool | None = can_manage_bots self._id_attrs = (self.id,) @@ -1730,6 +1742,14 @@ async def send_poll( question_entities: Sequence["MessageEntity"] | None = None, message_effect_id: str | None = None, allow_paid_broadcast: bool | None = None, + shuffle_options: bool | None = None, + allows_revoting: bool | None = None, + correct_option_ids: CorrectOptionIds | None = None, + allow_adding_options: bool | None = None, + hide_results_until_closes: bool | None = None, + description: str | None = None, + description_parse_mode: str | None = None, + description_entities: Sequence["MessageEntity"] | None = None, *, reply_to_message_id: int | None = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1760,6 +1780,9 @@ async def send_poll( type=type, # pylint=pylint, allows_multiple_answers=allows_multiple_answers, correct_option_id=correct_option_id, + allows_revoting=allows_revoting, + shuffle_options=shuffle_options, + correct_option_ids=correct_option_ids, is_closed=is_closed, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, @@ -1769,6 +1792,8 @@ async def send_poll( write_timeout=write_timeout, connect_timeout=connect_timeout, pool_timeout=pool_timeout, + message_effect_id=message_effect_id, + allow_paid_broadcast=allow_paid_broadcast, explanation=explanation, explanation_parse_mode=explanation_parse_mode, open_period=open_period, @@ -1781,8 +1806,11 @@ async def send_poll( business_connection_id=business_connection_id, question_parse_mode=question_parse_mode, question_entities=question_entities, - message_effect_id=message_effect_id, - allow_paid_broadcast=allow_paid_broadcast, + description=description, + description_parse_mode=description_parse_mode, + description_entities=description_entities, + hide_results_until_closes=hide_results_until_closes, + allow_adding_options=allow_adding_options, ) async def send_gift( @@ -2712,3 +2740,34 @@ async def set_chat_member_tag( pool_timeout=pool_timeout, api_kwargs=api_kwargs, ) + + async def replace_token( + self, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> str: + """ + Shortcut for:: + + await bot.replace_managed_bot_token(user_id=update.effective_user.id, *args, **kwargs) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.replace_managed_bot_token`. + + .. versionadded:: NEXT.VERSION + + Returns: + :obj:`bool`: On success, :obj:`str` is returned. + """ + return await self.get_bot().replace_managed_bot_token( + user_id=self.id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) diff --git a/src/telegram/_utils/types.py b/src/telegram/_utils/types.py index 1b29e2e8de6..0684219ad01 100644 --- a/src/telegram/_utils/types.py +++ b/src/telegram/_utils/types.py @@ -25,7 +25,7 @@ """ import datetime as dtm -from collections.abc import Callable, Collection +from collections.abc import Callable, Collection, Sequence from pathlib import Path from typing import IO, TYPE_CHECKING, Any, Literal, TypeAlias, TypeVar, Union @@ -93,6 +93,11 @@ CorrectOptionID: TypeAlias = Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # pylint: disable=invalid-name +CorrectOptionIds: TypeAlias = Sequence[Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]] +""" +.. versionadded:: NEXT.VERSION +""" + MarkdownVersion: TypeAlias = Literal[1, 2] SocketOpt: TypeAlias = ( diff --git a/src/telegram/constants.py b/src/telegram/constants.py index 3ae66ff0b4b..144e6763892 100644 --- a/src/telegram/constants.py +++ b/src/telegram/constants.py @@ -181,7 +181,7 @@ class _AccentColor(NamedTuple): #: :data:`telegram.__bot_api_version_info__`. #: #: .. versionadded:: 20.0 -BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=9, minor=5) +BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=9, minor=6) #: :obj:`str`: Telegram Bot API #: version supported by this version of `python-telegram-bot`. Also available as #: :data:`telegram.__bot_api_version__`. @@ -2347,6 +2347,8 @@ class MessageType(StringEnum): """:obj:`str`: Messages with :attr:`telegram.Message.left_chat_member`.""" LOCATION = "location" """:obj:`str`: Messages with :attr:`telegram.Message.location`.""" + MANAGED_BOT_CREATED = "managed_bot_created" + """:obj:`str`: Messages with :attr:`telegram.Message.managed_bot_created`.""" MESSAGE_AUTO_DELETE_TIMER_CHANGED = "message_auto_delete_timer_changed" """:obj:`str`: Messages with :attr:`telegram.Message.message_auto_delete_timer_changed`.""" MIGRATE_TO_CHAT_ID = "migrate_to_chat_id" @@ -2367,6 +2369,16 @@ class MessageType(StringEnum): .. versionadded:: v22.2 """ + POLL_OPTION_ADDED = "poll_option_added" + """:obj:`str`: Messages with :attr:`telegram.Message.poll_option_added`. + + .. versionadded:: NEXT.VERSION + """ + POLL_OPTION_DELETED = "poll_option_deleted" + """:obj:`str`: Messages with :attr:`telegram.Message.poll_option_deleted`. + + .. versionadded:: NEXT.VERSION + """ SUGGESTED_POST_APPROVAL_FAILED = "suggested_post_approval_failed" """:obj:`str`: Messages with :attr:`telegram.Message.suggested_post_approval_failed`. @@ -3439,11 +3451,20 @@ class PollLimit(IntEnum): Also used in the :paramref:`~telegram.Bot.send_poll.close_date` parameter of :meth:`telegram.Bot.send_poll`. """ - MAX_OPEN_PERIOD = 600 + MAX_OPEN_PERIOD = 2628000 """:obj:`int`: Maximum value allowed for the :paramref:`~telegram.Bot.send_poll.open_period` parameter of :meth:`telegram.Bot.send_poll`. Also used in the :paramref:`~telegram.Bot.send_poll.close_date` parameter of :meth:`telegram.Bot.send_poll`. + + .. versionchanged:: NEXT.VERSION + Changed from ``600`` to ``2628000`` since Bot API 9.6. + """ + MAX_DESCRIPTION_CHARACTERS = 1024 + """:obj:`int`: Maximum value allowed for the + :paramref:`~telegram.Bot.send_poll.description` parameter of :meth:`telegram.Bot.send_poll`. + + .. versionadded:: NEXT.VERSION """ @@ -3594,6 +3615,11 @@ class UpdateType(StringEnum): .. versionadded:: 21.6 """ + MANAGED_BOT = "managed_bot" + """:obj:`str`: Updates with :attr:`telegram.Update.managed_bot`. + + .. versionadded:: NEXT.VERSION + """ class InvoiceLimit(IntEnum): diff --git a/src/telegram/ext/__init__.py b/src/telegram/ext/__init__.py index 4c7fa9f5a8c..d286722b594 100644 --- a/src/telegram/ext/__init__.py +++ b/src/telegram/ext/__init__.py @@ -46,6 +46,7 @@ "InvalidCallbackData", "Job", "JobQueue", + "ManagedBotUpdatedHandler", "MessageHandler", "MessageReactionHandler", "PaidMediaPurchasedHandler", @@ -88,6 +89,7 @@ from ._handlers.commandhandler import CommandHandler from ._handlers.conversationhandler import ConversationHandler from ._handlers.inlinequeryhandler import InlineQueryHandler +from ._handlers.managedbotupdatedhandler import ManagedBotUpdatedHandler from ._handlers.messagehandler import MessageHandler from ._handlers.messagereactionhandler import MessageReactionHandler from ._handlers.paidmediapurchasedhandler import PaidMediaPurchasedHandler diff --git a/src/telegram/ext/_extbot.py b/src/telegram/ext/_extbot.py index c09b1a5f6d4..931115f7beb 100644 --- a/src/telegram/ext/_extbot.py +++ b/src/telegram/ext/_extbot.py @@ -63,6 +63,7 @@ InputPaidMedia, InputPollOption, InputProfilePhoto, + KeyboardButton, LinkPreviewOptions, MaskPosition, MenuButton, @@ -72,6 +73,7 @@ PhotoSize, Poll, PreparedInlineMessage, + PreparedKeyboardButton, ReactionType, ReplyParameters, SentWebAppMessage, @@ -98,6 +100,7 @@ from telegram._utils.types import ( BaseUrl, CorrectOptionID, + CorrectOptionIds, FileInput, JSONDict, ODVInput, @@ -3254,6 +3257,14 @@ async def send_poll( question_entities: Sequence["MessageEntity"] | None = None, message_effect_id: str | None = None, allow_paid_broadcast: bool | None = None, + allows_revoting: bool | None = None, + allow_adding_options: bool | None = None, + hide_results_until_closes: bool | None = None, + correct_option_ids: CorrectOptionIds | None = None, + description: str | None = None, + description_parse_mode: str | None = None, + description_entities: Sequence["MessageEntity"] | None = None, + shuffle_options: bool | None = None, *, reply_to_message_id: int | None = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3295,6 +3306,14 @@ async def send_poll( question_entities=question_entities, message_effect_id=message_effect_id, allow_paid_broadcast=allow_paid_broadcast, + allows_revoting=allows_revoting, + shuffle_options=shuffle_options, + correct_option_ids=correct_option_ids, + description=description, + description_parse_mode=description_parse_mode, + description_entities=description_entities, + hide_results_until_closes=hide_results_until_closes, + allow_adding_options=allow_adding_options, ) async def send_sticker( @@ -5511,6 +5530,69 @@ async def set_chat_member_tag( api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), ) + async def get_managed_bot_token( + self, + user_id: int, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + rate_limit_args: RLARGS | None = None, + ) -> str: + return await super().get_managed_bot_token( + user_id=user_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + + async def replace_managed_bot_token( + self, + user_id: int, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + rate_limit_args: RLARGS | None = None, + ) -> str: + return await super().replace_managed_bot_token( + user_id=user_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + + async def save_prepared_keyboard_button( + self, + user_id: int, + button: KeyboardButton, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + rate_limit_args: RLARGS | None = None, + ) -> PreparedKeyboardButton: + + return await super().save_prepared_keyboard_button( + user_id=user_id, + button=button, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + # updated camelCase aliases getMe = get_me sendMessage = send_message @@ -5677,3 +5759,6 @@ async def set_chat_member_tag( removeMyProfilePhoto = remove_my_profile_photo getUserProfileAudios = get_user_profile_audios setChatMemberTag = set_chat_member_tag + getManagedBotToken = get_managed_bot_token + replaceManagedBotToken = replace_managed_bot_token + savePreparedKeyboardButton = save_prepared_keyboard_button diff --git a/src/telegram/ext/_handlers/managedbotupdatedhandler.py b/src/telegram/ext/_handlers/managedbotupdatedhandler.py new file mode 100644 index 00000000000..2715f06c21e --- /dev/null +++ b/src/telegram/ext/_handlers/managedbotupdatedhandler.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2026 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""This module contains the ManagedBotUpdatedHandler class.""" + +from typing import TypeVar + +from telegram import Update +from telegram._utils.defaultvalue import DEFAULT_TRUE +from telegram._utils.types import SCT, DVType +from telegram.ext._handlers.basehandler import BaseHandler +from telegram.ext._utils._update_parsing import parse_chat_id, parse_username +from telegram.ext._utils.types import CCT, HandlerCallback + +RT = TypeVar("RT") + + +class ManagedBotUpdatedHandler(BaseHandler[Update, CCT, RT]): + """Handler class to handle + :attr:`updated Telegram Managed Bots `. + + .. versionadded:: NEXT.VERSION + + Args: + callback (:term:`coroutine function`): The callback function for this handler. Will be + called when :meth:`check_update` has determined that an update should be processed by + this handler. Callback signature:: + + async def callback(update: Update, context: CallbackContext) + user_id (:obj:`int` | Collection[:obj:`int`], optional): Filters requests to allow only + those which are from the specified user ID(s). The user ID(s) can be either the + creator of the managed bot or the bot itself. + + username (:obj:`str` | Collection[:obj:`str`], optional): Filters requests to allow only + those which are from the specified username(s). The username(s) can be either the + creator of the managed bot or the bot itself. + + block (:obj:`bool`, optional): Determines whether the return value of the callback should + be awaited before processing the next handler in + :meth:`telegram.ext.Application.process_update`. Defaults to :obj:`True`. + + .. seealso:: :wiki:`Concurrency` + Attributes: + callback (:term:`coroutine function`): The callback function for this handler. + block (:obj:`bool`): Determines whether the return value of the callback should be + awaited before processing the next handler in + :meth:`telegram.ext.Application.process_update`. + """ + + __slots__ = ( + "_user_ids", + "_usernames", + ) + + def __init__( + self: "ManagedBotUpdatedHandler[CCT, RT]", + callback: HandlerCallback[Update, CCT, RT], + user_id: SCT[int] | None = None, + username: SCT[str] | None = None, + block: DVType[bool] = DEFAULT_TRUE, + ): + super().__init__(callback, block=block) + + self._user_ids = parse_chat_id(user_id) + self._usernames = parse_username(username) + + def check_update(self, update: object) -> bool: + """Determines whether an update should be passed to this handler's :attr:`callback`. + + Args: + update (:class:`telegram.Update` | :obj:`object`): Incoming update. + + Returns: + :obj:`bool` + + """ + if isinstance(update, Update) and update.managed_bot: + if not self._user_ids and not self._usernames: + return True + if ( + update.managed_bot.user.id in self._user_ids + or update.managed_bot.bot.id in self._user_ids + ): + return True + return ( + update.managed_bot.user.username in self._usernames + or update.managed_bot.bot.username in self._usernames + ) + return False diff --git a/src/telegram/ext/filters.py b/src/telegram/ext/filters.py index 62901fa7a1c..d8fa167e7b0 100644 --- a/src/telegram/ext/filters.py +++ b/src/telegram/ext/filters.py @@ -1984,6 +1984,7 @@ def filter(self, update: Update) -> bool: or StatusUpdate.GIVEAWAY_COMPLETED.check_update(update) or StatusUpdate.GIVEAWAY_CREATED.check_update(update) or StatusUpdate.LEFT_CHAT_MEMBER.check_update(update) + or StatusUpdate.MANAGED_BOT_CREATED.check_update(update) or StatusUpdate.MESSAGE_AUTO_DELETE_TIMER_CHANGED.check_update(update) or StatusUpdate.MIGRATE.check_update(update) or StatusUpdate.NEW_CHAT_MEMBERS.check_update(update) @@ -1991,6 +1992,8 @@ def filter(self, update: Update) -> bool: or StatusUpdate.NEW_CHAT_TITLE.check_update(update) or StatusUpdate.PAID_MESSAGE_PRICE_CHANGED.check_update(update) or StatusUpdate.PINNED_MESSAGE.check_update(update) + or StatusUpdate.POLL_OPTION_ADDED.check_update(update) + or StatusUpdate.POLL_OPTION_DELETED.check_update(update) or StatusUpdate.PROXIMITY_ALERT_TRIGGERED.check_update(update) or StatusUpdate.REFUNDED_PAYMENT.check_update(update) or StatusUpdate.SUGGESTED_POST_APPROVAL_FAILED.check_update(update) @@ -2259,6 +2262,15 @@ def filter(self, message: Message) -> bool: LEFT_CHAT_MEMBER = _LeftChatMember(name="filters.StatusUpdate.LEFT_CHAT_MEMBER") """Messages that contain :attr:`telegram.Message.left_chat_member`.""" + class _ManagedBotCreated(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.managed_bot_created) + + MANAGED_BOT_CREATED = _ManagedBotCreated(name="filters.StatusUpdate.MANAGED_BOT_CREATED") + """Messages that contain :attr:`telegram.Message.managed_bot_created`.""" + class _MessageAutoDeleteTimerChanged(MessageFilter): __slots__ = () @@ -2333,6 +2345,28 @@ def filter(self, message: Message) -> bool: PINNED_MESSAGE = _PinnedMessage(name="filters.StatusUpdate.PINNED_MESSAGE") """Messages that contain :attr:`telegram.Message.pinned_message`.""" + class _PollOptionAdded(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.poll_option_added) + + POLL_OPTION_ADDED = _PollOptionAdded(name="filters.StatusUpdate.POLL_OPTION_ADDED") + """Messages that contain :attr:`telegram.Message.poll_option_added`. + .. versionadded:: NEXT.VERSION + """ + + class _PollOptionDeleted(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.poll_option_deleted) + + POLL_OPTION_DELETED = _PollOptionDeleted(name="filters.StatusUpdate.POLL_OPTION_DELETED") + """Messages that contain :attr:`telegram.Message.poll_option_deleted`. + .. versionadded:: NEXT.VERSION + """ + class _ProximityAlertTriggered(MessageFilter): __slots__ = () diff --git a/tests/_files/test_audio.py b/tests/_files/test_audio.py index 0bd9b2e6fd8..dd832966fde 100644 --- a/tests/_files/test_audio.py +++ b/tests/_files/test_audio.py @@ -263,7 +263,7 @@ async def test_send_all_args(self, bot, chat_id, audio_file, thumb_file, duratio assert message.audio.file_name == self.file_name assert message.audio.mime_type == self.mime_type assert message.audio.file_size == self.file_size - assert message.audio.thumbnail.file_size == self.thumb_file_size + assert message.audio.thumbnail.file_size in [self.thumb_file_size, 1395] assert message.audio.thumbnail.width == self.thumb_width assert message.audio.thumbnail.height == self.thumb_height assert message.has_protected_content diff --git a/tests/_files/test_photo.py b/tests/_files/test_photo.py index 83ba28ea701..93e0ac40ea1 100644 --- a/tests/_files/test_photo.py +++ b/tests/_files/test_photo.py @@ -75,7 +75,7 @@ def test_expected_values(self, photo, thumb): assert thumb.height == 90 # File sizes don't seem to be consistent, so we use the values that we have observed # so far - assert thumb.file_size in [1475, 1477] + assert thumb.file_size in [1474, 1475, 1477] def test_de_json(self, offline_bot, photo): json_dict = { diff --git a/tests/_files/test_video.py b/tests/_files/test_video.py index 8be6d9df2cf..4a34e126668 100644 --- a/tests/_files/test_video.py +++ b/tests/_files/test_video.py @@ -319,7 +319,7 @@ async def test_send_all_args( assert message.caption == self.caption.replace("*", "") - assert message.video.thumbnail.file_size == self.thumb_file_size + assert message.video.thumbnail.file_size in [self.thumb_file_size, 1769] assert message.video.thumbnail.width == self.thumb_width assert message.video.thumbnail.height == self.thumb_height diff --git a/tests/_files/test_videonote.py b/tests/_files/test_videonote.py index 3af79baf497..c2993021883 100644 --- a/tests/_files/test_videonote.py +++ b/tests/_files/test_videonote.py @@ -263,7 +263,7 @@ async def test_send_all_args( assert message.video_note.duration == video_note.duration assert message.video_note.file_size == video_note.file_size - assert message.video_note.thumbnail.file_size == self.thumb_file_size + assert message.video_note.thumbnail.file_size in [self.thumb_file_size, 11631] assert message.video_note.thumbnail.width == self.thumb_width assert message.video_note.thumbnail.height == self.thumb_height assert message.has_protected_content diff --git a/tests/auxil/dummy_objects.py b/tests/auxil/dummy_objects.py index 611e19a67f0..c0ff0ce0cf5 100644 --- a/tests/auxil/dummy_objects.py +++ b/tests/auxil/dummy_objects.py @@ -29,6 +29,7 @@ Poll, PollOption, PreparedInlineMessage, + PreparedKeyboardButton, SentWebAppMessage, StarAmount, StarTransaction, @@ -133,6 +134,7 @@ type="dummy_type", allows_multiple_answers=False, ), + "PreparedKeyboardButton": PreparedKeyboardButton(id=1234), "PreparedInlineMessage": PreparedInlineMessage(id="dummy_id", expiration_date=_DUMMY_DATE), "SentWebAppMessage": SentWebAppMessage(inline_message_id="dummy_inline_message_id"), "StarAmount": StarAmount(amount=100, nanostar_amount=356), diff --git a/tests/ext/test_filters.py b/tests/ext/test_filters.py index 5eccc49a6c3..e68fb0dbc09 100644 --- a/tests/ext/test_filters.py +++ b/tests/ext/test_filters.py @@ -1183,6 +1183,21 @@ def test_filters_status_update(self, update): assert filters.StatusUpdate.CHAT_OWNER_LEFT.check_update(update) update.message.chat_owner_left = None + update.message.poll_option_added = "poll_option_added" + assert filters.StatusUpdate.ALL.check_update(update) + assert filters.StatusUpdate.POLL_OPTION_ADDED.check_update(update) + update.message.poll_option_added = None + + update.message.poll_option_deleted = "poll_option_deleted" + assert filters.StatusUpdate.ALL.check_update(update) + assert filters.StatusUpdate.POLL_OPTION_DELETED.check_update(update) + update.message.poll_option_deleted = None + + update.message.managed_bot_created = "test" + assert filters.StatusUpdate.ALL.check_update(update) + assert filters.StatusUpdate.MANAGED_BOT_CREATED.check_update(update) + update.message.managed_bot_created = None + def test_filters_forwarded(self, update): assert filters.FORWARDED.check_update(update) update.message.forward_origin = MessageOriginHiddenUser(dtm.datetime.utcnow(), 1) diff --git a/tests/ext/test_managedbotupdatedhandler.py b/tests/ext/test_managedbotupdatedhandler.py new file mode 100644 index 00000000000..9fe5781d1d2 --- /dev/null +++ b/tests/ext/test_managedbotupdatedhandler.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2026 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +import asyncio +import datetime as dtm + +import pytest + +from telegram import ( + Bot, + CallbackQuery, + Chat, + ChosenInlineResult, + ManagedBotUpdated, + Message, + PreCheckoutQuery, + ShippingQuery, + Update, + User, +) +from telegram._utils.datetime import UTC +from telegram.ext import CallbackContext, JobQueue, ManagedBotUpdatedHandler +from tests.auxil.slots import mro_slots + +message = Message(1, None, Chat(1, ""), from_user=User(1, "", False), text="Text") + +params = [ + {"message": message}, + {"edited_message": message}, + {"callback_query": CallbackQuery(1, User(1, "", False), "chat", message=message)}, + {"channel_post": message}, + {"edited_channel_post": message}, + {"chosen_inline_result": ChosenInlineResult("id", User(1, "", False), "")}, + {"shipping_query": ShippingQuery("id", User(1, "", False), "", None)}, + {"pre_checkout_query": PreCheckoutQuery("id", User(1, "", False), "", 0, "")}, + {"callback_query": CallbackQuery(1, User(1, "", False), "chat")}, +] + +ids = ( + "message", + "edited_message", + "callback_query", + "channel_post", + "edited_channel_post", + "chosen_inline_result", + "shipping_query", + "pre_checkout_query", + "callback_query_without_message", +) + + +@pytest.fixture(scope="class", params=params, ids=ids) +def false_update(request): + return Update(update_id=2, **request.param) + + +@pytest.fixture(scope="class") +def time(): + return dtm.datetime.now(tz=UTC) + + +@pytest.fixture(scope="class") +def managed_bot_updated(bot): + bmd = ManagedBotUpdated( + user=User(1, "", False, username="user_a"), + bot=User(2, "", True, username="test_bot"), + ) + bmd.set_bot(bot) + return bmd + + +@pytest.fixture +def managed_bot_updated_update(bot, managed_bot_updated): + return Update(0, managed_bot=managed_bot_updated) + + +class TestManagedBotUpdatedHandler: + test_flag = False + + def test_slot_behaviour(self): + action = ManagedBotUpdatedHandler(self.callback) + for attr in action.__slots__: + assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot" + + @pytest.fixture(autouse=True) + def _reset(self): + self.test_flag = False + + async def callback(self, update, context): + self.test_flag = ( + isinstance(context, CallbackContext) + and isinstance(context.bot, Bot) + and isinstance(update, Update) + and isinstance(context.update_queue, asyncio.Queue) + and isinstance(context.job_queue, JobQueue) + and isinstance(context.user_data, dict) + and isinstance(context.bot_data, dict) + and isinstance( + update.managed_bot, + ManagedBotUpdated, + ) + ) + + def test_with_user_id(self, managed_bot_updated_update): + handler = ManagedBotUpdatedHandler(self.callback, user_id=1) + assert handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, user_id=[1]) + assert handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, user_id=2) + assert handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, user_id=2, username="@user_a") + assert handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, user_id=[1, 2]) + assert handler.check_update(managed_bot_updated_update) + + handler = ManagedBotUpdatedHandler(self.callback, user_id=3) + assert not handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, user_id=[3]) + assert not handler.check_update(managed_bot_updated_update) + + def test_with_username(self, managed_bot_updated_update): + handler = ManagedBotUpdatedHandler(self.callback, username="user_a") + assert handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, username="@user_a") + assert handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, username=["user_a"]) + assert handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, username=["@user_a"]) + assert handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, username=["test_bot"]) + assert handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, username=["@test_bot"]) + assert handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, user_id=[2], username="@user_b") + assert handler.check_update(managed_bot_updated_update) + + handler = ManagedBotUpdatedHandler(self.callback, username="user_b") + assert not handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, username="@user_b") + assert not handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, username=["user_b"]) + assert not handler.check_update(managed_bot_updated_update) + handler = ManagedBotUpdatedHandler(self.callback, username=["@user_b"]) + assert not handler.check_update(managed_bot_updated_update) + + managed_bot_updated_update.managed_bot.user._unfreeze() + managed_bot_updated_update.managed_bot.user.username = None + assert not handler.check_update(managed_bot_updated_update) + + def test_other_update_types(self, false_update): + handler = ManagedBotUpdatedHandler(self.callback) + assert not handler.check_update(false_update) + assert not handler.check_update(True) + + async def test_context(self, app, managed_bot_updated_update): + handler = ManagedBotUpdatedHandler(callback=self.callback) + app.add_handler(handler) + + async with app: + await app.process_update(managed_bot_updated_update) + assert self.test_flag diff --git a/tests/test_bot.py b/tests/test_bot.py index 1585c581aa1..2fd77550ecb 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -58,6 +58,8 @@ InputPollOption, InputProfilePhotoStatic, InputTextMessageContent, + KeyboardButton, + KeyboardButtonRequestManagedBot, LabeledPrice, LinkPreviewOptions, MenuButton, @@ -70,6 +72,7 @@ Poll, PollOption, PreparedInlineMessage, + PreparedKeyboardButton, ReactionTypeCustomEmoji, ReactionTypeEmoji, ReplyParameters, @@ -99,7 +102,7 @@ from telegram.ext import ExtBot, InvalidCallbackData from telegram.helpers import escape_markdown from telegram.request import BaseRequest, HTTPXRequest, RequestData -from telegram.warnings import PTBUserWarning +from telegram.warnings import PTBDeprecationWarning, PTBUserWarning from tests.auxil.bot_method_checks import check_defaults_handling from tests.auxil.ci_bots import FALLBACKS from tests.auxil.envvars import GITHUB_ACTIONS @@ -2847,6 +2850,76 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): await offline_bot.set_chat_member_tag(1234, 5678, "This is a tag") + async def test_send_poll_warn_correct_option_id(self, offline_bot, monkeypatch, recwarn): + async def make_first_assert(url, request_data: RequestData, *args, **kwargs): + assert request_data.parameters.get("correct_option_ids") == [1] + assert request_data.parameters.get("correct_option_id") is None + return make_message("dummy reply").to_dict() + + async def make_second_assert(url, request_data: RequestData, *args, **kwargs): + assert request_data.parameters.get("correct_option_ids") == [1, 2] + assert request_data.parameters.get("correct_option_id") is None + return make_message("dummy reply").to_dict() + + monkeypatch.setattr(offline_bot.request, "post", make_first_assert) + + await offline_bot.send_poll( + 1, + question="question", + options=["option1", "option2"], + correct_option_id=1, + ) + + w = recwarn.pop() + assert issubclass(w.category, PTBDeprecationWarning) + assert "correct_option_id" in str(w.message) + + # Test that correct_option_ids takes priority when both correct_option_id(s) are given + monkeypatch.setattr(offline_bot.request, "post", make_second_assert) + assert await offline_bot.send_poll( + 1, + question="question", + options=["option1", "option2"], + correct_option_id=1, + correct_option_ids=[1, 2], + ) + + # TODO: If we create a managed bot, we could test this for real + async def test_get_managed_bot_token(self, offline_bot, monkeypatch): + + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + assert request_data.parameters.get("user_id") == 1234 + + monkeypatch.setattr(offline_bot.request, "post", make_assertion) + await offline_bot.get_managed_bot_token(1234) + + async def test_replace_managed_bot_token(self, offline_bot, monkeypatch): + + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + assert request_data.parameters.get("user_id") == 1234 + + monkeypatch.setattr(offline_bot.request, "post", make_assertion) + await offline_bot.replace_managed_bot_token(1234) + + # Need real user id to test with request. Otherwise returns User_id_invalid + async def test_save_prepared_keyboard_button(self, offline_bot, monkeypatch): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + assert request_data.parameters.get("user_id") == 1234 + assert ( + request_data.parameters.get("button") + == KeyboardButton( + text="this", request_managed_bot=KeyboardButtonRequestManagedBot(1234) + ).to_dict() + ) + return PreparedKeyboardButton(234).to_dict() + + monkeypatch.setattr(offline_bot.request, "post", make_assertion) + inst = await offline_bot.save_prepared_keyboard_button( + 1234, + KeyboardButton(text="this", request_managed_bot=KeyboardButtonRequestManagedBot(1234)), + ) + assert isinstance(inst, PreparedKeyboardButton) + class TestBotWithRequest: """ diff --git a/tests/test_constants.py b/tests/test_constants.py index 40fd2d41814..c5e7ff000bf 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -208,6 +208,7 @@ def is_type_attribute(name: str) -> bool: "paid_star_count", "is_paid_post", "reply_to_checklist_task_id", + "reply_to_poll_option_id", } @pytest.mark.parametrize( diff --git a/tests/test_keyboardbutton.py b/tests/test_keyboardbutton.py index 22434be3efb..a664b81cd8e 100644 --- a/tests/test_keyboardbutton.py +++ b/tests/test_keyboardbutton.py @@ -23,6 +23,7 @@ KeyboardButton, KeyboardButtonPollType, KeyboardButtonRequestChat, + KeyboardButtonRequestManagedBot, KeyboardButtonRequestUsers, WebAppInfo, ) @@ -41,6 +42,7 @@ def keyboard_button(): request_users=KeyboardButtonTestBase.request_users, style=KeyboardButtonTestBase.style, icon_custom_emoji_id=KeyboardButtonTestBase.icon_custom_emoji_id, + request_managed_bot=KeyboardButtonTestBase.request_managed_bot, ) @@ -54,6 +56,7 @@ class KeyboardButtonTestBase: request_users = KeyboardButtonRequestUsers(2) style = "primary" icon_custom_emoji_id = "5237829955978547322" + request_managed_bot = KeyboardButtonRequestManagedBot(4, "suggested_name", "username") class TestKeyboardButtonWithoutRequest(KeyboardButtonTestBase): @@ -73,6 +76,7 @@ def test_expected_values(self, keyboard_button): assert keyboard_button.request_users == self.request_users assert keyboard_button.style == self.style assert keyboard_button.icon_custom_emoji_id == self.icon_custom_emoji_id + assert keyboard_button.request_managed_bot == self.request_managed_bot def test_to_dict(self, keyboard_button): keyboard_button_dict = keyboard_button.to_dict() @@ -87,6 +91,10 @@ def test_to_dict(self, keyboard_button): assert keyboard_button_dict["request_users"] == keyboard_button.request_users.to_dict() assert keyboard_button_dict["style"] == keyboard_button.style assert keyboard_button_dict["icon_custom_emoji_id"] == keyboard_button.icon_custom_emoji_id + assert ( + keyboard_button_dict["request_managed_bot"] + == keyboard_button.request_managed_bot.to_dict() + ) @pytest.mark.parametrize("request_user", [True, False]) def test_de_json(self, request_user): @@ -100,6 +108,7 @@ def test_de_json(self, request_user): "request_users": self.request_users.to_dict(), "style": self.style, "icon_custom_emoji_id": self.icon_custom_emoji_id, + "request_managed_bot": self.request_managed_bot.to_dict(), } if request_user: json_dict["request_user"] = {"request_id": 2} @@ -119,6 +128,7 @@ def test_de_json(self, request_user): assert keyboard_button.request_users == self.request_users assert keyboard_button.style == self.style assert keyboard_button.icon_custom_emoji_id == self.icon_custom_emoji_id + assert keyboard_button.request_managed_bot == self.request_managed_bot def test_equality(self): a = KeyboardButton("test", request_contact=True) diff --git a/tests/test_keyboardbuttonrequestmanagedbot.py b/tests/test_keyboardbuttonrequestmanagedbot.py new file mode 100644 index 00000000000..4f3fa95b73e --- /dev/null +++ b/tests/test_keyboardbuttonrequestmanagedbot.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2026 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +import pytest + +from telegram import KeyboardButtonRequestManagedBot +from tests.auxil.slots import mro_slots + + +@pytest.fixture(scope="module") +def keyboard_button_request_managed_bot(): + return KeyboardButtonRequestManagedBot( + KeyboardButtonRequestManagedBotTestBase.request_id, + KeyboardButtonRequestManagedBotTestBase.suggested_name, + KeyboardButtonRequestManagedBotTestBase.suggested_username, + ) + + +class KeyboardButtonRequestManagedBotTestBase: + request_id = 4 + suggested_name = "suggested_name" + suggested_username = "username" + + +class TestKeyboardButtonRequestManagedBotWithoutRequest(KeyboardButtonRequestManagedBotTestBase): + def test_slot_behaviour(self, keyboard_button_request_managed_bot): + inst = keyboard_button_request_managed_bot + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_de_json(self, offline_bot): + json_dict = { + "request_id": self.request_id, + "suggested_name": self.suggested_name, + "suggested_username": self.suggested_username, + } + keyboard_button_request_managed_bot = KeyboardButtonRequestManagedBot.de_json( + json_dict, offline_bot + ) + assert keyboard_button_request_managed_bot.api_kwargs == {} + assert keyboard_button_request_managed_bot.request_id == self.request_id + assert keyboard_button_request_managed_bot.suggested_name == self.suggested_name + assert keyboard_button_request_managed_bot.suggested_username == self.suggested_username + + def test_to_dict(self): + keyboard_button_request_managed_bot = KeyboardButtonRequestManagedBot( + self.request_id, + self.suggested_name, + self.suggested_username, + ) + keyboard_button_request_managed_bot_dict = keyboard_button_request_managed_bot.to_dict() + assert ( + keyboard_button_request_managed_bot_dict["request_id"] + == keyboard_button_request_managed_bot.request_id + ) + assert ( + keyboard_button_request_managed_bot_dict["suggested_name"] + == keyboard_button_request_managed_bot.suggested_name + ) + assert ( + keyboard_button_request_managed_bot_dict["suggested_username"] + == keyboard_button_request_managed_bot.suggested_username + ) + + def test_equality(self): + a = KeyboardButtonRequestManagedBot( + self.request_id, self.suggested_name, self.suggested_username + ) + b = KeyboardButtonRequestManagedBot( + self.request_id, self.suggested_name, self.suggested_username + ) + c = KeyboardButtonRequestManagedBot(1, self.suggested_name, self.suggested_username) + d = "not a KeyboardButtonRequestManagedBot" + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) diff --git a/tests/test_managedbot.py b/tests/test_managedbot.py new file mode 100644 index 00000000000..9ec374c5894 --- /dev/null +++ b/tests/test_managedbot.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2026 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +import pytest + +from telegram import ManagedBotCreated, ManagedBotUpdated, User +from tests.auxil.slots import mro_slots + + +@pytest.fixture +def managed_bot_created(): + return ManagedBotCreated( + bot=User( + id=ManagedBotTestBase.bot_id, + is_bot=ManagedBotTestBase.is_bot, + first_name=ManagedBotTestBase.bot_first_name, + username=ManagedBotTestBase.bot_username, + ), + ) + + +@pytest.fixture +def managed_bot_updated(): + return ManagedBotUpdated( + user=User( + id=ManagedBotTestBase.bot_creator_id, + is_bot=ManagedBotTestBase.is_bot, + first_name=ManagedBotTestBase.bot_creator_first_name, + username=ManagedBotTestBase.bot_creator_username, + ), + bot=User( + id=ManagedBotTestBase.bot_id, + is_bot=ManagedBotTestBase.is_bot, + first_name=ManagedBotTestBase.bot_first_name, + username=ManagedBotTestBase.bot_username, + ), + ) + + +class ManagedBotTestBase: + bot_creator_id = 123 + bot_creator_first_name = "TestBotManager" + bot_creator_username = "test_bot_manager" + bot_id = 321 + bot_first_name = "TestBot" + bot_username = "test_bot" + is_bot = True + + +class TestManagedBotCreatedWithoutRequest(ManagedBotTestBase): + def test_slot_behaviour(self, managed_bot_created): + inst = managed_bot_created + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_de_json(self, offline_bot, managed_bot_created): + json_dict = { + "bot": managed_bot_created.bot.to_dict(), + } + managed_bot_created = ManagedBotCreated.de_json(json_dict, offline_bot) + assert managed_bot_created.api_kwargs == {} + assert managed_bot_created.bot.id == self.bot_id + assert managed_bot_created.bot.is_bot is True + assert managed_bot_created.bot.first_name == self.bot_first_name + assert managed_bot_created.bot.username == self.bot_username + + def test_to_dict(self, managed_bot_created): + managed_bot_created_dict = managed_bot_created.to_dict() + assert managed_bot_created_dict["bot"]["id"] == self.bot_id + assert managed_bot_created_dict["bot"]["is_bot"] is True + assert managed_bot_created_dict["bot"]["first_name"] == self.bot_first_name + assert managed_bot_created_dict["bot"]["username"] == self.bot_username + + def test_equality(self, managed_bot_created): + managed_bot_created_2 = ManagedBotCreated( + bot=User( + id=self.bot_id, + is_bot=True, + first_name=self.bot_first_name, + username=self.bot_username, + ), + ) + managed_bot_created_3 = ManagedBotCreated( + bot=User( + id=4534, is_bot=True, first_name=self.bot_first_name, username=self.bot_username + ), + ) + not_a_managed_bot_created = ManagedBotUpdated( + user=User( + id=self.bot_creator_id, + is_bot=True, + first_name=self.bot_creator_first_name, + username=self.bot_creator_username, + ), + bot=User( + id=self.bot_id, + is_bot=True, + first_name=self.bot_first_name, + username=self.bot_username, + ), + ) + + assert managed_bot_created == managed_bot_created_2 + assert hash(managed_bot_created) == hash(managed_bot_created_2) + + assert managed_bot_created != managed_bot_created_3 + assert hash(managed_bot_created) != hash(managed_bot_created_3) + + assert managed_bot_created != not_a_managed_bot_created + assert hash(managed_bot_created) != hash(not_a_managed_bot_created) + + +class TestManagedBotUpdatedWithoutRequest(ManagedBotTestBase): + def test_slot_behaviour(self, managed_bot_updated): + inst = managed_bot_updated + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_de_json(self, offline_bot, managed_bot_updated): + json_dict = { + "user": managed_bot_updated.user.to_dict(), + "bot": managed_bot_updated.bot.to_dict(), + } + managed_bot_updated_new = ManagedBotUpdated.de_json(json_dict, offline_bot) + assert managed_bot_updated_new.api_kwargs == {} + assert managed_bot_updated_new.user == managed_bot_updated.user + assert managed_bot_updated_new.bot == managed_bot_updated.bot + + def test_to_dict(self, managed_bot_updated): + managed_bot_updated_dict = managed_bot_updated.to_dict() + assert managed_bot_updated_dict["user"] == managed_bot_updated.user.to_dict() + assert managed_bot_updated_dict["bot"] == managed_bot_updated.bot.to_dict() + + def test_equality(self, managed_bot_updated): + managed_bot_updated_2 = ManagedBotUpdated( + user=User( + id=self.bot_creator_id, + is_bot=True, + first_name=self.bot_creator_first_name, + username=self.bot_creator_username, + ), + bot=User( + id=self.bot_id, + is_bot=True, + first_name=self.bot_first_name, + username=self.bot_username, + ), + ) + managed_bot_updated_3 = ManagedBotUpdated( + user=User( + id=4534, + is_bot=True, + first_name=self.bot_creator_first_name, + username=self.bot_creator_username, + ), + bot=User( + id=self.bot_id, + is_bot=True, + first_name=self.bot_first_name, + username=self.bot_username, + ), + ) + not_a_managed_bot_updated = ManagedBotCreated( + bot=User( + id=self.bot_id, + is_bot=True, + first_name=self.bot_first_name, + username=self.bot_username, + ), + ) + + assert managed_bot_updated == managed_bot_updated_2 + assert hash(managed_bot_updated) == hash(managed_bot_updated_2) + + assert managed_bot_updated != managed_bot_updated_3 + assert hash(managed_bot_updated) != hash(managed_bot_updated_3) + + assert managed_bot_updated != not_a_managed_bot_updated + assert hash(managed_bot_updated) != hash(not_a_managed_bot_updated) diff --git a/tests/test_message.py b/tests/test_message.py index 2873f17414c..958c59b3109 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -56,6 +56,7 @@ Invoice, LinkPreviewOptions, Location, + ManagedBotCreated, Message, MessageAutoDeleteTimerChanged, MessageEntity, @@ -67,6 +68,7 @@ PhotoSize, Poll, PollOption, + PollOptionAdded, ProximityAlertTriggered, RefundedPayment, ReplyParameters, @@ -102,6 +104,7 @@ WebAppData, ) from telegram._directmessagestopic import DirectMessagesTopic +from telegram._poll import PollOptionDeleted from telegram._utils.datetime import UTC from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import ODVInput @@ -436,6 +439,10 @@ def message(bot): {"chat_owner_changed": ChatOwnerChanged(new_owner=User(4, "Snow", False))}, {"chat_owner_left": ChatOwnerLeft(new_owner=User(5, "Crash", False))}, {"sender_tag": "This is a tag"}, + {"poll_option_added": PollOptionAdded(option_persistent_id="abc", option_text="this")}, + {"poll_option_deleted": PollOptionDeleted(option_persistent_id="abc", option_text="this")}, + {"reply_to_poll_option_id": "3123"}, + {"managed_bot_created": ManagedBotCreated(bot=User(6, "ManagedBot", True))}, ], ids=[ "reply", @@ -530,6 +537,10 @@ def message(bot): "chat_owner_changed", "chat_owner_left", "sender_tag", + "poll_option_added", + "poll_option_deleted", + "reply_to_poll_option_id", + "managed_bot_created", ], ) def message_params(bot, request): diff --git a/tests/test_official/exceptions.py b/tests/test_official/exceptions.py index 1f7a86f0ebc..f62ef853990 100644 --- a/tests/test_official/exceptions.py +++ b/tests/test_official/exceptions.py @@ -18,6 +18,8 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains exceptions to our API compared to the official API.""" +from collections.abc import Sequence + from telegram import Animation, Audio, Document, Gift, PhotoSize, Sticker, Video, VideoNote, Voice from tests.test_official.helpers import _get_params_base @@ -71,7 +73,10 @@ class ParamTypeCheckingExceptions: # too complex to compare/predict with official API # structure: class/method_name: {param_name: reduced form of annotation} COMPLEX_TYPES = { - "send_poll": {"correct_option_id": int}, # actual: Literal + "send_poll": { + # "correct_option_id": int, + "correct_option_ids": Sequence[int] + }, "get_file": { "file_id": str, # actual: Union[str, objs_with_file_id_attr] }, @@ -214,7 +219,11 @@ def ignored_param_requirements(object_name: str) -> set[str]: # Arguments that are optional arguments for now for backwards compatibility -BACKWARDS_COMPAT_KWARGS: dict[str, set[str]] = {} +BACKWARDS_COMPAT_KWARGS: dict[str, set[str]] = { + "PollOption": {"persistent_id"}, + "PollAnswer": {"option_persistent_ids"}, + "Poll": {"allows_revoting"}, +} def backwards_compat_kwargs(object_name: str) -> set[str]: diff --git a/tests/test_poll.py b/tests/test_poll.py index 2edc93d3d9f..0f1998ed17d 100644 --- a/tests/test_poll.py +++ b/tests/test_poll.py @@ -19,7 +19,17 @@ import pytest -from telegram import Chat, InputPollOption, MessageEntity, Poll, PollAnswer, PollOption, User +from telegram import ( + Chat, + InputPollOption, + MaybeInaccessibleMessage, + MessageEntity, + Poll, + PollAnswer, + PollOption, + User, +) +from telegram._poll import PollOptionAdded, PollOptionDeleted from telegram._utils.datetime import UTC, to_timestamp from telegram.constants import PollType from telegram.warnings import PTBDeprecationWarning @@ -108,6 +118,9 @@ def poll_option(): text=PollOptionTestBase.text, voter_count=PollOptionTestBase.voter_count, text_entities=PollOptionTestBase.text_entities, + added_by_user=PollOptionTestBase.added_by_user, + added_by_chat=PollOptionTestBase.added_by_chat, + addition_date=PollOptionTestBase.addition_date, ) out._unfreeze() return out @@ -120,6 +133,9 @@ class PollOptionTestBase: MessageEntity(MessageEntity.BOLD, 0, 4), MessageEntity(MessageEntity.ITALIC, 5, 6), ] + added_by_user = User(1, "test_user", False) + added_by_chat = Chat(1, "test_chat") + addition_date = dtm.datetime.now(dtm.timezone.utc) class TestPollOptionWithoutRequest(PollOptionTestBase): @@ -129,26 +145,23 @@ def test_slot_behaviour(self, poll_option): assert len(mro_slots(poll_option)) == len(set(mro_slots(poll_option))), "duplicate slot" def test_de_json(self): - json_dict = {"text": self.text, "voter_count": self.voter_count} - poll_option = PollOption.de_json(json_dict, None) - assert poll_option.api_kwargs == {} - - assert poll_option.text == self.text - assert poll_option.voter_count == self.voter_count - - def test_de_json_all(self): json_dict = { "text": self.text, "voter_count": self.voter_count, "text_entities": [e.to_dict() for e in self.text_entities], + "added_by_user": self.added_by_user.to_dict(), + "added_by_chat": self.added_by_chat.to_dict(), + "addition_date": to_timestamp(self.addition_date), } poll_option = PollOption.de_json(json_dict, None) - assert poll_option.api_kwargs == {} assert poll_option.text == self.text assert poll_option.voter_count == self.voter_count assert poll_option.text_entities == tuple(self.text_entities) + assert poll_option.added_by_user == self.added_by_user + assert poll_option.added_by_chat == self.added_by_chat + assert abs((poll_option.addition_date - self.addition_date).total_seconds()) < 1 def test_to_dict(self, poll_option): poll_option_dict = poll_option.to_dict() @@ -159,6 +172,9 @@ def test_to_dict(self, poll_option): assert poll_option_dict["text_entities"] == [ e.to_dict() for e in poll_option.text_entities ] + assert poll_option_dict["added_by_user"] == poll_option.added_by_user.to_dict() + assert poll_option_dict["added_by_chat"] == poll_option.added_by_chat.to_dict() + assert poll_option_dict["addition_date"] == to_timestamp(poll_option.addition_date) def test_parse_entity(self, poll_option): entity = MessageEntity(MessageEntity.BOLD, 0, 4) @@ -201,6 +217,7 @@ def poll_answer(): PollAnswerTestBase.option_ids, PollAnswerTestBase.user, PollAnswerTestBase.voter_chat, + PollAnswerTestBase.option_persistent_ids, ) @@ -209,6 +226,7 @@ class PollAnswerTestBase: option_ids = [2] user = User(1, "", False) voter_chat = Chat(1, "") + option_persistent_ids = ["123"] class TestPollAnswerWithoutRequest(PollAnswerTestBase): @@ -218,6 +236,7 @@ def test_de_json(self): "option_ids": self.option_ids, "user": self.user.to_dict(), "voter_chat": self.voter_chat.to_dict(), + "option_persistent_ids": self.option_persistent_ids, } poll_answer = PollAnswer.de_json(json_dict, None) assert poll_answer.api_kwargs == {} @@ -226,6 +245,7 @@ def test_de_json(self): assert poll_answer.option_ids == tuple(self.option_ids) assert poll_answer.user == self.user assert poll_answer.voter_chat == self.voter_chat + assert poll_answer.option_persistent_ids == tuple(self.option_persistent_ids) def test_to_dict(self, poll_answer): poll_answer_dict = poll_answer.to_dict() @@ -235,6 +255,7 @@ def test_to_dict(self, poll_answer): assert poll_answer_dict["option_ids"] == list(poll_answer.option_ids) assert poll_answer_dict["user"] == poll_answer.user.to_dict() assert poll_answer_dict["voter_chat"] == poll_answer.voter_chat.to_dict() + assert poll_answer_dict["option_persistent_ids"] == list(poll_answer.option_persistent_ids) def test_equality(self): a = PollAnswer(123, [2], self.user, self.voter_chat) @@ -276,6 +297,10 @@ def poll(): open_period=PollTestBase.open_period, close_date=PollTestBase.close_date, question_entities=PollTestBase.question_entities, + allows_revoting=PollTestBase.allows_revoting, + correct_option_ids=PollTestBase.correct_option_ids, + description=PollTestBase.description, + description_entities=PollTestBase.description_entities, ) poll._unfreeze() return poll @@ -301,6 +326,10 @@ class PollTestBase: MessageEntity(MessageEntity.BOLD, 0, 4), MessageEntity(MessageEntity.ITALIC, 5, 8), ] + allows_revoting = True + correct_option_ids = [1, 2] + description = "description" + description_entities = [MessageEntity(MessageEntity.ITALIC, 0, 11)] class TestPollWithoutRequest(PollTestBase): @@ -319,6 +348,10 @@ def test_de_json(self, offline_bot): "open_period": int(self.open_period.total_seconds()), "close_date": to_timestamp(self.close_date), "question_entities": [e.to_dict() for e in self.question_entities], + "allows_revoting": self.allows_revoting, + "correct_option_ids": self.correct_option_ids, + "description": self.description, + "description_entities": [e.to_dict() for e in self.description_entities], } poll = Poll.de_json(json_dict, offline_bot) assert poll.api_kwargs == {} @@ -341,6 +374,10 @@ def test_de_json(self, offline_bot): assert abs(poll.close_date - self.close_date) < dtm.timedelta(seconds=1) assert to_timestamp(poll.close_date) == to_timestamp(self.close_date) assert poll.question_entities == tuple(self.question_entities) + assert poll.allows_revoting == self.allows_revoting + assert poll.correct_option_ids == tuple(self.correct_option_ids) + assert poll.description == self.description + assert poll.description_entities == tuple(self.description_entities) def test_de_json_localization(self, tz_bot, offline_bot, raw_bot): json_dict = { @@ -357,6 +394,10 @@ def test_de_json_localization(self, tz_bot, offline_bot, raw_bot): "open_period": int(self.open_period.total_seconds()), "close_date": to_timestamp(self.close_date), "question_entities": [e.to_dict() for e in self.question_entities], + "allows_revoting": self.allows_revoting, + "correct_option_ids": self.correct_option_ids, + "description": self.description, + "description_entities": [e.to_dict() for e in self.description_entities], } poll_raw = Poll.de_json(json_dict, raw_bot) @@ -390,6 +431,12 @@ def test_to_dict(self, poll): assert poll_dict["open_period"] == int(self.open_period.total_seconds()) assert poll_dict["close_date"] == to_timestamp(poll.close_date) assert poll_dict["question_entities"] == [e.to_dict() for e in poll.question_entities] + assert poll_dict["allows_revoting"] == poll.allows_revoting + assert poll_dict["correct_option_ids"] == list(poll.correct_option_ids) + assert poll_dict["description"] == poll.description + assert poll_dict["description_entities"] == [ + e.to_dict() for e in poll.description_entities + ] def test_time_period_properties(self, PTB_TIMEDELTA, poll): if PTB_TIMEDELTA: @@ -409,6 +456,26 @@ def test_time_period_int_deprecated(self, recwarn, PTB_TIMEDELTA, poll): assert "`open_period` will be of type `datetime.timedelta`" in str(recwarn[0].message) assert recwarn[0].category is PTBDeprecationWarning + def test_correct_option_id_deprecated(self, recwarn, poll): + poll.correct_option_id + + assert len(recwarn) == 1 + assert "The attribute `correct_option_id` is deprecated" in str(recwarn[0].message) + assert recwarn[0].category is PTBDeprecationWarning + + poll = Poll( + PollTestBase.id_, + PollTestBase.question, + PollTestBase.options, + PollTestBase.total_voter_count, + PollTestBase.is_closed, + PollTestBase.is_anonymous, + PollTestBase.type, + PollTestBase.allows_multiple_answers, + correct_option_id=1, + ) + assert poll.correct_option_ids == (1,) + def test_equality(self): a = Poll(123, "question", ["O1", "O2"], 1, False, True, Poll.REGULAR, True) b = Poll(123, "question", ["o1", "o2"], 1, True, False, Poll.REGULAR, True) @@ -499,3 +566,281 @@ def test_parse_question_entities(self, poll): assert poll.parse_question_entities(MessageEntity.ITALIC) == {entity: "Question"} assert poll.parse_question_entities() == {entity: "Question", entity_2: "Test"} + + def test_parse_description_entity(self, poll): + entity = MessageEntity(MessageEntity.ITALIC, 0, 11) + poll.description_entities = [entity] + + assert poll.parse_description_entity(entity) == "description" + with pytest.raises(RuntimeError, match="Poll has no"): + Poll( + "id", + "question", + [PollOption("text", voter_count=0)], + total_voter_count=0, + is_closed=False, + is_anonymous=False, + type=Poll.QUIZ, + allows_multiple_answers=False, + ).parse_description_entity(entity) + + def test_parse_description_entities(self, poll): + entity = MessageEntity(MessageEntity.ITALIC, 0, 11) + entity_2 = MessageEntity(MessageEntity.BOLD, 0, 4) + poll.description_entities = [entity_2, entity] + + assert poll.parse_description_entities(MessageEntity.ITALIC) == {entity: "description"} + assert poll.parse_description_entities() == {entity: "description", entity_2: "desc"} + with pytest.raises(RuntimeError, match="Poll has no"): + Poll( + "id", + "question", + [PollOption("text", voter_count=0)], + total_voter_count=0, + is_closed=False, + is_anonymous=False, + type=Poll.QUIZ, + allows_multiple_answers=False, + ).parse_description_entities() + + +@pytest.fixture(scope="module") +def poll_option_added(): + p = PollOptionAdded( + poll_message=PollOptionAddedTestBase.poll_message, + option_persistent_id=PollOptionAddedTestBase.option_persistent_id, + option_text=PollOptionAddedTestBase.option_text, + option_text_entities=PollOptionAddedTestBase.option_text_entities, + ) + p._unfreeze() + return p + + +class PollOptionAddedTestBase: + poll_message = MaybeInaccessibleMessage( + message_id=1, + date=dtm.datetime.now(dtm.timezone.utc), + chat=Chat(1, "test_chat"), + ) + option_persistent_id = "123" + option_text = "test option" + option_text_entities = [ + MessageEntity(MessageEntity.BOLD, 0, 4), + MessageEntity(MessageEntity.ITALIC, 5, 6), + ] + + +class TestPollOptionAddedWithoutRequest(PollOptionAddedTestBase): + def test_slot_behaviour(self, poll_option_added): + for attr in poll_option_added.__slots__: + assert getattr(poll_option_added, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(poll_option_added)) == len(set(mro_slots(poll_option_added))), ( + "duplicate slot" + ) + + def test_de_json(self, offline_bot): + json_dict = { + "poll_message": self.poll_message.to_dict(), + "option_persistent_id": self.option_persistent_id, + "option_text": self.option_text, + "option_text_entities": [e.to_dict() for e in self.option_text_entities], + } + poll_option_added = PollOptionAdded.de_json(json_dict, offline_bot) + assert poll_option_added.api_kwargs == {} + + assert poll_option_added.poll_message == self.poll_message + assert poll_option_added.option_persistent_id == self.option_persistent_id + assert poll_option_added.option_text == self.option_text + assert poll_option_added.option_text_entities == tuple(self.option_text_entities) + + def test_to_dict(self, poll_option_added): + poll_option_added_dict = poll_option_added.to_dict() + + assert isinstance(poll_option_added_dict, dict) + assert poll_option_added_dict["poll_message"] == poll_option_added.poll_message.to_dict() + assert ( + poll_option_added_dict["option_persistent_id"] + == poll_option_added.option_persistent_id + ) + assert poll_option_added_dict["option_text"] == poll_option_added.option_text + assert poll_option_added_dict["option_text_entities"] == [ + e.to_dict() for e in poll_option_added.option_text_entities + ] + + def test_parse_option_text_entity(self, poll_option_added): + entity = MessageEntity(MessageEntity.BOLD, 0, 4) + poll_option_added.option_text_entities = [entity] + + assert poll_option_added.parse_option_text_entity(entity) == "test" + + def test_parse_option_text_entities(self, poll_option_added): + entity = MessageEntity(MessageEntity.BOLD, 0, 4) + entity_2 = MessageEntity(MessageEntity.ITALIC, 5, 6) + poll_option_added.option_text_entities = [entity, entity_2] + + assert poll_option_added.parse_option_text_entities(MessageEntity.BOLD) == {entity: "test"} + assert poll_option_added.parse_option_text_entities() == { + entity: "test", + entity_2: "option", + } + + def test_equality(self): + a = PollOptionAdded( + poll_message=self.poll_message, + option_persistent_id=self.option_persistent_id, + option_text=self.option_text, + option_text_entities=self.option_text_entities, + ) + b = PollOptionAdded( + poll_message=self.poll_message, + option_persistent_id=self.option_persistent_id, + option_text=self.option_text, + option_text_entities=self.option_text_entities, + ) + c = PollOptionAdded( + poll_message=MaybeInaccessibleMessage( + 2, dtm.datetime.now(dtm.timezone.utc), Chat(1, "test_chat") + ), + option_persistent_id=self.option_persistent_id, + option_text=self.option_text, + option_text_entities=self.option_text_entities, + ) + + d = PollOptionAdded( + poll_message=self.poll_message, + option_persistent_id="different_id", + option_text=self.option_text, + option_text_entities=self.option_text_entities, + ) + + assert a == b + assert hash(a) == hash(b) + + assert a == c + assert hash(a) == hash(c) + + assert a != d + assert hash(a) != hash(d) + + +@pytest.fixture(scope="module") +def poll_option_deleted(): + p = PollOptionDeleted( + poll_message=PollOptionDeletedTestBase.poll_message, + option_persistent_id=PollOptionDeletedTestBase.option_persistent_id, + option_text=PollOptionDeletedTestBase.option_text, + option_text_entities=PollOptionDeletedTestBase.option_text_entities, + ) + p._unfreeze() + return p + + +class PollOptionDeletedTestBase: + poll_message = MaybeInaccessibleMessage( + message_id=1, + date=dtm.datetime.now(dtm.timezone.utc), + chat=Chat(1, "test_chat"), + ) + option_persistent_id = "123" + option_text = "test option" + option_text_entities = [ + MessageEntity(MessageEntity.BOLD, 0, 4), + MessageEntity(MessageEntity.ITALIC, 5, 6), + ] + + +class TestPollOptionDeletedWithoutRequest(PollOptionDeletedTestBase): + def test_slot_behaviour(self, poll_option_deleted): + for attr in poll_option_deleted.__slots__: + assert getattr(poll_option_deleted, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(poll_option_deleted)) == len(set(mro_slots(poll_option_deleted))), ( + "duplicate slot" + ) + + def test_de_json(self, offline_bot): + json_dict = { + "poll_message": self.poll_message.to_dict(), + "option_persistent_id": self.option_persistent_id, + "option_text": self.option_text, + "option_text_entities": [e.to_dict() for e in self.option_text_entities], + } + poll_option_deleted = PollOptionDeleted.de_json(json_dict, offline_bot) + assert poll_option_deleted.api_kwargs == {} + + assert poll_option_deleted.poll_message == self.poll_message + assert poll_option_deleted.option_persistent_id == self.option_persistent_id + assert poll_option_deleted.option_text == self.option_text + assert poll_option_deleted.option_text_entities == tuple(self.option_text_entities) + + def test_to_dict(self, poll_option_deleted): + poll_option_deleted_dict = poll_option_deleted.to_dict() + + assert isinstance(poll_option_deleted_dict, dict) + assert ( + poll_option_deleted_dict["poll_message"] == poll_option_deleted.poll_message.to_dict() + ) + assert ( + poll_option_deleted_dict["option_persistent_id"] + == poll_option_deleted.option_persistent_id + ) + assert poll_option_deleted_dict["option_text"] == poll_option_deleted.option_text + assert poll_option_deleted_dict["option_text_entities"] == [ + e.to_dict() for e in poll_option_deleted.option_text_entities + ] + + def test_parse_option_text_entity(self, poll_option_deleted): + entity = MessageEntity(MessageEntity.BOLD, 0, 4) + poll_option_deleted.option_text_entities = [entity] + + assert poll_option_deleted.parse_option_text_entity(entity) == "test" + + def test_parse_option_text_entities(self, poll_option_deleted): + entity = MessageEntity(MessageEntity.BOLD, 0, 4) + entity_2 = MessageEntity(MessageEntity.ITALIC, 5, 6) + poll_option_deleted.option_text_entities = [entity, entity_2] + + assert poll_option_deleted.parse_option_text_entities(MessageEntity.BOLD) == { + entity: "test" + } + assert poll_option_deleted.parse_option_text_entities() == { + entity: "test", + entity_2: "option", + } + + def test_equality(self): + a = PollOptionDeleted( + poll_message=self.poll_message, + option_persistent_id=self.option_persistent_id, + option_text=self.option_text, + option_text_entities=self.option_text_entities, + ) + b = PollOptionDeleted( + poll_message=self.poll_message, + option_persistent_id=self.option_persistent_id, + option_text=self.option_text, + option_text_entities=self.option_text_entities, + ) + c = PollOptionDeleted( + poll_message=MaybeInaccessibleMessage( + 2, dtm.datetime.now(dtm.timezone.utc), Chat(1, "test_chat") + ), + option_persistent_id=self.option_persistent_id, + option_text=self.option_text, + option_text_entities=self.option_text_entities, + ) + + d = PollOptionDeleted( + poll_message=self.poll_message, + option_persistent_id="different_id", + option_text=self.option_text, + option_text_entities=self.option_text_entities, + ) + + assert a == b + assert hash(a) == hash(b) + + assert a == c + assert hash(a) == hash(c) + + assert a != d + assert hash(a) != hash(d) diff --git a/tests/test_preparedkeyboardbutton.py b/tests/test_preparedkeyboardbutton.py new file mode 100644 index 00000000000..9b62be8445a --- /dev/null +++ b/tests/test_preparedkeyboardbutton.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2026 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +import pytest + +from telegram import PreparedKeyboardButton +from tests.auxil.slots import mro_slots + + +@pytest.fixture(scope="module") +def prepared_keyboard_button(): + return PreparedKeyboardButton(PreparedKeyboardButtonTestBase.id) + + +class PreparedKeyboardButtonTestBase: + id = "4" + + +class TestPreparedKeyboardButtonWithoutRequest(PreparedKeyboardButtonTestBase): + def test_slot_behaviour(self, prepared_keyboard_button): + inst = prepared_keyboard_button + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_de_json(self, offline_bot): + json_dict = { + "id": self.id, + } + prepared_keyboard_button = PreparedKeyboardButton.de_json(json_dict, offline_bot) + assert prepared_keyboard_button.api_kwargs == {} + assert prepared_keyboard_button.id == self.id + + def test_to_dict(self, prepared_keyboard_button): + prepared_keyboard_button_dict = prepared_keyboard_button.to_dict() + assert prepared_keyboard_button_dict["id"] == self.id + + def test_equality(self): + a = PreparedKeyboardButton(self.id) + b = PreparedKeyboardButton(self.id) + c = PreparedKeyboardButton("5") + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) diff --git a/tests/test_reply.py b/tests/test_reply.py index 16e9da49957..1822fff19f4 100644 --- a/tests/test_reply.py +++ b/tests/test_reply.py @@ -219,6 +219,7 @@ def reply_parameters(): quote_entities=ReplyParametersTestBase.quote_entities, quote_position=ReplyParametersTestBase.quote_position, checklist_task_id=ReplyParametersTestBase.checklist_task_id, + poll_option_id=ReplyParametersTestBase.poll_option_id, ) @@ -234,6 +235,7 @@ class ReplyParametersTestBase: ] quote_position = 5 checklist_task_id = 9 + poll_option_id = "213" class TestReplyParametersWithoutRequest(ReplyParametersTestBase): @@ -254,6 +256,7 @@ def test_de_json(self, offline_bot): "quote_entities": [entity.to_dict() for entity in self.quote_entities], "quote_position": self.quote_position, "checklist_task_id": self.checklist_task_id, + "poll_option_id": self.poll_option_id, } reply_parameters = ReplyParameters.de_json(json_dict, offline_bot) @@ -267,6 +270,7 @@ def test_de_json(self, offline_bot): assert reply_parameters.quote_entities == tuple(self.quote_entities) assert reply_parameters.quote_position == self.quote_position assert reply_parameters.checklist_task_id == self.checklist_task_id + assert reply_parameters.poll_option_id == self.poll_option_id def test_to_dict(self, reply_parameters): reply_parameters_dict = reply_parameters.to_dict() @@ -285,6 +289,7 @@ def test_to_dict(self, reply_parameters): ] assert reply_parameters_dict["quote_position"] == self.quote_position assert reply_parameters_dict["checklist_task_id"] == self.checklist_task_id + assert reply_parameters_dict["poll_option_id"] == self.poll_option_id def test_equality(self, reply_parameters): a = reply_parameters diff --git a/tests/test_update.py b/tests/test_update.py index b74234b9435..8a8e0c4853b 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -38,6 +38,7 @@ ChosenInlineResult, InaccessibleMessage, InlineQuery, + ManagedBotUpdated, Message, MessageReactionCountUpdated, MessageReactionUpdated, @@ -151,6 +152,10 @@ paid_media_payload="payload", ) +managed_bot = ManagedBotUpdated( + user=User(1, "creator", True), + bot=User(2, "bot", True), +) params = [ {"message": message}, @@ -191,6 +196,7 @@ {"business_message": business_message}, {"edited_business_message": business_message}, {"purchased_paid_media": purchased_paid_media}, + {"managed_bot": managed_bot}, # Must be last to conform with `ids` below! {"callback_query": CallbackQuery(1, User(1, "", False), "chat")}, ] @@ -219,6 +225,7 @@ "business_message", "edited_business_message", "purchased_paid_media", + "managed_bot", ) ids = (*all_types, "callback_query_without_message") @@ -300,6 +307,7 @@ def test_effective_chat(self, update): or update.poll_answer is not None or update.business_connection is not None or update.purchased_paid_media is not None + or update.managed_bot is not None ): assert chat.id == 1 else: @@ -414,6 +422,7 @@ def test_effective_message(self, update): or update.deleted_business_messages is not None or update.business_connection is not None or update.purchased_paid_media is not None + or update.managed_bot is not None ): assert eff_message.message_id == message.message_id else: diff --git a/tests/test_user.py b/tests/test_user.py index ba237ea476d..24f8ffe8c8d 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -46,6 +46,7 @@ def json_dict(): "has_main_web_app": UserTestBase.has_main_web_app, "has_topics_enabled": UserTestBase.has_topics_enabled, "allows_users_to_create_topics": UserTestBase.allows_users_to_create_topics, + "can_manage_bots": UserTestBase.can_manage_bots, } @@ -67,6 +68,7 @@ def user(bot): has_main_web_app=UserTestBase.has_main_web_app, has_topics_enabled=UserTestBase.has_topics_enabled, allows_users_to_create_topics=UserTestBase.allows_users_to_create_topics, + can_manage_bots=UserTestBase.can_manage_bots, ) user.set_bot(bot) user._unfreeze() @@ -89,6 +91,7 @@ class UserTestBase: has_main_web_app = False has_topics_enabled = False allows_users_to_create_topics = False + can_manage_bots = True class TestUserWithoutRequest(UserTestBase): @@ -116,6 +119,7 @@ def test_de_json(self, json_dict, offline_bot): assert user.has_main_web_app == self.has_main_web_app assert user.has_topics_enabled == self.has_topics_enabled assert user.allows_users_to_create_topics == self.allows_users_to_create_topics + assert user.can_manage_bots == self.can_manage_bots def test_to_dict(self, user): user_dict = user.to_dict() @@ -136,6 +140,7 @@ def test_to_dict(self, user): assert user_dict["has_main_web_app"] == user.has_main_web_app assert user_dict["has_topics_enabled"] == user.has_topics_enabled assert user_dict["allows_users_to_create_topics"] == user.allows_users_to_create_topics + assert user_dict["can_manage_bots"] == user.can_manage_bots def test_equality(self): a = User(self.id_, self.first_name, self.is_bot, self.last_name) @@ -906,3 +911,18 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(user.get_bot(), "set_chat_member_tag", make_assertion) assert await user.set_chat_member_tag(chat_id="chat_id", tag="tag") + + async def test_instance_method_replace_token(self, monkeypatch, user): + async def make_assertion(*_, **kwargs): + return kwargs["user_id"] == user.id + + assert check_shortcut_signature( + user.replace_token, Bot.replace_managed_bot_token, ["user_id"], [] + ) + assert await check_shortcut_call( + user.replace_token, user.get_bot(), "replace_managed_bot_token" + ) + assert await check_defaults_handling(user.replace_token, user.get_bot()) + + monkeypatch.setattr(user.get_bot(), "replace_managed_bot_token", make_assertion) + assert await user.replace_token() From b4067ce3637c7d1d87fe93932ab9883a164c05a5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 17 May 2026 01:20:28 +0000 Subject: [PATCH 24/39] Update Ruff to v0.15.13 (#5233) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- .../5233.YJVhiqtdZZ57TBRdNh2swZ.toml | 5 ++ pyproject.toml | 2 +- uv.lock | 46 +++++++++---------- 4 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 changes/unreleased/5233.YJVhiqtdZZ57TBRdNh2swZ.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 10e04d2940c..1856bd19747 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.15.12' + rev: 'v0.15.13' hooks: # Run the linter: - id: ruff-check diff --git a/changes/unreleased/5233.YJVhiqtdZZ57TBRdNh2swZ.toml b/changes/unreleased/5233.YJVhiqtdZZ57TBRdNh2swZ.toml new file mode 100644 index 00000000000..59e75163c9a --- /dev/null +++ b/changes/unreleased/5233.YJVhiqtdZZ57TBRdNh2swZ.toml @@ -0,0 +1,5 @@ +internal = "Update Ruff to v0.15.13" +[[pull_requests]] +uid = "5233" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index c5a1377164d..1dd06ce51f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,7 @@ docs = [ ] linting = [ "prek", - "ruff==0.15.12", + "ruff==0.15.13", "mypy==1.20.2", "pylint==4.0.5" ] diff --git a/uv.lock b/uv.lock index fceee73d550..60644990c6e 100644 --- a/uv.lock +++ b/uv.lock @@ -1647,7 +1647,7 @@ all = [ { name = "pytest-cov" }, { name = "pytest-xdist", specifier = "==3.8.0" }, { name = "pytz" }, - { name = "ruff", specifier = "==0.15.12" }, + { name = "ruff", specifier = "==0.15.13" }, { name = "sphinx", marker = "python_full_version >= '3.12'", specifier = "==9.1.0" }, { name = "sphinx-build-compatibility", git = "https://github.com/readthedocs/sphinx-build-compatibility.git?rev=58aabc5f207c6c2421f23d3578adc0b14af57047" }, { name = "sphinx-copybutton", specifier = "==0.5.2" }, @@ -1671,7 +1671,7 @@ linting = [ { name = "mypy", specifier = "==1.20.2" }, { name = "prek" }, { name = "pylint", specifier = "==4.0.5" }, - { name = "ruff", specifier = "==0.15.12" }, + { name = "ruff", specifier = "==0.15.13" }, ] tests = [ { name = "beautifulsoup4" }, @@ -1797,27 +1797,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, - { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, - { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, - { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, - { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, - { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, - { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, - { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, - { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, - { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, - { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, - { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, - { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, - { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, +version = "0.15.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" }, + { url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" }, + { url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" }, + { url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" }, + { url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" }, + { url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" }, + { url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" }, + { url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" }, + { url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" }, + { url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" }, ] [[package]] From 6fdab3a58d438cf998e0bde6b77f44d37bfef058 Mon Sep 17 00:00:00 2001 From: karin0 Date: Thu, 21 May 2026 06:44:41 +0900 Subject: [PATCH 25/39] Check `channel_post` and `edited_channel_post` in `Update.effective_user` (#5237) --- AUTHORS.rst | 1 + changes/unreleased/5237.effective-user-fix.toml | 5 +++++ src/telegram/_update.py | 11 ++++++++--- tests/ext/test_messagehandler.py | 17 +---------------- tests/test_update.py | 12 ++++++++++++ 5 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 changes/unreleased/5237.effective-user-fix.toml diff --git a/AUTHORS.rst b/AUTHORS.rst index 299f88ad3ee..848677d088e 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -75,6 +75,7 @@ The following wonderful people contributed directly or indirectly to this projec - `jossalgon `_ - `JRoot3D `_ - `Juan Cuevas ` +- `karin0 `_ - `kenjitagawa `_ - `kennethcheo `_ - `Kirill Vasin `_ diff --git a/changes/unreleased/5237.effective-user-fix.toml b/changes/unreleased/5237.effective-user-fix.toml new file mode 100644 index 00000000000..ae391c5d94d --- /dev/null +++ b/changes/unreleased/5237.effective-user-fix.toml @@ -0,0 +1,5 @@ +bugfixes = "`Update.effective_user` now checks for `channel_post` and `edited_channel_post`" +[[pull_requests]] +uid = "5237" +author_uids = ["karin0"] +closes_threads = ["5236"] diff --git a/src/telegram/_update.py b/src/telegram/_update.py index a8b8668ad17..dc2a7f2f1e3 100644 --- a/src/telegram/_update.py +++ b/src/telegram/_update.py @@ -500,8 +500,6 @@ def effective_user(self) -> "User | None": is. If no user is associated with this update, this gives :obj:`None`. This is the case if any of - * :attr:`channel_post` - * :attr:`edited_channel_post` * :attr:`poll` * :attr:`chat_boost` * :attr:`removed_chat_boost` @@ -518,7 +516,8 @@ def effective_user(self) -> "User | None": This property now also considers :attr:`purchased_paid_media`. .. versionchanged:: NEXT.VERSION - This property now also considers :attr:`managed_bot`. + This property now also considers :attr:`managed_bot`, :attr:`channel_post` + and :attr:`edited_channel_post`. Example: * If :attr:`message` is present, this will give @@ -537,6 +536,12 @@ def effective_user(self) -> "User | None": elif self.edited_message: user = self.edited_message.from_user + elif self.channel_post: + user = self.channel_post.from_user + + elif self.edited_channel_post: + user = self.edited_channel_post.from_user + elif self.inline_query: user = self.inline_query.from_user diff --git a/tests/ext/test_messagehandler.py b/tests/ext/test_messagehandler.py index aeb2e1b0391..8df5de7d1ef 100644 --- a/tests/ext/test_messagehandler.py +++ b/tests/ext/test_messagehandler.py @@ -95,22 +95,7 @@ async def callback(self, update, context): and isinstance(context.job_queue, JobQueue) and isinstance(context.chat_data, dict) and isinstance(context.bot_data, dict) - and ( - ( - isinstance(context.user_data, dict) - and ( - isinstance(update.message, Message) - or isinstance(update.edited_message, Message) - ) - ) - or ( - context.user_data is None - and ( - isinstance(update.channel_post, Message) - or isinstance(update.edited_channel_post, Message) - ) - ) - ) + and isinstance(context.user_data, dict) ) def callback_regex1(self, update, context): diff --git a/tests/test_update.py b/tests/test_update.py index 8a8e0c4853b..7b034090a0e 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -401,6 +401,18 @@ def test_effective_sender_anonymous(self, update): cached = update.effective_sender assert cached is sender + def test_effective_sender_signed_channel_post(self): + # channel_post with signatures can have its from_user + user = User(1, "", False) + post = Message( + 1, dtm.datetime.utcnow(), Chat(1, ""), author_signature="", from_user=user, text="Text" + ) + update = Update(update_id=1, channel_post=post) + assert update.effective_sender == update.effective_user == user + + update = Update(update_id=2, edited_channel_post=post) + assert update.effective_sender == update.effective_user == user + def test_effective_message(self, update): # Test that it's sometimes None per docstring eff_message = update.effective_message From 1a83d47fb32e0d5892ef208309e4e03721539f39 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 24 May 2026 01:34:16 +0000 Subject: [PATCH 26/39] Update Ruff to v0.15.14 (#5245) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- .../5245.3Xu2uH53fGn822LB5Y7Pqk.toml | 5 ++ pyproject.toml | 2 +- uv.lock | 46 +++++++++---------- 4 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 changes/unreleased/5245.3Xu2uH53fGn822LB5Y7Pqk.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1856bd19747..d159f5b2807 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.15.13' + rev: 'v0.15.14' hooks: # Run the linter: - id: ruff-check diff --git a/changes/unreleased/5245.3Xu2uH53fGn822LB5Y7Pqk.toml b/changes/unreleased/5245.3Xu2uH53fGn822LB5Y7Pqk.toml new file mode 100644 index 00000000000..8e2c94aad11 --- /dev/null +++ b/changes/unreleased/5245.3Xu2uH53fGn822LB5Y7Pqk.toml @@ -0,0 +1,5 @@ +internal = "Update Ruff to v0.15.14" +[[pull_requests]] +uid = "5245" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index 1dd06ce51f4..ffaf7530dcb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,7 @@ docs = [ ] linting = [ "prek", - "ruff==0.15.13", + "ruff==0.15.14", "mypy==1.20.2", "pylint==4.0.5" ] diff --git a/uv.lock b/uv.lock index 60644990c6e..f612a3daba0 100644 --- a/uv.lock +++ b/uv.lock @@ -1647,7 +1647,7 @@ all = [ { name = "pytest-cov" }, { name = "pytest-xdist", specifier = "==3.8.0" }, { name = "pytz" }, - { name = "ruff", specifier = "==0.15.13" }, + { name = "ruff", specifier = "==0.15.14" }, { name = "sphinx", marker = "python_full_version >= '3.12'", specifier = "==9.1.0" }, { name = "sphinx-build-compatibility", git = "https://github.com/readthedocs/sphinx-build-compatibility.git?rev=58aabc5f207c6c2421f23d3578adc0b14af57047" }, { name = "sphinx-copybutton", specifier = "==0.5.2" }, @@ -1671,7 +1671,7 @@ linting = [ { name = "mypy", specifier = "==1.20.2" }, { name = "prek" }, { name = "pylint", specifier = "==4.0.5" }, - { name = "ruff", specifier = "==0.15.13" }, + { name = "ruff", specifier = "==0.15.14" }, ] tests = [ { name = "beautifulsoup4" }, @@ -1797,27 +1797,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" }, - { url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" }, - { url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" }, - { url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" }, - { url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" }, - { url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" }, - { url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" }, - { url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" }, - { url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" }, - { url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" }, - { url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" }, - { url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" }, +version = "0.15.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" }, + { url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" }, + { url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" }, + { url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" }, + { url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" }, + { url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" }, + { url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" }, + { url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" }, ] [[package]] From 4ad65a3b403f9d2d52f6ba318136a49afd94b3d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 31 May 2026 01:37:51 +0000 Subject: [PATCH 27/39] Update Ruff to v0.15.15 (#5252) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- .../5252.Qo4J766e8AEw35gf9gLaJE.toml | 5 ++ pyproject.toml | 2 +- uv.lock | 46 +++++++++---------- 4 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 changes/unreleased/5252.Qo4J766e8AEw35gf9gLaJE.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d159f5b2807..d8b04f15017 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.15.14' + rev: 'v0.15.15' hooks: # Run the linter: - id: ruff-check diff --git a/changes/unreleased/5252.Qo4J766e8AEw35gf9gLaJE.toml b/changes/unreleased/5252.Qo4J766e8AEw35gf9gLaJE.toml new file mode 100644 index 00000000000..a5c6c67ea00 --- /dev/null +++ b/changes/unreleased/5252.Qo4J766e8AEw35gf9gLaJE.toml @@ -0,0 +1,5 @@ +internal = "Update Ruff to v0.15.15" +[[pull_requests]] +uid = "5252" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index ffaf7530dcb..52c001a4d41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,7 +133,7 @@ docs = [ ] linting = [ "prek", - "ruff==0.15.14", + "ruff==0.15.15", "mypy==1.20.2", "pylint==4.0.5" ] diff --git a/uv.lock b/uv.lock index f612a3daba0..1b6acb434db 100644 --- a/uv.lock +++ b/uv.lock @@ -1647,7 +1647,7 @@ all = [ { name = "pytest-cov" }, { name = "pytest-xdist", specifier = "==3.8.0" }, { name = "pytz" }, - { name = "ruff", specifier = "==0.15.14" }, + { name = "ruff", specifier = "==0.15.15" }, { name = "sphinx", marker = "python_full_version >= '3.12'", specifier = "==9.1.0" }, { name = "sphinx-build-compatibility", git = "https://github.com/readthedocs/sphinx-build-compatibility.git?rev=58aabc5f207c6c2421f23d3578adc0b14af57047" }, { name = "sphinx-copybutton", specifier = "==0.5.2" }, @@ -1671,7 +1671,7 @@ linting = [ { name = "mypy", specifier = "==1.20.2" }, { name = "prek" }, { name = "pylint", specifier = "==4.0.5" }, - { name = "ruff", specifier = "==0.15.14" }, + { name = "ruff", specifier = "==0.15.15" }, ] tests = [ { name = "beautifulsoup4" }, @@ -1797,27 +1797,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" }, - { url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" }, - { url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" }, - { url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" }, - { url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" }, - { url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" }, - { url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" }, - { url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" }, - { url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" }, - { url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" }, - { url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" }, - { url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" }, +version = "0.15.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/6f/a76f7d96e5c962f5b69cee865e49c15c1116897c01990faa8a57edb62e7f/ruff-0.15.15.tar.gz", hash = "sha256:b8dff018130b46d8e5bf0f926ef6b60cf871d6d5ae45fc9334e09632daa741d6", size = 4706985, upload-time = "2026-05-28T14:16:57.784Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/9d/3a45c05b8ab04b4705989de70a79008e27c8003296a0feaee9edc18dd7e9/ruff-0.15.15-py3-none-linux_armv6l.whl", hash = "sha256:cf93e5388f412e1b108b1f8b34a6e036b70fe8aff89393befad96fe48670311b", size = 10710652, upload-time = "2026-05-28T14:16:06.701Z" }, + { url = "https://files.pythonhosted.org/packages/05/66/da974431624bf3b49f6ee1f9543c02d929ff1cba78b0d5a79c38cf21f744/ruff-0.15.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac5a646d1f6a7dadd5d50842dae2c1f9862ac887ef5d1b1375e02def791fde6e", size = 11096615, upload-time = "2026-05-28T14:16:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/8c/09/7443452e5d290230a712103f2fdceeef7184f3ec99a2bd01c8be78aaceb5/ruff-0.15.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:77d955a431430c66f72dd94e379ad38a16daea3d25094872ac4edf9e797be530", size = 10436683, upload-time = "2026-05-28T14:16:40.974Z" }, + { url = "https://files.pythonhosted.org/packages/53/01/d330c26a57fa4f3943a14424904027428315b700fe4d14a84bb123a649e5/ruff-0.15.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7614ee79c69788cf6cedd568069ade9cecc22a1ad20494efe8d0c9ebb4b622d4", size = 10769064, upload-time = "2026-05-28T14:16:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/cc8770f8bdff541b1da8392d1634141fe4a0e3f4ee596605959b7906c27f/ruff-0.15.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3cdb1679e06a1f6b47bc384714ae96f6e2fb65ca441eb78c43d2ca554176ce1f", size = 10511987, upload-time = "2026-05-28T14:16:43.732Z" }, + { url = "https://files.pythonhosted.org/packages/7c/29/8c190c1472b63013583ba391f3342036e02010544c1270455ed8e519bdf3/ruff-0.15.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2728b93d7b23a603ea2c0ac6eb73d760bd38ec9de35f35fb41e18f7a3fee7622", size = 11275100, upload-time = "2026-05-28T14:16:55.244Z" }, + { url = "https://files.pythonhosted.org/packages/9f/6b/7e145ce2cc8e63d6834eca03d83a0e18d121def5c69f91b4cf4011ed4879/ruff-0.15.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be582fcc0db438902c7792b08d6ddf6c9b9e21addaa10092c2c741cfb09e5a45", size = 12176903, upload-time = "2026-05-28T14:16:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/80/a3/d5974637f68e451f7fadf015cf3101d1cd7d8ba5027cffe0b9e3826ebe6b/ruff-0.15.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7aa77465b8ecaf1a27bea098d696f7fed5e1eccbd10b321b682d6de586ae5627", size = 11404550, upload-time = "2026-05-28T14:16:20.138Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1c/e6e5e568f22be4fb05d6244234aba384c06b451252453b821e1a529263cf/ruff-0.15.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48decfa11d740de4889de623be1463308346312f2409a56e24aa280c86162dc4", size = 11382027, upload-time = "2026-05-28T14:16:46.615Z" }, + { url = "https://files.pythonhosted.org/packages/1d/01/170921b49fcd2e8858825593f91cf7146c3e40a5c3e6df763e4bb0484dde/ruff-0.15.15-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a5015088452ca0081387063649ec67f06d3d1d6b8b936a1f836b5e9657ecd48c", size = 11366041, upload-time = "2026-05-28T14:16:26.247Z" }, + { url = "https://files.pythonhosted.org/packages/87/54/a7bad711d7de93254e15e06a4c375b89a03d18de45d3e5dcc86a4472fb1a/ruff-0.15.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5294aab6356c81600fcdea3a62bb1b924dfd5e91767c12318d3f68f86af57cd", size = 10741795, upload-time = "2026-05-28T14:16:17.11Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/38c075963668f8b41c6914ee0f6f318727fbe30ab9145cb29e6df464c5fa/ruff-0.15.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db5bd4d802415cca656dc1616070b725952d6ae95eb5d4831e49fbd94a38f75f", size = 10511117, upload-time = "2026-05-28T14:16:31.767Z" }, + { url = "https://files.pythonhosted.org/packages/9d/96/6ff689e1f7e375d1d97075eca022f74c2bab59554a432fe4d2e6f091986a/ruff-0.15.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:587a6278ed42059191c1a466e490bd7930fb50bd2e255398bc29616c895a61cb", size = 10994867, upload-time = "2026-05-28T14:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c2/5dce0ab9f92a8d534fa62b9bf9caca3eddb8c1a81b616f5e195ada4f0d6e/ruff-0.15.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:df0c1c084f5f4be9812f61518a45c440d3c30d69ce4bf6c5270e66d38338f02a", size = 11482101, upload-time = "2026-05-28T14:16:49.598Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c0/1003b60edd697c649faf61f1a34094b1abb38fb3d1181e3f895781250a08/ruff-0.15.15-py3-none-win32.whl", hash = "sha256:29428ea79694afbe756d45fd59b36f22b6b020dc0443cf7de0173046236964b9", size = 10716774, upload-time = "2026-05-28T14:16:52.337Z" }, + { url = "https://files.pythonhosted.org/packages/02/a8/1269eddd6945a06c23f055ef7848886e37cf9d6a8bebb386a3115f01470c/ruff-0.15.15-py3-none-win_amd64.whl", hash = "sha256:8df0323902e15e24bc4bf246da830573d3cf3352bd0b9a164eab335d111ff4a4", size = 11868463, upload-time = "2026-05-28T14:16:11.333Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b2/920464c907b191e37469d477a1aa8bc048b8f36c4c1610dfa4ab87b39e18/ruff-0.15.15-py3-none-win_arm64.whl", hash = "sha256:3c8ceca6792f38196b8f589bc92eccd03eef286602da92e5dc05cc42ef6441b7", size = 11138498, upload-time = "2026-05-28T14:16:38.425Z" }, ] [[package]] From 1f343592def03b5d1060daa77a8eb497531126d0 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sun, 31 May 2026 12:47:47 -0400 Subject: [PATCH 28/39] Make instantiation of TelegramObjects 15% faster (#5250) --- changes/unreleased/5250.dTuzQhYiw2T8gfvp6Skj89.toml | 5 +++++ src/telegram/_telegramobject.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changes/unreleased/5250.dTuzQhYiw2T8gfvp6Skj89.toml diff --git a/changes/unreleased/5250.dTuzQhYiw2T8gfvp6Skj89.toml b/changes/unreleased/5250.dTuzQhYiw2T8gfvp6Skj89.toml new file mode 100644 index 00000000000..d18efa8117f --- /dev/null +++ b/changes/unreleased/5250.dTuzQhYiw2T8gfvp6Skj89.toml @@ -0,0 +1,5 @@ +other = "Make instantiation of ``TelegramObject``'s 15% faster" +[[pull_requests]] +uid = "5250" +author_uids = ["harshil21"] +closes_threads = [] diff --git a/src/telegram/_telegramobject.py b/src/telegram/_telegramobject.py index 4098c75c37e..ede04a033d8 100644 --- a/src/telegram/_telegramobject.py +++ b/src/telegram/_telegramobject.py @@ -154,7 +154,7 @@ def __setattr__(self, key: str, value: object) -> None: :exc:`AttributeError` """ # protected attributes can always be set for convenient internal use - if key[0] == "_" or not getattr(self, "_frozen", True): + if key[0] == "_" or not self._frozen: super().__setattr__(key, value) return @@ -169,7 +169,7 @@ def __delattr__(self, key: str) -> None: :exc:`AttributeError` """ # protected attributes can always be set for convenient internal use - if key[0] == "_" or not getattr(self, "_frozen", True): + if key[0] == "_" or not self._frozen: super().__delattr__(key) return From 59d2f62f8130369944e074a79ba3f0942048ba2d Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sun, 31 May 2026 13:23:01 -0400 Subject: [PATCH 29/39] Disallow packages newer than last 7 days (#5247) --- .github/renovate.json5 | 3 +++ changes/unreleased/5247.3DSJ9C36s7HCYSNTEUnDpT.toml | 5 +++++ pyproject.toml | 4 ++++ 3 files changed, 12 insertions(+) create mode 100644 changes/unreleased/5247.3DSJ9C36s7HCYSNTEUnDpT.toml diff --git a/.github/renovate.json5 b/.github/renovate.json5 index c52377794c0..a1682ed6c40 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -26,6 +26,9 @@ // Enable automerge globally: "automerge": true, + // Only upgrade packages if 7 days have passed: + "minimumReleaseAge": "7 days", + // Group package updates together: "packageRules": [ // Linting dependencies in pyproject.toml in sync with the pre-commit-config hooks: diff --git a/changes/unreleased/5247.3DSJ9C36s7HCYSNTEUnDpT.toml b/changes/unreleased/5247.3DSJ9C36s7HCYSNTEUnDpT.toml new file mode 100644 index 00000000000..ccecdcfd035 --- /dev/null +++ b/changes/unreleased/5247.3DSJ9C36s7HCYSNTEUnDpT.toml @@ -0,0 +1,5 @@ +security = "Disallow packages newer than last 7 days" +[[pull_requests]] +uid = "5247" +author_uids = ["harshil21"] +closes_threads = ["5195"] diff --git a/pyproject.toml b/pyproject.toml index 52c001a4d41..74d2c6a91a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -153,6 +153,10 @@ exclude = [".venv*", "venv*", ".github", "uv.lock"] [tool.hatch.build.targets.wheel] packages = ["src/telegram"] +# uv +[tool.uv] +exclude-newer = "7 days" + # CHANGO [tool.chango] sys_path = "changes" From 351a282fa3fc858d12b4ca185c7214175b2a941e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 03:38:21 +0000 Subject: [PATCH 30/39] Update dependency astral-sh/uv to v0.11.16 (#5253) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/copilot-setup-steps.yml | 2 +- changes/unreleased/5253.A9BQTN98eeqDPBrVZ674qa.toml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/unreleased/5253.A9BQTN98eeqDPBrVZ674qa.toml diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 9dc97e078c1..204b8d5c013 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -34,7 +34,7 @@ jobs: uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 with: # Install a specific version of uv. - version: "0.11.8" + version: "0.11.16" # Install 3.13: python-version: 3.13 diff --git a/changes/unreleased/5253.A9BQTN98eeqDPBrVZ674qa.toml b/changes/unreleased/5253.A9BQTN98eeqDPBrVZ674qa.toml new file mode 100644 index 00000000000..3316f0ab3c3 --- /dev/null +++ b/changes/unreleased/5253.A9BQTN98eeqDPBrVZ674qa.toml @@ -0,0 +1,5 @@ +internal = "Update dependency astral-sh/uv to v0.11.16" +[[pull_requests]] +uid = "5253" +author_uids = ["renovate[bot]"] +closes_threads = [] From 74e1d9fb5f4056d0bdef00fdb7f2fd6ece085f56 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 03:39:14 +0000 Subject: [PATCH 31/39] Update dependency sphinxcontrib-mermaid to v2.0.2 (#5254) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- changes/unreleased/5254.kaanCYDk82Jg6P8S9pJG3C.toml | 5 +++++ pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/unreleased/5254.kaanCYDk82Jg6P8S9pJG3C.toml diff --git a/changes/unreleased/5254.kaanCYDk82Jg6P8S9pJG3C.toml b/changes/unreleased/5254.kaanCYDk82Jg6P8S9pJG3C.toml new file mode 100644 index 00000000000..f9e26dec81e --- /dev/null +++ b/changes/unreleased/5254.kaanCYDk82Jg6P8S9pJG3C.toml @@ -0,0 +1,5 @@ +internal = "Update dependency sphinxcontrib-mermaid to v2.0.2" +[[pull_requests]] +uid = "5254" +author_uids = ["renovate[bot]"] +closes_threads = [] diff --git a/pyproject.toml b/pyproject.toml index 74d2c6a91a8..14698612a3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,7 +120,7 @@ docs = [ "sphinx==9.1.0; python_version >= '3.12'", "furo==2025.12.19", "sphinx-paramlinks==0.6.0", - "sphinxcontrib-mermaid==2.0.1", + "sphinxcontrib-mermaid==2.0.2", "sphinx-copybutton==0.5.2", "sphinx-inline-tabs==2025.12.21.14", # Temporary. See #4387 From 6cfed613954ec69b6f53b3f4705af7200044f133 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 03:44:49 +0000 Subject: [PATCH 32/39] Update pypa/gh-action-pypi-publish action to v1.14.0 (#5257) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/release_pypi.yml | 2 +- .github/workflows/release_test_pypi.yml | 2 +- changes/unreleased/5257.c4g6KoZswRyGb9ypHwfKzB.toml | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changes/unreleased/5257.c4g6KoZswRyGb9ypHwfKzB.toml diff --git a/.github/workflows/release_pypi.yml b/.github/workflows/release_pypi.yml index a6a42d10dde..402bcd42613 100644 --- a/.github/workflows/release_pypi.yml +++ b/.github/workflows/release_pypi.yml @@ -60,7 +60,7 @@ jobs: name: python-package-distributions path: dist/ - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 compute-signatures: name: Compute SHA1 Sums and Sign with Sigstore diff --git a/.github/workflows/release_test_pypi.yml b/.github/workflows/release_test_pypi.yml index 5a9661de79d..0d89a14c289 100644 --- a/.github/workflows/release_test_pypi.yml +++ b/.github/workflows/release_test_pypi.yml @@ -60,7 +60,7 @@ jobs: name: python-package-distributions path: dist/ - name: Publish to Test PyPI - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 with: repository-url: https://test.pypi.org/legacy/ diff --git a/changes/unreleased/5257.c4g6KoZswRyGb9ypHwfKzB.toml b/changes/unreleased/5257.c4g6KoZswRyGb9ypHwfKzB.toml new file mode 100644 index 00000000000..a4e37e3e5b4 --- /dev/null +++ b/changes/unreleased/5257.c4g6KoZswRyGb9ypHwfKzB.toml @@ -0,0 +1,5 @@ +internal = "Update pypa/gh-action-pypi-publish action to v1.14.0" +[[pull_requests]] +uid = "5257" +author_uids = ["renovate[bot]"] +closes_threads = [] From cc7b55d837c1c9132024b683148aebdfc3153f83 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 03:55:54 +0000 Subject: [PATCH 33/39] Update github/codeql-action action to v4.36.0 (#5256) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/gha_security.yml | 2 +- changes/unreleased/5256.VFGHhZz3SchcMmJJLETLzs.toml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/unreleased/5256.VFGHhZz3SchcMmJJLETLzs.toml diff --git a/.github/workflows/gha_security.yml b/.github/workflows/gha_security.yml index 113e3fcff7f..ccbc9226473 100644 --- a/.github/workflows/gha_security.yml +++ b/.github/workflows/gha_security.yml @@ -27,7 +27,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 + uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 with: sarif_file: results.sarif category: zizmor diff --git a/changes/unreleased/5256.VFGHhZz3SchcMmJJLETLzs.toml b/changes/unreleased/5256.VFGHhZz3SchcMmJJLETLzs.toml new file mode 100644 index 00000000000..60f0cb3dabd --- /dev/null +++ b/changes/unreleased/5256.VFGHhZz3SchcMmJJLETLzs.toml @@ -0,0 +1,5 @@ +internal = "Update github/codeql-action action to v4.36.0" +[[pull_requests]] +uid = "5256" +author_uids = ["renovate[bot]"] +closes_threads = [] From ab2996713d4675b8a22c2d404d05653ebd10016d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 04:01:27 +0000 Subject: [PATCH 34/39] Update actions/stale action to v10.3.0 (#5255) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- changes/unreleased/5255.Sx6a4Mf4dD3bUfNWSty78T.toml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changes/unreleased/5255.Sx6a4Mf4dD3bUfNWSty78T.toml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 48a11efbd66..ca446f61835 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -12,7 +12,7 @@ jobs: # For adding labels and closing issues: write steps: - - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 + - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 with: # PRs never get stale days-before-stale: 3 diff --git a/changes/unreleased/5255.Sx6a4Mf4dD3bUfNWSty78T.toml b/changes/unreleased/5255.Sx6a4Mf4dD3bUfNWSty78T.toml new file mode 100644 index 00000000000..1f0f113f0ff --- /dev/null +++ b/changes/unreleased/5255.Sx6a4Mf4dD3bUfNWSty78T.toml @@ -0,0 +1,5 @@ +internal = "Update actions/stale action to v10.3.0" +[[pull_requests]] +uid = "5255" +author_uids = ["renovate[bot]"] +closes_threads = [] From 0fb567818036b9844e53b11720e00802af029c0b Mon Sep 17 00:00:00 2001 From: Abdelrahman Elkheir <90580077+aelkheir@users.noreply.github.com> Date: Tue, 9 Jun 2026 18:49:15 +0300 Subject: [PATCH 35/39] Full Support for Bot API 10.0 (#5229) Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com> Co-authored-by: poolitzer Co-authored-by: Phil Bazun Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- .github/workflows/unit_tests.yml | 4 +- README.rst | 4 +- .../5196.7keq7yJhXbMb9RyShLHz4D.toml | 19 +- .../5229.87PBN4GFkuAaDhhgFwCYkY.toml | 33 + docs/auxil/sphinx_hooks.py | 1 + docs/source/inclusions/bot_methods.rst | 14 + docs/source/telegram.at-tree.rst | 12 + docs/source/telegram.botaccesssettings.rst | 6 + docs/source/telegram.inputmedialivephoto.rst | 6 + docs/source/telegram.inputmedialocation.rst | 6 + docs/source/telegram.inputmediasticker.rst | 6 + docs/source/telegram.inputmediavenue.rst | 6 + .../telegram.inputpaidmedialivephoto.rst | 6 + docs/source/telegram.inputpollmedia.rst | 6 + docs/source/telegram.inputpolloptionmedia.rst | 6 + docs/source/telegram.livephoto.rst | 6 + docs/source/telegram.paidmedialivephoto.rst | 6 + docs/source/telegram.pollmedia.rst | 6 + docs/source/telegram.sentguestmessage.rst | 6 + src/telegram/__init__.py | 24 + src/telegram/_bot.py | 569 +++++++++++-- src/telegram/_botaccesssettings.py | 77 ++ src/telegram/_chat.py | 168 +++- src/telegram/_chatfullinfo.py | 2 +- src/telegram/_chatmember.py | 19 + src/telegram/_chatowner.py | 4 +- src/telegram/_chatpermissions.py | 17 +- src/telegram/_files/inputmedia.py | 581 +++++++++++-- src/telegram/_files/livephoto.py | 119 +++ src/telegram/_forcereply.py | 2 +- src/telegram/_message.py | 242 +++++- src/telegram/_paidmedia.py | 45 ++ src/telegram/_poll.py | 253 +++++- src/telegram/_reply.py | 14 + src/telegram/_sentguestmessage.py | 54 ++ src/telegram/_update.py | 86 +- src/telegram/_user.py | 304 ++++++- src/telegram/_webhookinfo.py | 6 +- src/telegram/constants.py | 130 ++- src/telegram/ext/_extbot.py | 235 +++++- src/telegram/ext/filters.py | 35 +- tests/_files/conftest.py | 16 + tests/_files/test_inputmedia.py | 763 +++++++++++++++++- tests/_files/test_livephoto.py | 389 +++++++++ tests/auxil/bot_method_checks.py | 4 +- tests/auxil/dummy_objects.py | 8 +- tests/ext/test_filters.py | 25 + tests/ext/test_pollanswerhandler.py | 11 +- tests/ext/test_pollhandler.py | 21 +- tests/test_bot.py | 253 +++++- tests/test_botaccesssettings.py | 87 ++ tests/test_chat.py | 77 ++ tests/test_chatmember.py | 18 + tests/test_chatpermissions.py | 7 + tests/test_constants.py | 3 + tests/test_message.py | 119 ++- tests/test_official/exceptions.py | 35 +- tests/test_paidmedia.py | 62 ++ tests/test_poll.py | 328 +++++++- tests/test_reply.py | 15 + tests/test_sentguestmessage.py | 64 ++ tests/test_update.py | 39 +- tests/test_user.py | 132 +++ uv.lock | 14 +- 64 files changed, 5373 insertions(+), 262 deletions(-) create mode 100644 changes/unreleased/5229.87PBN4GFkuAaDhhgFwCYkY.toml create mode 100644 docs/source/telegram.botaccesssettings.rst create mode 100644 docs/source/telegram.inputmedialivephoto.rst create mode 100644 docs/source/telegram.inputmedialocation.rst create mode 100644 docs/source/telegram.inputmediasticker.rst create mode 100644 docs/source/telegram.inputmediavenue.rst create mode 100644 docs/source/telegram.inputpaidmedialivephoto.rst create mode 100644 docs/source/telegram.inputpollmedia.rst create mode 100644 docs/source/telegram.inputpolloptionmedia.rst create mode 100644 docs/source/telegram.livephoto.rst create mode 100644 docs/source/telegram.paidmedialivephoto.rst create mode 100644 docs/source/telegram.pollmedia.rst create mode 100644 docs/source/telegram.sentguestmessage.rst create mode 100644 src/telegram/_botaccesssettings.py create mode 100644 src/telegram/_files/livephoto.py create mode 100644 src/telegram/_sentguestmessage.py create mode 100644 tests/_files/test_livephoto.py create mode 100644 tests/test_botaccesssettings.py create mode 100644 tests/test_sentguestmessage.py diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 55fe104e4a5..b3b9b4431c6 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -87,14 +87,14 @@ jobs: .test_report_optionals_junit.xml - name: Submit coverage - uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5.5.4 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: env_vars: OS,PYTHON name: ${{ matrix.os }}-${{ matrix.python-version }} fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} - name: Upload test results to Codecov - uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5.5.4 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 if: ${{ !cancelled() }} with: files: .test_report_no_optionals_junit.xml,.test_report_optionals_junit.xml diff --git a/README.rst b/README.rst index 84f80c9bb4f..44cbe11f12e 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ :target: https://pypi.org/project/python-telegram-bot/ :alt: Supported Python versions -.. image:: https://img.shields.io/badge/Bot%20API-9.6-blue?logo=telegram +.. image:: https://img.shields.io/badge/Bot%20API-10.0-blue?logo=telegram :target: https://core.telegram.org/bots/api-changelog :alt: Supported Bot API version @@ -77,7 +77,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 **9.6** are natively supported by this library. +All types and methods of the Telegram Bot API **10.0** are natively supported by this library. In addition, Bot API functionality not yet natively included can still be used as described `in our wiki `_. Notable Features diff --git a/changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml b/changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml index 9a1ba1501e1..4d79925fa99 100644 --- a/changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml +++ b/changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml @@ -1,4 +1,21 @@ -features = "Full Support for Bot API 9.6" +features = """ +Full Support for Bot API 9.6 + +.. warning:: + + - Bot API 9.6 replaces the field ``correct_option_id`` of ``Poll`` with the new field ``correct_option_ids``. The field ``correct_option_id`` is still present in PTB for backward compatibility, but it will be removed in future releases. + + - Bot API 9.6 replaces the argument ``correct_option_id`` of ``Bot.send_poll`` with the new argument ``correct_option_ids``. The argument ``correct_option_id`` is still present in PTB for backward compatibility, but it will be removed in future releases. + + - Bot API 9.6 introduces a now required argument ``persistent_id`` to ``PollOption``. For backward compatibility, the argument is currently still marked as optional in the signature and its presence is enforced through a runtime check. In future versions, this argument will be made required in the signature as well. + + - Bot API 9.6 introduces a now required argument ``option_persistent_ids`` to ``PollAnswer``. For backward compatibility, the argument is currently still marked as optional in the signature and its presence is enforced through a runtime check. In future versions, this argument will be made required in the signature as well. + + - Bot API 9.6 introduces a now required argument ``allows_revoting`` to ``Poll``. For backward compatibility, the argument is currently still marked as optional in the signature and its presence is enforced through a runtime check. In future versions, this argument will be made required in the signature as well. + + + Please make sure to update your code accordingly to avoid potential issues in the future. We recommend using keyword arguments to ensure compatibility with future updates. +""" pull_requests = [ { uid = "5196", author_uid = "harshil21" }, diff --git a/changes/unreleased/5229.87PBN4GFkuAaDhhgFwCYkY.toml b/changes/unreleased/5229.87PBN4GFkuAaDhhgFwCYkY.toml new file mode 100644 index 00000000000..d0435053de4 --- /dev/null +++ b/changes/unreleased/5229.87PBN4GFkuAaDhhgFwCYkY.toml @@ -0,0 +1,33 @@ +features = """ +Full Support for Bot API 10.0 + +.. warning:: + + - Bot API 10.0 introduces a now required argument ``members_only`` to ``Poll``. For backward compatibility, the argument is currently still marked as optional in the signature and its presence is enforced through a runtime check. In future versions, this argument will be made required in the signature as well. + + Please make sure to update your code accordingly to avoid potential issues in the future. We recommend using keyword arguments to ensure compatibility with future updates. +""" + +deprecations = """ +* Deprecated passing the ``filename`` parameter positionally to the classes: + + * ``InputMediaAnimation`` + * ``InputMediaAudio`` + * ``InputMediaPhoto`` + * ``InputMediaDocument`` + * ``InputMediaVideo`` + + Please pass ``filename`` as a keyword argument instead, as this parameter will become keyword-only in the future. + +* Deprecated ``InputPollOption.de_json``. The class ``InputPollOption`` is input only and its ``de_json`` method will be removed in future versions. The Bot API 10.0 ``media`` field of ``InputPollOption`` will not be included for deserialization. + +""" + +pull_requests = [ + { uid = "5229", author_uid = "aelkheir", closes_threads = ["5228"] }, + { uid = "5230", author_uid = "harshil21" }, + { uid = "5235", author_uid = "harshil21" }, + { uid = "5238", author_uid = "harshil21" }, + { uid = "5232", author_uid = "aelkheir" }, + { uid = "5232", author_uid = ["Poolitzer", "Phil9l", "harshil21", "aelkheir"] }, +] diff --git a/docs/auxil/sphinx_hooks.py b/docs/auxil/sphinx_hooks.py index 7183a0dbaf7..796c367e2b0 100644 --- a/docs/auxil/sphinx_hooks.py +++ b/docs/auxil/sphinx_hooks.py @@ -52,6 +52,7 @@ "_BaseMedium": "TelegramObject", "_CredentialsBase": "TelegramObject", "_ChatBase": "TelegramObject", + "_BaseInputMedia": "TelegramObject", } diff --git a/docs/source/inclusions/bot_methods.rst b/docs/source/inclusions/bot_methods.rst index 72f58dea7bd..0a0a8e0f9a6 100644 --- a/docs/source/inclusions/bot_methods.rst +++ b/docs/source/inclusions/bot_methods.rst @@ -41,6 +41,8 @@ - Used for sending paid media to channels * - :meth:`~telegram.Bot.send_photo` - Used for sending photos + * - :meth:`~telegram.Bot.send_live_photo` + - Used for sending live photos * - :meth:`~telegram.Bot.send_poll` - Used for sending polls * - :meth:`~telegram.Bot.send_sticker` @@ -80,6 +82,8 @@ - Used for answering the callback query * - :meth:`~telegram.Bot.answer_inline_query` - Used for answering the inline query + * - :meth:`~telegram.Bot.answer_guest_query` + - Used for replying to a received guest message * - :meth:`~telegram.Bot.answer_pre_checkout_query` - Used for answering a pre checkout query * - :meth:`~telegram.Bot.answer_shipping_query` @@ -104,6 +108,10 @@ - Used for stopping the running poll * - :meth:`~telegram.Bot.set_message_reaction` - Used for setting reactions on messages + * - :meth:`~telegram.Bot.delete_message_reaction` + - Used for deleting reactions on messages + * - :meth:`~telegram.Bot.delete_all_message_reactions` + - Used for deleting all reactions by a chat or user .. raw:: html @@ -167,6 +175,8 @@ - Used for unpinning a message * - :meth:`~telegram.Bot.unpin_all_chat_messages` - Used for unpinning all pinned chat messages + * - :meth:`~telegram.Bot.get_user_personal_chat_messages` + - Used for obtaining the personal chat messages of a user * - :meth:`~telegram.Bot.get_user_profile_audios` - Used for obtaining user's profile audios * - :meth:`~telegram.Bot.get_user_profile_photos` @@ -237,6 +247,10 @@ - Used for obtaining the menu button of a private chat or the default menu button * - :meth:`~telegram.Bot.set_chat_menu_button` - Used for setting the menu button of a private chat or the default menu button + * - :meth:`~telegram.Bot.set_managed_bot_access_settings` + - Used for changing the access settings of a managed bot + * - :meth:`~telegram.Bot.get_managed_bot_access_settings` + - Used for obtaining the access settings of a managed bot * - :meth:`~telegram.Bot.set_my_description` - Used for setting the description of the bot * - :meth:`~telegram.Bot.get_my_description` diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index 25d9c269838..86f76d0f6ca 100644 --- a/docs/source/telegram.at-tree.rst +++ b/docs/source/telegram.at-tree.rst @@ -8,6 +8,7 @@ Available Types telegram.animation telegram.audio telegram.birthdate + telegram.botaccesssettings telegram.botcommand telegram.botcommandscope telegram.botcommandscopeallchatadministrators @@ -101,15 +102,22 @@ Available Types telegram.inputmediaanimation telegram.inputmediaaudio telegram.inputmediadocument + telegram.inputmedialivephoto + telegram.inputmedialocation telegram.inputmediaphoto + telegram.inputmediasticker + telegram.inputmediavenue telegram.inputmediavideo telegram.inputpaidmedia + telegram.inputpaidmedialivephoto telegram.inputpaidmediaphoto telegram.inputpaidmediavideo + telegram.inputpollmedia telegram.inputprofilephoto telegram.inputprofilephotoanimated telegram.inputprofilephotostatic telegram.inputpolloption + telegram.inputpolloptionmedia telegram.inputstorycontent telegram.inputstorycontentphoto telegram.inputstorycontentvideo @@ -119,6 +127,7 @@ Available Types telegram.keyboardbuttonrequestmanagedbot telegram.keyboardbuttonrequestusers telegram.linkpreviewoptions + telegram.livephoto telegram.location telegram.locationaddress telegram.loginurl @@ -146,6 +155,7 @@ Available Types telegram.ownedgiftunique telegram.paidmedia telegram.paidmediainfo + telegram.paidmedialivephoto telegram.paidmediaphoto telegram.paidmediapreview telegram.paidmediapurchased @@ -154,6 +164,7 @@ Available Types telegram.photosize telegram.poll telegram.pollanswer + telegram.pollmedia telegram.polloptionadded telegram.polloptiondeleted telegram.preparedkeyboardbutton @@ -166,6 +177,7 @@ Available Types telegram.replykeyboardmarkup telegram.replykeyboardremove telegram.replyparameters + telegram.sentguestmessage telegram.sentwebappmessage telegram.shareduser telegram.story diff --git a/docs/source/telegram.botaccesssettings.rst b/docs/source/telegram.botaccesssettings.rst new file mode 100644 index 00000000000..788689b9266 --- /dev/null +++ b/docs/source/telegram.botaccesssettings.rst @@ -0,0 +1,6 @@ +BotAccessSettings +================= + +.. autoclass:: telegram.BotAccessSettings + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.inputmedialivephoto.rst b/docs/source/telegram.inputmedialivephoto.rst new file mode 100644 index 00000000000..975614554a1 --- /dev/null +++ b/docs/source/telegram.inputmedialivephoto.rst @@ -0,0 +1,6 @@ +InputMediaLivePhoto +=================== + +.. autoclass:: telegram.InputMediaLivePhoto + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.inputmedialocation.rst b/docs/source/telegram.inputmedialocation.rst new file mode 100644 index 00000000000..aa20d631ea4 --- /dev/null +++ b/docs/source/telegram.inputmedialocation.rst @@ -0,0 +1,6 @@ +InputMediaLocation +================== + +.. autoclass:: telegram.InputMediaLocation + :members: + :show-inheritance: diff --git a/docs/source/telegram.inputmediasticker.rst b/docs/source/telegram.inputmediasticker.rst new file mode 100644 index 00000000000..7f2b6d7778e --- /dev/null +++ b/docs/source/telegram.inputmediasticker.rst @@ -0,0 +1,6 @@ +InputMediaSticker +================= + +.. autoclass:: telegram.InputMediaSticker + :members: + :show-inheritance: diff --git a/docs/source/telegram.inputmediavenue.rst b/docs/source/telegram.inputmediavenue.rst new file mode 100644 index 00000000000..e5e221e2f73 --- /dev/null +++ b/docs/source/telegram.inputmediavenue.rst @@ -0,0 +1,6 @@ +InputMediaVenue +=============== + +.. autoclass:: telegram.InputMediaVenue + :members: + :show-inheritance: diff --git a/docs/source/telegram.inputpaidmedialivephoto.rst b/docs/source/telegram.inputpaidmedialivephoto.rst new file mode 100644 index 00000000000..62bf1a2dac6 --- /dev/null +++ b/docs/source/telegram.inputpaidmedialivephoto.rst @@ -0,0 +1,6 @@ +InputPaidMediaLivePhoto +====================== + +.. autoclass:: telegram.InputPaidMediaLivePhoto + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.inputpollmedia.rst b/docs/source/telegram.inputpollmedia.rst new file mode 100644 index 00000000000..6178a00d4a4 --- /dev/null +++ b/docs/source/telegram.inputpollmedia.rst @@ -0,0 +1,6 @@ +InputPollMedia +============== + +.. versionadded:: NEXT.VERSION + +.. autoclass:: telegram.InputPollMedia diff --git a/docs/source/telegram.inputpolloptionmedia.rst b/docs/source/telegram.inputpolloptionmedia.rst new file mode 100644 index 00000000000..2af6693ad19 --- /dev/null +++ b/docs/source/telegram.inputpolloptionmedia.rst @@ -0,0 +1,6 @@ +InputPollOptionMedia +==================== + +.. versionadded:: NEXT.VERSION + +.. autoclass:: telegram.InputPollOptionMedia diff --git a/docs/source/telegram.livephoto.rst b/docs/source/telegram.livephoto.rst new file mode 100644 index 00000000000..969e4b4db4c --- /dev/null +++ b/docs/source/telegram.livephoto.rst @@ -0,0 +1,6 @@ +LivePhoto +========= + +.. autoclass:: telegram.LivePhoto + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.paidmedialivephoto.rst b/docs/source/telegram.paidmedialivephoto.rst new file mode 100644 index 00000000000..12fe8058ce9 --- /dev/null +++ b/docs/source/telegram.paidmedialivephoto.rst @@ -0,0 +1,6 @@ +PaidMediaLivePhoto +================== + +.. autoclass:: telegram.PaidMediaLivePhoto + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.pollmedia.rst b/docs/source/telegram.pollmedia.rst new file mode 100644 index 00000000000..8e7b38871b4 --- /dev/null +++ b/docs/source/telegram.pollmedia.rst @@ -0,0 +1,6 @@ +PollMedia +========= + +.. autoclass:: telegram.PollMedia + :members: + :show-inheritance: diff --git a/docs/source/telegram.sentguestmessage.rst b/docs/source/telegram.sentguestmessage.rst new file mode 100644 index 00000000000..b63e0735029 --- /dev/null +++ b/docs/source/telegram.sentguestmessage.rst @@ -0,0 +1,6 @@ +SentGuestMessage +================ + +.. autoclass:: telegram.SentGuestMessage + :members: + :show-inheritance: diff --git a/src/telegram/__init__.py b/src/telegram/__init__.py index 362f3253198..f0304c4962b 100644 --- a/src/telegram/__init__.py +++ b/src/telegram/__init__.py @@ -35,6 +35,7 @@ "BackgroundTypeWallpaper", "Birthdate", "Bot", + "BotAccessSettings", "BotCommand", "BotCommandScope", "BotCommandScopeAllChatAdministrators", @@ -157,13 +158,20 @@ "InputMediaAnimation", "InputMediaAudio", "InputMediaDocument", + "InputMediaLivePhoto", + "InputMediaLocation", "InputMediaPhoto", + "InputMediaSticker", + "InputMediaVenue", "InputMediaVideo", "InputMessageContent", "InputPaidMedia", + "InputPaidMediaLivePhoto", "InputPaidMediaPhoto", "InputPaidMediaVideo", + "InputPollMedia", "InputPollOption", + "InputPollOptionMedia", "InputProfilePhoto", "InputProfilePhotoAnimated", "InputProfilePhotoStatic", @@ -181,6 +189,7 @@ "KeyboardButtonRequestUsers", "LabeledPrice", "LinkPreviewOptions", + "LivePhoto", "Location", "LocationAddress", "LoginUrl", @@ -210,6 +219,7 @@ "OwnedGifts", "PaidMedia", "PaidMediaInfo", + "PaidMediaLivePhoto", "PaidMediaPhoto", "PaidMediaPreview", "PaidMediaPurchased", @@ -231,6 +241,7 @@ "PhotoSize", "Poll", "PollAnswer", + "PollMedia", "PollOption", "PollOptionAdded", "PollOptionDeleted", @@ -254,6 +265,7 @@ "RevenueWithdrawalStateSucceeded", "SecureData", "SecureValue", + "SentGuestMessage", "SentWebAppMessage", "SharedUser", "ShippingAddress", @@ -348,6 +360,7 @@ from . import _version, constants, error, helpers, request, warnings from ._birthdate import Birthdate from ._bot import Bot +from ._botaccesssettings import BotAccessSettings from ._botcommand import BotCommand from ._botcommandscope import ( BotCommandScope, @@ -435,11 +448,18 @@ InputMediaAnimation, InputMediaAudio, InputMediaDocument, + InputMediaLivePhoto, + InputMediaLocation, InputMediaPhoto, + InputMediaSticker, + InputMediaVenue, InputMediaVideo, InputPaidMedia, + InputPaidMediaLivePhoto, InputPaidMediaPhoto, InputPaidMediaVideo, + InputPollMedia, + InputPollOptionMedia, ) from ._files.inputprofilephoto import ( InputProfilePhoto, @@ -447,6 +467,7 @@ InputProfilePhotoStatic, ) from ._files.inputsticker import InputSticker +from ._files.livephoto import LivePhoto from ._files.location import Location from ._files.photosize import PhotoSize from ._files.sticker import MaskPosition, Sticker, StickerSet @@ -529,6 +550,7 @@ from ._paidmedia import ( PaidMedia, PaidMediaInfo, + PaidMediaLivePhoto, PaidMediaPhoto, PaidMediaPreview, PaidMediaPurchased, @@ -579,6 +601,7 @@ InputPollOption, Poll, PollAnswer, + PollMedia, PollOption, PollOptionAdded, PollOptionDeleted, @@ -595,6 +618,7 @@ from ._reply import ExternalReplyInfo, ReplyParameters, TextQuote from ._replykeyboardmarkup import ReplyKeyboardMarkup from ._replykeyboardremove import ReplyKeyboardRemove +from ._sentguestmessage import SentGuestMessage from ._sentwebappmessage import SentWebAppMessage from ._shared import ChatShared, SharedUser, UsersShared from ._story import Story diff --git a/src/telegram/_bot.py b/src/telegram/_bot.py index d9d1f83b069..fd873b067bf 100644 --- a/src/telegram/_bot.py +++ b/src/telegram/_bot.py @@ -47,7 +47,8 @@ serialization = None # type: ignore[assignment] CRYPTO_INSTALLED = False -from telegram._botcommand import BotCommand # pylint: disable=ungrouped-imports +from telegram._botaccesssettings import BotAccessSettings # pylint: disable=ungrouped-imports +from telegram._botcommand import BotCommand from telegram._botcommandscope import BotCommandScope from telegram._botdescription import BotDescription, BotShortDescription from telegram._botname import BotName @@ -65,6 +66,7 @@ from telegram._files.document import Document from telegram._files.file import File from telegram._files.inputmedia import InputMedia, InputPaidMedia +from telegram._files.livephoto import LivePhoto from telegram._files.location import Location from telegram._files.photosize import PhotoSize from telegram._files.sticker import MaskPosition, Sticker, StickerSet @@ -89,6 +91,7 @@ from telegram._preparedkeyboardbutton import PreparedKeyboardButton from telegram._reaction import ReactionType, ReactionTypeCustomEmoji, ReactionTypeEmoji from telegram._reply import ReplyParameters +from telegram._sentguestmessage import SentGuestMessage from telegram._sentwebappmessage import SentWebAppMessage from telegram._story import Story from telegram._telegramobject import TelegramObject @@ -126,8 +129,10 @@ InputFile, InputMediaAudio, InputMediaDocument, + InputMediaLivePhoto, InputMediaPhoto, InputMediaVideo, + InputPollMedia, InputProfilePhoto, InputSticker, InputStoryContent, @@ -1209,7 +1214,7 @@ async def send_message_draft( self, chat_id: int, draft_id: int, - text: str, + text: str | None = None, message_thread_id: int | None = None, parse_mode: ODVInput[str] = DEFAULT_NONE, entities: Sequence["MessageEntity"] | None = None, @@ -1221,7 +1226,9 @@ async def send_message_draft( api_kwargs: JSONDict | None = None, ) -> bool: """Use this method to stream a partial message to a user while the message is being - generated. + generated. Note that the streamed draft is ephemeral and acts as a temporary 30-second + preview - once the output is finalized, you must call :meth:`~Bot.send_message` with + the complete message to persist it in the user's chat. .. versionadded:: 22.6 @@ -1233,19 +1240,21 @@ async def send_message_draft( chat_id (:obj:`int`): Unique identifier for the target private chat. draft_id (:obj:`int`): Unique identifier of the message draft; must be non-zero. Changes of drafts with the same identifier are animated. - text (:obj:`str`): Text of the message to be sent, - :tg-const:`telegram.constants.MessageLimit.MIN_TEXT_LENGTH`- - :tg-const:`telegram.constants.MessageLimit.MAX_TEXT_LENGTH` characters after - entities parsing. + text (:obj:`str`, optional): Text of the message to be sent, + 0-:tg-const:`telegram.constants.MessageLimit.MAX_TEXT_LENGTH` characters after + entities parsing. Pass an empty text to show a "Thinking..." placeholder. + + .. versionchanged:: NEXT.VERSION + Bot API 10.0 now makes this an optional parameter. + + message_thread_id (:obj:`int`, optional): Unique identifier for the target + message thread. parse_mode (:obj:`str`): |parse_mode| entities (Sequence[:class:`telegram.MessageEntity`], optional): Sequence of special entities that appear in message text, which can be specified instead of :paramref:`parse_mode`. |sequenceargs| - message_thread_id (:obj:`int`, optional): Unique identifier for the target - message thread. - Returns: :obj:`bool`: On success, :obj:`True` is returned. @@ -2839,7 +2848,7 @@ async def send_media_group( self, chat_id: int | str, media: Sequence[ - "InputMediaAudio | InputMediaDocument | InputMediaPhoto | InputMediaVideo" + "InputMediaAudio | InputMediaDocument | InputMediaPhoto | InputMediaVideo | InputMediaLivePhoto" # noqa: E501 # pylint: disable=line-too-long ], disable_notification: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE, @@ -2878,8 +2887,8 @@ async def send_media_group( chat_id (:obj:`int` | :obj:`str`): |chat_id_channel| media (Sequence[:class:`telegram.InputMediaAudio`,\ :class:`telegram.InputMediaDocument`, :class:`telegram.InputMediaPhoto`,\ - :class:`telegram.InputMediaVideo`]): An array - describing messages to be sent, must include + :class:`telegram.InputMediaVideo`, :class:`telegram.InputMediaLivePhoto`]): An + array describing messages to be sent, must include :tg-const:`telegram.constants.MediaGroupLimit.MIN_MEDIA_LENGTH`- :tg-const:`telegram.constants.MediaGroupLimit.MAX_MEDIA_LENGTH` items. @@ -3677,7 +3686,7 @@ async def send_contact( async def send_game( self, - chat_id: int, + chat_id: int | str, game_short_name: str, disable_notification: ODVInput[bool] = DEFAULT_NONE, reply_markup: "InlineKeyboardMarkup | None" = None, @@ -3699,7 +3708,9 @@ async def send_game( """Use this method to send a game. Args: - chat_id (:obj:`int`): Unique identifier for the target chat. + chat_id (:obj:`int`): Unique identifier for the target chat or username of the + target bot in the format ``@username``. Games can't be sent to channel direct + messages chats and channel chats. game_short_name (:obj:`str`): Short name of the game, serves as the unique identifier for the game. Set up your games via `@BotFather `_. disable_notification (:obj:`bool`, optional): |disable_notification| @@ -4658,12 +4669,12 @@ async def edit_message_media( api_kwargs: JSONDict | None = None, ) -> "Message | bool": """ - Use this method to edit animation, audio, document, photo, or video messages, or to add - media to text messages. If a message + Use this method to edit animation, audio, document, live photo, photo, or video messages, + or to add media to text messages. If a message is part of a message album, then it can be edited only to an audio for audio albums, only - to a document for document albums and to a photo or a video otherwise. When an inline - message is edited, a new file can't be uploaded; use a previously uploaded file via its - :attr:`~telegram.File.file_id` or specify a URL. + to a document for document albums and to a photo, live photo, or a video otherwise. + When an inline message is edited, a new file can't be uploaded; use a previously + uploaded file via its :attr:`~telegram.File.file_id` or specify a URL. Note: * |editreplymarkup| @@ -5125,6 +5136,7 @@ async def get_chat( async def get_chat_administrators( self, chat_id: str | int, + return_bots: bool | None = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -5140,18 +5152,21 @@ async def get_chat_administrators( Args: chat_id (:obj:`int` | :obj:`str`): |chat_id_channel| + return_bots (:obj:`bool`, optional): Pass :obj:`True` to additionally receive all bots + that are administrators of the chat. By default, bots other than the current bot + are omitted. + + .. versionadded:: NEXT.VERSION Returns: tuple[:class:`telegram.ChatMember`]: On success, returns a tuple of ``ChatMember`` - objects that contains information about all chat administrators except - other bots. If the chat is a group or a supergroup and no administrators were - appointed, only the creator will be returned. + objects that contains information about all chat administrators. Raises: :class:`telegram.error.TelegramError` """ - data: JSONDict = {"chat_id": chat_id} + data: JSONDict = {"chat_id": chat_id, "return_bots": return_bots} result = await self._post( "getChatAdministrators", data, @@ -5848,6 +5863,51 @@ async def answer_web_app_query( return SentWebAppMessage.de_json(api_result, self) + async def answer_guest_query( + self, + guest_query_id: str, + result: "InlineQueryResult", + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> SentGuestMessage: + """Use this method to reply to a received guest message. + + .. versionadded:: NEXT.VERSION + + Args: + guest_query_id (:obj:`str`): Unique identifier for the query to be answered. + result (:class:`telegram.InlineQueryResult`): An object describing the message to be + sent. + + Returns: + :class:`telegram.SentGuestMessage`: On success, a + :class:`telegram.SentGuestMessage` is returned. + + Raises: + :class:`telegram.error.TelegramError` + + """ + data: JSONDict = { + "guest_query_id": guest_query_id, + "result": self._insert_defaults_for_ilq_results(result), + } + + api_result = await self._post( + "answerGuestQuery", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + return SentGuestMessage.de_json(api_result, self) + async def restrict_chat_member( self, chat_id: str | int, @@ -7624,9 +7684,13 @@ async def send_poll( hide_results_until_closes: bool | None = None, correct_option_ids: CorrectOptionIds | None = None, description: str | None = None, - description_parse_mode: str | None = None, + description_parse_mode: ODVInput[str] | None = None, description_entities: Sequence["MessageEntity"] | None = None, shuffle_options: bool | None = None, + members_only: bool | None = None, + country_codes: Sequence[str] | None = None, + explanation_media: "InputPollMedia | None" = None, + media: "InputPollMedia | None" = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: int | None = None, @@ -7769,6 +7833,27 @@ async def send_poll( shuffle_options (:obj:`bool`, optional): :obj:`True`, if the poll options must be shown in random order + .. versionadded:: NEXT.VERSION + members_only (:obj:`bool`, optional): :obj:`True`, if voting is limited to users who + have been members of the chat where the poll is being sent for more than + :tg-const:`telegram.Poll.MIN_MEMBERSHIP_HOURS` hours; for channel chats only + + .. versionadded:: NEXT.VERSION + country_codes (Sequence[:obj:`str`], optional): A list of + 0-:tg-const:`telegram.constants.PollLimit.MAX_COUNTRY_CODES` two-letter + ``ISO 3166-1 alpha-2`` country codes indicating the countries from which users can + vote in the poll; for channel chats only. Use ``"FT"`` as a country code to allow + users with anonymous numbers to vote. If omitted or empty, then users from any + country can participate in the poll. + + .. versionadded:: NEXT.VERSION + explanation_media (:class:`telegram.InputPollMedia`, optional): Media added to the quiz + explanation + + .. versionadded:: NEXT.VERSION + media (:class:`telegram.InputPollMedia`, optional): Media added to the poll + description. + .. versionadded:: NEXT.VERSION Keyword Args: @@ -7837,6 +7922,10 @@ async def send_poll( "close_date": close_date, "question_parse_mode": question_parse_mode, "question_entities": question_entities, + "members_only": members_only, + "country_codes": country_codes, + "explanation_media": explanation_media, + "media": media, } return await self._send_message( @@ -7912,7 +8001,7 @@ async def stop_poll( async def send_checklist( self, business_connection_id: str, - chat_id: int, + chat_id: int | str, checklist: InputChecklist, disable_notification: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE, @@ -7934,20 +8023,14 @@ async def send_checklist( .. versionadded:: 22.3 Args: - business_connection_id (:obj:`str`): - |business_id_str| - chat_id (:obj:`int`): - Unique identifier for the target chat. - checklist (:class:`telegram.InputChecklist`): - The checklist to send. - disable_notification (:obj:`bool`, optional): - |disable_notification| - protect_content (:obj:`bool`, optional): - |protect_content| - message_effect_id (:obj:`str`, optional): - |message_effect_id| - reply_parameters (:class:`telegram.ReplyParameters`, optional): - |reply_parameters| + business_connection_id (:obj:`str`): |business_id_str| + chat_id (:obj:`int`): Unique identifier for the target chat or username of the target + bot in the format ``@username``. + checklist (:class:`telegram.InputChecklist`): The checklist to send. + disable_notification (:obj:`bool`, optional): |disable_notification| + protect_content (:obj:`bool`, optional): |protect_content| + message_effect_id (:obj:`str`, optional): |message_effect_id| + reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters| reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): An object for an inline keyboard @@ -7992,7 +8075,7 @@ async def send_checklist( async def edit_message_checklist( self, business_connection_id: str, - chat_id: int, + chat_id: int | str, message_id: int, checklist: InputChecklist, reply_markup: "InlineKeyboardMarkup | None" = None, @@ -8009,14 +8092,11 @@ async def edit_message_checklist( .. versionadded:: 22.3 Args: - business_connection_id (:obj:`str`): - |business_id_str| - chat_id (:obj:`int`): - Unique identifier for the target chat. - message_id (:obj:`int`): - Unique identifier for the target message. - checklist (:class:`telegram.InputChecklist`): - The new checklist. + business_connection_id (:obj:`str`): |business_id_str| + chat_id (:obj:`int`): Unique identifier for the target chat or username of the target + bot in the format ``@username``. + message_id (:obj:`int`): Unique identifier for the target message. + checklist (:class:`telegram.InputChecklist`): The new checklist. reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): An object for the new inline keyboard for the message. @@ -8745,7 +8825,7 @@ async def set_chat_menu_button( Args: chat_id (:obj:`int`, optional): Unique identifier for the target private chat. If not - specified, default bot's menu button will be changed + specified, the bot's default menu button will be changed menu_button (:class:`telegram.MenuButton`, optional): An object for the new bot's menu button. Defaults to :class:`telegram.MenuButtonDefault`. @@ -8785,7 +8865,7 @@ async def get_chat_menu_button( Args: chat_id (:obj:`int`, optional): Unique identifier for the target private chat. If not - specified, default bot's menu button will be returned. + specified, the bot's default menu button will be returned. Returns: :class:`telegram.MenuButton`: On success, the current menu button is returned. @@ -11123,6 +11203,145 @@ async def edit_user_star_subscription( api_kwargs=api_kwargs, ) + async def get_managed_bot_access_settings( + self, + user_id: int, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> BotAccessSettings: + """ + Use this method to get the access settings of a managed bot. + + .. versionadded:: NEXT.VERSION + + Args: + user_id (:obj:`int`): User identifier of the managed bot whose access settings will be + returned. + + Returns: + :class:`telegram.BotAccessSettings`: The access settings of the managed bot. + + Raises: + :class:`telegram.error.TelegramError` + """ + + data: JSONDict = { + "user_id": user_id, + } + + return BotAccessSettings.de_json( + await self._post( + "getManagedBotAccessSettings", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ), + self, + ) + + async def set_managed_bot_access_settings( + self, + user_id: int, + is_access_restricted: bool, + added_user_ids: Sequence[int] | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Use this method to change the access settings of a managed bot. + + .. versionadded:: NEXT.VERSION + + Args: + user_id (:obj:`int`): User identifier of the managed bot whose access settings will be + changed. + is_access_restricted (:obj:`bool`): Pass :obj:`True`, if only selected users can access + the bot. The bot's owner can always access it. + added_user_ids (Sequence[:obj:`int`], optional): A list of up to + :tg-const:`telegram.constants.ManagedBotAccessLimit.MAX_ALLOWED_USERS` + identifiers of users who will have access to the bot in addition to its owner. + Ignored if :paramref:`is_access_restricted` is :obj:`False`. + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + + Raises: + :class:`telegram.error.TelegramError` + """ + + data: JSONDict = { + "user_id": user_id, + "is_access_restricted": is_access_restricted, + "added_user_ids": added_user_ids, + } + + return await self._post( + "setManagedBotAccessSettings", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def get_user_personal_chat_messages( + self, + user_id: int, + limit: int, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> tuple[Message, ...]: + """ + Use this method to get the last messages from the personal chat (i.e., the chat currently + added to their profile) of a given user. + + .. versionadded:: NEXT.VERSION + + Args: + user_id (:obj:`int`): Unique identifier of the target user. + limit (:obj:`int`): The maximum number of messages to return; + :tg-const:`telegram.constants.PersonalChatMessagesLimit.MIN_LIMIT`- + :tg-const:`telegram.constants.PersonalChatMessagesLimit.MAX_LIMIT`. + + Returns: + tuple[:class:`telegram.Message`, ...]: On success, a tuple of + :class:`telegram.Message` objects is returned. + + Raises: + :class:`telegram.error.TelegramError` + """ + + data: JSONDict = {"user_id": user_id, "limit": limit} + + return Message.de_list( + await self._post( + "getUserPersonalChatMessages", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ), + self, + ) + async def send_paid_media( self, chat_id: str | int, @@ -12283,6 +12502,240 @@ async def save_prepared_keyboard_button( self, ) + async def send_live_photo( + self, + chat_id: int | str, + live_photo: "FileInput | LivePhoto", + photo: "FileInput | PhotoSize", + business_connection_id: str | None = None, + message_thread_id: int | None = None, + direct_messages_topic_id: int | None = None, + caption: str | None = None, + parse_mode: ODVInput[str] = DEFAULT_NONE, + caption_entities: Sequence["MessageEntity"] | None = None, + show_caption_above_media: bool | None = None, + has_spoiler: bool | None = None, + disable_notification: ODVInput[bool] = DEFAULT_NONE, + protect_content: ODVInput[bool] = DEFAULT_NONE, + allow_paid_broadcast: bool | None = None, + message_effect_id: str | None = None, + suggested_post_parameters: "SuggestedPostParameters | None" = None, + reply_parameters: "ReplyParameters | None" = None, + reply_markup: "ReplyMarkup | None" = None, + *, + allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + reply_to_message_id: int | None = None, + filename: str | None = None, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> Message: + """ + Use this method to send live photos. + + .. seealso:: :wiki:`Working with Files and Media ` + + .. versionadded:: NEXT.VERSION + + Args: + chat_id (:obj:`int` | :obj:`str`): |chat_id_channel| + live_photo (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | \ + :obj:`bytes` | :class:`pathlib.Path` | :class:`telegram.LivePhoto`): Live photo + video to send. Pass a ``file_id`` to send a file that exists on the Telegram + servers (recommended). |uploadinputnopath| Sending live photos by a URL is + currently unsupported. Lastly you can pass an existing + :class:`telegram.LivePhoto` object to send. + + Caution: + * The video must be at most 10MB in size. + * The video duration must not exceed 10 seconds. + * If you pass a :class:`telegram.LivePhoto`, its + :attr:`~telegram.LivePhoto.photo` field will not be considered, use + :paramref:`photo` to specify the photo to send. + + photo (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \ + | :class:`pathlib.Path` | :class:`telegram.PhotoSize`): The static photo to send. + Pass a ``file_id`` to send a file that exists on the Telegram servers (recommended) + . |uploadinputnopath| Sending live photos by a URL is currently unsupported. + Lastly you can pass an existing :class:`telegram.PhotoSize` object to send. + business_connection_id (:obj:`str`, optional): |business_id_str| + message_thread_id (:obj:`int`, optional): |message_thread_id_arg| + direct_messages_topic_id (:obj:`int`, optional): |direct_messages_topic_id| + 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. + parse_mode (:obj:`str`, optional): |parse_mode| + caption_entities (Sequence[:class:`telegram.MessageEntity`], optional): + |caption_entities| + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + has_spoiler (:obj:`bool`, optional): Pass :obj:`True` if the video needs to be covered + with a spoiler animation. + disable_notification (:obj:`bool`, optional): |disable_notification| + protect_content (:obj:`bool`, optional): |protect_content| + allow_paid_broadcast (:obj:`bool`, optional): |allow_paid_broadcast| + message_effect_id (:obj:`str`, optional): |message_effect_id| + suggested_post_parameters (:class:`telegram.SuggestedPostParameters`, optional): + |suggested_post_parameters| + reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters| + reply_markup (:class:`InlineKeyboardMarkup` | :class:`ReplyKeyboardMarkup` | \ + :class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional): + Additional interface options. An object for an inline keyboard, custom reply + keyboard, instructions to remove reply keyboard or to force a reply from the user. + + Keyword Args: + allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| + Mutually exclusive with :paramref:`reply_parameters`, which this is a convenience + parameter for + + reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id| + Mutually exclusive with :paramref:`reply_parameters`, which this is a convenience + parameter for. + + filename (:obj:`str`, optional): Custom file name for :paramref:`photo`, when + uploading a new file. Convenience parameter, useful e.g. when sending files + generated by the :obj:`tempfile` module. + + Returns: + :class:`telegram.Message`: On success, the sent Message is returned. + + Raises: + :class:`telegram.error.TelegramError` + + """ + data: JSONDict = { + "chat_id": chat_id, + "live_photo": self._parse_file_input(live_photo, LivePhoto), + "photo": self._parse_file_input(photo, PhotoSize, filename=filename), + "has_spoiler": has_spoiler, + "show_caption_above_media": show_caption_above_media, + } + + return await self._send_message( + "sendLivePhoto", + data, + disable_notification=disable_notification, + reply_markup=reply_markup, + protect_content=protect_content, + message_thread_id=message_thread_id, + caption=caption, + parse_mode=parse_mode, + caption_entities=caption_entities, + reply_parameters=reply_parameters, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + business_connection_id=business_connection_id, + message_effect_id=message_effect_id, + allow_paid_broadcast=allow_paid_broadcast, + direct_messages_topic_id=direct_messages_topic_id, + suggested_post_parameters=suggested_post_parameters, + allow_sending_without_reply=allow_sending_without_reply, + reply_to_message_id=reply_to_message_id, + ) + + async def delete_message_reaction( + self, + chat_id: int | str, + message_id: int, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Use this method to remove a reaction from a message in a group or a supergroup chat. + The bot must have the :attr:`~telegram.ChatMemberAdministrator.can_delete_messages` + administrator right in the chat. + + .. versionadded:: NEXT.VERSION + + Args: + chat_id (:obj:`int` | :obj:`str`): |chat_id_group| + message_id (:obj:`int`): Identifier of the target message. + user_id (:obj:`int`, optional): Identifier of the user whose reaction will be removed, + if the reaction were added by a user. + actor_chat_id (:obj:`int`, optional): Identifier of the chat whose reaction will be + removed, if the reaction were added by a chat. + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + + Raises: + :class:`telegram.error.TelegramError` + """ + data: JSONDict = { + "chat_id": chat_id, + "message_id": message_id, + "user_id": user_id, + "actor_chat_id": actor_chat_id, + } + + return await self._post( + "deleteMessageReaction", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def delete_all_message_reactions( + self, + chat_id: int | str, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Use this method to remove up to ``10000`` recent reactions in a group or a supergroup chat + added by a given user or chat. The bot must have the + :attr:`~telegram.ChatMemberAdministrator.can_delete_messages` administrator right in the + chat. + + .. versionadded:: NEXT.VERSION + + Args: + chat_id (:obj:`int` | :obj:`str`): |chat_id_group| + user_id (:obj:`int`, optional): Identifier of the user whose reactions will be removed, + if the reactions were added by a user. + actor_chat_id (:obj:`int`, optional): Identifier of the chat whose reactions will be + removed, if the reactions were added by a chat. + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + + Raises: + :class:`telegram.error.TelegramError` + """ + data: JSONDict = { + "chat_id": chat_id, + "user_id": user_id, + "actor_chat_id": actor_chat_id, + } + + return await self._post( + "deleteAllMessageReactions", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002 """See :meth:`telegram.TelegramObject.to_dict`.""" data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name} @@ -12399,6 +12852,8 @@ def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002 """Alias for :meth:`answer_pre_checkout_query`""" answerWebAppQuery = answer_web_app_query """Alias for :meth:`answer_web_app_query`""" + answerGuestQuery = answer_guest_query + """Alias for :meth:`answer_guest_query`""" restrictChatMember = restrict_chat_member """Alias for :meth:`restrict_chat_member`""" promoteChatMember = promote_chat_member @@ -12629,3 +13084,15 @@ def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002 """Alias for :meth:`replace_managed_bot_token`""" savePreparedKeyboardButton = save_prepared_keyboard_button """Alias for :meth:`save_prepared_keyboard_button`""" + sendLivePhoto = send_live_photo + """Alias for :meth:`send_live_photo`""" + getManagedBotAccessSettings = get_managed_bot_access_settings + """Alias for :meth:`get_managed_bot_access_settings`""" + setManagedBotAccessSettings = set_managed_bot_access_settings + """Alias for :meth:`set_managed_bot_access_settings`""" + getUserPersonalChatMessages = get_user_personal_chat_messages + """Alias for :meth:`get_user_personal_chat_messages`""" + deleteMessageReaction = delete_message_reaction + """Alias for :meth:`delete_message_reaction`""" + deleteAllMessageReactions = delete_all_message_reactions + """Alias for :meth:`delete_all_message_reactions`""" diff --git a/src/telegram/_botaccesssettings.py b/src/telegram/_botaccesssettings.py new file mode 100644 index 00000000000..745d63ae67b --- /dev/null +++ b/src/telegram/_botaccesssettings.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2026 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""This module contains an object that represents a Telegram Bot Access Settings.""" + +from collections.abc import Sequence +from typing import TYPE_CHECKING + +from telegram._telegramobject import TelegramObject +from telegram._user import User +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 BotAccessSettings(TelegramObject): + """ + This object describes the access settings of a bot. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`is_access_restricted` and :attr:`added_users` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + is_access_restricted (:obj:`bool`): :obj:`True`, if only selected users can access the bot. + The bot's owner can always access it. + added_users (Sequence[:class:`telegram.User`], optional): The list of other users who + have access to the bot if the access is restricted. + + Attributes: + is_access_restricted (:obj:`bool`): :obj:`True`, if only selected users can access the bot. + The bot's owner can always access it. + added_users (Sequence[:class:`telegram.User`]): Optional. The list of other users who + have access to the bot if the access is restricted. + """ + + __slots__ = ("added_users", "is_access_restricted") + + def __init__( + self, + is_access_restricted: bool, + added_users: Sequence[User] | None = None, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.is_access_restricted: bool = is_access_restricted + self.added_users: tuple[User, ...] = parse_sequence_arg(added_users) + + self._id_attrs = (self.is_access_restricted, self.added_users) + self._freeze() + + @classmethod + def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "BotAccessSettings": + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + data["added_users"] = de_list_optional(data.get("added_users"), User, bot) + + return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_chat.py b/src/telegram/_chat.py index 764da868e7e..d7b7a60c73b 100644 --- a/src/telegram/_chat.py +++ b/src/telegram/_chat.py @@ -58,12 +58,15 @@ InputChecklist, InputMediaAudio, InputMediaDocument, + InputMediaLivePhoto, InputMediaPhoto, InputMediaVideo, InputPaidMedia, + InputPollMedia, InputPollOption, LabeledPrice, LinkPreviewOptions, + LivePhoto, Location, Message, MessageEntity, @@ -311,6 +314,7 @@ async def leave( async def get_administrators( self, + return_bots: bool | None = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -334,6 +338,7 @@ async def get_administrators( """ return await self.get_bot().get_chat_administrators( chat_id=self.id, + return_bots=return_bots, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, @@ -1088,7 +1093,7 @@ async def send_message( async def send_message_draft( self, draft_id: int, - text: str, + text: str | None = None, message_thread_id: int | None = None, parse_mode: ODVInput[str] = DEFAULT_NONE, entities: Sequence["MessageEntity"] | None = None, @@ -1105,6 +1110,9 @@ async def send_message_draft( For the documentation of the arguments, please see :meth:`telegram.Bot.send_message_draft`. + .. versionchanged:: NEXT.VERSION + Bot API 10.0 makes the ``text`` argument optional. + Returns: :obj:`bool`: On success, :obj:`True` is returned. @@ -1190,7 +1198,7 @@ async def delete_messages( async def send_media_group( self, media: Sequence[ - "InputMediaAudio | InputMediaDocument | InputMediaPhoto | InputMediaVideo" + "InputMediaAudio | InputMediaDocument | InputMediaPhoto | InputMediaVideo | InputMediaLivePhoto" # noqa: E501 # pylint: disable=line-too-long ], disable_notification: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE, @@ -1349,6 +1357,76 @@ async def send_photo( suggested_post_parameters=suggested_post_parameters, ) + async def send_live_photo( + self, + live_photo: "FileInput | LivePhoto", + photo: "FileInput | PhotoSize", + business_connection_id: str | None = None, + message_thread_id: int | None = None, + direct_messages_topic_id: int | None = None, + caption: str | None = None, + parse_mode: ODVInput[str] = DEFAULT_NONE, + caption_entities: Sequence["MessageEntity"] | None = None, + show_caption_above_media: bool | None = None, + has_spoiler: bool | None = None, + disable_notification: ODVInput[bool] = DEFAULT_NONE, + protect_content: ODVInput[bool] = DEFAULT_NONE, + allow_paid_broadcast: bool | None = None, + message_effect_id: str | None = None, + suggested_post_parameters: "SuggestedPostParameters | None" = None, + reply_parameters: "ReplyParameters | None" = None, + reply_markup: "ReplyMarkup | None" = None, + *, + allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + reply_to_message_id: int | None = None, + filename: str | None = None, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> "Message": + """Shortcut for:: + + await bot.send_live_photo(update.effective_chat.id, *args, **kwargs) + + For the documentation of the arguments, please see :meth:`telegram.Bot.send_live_photo`. + + .. versionadded:: NEXT.VERSION + + Returns: + :class:`telegram.Message`: On success, instance representing the message posted. + + """ + return await self.get_bot().send_live_photo( + chat_id=self.id, + live_photo=live_photo, + photo=photo, + business_connection_id=business_connection_id, + message_thread_id=message_thread_id, + direct_messages_topic_id=direct_messages_topic_id, + caption=caption, + parse_mode=parse_mode, + caption_entities=caption_entities, + show_caption_above_media=show_caption_above_media, + has_spoiler=has_spoiler, + disable_notification=disable_notification, + protect_content=protect_content, + allow_paid_broadcast=allow_paid_broadcast, + message_effect_id=message_effect_id, + suggested_post_parameters=suggested_post_parameters, + reply_parameters=reply_parameters, + reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, + reply_to_message_id=reply_to_message_id, + filename=filename, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + async def send_contact( self, phone_number: str | None = None, @@ -2303,8 +2381,12 @@ async def send_poll( allow_adding_options: bool | None = None, hide_results_until_closes: bool | None = None, description: str | None = None, - description_parse_mode: str | None = None, + description_parse_mode: ODVInput[str] | None = None, description_entities: Sequence["MessageEntity"] | None = None, + members_only: bool | None = None, + country_codes: Sequence[str] | None = None, + explanation_media: "InputPollMedia | None" = None, + media: "InputPollMedia | None" = None, *, reply_to_message_id: int | None = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2363,6 +2445,10 @@ async def send_poll( description_entities=description_entities, hide_results_until_closes=hide_results_until_closes, allow_adding_options=allow_adding_options, + members_only=members_only, + country_codes=country_codes, + explanation_media=explanation_media, + media=media, ) async def send_copy( @@ -4049,6 +4135,82 @@ async def set_chat_member_tag( api_kwargs=api_kwargs, ) + async def delete_reaction( + self, + message_id: int, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Shortcut for:: + + await bot.delete_message_reaction(chat_id=update.effective_chat.id, *args, **kwargs) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.delete_message_reaction`. + + .. versionadded:: NEXT.VERSION + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().delete_message_reaction( + chat_id=self.id, + message_id=message_id, + user_id=user_id, + actor_chat_id=actor_chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def delete_all_reactions( + self, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Shortcut for:: + + await bot.delete_all_message_reactions( + chat_id=update.effective_chat.id, + *args, + **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.delete_all_message_reactions`. + + .. versionadded:: NEXT.VERSION + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().delete_all_message_reactions( + chat_id=self.id, + user_id=user_id, + actor_chat_id=actor_chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + class Chat(_ChatBase): """This object represents a chat. diff --git a/src/telegram/_chatfullinfo.py b/src/telegram/_chatfullinfo.py index 7142b5feb6a..3e240b08879 100644 --- a/src/telegram/_chatfullinfo.py +++ b/src/telegram/_chatfullinfo.py @@ -245,7 +245,7 @@ class ChatFullInfo(_ChatBase): .. versionadded:: 22.6 paid_message_star_count (:obj:`int`, optional): The number of Telegram Stars a general user - have to pay to send a message to the chat + has to pay to send a message to the chat .. versionadded:: 22.6 first_profile_audio (:obj:`telegram.Audio`, optional): For private chats, the first audio diff --git a/src/telegram/_chatmember.py b/src/telegram/_chatmember.py index be55c8469e8..58cce787710 100644 --- a/src/telegram/_chatmember.py +++ b/src/telegram/_chatmember.py @@ -515,8 +515,13 @@ class ChatMemberRestricted(ChatMember): .. versionadded:: 20.1 can_edit_tag (:obj:`bool`): :obj:`True`, if the user is allowed to edit their own tag. + If omitted, defaults to the value of :attr:`can_pin_messages`. .. versionadded:: 22.7 + can_react_to_messages (:obj:`bool`): :obj:`True`, if the user is allowed to react to + messages. + + .. versionadded:: NEXT.VERSION tag (:obj:`str`, optional): Tag of the member. .. versionadded:: 22.7 @@ -571,8 +576,13 @@ class ChatMemberRestricted(ChatMember): .. versionadded:: 20.1 can_edit_tag (:obj:`bool`): :obj:`True`, if the user is allowed to edit their own tag. + If omitted, defaults to the value of :attr:`can_pin_messages`. .. versionadded:: 22.7 + can_react_to_messages (:obj:`bool`): :obj:`True`, if the user is allowed to react to + messages. + + .. versionadded:: NEXT.VERSION tag (:obj:`str`): Optional. Tag of the member. .. versionadded:: 22.7 @@ -586,6 +596,7 @@ class ChatMemberRestricted(ChatMember): "can_invite_users", "can_manage_topics", "can_pin_messages", + "can_react_to_messages", "can_send_audios", "can_send_documents", "can_send_messages", @@ -621,10 +632,17 @@ def __init__( can_send_voice_notes: bool, can_edit_tag: bool, tag: str | None = None, + # tags: NEXT.VERSION + # temporarily optional to make it not breaking + can_react_to_messages: bool | None = None, *, api_kwargs: JSONDict | None = None, ): super().__init__(status=ChatMember.RESTRICTED, user=user, api_kwargs=api_kwargs) + + if can_react_to_messages is None: + raise TypeError("`can_react_to_messages` is required and cannot be None") + with self._unfrozen(): self.is_member: bool = is_member self.can_change_info: bool = can_change_info @@ -643,6 +661,7 @@ def __init__( self.can_send_video_notes: bool = can_send_video_notes self.can_send_voice_notes: bool = can_send_voice_notes self.can_edit_tag: bool = can_edit_tag + self.can_react_to_messages: bool = can_react_to_messages self.tag: str | None = tag diff --git a/src/telegram/_chatowner.py b/src/telegram/_chatowner.py index 1d8ce6619bd..17aaaff295d 100644 --- a/src/telegram/_chatowner.py +++ b/src/telegram/_chatowner.py @@ -79,11 +79,11 @@ class ChatOwnerLeft(TelegramObject): .. versionadded:: 22.7 Args: - new_owner (:class:`telegram.User`, optional): The user which will be the new owner of the + new_owner (:class:`telegram.User`, optional): The user who will become the new owner of the chat if the previous owner does not return to the chat Attributes: - new_owner (:class:`telegram.User`): Optional. The user which will be the new owner of the + new_owner (:class:`telegram.User`): Optional. The user who will become the new owner of the chat if the previous owner does not return to the chat """ diff --git a/src/telegram/_chatpermissions.py b/src/telegram/_chatpermissions.py index 2116f4f4c33..7ca1871473d 100644 --- a/src/telegram/_chatpermissions.py +++ b/src/telegram/_chatpermissions.py @@ -36,7 +36,7 @@ class ChatPermissions(TelegramObject): :attr:`can_change_info`, :attr:`can_invite_users`, :attr:`can_pin_messages`, :attr:`can_send_audios`, :attr:`can_send_documents`, :attr:`can_send_photos`, :attr:`can_send_videos`, :attr:`can_send_video_notes`, :attr:`can_send_voice_notes`, - :attr:`can_manage_topics` and :attr:`can_edit_tag` are equal. + :attr:`can_manage_topics`, :attr:`can_edit_tag`, and :attr:`can_react_to_messages` are equal. .. versionchanged:: 20.0 :attr:`can_manage_topics` is considered as well when comparing objects of @@ -50,6 +50,9 @@ class ChatPermissions(TelegramObject): .. versionchanged:: 22.7 :attr:`can_edit_tag` is considered as well when comparing objects of this type in terms of equality. + .. versionchanged:: NEXT.VERSION + :attr:`can_react_to_messages` is considered as well when comparing objects of + this type in terms of equality. Note: @@ -100,6 +103,10 @@ class ChatPermissions(TelegramObject): tag. .. versionadded:: 22.7 + can_react_to_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to react + to messages. If omitted, defaults to the value of :attr:`can_send_messages`. + + .. versionadded:: NEXT.VERSION Attributes: can_send_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to send text @@ -145,6 +152,10 @@ class ChatPermissions(TelegramObject): tag. .. versionadded:: 22.7 + can_react_to_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to react + to messages. If omitted, defaults to the value of :attr:`can_send_messages`. + + .. versionadded:: NEXT.VERSION """ @@ -155,6 +166,7 @@ class ChatPermissions(TelegramObject): "can_invite_users", "can_manage_topics", "can_pin_messages", + "can_react_to_messages", "can_send_audios", "can_send_documents", "can_send_messages", @@ -183,6 +195,7 @@ def __init__( can_send_video_notes: bool | None = None, can_send_voice_notes: bool | None = None, can_edit_tag: bool | None = None, + can_react_to_messages: bool | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -203,6 +216,7 @@ def __init__( self.can_send_video_notes: bool | None = can_send_video_notes self.can_send_voice_notes: bool | None = can_send_voice_notes self.can_edit_tag: bool | None = can_edit_tag + self.can_react_to_messages: bool | None = can_react_to_messages self._id_attrs = ( self.can_send_messages, @@ -220,6 +234,7 @@ def __init__( self.can_send_video_notes, self.can_send_voice_notes, self.can_edit_tag, + self.can_react_to_messages, ) self._freeze() diff --git a/src/telegram/_files/inputmedia.py b/src/telegram/_files/inputmedia.py index 23b6620985a..80886258414 100644 --- a/src/telegram/_files/inputmedia.py +++ b/src/telegram/_files/inputmedia.py @@ -16,7 +16,8 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. -"""Base class for Telegram InputMedia Objects.""" +"""Base classes for Telegram InputMedia, InputPaidMedia, InputPollMedia +and InputPollOptionMedia Objects.""" import datetime as dtm from collections.abc import Sequence @@ -28,6 +29,7 @@ from telegram._files.document import Document from telegram._files.inputfile import InputFile from telegram._files.photosize import PhotoSize +from telegram._files.sticker import Sticker from telegram._files.video import Video from telegram._messageentity import MessageEntity from telegram._telegramobject import TelegramObject @@ -37,17 +39,47 @@ from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.files import parse_file_input from telegram._utils.types import JSONDict, ODVInput, TimePeriod -from telegram.constants import InputMediaType +from telegram._utils.warnings import warn +from telegram.constants import BaseInputMediaType +from telegram.warnings import PTBDeprecationWarning if TYPE_CHECKING: from telegram._utils.types import FileInput -MediaType: TypeAlias = Animation | Audio | Document | PhotoSize | Video +class _BaseInputMedia(TelegramObject): + """ + Base class for objects representing the various input media types. + + Args: + media_type (:obj:`str`): Type of media that the instance represents. + + Attributes: + type (:obj:`str`): Type of media that the instance represents. + """ + + __slots__ = ("type",) + + def __init__( + self, + media_type: str, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.type: str = enum.get_member(constants.BaseInputMediaType, media_type, media_type) -class InputMedia(TelegramObject): + +class InputMedia(_BaseInputMedia): """ - Base class for Telegram InputMedia Objects. + This object represents the content of a media message to be sent. It should be one of: + + * :class:`telegram.InputMediaAnimation` + * :class:`telegram.InputMediaAudio` + * :class:`telegram.InputMediaDocument` + * :class:`telegram.InputMediaLivePhoto` + * :class:`telegram.InputMediaPhoto` + * :class:`telegram.InputMediaVideo` .. versionchanged:: 20.0 Added arguments and attributes :attr:`type`, :attr:`media`, :attr:`caption`, @@ -85,7 +117,7 @@ class InputMedia(TelegramObject): """ - __slots__ = ("caption", "caption_entities", "media", "parse_mode", "type") + __slots__ = ("caption", "caption_entities", "media", "parse_mode") def __init__( self, @@ -97,14 +129,12 @@ def __init__( *, api_kwargs: JSONDict | None = None, ): - super().__init__(api_kwargs=api_kwargs) - self.type: str = enum.get_member(constants.InputMediaType, media_type, media_type) - self.media: str | InputFile = media - self.caption: str | None = caption - self.caption_entities: tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) - self.parse_mode: ODVInput[str] = parse_mode - - self._freeze() + super().__init__(media_type=media_type, api_kwargs=api_kwargs) + with self._unfrozen(): + self.media: str | InputFile = media + self.caption: str | None = caption + self.caption_entities: tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) + self.parse_mode: ODVInput[str] = parse_mode @staticmethod def _parse_thumbnail_input(thumbnail: "FileInput | None") -> str | InputFile | None: @@ -123,6 +153,7 @@ class InputPaidMedia(TelegramObject): * :class:`telegram.InputPaidMediaPhoto` * :class:`telegram.InputPaidMediaVideo` + * :class:`telegram.InputPaidMediaLivePhoto` .. seealso:: :wiki:`Working with Files and Media ` @@ -142,6 +173,11 @@ class InputPaidMedia(TelegramObject): """:const:`telegram.constants.InputPaidMediaType.PHOTO`""" VIDEO: Final[str] = constants.InputPaidMediaType.VIDEO """:const:`telegram.constants.InputPaidMediaType.VIDEO`""" + LIVE_PHOTO: Final[str] = constants.InputPaidMediaType.LIVE_PHOTO + """:const:`telegram.constants.InputPaidMediaType.LIVE_PHOTO` + + .. versionadded:: NEXT.VERSION + """ __slots__ = ("media", "type") @@ -300,6 +336,51 @@ def duration(self) -> int | dtm.timedelta | None: return get_timedelta_value(self._duration, attribute="duration") +class InputPaidMediaLivePhoto(InputPaidMedia): + """ + The paid media to send is a live photo. + + .. seealso:: :wiki:`Working with Files and Media ` + + .. versionadded:: NEXT.VERSION + + Args: + media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \ + | :class:`pathlib.Path` | :class:`~telegram.Video`): Video of the live photo to send. + Pass a ``file_id`` to send a file that exists on the Telegram servers (recommended). + |uploadinputnopath| Sending live photos by a URL is currently unsupported. Lastly you + can pass an existing :class:`telegram.Video` object to send. + photo (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \ + :class:`pathlib.Path` | :class:`~telegram.PhotoSize`): Photo of the live photo to send. + Pass a ``file_id`` to send a file that exists on the Telegram servers (recommended). + |uploadinputnopath| Sending live photos by a URL is currently unsupported. + Lastly you can pass an existing :class:`telegram.PhotoSize` object to send. + + Attributes: + type (:obj:`str`): Type of the media, always + :tg-const:`telegram.constants.InputPaidMediaType.LIVE_PHOTO`. + media (:obj:`str` | :class:`telegram.InputFile`): Video of the live photo to send. + |fileinputnopath| + photo (:obj:`str` | :class:`telegram.InputFile`): Photo of the live photo to send. + |fileinputnopath| + """ + + __slots__ = ("photo",) + + def __init__( + self, + media: "FileInput | Video", + photo: "FileInput | PhotoSize", + *, + api_kwargs: JSONDict | None = None, + ): + media = parse_file_input(media, tg_type=Video, attach=True, local_mode=True) + photo = parse_file_input(photo, tg_type=PhotoSize, attach=True, local_mode=True) + super().__init__(type=InputPaidMedia.LIVE_PHOTO, media=media, api_kwargs=api_kwargs) + with self._unfrozen(): + self.photo: str | InputFile = photo + + class InputMediaAnimation(InputMedia): """Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. @@ -320,11 +401,12 @@ class InputMediaAnimation(InputMedia): .. versionchanged:: 13.2 Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the animation, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. + filename_depr (:obj:`str`, optional): Positional placeholder for keyword only parameter + :paramref:`filename`. For backward compatibility. - .. versionadded:: 13.1 + .. versionadded:: NEXT.VERSION + .. deprecated:: NEXT.VERSION + This parameter is deprecated, use :paramref:`filename` instead. caption (:obj:`str`, optional): Caption of the animation to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after entities parsing. @@ -353,8 +435,17 @@ class InputMediaAnimation(InputMedia): .. versionadded:: 21.3 + Keyword Args: + filename (:obj:`str`, optional): Custom file name for the animation, when uploading a + new file. Convenience parameter, useful e.g. when sending files generated by the + :obj:`tempfile` module. + + .. versionadded:: 13.1 + .. versionchanged:: NEXT.VERSION + This parameter is now keyword-only. + Attributes: - type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.ANIMATION`. + type (:obj:`str`): :tg-const:`telegram.constants.BaseInputMediaType.ANIMATION`. media (:obj:`str` | :class:`telegram.InputFile`): Animation to send. caption (:obj:`str`): Optional. Caption of the animation to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters @@ -403,13 +494,28 @@ def __init__( height: int | None = None, duration: TimePeriod | None = None, caption_entities: Sequence[MessageEntity] | None = None, - filename: str | None = None, + # tag: deprecated NEXT.VERSION + filename_depr: str | None = None, + # - has_spoiler: bool | None = None, thumbnail: "FileInput | None" = None, show_caption_above_media: bool | None = None, *, + filename: str | None = None, api_kwargs: JSONDict | None = None, ): + if filename_depr is not None and filename is not None: + raise ValueError("`filename_depr` and `filename` are mutually exclusive.") + if filename_depr is not None: + warn( + PTBDeprecationWarning( + "NEXT.VERSION", + "Positional passing of `filename` or keyword usage of `filename_depr`" + " is deprecated. `filename` will become a keyword-only argument.", + ), + stacklevel=2, + ) + if isinstance(media, Animation): width = media.width if width is None else width height = media.height if height is None else height @@ -418,10 +524,13 @@ def __init__( else: # We use local_mode=True because we don't have access to the actual setting and want # things to work in local mode. - media = parse_file_input(media, filename=filename, attach=True, local_mode=True) + effective_filename = filename_depr or filename + media = parse_file_input( + media, filename=effective_filename, attach=True, local_mode=True + ) super().__init__( - InputMediaType.ANIMATION, + BaseInputMediaType.ANIMATION, media, caption, caption_entities, @@ -453,11 +562,12 @@ class InputMediaPhoto(InputMedia): .. versionchanged:: 13.2 Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the photo, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. + filename_depr (:obj:`str`, optional): Positional placeholder for keyword only parameter + :paramref:`filename`. For backward compatibility. - .. versionadded:: 13.1 + .. versionadded:: NEXT.VERSION + .. deprecated:: NEXT.VERSION + This parameter is deprecated, use :paramref:`filename` instead. caption (:obj:`str`, optional ): Caption of the photo to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after entities parsing. @@ -474,8 +584,17 @@ class InputMediaPhoto(InputMedia): .. versionadded:: 21.3 + Keyword Args: + filename (:obj:`str`, optional): Custom file name for the photo, when uploading a + new file. Convenience parameter, useful e.g. when sending files generated by the + :obj:`tempfile` module. + + .. versionadded:: 13.1 + .. versionchanged:: NEXT.VERSION + This parameter is now keyword-only. + Attributes: - type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.PHOTO`. + type (:obj:`str`): :tg-const:`telegram.constants.BaseInputMediaType.PHOTO`. media (:obj:`str` | :class:`telegram.InputFile`): Photo to send. caption (:obj:`str`): Optional. Caption of the photo to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters @@ -507,17 +626,35 @@ def __init__( caption: str | None = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Sequence[MessageEntity] | None = None, - filename: str | None = None, + # tag: deprecated NEXT.VERSION + filename_depr: str | None = None, + # - has_spoiler: bool | None = None, show_caption_above_media: bool | None = None, *, + filename: str | None = None, api_kwargs: JSONDict | None = None, ): + if filename_depr is not None and filename is not None: + raise ValueError("`filename_depr` and `filename` are mutually exclusive.") + if filename_depr is not None: + warn( + PTBDeprecationWarning( + "NEXT.VERSION", + "Positional passing of `filename` or keyword usage of `filename_depr`" + " is deprecated. `filename` will become a keyword-only argument.", + ), + stacklevel=2, + ) + # We use local_mode=True because we don't have access to the actual setting and want # things to work in local mode. - media = parse_file_input(media, PhotoSize, filename=filename, attach=True, local_mode=True) + effective_filename = filename_depr or filename + media = parse_file_input( + media, PhotoSize, filename=effective_filename, attach=True, local_mode=True + ) super().__init__( - InputMediaType.PHOTO, + BaseInputMediaType.PHOTO, media, caption, caption_entities, @@ -553,11 +690,12 @@ class InputMediaVideo(InputMedia): .. versionchanged:: 13.2 Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the video, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. + filename_depr (:obj:`str`, optional): Positional placeholder for keyword only parameter + :paramref:`filename`. For backward compatibility. - .. versionadded:: 13.1 + .. versionadded:: NEXT.VERSION + .. deprecated:: NEXT.VERSION + This parameter is deprecated, use :paramref:`filename` instead. caption (:obj:`str`, optional): Caption of the video to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after entities parsing. @@ -594,8 +732,17 @@ class InputMediaVideo(InputMedia): .. versionadded:: 21.3 + Keyword Args: + filename (:obj:`str`, optional): Custom file name for the video, when uploading a + new file. Convenience parameter, useful e.g. when sending files generated by the + :obj:`tempfile` module. + + .. versionadded:: 13.1 + .. versionchanged:: NEXT.VERSION + This parameter is now keyword-only. + Attributes: - type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.VIDEO`. + type (:obj:`str`): :tg-const:`telegram.constants.BaseInputMediaType.VIDEO`. media (:obj:`str` | :class:`telegram.InputFile`): Video file to send. caption (:obj:`str`): Optional. Caption of the video to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters @@ -656,15 +803,30 @@ def __init__( supports_streaming: bool | None = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Sequence[MessageEntity] | None = None, - filename: str | None = None, + # tag: deprecated NEXT.VERSION + filename_depr: str | None = None, + # - has_spoiler: bool | None = None, thumbnail: "FileInput | None" = None, show_caption_above_media: bool | None = None, cover: "FileInput | None" = None, start_timestamp: int | None = None, *, + filename: str | None = None, api_kwargs: JSONDict | None = None, ): + if filename_depr is not None and filename is not None: + raise ValueError("`filename_depr` and `filename` are mutually exclusive.") + if filename_depr is not None: + warn( + PTBDeprecationWarning( + "NEXT.VERSION", + "Positional passing of `filename` or keyword usage of `filename_depr`" + " is deprecated. `filename` will become a keyword-only argument.", + ), + stacklevel=2, + ) + if isinstance(media, Video): width = width if width is not None else media.width height = height if height is not None else media.height @@ -673,10 +835,13 @@ def __init__( else: # We use local_mode=True because we don't have access to the actual setting and want # things to work in local mode. - media = parse_file_input(media, filename=filename, attach=True, local_mode=True) + effective_filename = filename_depr or filename + media = parse_file_input( + media, filename=effective_filename, attach=True, local_mode=True + ) super().__init__( - InputMediaType.VIDEO, + BaseInputMediaType.VIDEO, media, caption, caption_entities, @@ -701,6 +866,156 @@ def duration(self) -> int | dtm.timedelta | None: return get_timedelta_value(self._duration, attribute="duration") +class InputMediaLocation(_BaseInputMedia): + """Represents a location to be sent. + + .. versionadded:: NEXT.VERSION + + Args: + latitude (:obj:`float`): Latitude of the location. + longitude (:obj:`float`): Longitude of the location. + horizontal_accuracy (:obj:`float`, optional): The radius of uncertainty for the location, + measured in meters; 0-:tg-const:`telegram.Location.HORIZONTAL_ACCURACY`. + + Attributes: + type (:obj:`str`): :tg-const:`telegram.constants.BaseInputMediaType.LOCATION`. + latitude (:obj:`float`): Latitude of the location. + longitude (:obj:`float`): Longitude of the location. + horizontal_accuracy (:obj:`float`): Optional. The radius of uncertainty for the location, + measured in meters; 0-:tg-const:`telegram.Location.HORIZONTAL_ACCURACY`. + """ + + __slots__ = ("horizontal_accuracy", "latitude", "longitude") + + def __init__( + self, + latitude: float, + longitude: float, + horizontal_accuracy: float | None = None, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(media_type=BaseInputMediaType.LOCATION, api_kwargs=api_kwargs) + with self._unfrozen(): + self.latitude: float = latitude + self.longitude: float = longitude + self.horizontal_accuracy: float | None = horizontal_accuracy + + +class InputMediaVenue(_BaseInputMedia): + """Represents a venue to be sent. + + .. versionadded:: NEXT.VERSION + + Args: + latitude (:obj:`float`): Latitude of the location. + longitude (:obj:`float`): Longitude of the location. + title (:obj:`str`): Name of the venue. + address (:obj:`str`): Address of the venue. + foursquare_id (:obj:`str`, optional): Foursquare identifier of the venue. + foursquare_type (:obj:`str`, optional): Foursquare type of the venue, if known. (For + example, ``“arts_entertainment/default”``, ``“arts_entertainment/aquarium”`` + or ``“food/icecream”``). + google_place_id (:obj:`str`, optional): Google Places identifier of the venue. + google_place_type (:obj:`str`, optional): Google Places type of the venue. (See\ + `supported types `__) + + Attributes: + type (:obj:`str`): :tg-const:`telegram.constants.BaseInputMediaType.VENUE`. + latitude (:obj:`float`): Latitude of the location. + longitude (:obj:`float`): Longitude of the location. + title (:obj:`str`): Name of the venue. + address (:obj:`str`): Address of the venue. + foursquare_id (:obj:`str`): Optional. Foursquare identifier of the venue. + foursquare_type (:obj:`str`): Optional. Foursquare type of the venue, if known. (For + example, ``“arts_entertainment/default”``, ``“arts_entertainment/aquarium”`` + or ``“food/icecream”``). + google_place_id (:obj:`str`): Optional. Google Places identifier of the venue. + google_place_type (:obj:`str`): Optional. Google Places type of the venue. (See\ + `supported types `__) + """ + + __slots__ = ( + "address", + "foursquare_id", + "foursquare_type", + "google_place_id", + "google_place_type", + "latitude", + "longitude", + "title", + ) + + def __init__( + self, + latitude: float, + longitude: float, + title: str, + address: str, + foursquare_id: str | None = None, + foursquare_type: str | None = None, + google_place_id: str | None = None, + google_place_type: str | None = None, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(media_type=BaseInputMediaType.VENUE, api_kwargs=api_kwargs) + with self._unfrozen(): + self.latitude: float = latitude + self.longitude: float = longitude + self.title: str = title + self.address: str = address + self.foursquare_id: str | None = foursquare_id + self.foursquare_type: str | None = foursquare_type + self.google_place_id: str | None = google_place_id + self.google_place_type: str | None = google_place_type + + +class InputMediaSticker(_BaseInputMedia): + """Represents a sticker file to be sent. + + .. seealso:: :wiki:`Working with Files and Media ` + + .. versionadded:: NEXT.VERSION + + Args: + media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \ + :class:`pathlib.Path` | :class:`telegram.Sticker`): File to send. |fileinputnopath| + + Lastly you can pass an existing :class:`telegram.Sticker` object to send. + emoji (:obj:`str`, optional): Emoji associated with the sticker; only for just uploaded + stickers. + + Keyword Args: + filename (:obj:`str`, optional): Custom file name for the sticker, when uploading a + new file. Convenience parameter, useful e.g. when sending files generated by the + :obj:`tempfile` module. + + Attributes: + type (:obj:`str`): :tg-const:`telegram.constants.BaseInputMediaType.STICKER`. + media (:obj:`str` | :class:`telegram.InputFile`): Sticker file to send. + emoji (:obj:`str`): Optional. Emoji associated with the sticker; only for just uploaded + stickers. + """ + + __slots__ = ("emoji", "media") + + def __init__( + self, + media: "FileInput | Sticker", + emoji: str | None = None, + *, + filename: str | None = None, + api_kwargs: JSONDict | None = None, + ): + media = parse_file_input(media, Sticker, filename=filename, attach=True, local_mode=True) + + super().__init__(media_type=BaseInputMediaType.STICKER, api_kwargs=api_kwargs) + with self._unfrozen(): + self.media: str | InputFile = media + self.emoji: str | None = emoji + + class InputMediaAudio(InputMedia): """Represents an audio file to be treated as music to be sent. @@ -721,11 +1036,12 @@ class InputMediaAudio(InputMedia): .. versionchanged:: 13.2 Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the audio, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. + filename_depr (:obj:`str`, optional): Positional placeholder for keyword only parameter + :paramref:`filename`. For backward compatibility. - .. versionadded:: 13.1 + .. versionadded:: NEXT.VERSION + .. deprecated:: NEXT.VERSION + This parameter is deprecated, use :paramref:`filename` instead. caption (:obj:`str`, optional): Caption of the audio to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after entities parsing. @@ -748,8 +1064,17 @@ class InputMediaAudio(InputMedia): .. versionadded:: 20.2 + Keyword Args: + filename (:obj:`str`, optional): Custom file name for the audio, when uploading a + new file. Convenience parameter, useful e.g. when sending files generated by the + :obj:`tempfile` module. + + .. versionadded:: 13.1 + .. versionchanged:: NEXT.VERSION + This parameter is now keyword-only. + Attributes: - type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.AUDIO`. + type (:obj:`str`): :tg-const:`telegram.constants.BaseInputMediaType.AUDIO`. media (:obj:`str` | :class:`telegram.InputFile`): Audio file to send. caption (:obj:`str`): Optional. Caption of the audio to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters @@ -786,11 +1111,26 @@ def __init__( performer: str | None = None, title: str | None = None, caption_entities: Sequence[MessageEntity] | None = None, - filename: str | None = None, + # tag: deprecated NEXT.VERSION + filename_depr: str | None = None, + # - thumbnail: "FileInput | None" = None, *, + filename: str | None = None, api_kwargs: JSONDict | None = None, ): + if filename_depr is not None and filename is not None: + raise ValueError("`filename_depr` and `filename` are mutually exclusive.") + if filename_depr is not None: + warn( + PTBDeprecationWarning( + "NEXT.VERSION", + "Positional passing of `filename` or keyword usage of `filename_depr`" + " is deprecated. `filename` will become a keyword-only argument.", + ), + stacklevel=2, + ) + if isinstance(media, Audio): duration = duration if duration is not None else media._duration performer = media.performer if performer is None else performer @@ -799,10 +1139,13 @@ def __init__( else: # We use local_mode=True because we don't have access to the actual setting and want # things to work in local mode. - media = parse_file_input(media, filename=filename, attach=True, local_mode=True) + effective_filename = filename_depr or filename + media = parse_file_input( + media, filename=effective_filename, attach=True, local_mode=True + ) super().__init__( - InputMediaType.AUDIO, + BaseInputMediaType.AUDIO, media, caption, caption_entities, @@ -835,11 +1178,12 @@ class InputMediaDocument(InputMedia): .. versionchanged:: 13.2 Accept :obj:`bytes` as input. - filename (:obj:`str`, optional): Custom file name for the document, when uploading a - new file. Convenience parameter, useful e.g. when sending files generated by the - :obj:`tempfile` module. + filename_depr (:obj:`str`, optional): Positional placeholder for keyword only parameter + :paramref:`filename`. For backward compatibility. - .. versionadded:: 13.1 + .. versionadded:: NEXT.VERSION + .. deprecated:: NEXT.VERSION + This parameter is deprecated, use :paramref:`filename` instead. caption (:obj:`str`, optional): Caption of the document to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after entities parsing. @@ -857,8 +1201,17 @@ class InputMediaDocument(InputMedia): .. versionadded:: 20.2 + Keyword Args: + filename (:obj:`str`, optional): Custom file name for the document, when uploading a + new file. Convenience parameter, useful e.g. when sending files generated by the + :obj:`tempfile` module. + + .. versionadded:: 13.1 + .. versionchanged:: NEXT.VERSION + This parameter is now keyword-only. + Attributes: - type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.DOCUMENT`. + type (:obj:`str`): :tg-const:`telegram.constants.BaseInputMediaType.DOCUMENT`. media (:obj:`str` | :class:`telegram.InputFile`): File to send. caption (:obj:`str`): Optional. Caption of the document to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters @@ -887,17 +1240,35 @@ def __init__( parse_mode: ODVInput[str] = DEFAULT_NONE, disable_content_type_detection: bool | None = None, caption_entities: Sequence[MessageEntity] | None = None, - filename: str | None = None, + # tag: deprecated NEXT.VERSION + filename_depr: str | None = None, + # - thumbnail: "FileInput | None" = None, *, + filename: str | None = None, api_kwargs: JSONDict | None = None, ): + if filename_depr is not None and filename is not None: + raise ValueError("`filename_depr` and `filename` are mutually exclusive.") + if filename_depr is not None: + warn( + PTBDeprecationWarning( + "NEXT.VERSION", + "Positional passing of `filename` or keyword usage of `filename_depr`" + " is deprecated. `filename` will become a keyword-only argument.", + ), + stacklevel=2, + ) + # We use local_mode=True because we don't have access to the actual setting and want # things to work in local mode. - media = parse_file_input(media, Document, filename=filename, attach=True, local_mode=True) + effective_filename = filename_depr or filename + media = parse_file_input( + media, Document, filename=effective_filename, attach=True, local_mode=True + ) super().__init__( - InputMediaType.DOCUMENT, + BaseInputMediaType.DOCUMENT, media, caption, caption_entities, @@ -907,3 +1278,105 @@ def __init__( with self._unfrozen(): self.thumbnail: str | InputFile | None = self._parse_thumbnail_input(thumbnail) self.disable_content_type_detection: bool | None = disable_content_type_detection + + +class InputMediaLivePhoto(InputMedia): + """Represents a live photo to be sent. + + .. seealso:: :wiki:`Working with Files and Media ` + + .. versionadded:: NEXT.VERSION + + Args: + media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \ + | :class:`pathlib.Path` | :class:`~telegram.Video`): Video of the live photo to send. + Pass a ``file_id`` to send a file that exists on the Telegram servers (recommended). + |uploadinputnopath| Sending live photos by a URL is currently unsupported. Lastly + you can pass an existing :class:`telegram.Video` object to send. + photo (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \ + | :class:`pathlib.Path` | :class:`~telegram.PhotoSize`): The static photo to send. + Pass a ``file_id`` to send a file that exists on the Telegram servers (recommended). + |uploadinputnopath| Sending live photos by a URL is currently unsupported. Lastly + you can pass an existing :class:`telegram.PhotoSize` object to send. + caption (:obj:`str`, optional): Caption of the live photo to be sent, + 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after + entities parsing. + parse_mode (:obj:`str`, optional): |parse_mode| + caption_entities (Sequence[:class:`telegram.MessageEntity`], optional): |caption_entities| + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + has_spoiler (:obj:`bool`, optional): Pass :obj:`True`, if the video needs to be covered + with a spoiler animation. + + Attributes: + type (:obj:`str`): :tg-const:`telegram.constants.BaseInputMediaType.LIVE_PHOTO`. + media (:obj:`str` | :class:`telegram.InputFile`): Video of the live photo to send. + photo (:obj:`str` | :class:`telegram.InputFile`): The static photo to send. + caption (:obj:`str`): Optional. Caption of the live photo to be sent, + 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters + after entities parsing. + parse_mode (:obj:`str`): Optional. |parse_mode| + caption_entities (tuple[:class:`telegram.MessageEntity`]): Optional. |captionentitiesattr| + show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| + has_spoiler (:obj:`bool`): Optional. :obj:`True`, if the video is covered with a + spoiler animation. + """ + + __slots__ = ("has_spoiler", "photo", "show_caption_above_media") + + def __init__( + self, + media: "FileInput | Video", + photo: "FileInput | PhotoSize", + caption: str | None = None, + parse_mode: ODVInput[str] = DEFAULT_NONE, + caption_entities: Sequence[MessageEntity] | None = None, + show_caption_above_media: bool | None = None, + has_spoiler: bool | None = None, + *, + api_kwargs: JSONDict | None = None, + ): + media = parse_file_input(media, tg_type=Video, attach=True, local_mode=True) + photo = parse_file_input(photo, tg_type=PhotoSize, attach=True, local_mode=True) + + super().__init__( + BaseInputMediaType.LIVE_PHOTO, + media, + caption, + caption_entities, + parse_mode, + api_kwargs=api_kwargs, + ) + with self._unfrozen(): + self.photo: str | InputFile = photo + self.show_caption_above_media: bool | None = show_caption_above_media + self.has_spoiler: bool | None = has_spoiler + + +InputPollMedia: TypeAlias = ( + InputMediaAnimation + | InputMediaAudio + | InputMediaDocument + | InputMediaLivePhoto + | InputMediaLocation + | InputMediaPhoto + | InputMediaVenue + | InputMediaVideo +) +"""Type alias for InputPollMedia objects. + +versionadded:: NEXT.VERSION +""" + +InputPollOptionMedia: TypeAlias = ( + InputMediaAnimation + | InputMediaLivePhoto + | InputMediaLocation + | InputMediaPhoto + | InputMediaSticker + | InputMediaVenue + | InputMediaVideo +) +"""Type alias for InputPollOptionMedia objects. + +.. versionadded:: NEXT.VERSION +""" diff --git a/src/telegram/_files/livephoto.py b/src/telegram/_files/livephoto.py new file mode 100644 index 00000000000..3700827fa6d --- /dev/null +++ b/src/telegram/_files/livephoto.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2026 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""This module contains an object that represents a Telegram LivePhoto.""" + +from collections.abc import Sequence +from typing import TYPE_CHECKING + +from telegram._files._basemedium import _BaseMedium +from telegram._files.photosize import PhotoSize +from telegram._utils.argumentparsing import de_list_optional, parse_sequence_arg, to_timedelta +from telegram._utils.types import JSONDict, TimePeriod + +if TYPE_CHECKING: + import datetime as dtm + + from telegram import Bot + + +class LivePhoto(_BaseMedium): + """ + This object represents a live photo. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`file_unique_id` is equal. + + .. versionadded:: NEXT.VERSION + + Args: + file_id (:obj:`str`): Identifier for the video file which can be used to download or reuse + the file. + file_unique_id (:obj:`str`): Unique identifier for this file, which + is supposed to be the same over time and for different bots. + Can't be used to download or reuse the file. + width (:obj:`int`): Video width as defined by the sender. + height (:obj:`int`): Video height as defined by the sender. + duration (:obj:`int` | :class:`datetime.timedelta`): Duration of the video + in seconds as defined by the sender. + photo (Sequence[:obj:`telegram.PhotoSize`], optional): Available sizes of the corresponding + static photo. + mime_type (:obj:`str`, optional): MIME type of a file as defined by the sender. + file_size (:obj:`int`, optional): File size in bytes. + + Attributes: + file_id (:obj:`str`): Identifier for the video file which can be used to download or reuse + the file. + file_unique_id (:obj:`str`): Unique identifier for this file, which + is supposed to be the same over time and for different bots. + Can't be used to download or reuse the file. + width (:obj:`int`): Video width as defined by the sender. + height (:obj:`int`): Video height as defined by the sender. + duration (:class:`datetime.timedelta`): Duration of the video + in seconds as defined by the sender. + photo (tuple[:obj:`telegram.PhotoSize`]): Optional. Available sizes of the corresponding + static photo. + mime_type (:obj:`str`): Optional. MIME type of a file as defined by the sender. + file_size (:obj:`int`): Optional. File size in bytes. + + """ + + __slots__ = ( + "duration", + "height", + "mime_type", + "photo", + "width", + ) + + def __init__( + self, + file_id: str, + file_unique_id: str, + width: int, + height: int, + duration: TimePeriod, + photo: Sequence[PhotoSize] | None = None, + mime_type: str | None = None, + file_size: int | None = None, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__( + file_id=file_id, + file_unique_id=file_unique_id, + file_size=file_size, + api_kwargs=api_kwargs, + ) + with self._unfrozen(): + # Required + self.width: int = width + self.height: int = height + self.duration: dtm.timedelta = to_timedelta(duration) + # Optional + self.photo: Sequence[PhotoSize] | None = parse_sequence_arg(photo) + self.mime_type: str | None = mime_type + + @classmethod + def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "LivePhoto": + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot) + + return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_forcereply.py b/src/telegram/_forcereply.py index bf69c93aa43..228aef19aa5 100644 --- a/src/telegram/_forcereply.py +++ b/src/telegram/_forcereply.py @@ -31,7 +31,7 @@ class ForceReply(TelegramObject): the user (act as if the user has selected the bot's message and tapped 'Reply'). This can be extremely useful if you want to create user-friendly step-by-step interfaces without having to sacrifice `privacy mode `_. Not - supported in channels and for messages sent on behalf of a Telegram Business account. + supported in channels and for messages sent on behalf of a user account. Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`selective` is equal. diff --git a/src/telegram/_message.py b/src/telegram/_message.py index 8c6626f50a6..5bc02a53ccc 100644 --- a/src/telegram/_message.py +++ b/src/telegram/_message.py @@ -37,6 +37,7 @@ from telegram._files.audio import Audio from telegram._files.contact import Contact from telegram._files.document import Document +from telegram._files.livephoto import LivePhoto from telegram._files.location import Location from telegram._files.photosize import PhotoSize from telegram._files.sticker import Sticker @@ -109,17 +110,21 @@ GiveawayCompleted, GiveawayCreated, GiveawayWinners, + InlineQueryResult, InputMedia, InputMediaAudio, InputMediaDocument, + InputMediaLivePhoto, InputMediaPhoto, InputMediaVideo, InputPaidMedia, + InputPollMedia, InputPollOption, LabeledPrice, MessageId, MessageOrigin, ReactionType, + SentGuestMessage, SuggestedPostApprovalFailed, SuggestedPostApproved, SuggestedPostDeclined, @@ -707,6 +712,26 @@ class Message(MaybeInaccessibleMessage): managed_bot_created (:class:`telegram.ManagedBotCreated`, optional): Service message: user created a bot that will be managed by the current bot. + .. versionadded:: NEXT.VERSION + guest_bot_caller_user (:class:`telegram.User`, optional): For a message sent by a guest + bot, this is the user whose original message triggered the bot's response. + + .. versionadded:: NEXT.VERSION + guest_bot_caller_chat (:class:`telegram.Chat`, optional): For a message sent by a guest + bot, this is the chat whose original message triggered the bot's response. + + .. versionadded:: NEXT.VERSION + guest_query_id (:obj:`str`, optional): The unique identifier for the guest query. Use this + identifier with the method :meth:`telegram.Bot.answer_guest_query` to send a response + message. If non-empty, the message belongs to the chat where the guest bot was + summoned, which may not coincide with other existing bot chats sharing the same + identifier. + + .. versionadded:: NEXT.VERSION + live_photo (:class:`telegram.LivePhoto`, optional): Message is a live photo, information + about the live photo. For backward compatibility, when this field is set, the photo + field will also be set. + .. versionadded:: NEXT.VERSION Attributes: @@ -1139,6 +1164,26 @@ class Message(MaybeInaccessibleMessage): managed_bot_created (:class:`telegram.ManagedBotCreated`): Optional. Service message: user created a bot that will be managed by the current bot. + .. versionadded:: NEXT.VERSION + guest_bot_caller_user (:class:`telegram.User`): Optional. For a message sent by a guest + bot, this is the user whose original message triggered the bot's response. + + .. versionadded:: NEXT.VERSION + guest_bot_caller_chat (:class:`telegram.Chat`): Optional. For a message sent by a guest + bot, this is the chat whose original message triggered the bot's response. + + .. versionadded:: NEXT.VERSION + guest_query_id (:obj:`str`): Optional. The unique identifier for the guest query. Use this + identifier with the method :meth:`telegram.Bot.answer_guest_query` to send a response + message. If non-empty, the message belongs to the chat where the guest bot was + summoned, which may not coincide with other existing bot chats sharing the same + identifier. + + .. versionadded:: NEXT.VERSION + live_photo (:class:`telegram.LivePhoto`): Optional. Message is a live photo, information + about the live photo. For backward compatibility, when this field is set, the photo + field will also be set. + .. versionadded:: NEXT.VERSION .. |custom_emoji_no_md1_support| replace:: Since custom emoji entities are not supported by @@ -1201,6 +1246,9 @@ class Message(MaybeInaccessibleMessage): "giveaway_created", "giveaway_winners", "group_chat_created", + "guest_bot_caller_chat", + "guest_bot_caller_user", + "guest_query_id", "has_media_spoiler", "has_protected_content", "invoice", @@ -1210,6 +1258,7 @@ class Message(MaybeInaccessibleMessage): "is_topic_message", "left_chat_member", "link_preview_options", + "live_photo", "location", "managed_bot_created", "media_group_id", @@ -1380,6 +1429,10 @@ def __init__( poll_option_deleted: PollOptionDeleted | None = None, reply_to_poll_option_id: str | None = None, managed_bot_created: ManagedBotCreated | None = None, + guest_bot_caller_user: User | None = None, + guest_bot_caller_chat: Chat | None = None, + guest_query_id: str | None = None, + live_photo: LivePhoto | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -1514,6 +1567,10 @@ def __init__( self.poll_option_deleted: PollOptionDeleted | None = poll_option_deleted self.reply_to_poll_option_id: str | None = reply_to_poll_option_id self.managed_bot_created: ManagedBotCreated | None = managed_bot_created + self.guest_bot_caller_user: User | None = guest_bot_caller_user + self.guest_bot_caller_chat: Chat | None = guest_bot_caller_chat + self.guest_query_id: str | None = guest_query_id + self.live_photo: LivePhoto | None = live_photo self._effective_attachment = DEFAULT_NONE @@ -1743,6 +1800,13 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Message": data["managed_bot_created"] = de_json_optional( data.get("managed_bot_created"), ManagedBotCreated, bot ) + data["guest_bot_caller_user"] = de_json_optional( + data.get("guest_bot_caller_user"), User, bot + ) + data["guest_bot_caller_chat"] = de_json_optional( + data.get("guest_bot_caller_chat"), Chat, bot + ) + data["live_photo"] = de_json_optional(data.get("live_photo"), LivePhoto, bot) api_kwargs = {} # This is a deprecated field that TG still returns for backwards compatibility @@ -1774,6 +1838,7 @@ def effective_attachment( | Document | Game | Invoice + | LivePhoto | Location | PassportData | Sequence[PhotoSize] @@ -1798,6 +1863,7 @@ def effective_attachment( * :class:`telegram.Animation` * :class:`telegram.Game` * :class:`telegram.Invoice` + * :class:`telegram.LivePhoto` * :class:`telegram.Location` * :class:`telegram.PassportData` * list[:class:`telegram.PhotoSize`] @@ -2186,7 +2252,7 @@ async def reply_text( async def reply_text_draft( self, draft_id: int, - text: str, + text: str | None = None, parse_mode: ODVInput[str] = DEFAULT_NONE, entities: Sequence["MessageEntity"] | None = None, message_thread_id: ODVInput[int] = DEFAULT_NONE, @@ -2213,6 +2279,9 @@ async def reply_text_draft( .. versionadded:: 22.6 + .. versionchanged:: NEXT.VERSION + Bot API 10.0 makes the ``text`` argument optional. + Returns: :obj:`bool`: On success, :obj:`True` is returned. @@ -2485,7 +2554,7 @@ async def reply_html( async def reply_media_group( self, media: Sequence[ - "InputMediaAudio | InputMediaDocument | InputMediaPhoto | InputMediaVideo" + "InputMediaAudio | InputMediaDocument | InputMediaPhoto | InputMediaVideo | InputMediaLivePhoto" # noqa: E501 # pylint: disable=line-too-long ], disable_notification: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE, @@ -2646,6 +2715,87 @@ async def reply_photo( suggested_post_parameters=suggested_post_parameters, ) + async def reply_live_photo( + self, + live_photo: "FileInput | LivePhoto", + photo: "FileInput | PhotoSize", + caption: str | None = None, + disable_notification: ODVInput[bool] = DEFAULT_NONE, + reply_markup: "ReplyMarkup | None" = None, + parse_mode: ODVInput[str] = DEFAULT_NONE, + caption_entities: Sequence["MessageEntity"] | None = None, + show_caption_above_media: bool | None = None, + has_spoiler: bool | None = None, + protect_content: ODVInput[bool] = DEFAULT_NONE, + message_thread_id: ODVInput[int] = DEFAULT_NONE, + reply_parameters: "ReplyParameters | None" = None, + message_effect_id: str | None = None, + allow_paid_broadcast: bool | None = None, + suggested_post_parameters: "SuggestedPostParameters | None" = None, + *, + reply_to_message_id: int | None = None, + allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + filename: str | None = None, + do_quote: bool | (_ReplyKwargs | None) = None, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> "Message": + """Shortcut for:: + + await bot.send_live_photo( + update.effective_message.chat_id, + message_thread_id=update.effective_message.message_thread_id, + business_connection_id=self.business_connection_id, + direct_messages_topic_id=self.direct_messages_topic.topic_id, + *args, + **kwargs, + ) + + For the documentation of the arguments, please see :meth:`telegram.Bot.send_live_photo`. + + .. versionadded:: NEXT.VERSION + + Keyword Args: + do_quote (:obj:`bool` | :obj:`dict`, optional): |do_quote| + + Returns: + :class:`telegram.Message`: On success, instance representing the message posted. + + """ + chat_id, effective_reply_parameters = await self._parse_quote_arguments( + do_quote, reply_to_message_id, reply_parameters, allow_sending_without_reply + ) + message_thread_id = self._parse_message_thread_id(chat_id, message_thread_id) + return await self.get_bot().send_live_photo( + chat_id=chat_id, + live_photo=live_photo, + photo=photo, + caption=caption, + disable_notification=disable_notification, + reply_parameters=effective_reply_parameters, + reply_markup=reply_markup, + parse_mode=parse_mode, + caption_entities=caption_entities, + filename=filename, + protect_content=protect_content, + message_thread_id=message_thread_id, + has_spoiler=has_spoiler, + show_caption_above_media=show_caption_above_media, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, + allow_paid_broadcast=allow_paid_broadcast, + direct_messages_topic_id=self._extract_direct_messages_topic_id(), + suggested_post_parameters=suggested_post_parameters, + ) + async def reply_audio( self, audio: "FileInput | Audio", @@ -3535,8 +3685,12 @@ async def reply_poll( allow_adding_options: bool | None = None, hide_results_until_closes: bool | None = None, description: str | None = None, - description_parse_mode: str | None = None, + description_parse_mode: ODVInput[str] | None = None, description_entities: Sequence["MessageEntity"] | None = None, + members_only: bool | None = None, + country_codes: Sequence[str] | None = None, + explanation_media: "InputPollMedia | None" = None, + media: "InputPollMedia | None" = None, *, reply_to_message_id: int | None = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3615,6 +3769,10 @@ async def reply_poll( description_entities=description_entities, hide_results_until_closes=hide_results_until_closes, allow_adding_options=allow_adding_options, + members_only=members_only, + country_codes=country_codes, + explanation_media=explanation_media, + media=media, ) async def reply_dice( @@ -3733,7 +3891,7 @@ async def reply_checklist( ) return await self.get_bot().send_checklist( business_connection_id=self.business_connection_id, - chat_id=chat_id, # type: ignore[arg-type] + chat_id=chat_id, checklist=checklist, disable_notification=disable_notification, reply_parameters=effective_reply_parameters, @@ -3845,7 +4003,7 @@ async def reply_game( ) message_thread_id = self._parse_message_thread_id(chat_id, message_thread_id) return await self.get_bot().send_game( - chat_id=chat_id, # type: ignore[arg-type] + chat_id=chat_id, game_short_name=game_short_name, disable_notification=disable_notification, reply_parameters=effective_reply_parameters, @@ -5220,6 +5378,80 @@ async def decline_suggested_post( api_kwargs=api_kwargs, ) + async def delete_reaction( + self, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """Shortcut for:: + + await bot.delete_message_reaction( + chat_id=message.chat_id, + message_id=message.message_id, + *args, **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.delete_message_reaction`. + + .. versionadded:: NEXT.VERSION + + Returns: + :obj:`bool` On success, :obj:`True` is returned. + """ + return await self.get_bot().delete_message_reaction( + chat_id=self.chat_id, + message_id=self.message_id, + user_id=user_id, + actor_chat_id=actor_chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def answer_guest_query( + self, + result: "InlineQueryResult", + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> "SentGuestMessage": + """Shortcut for:: + + await bot.answer_guest_query( + self.guest_query_id, + *args, **kwargs, + ) + + For the documentation of the arguments, please see :meth:`telegram.Bot.answer_guest_query`. + + .. versionadded:: NEXT.VERSION + + Returns: + :class:`telegram.SentGuestMessage`: On success, a + :class:`telegram.SentGuestMessage` is returned. + """ + return await self.get_bot().answer_guest_query( + guest_query_id=self.guest_query_id, + result=result, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + def parse_entity(self, entity: MessageEntity) -> str: """Returns the text from a given :class:`telegram.MessageEntity`. diff --git a/src/telegram/_paidmedia.py b/src/telegram/_paidmedia.py index 67af46710a5..ecf6830cd99 100644 --- a/src/telegram/_paidmedia.py +++ b/src/telegram/_paidmedia.py @@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, Final from telegram import constants +from telegram._files.livephoto import LivePhoto from telegram._files.photosize import PhotoSize from telegram._files.video import Video from telegram._telegramobject import TelegramObject @@ -68,6 +69,8 @@ class PaidMedia(TelegramObject): """:const:`telegram.constants.PaidMediaType.PHOTO`""" VIDEO: Final[str] = constants.PaidMediaType.VIDEO """:const:`telegram.constants.PaidMediaType.VIDEO`""" + LIVE_PHOTO: Final[str] = constants.PaidMediaType.LIVE_PHOTO + """:const:`telegram.constants.PaidMediaType.LIVE_PHOTO`""" def __init__( self, @@ -100,6 +103,7 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PaidMedia": cls.PREVIEW: PaidMediaPreview, cls.PHOTO: PaidMediaPhoto, cls.VIDEO: PaidMediaVideo, + cls.LIVE_PHOTO: PaidMediaLivePhoto, } if cls is PaidMedia and data.get("type") in _class_mapping: @@ -251,6 +255,47 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PaidMediaVideo": return super().de_json(data=data, bot=bot) # type: ignore[return-value] +class PaidMediaLivePhoto(PaidMedia): + """ + The paid media is a live photo. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`live_photo` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + type (:obj:`str`): Type of the paid media, always :tg-const:`telegram.PaidMedia.LIVE_PHOTO` + live_photo (:class:`telegram.LivePhoto`): The photo. + + Attributes: + type (:obj:`str`): Type of the paid media, always :tg-const:`telegram.PaidMedia.LIVE_PHOTO` + live_photo (:class:`telegram.LivePhoto`): The photo. + + """ + + __slots__ = ("live_photo",) + + def __init__( + self, + live_photo: LivePhoto, + *, + api_kwargs: JSONDict | None = None, + ) -> None: + super().__init__(type=PaidMedia.LIVE_PHOTO, api_kwargs=api_kwargs) + + with self._unfrozen(): + self.live_photo: LivePhoto = live_photo + self._id_attrs = (self.type, self.live_photo) + + @classmethod + def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PaidMediaLivePhoto": + data = cls._parse_data(data) + + data["live_photo"] = de_json_optional(data.get("live_photo"), LivePhoto, bot) + return super().de_json(data=data, bot=bot) # type: ignore[return-value] + + class PaidMediaInfo(TelegramObject): """ Describes the paid media added to a message. diff --git a/src/telegram/_poll.py b/src/telegram/_poll.py index ba02062b33a..2f834403038 100644 --- a/src/telegram/_poll.py +++ b/src/telegram/_poll.py @@ -24,6 +24,15 @@ from telegram import constants from telegram._chat import Chat +from telegram._files.animation import Animation +from telegram._files.audio import Audio +from telegram._files.document import Document +from telegram._files.livephoto import LivePhoto +from telegram._files.location import Location +from telegram._files.photosize import PhotoSize +from telegram._files.sticker import Sticker +from telegram._files.venue import Venue +from telegram._files.video import Video from telegram._messageentity import MessageEntity from telegram._telegramobject import TelegramObject from telegram._user import User @@ -46,7 +55,122 @@ from telegram.warnings import PTBDeprecationWarning if TYPE_CHECKING: - from telegram import Bot, MaybeInaccessibleMessage + from telegram import Bot, InputPollOptionMedia, MaybeInaccessibleMessage + + +class PollMedia(TelegramObject): + """ + At most one of the optional fields can be present in any given object. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if all of their attributes are equal. + + .. versionadded:: NEXT.VERSION + + Args: + animation (:class:`telegram.Animation`, optional): Media is an animation, information about + the animation + audio (:class:`telegram.Audio`, optional): Media is an audio file, information about the + file; currently, can't be received in a poll option + document (:class:`telegram.Document`, optional): Media is a general file, information about + the file; currently, can't be received in a poll option + live_photo (:class:`telegram.LivePhoto`, optional): Media is a live photo, information + about the live photo + location (:class:`telegram.Location`, optional): Media is a shared location, information + about the location + photo (Sequence[:class:`telegram.PhotoSize`], optional): Media is a photo, available sizes + of the photo + sticker (:class:`telegram.Sticker`, optional): Media is a sticker, information about the + sticker; currently, for poll options only + venue (:class:`telegram.Venue`, optional): Media is a venue, information about the venue + video (:class:`telegram.Video`, optional): Media is a video, information about the video + + Attributes: + animation (:class:`telegram.Animation`): Optional. Media is an animation, information about + the animation + audio (:class:`telegram.Audio`): Optional. Media is an audio file, information about the + file; currently, can't be received in a poll option + document (:class:`telegram.Document`): Optional. Media is a general file, information about + the file; currently, can't be received in a poll option + live_photo (:class:`telegram.LivePhoto`, optional): Media is a live photo, information + about the live photo + location (:class:`telegram.Location`): Optional. Media is a shared location, information + about the location + photo (tuple[:class:`telegram.PhotoSize`]): Optional. Media is a photo, available sizes + of the photo + sticker (:class:`telegram.Sticker`): Optional. Media is a sticker, information about the + sticker; currently, for poll options only + venue (:class:`telegram.Venue`): Optional. Media is a venue, information about the venue + video (:class:`telegram.Video`): Optional. Media is a video, information about the video + """ + + __slots__ = ( + "animation", + "audio", + "document", + "live_photo", + "location", + "photo", + "sticker", + "venue", + "video", + ) + + def __init__( + self, + animation: Animation | None = None, + audio: Audio | None = None, + document: Document | None = None, + live_photo: LivePhoto | None = None, + location: Location | None = None, + photo: Sequence[PhotoSize] | None = None, + sticker: Sticker | None = None, + venue: Venue | None = None, + video: Video | None = None, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.animation: Animation | None = animation + self.audio: Audio | None = audio + self.document: Document | None = document + self.live_photo: LivePhoto | None = live_photo + self.location: Location | None = location + self.photo: tuple[PhotoSize, ...] = parse_sequence_arg(photo) + self.sticker: Sticker | None = sticker + self.venue: Venue | None = venue + self.video: Video | None = video + + self._id_attrs = ( + self.animation, + self.audio, + self.document, + self.live_photo, + self.location, + self.photo, + self.sticker, + self.venue, + self.video, + ) + + self._freeze() + + @classmethod + def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PollMedia": + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + data["animation"] = de_json_optional(data.get("animation"), Animation, bot) + data["audio"] = de_json_optional(data.get("audio"), Audio, bot) + data["document"] = de_json_optional(data.get("document"), Document, bot) + data["live_photo"] = de_json_optional(data.get("live_photo"), LivePhoto, bot) + data["location"] = de_json_optional(data.get("location"), Location, bot) + data["photo"] = de_list_optional(data.get("photo"), PhotoSize, bot) + data["sticker"] = de_json_optional(data.get("sticker"), Sticker, bot) + data["venue"] = de_json_optional(data.get("venue"), Venue, bot) + data["video"] = de_json_optional(data.get("video"), Video, bot) + + return super().de_json(data=data, bot=bot) class InputPollOption(TelegramObject): @@ -69,6 +193,9 @@ class InputPollOption(TelegramObject): :paramref:`text_parse_mode`. Currently, only custom emoji entities are allowed. This list is empty if the text does not contain entities. + media (:class:`telegram.InputPollOptionMedia`, optional): Media added to the poll option. + + .. versionadded:: NEXT.VERSION Attributes: text (:obj:`str`): Option text, @@ -81,15 +208,19 @@ class InputPollOption(TelegramObject): :paramref:`text_parse_mode`. Currently, only custom emoji entities are allowed. This list is empty if the text does not contain entities. + media (:class:`telegram.InputPollOptionMedia`): Optional. Media added to the poll option. + + .. versionadded:: NEXT.VERSION """ - __slots__ = ("text", "text_entities", "text_parse_mode") + __slots__ = ("media", "text", "text_entities", "text_parse_mode") def __init__( self, text: str, text_parse_mode: ODVInput[str] = DEFAULT_NONE, text_entities: Sequence[MessageEntity] | None = None, + media: "InputPollOptionMedia | None" = None, *, api_kwargs: JSONDict | None = None, ): @@ -97,14 +228,30 @@ def __init__( self.text: str = text self.text_parse_mode: ODVInput[str] = text_parse_mode self.text_entities: tuple[MessageEntity, ...] = parse_sequence_arg(text_entities) + self.media: InputPollOptionMedia | None = media self._id_attrs = (self.text,) self._freeze() + # tags: deprecated NEXT.VERSION @classmethod def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "InputPollOption": - """See :meth:`telegram.TelegramObject.de_json`.""" + """See :meth:`telegram.TelegramObject.de_json`. The :paramref:`media` field will + not be included for deserialization. + + .. deprecated:: NEXT.VERSION + This class is input only and will be removed in the next version. + """ + warn( + PTBDeprecationWarning( + "NEXT.VERSION", + "`InputPollOption.de_json` is deprecated. This class is input only and will be " + "removed in the next version. The `media` field will not be included for " + "deserialization.", + ), + stacklevel=2, + ) data = cls._parse_data(data) data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot) @@ -117,7 +264,12 @@ class PollOption(TelegramObject): This object contains information about one answer option in a poll. Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`text` and :attr:`voter_count` are equal. + considered equal, if their :attr:`text`, :attr:`voter_count` and :attr:`persistent_id` + are equal. + + .. versionchanged:: NEXT.VERSION + Added attribute :attr:`persistent_id` to equality checks. + Args: persistent_id (:obj:`str`): Unique identifier of the option, persistent on option addition @@ -133,6 +285,9 @@ class PollOption(TelegramObject): poll option texts. .. versionadded:: 21.2 + media (:class:`telegram.PollMedia`, optional): Media added to the poll option. + + .. versionadded:: NEXT.VERSION added_by_user (:class:`telegram.User`, optional): User who added the option; omitted if the option wasn't added by a user after poll creation. @@ -161,6 +316,9 @@ class PollOption(TelegramObject): This list is empty if the question does not contain entities. .. versionadded:: 21.2 + media (:class:`telegram.PollMedia`): Optional. Media added to the poll option. + + .. versionadded:: NEXT.VERSION added_by_user (:class:`telegram.User`): Optional. User who added the option; omitted if the option wasn't added by a user after poll creation. @@ -179,6 +337,7 @@ class PollOption(TelegramObject): "added_by_chat", "added_by_user", "addition_date", + "media", "persistent_id", "text", "text_entities", @@ -193,23 +352,28 @@ def __init__( added_by_user: User | None = None, added_by_chat: Chat | None = None, addition_date: dtm.datetime | None = None, + media: PollMedia | None = None, # tags: required in NEXT.VERSION, bot api 9.6 # temporarily optional to avoid breaking changes persistent_id: str | None = None, *, api_kwargs: JSONDict | None = None, ): + if persistent_id is None: + raise TypeError("`persistent_id` is a required argument since Bot API 9.6") + super().__init__(api_kwargs=api_kwargs) self.text: str = text self.voter_count: int = voter_count self.added_by_user: User | None = added_by_user self.added_by_chat: Chat | None = added_by_chat self.addition_date: dtm.datetime | None = addition_date - self.persistent_id: str | None = persistent_id + self.persistent_id: str = persistent_id + self.media: PollMedia | None = media self.text_entities: tuple[MessageEntity, ...] = parse_sequence_arg(text_entities) - self._id_attrs = (self.text, self.voter_count) + self._id_attrs = (self.text, self.voter_count, self.persistent_id) self._freeze() @@ -225,6 +389,7 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "PollOption": data["added_by_user"] = de_json_optional(data.get("added_by_user"), User, bot) data["added_by_chat"] = de_json_optional(data.get("added_by_chat"), Chat, bot) data["addition_date"] = from_timestamp(data.get("addition_date"), tzinfo=loc_tzinfo) + data["media"] = de_json_optional(data.get("media"), PollMedia, bot) return super().de_json(data=data, bot=bot) @@ -358,6 +523,9 @@ def __init__( *, api_kwargs: JSONDict | None = None, ): + if option_persistent_ids is None: + raise TypeError("`option_persistent_ids` is a required argument since Bot API 9.6") + super().__init__(api_kwargs=api_kwargs) self.poll_id: str = poll_id self.voter_chat: Chat | None = voter_chat @@ -637,6 +805,11 @@ class Poll(TelegramObject): is_anonymous (:obj:`bool`): :obj:`True`, if the poll is anonymous. type (:obj:`str`): Poll type, currently can be :attr:`REGULAR` or :attr:`QUIZ`. allows_multiple_answers (:obj:`bool`): :obj:`True`, if the poll allows multiple answers. + members_only (:obj:`bool`): :obj:`True`, if voting is limited to users who have been + members of the chat where the poll was originally sent for more than + :tg-const:`telegram.Poll.MIN_MEMBERSHIP_HOURS` hours. + + .. versionadded:: NEXT.VERSION correct_option_id (:obj:`int`, optional): A zero based identifier of the correct answer option. Available only for closed polls in the quiz mode, which were sent (not forwarded), by the bot or to a private chat with the bot. @@ -655,6 +828,10 @@ class Poll(TelegramObject): * This attribute is now always a (possibly empty) list and never :obj:`None`. * |sequenceclassargs| + explanation_media (:class:`telegram.PollMedia`, optional): Media added to the quiz + explanation. + + .. versionadded:: NEXT.VERSION open_period (:obj:`int` | :class:`datetime.timedelta`, optional): Amount of time in seconds the poll will be active after creation. @@ -678,6 +855,12 @@ class Poll(TelegramObject): the correct answer options. Available only for polls in quiz mode which are closed or were sent (not forwarded) by the bot or to the private chat with the bot. + .. versionadded:: NEXT.VERSION + country_codes (Sequence[:obj:`str`], optional): A list of two-letter ``ISO 3166-1 alpha-2`` + country codes indicating the countries from which users can vote in the poll. The + country code ``"FT"`` is used for users with anonymous numbers. If omitted, then users + from any country can participate in the poll. + .. versionadded:: NEXT.VERSION description (:obj:`str`, optional): Description of the poll; for polls inside the :class:`~telegram.Message` object only. @@ -686,6 +869,10 @@ class Poll(TelegramObject): description_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities like usernames, URLs, bot commands, etc. that appear in the description + .. versionadded:: NEXT.VERSION + media (:class:`telegram.PollMedia`, optional): Media added to the poll description; + for polls inside the :class:`~telegram.Message` object only. + .. versionadded:: NEXT.VERSION Attributes: @@ -701,12 +888,11 @@ class Poll(TelegramObject): is_anonymous (:obj:`bool`): :obj:`True`, if the poll is anonymous. type (:obj:`str`): Poll type, currently can be :attr:`REGULAR` or :attr:`QUIZ`. allows_multiple_answers (:obj:`bool`): :obj:`True`, if the poll allows multiple answers. - correct_option_id (:obj:`int`): Optional. A zero based identifier of the correct answer - option. Available only for closed polls in the quiz mode, which were sent - (not forwarded), by the bot or to a private chat with the bot. + members_only (:obj:`bool`): :obj:`True`, if voting is limited to users who have been + members of the chat where the poll was originally sent for more than + :tg-const:`telegram.Poll.MIN_MEMBERSHIP_HOURS` hours. - .. deprecated:: NEXT.VERSION - Use :attr:`correct_option_ids` instead. + .. versionadded:: NEXT.VERSION explanation (:obj:`str`): Optional. Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-:tg-const:`telegram.Poll.MAX_EXPLANATION_LENGTH` characters. @@ -719,6 +905,10 @@ class Poll(TelegramObject): .. versionchanged:: 20.0 This attribute is now always a (possibly empty) list and never :obj:`None`. + explanation_media (:class:`telegram.PollMedia`): Optional. Media added to the quiz + explanation. + + .. versionadded:: NEXT.VERSION open_period (:obj:`int` | :class:`datetime.timedelta`): Optional. Amount of time in seconds the poll will be active after creation. @@ -735,7 +925,7 @@ class Poll(TelegramObject): This list is empty if the question does not contain entities. .. versionadded:: 21.2 - allows_revoting (:obj:`bool`): Optional. :obj:`True`, if the poll + allows_revoting (:obj:`bool`): :obj:`True`, if the poll allows to change the chosenanswer options .. versionadded:: NEXT.VERSION @@ -743,6 +933,12 @@ class Poll(TelegramObject): correct answer options. Available only for polls in quiz mode which are closed or were sent (not forwarded) by the bot or to the private chat with the bot. + .. versionadded:: NEXT.VERSION + country_codes (tuple[:obj:`str`]): Optional. A list of two-letter ``ISO 3166-1 alpha-2`` + country codes indicating the countries from which users can vote in the poll. The + country code ``"FT"`` is used for users with anonymous numbers. If omitted, then users + from any country can participate in the poll. + .. versionadded:: NEXT.VERSION description (:obj:`str`): Optional. Description of the poll; for polls inside the Message object only @@ -751,6 +947,10 @@ class Poll(TelegramObject): description_entities (tuple[:class:`telegram.MessageEntity`]): Special entities like usernames, URLs, bot commands, etc. that appear in the description + .. versionadded:: NEXT.VERSION + media (:class:`telegram.PollMedia`): Optional. Media added to the poll description; + for polls inside the Message object only. + .. versionadded:: NEXT.VERSION """ @@ -761,13 +961,17 @@ class Poll(TelegramObject): "allows_revoting", "close_date", "correct_option_ids", + "country_codes", "description", "description_entities", "explanation", "explanation_entities", + "explanation_media", "id", "is_anonymous", "is_closed", + "media", + "members_only", "options", "question", "question_entities", @@ -788,6 +992,7 @@ def __init__( # tags: deprecated NEXT.VERSION # Removed in bot api 9.6: correct_option_id: int | None = None, + # --- explanation: str | None = None, explanation_entities: Sequence[MessageEntity] | None = None, open_period: TimePeriod | None = None, @@ -796,12 +1001,23 @@ def __init__( # tags: required in NEXT.VERSION # temporarily optional to avoid breaking changes allows_revoting: bool | None = None, + members_only: bool | None = None, + # --- correct_option_ids: Sequence[int] | None = None, description: str | None = None, description_entities: Sequence[MessageEntity] | None = None, + country_codes: Sequence[str] | None = None, + media: PollMedia | None = None, + explanation_media: PollMedia | None = None, *, api_kwargs: JSONDict | None = None, ): + if allows_revoting is None: + raise TypeError("`allows_revoting` is a required argument since Bot API 9.6") + + if members_only is None: + raise TypeError("`members_only` is a required argument since Bot API 10.0") + super().__init__(api_kwargs=api_kwargs) self.id: str = id self.question: str = question @@ -811,7 +1027,8 @@ def __init__( self.is_anonymous: bool = is_anonymous self.type: str = enum.get_member(constants.PollType, type, type) self.allows_multiple_answers: bool = allows_multiple_answers - self.allows_revoting: bool | None = allows_revoting + self.allows_revoting: bool = allows_revoting + self.members_only: bool = members_only # tag: deprecated NEXT.VERSION if correct_option_id is not None: @@ -838,6 +1055,9 @@ def __init__( self._open_period: dtm.timedelta | None = to_timedelta(open_period) self.close_date: dtm.datetime | None = close_date self.question_entities: tuple[MessageEntity, ...] = parse_sequence_arg(question_entities) + self.country_codes: tuple[str, ...] = parse_sequence_arg(country_codes) + self.media: PollMedia | None = media + self.explanation_media: PollMedia | None = explanation_media self._id_attrs = (self.id,) @@ -866,6 +1086,8 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Poll": data["description_entities"] = de_list_optional( data.get("description_entities"), MessageEntity, bot ) + data["media"] = de_json_optional(data.get("media"), PollMedia, bot) + data["explanation_media"] = de_json_optional(data.get("explanation_media"), PollMedia, bot) return super().de_json(data=data, bot=bot) @@ -1105,3 +1327,8 @@ def correct_option_id(self) -> int | None: .. versionadded:: NEXT.VERSION """ + MIN_MEMBERSHIP_HOURS: Final[int] = constants.PollLimit.MIN_MEMBERSHIP_HOURS + """:const:`telegram.constants.PollLimit.MIN_MEMBERSHIP_HOURS` + + .. versionadded:: NEXT.VERSION + """ diff --git a/src/telegram/_reply.py b/src/telegram/_reply.py index 6f1edfadeb8..a8e37cafacb 100644 --- a/src/telegram/_reply.py +++ b/src/telegram/_reply.py @@ -28,6 +28,7 @@ from telegram._files.audio import Audio from telegram._files.contact import Contact from telegram._files.document import Document +from telegram._files.livephoto import LivePhoto from telegram._files.location import Location from telegram._files.photosize import PhotoSize from telegram._files.sticker import Sticker @@ -114,6 +115,10 @@ class ExternalReplyInfo(TelegramObject): information about the paid media. .. versionadded:: 21.4 + live_photo (:class:`telegram.LivePhoto`, optional): Message is a live photo, information + about the live photo. + + .. versionadded:: NEXT.VERSION Attributes: origin (:class:`telegram.MessageOrigin`): Origin of the message replied to by the given @@ -166,6 +171,11 @@ class ExternalReplyInfo(TelegramObject): information about the paid media. .. versionadded:: 21.4 + live_photo (:class:`telegram.LivePhoto`): Optional. Message is a live photo, information + about the live photo. + + .. versionadded:: NEXT.VERSION + """ __slots__ = ( @@ -182,6 +192,7 @@ class ExternalReplyInfo(TelegramObject): "has_media_spoiler", "invoice", "link_preview_options", + "live_photo", "location", "message_id", "origin", @@ -223,6 +234,7 @@ def __init__( venue: Venue | None = None, paid_media: PaidMediaInfo | None = None, checklist: Checklist | None = None, + live_photo: LivePhoto | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -253,6 +265,7 @@ def __init__( self.poll: Poll | None = poll self.venue: Venue | None = venue self.paid_media: PaidMediaInfo | None = paid_media + self.live_photo: LivePhoto | None = live_photo self._id_attrs = (self.origin,) @@ -290,6 +303,7 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ExternalReplyInfo data["venue"] = de_json_optional(data.get("venue"), Venue, bot) data["paid_media"] = de_json_optional(data.get("paid_media"), PaidMediaInfo, bot) data["checklist"] = de_json_optional(data.get("checklist"), Checklist, bot) + data["live_photo"] = de_json_optional(data.get("live_photo"), LivePhoto, bot) return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_sentguestmessage.py b/src/telegram/_sentguestmessage.py new file mode 100644 index 00000000000..ecbc6ce6fe6 --- /dev/null +++ b/src/telegram/_sentguestmessage.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2026 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""This module contains an object that represents a Telegram Sent Guest Message.""" + +from telegram._telegramobject import TelegramObject +from telegram._utils.types import JSONDict + + +class SentGuestMessage(TelegramObject): + """Describes an inline message sent by a guest bot. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`inline_message_id` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + inline_message_id (:obj:`str`): Identifier of the sent inline message. + + Attributes: + inline_message_id (:obj:`str`): Identifier of the sent inline message. + """ + + __slots__ = ("inline_message_id",) + + def __init__( + self, + inline_message_id: str, + *, + api_kwargs: JSONDict | None = None, + ): + super().__init__(api_kwargs=api_kwargs) + # Required + self.inline_message_id: str = inline_message_id + + self._id_attrs = (self.inline_message_id,) + + self._freeze() diff --git a/src/telegram/_update.py b/src/telegram/_update.py index dc2a7f2f1e3..09c06896594 100644 --- a/src/telegram/_update.py +++ b/src/telegram/_update.py @@ -168,6 +168,11 @@ class Update(TelegramObject): managed by the bot, or token or owner of a managed bot was changed. .. versionadded:: NEXT.VERSION + guest_message (:class:`telegram.Message`, optional): New guest message. The bot can use + the field :attr:`telegram.Message.guest_query_id` and the method + :meth:`telegram.Bot.answer_guest_query` to send a message in response. + + .. versionadded:: NEXT.VERSION Attributes: @@ -284,6 +289,11 @@ class Update(TelegramObject): managed_bot (:class:`telegram.ManagedBotUpdated`): Optional. A new bot was created to be managed by the bot, or token or owner of a managed bot was changed. + .. versionadded:: NEXT.VERSION + guest_message (:class:`telegram.Message`): Optional. New guest message. The bot can use + the field :attr:`telegram.Message.guest_query_id` and the method + :meth:`telegram.Bot.answerGuestQuery` to send a message in response. + .. versionadded:: NEXT.VERSION """ @@ -304,6 +314,7 @@ class Update(TelegramObject): "edited_business_message", "edited_channel_post", "edited_message", + "guest_message", "inline_query", "managed_bot", "message", @@ -417,6 +428,11 @@ class Update(TelegramObject): MANAGED_BOT: Final[str] = constants.UpdateType.MANAGED_BOT """:const:`telegram.constants.UpdateType.MANAGED_BOT` + .. versionadded:: NEXT.VERSION + """ + GUEST_MESSAGE: Final[str] = constants.UpdateType.GUEST_MESSAGE + """:const:`telegram.constants.UpdateType.GUEST_MESSAGE` + .. versionadded:: NEXT.VERSION """ @@ -452,6 +468,7 @@ def __init__( deleted_business_messages: BusinessMessagesDeleted | None = None, purchased_paid_media: PaidMediaPurchased | None = None, managed_bot: ManagedBotUpdated | None = None, + guest_message: Message | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -483,6 +500,7 @@ def __init__( self.deleted_business_messages: BusinessMessagesDeleted | None = deleted_business_messages self.purchased_paid_media: PaidMediaPurchased | None = purchased_paid_media self.managed_bot: ManagedBotUpdated | None = managed_bot + self.guest_message: Message | None = guest_message self._effective_user: User | None = None self._effective_sender: User | Chat | None = None @@ -516,8 +534,8 @@ def effective_user(self) -> "User | None": This property now also considers :attr:`purchased_paid_media`. .. versionchanged:: NEXT.VERSION - This property now also considers :attr:`managed_bot`, :attr:`channel_post` - and :attr:`edited_channel_post`. + This property now also considers :attr:`managed_bot`, :attr:`guest_message`, + :attr:`channel_post`, and :attr:`edited_channel_post`. Example: * If :attr:`message` is present, this will give @@ -530,11 +548,14 @@ def effective_user(self) -> "User | None": user = None - if self.message: - user = self.message.from_user - - elif self.edited_message: - user = self.edited_message.from_user + if message := ( + self.message + or self.edited_message + or self.business_message + or self.edited_business_message + or self.guest_message + ): + user = message.from_user elif self.channel_post: user = self.channel_post.from_user @@ -572,12 +593,6 @@ def effective_user(self) -> "User | None": elif self.message_reaction: user = self.message_reaction.user - elif self.business_message: - user = self.business_message.from_user - - elif self.edited_business_message: - user = self.edited_business_message.from_user - elif self.business_connection: user = self.business_connection.user @@ -611,6 +626,9 @@ def effective_sender(self) -> "User | Chat | None": is present. + .. versionchanged:: NEXT.VERSION + This property now also considers :attr:`guest_message`. + Example: * If :attr:`message` is present, this will give either :attr:`telegram.Message.from_user` or :attr:`telegram.Message.sender_chat`. @@ -633,6 +651,7 @@ def effective_sender(self) -> "User | Chat | None": or self.edited_channel_post or self.business_message or self.edited_business_message + or self.guest_message ): sender = message.sender_chat @@ -664,6 +683,9 @@ def effective_chat(self) -> "Chat | None": This property now also considers :attr:`business_message`, :attr:`edited_business_message`, and :attr:`deleted_business_messages`. + .. versionchanged:: NEXT.VERSION + This property now also considers :attr:`guest_message`. + Example: If :attr:`message` is present, this will give :attr:`telegram.Message.chat`. @@ -673,21 +695,21 @@ def effective_chat(self) -> "Chat | None": chat = None - if self.message: - chat = self.message.chat - - elif self.edited_message: - chat = self.edited_message.chat + if message := ( + self.message + or self.edited_message + or self.channel_post + or self.edited_channel_post + or self.business_message + or self.edited_business_message + or self.deleted_business_messages + or self.guest_message + ): + chat = message.chat elif self.callback_query and self.callback_query.message: chat = self.callback_query.message.chat - elif self.channel_post: - chat = self.channel_post.chat - - elif self.edited_channel_post: - chat = self.edited_channel_post.chat - elif self.my_chat_member: chat = self.my_chat_member.chat @@ -709,15 +731,6 @@ def effective_chat(self) -> "Chat | None": elif self.message_reaction_count: chat = self.message_reaction_count.chat - elif self.business_message: - chat = self.business_message.chat - - elif self.edited_business_message: - chat = self.edited_business_message.chat - - elif self.deleted_business_messages: - chat = self.deleted_business_messages.chat - self._effective_chat = chat return chat @@ -734,6 +747,9 @@ def effective_message(self) -> Message | None: This property now also considers :attr:`business_message`, and :attr:`edited_business_message`. + .. versionchanged:: NEXT.VERSION + This property now also considers :attr:`guest_message`. + Tip: This property will only ever return objects of type :class:`telegram.Message` or :obj:`None`, never :class:`telegram.MaybeInaccessibleMessage` or @@ -782,6 +798,9 @@ def effective_message(self) -> Message | None: elif self.edited_business_message: message = self.edited_business_message + elif self.guest_message: + message = self.guest_message + self._effective_message = message return message @@ -838,5 +857,6 @@ def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "Update": data.get("purchased_paid_media"), PaidMediaPurchased, bot ) data["managed_bot"] = de_json_optional(data.get("managed_bot"), ManagedBotUpdated, bot) + data["guest_message"] = de_json_optional(data.get("guest_message"), Message, bot) return super().de_json(data=data, bot=bot) diff --git a/src/telegram/_user.py b/src/telegram/_user.py index 821f230adb6..6eb3a6cf18a 100644 --- a/src/telegram/_user.py +++ b/src/telegram/_user.py @@ -42,17 +42,21 @@ from telegram import ( Animation, Audio, + BotAccessSettings, Contact, Document, Gift, InlineKeyboardMarkup, InputMediaAudio, InputMediaDocument, + InputMediaLivePhoto, InputMediaPhoto, InputMediaVideo, + InputPollMedia, InputPollOption, LabeledPrice, LinkPreviewOptions, + LivePhoto, Location, Message, MessageEntity, @@ -108,7 +112,7 @@ class User(TelegramObject): .. versionadded:: 20.0 can_connect_to_business (:obj:`bool`, optional): :obj:`True`, if the bot can be connected - to a Telegram Business account to receive its messages. Returned only in + to a user account to manage it. Returned only in :meth:`telegram.Bot.get_me`. .. versionadded:: 21.1 @@ -128,6 +132,11 @@ class User(TelegramObject): can_manage_bots (:obj:`bool`, optional): :obj:`True`, if other bots can be created to be controlled by the bot. Returned only in :meth:`telegram.Bot.get_me`. + .. versionadded:: NEXT.VERSION + supports_guest_queries (:obj:`bool`, optional): :obj:`True`, if the bot supports guest + queries from chats it is not a member of. Returned only in + :meth:`telegram.Bot.get_me`. + .. versionadded:: NEXT.VERSION Attributes: @@ -172,6 +181,11 @@ class User(TelegramObject): can_manage_bots (:obj:`bool`): Optional. :obj:`True`, if other bots can be created to be controlled by the bot. Returned only in :meth:`telegram.Bot.get_me`. + .. versionadded:: NEXT.VERSION + supports_guest_queries (:obj:`bool`): Optional. :obj:`True`, if the bot supports guest + queries from chats it is not a member of. Returned only in + :meth:`telegram.Bot.get_me`. + .. versionadded:: NEXT.VERSION .. |user_chat_id_note| replace:: This shortcuts build on the assumption that :attr:`User.id` @@ -194,6 +208,7 @@ class User(TelegramObject): "is_premium", "language_code", "last_name", + "supports_guest_queries", "supports_inline_queries", "username", ) @@ -216,6 +231,7 @@ def __init__( has_topics_enabled: bool | None = None, allows_users_to_create_topics: bool | None = None, can_manage_bots: bool | None = None, + supports_guest_queries: bool | None = None, *, api_kwargs: JSONDict | None = None, ): @@ -238,6 +254,7 @@ def __init__( self.has_topics_enabled: bool | None = has_topics_enabled self.allows_users_to_create_topics: bool | None = allows_users_to_create_topics self.can_manage_bots: bool | None = can_manage_bots + self.supports_guest_queries: bool | None = supports_guest_queries self._id_attrs = (self.id,) @@ -528,7 +545,7 @@ async def send_message( async def send_message_draft( self, draft_id: int, - text: str, + text: str | None = None, message_thread_id: int | None = None, parse_mode: ODVInput[str] = DEFAULT_NONE, entities: Sequence["MessageEntity"] | None = None, @@ -550,6 +567,9 @@ async def send_message_draft( .. versionadded:: 22.6 + .. versionchanged:: NEXT.VERSION + Bot API 10.0 makes the ``text`` argument optional. + Returns: :obj:`bool`: On success, :obj:`True` is returned. @@ -701,10 +721,83 @@ async def send_photo( suggested_post_parameters=suggested_post_parameters, ) + async def send_live_photo( + self, + live_photo: "FileInput | LivePhoto", + photo: "FileInput | PhotoSize", + business_connection_id: str | None = None, + message_thread_id: int | None = None, + direct_messages_topic_id: int | None = None, + caption: str | None = None, + parse_mode: ODVInput[str] = DEFAULT_NONE, + caption_entities: Sequence["MessageEntity"] | None = None, + show_caption_above_media: bool | None = None, + has_spoiler: bool | None = None, + disable_notification: ODVInput[bool] = DEFAULT_NONE, + protect_content: ODVInput[bool] = DEFAULT_NONE, + allow_paid_broadcast: bool | None = None, + message_effect_id: str | None = None, + suggested_post_parameters: "SuggestedPostParameters | None" = None, + reply_parameters: "ReplyParameters | None" = None, + reply_markup: "ReplyMarkup | None" = None, + *, + allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + reply_to_message_id: int | None = None, + filename: str | None = None, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> "Message": + """Shortcut for:: + + await bot.send_live_photo(update.effective_user.id, *args, **kwargs) + + For the documentation of the arguments, please see :meth:`telegram.Bot.send_live_photo`. + + .. versionadded:: NEXT.VERSION + + Note: + |user_chat_id_note| + + Returns: + :class:`telegram.Message`: On success, instance representing the message posted. + + """ + return await self.get_bot().send_live_photo( + chat_id=self.id, + live_photo=live_photo, + photo=photo, + business_connection_id=business_connection_id, + message_thread_id=message_thread_id, + direct_messages_topic_id=direct_messages_topic_id, + caption=caption, + parse_mode=parse_mode, + caption_entities=caption_entities, + show_caption_above_media=show_caption_above_media, + has_spoiler=has_spoiler, + disable_notification=disable_notification, + protect_content=protect_content, + allow_paid_broadcast=allow_paid_broadcast, + message_effect_id=message_effect_id, + suggested_post_parameters=suggested_post_parameters, + reply_parameters=reply_parameters, + reply_markup=reply_markup, + allow_sending_without_reply=allow_sending_without_reply, + reply_to_message_id=reply_to_message_id, + filename=filename, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + async def send_media_group( self, media: Sequence[ - "InputMediaAudio | InputMediaDocument | InputMediaPhoto | InputMediaVideo" + "InputMediaAudio | InputMediaDocument | InputMediaPhoto | InputMediaVideo | InputMediaLivePhoto" # noqa: E501 # pylint: disable=line-too-long ], disable_notification: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE, @@ -1748,8 +1841,12 @@ async def send_poll( allow_adding_options: bool | None = None, hide_results_until_closes: bool | None = None, description: str | None = None, - description_parse_mode: str | None = None, + description_parse_mode: ODVInput[str] | None = None, description_entities: Sequence["MessageEntity"] | None = None, + members_only: bool | None = None, + country_codes: Sequence[str] | None = None, + explanation_media: "InputPollMedia | None" = None, + media: "InputPollMedia | None" = None, *, reply_to_message_id: int | None = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1811,6 +1908,10 @@ async def send_poll( description_entities=description_entities, hide_results_until_closes=hide_results_until_closes, allow_adding_options=allow_adding_options, + members_only=members_only, + country_codes=country_codes, + explanation_media=explanation_media, + media=media, ) async def send_gift( @@ -2761,7 +2862,7 @@ async def replace_token( .. versionadded:: NEXT.VERSION Returns: - :obj:`bool`: On success, :obj:`str` is returned. + :obj:`str`: On success, :obj:`str` is returned. """ return await self.get_bot().replace_managed_bot_token( user_id=self.id, @@ -2771,3 +2872,196 @@ async def replace_token( pool_timeout=pool_timeout, api_kwargs=api_kwargs, ) + + async def get_managed_bot_access_settings( + self, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> "BotAccessSettings": + """ + Shortcut for:: + + await bot.get_managed_bot_access_settings( + user_id=update.effective_user.id, + *args, **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.get_managed_bot_access_settings`. + + .. versionadded:: NEXT.VERSION + + Returns: + :class:`telegram.BotAccessSettings`: On success, returns the access settings of the bot + managed by the user. + """ + + return await self.get_bot().get_managed_bot_access_settings( + user_id=self.id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def set_managed_bot_access_settings( + self, + is_access_restricted: bool, + added_user_ids: Sequence[int] | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Shortcut for:: + + await bot.set_managed_bot_access_settings( + user_id=update.effective_user.id, + *args, **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.set_managed_bot_access_settings`. + + .. versionadded:: NEXT.VERSION + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + + return await self.get_bot().set_managed_bot_access_settings( + user_id=self.id, + is_access_restricted=is_access_restricted, + added_user_ids=added_user_ids, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def delete_reaction( + self, + chat_id: int | str, + message_id: int, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Shortcut for:: + + await bot.delete_message_reaction( + user_id=update.effective_user.id, + *args, + **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.delete_message_reaction`. + + .. versionadded:: NEXT.VERSION + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().delete_message_reaction( + user_id=self.id, + chat_id=chat_id, + message_id=message_id, + actor_chat_id=actor_chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def get_personal_chat_messages( + self, + limit: int, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> tuple["Message", ...]: + """ + Shortcut for:: + + await bot.get_user_personal_chat_messages( + user_id=update.effective_user.id, + *args, **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.get_user_personal_chat_messages`. + + .. versionadded:: NEXT.VERSION + + Returns: + tuple[:class:`telegram.Message`]: On success, a tuple of messages from the personal + channel chat is returned. + """ + + return await self.get_bot().get_user_personal_chat_messages( + user_id=self.id, + limit=limit, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def delete_all_reactions( + self, + chat_id: int | str, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + ) -> bool: + """ + Shortcut for:: + + await bot.delete_all_message_reactions( + user_id=update.effective_user.id, + *args, + **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.delete_all_message_reactions`. + + .. versionadded:: NEXT.VERSION + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().delete_all_message_reactions( + chat_id=chat_id, + user_id=self.id, + actor_chat_id=actor_chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) diff --git a/src/telegram/_webhookinfo.py b/src/telegram/_webhookinfo.py index 94f6f7bf22e..7955949d516 100644 --- a/src/telegram/_webhookinfo.py +++ b/src/telegram/_webhookinfo.py @@ -63,7 +63,8 @@ class WebhookInfo(TelegramObject): connections to the webhook for update delivery. allowed_updates (Sequence[:obj:`str`], optional): A sequence of update types the bot is subscribed to. Defaults to all update types, except - :attr:`telegram.Update.chat_member`. + :attr:`~telegram.Update.chat_member`, :attr:`~telegram.Update.message_reaction`, + and :attr:`~telegram.Update.message_reaction_count`. .. versionchanged:: 20.0 |sequenceclassargs| @@ -93,7 +94,8 @@ class WebhookInfo(TelegramObject): connections to the webhook for update delivery. allowed_updates (tuple[:obj:`str`]): Optional. A tuple of update types the bot is subscribed to. Defaults to all update types, except - :attr:`telegram.Update.chat_member`. + :attr:`~telegram.Update.chat_member`, :attr:`~telegram.Update.message_reaction`, + and :attr:`~telegram.Update.message_reaction_count`. .. versionchanged:: 20.0 diff --git a/src/telegram/constants.py b/src/telegram/constants.py index 144e6763892..0bcd88e8f2c 100644 --- a/src/telegram/constants.py +++ b/src/telegram/constants.py @@ -45,6 +45,7 @@ "BackgroundFillType", "BackgroundTypeLimit", "BackgroundTypeType", + "BaseInputMediaType", "BotCommandLimit", "BotCommandScopeType", "BotDescriptionLimit", @@ -87,6 +88,7 @@ "KeyboardButtonRequestUsersLimit", "KeyboardButtonStyle", "LocationLimit", + "ManagedBotAccessLimit", "MaskPosition", "MediaGroupLimit", "MenuButtonType", @@ -101,6 +103,7 @@ "OwnedGiftType", "PaidMediaType", "ParseMode", + "PersonalChatMessagesLimit", "PollLimit", "PollType", "PollingLimit", @@ -181,7 +184,7 @@ class _AccentColor(NamedTuple): #: :data:`telegram.__bot_api_version_info__`. #: #: .. versionadded:: 20.0 -BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=9, minor=6) +BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=10, minor=0) #: :obj:`str`: Telegram Bot API #: version supported by this version of `python-telegram-bot`. Also available as #: :data:`telegram.__bot_api_version__`. @@ -1519,10 +1522,43 @@ class InputChecklistLimit(IntEnum): """ +class BaseInputMediaType(StringEnum): + """This enum contains the available types of :class:`telegram.InputMedia`, + :class:`telegram.InputPollMedia` and :class:`telegram.InputPollOptionMedia`. The enum + members of this enumeration are instances of :class:`str` and can be treated as such. + + .. versionadded:: NEXT.VERSION + """ + + __slots__ = () + + ANIMATION = "animation" + """:obj:`str`: Type of :class:`telegram.InputMediaAnimation`.""" + DOCUMENT = "document" + """:obj:`str`: Type of :class:`telegram.InputMediaDocument`.""" + AUDIO = "audio" + """:obj:`str`: Type of :class:`telegram.InputMediaAudio`.""" + PHOTO = "photo" + """:obj:`str`: Type of :class:`telegram.InputMediaPhoto`.""" + VIDEO = "video" + """:obj:`str`: Type of :class:`telegram.InputMediaVideo`.""" + LOCATION = "location" + """:obj:`str`: Type of :class:`telegram.InputMediaLocation`.""" + STICKER = "sticker" + """:obj:`str`: Type of :class:`telegram.InputMediaSticker`.""" + VENUE = "venue" + """:obj:`str`: Type of :class:`telegram.InputMediaVenue`.""" + LIVE_PHOTO = "live_photo" + """:obj:`str`: Type of :class:`telegram.InputMediaLivePhoto`.""" + + class InputMediaType(StringEnum): """This enum contains the available types of :class:`telegram.InputMedia`. The enum members of this enumeration are instances of :class:`str` and can be treated as such. + .. deprecated:: NEXT.VERSION + Use :class:`telegram.constants.BaseInputMediaType` instead. + .. versionadded:: 20.0 """ @@ -1538,6 +1574,11 @@ class InputMediaType(StringEnum): """:obj:`str`: Type of :class:`telegram.InputMediaPhoto`.""" VIDEO = "video" """:obj:`str`: Type of :class:`telegram.InputMediaVideo`.""" + LIVE_PHOTO = "live_photo" + """:obj:`str`: Type of :class:`telegram.InputMediaLivePhoto`. + + .. versionadded:: NEXT.VERSION + """ class InputPaidMediaType(StringEnum): @@ -1550,9 +1591,14 @@ class InputPaidMediaType(StringEnum): __slots__ = () PHOTO = "photo" - """:obj:`str`: Type of :class:`telegram.InputMediaPhoto`.""" + """:obj:`str`: Type of :class:`telegram.InputPaidMediaPhoto`.""" VIDEO = "video" - """:obj:`str`: Type of :class:`telegram.InputMediaVideo`.""" + """:obj:`str`: Type of :class:`telegram.InputPaidMediaVideo`.""" + LIVE_PHOTO = "live_photo" + """:obj:`str`: Type of :class:`telegram.InputPaidMediaLivePhoto`. + + .. versionadded:: NEXT.VERSION + """ class InputProfilePhotoType(StringEnum): @@ -1784,6 +1830,8 @@ class LocationLimit(IntEnum): :meth:`telegram.Bot.edit_message_live_location` * :paramref:`~telegram.Bot.send_location.horizontal_accuracy` parameter of :meth:`telegram.Bot.send_location` + * :paramref:`~telegram.InputMediaLocation.horizontal_accuracy` parameter of + :class:`telegram.InputMediaLocation` """ MIN_HEADING = 1 @@ -1963,6 +2011,11 @@ class MessageAttachmentType(StringEnum): """:obj:`str`: Messages with :attr:`telegram.Message.game`.""" INVOICE = "invoice" """:obj:`str`: Messages with :attr:`telegram.Message.invoice`.""" + LIVE_PHOTO = "live_photo" + """:obj:`str`: Messages with :attr:`telegram.Message.live_photo`. + + .. versionadded:: NEXT.VERSION + """ LOCATION = "location" """:obj:`str`: Messages with :attr:`telegram.Message.location`.""" PAID_MEDIA = "paid_media" @@ -2345,6 +2398,11 @@ class MessageType(StringEnum): """:obj:`str`: Messages with :attr:`telegram.Message.invoice`.""" LEFT_CHAT_MEMBER = "left_chat_member" """:obj:`str`: Messages with :attr:`telegram.Message.left_chat_member`.""" + LIVE_PHOTO = "live_photo" + """:obj:`str`: Messages with :attr:`telegram.Message.live_photo`. + + .. versionadded:: NEXT.VERSION + """ LOCATION = "location" """:obj:`str`: Messages with :attr:`telegram.Message.location`.""" MANAGED_BOT_CREATED = "managed_bot_created" @@ -2566,6 +2624,33 @@ class PaidMediaType(StringEnum): """:obj:`str`: The type of :class:`telegram.PaidMediaVideo`.""" PHOTO = "photo" """:obj:`str`: The type of :class:`telegram.PaidMediaPhoto`.""" + LIVE_PHOTO = "live_photo" + """:obj:`str`: The type of :class:`telegram.PaidMediaLivePhoto` + + .. versionadded:: NEXT.VERSION + """ + + +class PersonalChatMessagesLimit(IntEnum): + """This enum contains limitations for + :paramref:`telegram.Bot.get_user_personal_chat_messages.limit`. + The enum members of this enumeration are instances of :class:`int` and can be treated as such. + + .. versionadded:: NEXT.VERSION + """ + + __slots__ = () + + MIN_LIMIT = 1 + """:obj:`int`: Minimum value allowed for the + :paramref:`~telegram.Bot.get_user_personal_chat_messages.limit` + parameter of :meth:`telegram.Bot.get_user_personal_chat_messages`. + """ + MAX_LIMIT = 20 + """:obj:`int`: Maximum value allowed for the + :paramref:`~telegram.Bot.get_user_personal_chat_messages.limit` + parameter of :meth:`telegram.Bot.get_user_personal_chat_messages`. + """ class PollingLimit(IntEnum): @@ -3422,10 +3507,13 @@ class PollLimit(IntEnum): to the :paramref:`~telegram.Bot.send_poll.options` parameter of :meth:`telegram.Bot.send_poll`. """ - MIN_OPTION_NUMBER = 2 + MIN_OPTION_NUMBER = 1 """:obj:`int`: Minimum number of strings passed in a :obj:`list` to the :paramref:`~telegram.Bot.send_poll.options` parameter of :meth:`telegram.Bot.send_poll`. + + .. versionchanged:: NEXT.VERSION + Bot API 10.0 decreased this value from ``2`` to ``1``. """ MAX_OPTION_NUMBER = 12 """:obj:`int`: Maximum number of strings passed in a :obj:`list` @@ -3466,6 +3554,19 @@ class PollLimit(IntEnum): .. versionadded:: NEXT.VERSION """ + MIN_MEMBERSHIP_HOURS = 24 + """:obj:`int`: Minimum number of hours a user must have been a member of the chat + before they can vote in a members-only poll. + + .. versionadded:: NEXT.VERSION + """ + MAX_COUNTRY_CODES = 12 + """:obj:`int`: Maximum number of two-letter ``ISO 3166-1 alpha-2`` country codes passed in a + :obj:`list` to the :paramref:`~telegram.Bot.send_poll.country_codes` parameter of + :meth:`telegram.Bot.send_poll`. + + .. versionadded:: NEXT.VERSION + """ class PollType(StringEnum): @@ -3620,6 +3721,11 @@ class UpdateType(StringEnum): .. versionadded:: NEXT.VERSION """ + GUEST_MESSAGE = "guest_message" + """:obj:`str`: Updates with :attr:`telegram.Update.guest_message`. + + .. versionadded:: NEXT.VERSION + """ class InvoiceLimit(IntEnum): @@ -4039,6 +4145,22 @@ class ReactionEmoji(StringEnum): """:obj:`str`: Pouting face""" +class ManagedBotAccessLimit(IntEnum): + """This enum contains limitations for :meth:`~telegram.Bot.set_managed_bot_access_settings`. + The enum members of this enumeration are instances of :class:`int` and can be treated as such. + + .. versionadded:: NEXT.VERSION + """ + + __slots__ = () + + MAX_ALLOWED_USERS = 10 + """:obj:`int`: Maximum number of users that can be allowed to access a managed bot in the + :paramref:`~telegram.Bot.set_managed_bot_access_settings.added_user_ids` parameter of + :meth:`~telegram.Bot.set_managed_bot_access_settings`. + """ + + class VerifyLimit(IntEnum): """This enum contains limitations for :meth:`~telegram.Bot.verify_chat` and :meth:`~telegram.Bot.verify_user`. diff --git a/src/telegram/ext/_extbot.py b/src/telegram/ext/_extbot.py index 931115f7beb..0d8ef596e50 100644 --- a/src/telegram/ext/_extbot.py +++ b/src/telegram/ext/_extbot.py @@ -38,6 +38,7 @@ Animation, Audio, Bot, + BotAccessSettings, BotCommand, BotCommandScope, BotDescription, @@ -76,6 +77,7 @@ PreparedKeyboardButton, ReactionType, ReplyParameters, + SentGuestMessage, SentWebAppMessage, StarAmount, StarTransactions, @@ -119,11 +121,14 @@ InlineQueryResult, InputMediaAudio, InputMediaDocument, + InputMediaLivePhoto, InputMediaPhoto, InputMediaVideo, + InputPollMedia, InputSticker, InputStoryContent, LabeledPrice, + LivePhoto, Location, MessageEntity, PassportElementError, @@ -1119,6 +1124,28 @@ async def answer_web_app_query( api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), ) + async def answer_guest_query( + self, + guest_query_id: str, + result: "InlineQueryResult", + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + rate_limit_args: RLARGS | None = None, + ) -> SentGuestMessage: + return await super().answer_guest_query( + guest_query_id=guest_query_id, + result=result, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + async def approve_chat_join_request( self, chat_id: str | int, @@ -1844,6 +1871,7 @@ async def forward_messages( async def get_chat_administrators( self, chat_id: str | int, + return_bots: bool | None = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -1854,6 +1882,7 @@ async def get_chat_administrators( ) -> tuple[ChatMember, ...]: return await super().get_chat_administrators( chat_id=chat_id, + return_bots=return_bots, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, @@ -2689,7 +2718,7 @@ async def send_contact( async def send_checklist( self, business_connection_id: str, - chat_id: int, + chat_id: int | str, checklist: InputChecklist, disable_notification: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE, @@ -2727,7 +2756,7 @@ async def send_checklist( async def edit_message_checklist( self, business_connection_id: str, - chat_id: int, + chat_id: int | str, message_id: int, checklist: InputChecklist, reply_markup: "InlineKeyboardMarkup | None" = None, @@ -2858,7 +2887,7 @@ async def send_document( async def send_game( self, - chat_id: int, + chat_id: int | str, game_short_name: str, disable_notification: ODVInput[bool] = DEFAULT_NONE, reply_markup: "InlineKeyboardMarkup | None" = None, @@ -3044,7 +3073,7 @@ async def send_media_group( self, chat_id: int | str, media: Sequence[ - "InputMediaAudio | InputMediaDocument | InputMediaPhoto | InputMediaVideo" + "InputMediaAudio | InputMediaDocument | InputMediaPhoto | InputMediaVideo | InputMediaLivePhoto" # noqa: E501 # pylint: disable=line-too-long ], disable_notification: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE, @@ -3148,7 +3177,7 @@ async def send_message_draft( self, chat_id: int, draft_id: int, - text: str, + text: str | None = None, message_thread_id: int | None = None, parse_mode: ODVInput[str] = DEFAULT_NONE, entities: Sequence["MessageEntity"] | None = None, @@ -3262,9 +3291,13 @@ async def send_poll( hide_results_until_closes: bool | None = None, correct_option_ids: CorrectOptionIds | None = None, description: str | None = None, - description_parse_mode: str | None = None, + description_parse_mode: ODVInput[str] | None = None, description_entities: Sequence["MessageEntity"] | None = None, shuffle_options: bool | None = None, + members_only: bool | None = None, + country_codes: Sequence[str] | None = None, + explanation_media: "InputPollMedia | None" = None, + media: "InputPollMedia | None" = None, *, reply_to_message_id: int | None = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3314,6 +3347,10 @@ async def send_poll( description_entities=description_entities, hide_results_until_closes=hide_results_until_closes, allow_adding_options=allow_adding_options, + members_only=members_only, + country_codes=country_codes, + explanation_media=explanation_media, + media=media, ) async def send_sticker( @@ -5032,6 +5069,74 @@ async def edit_user_star_subscription( api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), ) + async def get_managed_bot_access_settings( + self, + user_id: int, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + rate_limit_args: RLARGS | None = None, + ) -> BotAccessSettings: + + return await super().get_managed_bot_access_settings( + user_id=user_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + + async def set_managed_bot_access_settings( + self, + user_id: int, + is_access_restricted: bool, + added_user_ids: Sequence[int] | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + rate_limit_args: RLARGS | None = None, + ) -> bool: + + return await super().set_managed_bot_access_settings( + user_id=user_id, + is_access_restricted=is_access_restricted, + added_user_ids=added_user_ids, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + + async def get_user_personal_chat_messages( + self, + user_id: int, + limit: int, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + rate_limit_args: RLARGS | None = None, + ) -> tuple[Message, ...]: + return await super().get_user_personal_chat_messages( + user_id=user_id, + limit=limit, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + async def send_paid_media( self, chat_id: str | int, @@ -5593,6 +5698,117 @@ async def save_prepared_keyboard_button( api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), ) + async def send_live_photo( + self, + chat_id: int | str, + live_photo: "FileInput | LivePhoto", + photo: "FileInput | PhotoSize", + business_connection_id: str | None = None, + message_thread_id: int | None = None, + direct_messages_topic_id: int | None = None, + caption: str | None = None, + parse_mode: ODVInput[str] = DEFAULT_NONE, + caption_entities: Sequence["MessageEntity"] | None = None, + show_caption_above_media: bool | None = None, + has_spoiler: bool | None = None, + disable_notification: ODVInput[bool] = DEFAULT_NONE, + protect_content: ODVInput[bool] = DEFAULT_NONE, + allow_paid_broadcast: bool | None = None, + message_effect_id: str | None = None, + suggested_post_parameters: "SuggestedPostParameters | None" = None, + reply_parameters: "ReplyParameters | None" = None, + reply_markup: "ReplyMarkup | None" = None, + *, + allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + reply_to_message_id: int | None = None, + filename: str | None = None, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + rate_limit_args: RLARGS | None = None, + ) -> Message: + + return await super().send_live_photo( + chat_id=chat_id, + live_photo=live_photo, + photo=photo, + business_connection_id=business_connection_id, + message_thread_id=message_thread_id, + direct_messages_topic_id=direct_messages_topic_id, + caption=caption, + parse_mode=parse_mode, + caption_entities=caption_entities, + show_caption_above_media=show_caption_above_media, + has_spoiler=has_spoiler, + disable_notification=disable_notification, + protect_content=protect_content, + allow_paid_broadcast=allow_paid_broadcast, + message_effect_id=message_effect_id, + suggested_post_parameters=suggested_post_parameters, + reply_parameters=reply_parameters, + allow_sending_without_reply=allow_sending_without_reply, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + filename=filename, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + + async def delete_message_reaction( + self, + chat_id: int | str, + message_id: int, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + rate_limit_args: RLARGS | None = None, + ) -> bool: + return await super().delete_message_reaction( + chat_id=chat_id, + message_id=message_id, + user_id=user_id, + actor_chat_id=actor_chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + + async def delete_all_message_reactions( + self, + chat_id: int | str, + user_id: int | None = None, + actor_chat_id: int | None = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict | None = None, + rate_limit_args: RLARGS | None = None, + ) -> bool: + return await super().delete_all_message_reactions( + chat_id=chat_id, + user_id=user_id, + actor_chat_id=actor_chat_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + # updated camelCase aliases getMe = get_me sendMessage = send_message @@ -5647,6 +5863,7 @@ async def save_prepared_keyboard_button( answerShippingQuery = answer_shipping_query answerPreCheckoutQuery = answer_pre_checkout_query answerWebAppQuery = answer_web_app_query + answerGuestQuery = answer_guest_query restrictChatMember = restrict_chat_member promoteChatMember = promote_chat_member setChatPermissions = set_chat_permissions @@ -5762,3 +5979,9 @@ async def save_prepared_keyboard_button( getManagedBotToken = get_managed_bot_token replaceManagedBotToken = replace_managed_bot_token savePreparedKeyboardButton = save_prepared_keyboard_button + sendLivePhoto = send_live_photo + getManagedBotAccessSettings = get_managed_bot_access_settings + setManagedBotAccessSettings = set_managed_bot_access_settings + getUserPersonalChatMessages = get_user_personal_chat_messages + deleteMessageReaction = delete_message_reaction + deleteAllMessageReactions = delete_all_message_reactions diff --git a/src/telegram/ext/filters.py b/src/telegram/ext/filters.py index d8fa167e7b0..701179da083 100644 --- a/src/telegram/ext/filters.py +++ b/src/telegram/ext/filters.py @@ -62,6 +62,7 @@ "IS_AUTOMATIC_FORWARD", "IS_FROM_OFFLINE", "IS_TOPIC_MESSAGE", + "LIVE_PHOTO", "LOCATION", "PAID_MEDIA", "PASSPORT_DATA", @@ -273,6 +274,10 @@ def check_update(self, update: Update) -> bool | FilterDataDict | None: :attr:`~telegram.Update.business_message` or :attr:`~telegram.Update.edited_business_message`. + .. versionchanged:: NEXT.VERSION + This filter now also returns :obj:`True` if the update contains + :attr:`~telegram.Update.guest_message`. + Args: update (:class:`telegram.Update`): The update to check. @@ -281,7 +286,8 @@ def check_update(self, update: Update) -> bool | FilterDataDict | None: :attr:`~telegram.Update.channel_post`, :attr:`~telegram.Update.message`, :attr:`~telegram.Update.edited_channel_post`, :attr:`~telegram.Update.edited_message`, :attr:`telegram.Update.business_message`, - :attr:`telegram.Update.edited_business_message`, or :obj:`False` otherwise. + :attr:`telegram.Update.edited_business_message`, + :attr:`telegram.Update.guest_message`, or :obj:`False` otherwise. """ return bool( # Only message updates should be handled. update.channel_post @@ -290,6 +296,7 @@ def check_update(self, update: Update) -> bool | FilterDataDict | None: or update.edited_message or update.business_message or update.edited_business_message + or update.guest_message ) @@ -1651,6 +1658,20 @@ def filter(self, message: Message) -> bool: ) +class _LivePhoto(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.live_photo) + + +LIVE_PHOTO = _LivePhoto(name="filters.LIVE_PHOTO") +"""Messages that contain :attr:`telegram.Message.live_photo`. + +.. versionadded:: NEXT.VERSION +""" + + class _Location(MessageFilter): __slots__ = () @@ -2894,6 +2915,18 @@ def filter(self, update: Update) -> bool: .. versionadded:: 21.1 """ + class _GuestMessage(UpdateFilter): + __slots__ = () + + def filter(self, update: Update) -> bool: + return update.guest_message is not None + + GUEST_MESSAGE = _GuestMessage(name="filters.UpdateType.GUEST_MESSAGE") + """Updates with :attr:`telegram.Update.guest_message`. + + .. versionadded:: NEXT.VERSION + """ + class User(_ChatUserBaseFilter): """Filters messages to allow only those which are from specified user ID(s) or diff --git a/tests/_files/conftest.py b/tests/_files/conftest.py index 5bff5e23728..9e8e5212e11 100644 --- a/tests/_files/conftest.py +++ b/tests/_files/conftest.py @@ -145,3 +145,19 @@ def video_sticker_file(): def video_sticker(bot, chat_id): with data_file("telegram_video_sticker.webm").open("rb") as f: return bot.send_sticker(chat_id, sticker=f, timeout=50).sticker + + +@pytest.fixture(scope="session") +async def real_live_photo(bot, chat_id): + with ( + data_file("telegram.jpg").open("rb") as photo, + data_file("telegram.mp4").open("rb") as video, + ): + return ( + await bot.send_live_photo( + chat_id, + live_photo=video, + photo=photo, + read_timeout=50, + ) + ).live_photo diff --git a/tests/_files/test_inputmedia.py b/tests/_files/test_inputmedia.py index d43a853ec7d..2cd9c9bc75a 100644 --- a/tests/_files/test_inputmedia.py +++ b/tests/_files/test_inputmedia.py @@ -29,15 +29,22 @@ InputMediaAnimation, InputMediaAudio, InputMediaDocument, + InputMediaLivePhoto, + InputMediaLocation, InputMediaPhoto, + InputMediaSticker, + InputMediaVenue, InputMediaVideo, + InputPaidMediaLivePhoto, InputPaidMediaPhoto, InputPaidMediaVideo, + InputPollMedia, + InputPollOptionMedia, Message, MessageEntity, ReplyParameters, ) -from telegram.constants import InputMediaType, ParseMode +from telegram.constants import BaseInputMediaType, ParseMode from telegram.error import BadRequest from telegram.request import RequestData from telegram.warnings import PTBDeprecationWarning @@ -121,6 +128,37 @@ def input_media_document(class_thumb_file): ) +@pytest.fixture(scope="module") +def input_media_location(): + return InputMediaLocation( + latitude=InputMediaLocationTestBase.latitude, + longitude=InputMediaLocationTestBase.longitude, + horizontal_accuracy=InputMediaLocationTestBase.horizontal_accuracy, + ) + + +@pytest.fixture(scope="module") +def input_media_venue(): + return InputMediaVenue( + latitude=InputMediaVenueTestBase.latitude, + longitude=InputMediaVenueTestBase.longitude, + title=InputMediaVenueTestBase.title, + address=InputMediaVenueTestBase.address, + foursquare_id=InputMediaVenueTestBase.foursquare_id, + foursquare_type=InputMediaVenueTestBase.foursquare_type, + google_place_id=InputMediaVenueTestBase.google_place_id, + google_place_type=InputMediaVenueTestBase.google_place_type, + ) + + +@pytest.fixture(scope="module") +def input_media_sticker(): + return InputMediaSticker( + media=InputMediaStickerTestBase.media, + emoji=InputMediaStickerTestBase.emoji, + ) + + @pytest.fixture(scope="module") def input_paid_media_photo(): return InputPaidMediaPhoto( @@ -142,6 +180,27 @@ def input_paid_media_video(class_thumb_file): ) +@pytest.fixture(scope="module") +def input_media_live_photo(): + return InputMediaLivePhoto( + media=InputMediaLivePhotoTestBase.media, + photo=InputMediaLivePhotoTestBase.photo, + caption=InputMediaLivePhotoTestBase.caption, + parse_mode=InputMediaLivePhotoTestBase.parse_mode, + caption_entities=InputMediaLivePhotoTestBase.caption_entities, + show_caption_above_media=InputMediaLivePhotoTestBase.show_caption_above_media, + has_spoiler=InputMediaLivePhotoTestBase.has_spoiler, + ) + + +@pytest.fixture(scope="module") +def input_paid_media_live_photo(): + return InputPaidMediaLivePhoto( + media=InputMediaLivePhotoTestBase.media, + photo=InputMediaLivePhotoTestBase.photo, + ) + + class InputMediaVideoTestBase: type_ = "video" media = "NOTAREALFILEID" @@ -157,6 +216,21 @@ class InputMediaVideoTestBase: show_caption_above_media = True +class TestInputMediaWithoutRequest: + def test_type_enum_conversion(self): + assert type(InputMedia(media_type="video", media="media").type) is BaseInputMediaType + assert InputMedia(media_type="unknown", media="media").type == "unknown" + + def test_to_dict(self): + assert InputMedia( + media_type="video", + media="media", + ).to_dict() == { + "type": BaseInputMediaType.VIDEO, + "media": "media", + } + + class TestInputMediaVideoWithoutRequest(InputMediaVideoTestBase): def test_slot_behaviour(self, input_media_video): inst = input_media_video @@ -180,6 +254,10 @@ def test_expected_values(self, input_media_video): assert input_media_video.has_spoiler == self.has_spoiler assert input_media_video.show_caption_above_media == self.show_caption_above_media + assert isinstance(input_media_video, InputMedia) + assert isinstance(input_media_video, InputPollMedia) + assert isinstance(input_media_video, InputPollOptionMedia) + def test_caption_entities_always_tuple(self): input_media_video = InputMediaVideo(self.media) assert input_media_video.caption_entities == () @@ -253,25 +331,91 @@ def test_with_local_files(self): assert input_media_video.thumbnail == data_file("telegram.jpg").as_uri() assert input_media_video.cover == data_file("telegram.jpg").as_uri() - def test_type_enum_conversion(self): - # Since we have a lot of different test classes for all the input media types, we test this - # conversion only here. It is independent of the specific class - assert ( - type( - InputMedia( - media_type="animation", - media="media", - ).type - ) - is InputMediaType + def test_effective_filename(self, video_file): + inst = InputMediaVideo( + video_file, + "caption", + 24, + 24, + 10, + True, + "parse_mode", + [], + "pos_filename_depr", ) - assert ( - InputMedia( - media_type="unknown", - media="media", - ).type - == "unknown" + assert inst.media.filename == "pos_filename_depr" + + inst = InputMediaVideo( + video_file, + filename="kw_only_filename", + ) + assert inst.media.filename == "kw_only_filename" + + # Deprecated, but for completeness + inst = InputMediaVideo( + video_file, + filename_depr="kw_filename_depr", ) + assert inst.media.filename == "kw_filename_depr" + + def test_filename_depr_mutually_exclusive_filename(self, video_file): + with pytest.raises( + ValueError, match="`filename_depr` and `filename` are mutually exclusive" + ): + InputMediaVideo( + video_file, + "caption", + 24, + 24, + 10, + True, + "parse_mode", + [], + "pos_filename_depr", + filename="kw_filename", + ) + + with pytest.raises( + ValueError, match="`filename_depr` and `filename` are mutually exclusive" + ): + InputMediaVideo( + video_file, + filename_depr="filename_depr", + filename="kw_filename", + ) + + def test_positional_filename_deprecated(self, video_file): + with pytest.warns( + PTBDeprecationWarning, + match="Positional.*`filename`.*keyword.*`filename_depr`.*deprecated", + ) as record: + InputMediaVideo( + video_file, + "caption", + 24, + 24, + 10, + True, + "parse_mode", + [], + "pos_filename_depr", + ) + + assert record[0].category == PTBDeprecationWarning + assert record[0].filename == __file__, "wrong stacklevel!" + + def test_keyword_filename_depr_deprecated(self, video_file): + with pytest.warns( + PTBDeprecationWarning, + match="Positional.*`filename`.*keyword.*`filename_depr`.*deprecated", + ) as record: + InputMediaVideo( + video_file, + filename_depr="filename_depr", + ) + + assert record[0].category == PTBDeprecationWarning + assert record[0].filename == __file__, "wrong stacklevel!" class InputMediaPhotoTestBase: @@ -300,6 +444,10 @@ def test_expected_values(self, input_media_photo): assert input_media_photo.has_spoiler == self.has_spoiler assert input_media_photo.show_caption_above_media == self.show_caption_above_media + assert isinstance(input_media_photo, InputMedia) + assert isinstance(input_media_photo, InputPollMedia) + assert isinstance(input_media_photo, InputPollOptionMedia) + def test_caption_entities_always_tuple(self): input_media_photo = InputMediaPhoto(self.media) assert input_media_photo.caption_entities == () @@ -337,6 +485,80 @@ def test_with_local_files(self): input_media_photo = InputMediaPhoto(data_file("telegram.mp4")) assert input_media_photo.media == data_file("telegram.mp4").as_uri() + def test_effective_filename(self, photo_file): + inst = InputMediaPhoto( + photo_file, + "caption", + "parse_mode", + [], + "pos_filename_depr", + ) + assert inst.media.filename == "pos_filename_depr" + + inst = InputMediaPhoto( + photo_file, + filename="kw_only_filename", + ) + assert inst.media.filename == "kw_only_filename" + + # Deprecated, but for completeness + inst = InputMediaPhoto( + photo_file, + filename_depr="kw_filename_depr", + ) + assert inst.media.filename == "kw_filename_depr" + + def test_filename_depr_mutually_exclusive_filename(self, photo_file): + with pytest.raises( + ValueError, match="`filename_depr` and `filename` are mutually exclusive" + ): + InputMediaPhoto( + photo_file, + "caption", + "parse_mode", + [], + "filename_depr", + filename="kw_filename", + ) + + with pytest.raises( + ValueError, match="`filename_depr` and `filename` are mutually exclusive" + ): + InputMediaPhoto( + photo_file, + filename_depr="filename_depr", + filename="kw_filename", + ) + + def test_positional_filename_deprecated(self, photo_file): + with pytest.warns( + PTBDeprecationWarning, + match="Positional.*`filename`.*keyword.*`filename_depr`.*deprecated", + ) as record: + InputMediaPhoto( + photo_file, + "caption", + "parse_mode", + [], + "filename_depr", + ) + + assert record[0].category == PTBDeprecationWarning + assert record[0].filename == __file__, "wrong stacklevel!" + + def test_keyword_filename_depr_deprecated(self, photo_file): + with pytest.warns( + PTBDeprecationWarning, + match="Positional.*`filename`.*keyword.*`filename_depr`.*deprecated", + ) as record: + InputMediaPhoto( + photo_file, + filename_depr="filename_depr", + ) + + assert record[0].category == PTBDeprecationWarning + assert record[0].filename == __file__, "wrong stacklevel!" + class InputMediaAnimationTestBase: type_ = "animation" @@ -369,6 +591,10 @@ def test_expected_values(self, input_media_animation): assert input_media_animation.show_caption_above_media == self.show_caption_above_media assert input_media_animation._duration == self.duration + assert isinstance(input_media_animation, InputMedia) + assert isinstance(input_media_animation, InputPollMedia) + assert isinstance(input_media_animation, InputPollOptionMedia) + def test_caption_entities_always_tuple(self): input_media_animation = InputMediaAnimation(self.media) assert input_media_animation.caption_entities == () @@ -433,6 +659,89 @@ def test_with_local_files(self): assert input_media_animation.media == data_file("telegram.mp4").as_uri() assert input_media_animation.thumbnail == data_file("telegram.jpg").as_uri() + def test_effective_filename(self, animation_file): + inst = InputMediaAnimation( + animation_file, + "caption", + "parse_mode", + 24, + 24, + 10, + [], + "pos_filename_depr", + ) + assert inst.media.filename == "pos_filename_depr" + + inst = InputMediaAnimation( + animation_file, + filename="kw_only_filename", + ) + assert inst.media.filename == "kw_only_filename" + + # Deprecated, but for completeness + inst = InputMediaAnimation( + animation_file, + filename_depr="kw_filename_depr", + ) + assert inst.media.filename == "kw_filename_depr" + + def test_filename_depr_mutually_exclusive_filename(self, animation_file): + with pytest.raises( + ValueError, match="`filename_depr` and `filename` are mutually exclusive" + ): + InputMediaAnimation( + animation_file, + "caption", + "parse_mode", + 24, + 24, + 10, + [], + "pos_filename_depr", + filename="kw_filename", + ) + + with pytest.raises( + ValueError, match="`filename_depr` and `filename` are mutually exclusive" + ): + InputMediaAnimation( + animation_file, + filename_depr="filename_depr", + filename="kw_filename", + ) + + def test_positional_filename_deprecated(self, animation_file): + with pytest.warns( + PTBDeprecationWarning, + match="Positional.*`filename`.*keyword.*`filename_depr`.*deprecated", + ) as record: + InputMediaAnimation( + animation_file, + "caption", + "parse_mode", + 24, + 24, + 10, + [], + "pos_filename_depr", + ) + + assert record[0].category == PTBDeprecationWarning + assert record[0].filename == __file__, "wrong stacklevel!" + + def test_keyword_filename_depr_deprecated(self, animation_file): + with pytest.warns( + PTBDeprecationWarning, + match="Positional.*`filename`.*keyword.*`filename_depr`.*deprecated", + ) as record: + InputMediaAnimation( + animation_file, + filename_depr="filename_depr", + ) + + assert record[0].category == PTBDeprecationWarning + assert record[0].filename == __file__, "wrong stacklevel!" + class InputMediaAudioTestBase: type_ = "audio" @@ -463,6 +772,10 @@ def test_expected_values(self, input_media_audio): assert input_media_audio.caption_entities == tuple(self.caption_entities) assert isinstance(input_media_audio.thumbnail, InputFile) + assert isinstance(input_media_audio, InputMedia) + assert isinstance(input_media_audio, InputPollMedia) + assert not isinstance(input_media_audio, InputPollOptionMedia) + def test_caption_entities_always_tuple(self): input_media_audio = InputMediaAudio(self.media) assert input_media_audio.caption_entities == () @@ -526,6 +839,89 @@ def test_with_local_files(self): assert input_media_audio.media == data_file("telegram.mp4").as_uri() assert input_media_audio.thumbnail == data_file("telegram.jpg").as_uri() + def test_effective_filename(self, audio_file): + inst = InputMediaAudio( + audio_file, + "caption", + "parse_mode", + 10, + "performer", + "title", + [], + "pos_filename_depr", + ) + assert inst.media.filename == "pos_filename_depr" + + inst = InputMediaAudio( + audio_file, + filename="kw_only_filename", + ) + assert inst.media.filename == "kw_only_filename" + + # Deprecated, but for completeness + inst = InputMediaAudio( + audio_file, + filename_depr="kw_filename_depr", + ) + assert inst.media.filename == "kw_filename_depr" + + def test_filename_depr_mutually_exclusive_filename(self, audio_file): + with pytest.raises( + ValueError, match="`filename_depr` and `filename` are mutually exclusive" + ): + InputMediaAudio( + audio_file, + "caption", + "parse_mode", + 10, + "performer", + "title", + [], + "pos_filename_depr", + filename="kw_filename", + ) + + with pytest.raises( + ValueError, match="`filename_depr` and `filename` are mutually exclusive" + ): + InputMediaAudio( + audio_file, + filename_depr="filename_depr", + filename="kw_filename", + ) + + def test_positional_filename_deprecated(self, audio_file): + with pytest.warns( + PTBDeprecationWarning, + match="Positional.*`filename`.*keyword.*`filename_depr`.*deprecated", + ) as record: + InputMediaAudio( + audio_file, + "caption", + "parse_mode", + 10, + "performer", + "title", + [], + "pos_filename_depr", + ) + + assert record[0].category == PTBDeprecationWarning + assert record[0].filename == __file__, "wrong stacklevel!" + + def test_keyword_filename_depr_deprecated(self, audio_file): + with pytest.warns( + PTBDeprecationWarning, + match="Positional.*`filename`.*keyword.*`filename_depr`.*deprecated", + ) as record: + InputMediaAudio( + audio_file, + filename_depr="filename_depr", + ) + + assert record[0].category == PTBDeprecationWarning + assert record[0].filename == __file__, "wrong stacklevel!" + class InputMediaDocumentTestBase: type_ = "document" @@ -536,6 +932,136 @@ class InputMediaDocumentTestBase: disable_content_type_detection = True +class InputMediaLocationTestBase: + type_ = "location" + latitude = 1.0 + longitude = 2.0 + horizontal_accuracy = 10.0 + + +class TestInputMediaLocationWithoutRequest(InputMediaLocationTestBase): + def test_slot_behaviour(self, input_media_location): + inst = input_media_location + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_expected_values(self, input_media_location): + assert input_media_location.type == self.type_ + assert input_media_location.latitude == self.latitude + assert input_media_location.longitude == self.longitude + assert input_media_location.horizontal_accuracy == self.horizontal_accuracy + + assert isinstance(input_media_location, InputPollMedia) + assert isinstance(input_media_location, InputPollOptionMedia) + assert not isinstance(input_media_location, InputMedia) + + def test_to_dict(self, input_media_location): + input_media_location_dict = input_media_location.to_dict() + assert input_media_location_dict["type"] == input_media_location.type + assert input_media_location_dict["latitude"] == input_media_location.latitude + assert input_media_location_dict["longitude"] == input_media_location.longitude + assert ( + input_media_location_dict["horizontal_accuracy"] + == input_media_location.horizontal_accuracy + ) + + +class InputMediaVenueTestBase: + type_ = "venue" + latitude = 1.0 + longitude = 2.0 + title = "title" + address = "address" + foursquare_id = "foursquare_id" + foursquare_type = "food/icecream" + google_place_id = "google_place_id" + google_place_type = "restaurant" + + +class TestInputMediaVenueWithoutRequest(InputMediaVenueTestBase): + def test_slot_behaviour(self, input_media_venue): + inst = input_media_venue + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_expected_values(self, input_media_venue): + assert input_media_venue.type == self.type_ + assert input_media_venue.latitude == self.latitude + assert input_media_venue.longitude == self.longitude + assert input_media_venue.title == self.title + assert input_media_venue.address == self.address + assert input_media_venue.foursquare_id == self.foursquare_id + assert input_media_venue.foursquare_type == self.foursquare_type + assert input_media_venue.google_place_id == self.google_place_id + assert input_media_venue.google_place_type == self.google_place_type + + assert isinstance(input_media_venue, InputPollMedia) + assert isinstance(input_media_venue, InputPollOptionMedia) + assert not isinstance(input_media_venue, InputMedia) + + def test_to_dict(self, input_media_venue): + input_media_venue_dict = input_media_venue.to_dict() + assert input_media_venue_dict["type"] == input_media_venue.type + assert input_media_venue_dict["latitude"] == input_media_venue.latitude + assert input_media_venue_dict["longitude"] == input_media_venue.longitude + assert input_media_venue_dict["title"] == input_media_venue.title + assert input_media_venue_dict["address"] == input_media_venue.address + assert input_media_venue_dict["foursquare_id"] == input_media_venue.foursquare_id + assert input_media_venue_dict["foursquare_type"] == input_media_venue.foursquare_type + assert input_media_venue_dict["google_place_id"] == input_media_venue.google_place_id + assert input_media_venue_dict["google_place_type"] == input_media_venue.google_place_type + + +class InputMediaStickerTestBase: + type_ = "sticker" + media = "NOTAREALFILEID" + emoji = "💪" + + +class TestInputMediaStickerWithoutRequest(InputMediaStickerTestBase): + def test_slot_behaviour(self, input_media_sticker): + inst = input_media_sticker + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_expected_values(self, input_media_sticker): + assert input_media_sticker.type == self.type_ + assert input_media_sticker.media == self.media + assert input_media_sticker.emoji == self.emoji + + assert isinstance(input_media_sticker, InputPollOptionMedia) + assert not isinstance(input_media_sticker, InputPollMedia) + assert not isinstance(input_media_sticker, InputMedia) + + def test_to_dict(self, input_media_sticker): + input_media_sticker_dict = input_media_sticker.to_dict() + assert input_media_sticker_dict["type"] == input_media_sticker.type + assert input_media_sticker_dict["media"] == input_media_sticker.media + assert input_media_sticker_dict["emoji"] == input_media_sticker.emoji + + def test_with_sticker(self, sticker): + input_media_sticker = InputMediaSticker(sticker, emoji=self.emoji) + assert input_media_sticker.type == self.type_ + assert input_media_sticker.media == sticker.file_id + assert input_media_sticker.emoji == self.emoji + + def test_with_sticker_file(self, sticker_file): + input_media_sticker = InputMediaSticker(sticker_file, emoji=self.emoji) + assert input_media_sticker.type == self.type_ + assert isinstance(input_media_sticker.media, InputFile) + assert input_media_sticker.emoji == self.emoji + + def test_with_local_files(self): + input_media_sticker = InputMediaSticker( + data_file("telegram_sticker.png"), emoji=self.emoji + ) + assert input_media_sticker.media == data_file("telegram_sticker.png").as_uri() + assert input_media_sticker.emoji == self.emoji + + class TestInputMediaDocumentWithoutRequest(InputMediaDocumentTestBase): def test_slot_behaviour(self, input_media_document): inst = input_media_document @@ -555,6 +1081,10 @@ def test_expected_values(self, input_media_document): ) assert isinstance(input_media_document.thumbnail, InputFile) + assert isinstance(input_media_document, InputMedia) + assert isinstance(input_media_document, InputPollMedia) + assert not isinstance(input_media_document, InputPollOptionMedia) + def test_caption_entities_always_tuple(self): input_media_document = InputMediaDocument(self.media) assert input_media_document.caption_entities == () @@ -594,6 +1124,83 @@ def test_with_local_files(self): assert input_media_document.media == data_file("telegram.mp4").as_uri() assert input_media_document.thumbnail == data_file("telegram.jpg").as_uri() + def test_effective_filename(self, document_file): + inst = InputMediaDocument( + document_file, + "caption", + "parse_mode", + True, + [], + "pos_filename_depr", + ) + assert inst.media.filename == "pos_filename_depr" + + inst = InputMediaDocument( + document_file, + filename="kw_only_filename", + ) + assert inst.media.filename == "kw_only_filename" + + # Deprecated, but for completeness + inst = InputMediaDocument( + document_file, + filename_depr="kw_filename_depr", + ) + assert inst.media.filename == "kw_filename_depr" + + def test_filename_depr_mutually_exclusive_filename(self, document_file): + with pytest.raises( + ValueError, match="`filename_depr` and `filename` are mutually exclusive" + ): + InputMediaDocument( + document_file, + "caption", + "parse_mode", + True, + [], + "pos_filename_depr", + filename="kw_filename", + ) + + with pytest.raises( + ValueError, match="`filename_depr` and `filename` are mutually exclusive" + ): + InputMediaDocument( + document_file, + filename_depr="filename_depr", + filename="kw_filename", + ) + + def test_positional_filename_deprecated(self, document_file): + with pytest.warns( + PTBDeprecationWarning, + match="Positional.*`filename`.*keyword.*`filename_depr`.*deprecated", + ) as record: + InputMediaDocument( + document_file, + "caption", + "parse_mode", + True, + [], + "pos_filename_depr", + ) + + assert record[0].category == PTBDeprecationWarning + assert record[0].filename == __file__, "wrong stacklevel!" + + def test_keyword_filename_depr_deprecated(self, document_file): + with pytest.warns( + PTBDeprecationWarning, + match="Positional.*`filename`.*keyword.*`filename_depr`.*deprecated", + ) as record: + InputMediaDocument( + document_file, + filename_depr="filename_depr", + ) + + assert record[0].category == PTBDeprecationWarning + assert record[0].filename == __file__, "wrong stacklevel!" + class TestInputPaidMediaPhotoWithoutRequest(InputMediaPhotoTestBase): def test_slot_behaviour(self, input_paid_media_photo): @@ -612,13 +1219,13 @@ def test_to_dict(self, input_paid_media_photo): assert input_paid_media_photo_dict["media"] == input_paid_media_photo.media def test_with_photo(self, photo): - # fixture found in test_photo + # fixture found in conftest.py input_paid_media_photo = InputPaidMediaPhoto(photo) assert input_paid_media_photo.type == self.type_ assert input_paid_media_photo.media == photo.file_id def test_with_photo_file(self, photo_file): - # fixture found in test_photo + # fixture found in conftest.py input_paid_media_photo = InputPaidMediaPhoto(photo_file) assert input_paid_media_photo.type == self.type_ assert isinstance(input_paid_media_photo.media, InputFile) @@ -628,6 +1235,76 @@ def test_with_local_files(self): assert input_paid_media_photo.media == data_file("telegram.jpg").as_uri() +class InputMediaLivePhotoTestBase: + type_ = "live_photo" + media = "NOTAREALFILEID" + photo = "NOTAREALFILEID" + caption = "My Caption" + parse_mode = "Markdown" + caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)] + show_caption_above_media = True + has_spoiler = True + + +class TestInputMediaLivePhotoWithoutRequest(InputMediaLivePhotoTestBase): + def test_slot_behaviour(self, input_media_live_photo): + inst = input_media_live_photo + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_expected_values(self, input_media_live_photo): + assert input_media_live_photo.type == self.type_ + assert input_media_live_photo.media == self.media + assert input_media_live_photo.photo == self.photo + assert input_media_live_photo.caption == self.caption + assert input_media_live_photo.parse_mode == self.parse_mode + assert input_media_live_photo.caption_entities == tuple(self.caption_entities) + assert input_media_live_photo.show_caption_above_media == self.show_caption_above_media + assert input_media_live_photo.has_spoiler == self.has_spoiler + + def test_caption_entities_always_tuple(self): + input_media_live_photo = InputMediaLivePhoto(self.media, self.photo) + assert input_media_live_photo.caption_entities == () + + def test_to_dict(self, input_media_live_photo): + input_media_live_photo_dict = input_media_live_photo.to_dict() + assert input_media_live_photo_dict["type"] == input_media_live_photo.type + assert input_media_live_photo_dict["media"] == input_media_live_photo.media + assert input_media_live_photo_dict["photo"] == input_media_live_photo.photo + assert input_media_live_photo_dict["caption"] == input_media_live_photo.caption + assert input_media_live_photo_dict["parse_mode"] == input_media_live_photo.parse_mode + assert input_media_live_photo_dict["caption_entities"] == [ + ce.to_dict() for ce in input_media_live_photo.caption_entities + ] + assert ( + input_media_live_photo_dict["show_caption_above_media"] + == input_media_live_photo.show_caption_above_media + ) + assert input_media_live_photo_dict["has_spoiler"] == input_media_live_photo.has_spoiler + + def test_with_photo_and_video(self, video, photo): + # fixtures found in conftest.py + input_media_live_photo = InputMediaLivePhoto(video, photo) + assert input_media_live_photo.type == self.type_ + assert input_media_live_photo.media == video.file_id + assert input_media_live_photo.photo == photo.file_id + + def test_with_photo_and_video_files(self, video_file, photo_file): + # fixture found in conftest.py + input_media_live_photo = InputMediaLivePhoto(video_file, photo_file) + assert input_media_live_photo.type == self.type_ + assert isinstance(input_media_live_photo.media, InputFile) + assert isinstance(input_media_live_photo.photo, InputFile) + + def test_with_local_files(self): + input_media_live_photo = InputMediaLivePhoto( + media=data_file("telegram.mp4"), photo=data_file("telegram.jpg") + ) + assert input_media_live_photo.media == data_file("telegram.mp4").as_uri() + assert input_media_live_photo.photo == data_file("telegram.jpg").as_uri() + + class TestInputPaidMediaVideoWithoutRequest(InputMediaVideoTestBase): def test_slot_behaviour(self, input_paid_media_video): inst = input_paid_media_video @@ -711,6 +1388,46 @@ def test_with_local_files(self): assert input_paid_media_video.cover == data_file("telegram.jpg").as_uri() +class TestInputPaidMediaLivePhotoWithoutRequest(InputMediaLivePhotoTestBase): + def test_slot_behaviour(self, input_paid_media_live_photo): + inst = input_paid_media_live_photo + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_expected_values(self, input_paid_media_live_photo): + assert input_paid_media_live_photo.type == self.type_ + assert input_paid_media_live_photo.media == self.media + assert input_paid_media_live_photo.photo == self.photo + + def test_to_dict(self, input_paid_media_live_photo): + input_paid_media_live_photo_dict = input_paid_media_live_photo.to_dict() + assert input_paid_media_live_photo_dict["type"] == input_paid_media_live_photo.type + assert input_paid_media_live_photo_dict["media"] == input_paid_media_live_photo.media + assert input_paid_media_live_photo_dict["photo"] == input_paid_media_live_photo.photo + + def test_with_photo(self, video, photo): + # fixtures found in conftest.py + input_paid_media_live_photo = InputPaidMediaLivePhoto(video, photo) + assert input_paid_media_live_photo.type == self.type_ + assert input_paid_media_live_photo.media == video.file_id + assert input_paid_media_live_photo.photo == photo.file_id + + def test_with_photo_file(self, photo_file): + # fixture found in conftest.py + input_paid_media_live_photo = InputPaidMediaLivePhoto(photo_file, photo_file) + assert input_paid_media_live_photo.type == self.type_ + assert isinstance(input_paid_media_live_photo.media, InputFile) + assert isinstance(input_paid_media_live_photo.photo, InputFile) + + def test_with_local_files(self): + input_paid_media_live_photo = InputPaidMediaLivePhoto( + media=data_file("telegram.mp4"), photo=data_file("telegram.jpg") + ) + assert input_paid_media_live_photo.media == data_file("telegram.mp4").as_uri() + assert input_paid_media_live_photo.photo == data_file("telegram.jpg").as_uri() + + @pytest.fixture(scope="module") def media_group(photo, thumb): return [ @@ -1155,7 +1872,9 @@ async def test_send_media_group_default_parse_mode( @pytest.mark.parametrize( "default_bot", [{"parse_mode": ParseMode.HTML}], indirect=True, ids=["HTML-Bot"] ) - @pytest.mark.parametrize("media_type", ["animation", "document", "audio", "photo", "video"]) + @pytest.mark.parametrize( + "media_type", ["animation", "document", "audio", "live_photo", "photo", "video"] + ) async def test_edit_message_media_default_parse_mode( self, chat_id, @@ -1194,6 +1913,8 @@ def build_media(parse_mode, med_type): return InputMediaPhoto(photo, **kwargs) if med_type == "video": return InputMediaVideo(video, **kwargs) + if med_type == "live_photo": + return InputMediaLivePhoto(video, photo, **kwargs) return None message = await default_bot.send_photo(chat_id, photo) diff --git a/tests/_files/test_livephoto.py b/tests/_files/test_livephoto.py new file mode 100644 index 00000000000..0dba659033e --- /dev/null +++ b/tests/_files/test_livephoto.py @@ -0,0 +1,389 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2026 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""This module contains an object that tests a Telegram LivePhoto.""" + +import asyncio +import datetime as dtm +import os +from pathlib import Path + +import pytest + +from telegram import ( + InputFile, + LivePhoto, + MessageEntity, + PhotoSize, + ReplyParameters, + Voice, +) +from telegram.constants import ParseMode +from telegram.error import BadRequest, TelegramError +from telegram.helpers import escape_markdown +from telegram.request import RequestData +from tests.auxil.build_messages import make_message +from tests.auxil.files import data_file +from tests.auxil.slots import mro_slots + + +@pytest.fixture(scope="module") +def live_photo(): + return LivePhoto( + file_id=LivePhotoTestBase.file_id, + file_unique_id=LivePhotoTestBase.file_unique_id, + width=LivePhotoTestBase.width, + height=LivePhotoTestBase.height, + duration=LivePhotoTestBase.duration, + photo=LivePhotoTestBase.photo, + mime_type=LivePhotoTestBase.mime_type, + file_size=LivePhotoTestBase.file_size, + ) + + +class LivePhotoTestBase: + caption = "LivePhotoTest - *Caption*" + width = 360 + height = 640 + duration = dtm.timedelta(seconds=5) + file_size = 326534 + mime_type = "video/mp4" + photo = (PhotoSize("file_id", "unique_id", 640, 360, file_size=0),) + file_id = "5a3128a4d2a04750b5b58397f3b5e812" + file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e" + + +class TestLivePhotoWithoutRequest(LivePhotoTestBase): + def test_slot_behaviour(self, live_photo): + for attr in live_photo.__slots__: + assert getattr(live_photo, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(live_photo)) == len(set(mro_slots(live_photo))), "duplicate slot" + + def test_de_json(self, offline_bot): + json_dict = { + "file_id": self.file_id, + "file_unique_id": self.file_unique_id, + "width": self.width, + "height": self.height, + "duration": int(self.duration.total_seconds()), + "mime_type": self.mime_type, + "file_size": self.file_size, + "photo": [photo_size.to_dict() for photo_size in self.photo], + } + json_live_photo = LivePhoto.de_json(json_dict, offline_bot) + assert json_live_photo.api_kwargs == {} + + assert json_live_photo.file_id == self.file_id + assert json_live_photo.file_unique_id == self.file_unique_id + assert json_live_photo.width == self.width + assert json_live_photo.height == self.height + assert json_live_photo.duration == self.duration + assert json_live_photo.mime_type == self.mime_type + assert json_live_photo.file_size == self.file_size + assert json_live_photo.photo == self.photo + + def test_to_dict(self, live_photo): + live_photo_dict = live_photo.to_dict() + + assert isinstance(live_photo_dict, dict) + assert live_photo_dict["file_id"] == live_photo.file_id + assert live_photo_dict["file_unique_id"] == live_photo.file_unique_id + assert live_photo_dict["width"] == live_photo.width + assert live_photo_dict["height"] == live_photo.height + assert live_photo_dict["duration"] == int(self.duration.total_seconds()) + assert isinstance(live_photo_dict["duration"], int) + assert live_photo_dict["mime_type"] == live_photo.mime_type + assert live_photo_dict["file_size"] == live_photo.file_size + assert live_photo_dict["photo"] == [p.to_dict() for p in self.photo] + + def test_equality(self, live_photo): + a = LivePhoto( + live_photo.file_id, live_photo.file_unique_id, self.width, self.height, self.duration + ) + b = LivePhoto("", live_photo.file_unique_id, self.width, self.height, self.duration) + c = LivePhoto(live_photo.file_id, live_photo.file_unique_id, 0, 0, 0) + d = LivePhoto("", "", self.width, self.height, self.duration) + e = Voice(live_photo.file_id, live_photo.file_unique_id, self.duration) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a == c + assert hash(a) == hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) + + async def test_send_with_live_photo(self, monkeypatch, offline_bot, chat_id, live_photo): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + data = request_data.parameters + return ( + data["live_photo"] == live_photo.file_id + and data["photo"] == live_photo.photo[0].file_id + ) + + monkeypatch.setattr(offline_bot.request, "post", make_assertion) + + assert await offline_bot.send_live_photo( + chat_id=chat_id, + live_photo=live_photo, + photo=live_photo.photo[0], + ) + + @pytest.mark.parametrize( + ("default_bot", "custom"), + [ + ({"parse_mode": ParseMode.HTML}, None), + ({"parse_mode": ParseMode.HTML}, ParseMode.MARKDOWN_V2), + ({"parse_mode": None}, ParseMode.MARKDOWN_V2), + ], + indirect=["default_bot"], + ) + async def test_send_live_photo_default_quote_parse_mode( + self, default_bot, chat_id, live_photo, custom, monkeypatch + ): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + assert request_data.parameters["reply_parameters"].get("quote_parse_mode") == ( + custom or default_bot.defaults.quote_parse_mode + ) + return make_message("dummy reply").to_dict() + + kwargs = {"message_id": "1"} + if custom is not None: + kwargs["quote_parse_mode"] = custom + + monkeypatch.setattr(default_bot.request, "post", make_assertion) + await default_bot.send_live_photo( + chat_id=chat_id, + live_photo=live_photo, + photo=live_photo.photo[0], + reply_parameters=ReplyParameters(**kwargs), + ) + + @pytest.mark.parametrize("local_mode", [True, False]) + async def test_send_live_photo( + self, dummy_message_dict, monkeypatch, offline_bot, chat_id, local_mode + ): + try: + offline_bot._local_mode = local_mode + # For just test that the correct paths are passed as we have no local Bot API set up + test_flag = False + photo = data_file("telegram.jpg") + expected_photo = photo.as_uri() + + live_photo = data_file("telegram.mp4") + expected_live_photo = live_photo.as_uri() + + async def make_assertion(_, data, *args, **kwargs): + nonlocal test_flag + if local_mode: + test_flag = ( + data.get("live_photo") == expected_live_photo + and data.get("photo") == expected_photo + ) + else: + test_flag = isinstance(data.get("live_photo"), InputFile) and isinstance( + data.get("photo"), InputFile + ) + return dummy_message_dict + + monkeypatch.setattr(offline_bot, "_post", make_assertion) + await offline_bot.send_live_photo(chat_id, live_photo=live_photo, photo=photo) + assert test_flag + finally: + offline_bot._local_mode = False + + +class TestLivePhotoWithRequest(LivePhotoTestBase): + async def test_error_send_empty_file(self, bot, chat_id): + with Path(os.devnull).open("rb") as f, pytest.raises(TelegramError): + await bot.send_live_photo(chat_id=chat_id, live_photo=f, photo=f) + + async def test_error_send_empty_file_id(self, bot, chat_id): + with pytest.raises(TelegramError): + await bot.send_live_photo(chat_id=chat_id, live_photo="", photo="") + + async def test_get_and_download(self, bot, real_live_photo, tmp_file): + new_file = await bot.get_file(real_live_photo.file_id) + + assert new_file.file_size == real_live_photo.file_size + assert new_file.file_unique_id == real_live_photo.file_unique_id + assert new_file.file_path.startswith("https://") + + await new_file.download_to_drive(tmp_file) + + assert tmp_file.is_file() + + async def test_send_resend(self, bot, chat_id, real_live_photo, photo_file): + message = await bot.send_live_photo( + chat_id=chat_id, live_photo=real_live_photo.file_id, photo=photo_file + ) + assert message.live_photo == real_live_photo + + async def test_send_all_args(self, bot, chat_id, video_file, live_photo, photo_file): + message = await bot.send_live_photo( + chat_id, + live_photo=video_file, + photo=photo_file, + caption=self.caption, + disable_notification=False, + protect_content=True, + filename="telegram_custom.png", + parse_mode="Markdown", + ) + + assert isinstance(message.live_photo, LivePhoto) + assert isinstance(message.live_photo.file_id, str) + assert message.live_photo.file_id + assert isinstance(message.live_photo.file_unique_id, str) + assert message.live_photo.file_unique_id + assert message.live_photo.photo + assert isinstance(message.live_photo.photo[0], PhotoSize) + assert message.live_photo.mime_type == live_photo.mime_type + assert message.live_photo.file_size == live_photo.file_size + assert message.caption == self.caption.replace("*", "") + assert message.has_protected_content + + @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) + async def test_send_live_photo_default_protect_content( + self, + chat_id, + default_bot, + real_live_photo, + ): + tasks = asyncio.gather( + default_bot.send_live_photo( + chat_id, photo=real_live_photo.photo[0], live_photo=real_live_photo + ), + default_bot.send_live_photo( + chat_id, + photo=real_live_photo.photo[0], + live_photo=real_live_photo, + protect_content=False, + ), + ) + protected, unprotected = await tasks + assert protected.has_protected_content + assert not unprotected.has_protected_content + + async def test_send_live_photo_caption_entities(self, bot, chat_id, video_file, photo_file): + test_string = "Italic Bold Code" + entities = [ + MessageEntity(MessageEntity.ITALIC, 0, 6), + MessageEntity(MessageEntity.ITALIC, 7, 4), + MessageEntity(MessageEntity.ITALIC, 12, 4), + ] + message = await bot.send_live_photo( + chat_id, + photo=photo_file, + live_photo=video_file, + caption=test_string, + caption_entities=entities, + ) + + assert message.caption == test_string + assert message.caption_entities == tuple(entities) + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_live_photo_default_parse_mode_1( + self, default_bot, chat_id, video_file, photo_file + ): + test_string = "Italic Bold Code" + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_live_photo( + chat_id, photo=photo_file, live_photo=video_file, caption=test_markdown_string + ) + assert message.caption_markdown == test_markdown_string + assert message.caption == test_string + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_live_photo_default_parse_mode_2( + self, default_bot, chat_id, video_file, photo_file + ): + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_live_photo( + chat_id, + photo=photo_file, + live_photo=video_file, + caption=test_markdown_string, + parse_mode=None, + ) + assert message.caption == test_markdown_string + assert message.caption_markdown == escape_markdown(test_markdown_string) + + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) + async def test_send_live_photo_default_parse_mode_3( + self, default_bot, chat_id, video_file, photo_file + ): + test_markdown_string = "_Italic_ *Bold* `Code`" + + message = await default_bot.send_live_photo( + chat_id, + photo=photo_file, + live_photo=video_file, + caption=test_markdown_string, + parse_mode="HTML", + ) + assert message.caption == test_markdown_string + assert message.caption_markdown == escape_markdown(test_markdown_string) + + @pytest.mark.parametrize( + ("default_bot", "custom"), + [ + ({"allow_sending_without_reply": True}, None), + ({"allow_sending_without_reply": False}, None), + ({"allow_sending_without_reply": False}, True), + ], + indirect=["default_bot"], + ) + async def test_send_live_photo_default_allow_sending_without_reply( + self, default_bot, chat_id, video_file, photo_file, custom + ): + reply_to_message = await default_bot.send_message(chat_id, "test") + await reply_to_message.delete() + if custom is not None: + message = await default_bot.send_live_photo( + chat_id, + photo=photo_file, + live_photo=video_file, + allow_sending_without_reply=custom, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + elif default_bot.defaults.allow_sending_without_reply: + message = await default_bot.send_live_photo( + chat_id, + photo=photo_file, + live_photo=video_file, + reply_to_message_id=reply_to_message.message_id, + ) + assert message.reply_to_message is None + else: + with pytest.raises(BadRequest, match="Message to be replied not found"): + await default_bot.send_live_photo( + chat_id, + photo=photo_file, + live_photo=video_file, + reply_to_message_id=reply_to_message.message_id, + ) diff --git a/tests/auxil/bot_method_checks.py b/tests/auxil/bot_method_checks.py index 6714e65655a..8624b45a739 100644 --- a/tests/auxil/bot_method_checks.py +++ b/tests/auxil/bot_method_checks.py @@ -45,7 +45,7 @@ ) from telegram._utils.datetime import to_timestamp from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue -from telegram.constants import InputMediaType +from telegram.constants import BaseInputMediaType from telegram.ext import Defaults, ExtBot from telegram.request import RequestData from tests.auxil.dummy_objects import get_dummy_object_json_dict @@ -512,7 +512,7 @@ def check_input_media(m: dict): media = data.pop("media", None) paid_media = media and data.pop("star_count", None) if media and not paid_media: - if isinstance(media, dict) and isinstance(media.get("type", None), InputMediaType): + if isinstance(media, dict) and isinstance(media.get("type", None), BaseInputMediaType): check_input_media(media) else: for m in media: diff --git a/tests/auxil/dummy_objects.py b/tests/auxil/dummy_objects.py index c0ff0ce0cf5..41c938c70d0 100644 --- a/tests/auxil/dummy_objects.py +++ b/tests/auxil/dummy_objects.py @@ -4,6 +4,7 @@ from telegram import ( AcceptedGiftTypes, + BotAccessSettings, BotCommand, BotDescription, BotName, @@ -30,6 +31,7 @@ PollOption, PreparedInlineMessage, PreparedKeyboardButton, + SentGuestMessage, SentWebAppMessage, StarAmount, StarTransaction, @@ -63,6 +65,7 @@ _PREPARED_DUMMY_OBJECTS: dict[str, object] = { "bool": True, + "BotAccessSettings": BotAccessSettings(is_access_restricted=True, added_users=[_DUMMY_USER]), "BotCommand": BotCommand(command="dummy_command", description="dummy_description"), "BotDescription": BotDescription(description="dummy_description"), "BotName": BotName(name="dummy_name"), @@ -127,15 +130,18 @@ "Poll": Poll( id="dummy_id", question="dummy_question", - options=[PollOption(text="dummy_text", voter_count=1)], + options=[PollOption(text="dummy_text", voter_count=1, persistent_id="persistent_id")], is_closed=False, is_anonymous=False, total_voter_count=1, type="dummy_type", allows_multiple_answers=False, + allows_revoting=True, + members_only=True, ), "PreparedKeyboardButton": PreparedKeyboardButton(id=1234), "PreparedInlineMessage": PreparedInlineMessage(id="dummy_id", expiration_date=_DUMMY_DATE), + "SentGuestMessage": SentGuestMessage(inline_message_id="dummy_inline_message_id"), "SentWebAppMessage": SentWebAppMessage(inline_message_id="dummy_inline_message_id"), "StarAmount": StarAmount(amount=100, nanostar_amount=356), "StarTransactions": StarTransactions( diff --git a/tests/ext/test_filters.py b/tests/ext/test_filters.py index e68fb0dbc09..9271649d635 100644 --- a/tests/ext/test_filters.py +++ b/tests/ext/test_filters.py @@ -945,6 +945,11 @@ def test_filters_location(self, update): update.message.location = "test" assert filters.LOCATION.check_update(update) + def test_filters_live_photo(self, update): + assert not filters.LIVE_PHOTO.check_update(update) + update.message.live_photo = "test" + assert filters.LIVE_PHOTO.check_update(update) + def test_filters_venue(self, update): assert not filters.VENUE.check_update(update) update.message.venue = "test" @@ -2471,6 +2476,7 @@ def test_update_type_message(self, update): assert not filters.UpdateType.BUSINESS_MESSAGES.check_update(update) assert not filters.UpdateType.BUSINESS_MESSAGE.check_update(update) assert not filters.UpdateType.EDITED_BUSINESS_MESSAGE.check_update(update) + assert not filters.UpdateType.GUEST_MESSAGE.check_update(update) def test_update_type_edited_message(self, update): update.edited_message, update.message = update.message, update.edited_message @@ -2484,6 +2490,7 @@ def test_update_type_edited_message(self, update): assert not filters.UpdateType.BUSINESS_MESSAGES.check_update(update) assert not filters.UpdateType.BUSINESS_MESSAGE.check_update(update) assert not filters.UpdateType.EDITED_BUSINESS_MESSAGE.check_update(update) + assert not filters.UpdateType.GUEST_MESSAGE.check_update(update) def test_update_type_channel_post(self, update): update.channel_post, update.message = update.message, update.edited_message @@ -2497,6 +2504,7 @@ def test_update_type_channel_post(self, update): assert not filters.UpdateType.BUSINESS_MESSAGES.check_update(update) assert not filters.UpdateType.BUSINESS_MESSAGE.check_update(update) assert not filters.UpdateType.EDITED_BUSINESS_MESSAGE.check_update(update) + assert not filters.UpdateType.GUEST_MESSAGE.check_update(update) def test_update_type_edited_channel_post(self, update): update.edited_channel_post, update.message = update.message, update.edited_message @@ -2510,6 +2518,7 @@ def test_update_type_edited_channel_post(self, update): assert not filters.UpdateType.BUSINESS_MESSAGES.check_update(update) assert not filters.UpdateType.BUSINESS_MESSAGE.check_update(update) assert not filters.UpdateType.EDITED_BUSINESS_MESSAGE.check_update(update) + assert not filters.UpdateType.GUEST_MESSAGE.check_update(update) def test_update_type_business_message(self, update): update.business_message, update.message = update.message, update.edited_message @@ -2523,6 +2532,7 @@ def test_update_type_business_message(self, update): assert filters.UpdateType.BUSINESS_MESSAGES.check_update(update) assert filters.UpdateType.BUSINESS_MESSAGE.check_update(update) assert not filters.UpdateType.EDITED_BUSINESS_MESSAGE.check_update(update) + assert not filters.UpdateType.GUEST_MESSAGE.check_update(update) def test_update_type_edited_business_message(self, update): update.edited_business_message, update.message = update.message, update.edited_message @@ -2536,6 +2546,21 @@ def test_update_type_edited_business_message(self, update): assert filters.UpdateType.BUSINESS_MESSAGES.check_update(update) assert not filters.UpdateType.BUSINESS_MESSAGE.check_update(update) assert filters.UpdateType.EDITED_BUSINESS_MESSAGE.check_update(update) + assert not filters.UpdateType.GUEST_MESSAGE.check_update(update) + + def test_update_type_guest_message(self, update): + update.guest_message, update.message = update.message, update.edited_message + assert not filters.UpdateType.MESSAGE.check_update(update) + assert not filters.UpdateType.EDITED_MESSAGE.check_update(update) + assert not filters.UpdateType.MESSAGES.check_update(update) + assert not filters.UpdateType.CHANNEL_POST.check_update(update) + assert not filters.UpdateType.EDITED_CHANNEL_POST.check_update(update) + assert not filters.UpdateType.CHANNEL_POSTS.check_update(update) + assert not filters.UpdateType.EDITED.check_update(update) + assert not filters.UpdateType.BUSINESS_MESSAGES.check_update(update) + assert not filters.UpdateType.BUSINESS_MESSAGE.check_update(update) + assert not filters.UpdateType.EDITED_BUSINESS_MESSAGE.check_update(update) + assert filters.UpdateType.GUEST_MESSAGE.check_update(update) def test_merged_short_circuit_and(self, update, base_class): update.message.text = "/test" diff --git a/tests/ext/test_pollanswerhandler.py b/tests/ext/test_pollanswerhandler.py index 8900f466486..b22e9f0e0d9 100644 --- a/tests/ext/test_pollanswerhandler.py +++ b/tests/ext/test_pollanswerhandler.py @@ -69,7 +69,16 @@ def false_update(request): @pytest.fixture def poll_answer(bot): - return Update(0, poll_answer=PollAnswer(1, [0, 1], User(2, "test user", False), Chat(1, ""))) + return Update( + 0, + poll_answer=PollAnswer( + poll_id=1, + option_ids=[0, 1], + option_persistent_ids=["0", "1"], + user=User(2, "test user", False), + voter_chat=Chat(1, ""), + ), + ) class TestPollAnswerHandler: diff --git a/tests/ext/test_pollhandler.py b/tests/ext/test_pollhandler.py index cb2864822fe..c85da77eec3 100644 --- a/tests/ext/test_pollhandler.py +++ b/tests/ext/test_pollhandler.py @@ -73,14 +73,19 @@ def poll(bot): return Update( 0, poll=Poll( - 1, - "question", - [PollOption("1", 0), PollOption("2", 0)], - 0, - False, - False, - Poll.REGULAR, - True, + id=1, + question="question", + options=[ + PollOption(text="1", voter_count=0, persistent_id="1"), + PollOption(text="2", voter_count=0, persistent_id="2"), + ], + total_voter_count=0, + is_closed=False, + is_anonymous=False, + type=Poll.REGULAR, + allows_revoting=True, + members_only=True, + allows_multiple_answers=True, ), ) diff --git a/tests/test_bot.py b/tests/test_bot.py index 2fd77550ecb..5f9fb22a3eb 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -33,6 +33,7 @@ from telegram import ( Bot, + BotAccessSettings, BotCommand, BotCommandScopeChat, BotDescription, @@ -53,6 +54,7 @@ InlineQueryResultVoice, InputFile, InputMediaDocument, + InputMediaLocation, InputMediaPhoto, InputMessageContent, InputPollOption, @@ -76,6 +78,7 @@ ReactionTypeCustomEmoji, ReactionTypeEmoji, ReplyParameters, + SentGuestMessage, SentWebAppMessage, ShippingOption, StarTransaction, @@ -860,6 +863,150 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): == copied_result.input_message_content.parse_mode ) + async def test_answer_guest_query(self, offline_bot, raw_bot, monkeypatch): + params = False + + # For now just test that our internals pass the correct data + + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + nonlocal params + params = request_data.parameters == { + "guest_query_id": "12345", + "result": { + "title": "title", + "input_message_content": { + "message_text": "text", + }, + "type": InlineQueryResultType.ARTICLE, + "id": "1", + }, + } + return SentGuestMessage("321").to_dict() + + result = InlineQueryResultArticle("1", "title", InputTextMessageContent("text")) + copied_result = copy.copy(result) + + ext_bot = offline_bot + for bot_type in (ext_bot, raw_bot): + monkeypatch.setattr(bot_type.request, "post", make_assertion) + guest_msg = await bot_type.answer_guest_query("12345", result) + assert params, "something went wrong with passing arguments to the request" + assert isinstance(guest_msg, SentGuestMessage) + assert guest_msg.inline_message_id == "321" + + # make sure that the results were not edited in-place + assert result == copied_result + assert ( + result.input_message_content.parse_mode + == copied_result.input_message_content.parse_mode + ) + + @pytest.mark.parametrize( + "default_bot", + [{"parse_mode": "Markdown", "link_preview_options": LinkPreviewOptions(is_disabled=True)}], + indirect=True, + ) + @pytest.mark.parametrize( + ("ilq_result", "expected_params"), + [ + ( + InlineQueryResultArticle("1", "title", InputTextMessageContent("text")), + { + "guest_query_id": "12345", + "result": { + "title": "title", + "input_message_content": { + "message_text": "text", + "parse_mode": "Markdown", + "link_preview_options": { + "is_disabled": True, + }, + }, + "type": InlineQueryResultType.ARTICLE, + "id": "1", + }, + }, + ), + ( + InlineQueryResultArticle( + "1", + "title", + InputTextMessageContent( + "text", parse_mode="HTML", disable_web_page_preview=False + ), + ), + { + "guest_query_id": "12345", + "result": { + "title": "title", + "input_message_content": { + "message_text": "text", + "parse_mode": "HTML", + "link_preview_options": { + "is_disabled": False, + }, + }, + "type": InlineQueryResultType.ARTICLE, + "id": "1", + }, + }, + ), + ( + InlineQueryResultArticle( + "1", + "title", + InputTextMessageContent( + "text", parse_mode=None, disable_web_page_preview="False" + ), + ), + { + "guest_query_id": "12345", + "result": { + "title": "title", + "input_message_content": { + "message_text": "text", + "link_preview_options": { + "is_disabled": "False", + }, + }, + "type": InlineQueryResultType.ARTICLE, + "id": "1", + }, + }, + ), + ], + ) + async def test_answer_guest_query_defaults( + self, default_bot, ilq_result, expected_params, monkeypatch + ): + offline_bot = default_bot + params = False + + # For now just test that our internals pass the correct data + + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + nonlocal params + params = request_data.parameters == expected_params + return SentGuestMessage("321").to_dict() + + monkeypatch.setattr(offline_bot.request, "post", make_assertion) + + # We test different result types more thoroughly for answer_inline_query, so we just + # use the one type here + copied_result = copy.copy(ilq_result) + + guest_msg = await offline_bot.answer_guest_query("12345", ilq_result) + assert params, "something went wrong with passing arguments to the request" + assert isinstance(guest_msg, SentGuestMessage) + assert guest_msg.inline_message_id == "321" + + # make sure that the results were not edited in-place + assert ilq_result == copied_result + assert ( + ilq_result.input_message_content.parse_mode + == copied_result.input_message_content.parse_mode + ) + # TODO: Needs improvement. We need incoming inline query to test answer. @pytest.mark.parametrize("button_type", ["start", "web_app"]) @pytest.mark.parametrize("cache_time", [74, dtm.timedelta(seconds=74)]) @@ -1794,12 +1941,16 @@ async def post(*args, **kwargs): poll=Poll( "42", "question", - options=[PollOption("option", 0)], + options=[ + PollOption(text="option", voter_count=0, persistent_id="persistent_id") + ], total_voter_count=0, is_closed=False, is_anonymous=True, type=Poll.REGULAR, allows_multiple_answers=False, + allows_revoting=True, + members_only=True, ), ) return [update.to_dict()] @@ -2446,12 +2597,14 @@ async def test_business_connection_id_argument( Poll( id="42", question="question", - options=[PollOption("option", 0)], + options=[PollOption(text="option", voter_count=0, persistent_id="persistent_id")], total_voter_count=5, is_closed=True, is_anonymous=True, type="regular", allows_multiple_answers=False, + allows_revoting=True, + members_only=True, ).to_dict() ) await return_values.put(True) @@ -2920,6 +3073,62 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): ) assert isinstance(inst, PreparedKeyboardButton) + async def test_get_managed_bot_access_settings(self, offline_bot, monkeypatch): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + assert request_data.parameters.get("user_id") == 1234 + return BotAccessSettings( + is_access_restricted=True, + added_users=[User(1, "first", False)], + ).to_dict() + + monkeypatch.setattr(offline_bot.request, "post", make_assertion) + settings = await offline_bot.get_managed_bot_access_settings(1234) + assert isinstance(settings, BotAccessSettings) + + async def test_set_managed_bot_access_settings(self, offline_bot, monkeypatch): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + assert request_data.parameters.get("user_id") == 1234 + assert request_data.parameters.get("is_access_restricted") is True + assert request_data.parameters.get("added_user_ids") == [1, 2, 3] + + monkeypatch.setattr(offline_bot.request, "post", make_assertion) + await offline_bot.set_managed_bot_access_settings( + 1234, + is_access_restricted=True, + added_user_ids=[1, 2, 3], + ) + + async def test_get_user_personal_chat_messages(self, offline_bot, monkeypatch): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + assert request_data.parameters.get("user_id") == 1234 + assert request_data.parameters.get("limit") == 1 + return [make_message("dummy reply").to_dict()] + + monkeypatch.setattr(offline_bot.request, "post", make_assertion) + msgs = await offline_bot.get_user_personal_chat_messages(1234, limit=1) + assert isinstance(msgs, tuple) + assert all(isinstance(msg, Message) for msg in msgs) + + # Bots cannot delete their own reaction from my testing, so we aren't making a real request + async def test_delete_message_reaction(self, offline_bot, monkeypatch): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + assert request_data.parameters.get("chat_id") == 1234 + assert request_data.parameters.get("message_id") == 12 + assert request_data.parameters.get("user_id") == 3432 + assert request_data.parameters.get("actor_chat_id") == 1232 + + monkeypatch.setattr(offline_bot.request, "post", make_assertion) + await offline_bot.delete_message_reaction(1234, 12, 3432, 1232) + + async def test_delete_all_message_reactions(self, offline_bot, monkeypatch): + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + assert request_data.parameters.get("chat_id") == 1234 + assert request_data.parameters.get("user_id") == 3432 + assert request_data.parameters.get("actor_chat_id") == 1232 + + monkeypatch.setattr(offline_bot.request, "post", make_assertion) + await offline_bot.delete_all_message_reactions(1234, 3432, 1232) + class TestBotWithRequest: """ @@ -3275,6 +3484,34 @@ async def test_send_poll_explanation_entities(self, bot, chat_id): assert message.poll.explanation == test_string assert message.poll.explanation_entities == tuple(entities) + async def test_send_poll_media_parameters(self, bot, channel_id): + with ( + data_file("telegram.jpg").open("rb") as photo_file, + data_file("text_file.txt").open("rb") as document_file, + ): + i_photo = InputMediaPhoto(InputFile(photo_file, attach=True)) + i_document = InputMediaDocument(InputFile(document_file, attach=True)) + i_location = InputMediaLocation(latitude=0, longitude=0) + + message = await bot.send_poll( + channel_id, + question="question", + options=[ + InputPollOption("option1", media=i_location), + InputPollOption("option2"), + ], + type=Poll.QUIZ, + correct_option_ids=[0], + media=i_photo, + explanation_media=i_document, + is_closed=True, + read_timeout=60, + ) + + assert message.poll.media.photo + assert message.poll.explanation_media.document + assert message.poll.options[0].media.location + @pytest.mark.parametrize("default_bot", [{"parse_mode": "Markdown"}], indirect=True) async def test_send_poll_default_parse_mode(self, default_bot, super_group_id): explanation = "Italic Bold Code" @@ -3705,11 +3942,15 @@ async def test_get_chat(self, bot, super_group_id): assert cfi.id == int(super_group_id) async def test_get_chat_administrators(self, bot, channel_id): - admins = await bot.get_chat_administrators(channel_id) + admins = await bot.get_chat_administrators(channel_id, return_bots=True) assert isinstance(admins, tuple) + bots_found = 0 for a in admins: assert a.status in ("administrator", "creator") + if a.user.is_bot: + bots_found += 1 + assert bots_found > 1 # will be False if return_bots=False async def test_get_chat_member_count(self, bot, channel_id): count = await bot.get_chat_member_count(channel_id) @@ -4924,6 +5165,12 @@ async def test_my_profile_photo(self, bot): bot_profile_photos = await bot.get_user_profile_photos(bot.id) assert bot_profile_photos.total_count == 1 + async def test_get_user_personal_chat_messages(self, bot): + # id is of the Test User + messages = await bot.get_user_personal_chat_messages(user_id=675666224, limit=2) + assert isinstance(messages, tuple) + assert len(messages) == 2 + async def test_initialize_tracks_requests_and_bot_separately(self, offline_bot, monkeypatch): """Test that requests and bot user are initialized separately and only once.""" request_init_count = 0 diff --git a/tests/test_botaccesssettings.py b/tests/test_botaccesssettings.py new file mode 100644 index 00000000000..56cb8d05f50 --- /dev/null +++ b/tests/test_botaccesssettings.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2026 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""This module contains an object that represents a Telegram Bot Access Settings.""" + +import pytest + +from telegram import BotAccessSettings, Dice +from telegram._user import User +from tests.auxil.slots import mro_slots + + +@pytest.fixture(scope="module") +def bot_access_settings(): + return BotAccessSettings( + is_access_restricted=BotAccessSettingsTestBase.is_access_restricted, + added_users=BotAccessSettingsTestBase.added_users, + ) + + +class BotAccessSettingsTestBase: + is_access_restricted = True + added_users = [User(id=123, first_name="John", is_bot=False)] + + +class TestBotAccessSettingsWithoutRequest(BotAccessSettingsTestBase): + def test_slot_behaviour(self, bot_access_settings): + inst = bot_access_settings + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_de_json(self, offline_bot): + json_dict = { + "is_access_restricted": self.is_access_restricted, + "added_users": [user.to_dict() for user in self.added_users], + } + bot_access_settings = BotAccessSettings.de_json(json_dict, offline_bot) + assert bot_access_settings.api_kwargs == {} + + assert bot_access_settings.is_access_restricted == self.is_access_restricted + assert bot_access_settings.added_users == tuple(self.added_users) + + def test_to_dict(self, bot_access_settings): + bot_access_settings_dict = bot_access_settings.to_dict() + + assert isinstance(bot_access_settings_dict, dict) + assert ( + bot_access_settings_dict["is_access_restricted"] + == bot_access_settings.is_access_restricted + ) + assert isinstance(bot_access_settings_dict["added_users"], list) + assert bot_access_settings_dict["added_users"][0] == self.added_users[0].to_dict() + + def test_equality(self): + a = BotAccessSettings(is_access_restricted=True, added_users=self.added_users) + b = BotAccessSettings(is_access_restricted=True, added_users=self.added_users) + c = BotAccessSettings(is_access_restricted=False, added_users=self.added_users) + d = BotAccessSettings(is_access_restricted=True, added_users=None) + e = Dice(emoji="🎲", value=4) + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) diff --git a/tests/test_chat.py b/tests/test_chat.py index 8d1d2db23ce..99208b5844c 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -563,6 +563,21 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(chat.get_bot(), "send_photo", make_assertion) assert await chat.send_photo(photo="test_photo") + async def test_instance_method_send_live_photo(self, monkeypatch, chat): + async def make_assertion(*_, **kwargs): + return ( + kwargs["chat_id"] == chat.id + and kwargs["live_photo"] == "test_live_photo" + and kwargs["photo"] == "test_photo" + ) + + assert check_shortcut_signature(Chat.send_live_photo, Bot.send_live_photo, ["chat_id"], []) + assert await check_shortcut_call(chat.send_live_photo, chat.get_bot(), "send_live_photo") + assert await check_defaults_handling(chat.send_live_photo, chat.get_bot()) + + monkeypatch.setattr(chat.get_bot(), "send_live_photo", make_assertion) + assert await chat.send_live_photo(live_photo="test_live_photo", photo="test_photo") + async def test_instance_method_send_contact(self, monkeypatch, chat): async def make_assertion(*_, **kwargs): return kwargs["chat_id"] == chat.id and kwargs["phone_number"] == "test_contact" @@ -1537,6 +1552,68 @@ async def make_assertion(*_, **kwargs): active_period=3600, ) + async def test_instance_method_delete_reaction(self, monkeypatch, chat): + async def make_assertion(*_, **kwargs): + return ( + kwargs["chat_id"] == chat.id + and kwargs["message_id"] == 321 + and kwargs["user_id"] == 123 + and kwargs["actor_chat_id"] == 222 + ) + + assert check_shortcut_signature( + Chat.delete_reaction, + Bot.delete_message_reaction, + [ + "chat_id", + ], + additional_kwargs=[], + ) + assert await check_shortcut_call( + chat.delete_reaction, + chat.get_bot(), + "delete_message_reaction", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(chat.delete_reaction, chat.get_bot()) + + monkeypatch.setattr(chat.get_bot(), "delete_message_reaction", make_assertion) + assert await chat.delete_reaction( + user_id=123, + message_id=321, + actor_chat_id=222, + ) + + async def test_instance_method_delete_all_reactions(self, monkeypatch, chat): + async def make_assertion(*_, **kwargs): + return ( + kwargs["chat_id"] == chat.id + and kwargs["user_id"] == 123 + and kwargs["actor_chat_id"] == 222 + ) + + assert check_shortcut_signature( + Chat.delete_all_reactions, + Bot.delete_all_message_reactions, + [ + "chat_id", + ], + additional_kwargs=[], + ) + assert await check_shortcut_call( + chat.delete_all_reactions, + chat.get_bot(), + "delete_all_message_reactions", + shortcut_kwargs=["chat_id"], + ) + assert await check_defaults_handling(chat.delete_all_reactions, chat.get_bot()) + + monkeypatch.setattr(chat.get_bot(), "delete_all_message_reactions", make_assertion) + assert await chat.delete_all_reactions( + user_id=123, + actor_chat_id=222, + ) + async def test_instance_method_get_gifts(self, monkeypatch, chat): async def make_assertion(*_, **kwargs): return kwargs["chat_id"] == chat.id diff --git a/tests/test_chatmember.py b/tests/test_chatmember.py index 7bc4a2979f6..ead273bdbd2 100644 --- a/tests/test_chatmember.py +++ b/tests/test_chatmember.py @@ -61,6 +61,7 @@ class ChatMemberTestBase: can_pin_messages = True can_post_stories = True can_edit_stories = True + can_react_to_messages = True can_delete_stories = True can_manage_topics = True until_date = dtm.datetime.now(UTC).replace(microsecond=0) @@ -576,6 +577,7 @@ def chat_member_restricted(): is_member=TestChatMemberRestrictedWithoutRequest.is_member, until_date=TestChatMemberRestrictedWithoutRequest.until_date, can_edit_tag=TestChatMemberRestrictedWithoutRequest.can_edit_tag, + can_react_to_messages=TestChatMemberRestrictedWithoutRequest.can_react_to_messages, tag=TestChatMemberRestrictedWithoutRequest.tag, ) @@ -609,6 +611,7 @@ def test_de_json(self, offline_bot): "is_member": self.is_member, "until_date": to_timestamp(self.until_date), "can_edit_tag": self.can_edit_tag, + "can_react_to_messages": self.can_react_to_messages, "tag": self.tag, # legacy argument "can_send_media_messages": False, @@ -636,6 +639,7 @@ def test_de_json(self, offline_bot): assert chat_member.is_member == self.is_member assert chat_member.until_date == self.until_date assert chat_member.can_edit_tag == self.can_edit_tag + assert chat_member.can_react_to_messages == self.can_react_to_messages assert chat_member.tag == self.tag def test_de_json_localization(self, tz_bot, offline_bot, raw_bot, chat_member_restricted): @@ -676,9 +680,22 @@ def test_to_dict(self, chat_member_restricted): "is_member": chat_member_restricted.is_member, "until_date": to_timestamp(chat_member_restricted.until_date), "can_edit_tag": chat_member_restricted.can_edit_tag, + "can_react_to_messages": chat_member_restricted.can_react_to_messages, "tag": chat_member_restricted.tag, } + def test_can_react_to_messages_raises(self, chat_member_restricted): + with pytest.raises( + TypeError, match="`can_react_to_messages` is required and cannot be None" + ): + ChatMemberRestricted( + *[ + getattr(chat_member_restricted, k) + for k in chat_member_restricted.__slots__ + if k != "can_react_to_messages" + ] + ) + def test_equality(self, chat_member_restricted): a = chat_member_restricted b = deepcopy(chat_member_restricted) @@ -701,6 +718,7 @@ def test_equality(self, chat_member_restricted): False, False, False, + False, "tag", ) d = Dice(5, "test") diff --git a/tests/test_chatpermissions.py b/tests/test_chatpermissions.py index 0baee3f89d7..43aeabcf537 100644 --- a/tests/test_chatpermissions.py +++ b/tests/test_chatpermissions.py @@ -41,6 +41,7 @@ def chat_permissions(): can_send_video_notes=True, can_send_voice_notes=True, can_edit_tag=True, + can_react_to_messages=True, ) @@ -60,6 +61,7 @@ class ChatPermissionsTestBase: can_send_video_notes = False can_send_voice_notes = None can_edit_tag = None + can_react_to_messages = True class TestChatPermissionsWithoutRequest(ChatPermissionsTestBase): @@ -86,6 +88,7 @@ def test_de_json(self, offline_bot): "can_send_video_notes": self.can_send_video_notes, "can_send_voice_notes": self.can_send_voice_notes, "can_edit_tag": self.can_edit_tag, + "can_react_to_messages": self.can_react_to_messages, } permissions = ChatPermissions.de_json(json_dict, offline_bot) assert permissions.api_kwargs == {"can_send_media_messages": "can_send_media_messages"} @@ -105,6 +108,7 @@ def test_de_json(self, offline_bot): assert permissions.can_send_video_notes == self.can_send_video_notes assert permissions.can_send_voice_notes == self.can_send_voice_notes assert permissions.can_edit_tag == self.can_edit_tag + assert permissions.can_react_to_messages == self.can_react_to_messages def test_to_dict(self, chat_permissions): permissions_dict = chat_permissions.to_dict() @@ -130,6 +134,7 @@ def test_to_dict(self, chat_permissions): assert permissions_dict["can_send_video_notes"] == chat_permissions.can_send_video_notes assert permissions_dict["can_send_voice_notes"] == chat_permissions.can_send_voice_notes assert permissions_dict["can_edit_tag"] == chat_permissions.can_edit_tag + assert permissions_dict["can_react_to_messages"] == chat_permissions.can_react_to_messages def test_equality(self): a = ChatPermissions( @@ -159,6 +164,7 @@ def test_equality(self): can_send_video_notes=True, can_send_voice_notes=True, can_edit_tag=True, + can_react_to_messages=True, ) f = ChatPermissions( can_send_messages=True, @@ -171,6 +177,7 @@ def test_equality(self): can_send_video_notes=True, can_send_voice_notes=True, can_edit_tag=True, + can_react_to_messages=True, ) assert a == b diff --git a/tests/test_constants.py b/tests/test_constants.py index c5e7ff000bf..0a72417cfab 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -204,6 +204,9 @@ def is_type_attribute(name: str) -> bool: "external_reply", "via_bot", "is_from_offline", + "guest_bot_caller_chat", + "guest_bot_caller_user", + "guest_query_id", "show_caption_above_media", "paid_star_count", "is_paid_post", diff --git a/tests/test_message.py b/tests/test_message.py index 958c59b3109..1003e3b161e 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -50,11 +50,14 @@ GiveawayCompleted, GiveawayCreated, GiveawayWinners, + InlineQueryResultArticle, InputChecklist, InputChecklistTask, InputPaidMediaPhoto, + InputTextMessageContent, Invoice, LinkPreviewOptions, + LivePhoto, Location, ManagedBotCreated, Message, @@ -131,6 +134,7 @@ def message(bot): chat=copy(MessageTestBase.chat), from_user=copy(MessageTestBase.from_user), business_connection_id="123456789", + guest_query_id="706654132", ) message.set_bot(bot) message._unfreeze() @@ -213,13 +217,18 @@ def message(bot): "poll": Poll( id="abc", question="What is this?", - options=[PollOption(text="a", voter_count=1), PollOption(text="b", voter_count=2)], + options=[ + PollOption(text="a", voter_count=1, persistent_id="persistent_id_a"), + PollOption(text="b", voter_count=2, persistent_id="persistent_id_b"), + ], is_closed=False, total_voter_count=0, is_anonymous=False, type=Poll.REGULAR, allows_multiple_answers=True, explanation_entities=[], + allows_revoting=True, + members_only=True, ) }, { @@ -443,6 +452,10 @@ def message(bot): {"poll_option_deleted": PollOptionDeleted(option_persistent_id="abc", option_text="this")}, {"reply_to_poll_option_id": "3123"}, {"managed_bot_created": ManagedBotCreated(bot=User(6, "ManagedBot", True))}, + {"guest_bot_caller_user": User(10, "hm", False)}, + {"guest_bot_caller_chat": Chat(14, "om")}, + {"guest_query_id": "This is a guest_query_id"}, + {"live_photo": LivePhoto("file_id", "file_unique_id", 12, 12, 5)}, ], ids=[ "reply", @@ -541,6 +554,10 @@ def message(bot): "poll_option_deleted", "reply_to_poll_option_id", "managed_bot_created", + "guest_bot_caller_user", + "guest_bot_caller_chat", + "guest_query_id", + "live_photo", ], ) def message_params(bot, request): @@ -1486,6 +1503,7 @@ def test_effective_attachment(self, message_params): "document", "game", "invoice", + "live_photo", "location", "paid_media", "passport_data", @@ -2009,6 +2027,54 @@ async def make_assertion(*_, **kwargs): message, message.reply_photo, "send_photo", ["test_photo"], monkeypatch ) + async def test_reply_live_photo(self, monkeypatch, message): + async def make_assertion(*_, **kwargs): + id_ = kwargs["chat_id"] == message.chat_id + live_photo = kwargs["live_photo"] == "test_live_photo" + photo = kwargs["photo"] == "test_photo" + return id_ and live_photo and photo + + assert check_shortcut_signature( + Message.reply_live_photo, + Bot.send_live_photo, + [ + "chat_id", + "reply_to_message_id", + "business_connection_id", + "direct_messages_topic_id", + ], + ["do_quote", "reply_to_message_id"], + annotation_overrides={"message_thread_id": (ODVInput[int], DEFAULT_NONE)}, + ) + assert await check_shortcut_call( + message.reply_live_photo, + message.get_bot(), + "send_live_photo", + skip_params=["reply_to_message_id"], + shortcut_kwargs=["business_connection_id", "direct_messages_topic_id"], + ) + assert await check_defaults_handling( + message.reply_live_photo, message.get_bot(), no_default_kwargs={"message_thread_id"} + ) + + monkeypatch.setattr(message.get_bot(), "send_live_photo", make_assertion) + assert await message.reply_live_photo(live_photo="test_live_photo", photo="test_photo") + await self.check_quote_parsing( + message, + message.reply_live_photo, + "send_live_photo", + ["test_live_photo", "test_photo"], + monkeypatch, + ) + + await self.check_thread_id_parsing( + message, + message.reply_live_photo, + "send_live_photo", + ["test_live_photo", "test_photo"], + monkeypatch, + ) + async def test_reply_audio(self, monkeypatch, message): async def make_assertion(*_, **kwargs): id_ = kwargs["chat_id"] == message.chat_id @@ -3419,3 +3485,54 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(message.get_bot(), "decline_suggested_post", make_assertion) assert await message.decline_suggested_post(comment="some comment") + + async def test_delete_reaction(self, monkeypatch, message): + async def make_assertion(*_, **kwargs): + return ( + kwargs["chat_id"] == message.chat_id + and kwargs["message_id"] == message.message_id + and kwargs["user_id"] == 23 + and kwargs["actor_chat_id"] == 12 + ) + + assert check_shortcut_signature( + Message.delete_reaction, + Bot.delete_message_reaction, + ["chat_id", "message_id"], + [], + ) + assert await check_shortcut_call( + message.delete_reaction, + message.get_bot(), + "delete_message_reaction", + shortcut_kwargs=["chat_id", "message_id"], + ) + assert await check_defaults_handling(message.delete_reaction, message.get_bot()) + + monkeypatch.setattr(message.get_bot(), "delete_message_reaction", make_assertion) + assert await message.delete_reaction(user_id=23, actor_chat_id=12) + + async def test_answer_guest_query(self, monkeypatch, message): + iqra = InlineQueryResultArticle( + id="iqra_id", title="title", input_message_content=InputTextMessageContent("content") + ) + + async def make_assertion(*_, **kwargs): + return kwargs["guest_query_id"] == message.guest_query_id and kwargs["result"] == iqra + + assert check_shortcut_signature( + Message.answer_guest_query, + Bot.answer_guest_query, + ["guest_query_id"], + [], + ) + assert await check_shortcut_call( + message.answer_guest_query, + message.get_bot(), + "answer_guest_query", + shortcut_kwargs=["guest_query_id"], + ) + assert await check_defaults_handling(message.answer_guest_query, message.get_bot()) + + monkeypatch.setattr(message.get_bot(), "answer_guest_query", make_assertion) + assert await message.answer_guest_query(result=iqra) diff --git a/tests/test_official/exceptions.py b/tests/test_official/exceptions.py index f62ef853990..d063909db9d 100644 --- a/tests/test_official/exceptions.py +++ b/tests/test_official/exceptions.py @@ -20,7 +20,18 @@ from collections.abc import Sequence -from telegram import Animation, Audio, Document, Gift, PhotoSize, Sticker, Video, VideoNote, Voice +from telegram import ( + Animation, + Audio, + Document, + Gift, + LivePhoto, + PhotoSize, + Sticker, + Video, + VideoNote, + Voice, +) from tests.test_official.helpers import _get_params_base IGNORED_OBJECTS = ("ResponseParameters",) @@ -41,6 +52,7 @@ class ParamTypeCheckingExceptions: ADDITIONAL_TYPES = { r"send_\w*": { "photo$": PhotoSize, + "live_photo": LivePhoto, "video$": Video, "video_note": VideoNote, "audio": Audio, @@ -67,6 +79,7 @@ class ParamTypeCheckingExceptions: ("keyboard", True): "KeyboardButton", # + sequence[sequence[str]] ("reaction", False): "ReactionType", # + str ("options", False): "InputPollOption", # + str + ("correct_option_ids", False): "Sequence[typing.Literal[", } # Special cases for other parameters that accept more types than the official API, and are @@ -91,6 +104,7 @@ class ParamTypeCheckingExceptions: }, "Input(Paid)?Media.*": { "media": str, # actual: Union[str, InputMedia*, FileInput] + "photo": str, # actual: Union[str, FileInput] # see also https://github.com/tdlib/telegram-bot-api/issues/707 "thumbnail": str, # actual: Union[str, FileInput] "cover": str, # actual: Union[str, FileInput] @@ -138,7 +152,7 @@ class ParamTypeCheckingExceptions: "send_venue": {"venue"}, "answer_inline_query": {"current_offset"}, "send_media_group": {"caption", "parse_mode", "caption_entities"}, - "send_(animation|audio|document|photo|video(_note)?|voice)": {"filename"}, + "send_(animation|audio|document|photo|video(_note)?|voice|live_photo)": {"filename"}, "InlineQueryResult": {"id", "type"}, # attributes common to all subclasses "ChatMember": {"user", "status"}, # attributes common to all subclasses "BotCommandScope": {"type"}, # attributes common to all subclasses @@ -146,8 +160,13 @@ class ParamTypeCheckingExceptions: "PassportFile": {"credentials"}, "EncryptedPassportElement": {"credentials"}, "PassportElementError": {"source", "type", "message"}, + "InputPoll(Option)?Media": {"media_type"}, "InputMedia": {"caption", "caption_entities", "media", "media_type", "parse_mode"}, - "InputMedia(Animation|Audio|Document|Photo|Video|VideoNote|Voice)": {"filename"}, + "InputMedia(Animation|Audio|Document|Photo|Sticker|Video|VideoNote|Voice)": { + "filename", + # tags: deprecated NEXT.VERSION + "filename_depr", + }, "InputFile": {"attach", "filename", "obj", "read_file_handle"}, "MaybeInaccessibleMessage": {"date", "message_id", "chat"}, # attributes common to all subcls "ChatBoostSource": {"source"}, # attributes common to all subclasses @@ -164,6 +183,12 @@ class ParamTypeCheckingExceptions: "InputStoryContent": {"type"}, # attributes common to all subclasses "StoryAreaType": {"type"}, # attributes common to all subclasses "InputProfilePhoto": {"type"}, # attributes common to all subclasses + "InputPollOptionMedia": {"args", "kwargs"}, # UnionType's __init__ signature + "InputPollMedia": {"args", "kwargs"}, # UnionType's __init__ signature + # backwards compatibility for api 10.0 changes + # tags: deprecated NEXT.VERSION, bot api 10.0 + "Poll": {"correct_option_id"}, + "send_poll": {"correct_option_id"}, } @@ -222,7 +247,9 @@ def ignored_param_requirements(object_name: str) -> set[str]: BACKWARDS_COMPAT_KWARGS: dict[str, set[str]] = { "PollOption": {"persistent_id"}, "PollAnswer": {"option_persistent_ids"}, - "Poll": {"allows_revoting"}, + "Poll": {"allows_revoting", "members_only"}, + "ChatMemberRestricted": {"can_react_to_messages"}, + "send_poll": {"correct_option_id"}, } diff --git a/tests/test_paidmedia.py b/tests/test_paidmedia.py index 536fcc1f2ad..b6efb1b01a8 100644 --- a/tests/test_paidmedia.py +++ b/tests/test_paidmedia.py @@ -24,8 +24,10 @@ from telegram import ( Dice, + LivePhoto, PaidMedia, PaidMediaInfo, + PaidMediaLivePhoto, PaidMediaPhoto, PaidMediaPreview, PaidMediaPurchased, @@ -64,6 +66,14 @@ class PaidMediaTestBase: file_unique_id="file_unique_id", ), ) + live_photo = LivePhoto( + file_id="live_photo_file_id", + file_unique_id="live_photo_file_unique_id", + width=640, + height=480, + duration=dtm.timedelta(seconds=60), + photo=photo, + ) class TestPaidMediaWithoutRequest(PaidMediaTestBase): @@ -89,6 +99,7 @@ def test_de_json(self, offline_bot): ("photo", PaidMediaPhoto), ("video", PaidMediaVideo), ("preview", PaidMediaPreview), + ("live_photo", PaidMediaLivePhoto), ], ) def test_de_json_subclass(self, offline_bot, pm_type, subclass): @@ -99,6 +110,7 @@ def test_de_json_subclass(self, offline_bot, pm_type, subclass): "width": self.width, "height": self.height, "duration": int(self.duration.total_seconds()), + "live_photo": self.live_photo.to_dict(), } pm = PaidMedia.de_json(json_dict, offline_bot) @@ -226,6 +238,56 @@ def test_equality(self, paid_media_video): assert hash(a) != hash(d) +@pytest.fixture +def paid_media_live_photo(): + return PaidMediaLivePhoto( + live_photo=TestPaidMediaLivePhotoWithoutRequest.live_photo, + ) + + +class TestPaidMediaLivePhotoWithoutRequest(PaidMediaTestBase): + type = PaidMediaType.LIVE_PHOTO + + def test_slot_behaviour(self, paid_media_live_photo): + inst = paid_media_live_photo + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_de_json(self, offline_bot): + json_dict = { + "live_photo": self.live_photo.to_dict(), + } + pmlp = PaidMediaLivePhoto.de_json(json_dict, offline_bot) + assert pmlp.live_photo == self.live_photo + assert pmlp.api_kwargs == {} + + def test_to_dict(self, paid_media_live_photo): + assert paid_media_live_photo.to_dict() == { + "type": self.type, + "live_photo": paid_media_live_photo.live_photo.to_dict(), + } + + def test_equality(self, paid_media_live_photo): + a = paid_media_live_photo + b = PaidMediaLivePhoto( + live_photo=deepcopy(self.live_photo), + ) + c = PaidMediaLivePhoto( + live_photo=LivePhoto("test", "test_unique", 640, 480, 60), + ) + d = Dice(5, "test") + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) + + @pytest.fixture def paid_media_preview(): return PaidMediaPreview( diff --git a/tests/test_poll.py b/tests/test_poll.py index 0f1998ed17d..10d39a2fe98 100644 --- a/tests/test_poll.py +++ b/tests/test_poll.py @@ -20,16 +20,28 @@ import pytest from telegram import ( + Animation, + Audio, Chat, + Document, + InputMediaPhoto, InputPollOption, + LivePhoto, + Location, MaybeInaccessibleMessage, MessageEntity, + PhotoSize, Poll, PollAnswer, + PollMedia, PollOption, + PollOptionAdded, + PollOptionDeleted, + Sticker, User, + Venue, + Video, ) -from telegram._poll import PollOptionAdded, PollOptionDeleted from telegram._utils.datetime import UTC, to_timestamp from telegram.constants import PollType from telegram.warnings import PTBDeprecationWarning @@ -42,6 +54,7 @@ def input_poll_option(): text=InputPollOptionTestBase.text, text_parse_mode=InputPollOptionTestBase.text_parse_mode, text_entities=InputPollOptionTestBase.text_entities, + media=InputPollOptionTestBase.media, ) out._unfreeze() return out @@ -54,6 +67,7 @@ class InputPollOptionTestBase: MessageEntity(0, 4, MessageEntity.BOLD), MessageEntity(5, 7, MessageEntity.ITALIC), ] + media = InputMediaPhoto("media") class TestInputPollOptionWithoutRequest(InputPollOptionTestBase): @@ -64,6 +78,7 @@ def test_slot_behaviour(self, input_poll_option): "duplicate slot" ) + # tags: deprecated NEXT.VERSION def test_de_json(self): json_dict = { "text": self.text, @@ -77,6 +92,16 @@ def test_de_json(self): assert input_poll_option.text_parse_mode == self.text_parse_mode assert input_poll_option.text_entities == tuple(self.text_entities) + def test_de_json_deprecated(self, recwarn): + InputPollOption.de_json({"text": self.text}, None) + + assert len(recwarn) == 1 + assert "`InputPollOption.de_json` is deprecated" in str(recwarn[0].message) + assert "The `media` field will not be included for deserialization" in str( + recwarn[0].message + ) + assert recwarn[0].category is PTBDeprecationWarning + def test_to_dict(self, input_poll_option): input_poll_option_dict = input_poll_option.to_dict() @@ -86,6 +111,7 @@ def test_to_dict(self, input_poll_option): assert input_poll_option_dict["text_entities"] == [ e.to_dict() for e in input_poll_option.text_entities ] + assert input_poll_option_dict["media"] == input_poll_option.media.to_dict() # Test that the default-value parameter is handled correctly input_poll_option = InputPollOption("text") @@ -97,7 +123,18 @@ def test_equality(self): b = InputPollOption("text", self.text_parse_mode) c = InputPollOption("text", text_entities=self.text_entities) d = InputPollOption("different_text") - e = Poll(123, "question", ["O1", "O2"], 1, False, True, Poll.REGULAR, True) + e = Poll( + 123, + "question", + ["O1", "O2"], + 1, + False, + True, + Poll.REGULAR, + True, + allows_revoting=True, + members_only=True, + ) assert a == b assert hash(a) == hash(b) @@ -112,6 +149,112 @@ def test_equality(self): assert hash(a) != hash(e) +@pytest.fixture(scope="module") +def poll_media(): + return PollMedia( + animation=PollMediaTestBase.animation, + audio=PollMediaTestBase.audio, + document=PollMediaTestBase.document, + live_photo=PollMediaTestBase.live_photo, + location=PollMediaTestBase.location, + photo=PollMediaTestBase.photo, + sticker=PollMediaTestBase.sticker, + venue=PollMediaTestBase.venue, + video=PollMediaTestBase.video, + ) + + +class PollMediaTestBase: + animation = Animation("blah", "unique_id", 320, 180, 1) + audio = Audio(file_id="file_id", file_unique_id="file_unique_id", duration=30) + document = Document("file_id", "file_unique_id", "file_name", 42) + location = Location(123, 456) + photo = (PhotoSize("file_id", "file_unique_id", 1, 1),) + sticker = Sticker("file_id", "file_unique_id", 512, 512, False, False, "regular") + venue = Venue(location=Location(123, 456), title="title", address="address") + video = Video( + file_id="video_file_id", + width=640, + height=480, + file_unique_id="file_unique_id", + duration=dtm.timedelta(seconds=60), + ) + live_photo = LivePhoto( + file_id="video_file_id", + file_unique_id="file_unique_id", + width=640, + height=480, + duration=dtm.timedelta(seconds=60), + mime_type="video/mp4", + file_size=326534, + ) + + +class TestPollMediaWithoutRequest(PollMediaTestBase): + def test_slot_behaviour(self, poll_media): + for attr in poll_media.__slots__: + assert getattr(poll_media, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(poll_media)) == len(set(mro_slots(poll_media))), "duplicate slot" + + def test_de_json(self): + json_dict = { + "animation": self.animation.to_dict(), + "audio": self.audio.to_dict(), + "document": self.document.to_dict(), + "live_photo": self.live_photo.to_dict(), + "location": self.location.to_dict(), + "photo": [photo.to_dict() for photo in self.photo], + "sticker": self.sticker.to_dict(), + "venue": self.venue.to_dict(), + "video": self.video.to_dict(), + } + poll_media = PollMedia.de_json(json_dict, None) + + assert poll_media.api_kwargs == {} + assert poll_media.animation == self.animation + assert poll_media.audio == self.audio + assert poll_media.document == self.document + assert poll_media.live_photo == self.live_photo + assert poll_media.location == self.location + assert poll_media.photo == self.photo + assert poll_media.sticker == self.sticker + assert poll_media.venue == self.venue + assert poll_media.video == self.video + + def test_to_dict(self, poll_media): + poll_media_dict = poll_media.to_dict() + + assert isinstance(poll_media_dict, dict) + assert poll_media_dict["animation"] == poll_media.animation.to_dict() + assert poll_media_dict["audio"] == poll_media.audio.to_dict() + assert poll_media_dict["document"] == poll_media.document.to_dict() + assert poll_media_dict["live_photo"] == poll_media.live_photo.to_dict() + assert poll_media_dict["location"] == poll_media.location.to_dict() + assert poll_media_dict["photo"] == [photo.to_dict() for photo in poll_media.photo] + assert poll_media_dict["sticker"] == poll_media.sticker.to_dict() + assert poll_media_dict["venue"] == poll_media.venue.to_dict() + assert poll_media_dict["video"] == poll_media.video.to_dict() + + def test_equality(self): + a = PollMedia(photo=self.photo) + b = PollMedia(photo=self.photo) + c = PollMedia(photo=(PhotoSize("file_id", "other_file_unique_id", 1, 1),)) + d = PollMedia(video=self.video) + e = PollOption("text", 1, persistent_id="persistent_id") + + assert a == b + assert hash(a) == hash(b) + + assert a != d + assert hash(a) != hash(d) + + assert a != c + assert hash(a) != hash(c) + + assert a != e + assert hash(a) != hash(e) + + @pytest.fixture(scope="module") def poll_option(): out = PollOption( @@ -121,6 +264,8 @@ def poll_option(): added_by_user=PollOptionTestBase.added_by_user, added_by_chat=PollOptionTestBase.added_by_chat, addition_date=PollOptionTestBase.addition_date, + persistent_id=PollOptionTestBase.persistent_id, + media=PollOptionTestBase.media, ) out._unfreeze() return out @@ -136,6 +281,8 @@ class PollOptionTestBase: added_by_user = User(1, "test_user", False) added_by_chat = Chat(1, "test_chat") addition_date = dtm.datetime.now(dtm.timezone.utc) + persistent_id = "persistent_id" + media = PollMedia(location=Location(123, 456)) class TestPollOptionWithoutRequest(PollOptionTestBase): @@ -152,6 +299,8 @@ def test_de_json(self): "added_by_user": self.added_by_user.to_dict(), "added_by_chat": self.added_by_chat.to_dict(), "addition_date": to_timestamp(self.addition_date), + "persistent_id": self.persistent_id, + "media": self.media.to_dict(), } poll_option = PollOption.de_json(json_dict, None) assert poll_option.api_kwargs == {} @@ -162,6 +311,8 @@ def test_de_json(self): assert poll_option.added_by_user == self.added_by_user assert poll_option.added_by_chat == self.added_by_chat assert abs((poll_option.addition_date - self.addition_date).total_seconds()) < 1 + assert poll_option.persistent_id == self.persistent_id + assert poll_option.media == self.media def test_to_dict(self, poll_option): poll_option_dict = poll_option.to_dict() @@ -175,6 +326,8 @@ def test_to_dict(self, poll_option): assert poll_option_dict["added_by_user"] == poll_option.added_by_user.to_dict() assert poll_option_dict["added_by_chat"] == poll_option.added_by_chat.to_dict() assert poll_option_dict["addition_date"] == to_timestamp(poll_option.addition_date) + assert poll_option_dict["persistent_id"] == poll_option.persistent_id + assert poll_option_dict["media"] == poll_option.media.to_dict() def test_parse_entity(self, poll_option): entity = MessageEntity(MessageEntity.BOLD, 0, 4) @@ -190,12 +343,29 @@ def test_parse_entities(self, poll_option): assert poll_option.parse_entities(MessageEntity.BOLD) == {entity: "test"} assert poll_option.parse_entities() == {entity: "test", entity_2: "option"} + def test_persistent_id_required_workaround(self): + # tags: deprecated NEXT.VERSION, bot api 9.6 + with pytest.raises(TypeError, match="`persistent_id` is a required"): + PollOption(self.text, self.voter_count) + def test_equality(self): - a = PollOption("text", 1) - b = PollOption("text", 1) - c = PollOption("text_1", 1) - d = PollOption("text", 2) - e = Poll(123, "question", ["O1", "O2"], 1, False, True, Poll.REGULAR, True) + a = PollOption("text", 1, persistent_id="persistent_id") + b = PollOption("text", 1, persistent_id="persistent_id") + c = PollOption("other_text", 1, persistent_id="persistent_id") + d = PollOption("text", 1 + 9, persistent_id="persistent_id") + e = PollOption("text", 1, persistent_id="other_persistent_id") + f = Poll( + 123, + "question", + ["O1", "O2"], + 1, + False, + True, + Poll.REGULAR, + True, + allows_revoting=True, + members_only=True, + ) assert a == b assert hash(a) == hash(b) @@ -209,6 +379,9 @@ def test_equality(self): assert a != e assert hash(a) != hash(e) + assert a != f + assert hash(a) != hash(f) + @pytest.fixture(scope="module") def poll_answer(): @@ -257,13 +430,20 @@ def test_to_dict(self, poll_answer): assert poll_answer_dict["voter_chat"] == poll_answer.voter_chat.to_dict() assert poll_answer_dict["option_persistent_ids"] == list(poll_answer.option_persistent_ids) + def test_persistent_id_required_workaround(self): + # tags: deprecated NEXT.VERSION, bot api 9.6 + with pytest.raises(TypeError, match="`option_persistent_ids` is a required"): + PollAnswer(poll_id=123, option_ids=[2], user=self.user, voter_chat=self.voter_chat) + def test_equality(self): - a = PollAnswer(123, [2], self.user, self.voter_chat) - b = PollAnswer(123, [2], self.user, Chat(1, "")) - c = PollAnswer(123, [2], User(1, "first", False), self.voter_chat) - d = PollAnswer(123, [1, 2], self.user, self.voter_chat) - e = PollAnswer(456, [2], self.user, self.voter_chat) - f = PollOption("Text", 1) + a = PollAnswer(123, [2], self.user, self.voter_chat, option_persistent_ids=["2"]) + b = PollAnswer(123, [2], self.user, Chat(1, ""), option_persistent_ids=["2"]) + c = PollAnswer( + 123, [2], User(1, "first", False), self.voter_chat, option_persistent_ids=["2"] + ) + d = PollAnswer(123, [1, 2], self.user, self.voter_chat, option_persistent_ids=["1", "2"]) + e = PollAnswer(456, [2], self.user, self.voter_chat, option_persistent_ids=["2"]) + f = PollOption("Text", 1, persistent_id="persistent_id") assert a == b assert hash(a) == hash(b) @@ -298,9 +478,13 @@ def poll(): close_date=PollTestBase.close_date, question_entities=PollTestBase.question_entities, allows_revoting=PollTestBase.allows_revoting, + members_only=PollTestBase.members_only, correct_option_ids=PollTestBase.correct_option_ids, description=PollTestBase.description, description_entities=PollTestBase.description_entities, + country_codes=PollTestBase.country_codes, + media=PollTestBase.media, + explanation_media=PollTestBase.explanation_media, ) poll._unfreeze() return poll @@ -309,12 +493,16 @@ def poll(): class PollTestBase: id_ = "id" question = "Test Question?" - options = [PollOption("test", 10), PollOption("test2", 11)] + options = [ + PollOption("test", 10, persistent_id="persistent_id"), + PollOption("test2", 11, persistent_id="persistent_id_2"), + ] total_voter_count = 0 is_closed = True is_anonymous = False type = Poll.REGULAR allows_multiple_answers = True + members_only = True explanation = ( b"\\U0001f469\\u200d\\U0001f469\\u200d\\U0001f467" b"\\u200d\\U0001f467\\U0001f431http://google.com" @@ -330,6 +518,9 @@ class PollTestBase: correct_option_ids = [1, 2] description = "description" description_entities = [MessageEntity(MessageEntity.ITALIC, 0, 11)] + country_codes = ["AB", "CD"] + media = PollMedia(document=Document("file_id", "file_unique_id", "file_name", 42)) + explanation_media = PollMedia(animation=Animation("blah", "unique_id", 320, 180, 1)) class TestPollWithoutRequest(PollTestBase): @@ -349,9 +540,13 @@ def test_de_json(self, offline_bot): "close_date": to_timestamp(self.close_date), "question_entities": [e.to_dict() for e in self.question_entities], "allows_revoting": self.allows_revoting, + "members_only": self.members_only, "correct_option_ids": self.correct_option_ids, "description": self.description, "description_entities": [e.to_dict() for e in self.description_entities], + "country_codes": self.country_codes, + "media": self.media.to_dict(), + "explanation_media": self.explanation_media.to_dict(), } poll = Poll.de_json(json_dict, offline_bot) assert poll.api_kwargs == {} @@ -368,6 +563,7 @@ def test_de_json(self, offline_bot): assert poll.is_anonymous == self.is_anonymous assert poll.type == self.type assert poll.allows_multiple_answers == self.allows_multiple_answers + assert poll.members_only == self.members_only assert poll.explanation == self.explanation assert poll.explanation_entities == tuple(self.explanation_entities) assert poll._open_period == self.open_period @@ -378,6 +574,9 @@ def test_de_json(self, offline_bot): assert poll.correct_option_ids == tuple(self.correct_option_ids) assert poll.description == self.description assert poll.description_entities == tuple(self.description_entities) + assert poll.country_codes == tuple(self.country_codes) + assert poll.media == self.media + assert poll.explanation_media == self.explanation_media def test_de_json_localization(self, tz_bot, offline_bot, raw_bot): json_dict = { @@ -395,9 +594,13 @@ def test_de_json_localization(self, tz_bot, offline_bot, raw_bot): "close_date": to_timestamp(self.close_date), "question_entities": [e.to_dict() for e in self.question_entities], "allows_revoting": self.allows_revoting, + "members_only": self.members_only, "correct_option_ids": self.correct_option_ids, "description": self.description, "description_entities": [e.to_dict() for e in self.description_entities], + "country_codes": self.country_codes, + "media": self.media.to_dict(), + "explanation_media": self.explanation_media.to_dict(), } poll_raw = Poll.de_json(json_dict, raw_bot) @@ -426,6 +629,7 @@ def test_to_dict(self, poll): assert poll_dict["is_anonymous"] == poll.is_anonymous assert poll_dict["type"] == poll.type assert poll_dict["allows_multiple_answers"] == poll.allows_multiple_answers + assert poll_dict["members_only"] == poll.members_only assert poll_dict["explanation"] == poll.explanation assert poll_dict["explanation_entities"] == [poll.explanation_entities[0].to_dict()] assert poll_dict["open_period"] == int(self.open_period.total_seconds()) @@ -437,6 +641,9 @@ def test_to_dict(self, poll): assert poll_dict["description_entities"] == [ e.to_dict() for e in poll.description_entities ] + assert poll_dict["country_codes"] == list(poll.country_codes) + assert poll_dict["media"] == poll.media.to_dict() + assert poll_dict["explanation_media"] == poll.explanation_media.to_dict() def test_time_period_properties(self, PTB_TIMEDELTA, poll): if PTB_TIMEDELTA: @@ -473,14 +680,79 @@ def test_correct_option_id_deprecated(self, recwarn, poll): PollTestBase.type, PollTestBase.allows_multiple_answers, correct_option_id=1, + allows_revoting=PollTestBase.allows_revoting, + members_only=PollTestBase.members_only, ) assert poll.correct_option_ids == (1,) + def test_allows_revoting_required_workaround(self): + # tags: deprecated NEXT.VERSION, bot api 9.6 + with pytest.raises(TypeError, match="`allows_revoting` is a required"): + Poll( + self.id_, + self.question, + self.options, + self.total_voter_count, + self.is_closed, + self.is_anonymous, + self.type, + self.allows_multiple_answers, + members_only=self.members_only, + ) + + def test_members_only_required_workaround(self): + # tags: deprecated NEXT.VERSION, bot api 10.0 + with pytest.raises(TypeError, match="`members_only` is a required"): + Poll( + self.id_, + self.question, + self.options, + self.total_voter_count, + self.is_closed, + self.is_anonymous, + self.type, + self.allows_multiple_answers, + allows_revoting=self.allows_revoting, + ) + def test_equality(self): - a = Poll(123, "question", ["O1", "O2"], 1, False, True, Poll.REGULAR, True) - b = Poll(123, "question", ["o1", "o2"], 1, True, False, Poll.REGULAR, True) - c = Poll(456, "question", ["o1", "o2"], 1, True, False, Poll.REGULAR, True) - d = PollOption("Text", 1) + a = Poll( + 123, + "question", + ["O1", "O2"], + 1, + False, + True, + Poll.REGULAR, + True, + allows_revoting=True, + members_only=True, + ) + b = Poll( + 123, + "question", + ["o1", "o2"], + 1, + True, + False, + Poll.REGULAR, + True, + allows_revoting=False, + members_only=False, + ) + c = Poll( + 456, + "question", + ["o1", "o2"], + 1, + True, + False, + Poll.REGULAR, + True, + allows_revoting=True, + members_only=True, + ) + d = PollOption("Text", 1, persistent_id="persistent_id") assert a == b assert hash(a) == hash(b) @@ -501,6 +773,8 @@ def test_enum_init(self): is_closed=False, is_anonymous=False, allows_multiple_answers=False, + allows_revoting=True, + members_only=True, ) assert poll.type == "foo" poll = Poll( @@ -512,6 +786,8 @@ def test_enum_init(self): is_closed=False, is_anonymous=False, allows_multiple_answers=False, + allows_revoting=True, + members_only=True, ) assert poll.type is PollType.QUIZ @@ -525,12 +801,14 @@ def test_parse_explanation_entity(self, poll): Poll( "id", "question", - [PollOption("text", voter_count=0)], + [PollOption("text", voter_count=0, persistent_id="persistent_id")], total_voter_count=0, is_closed=False, is_anonymous=False, type=Poll.QUIZ, allows_multiple_answers=False, + allows_revoting=True, + members_only=True, ).parse_explanation_entity(entity) def test_parse_explanation_entities(self, poll): @@ -545,12 +823,14 @@ def test_parse_explanation_entities(self, poll): Poll( "id", "question", - [PollOption("text", voter_count=0)], + [PollOption("text", voter_count=0, persistent_id="persistent_id")], total_voter_count=0, is_closed=False, is_anonymous=False, type=Poll.QUIZ, allows_multiple_answers=False, + allows_revoting=True, + members_only=True, ).parse_explanation_entities() def test_parse_question_entity(self, poll): @@ -576,12 +856,14 @@ def test_parse_description_entity(self, poll): Poll( "id", "question", - [PollOption("text", voter_count=0)], + [PollOption("text", voter_count=0, persistent_id="persistent_id")], total_voter_count=0, is_closed=False, is_anonymous=False, type=Poll.QUIZ, allows_multiple_answers=False, + allows_revoting=True, + members_only=True, ).parse_description_entity(entity) def test_parse_description_entities(self, poll): @@ -595,12 +877,14 @@ def test_parse_description_entities(self, poll): Poll( "id", "question", - [PollOption("text", voter_count=0)], + [PollOption("text", voter_count=0, persistent_id="persistent_id")], total_voter_count=0, is_closed=False, is_anonymous=False, type=Poll.QUIZ, allows_multiple_answers=False, + allows_revoting=True, + members_only=True, ).parse_description_entities() diff --git a/tests/test_reply.py b/tests/test_reply.py index 1822fff19f4..fa544845522 100644 --- a/tests/test_reply.py +++ b/tests/test_reply.py @@ -29,6 +29,7 @@ ExternalReplyInfo, Giveaway, LinkPreviewOptions, + LivePhoto, MessageEntity, MessageOriginUser, PaidMediaInfo, @@ -50,6 +51,7 @@ def external_reply_info(): giveaway=ExternalReplyInfoTestBase.giveaway, paid_media=ExternalReplyInfoTestBase.paid_media, checklist=ExternalReplyInfoTestBase.checklist, + live_photo=ExternalReplyInfoTestBase.live_photo, ) @@ -73,6 +75,16 @@ class ExternalReplyInfoTestBase: ChecklistTask(text="Item 2", id=2), ], ) + live_photo = LivePhoto( + file_id="file_id", + file_unique_id="file_unique_id", + width=100, + height=100, + duration=dtm.timedelta(seconds=10), + photo=[], + mime_type="image/jpeg", + file_size=1024, + ) class TestExternalReplyInfoWithoutRequest(ExternalReplyInfoTestBase): @@ -92,6 +104,7 @@ def test_de_json(self, offline_bot): "giveaway": self.giveaway.to_dict(), "paid_media": self.paid_media.to_dict(), "checklist": self.checklist.to_dict(), + "live_photo": self.live_photo.to_dict(), } external_reply_info = ExternalReplyInfo.de_json(json_dict, offline_bot) @@ -104,6 +117,7 @@ def test_de_json(self, offline_bot): assert external_reply_info.giveaway == self.giveaway assert external_reply_info.paid_media == self.paid_media assert external_reply_info.checklist == self.checklist + assert external_reply_info.live_photo == self.live_photo def test_to_dict(self, external_reply_info): ext_reply_info_dict = external_reply_info.to_dict() @@ -116,6 +130,7 @@ def test_to_dict(self, external_reply_info): assert ext_reply_info_dict["giveaway"] == self.giveaway.to_dict() assert ext_reply_info_dict["paid_media"] == self.paid_media.to_dict() assert ext_reply_info_dict["checklist"] == self.checklist.to_dict() + assert ext_reply_info_dict["live_photo"] == self.live_photo.to_dict() def test_equality(self, external_reply_info): a = external_reply_info diff --git a/tests/test_sentguestmessage.py b/tests/test_sentguestmessage.py new file mode 100644 index 00000000000..0208342fb57 --- /dev/null +++ b/tests/test_sentguestmessage.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2026 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +import pytest + +from telegram import SentGuestMessage +from tests.auxil.slots import mro_slots + + +@pytest.fixture(scope="module") +def sent_guest_message(): + return SentGuestMessage(inline_message_id=SentGuestMessageTestBase.inline_message_id) + + +class SentGuestMessageTestBase: + inline_message_id = "123" + + +class TestSentGuestMessageWithoutRequest(SentGuestMessageTestBase): + def test_slot_behaviour(self, sent_guest_message): + inst = sent_guest_message + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_to_dict(self, sent_guest_message): + sent_guest_message_dict = sent_guest_message.to_dict() + + assert isinstance(sent_guest_message_dict, dict) + assert sent_guest_message_dict["inline_message_id"] == self.inline_message_id + + def test_de_json(self, offline_bot): + data = {"inline_message_id": self.inline_message_id} + m = SentGuestMessage.de_json(data, None) + assert m.api_kwargs == {} + assert m.inline_message_id == self.inline_message_id + + def test_equality(self): + a = SentGuestMessage(self.inline_message_id) + b = SentGuestMessage(self.inline_message_id) + c = SentGuestMessage("not_inline_message_id") + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a != c + assert hash(a) != hash(c) diff --git a/tests/test_update.py b/tests/test_update.py index 7b034090a0e..cbaa1d4b66a 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -156,6 +156,14 @@ user=User(1, "creator", True), bot=User(2, "bot", True), ) +guest_message = Message( + 1, + dtm.datetime.utcnow(), + Chat(1, ""), + User(1, "", False), + sender_chat=Chat(1, ""), +) + params = [ {"message": message}, @@ -171,17 +179,31 @@ ) }, {"pre_checkout_query": PreCheckoutQuery("id", User(1, "", False), "", 0, "")}, - {"poll": Poll("id", "?", [PollOption(".", 1)], False, False, False, Poll.REGULAR, True)}, { - "poll_answer": PollAnswer( + "poll": Poll( "id", - [1], - User( + "?", + [PollOption(text=".", voter_count=1, persistent_id="persistent_id")], + False, + False, + False, + Poll.REGULAR, + True, + allows_revoting=True, + members_only=True, + ) + }, + { + "poll_answer": PollAnswer( + poll_id="id", + option_ids=[1], + option_persistent_ids=["1"], + user=User( 1, "", False, ), - Chat(1, ""), + voter_chat=Chat(1, ""), ) }, {"my_chat_member": chat_member_updated}, @@ -197,6 +219,7 @@ {"edited_business_message": business_message}, {"purchased_paid_media": purchased_paid_media}, {"managed_bot": managed_bot}, + {"guest_message": guest_message}, # Must be last to conform with `ids` below! {"callback_query": CallbackQuery(1, User(1, "", False), "chat")}, ] @@ -226,6 +249,7 @@ "edited_business_message", "purchased_paid_media", "managed_bot", + "guest_message", ) ids = (*all_types, "callback_query_without_message") @@ -332,7 +356,7 @@ def test_effective_user(self, update): def test_effective_sender_non_anonymous(self, update): update = deepcopy(update) # Simulate 'Remain anonymous' being turned off - if message := (update.message or update.edited_message): + if message := (update.message or update.edited_message or update.guest_message): message._unfreeze() message.sender_chat = None elif reaction := (update.message_reaction): @@ -365,7 +389,7 @@ def test_effective_sender_non_anonymous(self, update): def test_effective_sender_anonymous(self, update): update = deepcopy(update) # Simulate 'Remain anonymous' being turned on - if message := (update.message or update.edited_message): + if message := (update.message or update.edited_message or update.guest_message): message._unfreeze() message.from_user = None elif reaction := (update.message_reaction): @@ -391,6 +415,7 @@ def test_effective_sender_anonymous(self, update): or update.edited_channel_post or update.message_reaction or update.poll_answer + or update.guest_message ): assert isinstance(sender, Chat) else: diff --git a/tests/test_user.py b/tests/test_user.py index 24f8ffe8c8d..197dbbe3967 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -47,6 +47,7 @@ def json_dict(): "has_topics_enabled": UserTestBase.has_topics_enabled, "allows_users_to_create_topics": UserTestBase.allows_users_to_create_topics, "can_manage_bots": UserTestBase.can_manage_bots, + "supports_guest_queries": UserTestBase.supports_guest_queries, } @@ -69,6 +70,7 @@ def user(bot): has_topics_enabled=UserTestBase.has_topics_enabled, allows_users_to_create_topics=UserTestBase.allows_users_to_create_topics, can_manage_bots=UserTestBase.can_manage_bots, + supports_guest_queries=UserTestBase.supports_guest_queries, ) user.set_bot(bot) user._unfreeze() @@ -92,6 +94,7 @@ class UserTestBase: has_topics_enabled = False allows_users_to_create_topics = False can_manage_bots = True + supports_guest_queries = False class TestUserWithoutRequest(UserTestBase): @@ -120,6 +123,7 @@ def test_de_json(self, json_dict, offline_bot): assert user.has_topics_enabled == self.has_topics_enabled assert user.allows_users_to_create_topics == self.allows_users_to_create_topics assert user.can_manage_bots == self.can_manage_bots + assert user.supports_guest_queries == self.supports_guest_queries def test_to_dict(self, user): user_dict = user.to_dict() @@ -141,6 +145,7 @@ def test_to_dict(self, user): assert user_dict["has_topics_enabled"] == user.has_topics_enabled assert user_dict["allows_users_to_create_topics"] == user.allows_users_to_create_topics assert user_dict["can_manage_bots"] == user.can_manage_bots + assert user_dict["supports_guest_queries"] == user.supports_guest_queries def test_equality(self): a = User(self.id_, self.first_name, self.is_bot, self.last_name) @@ -291,6 +296,21 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(user.get_bot(), "send_photo", make_assertion) assert await user.send_photo("test_photo") + async def test_instance_method_send_live_photo(self, monkeypatch, user): + async def make_assertion(*_, **kwargs): + return ( + kwargs["chat_id"] == user.id + and kwargs["live_photo"] == "test_live_photo" + and kwargs["photo"] == "test_photo" + ) + + assert check_shortcut_signature(User.send_live_photo, Bot.send_live_photo, ["chat_id"], []) + assert await check_shortcut_call(user.send_live_photo, user.get_bot(), "send_live_photo") + assert await check_defaults_handling(user.send_live_photo, user.get_bot()) + + monkeypatch.setattr(user.get_bot(), "send_live_photo", make_assertion) + assert await user.send_live_photo("test_live_photo", "test_photo") + async def test_instance_method_send_media_group(self, monkeypatch, user): async def make_assertion(*_, **kwargs): return kwargs["chat_id"] == user.id and kwargs["media"] == "test_media_group" @@ -926,3 +946,115 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(user.get_bot(), "replace_managed_bot_token", make_assertion) assert await user.replace_token() + + async def test_instance_method_get_managed_bot_access_settings(self, monkeypatch, user): + async def make_assertion(*_, **kwargs): + return kwargs["user_id"] == user.id + + assert check_shortcut_signature( + user.get_managed_bot_access_settings, + Bot.get_managed_bot_access_settings, + ["user_id"], + [], + ) + assert await check_shortcut_call( + user.get_managed_bot_access_settings, + user.get_bot(), + "get_managed_bot_access_settings", + ) + assert await check_defaults_handling(user.get_managed_bot_access_settings, user.get_bot()) + + monkeypatch.setattr(user.get_bot(), "get_managed_bot_access_settings", make_assertion) + assert await user.get_managed_bot_access_settings() + + async def test_instance_method_set_managed_bot_access_settings(self, monkeypatch, user): + async def make_assertion(*_, **kwargs): + return ( + kwargs["user_id"] == user.id + and kwargs["is_access_restricted"] is True + and kwargs["added_user_ids"] == [123] + ) + + assert check_shortcut_signature( + user.set_managed_bot_access_settings, + Bot.set_managed_bot_access_settings, + ["user_id"], + [], + ) + assert await check_shortcut_call( + user.set_managed_bot_access_settings, + user.get_bot(), + "set_managed_bot_access_settings", + ) + assert await check_defaults_handling(user.set_managed_bot_access_settings, user.get_bot()) + + monkeypatch.setattr(user.get_bot(), "set_managed_bot_access_settings", make_assertion) + assert await user.set_managed_bot_access_settings( + is_access_restricted=True, + added_user_ids=[123], + ) + + async def test_instance_method_get_personal_chat_messages(self, monkeypatch, user): + async def make_assertion(*_, **kwargs): + return kwargs["user_id"] == user.id and kwargs["limit"] == 2 + + assert check_shortcut_signature( + user.get_personal_chat_messages, + Bot.get_user_personal_chat_messages, + ["user_id"], + [], + ) + assert await check_shortcut_call( + user.get_personal_chat_messages, + user.get_bot(), + "get_user_personal_chat_messages", + ) + assert await check_defaults_handling(user.get_personal_chat_messages, user.get_bot()) + + monkeypatch.setattr(user.get_bot(), "get_user_personal_chat_messages", make_assertion) + assert await user.get_personal_chat_messages(limit=2) + + async def test_instance_method_delete_reaction(self, monkeypatch, user): + async def make_assertion(*_, **kwargs): + return ( + kwargs["user_id"] == user.id + and kwargs["chat_id"] == 1234 + and kwargs["message_id"] == 123 + and kwargs["actor_chat_id"] == 42 + ) + + assert check_shortcut_signature( + user.delete_reaction, Bot.delete_message_reaction, ["user_id"], [] + ) + assert await check_shortcut_call( + user.delete_reaction, + user.get_bot(), + "delete_message_reaction", + shortcut_kwargs=["user_id"], + ) + assert await check_defaults_handling(user.delete_reaction, user.get_bot()) + + monkeypatch.setattr(user.get_bot(), "delete_message_reaction", make_assertion) + assert await user.delete_reaction(chat_id=1234, message_id=123, actor_chat_id=42) + + async def test_instance_method_delete_all_reactions(self, monkeypatch, user): + async def make_assertion(*_, **kwargs): + return ( + kwargs["user_id"] == user.id + and kwargs["chat_id"] == 1234 + and kwargs["actor_chat_id"] == 42 + ) + + assert check_shortcut_signature( + user.delete_all_reactions, Bot.delete_all_message_reactions, ["user_id"], [] + ) + assert await check_shortcut_call( + user.delete_all_reactions, + user.get_bot(), + "delete_all_message_reactions", + shortcut_kwargs=["user_id"], + ) + assert await check_defaults_handling(user.delete_all_reactions, user.get_bot()) + + monkeypatch.setattr(user.get_bot(), "delete_all_message_reactions", make_assertion) + assert await user.delete_all_reactions(chat_id=1234, actor_chat_id=42) diff --git a/uv.lock b/uv.lock index 1b6acb434db..3c76a170d28 100644 --- a/uv.lock +++ b/uv.lock @@ -10,6 +10,10 @@ resolution-markers = [ "python_full_version < '3.11'", ] +[options] +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer-span = "P7D" + [[package]] name = "accessible-pygments" version = "0.0.5" @@ -1653,7 +1657,7 @@ all = [ { name = "sphinx-copybutton", specifier = "==0.5.2" }, { name = "sphinx-inline-tabs", specifier = "==2025.12.21.14" }, { name = "sphinx-paramlinks", specifier = "==0.6.0" }, - { name = "sphinxcontrib-mermaid", specifier = "==2.0.1" }, + { name = "sphinxcontrib-mermaid", specifier = "==2.0.2" }, { name = "tzdata" }, ] docs = [ @@ -1665,7 +1669,7 @@ docs = [ { name = "sphinx-copybutton", specifier = "==0.5.2" }, { name = "sphinx-inline-tabs", specifier = "==2025.12.21.14" }, { name = "sphinx-paramlinks", specifier = "==0.6.0" }, - { name = "sphinxcontrib-mermaid", specifier = "==2.0.1" }, + { name = "sphinxcontrib-mermaid", specifier = "==2.0.2" }, ] linting = [ { name = "mypy", specifier = "==1.20.2" }, @@ -2065,7 +2069,7 @@ wheels = [ [[package]] name = "sphinxcontrib-mermaid" -version = "2.0.1" +version = "2.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinja2" }, @@ -2074,9 +2078,9 @@ dependencies = [ { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2b/ae/999891de292919b66ea34f2c22fc22c9be90ab3536fbc0fca95716277351/sphinxcontrib_mermaid-2.0.1.tar.gz", hash = "sha256:a21a385a059a6cafd192aa3a586b14bf5c42721e229db67b459dc825d7f0a497", size = 19839, upload-time = "2026-03-05T14:10:41.901Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/3a1cc926da8c563c58ddc124a7b3fe5ccadcae96c96e3a6f8ac3653a210a/sphinxcontrib_mermaid-2.0.2.tar.gz", hash = "sha256:f09576c78ca93fa0e3034fd9c45aaffa7c44ab449de9c43b8b8d262afe52bc66", size = 19265, upload-time = "2026-05-05T13:59:02.959Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/46/25d64bcd7821c8d6f1080e1c43d5fcdfc442a18f759a230b5ccdc891093e/sphinxcontrib_mermaid-2.0.1-py3-none-any.whl", hash = "sha256:9dca7fbe827bad5e7e2b97c4047682cfd26e3e07398cfdc96c7a8842ae7f06e7", size = 14064, upload-time = "2026-03-05T14:10:40.533Z" }, + { url = "https://files.pythonhosted.org/packages/16/8d/93be7e0f7fa915a576859b3bfac7a7baa3303181c44d7db7eefbd3e8a69f/sphinxcontrib_mermaid-2.0.2-py3-none-any.whl", hash = "sha256:d862e514991279fb4816302c5cfe167d2557bf3ce7125ae0cb47dac80a0f46ce", size = 14094, upload-time = "2026-05-05T13:59:01.585Z" }, ] [[package]] From 4c710a3455156ba0fd541dbbfeac167b8fedf4cc Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Fri, 12 Jun 2026 03:36:40 -0400 Subject: [PATCH 36/39] Support Python 3.15 beta (#5259) --- .github/workflows/unit_tests.yml | 2 +- .../5259.iZALQbcHZUBRwuvxwMLpf4.toml | 9 ++ pyproject.toml | 1 + src/telegram/__init__.py | 27 ++--- src/telegram/ext/_utils/trackingdict.py | 8 ++ tests/ext/test_filters.py | 8 +- tests/test_official/helpers.py | 1 + tests/test_official/test_official.py | 4 + uv.lock | 102 +++++++++--------- 9 files changed, 96 insertions(+), 66 deletions(-) create mode 100644 changes/unreleased/5259.iZALQbcHZUBRwuvxwMLpf4.toml diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index b3b9b4431c6..c9bb791e1d0 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -19,7 +19,7 @@ jobs: runs-on: ${{matrix.os}} strategy: matrix: - python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] + python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', '3.15.0-beta.2'] os: [ubuntu-latest, windows-latest, macos-latest] include: - python-version: '3.14t' diff --git a/changes/unreleased/5259.iZALQbcHZUBRwuvxwMLpf4.toml b/changes/unreleased/5259.iZALQbcHZUBRwuvxwMLpf4.toml new file mode 100644 index 00000000000..370e72fa9e5 --- /dev/null +++ b/changes/unreleased/5259.iZALQbcHZUBRwuvxwMLpf4.toml @@ -0,0 +1,9 @@ +other = """Support Python 3.15 Beta + +* Python 3.15 free threading is not fully supported yet, as the optional dependency ``cryptography`` is not yet compatible with it. + +""" +[[pull_requests]] +uid = "5259" +author_uids = ["harshil21"] +closes_threads = ["5231"] diff --git a/pyproject.toml b/pyproject.toml index 14698612a3c..9a47b954eb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", ] dependencies = [ "httpx >=0.27,<0.29", diff --git a/src/telegram/__init__.py b/src/telegram/__init__.py index f0304c4962b..a531daae8bd 100644 --- a/src/telegram/__init__.py +++ b/src/telegram/__init__.py @@ -343,19 +343,7 @@ "warnings", ) -from telegram._inputchecklist import InputChecklist, InputChecklistTask -from telegram._payment.stars.staramount import StarAmount -from telegram._payment.stars.startransactions import StarTransaction, StarTransactions -from telegram._payment.stars.transactionpartner import ( - TransactionPartner, - TransactionPartnerAffiliateProgram, - TransactionPartnerChat, - TransactionPartnerFragment, - TransactionPartnerOther, - TransactionPartnerTelegramAds, - TransactionPartnerTelegramApi, - TransactionPartnerUser, -) +__lazy_modules__: list[str] = ["constants", "error", "helpers", "request", "warnings"] from . import _version, constants, error, helpers, request, warnings from ._birthdate import Birthdate @@ -523,6 +511,7 @@ from ._inline.inputtextmessagecontent import InputTextMessageContent from ._inline.inputvenuemessagecontent import InputVenueMessageContent from ._inline.preparedinlinemessage import PreparedInlineMessage +from ._inputchecklist import InputChecklist, InputChecklistTask from ._keyboardbutton import KeyboardButton from ._keyboardbuttonpolltype import KeyboardButtonPollType from ._keyboardbuttonrequest import ( @@ -596,6 +585,18 @@ RevenueWithdrawalStatePending, RevenueWithdrawalStateSucceeded, ) +from ._payment.stars.staramount import StarAmount +from ._payment.stars.startransactions import StarTransaction, StarTransactions +from ._payment.stars.transactionpartner import ( + TransactionPartner, + TransactionPartnerAffiliateProgram, + TransactionPartnerChat, + TransactionPartnerFragment, + TransactionPartnerOther, + TransactionPartnerTelegramAds, + TransactionPartnerTelegramApi, + TransactionPartnerUser, +) from ._payment.successfulpayment import SuccessfulPayment from ._poll import ( InputPollOption, diff --git a/src/telegram/ext/_utils/trackingdict.py b/src/telegram/ext/_utils/trackingdict.py index 04685efca23..a5eedbe86e7 100644 --- a/src/telegram/ext/_utils/trackingdict.py +++ b/src/telegram/ext/_utils/trackingdict.py @@ -26,6 +26,7 @@ the changelog. """ +import sys from collections import UserDict from collections.abc import Mapping from typing import Final, Generic, TypeVar @@ -111,6 +112,13 @@ def pop( # type: ignore[override] return super().pop(key) return super().pop(key, default) + # Python 3.15 added a popitem() to UserDict, which does LIFO instead of the FIFO behaviour + # of MutableMapping.popitem(). So we keep it consistent across versions by overriding here. + if sys.version_info >= (3, 15): + + def popitem(self) -> tuple[_KT, _VT]: + return super(UserDict, self).popitem() + def clear(self) -> None: self.__track_write(set(super().keys())) super().clear() diff --git a/tests/ext/test_filters.py b/tests/ext/test_filters.py index 9271649d635..8b101b73f4c 100644 --- a/tests/ext/test_filters.py +++ b/tests/ext/test_filters.py @@ -671,7 +671,13 @@ def test_filters_document_type(self, update): assert not filters.Document.AUDIO.check_update(update) update.message.document.mime_type = "application/octet-stream" - assert filters.Document.EXE.check_update(update) + # Python 3.15 changes the "exe" mime type to application/vnd.microsoft.portable-executable + if int(platform.python_version_tuple()[1]) <= 14: + assert filters.Document.EXE.check_update(update) + else: + assert not filters.Document.EXE.check_update(update) + update.message.document.mime_type = "application/vnd.microsoft.portable-executable" + assert filters.Document.EXE.check_update(update) assert filters.Document.APPLICATION.check_update(update) assert not filters.Document.DOCX.check_update(update) assert not filters.Document.AUDIO.check_update(update) diff --git a/tests/test_official/helpers.py b/tests/test_official/helpers.py index 2257adb942a..eca39bcf127 100644 --- a/tests/test_official/helpers.py +++ b/tests/test_official/helpers.py @@ -109,6 +109,7 @@ def cached_type_hints(obj: Any, is_class: bool) -> dict[str, Any]: @functools.cache def resolve_forward_refs_in_type(obj: type) -> type: """Resolves forward references in a type hint.""" + # TODO: Refactor test_official to properly use annotationlib return _eval_type(obj, localns=tg_objects, globalns=None) diff --git a/tests/test_official/test_official.py b/tests/test_official/test_official.py index 2c3fd03d6b1..70ec78a1912 100644 --- a/tests/test_official/test_official.py +++ b/tests/test_official/test_official.py @@ -17,10 +17,14 @@ # 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 inspect +import platform from typing import TYPE_CHECKING import pytest +if platform.python_version_tuple() >= ("3", "15"): + pytest.skip("Running on Python version > 3.15 is not supported yet!", allow_module_level=True) + import telegram from tests.auxil.envvars import RUN_TEST_OFFICIAL from tests.test_official.arg_type_checker import ( diff --git a/uv.lock b/uv.lock index 3c76a170d28..456da08c6e4 100644 --- a/uv.lock +++ b/uv.lock @@ -500,62 +500,62 @@ toml = [ [[package]] name = "cryptography" -version = "46.0.7" +version = "48.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" }, - { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, - { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, - { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, - { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, - { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, - { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, - { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, - { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, - { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, - { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, - { url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" }, - { url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" }, - { url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" }, - { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, - { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, - { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, - { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, - { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, - { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, - { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, - { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, - { url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" }, - { url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" }, - { url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" }, - { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, - { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, - { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, - { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, - { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, - { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, - { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, - { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, - { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, - { url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" }, - { url = "https://files.pythonhosted.org/packages/63/0c/dca8abb64e7ca4f6b2978769f6fea5ad06686a190cec381f0a796fdcaaba/cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f", size = 3476879, upload-time = "2026-04-08T01:57:38.664Z" }, - { url = "https://files.pythonhosted.org/packages/3a/ea/075aac6a84b7c271578d81a2f9968acb6e273002408729f2ddff517fed4a/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15", size = 4219700, upload-time = "2026-04-08T01:57:40.625Z" }, - { url = "https://files.pythonhosted.org/packages/6c/7b/1c55db7242b5e5612b29fc7a630e91ee7a6e3c8e7bf5406d22e206875fbd/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455", size = 4385982, upload-time = "2026-04-08T01:57:42.725Z" }, - { url = "https://files.pythonhosted.org/packages/cb/da/9870eec4b69c63ef5925bf7d8342b7e13bc2ee3d47791461c4e49ca212f4/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65", size = 4219115, upload-time = "2026-04-08T01:57:44.939Z" }, - { url = "https://files.pythonhosted.org/packages/f4/72/05aa5832b82dd341969e9a734d1812a6aadb088d9eb6f0430fc337cc5a8f/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968", size = 4385479, upload-time = "2026-04-08T01:57:46.86Z" }, - { url = "https://files.pythonhosted.org/packages/20/2a/1b016902351a523aa2bd446b50a5bc1175d7a7d1cf90fe2ef904f9b84ebc/cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4", size = 3412829, upload-time = "2026-04-08T01:57:48.874Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz", hash = "sha256:5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920", size = 832984, upload-time = "2026-05-04T22:59:38.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/3d/01f6dd9190170a5a241e0e98c2d04be3664a9e6f5b9b872cde63aff1c3dd/cryptography-48.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:0c558d2cdffd8f4bbb30fc7134c74d2ca9a476f830bb053074498fbc86f41ed6", size = 8001587, upload-time = "2026-05-04T22:57:36.803Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6e/e90527eef33f309beb811cf7c982c3aeffcce8e3edb178baa4ca3ae4a6fa/cryptography-48.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5333311663ea94f75dd408665686aaf426563556bb5283554a3539177e03b8c", size = 4690433, upload-time = "2026-05-04T22:57:40.373Z" }, + { url = "https://files.pythonhosted.org/packages/90/04/673510ed51ddff56575f306cf1617d80411ee76831ccd3097599140efdfe/cryptography-48.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7995ef305d7165c3f11ae07f2517e5a4f1d5c18da1376a0a9ed496336b69e5f3", size = 4710620, upload-time = "2026-05-04T22:57:42.935Z" }, + { url = "https://files.pythonhosted.org/packages/14/d5/e9c4ef932c8d800490c34d8bd589d64a31d5890e27ec9e9ad532be893294/cryptography-48.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:40ba1f85eaa6959837b1d51c9767e230e14612eea4ef110ee8854ada22da1bf5", size = 4696283, upload-time = "2026-05-04T22:57:45.294Z" }, + { url = "https://files.pythonhosted.org/packages/0c/29/174b9dfb60b12d59ecfc6cfa04bc88c21b42a54f01b8aae09bb6e51e4c7f/cryptography-48.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:369a6348999f94bbd53435c894377b20ab95f25a9065c283570e70150d8abc3c", size = 5296573, upload-time = "2026-05-04T22:57:47.933Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/0d29a6fd7d0d1373f0c0c88a04ba20e359b257753ac497564cd660fc1d55/cryptography-48.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a0e692c683f4df67815a2d258b324e66f4738bd7a96a218c826dce4f4bd05d8f", size = 4743677, upload-time = "2026-05-04T22:57:50.067Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/eef653013d5c63b6a490529e0316f9ac14a37602965d4903efed1399f32b/cryptography-48.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:18349bbc56f4743c8b12dc32e2bccb2cf83ee8b69a3bba74ef8ae857e26b3d25", size = 4330808, upload-time = "2026-05-04T22:57:52.301Z" }, + { url = "https://files.pythonhosted.org/packages/84/9e/500463e87abb7a0a0f9f256ec21123ecde0a7b5541a15e840ea54551fd81/cryptography-48.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e8eac43dfca5c4cccc6dad9a80504436fca53bb9bc3100a2386d730fbe6b602", size = 4695941, upload-time = "2026-05-04T22:57:54.603Z" }, + { url = "https://files.pythonhosted.org/packages/e3/dc/7303087450c2ec9e7fbb750e17c2abfbc658f23cbd0e54009509b7cc4091/cryptography-48.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9ccdac7d40688ecb5a3b4a604b8a88c8002e3442d6c60aead1db2a89a041560c", size = 5252579, upload-time = "2026-05-04T22:57:57.207Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c0/7101d3b7215edcdc90c45da544961fd8ed2d6448f77577460fa75a8443f7/cryptography-48.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:bd72e68b06bb1e96913f97dd4901119bc17f39d4586a5adf2d3e47bc2b9d58b5", size = 4743326, upload-time = "2026-05-04T22:57:59.535Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d8/5b833bad13016f562ab9d063d68199a4bd121d18458e439515601d3357ec/cryptography-48.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:59baa2cb386c4f0b9905bd6eb4c2a79a69a128408fd31d32ca4d7102d4156321", size = 4826672, upload-time = "2026-05-04T22:58:01.996Z" }, + { url = "https://files.pythonhosted.org/packages/98/e1/7074eb8bf3c135558c73fc2bcf0f5633f912e6fb87e868a55c454080ef09/cryptography-48.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9249e3cd978541d665967ac2cb2787fd6a62bddf1e75b3e347a594d7dacf4f74", size = 4972574, upload-time = "2026-05-04T22:58:03.968Z" }, + { url = "https://files.pythonhosted.org/packages/04/70/e5a1b41d325f797f39427aa44ef8baf0be500065ab6d8e10369d850d4a4f/cryptography-48.0.0-cp311-abi3-win32.whl", hash = "sha256:9c459db21422be75e2809370b829a87eb37f74cd785fc4aa9ea1e5f43b47cda4", size = 3294868, upload-time = "2026-05-04T22:58:06.467Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ac/8ac51b4a5fc5932eb7ee5c517ba7dc8cd834f0048962b6b352f00f41ebf9/cryptography-48.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:5b012212e08b8dd5edc78ef54da83dd9892fd9105323b3993eff6bea65dc21d7", size = 3817107, upload-time = "2026-05-04T22:58:08.845Z" }, + { url = "https://files.pythonhosted.org/packages/6b/84/70e3feea9feea87fd7cbe77efb2712ae1e3e6edf10749dc6e95f4e60e455/cryptography-48.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:3cb07a3ed6431663cd321ea8a000a1314c74211f823e4177fefa2255e057d1ec", size = 7986556, upload-time = "2026-05-04T22:58:11.172Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/18e07a618bb5442ba10cf4df16e99c071365528aa570dfcb8c02e25a303b/cryptography-48.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c7378637d7d88016fa6791c159f698b3d3eed28ebf844ac36b9dc04a14dae18", size = 4684776, upload-time = "2026-05-04T22:58:13.712Z" }, + { url = "https://files.pythonhosted.org/packages/be/6a/4ea3b4c6c6759794d5ee2103c304a5076dc4b19ae1f9fe47dba439e159e9/cryptography-48.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc90c0b39b2e3c65ef52c804b72e3c58f8a04ab2a1871272798e5f9572c17d20", size = 4698121, upload-time = "2026-05-04T22:58:16.448Z" }, + { url = "https://files.pythonhosted.org/packages/2f/59/6ff6ad6cae03bb887da2a5860b2c9805f8dac969ef01ce563336c49bd1d1/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:76341972e1eff8b4bea859f09c0d3e64b96ce931b084f9b9b7db8ef364c30eff", size = 4690042, upload-time = "2026-05-04T22:58:18.544Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b4/fc334ed8cfd705aca282fe4d8f5ae64a8e0f74932e9feecb344610cf6e4d/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:55b7718303bf06a5753dcdccf2f3945cf18ad7bffde41b61226e4db31ab89a9c", size = 5282526, upload-time = "2026-05-04T22:58:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/11/08/9f8c5386cc4cd90d8255c7cdd0f5baf459a08502a09de30dc51f553d38dc/cryptography-48.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a64697c641c7b1b2178e573cbc31c7c6684cd56883a478d75143dbb7118036db", size = 4733116, upload-time = "2026-05-04T22:58:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/b8/77/99307d7574045699f8805aa500fa0fb83422d115b5400a064ddd306d7750/cryptography-48.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:561215ea3879cb1cbbf272867e2efda62476f240fb58c64de6b393ae19246741", size = 4316030, upload-time = "2026-05-04T22:58:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/fd/36/a608b98337af3cb2aff4818e406649d30572b7031918b04c87d979495348/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ad64688338ed4bc1a6618076ba75fd7194a5f1797ac60b47afe926285adb3166", size = 4689640, upload-time = "2026-05-04T22:58:27.747Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a6/825010a291b4438aecc1f568bc428189fc1175515223632477c07dc0a6df/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:906cbf0670286c6e0044156bc7d4af9cbb0ef6db9f73e52c3ec56ba6bdde5336", size = 5237657, upload-time = "2026-05-04T22:58:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/b9/09/4e76a09b4caa29aad535ddc806f5d4c5d01885bd978bd984fbc6ca032cae/cryptography-48.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:ea8990436d914540a40ab24b6a77c0969695ed52f4a4874c5137ccf7045a7057", size = 4732362, upload-time = "2026-05-04T22:58:32.009Z" }, + { url = "https://files.pythonhosted.org/packages/18/78/444fa04a77d0cb95f417dda20d450e13c56ba8e5220fc892a1658f44f882/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c18684a7f0cc9a3cb60328f496b8e3372def7c5d2df39ac267878b05565aaaae", size = 4819580, upload-time = "2026-05-04T22:58:34.254Z" }, + { url = "https://files.pythonhosted.org/packages/38/85/ea67067c70a1fd4be2c63d35eeed82658023021affccc7b17705f8527dd2/cryptography-48.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9be5aafa5736574f8f15f262adc81b2a9869e2cfe9014d52a44633905b40d52c", size = 4963283, upload-time = "2026-05-04T22:58:36.376Z" }, + { url = "https://files.pythonhosted.org/packages/75/54/cc6d0f3deac3e81c7f847e8a189a12b6cdd65059b43dad25d4316abd849a/cryptography-48.0.0-cp314-cp314t-win32.whl", hash = "sha256:c17dfe85494deaeddc5ce251aebd1d60bbe6afc8b62071bb0b469431a000124f", size = 3270954, upload-time = "2026-05-04T22:58:38.791Z" }, + { url = "https://files.pythonhosted.org/packages/49/67/cc947e288c0758a4e5473d1dcb743037ab7785541265a969240b8885441a/cryptography-48.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27241b1dc9962e056062a8eef1991d02c3a24569c95975bd2322a8a52c6e5e12", size = 3797313, upload-time = "2026-05-04T22:58:40.746Z" }, + { url = "https://files.pythonhosted.org/packages/f2/63/61d4a4e1c6b6bab6ce1e213cd36a24c415d90e76d78c5eb8577c5541d2e8/cryptography-48.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:58d00498e8933e4a194f3076aee1b4a97dfec1a6da444535755822fe5d8b0b86", size = 7983482, upload-time = "2026-05-04T22:58:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ac/f5b5995b87770c693e2596559ffafe195b4033a57f14a82268a2842953f3/cryptography-48.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:614d0949f4790582d2cc25553abd09dd723025f0c0e7c67376a1d77196743d6e", size = 4683266, upload-time = "2026-05-04T22:58:46.064Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c6/8b14f67e18338fbc4adb76f66c001f5c3610b3e2d1837f268f47a347dbbb/cryptography-48.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ce4bfae76319a532a2dc68f82cc32f5676ee792a983187dac07183690e5c66f", size = 4696228, upload-time = "2026-05-04T22:58:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/ea/73/f808fbae9514bd91b47875b003f13e284c8c6bdfd904b7944e803937eec1/cryptography-48.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2eb992bbd4661238c5a397594c83f5b4dc2bc5b848c365c8f991b6780efcc5c7", size = 4689097, upload-time = "2026-05-04T22:58:50.9Z" }, + { url = "https://files.pythonhosted.org/packages/93/01/d86632d7d28db8ae83221995752eeb6639ffb374c2d22955648cf8d52797/cryptography-48.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:22a5cb272895dce158b2cacdfdc3debd299019659f42947dbdac6f32d68fe832", size = 5283582, upload-time = "2026-05-04T22:58:53.017Z" }, + { url = "https://files.pythonhosted.org/packages/02/e1/50edc7a50334807cc4791fc4a0ce7468b4a1416d9138eab358bfc9a3d70b/cryptography-48.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2b4d59804e8408e2fea7d1fbaf218e5ec984325221db76e6a241a9abd6cdd95c", size = 4730479, upload-time = "2026-05-04T22:58:55.611Z" }, + { url = "https://files.pythonhosted.org/packages/6f/af/99a582b1b1641ff5911ac559beb45097cf79efd4ead4657f578ef1af2d47/cryptography-48.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:984a20b0f62a26f48a3396c72e4bc34c66e356d356bf370053066b3b6d54634a", size = 4326481, upload-time = "2026-05-04T22:58:57.607Z" }, + { url = "https://files.pythonhosted.org/packages/90/ee/89aa26a06ef0a7d7611788ffd571a7c50e368cc6a4d5eef8b4884e866edb/cryptography-48.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5a5ed8fde7a1d09376ca0b40e68cd59c69fe23b1f9768bd5824f54681626032a", size = 4688713, upload-time = "2026-05-04T22:59:00.077Z" }, + { url = "https://files.pythonhosted.org/packages/70/ba/bcb1b0bb7a33d4c7c0c4d4c7874b4a62ae4f56113a5f4baefa362dfb1f0f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:8cd666227ef7af430aa5914a9910e0ddd703e75f039cef0825cd0da71b6b711a", size = 5238165, upload-time = "2026-05-04T22:59:02.317Z" }, + { url = "https://files.pythonhosted.org/packages/c9/70/ca4003b1ce5ca3dc3186ada51908c8a9b9ff7d5cab83cc0d43ee14ec144f/cryptography-48.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9071196d81abc88b3516ac8cdfad32e2b66dd4a5393a8e68a961e9161ddc6239", size = 4729947, upload-time = "2026-05-04T22:59:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/4ec7cf774207905aef1a8d11c3750d5a1db805eb380ee4e16df317870128/cryptography-48.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e2d54c8be6152856a36f0882ab231e70f8ec7f14e93cf87db8a2ed056bf160c", size = 4822059, upload-time = "2026-05-04T22:59:07.802Z" }, + { url = "https://files.pythonhosted.org/packages/1e/75/a2e55f99c16fcac7b5d6c1eb19ad8e00799854d6be5ca845f9259eae1681/cryptography-48.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5da777e32ffed6f85a7b2b3f7c5cbc88c146bfcd0a1d7baf5fcc6c52ee35dd4", size = 4960575, upload-time = "2026-05-04T22:59:09.851Z" }, + { url = "https://files.pythonhosted.org/packages/b8/23/6e6f32143ab5d8b36ca848a502c4bcd477ae75b9e1677e3530d669062578/cryptography-48.0.0-cp39-abi3-win32.whl", hash = "sha256:77a2ccbbe917f6710e05ba9adaa25fb5075620bf3ea6fb751997875aff4ae4bd", size = 3279117, upload-time = "2026-05-04T22:59:12.019Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9a/0fea98a70cf1749d41d738836f6349d97945f7c89433a259a6c2642eefeb/cryptography-48.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:16cd65b9330583e4619939b3a3843eec1e6e789744bb01e7c7e2e62e33c239c8", size = 3792100, upload-time = "2026-05-04T22:59:14.884Z" }, + { url = "https://files.pythonhosted.org/packages/be/d2/024b5e06be9d44cb021fb0e1a03d34d63989cf56a0fe62f3dfbab695b9b4/cryptography-48.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:84cf79f0dc8b36ac5da873481716e87aef31fcfa0444f9e1d8b4b2cece142855", size = 3950391, upload-time = "2026-05-04T22:59:17.415Z" }, + { url = "https://files.pythonhosted.org/packages/bc/17/3861e17c56fa0fd37491a14a8673fdb77c57fc5693cafe745ea8b06dba75/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fdfef35d751d510fcef5252703621574364fec16418c4a1e5e1055248401054b", size = 4637126, upload-time = "2026-05-04T22:59:20.197Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0a/7e226dbff530f21480727eb764973a7bff2b912f8e15cd4f129e71b56d1d/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0890f502ddf7d9c6426129c3f49f5c0a39278ed7cd6322c8755ffca6ee675a13", size = 4667270, upload-time = "2026-05-04T22:59:22.647Z" }, + { url = "https://files.pythonhosted.org/packages/3b/f2/5a72274ca9f1b2a8b44a662ee0bf1b435909deb473d6f97bcd035bcdbc71/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:ecde28a596bead48b0cfd2a1b4416c3d43074c2d785e3a398d7ec1fc4d0f7fbb", size = 4636797, upload-time = "2026-05-04T22:59:24.912Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e1/48cedb2fe63626e91ded1edad159e2a4fb8b6906c4425eb7749673077ce7/cryptography-48.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:4defde8685ae324a9eb9d818717e93b4638ef67070ac9bc15b8ca85f63048355", size = 4666800, upload-time = "2026-05-04T22:59:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ca/7e8365deec19afb2b2c7be7c1c0aa8f99633b54e90c570999acda93260fc/cryptography-48.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:db63bf618e5dea46c07de12e900fe1cdd2541e6dc9dbae772a70b7d4d4765f6a", size = 3739536, upload-time = "2026-05-04T22:59:29.61Z" }, ] [[package]] From 0dd6afc1779b66ff649975294ffd3e69af228871 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Fri, 12 Jun 2026 03:46:44 -0400 Subject: [PATCH 37/39] Documentation Improvements (#5240) Co-authored-by: Poolitzer --- .../5240.iJXyH8RNWNNQpC47SfNHzw.toml | 6 + .../{kwargs_insertion.py => bot_insertion.py} | 14 + docs/auxil/sphinx_hooks.py | 22 +- docs/source/telegram.at-tree.rst | 1 + src/telegram/__init__.py | 10 +- src/telegram/_bot.py | 612 +----------------- ...utstorycontent.py => inputstorycontent.py} | 0 src/telegram/_files/video.py | 2 +- src/telegram/_message.py | 4 +- src/telegram/request/_requestparameter.py | 2 +- tests/test_business_methods.py | 2 +- 11 files changed, 67 insertions(+), 608 deletions(-) create mode 100644 changes/unreleased/5240.iJXyH8RNWNNQpC47SfNHzw.toml rename docs/auxil/{kwargs_insertion.py => bot_insertion.py} (90%) rename src/telegram/_files/{_inputstorycontent.py => inputstorycontent.py} (100%) diff --git a/changes/unreleased/5240.iJXyH8RNWNNQpC47SfNHzw.toml b/changes/unreleased/5240.iJXyH8RNWNNQpC47SfNHzw.toml new file mode 100644 index 00000000000..24424c4695a --- /dev/null +++ b/changes/unreleased/5240.iJXyH8RNWNNQpC47SfNHzw.toml @@ -0,0 +1,6 @@ +documentation = "Documentation Improvements" + +pull_requests = [ + { uid = "5240", author_uids = ["harshil21", "Poolitzer"] }, + { uid = "5241", author_uids = ["harshil21"] }, +] \ No newline at end of file diff --git a/docs/auxil/kwargs_insertion.py b/docs/auxil/bot_insertion.py similarity index 90% rename from docs/auxil/kwargs_insertion.py rename to docs/auxil/bot_insertion.py index b2f05f8741b..008f5413856 100644 --- a/docs/auxil/kwargs_insertion.py +++ b/docs/auxil/bot_insertion.py @@ -83,6 +83,13 @@ " ``2``.", ] +RAISES_BLOCK = [ + "Raises:", + "", + " :class:`telegram.error.TelegramError`", + "", +] + def find_insert_pos_for_kwargs(lines: list[str]) -> int: """Finds the correct position to insert the keyword arguments and returns the index.""" @@ -92,6 +99,13 @@ def find_insert_pos_for_kwargs(lines: list[str]) -> int: return False +def find_insert_pos_for_raises(lines: list[str]) -> int: + """Finds the correct position to insert the Raises block and returns the index.""" + if "Raises:" in lines: + return -1 # Don't insert if there's already a Raises block + return len(lines) # Insert at the end if there's no Raises block + + def check_timeout_and_api_kwargs_presence(obj: object) -> int: """Checks if the method has timeout and api_kwargs keyword only parameters.""" sig = inspect.signature(obj) diff --git a/docs/auxil/sphinx_hooks.py b/docs/auxil/sphinx_hooks.py index 796c367e2b0..a8a3e5271e5 100644 --- a/docs/auxil/sphinx_hooks.py +++ b/docs/auxil/sphinx_hooks.py @@ -27,9 +27,11 @@ import telegram import telegram.ext from docs.auxil.admonition_inserter import AdmonitionInserter -from docs.auxil.kwargs_insertion import ( +from docs.auxil.bot_insertion import ( + RAISES_BLOCK, check_timeout_and_api_kwargs_presence, find_insert_pos_for_kwargs, + find_insert_pos_for_raises, get_updates_read_timeout_addition, keyword_args, media_write_timeout_change, @@ -85,8 +87,8 @@ def autodoc_process_docstring( app: Sphinx, what, name: str, obj: object, options, lines: list[str] ): """We do the following things: - 1) Use this method to automatically insert the Keyword Args and "Shortcuts" admonitions - for the Bot methods. + 1) Use this method to automatically insert the Keyword Args, "Shortcuts" admonitions, + and the Raises block, wherever applicable, for the Bot methods. 2) Use this method to automatically insert "Returned in" admonition into classes that are returned from the Bot methods @@ -102,13 +104,15 @@ def autodoc_process_docstring( """ # 1) Insert the Keyword Args and "Shortcuts" admonitions for the Bot methods - method_name = name.rsplit(".", maxsplit=1)[0] + method_name = name.rsplit(".", maxsplit=1)[-1] if ( name.startswith("telegram.Bot.") and what == "method" and method_name.islower() and check_timeout_and_api_kwargs_presence(obj) ): + # Logic for inserting keyword args into docstrings: + # ------------------------------------------------- insert_index = find_insert_pos_for_kwargs(lines) if not insert_index: raise ValueError( @@ -134,6 +138,16 @@ def autodoc_process_docstring( lines[insert_idx:insert_idx] = effective_insert insert_idx += len(effective_insert) + # Logic for inserting Raises: + # ------------------------------------------------- + # We will only insert the Raises block if there isn't already one. + + insert_index = find_insert_pos_for_raises(lines) + if insert_index != -1: + lines[insert_index:insert_index] = RAISES_BLOCK + + # Logic for inserting "Shortcuts" admonition: + # ------------------------------------------- ADMONITION_INSERTER.insert_admonitions( obj=typing.cast("collections.abc.Callable", obj), docstring_lines=lines, diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index 86f76d0f6ca..4e638e33864 100644 --- a/docs/source/telegram.at-tree.rst +++ b/docs/source/telegram.at-tree.rst @@ -165,6 +165,7 @@ Available Types telegram.poll telegram.pollanswer telegram.pollmedia + telegram.polloption telegram.polloptionadded telegram.polloptiondeleted telegram.preparedkeyboardbutton diff --git a/src/telegram/__init__.py b/src/telegram/__init__.py index a531daae8bd..0050a640227 100644 --- a/src/telegram/__init__.py +++ b/src/telegram/__init__.py @@ -419,11 +419,6 @@ from ._dice import Dice from ._directmessagepricechanged import DirectMessagePriceChanged from ._directmessagestopic import DirectMessagesTopic -from ._files._inputstorycontent import ( - InputStoryContent, - InputStoryContentPhoto, - InputStoryContentVideo, -) from ._files.animation import Animation from ._files.audio import Audio from ._files.chatphoto import ChatPhoto @@ -455,6 +450,11 @@ InputProfilePhotoStatic, ) from ._files.inputsticker import InputSticker +from ._files.inputstorycontent import ( + InputStoryContent, + InputStoryContentPhoto, + InputStoryContentVideo, +) from ._files.livephoto import LivePhoto from ._files.location import Location from ._files.photosize import PhotoSize diff --git a/src/telegram/_bot.py b/src/telegram/_bot.py index fd873b067bf..07939f3942f 100644 --- a/src/telegram/_bot.py +++ b/src/telegram/_bot.py @@ -926,13 +926,24 @@ async def do_api_request( instances of this class. If not specified, the raw result of the API call will be returned. + Keyword Args: + read_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to + :paramref:`telegram.request.BaseRequest.post.read_timeout`. Defaults to + :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`. + write_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to + :paramref:`telegram.request.BaseRequest.post.write_timeout`. Defaults to + :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`. + connect_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to + :paramref:`telegram.request.BaseRequest.post.connect_timeout`. Defaults to + :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`. + pool_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to + :paramref:`telegram.request.BaseRequest.post.pool_timeout`. Defaults to + :attr:`~telegram.request.BaseRequest.DEFAULT_NONE`. + Returns: The result of the API call. If :paramref:`return_type` is not specified, this is a :obj:`dict` or :obj:`bool`, otherwise an instance of :paramref:`return_type` or a tuple of :paramref:`return_type`. - - Raises: - :class:`telegram.error.TelegramError` """ if hasattr(self, endpoint): self._warn( @@ -990,10 +1001,6 @@ async def get_me( Returns: :class:`telegram.User`: A :class:`telegram.User` instance representing that bot if the credentials are valid, :obj:`None` otherwise. - - Raises: - :class:`telegram.error.TelegramError` - """ result = await self._post( "getMe", @@ -1194,10 +1201,6 @@ async def delete_message( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id, "message_id": message_id} return await self._post( @@ -1258,10 +1261,6 @@ async def send_message_draft( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -1308,9 +1307,6 @@ async def delete_messages( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = {"chat_id": chat_id, "message_ids": message_ids} return await self._post( @@ -1387,9 +1383,6 @@ async def forward_message( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "chat_id": chat_id, @@ -1458,9 +1451,6 @@ async def forward_messages( Returns: tuple[:class:`telegram.Message`]: On success, a tuple of ``MessageId`` of sent messages is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "chat_id": chat_id, @@ -1610,9 +1600,6 @@ async def send_photo( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -1785,9 +1772,6 @@ async def send_audio( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -1953,9 +1937,6 @@ async def send_document( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -2091,10 +2072,6 @@ async def send_sticker( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -2284,10 +2261,6 @@ async def send_video( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -2456,10 +2429,6 @@ async def send_video_note( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -2636,10 +2605,6 @@ async def send_animation( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -2808,10 +2773,6 @@ async def send_voice( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -2961,9 +2922,6 @@ async def send_media_group( Returns: tuple[:class:`telegram.Message`]: An array of the sent Messages. - - Raises: - :class:`telegram.error.TelegramError` """ if caption and any( [ @@ -3146,10 +3104,6 @@ async def send_location( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ if not ((latitude is not None and longitude is not None) or location): raise ValueError( @@ -3473,10 +3427,6 @@ async def send_venue( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ # The venue parameter is a convenience functionality added by us, so enforcing the # mutual exclusivity here is nothing that Telegram would handle anyway @@ -3631,10 +3581,6 @@ async def send_contact( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ # The contact parameter is a convenience functionality added by us, so enforcing the # mutual exclusivity here is nothing that Telegram would handle anyway @@ -3759,10 +3705,6 @@ async def send_game( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id, "game_short_name": game_short_name} @@ -3819,10 +3761,6 @@ async def send_chat_action( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -3999,10 +3937,6 @@ async def answer_inline_query( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ effective_results, next_offset = self._effective_inline_results( results=results, next_offset=next_offset, current_offset=current_offset @@ -4066,9 +4000,6 @@ async def save_prepared_inline_message( Returns: :class:`telegram.PreparedInlineMessage`: On success, the prepared message is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "user_id": user_id, @@ -4116,10 +4047,6 @@ async def get_user_profile_photos( Returns: :class:`telegram.UserProfilePhotos` - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"user_id": user_id, "offset": offset, "limit": limit} @@ -4182,10 +4109,6 @@ async def get_file( Returns: :class:`telegram.File` - - Raises: - :class:`telegram.error.TelegramError` - """ # Try to get the file_id from the object, if it fails, assume it's a string with contextlib.suppress(AttributeError): @@ -4248,10 +4171,6 @@ async def ban_chat_member( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -4296,10 +4215,6 @@ async def ban_chat_sender_chat( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id, "sender_chat_id": sender_chat_id} @@ -4340,10 +4255,6 @@ async def unban_chat_member( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id, "user_id": user_id, "only_if_banned": only_if_banned} @@ -4380,10 +4291,6 @@ async def unban_chat_sender_chat( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id, "sender_chat_id": sender_chat_id} @@ -4443,10 +4350,6 @@ async def answer_callback_query( Returns: :obj:`bool` On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "callback_query_id": callback_query_id, @@ -4626,10 +4529,6 @@ async def edit_message_caption( Returns: :class:`telegram.Message`: On success, if edited message is not an inline message, the edited message is returned, otherwise :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -4700,9 +4599,6 @@ async def edit_message_media( Returns: :class:`telegram.Message`: On success, if edited message is not an inline message, the edited Message is returned, otherwise :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "media": media, @@ -4761,10 +4657,6 @@ async def edit_message_reply_markup( Returns: :class:`telegram.Message`: On success, if edited message is not an inline message, the edited message is returned, otherwise :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -4845,10 +4737,6 @@ async def get_updates( Returns: tuple[:class:`telegram.Update`] - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "timeout": timeout, @@ -4995,8 +4883,7 @@ async def set_webhook( Returns: :obj:`bool` On success, :obj:`True` is returned. - Raises: - :class:`telegram.error.TelegramError` + .. _`guide to Webhooks`: https://core.telegram.org/bots/webhooks @@ -5041,10 +4928,6 @@ async def delete_webhook( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data = {"drop_pending_updates": drop_pending_updates} @@ -5075,10 +4958,6 @@ async def leave_chat( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id} @@ -5114,10 +4993,6 @@ async def get_chat( Returns: :class:`telegram.ChatFullInfo` - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id} @@ -5160,11 +5035,9 @@ async def get_chat_administrators( Returns: tuple[:class:`telegram.ChatMember`]: On success, returns a tuple of ``ChatMember`` - objects that contains information about all chat administrators. - - Raises: - :class:`telegram.error.TelegramError` - + objects that contains information about all chat administrators except + other bots. If the chat is a group or a supergroup and no administrators were + appointed, only the creator will be returned. """ data: JSONDict = {"chat_id": chat_id, "return_bots": return_bots} result = await self._post( @@ -5197,10 +5070,6 @@ async def get_chat_member_count( Returns: :obj:`int`: Number of members in the chat. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id} return await self._post( @@ -5233,10 +5102,6 @@ async def get_chat_member( Returns: :class:`telegram.ChatMember` - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id, "user_id": user_id} result = await self._post( @@ -5445,10 +5310,6 @@ async def get_game_high_scores( Returns: tuple[:class:`telegram.GameHighScore`] - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "user_id": user_id, @@ -5652,10 +5513,6 @@ async def send_invoice( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -5739,10 +5596,6 @@ async def answer_shipping_query( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "shipping_query_id": shipping_query_id, @@ -5796,10 +5649,6 @@ async def answer_pre_checkout_query( Returns: :obj:`bool`: On success, :obj:`True` is returned - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "pre_checkout_query_id": pre_checkout_query_id, @@ -5841,10 +5690,6 @@ async def answer_web_app_query( Returns: :class:`telegram.SentWebAppMessage`: On success, a sent :class:`telegram.SentWebAppMessage` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "web_app_query_id": web_app_query_id, @@ -5886,10 +5731,6 @@ async def answer_guest_query( Returns: :class:`telegram.SentGuestMessage`: On success, a :class:`telegram.SentGuestMessage` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "guest_query_id": guest_query_id, @@ -5957,9 +5798,6 @@ async def restrict_chat_member( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "chat_id": chat_id, @@ -6081,10 +5919,6 @@ async def promote_chat_member( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -6156,10 +5990,6 @@ async def set_chat_permissions( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -6201,10 +6031,6 @@ async def set_chat_administrator_custom_title( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id, "user_id": user_id, "custom_title": custom_title} @@ -6245,10 +6071,6 @@ async def export_chat_invite_link( Returns: :obj:`str`: New invite link on success. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id} return await self._post( @@ -6311,10 +6133,6 @@ async def create_chat_invite_link( Returns: :class:`telegram.ChatInviteLink` - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -6388,10 +6206,6 @@ async def edit_chat_invite_link( Returns: :class:`telegram.ChatInviteLink` - - Raises: - :class:`telegram.error.TelegramError` - """ link = invite_link.invite_link if isinstance(invite_link, ChatInviteLink) else invite_link data: JSONDict = { @@ -6442,10 +6256,6 @@ async def revoke_chat_invite_link( Returns: :class:`telegram.ChatInviteLink` - - Raises: - :class:`telegram.error.TelegramError` - """ link = invite_link.invite_link if isinstance(invite_link, ChatInviteLink) else invite_link data: JSONDict = {"chat_id": chat_id, "invite_link": link} @@ -6486,9 +6296,6 @@ async def approve_chat_join_request( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = {"chat_id": chat_id, "user_id": user_id} @@ -6526,9 +6333,6 @@ async def decline_chat_join_request( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = {"chat_id": chat_id, "user_id": user_id} @@ -6572,10 +6376,6 @@ async def set_chat_photo( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id, "photo": self._parse_file_input(photo)} return await self._post( @@ -6608,10 +6408,6 @@ async def delete_chat_photo( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id} return await self._post( @@ -6648,10 +6444,6 @@ async def set_chat_title( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id, "title": title} return await self._post( @@ -6688,10 +6480,6 @@ async def set_chat_description( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id, "description": description} @@ -6735,10 +6523,6 @@ async def set_user_emoji_status( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "user_id": user_id, @@ -6788,10 +6572,6 @@ async def pin_chat_message( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -6841,10 +6621,6 @@ async def unpin_chat_message( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -6884,10 +6660,6 @@ async def unpin_all_chat_messages( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id} return await self._post( @@ -6917,10 +6689,6 @@ async def get_sticker_set( Returns: :class:`telegram.StickerSet` - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"name": name} result = await self._post( @@ -6960,10 +6728,6 @@ async def get_custom_emoji_stickers( Returns: tuple[:class:`telegram.Sticker`] - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"custom_emoji_ids": custom_emoji_ids} result = await self._post( @@ -7016,10 +6780,6 @@ async def upload_sticker_file( Returns: :class:`telegram.File`: On success, the uploaded File is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "user_id": user_id, @@ -7075,10 +6835,6 @@ async def add_sticker_to_set( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "user_id": user_id, @@ -7119,10 +6875,6 @@ async def set_sticker_position_in_set( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "sticker": sticker if isinstance(sticker, str) else sticker.file_id, @@ -7207,9 +6959,6 @@ async def create_new_sticker_set( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "user_id": user_id, @@ -7251,10 +7000,6 @@ async def delete_sticker_from_set( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"sticker": sticker if isinstance(sticker, str) else sticker.file_id} return await self._post( @@ -7287,10 +7032,6 @@ async def delete_sticker_set( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"name": name} return await self._post( @@ -7360,10 +7101,6 @@ async def set_sticker_set_thumbnail( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "name": name, @@ -7406,10 +7143,6 @@ async def set_sticker_set_title( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"name": name, "title": title} return await self._post( @@ -7452,9 +7185,6 @@ async def set_sticker_emoji_list( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "sticker": sticker if isinstance(sticker, str) else sticker.file_id, @@ -7500,9 +7230,6 @@ async def set_sticker_keywords( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "sticker": sticker if isinstance(sticker, str) else sticker.file_id, @@ -7547,9 +7274,6 @@ async def set_sticker_mask_position( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "sticker": sticker if isinstance(sticker, str) else sticker.file_id, @@ -7589,10 +7313,6 @@ async def set_custom_emoji_sticker_set_thumbnail( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"name": name, "custom_emoji_id": custom_emoji_id} @@ -7636,10 +7356,6 @@ async def set_passport_data_errors( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"user_id": user_id, "errors": errors} return await self._post( @@ -7878,10 +7594,6 @@ async def send_poll( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ if correct_option_id is not None: @@ -7975,10 +7687,6 @@ async def stop_poll( Returns: :class:`telegram.Poll`: On success, the stopped Poll is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -8044,10 +7752,6 @@ async def send_checklist( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -8102,10 +7806,6 @@ async def edit_message_checklist( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -8222,10 +7922,6 @@ async def send_dice( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id, "emoji": emoji} @@ -8274,9 +7970,6 @@ async def get_my_default_administrator_rights( Returns: :class:`telegram.ChatAdministratorRights`: On success. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = {"for_channels": for_channels} @@ -8322,9 +8015,6 @@ async def set_my_default_administrator_rights( Returns: :obj:`bool`: Returns :obj:`True` on success. - - Raises: - :exc:`telegram.error.TelegramError` """ data: JSONDict = {"rights": rights, "for_channels": for_channels} @@ -8372,10 +8062,6 @@ async def get_my_commands( Returns: tuple[:class:`telegram.BotCommand`]: On success, the commands set for the bot. An empty tuple is returned if commands are not set. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"scope": scope, "language_code": language_code} @@ -8437,10 +8123,6 @@ async def set_my_commands( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ cmds = [c if isinstance(c, BotCommand) else BotCommand(c[0], c[1]) for c in commands] data: JSONDict = {"commands": cmds, "scope": scope, "language_code": language_code} @@ -8486,9 +8168,6 @@ async def delete_my_commands( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = {"scope": scope, "language_code": language_code} @@ -8520,10 +8199,6 @@ async def log_out( Returns: :obj:`True`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ return await self._post( "logOut", @@ -8551,10 +8226,6 @@ async def close( Returns: :obj:`True`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ return await self._post( "close", @@ -8673,10 +8344,6 @@ async def copy_message( Returns: :class:`telegram.MessageId`: On success, the :class:`telegram.MessageId` of the sent message is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ if allow_sending_without_reply is not DEFAULT_NONE and reply_parameters is not None: raise ValueError( @@ -8777,10 +8444,6 @@ async def copy_messages( Returns: tuple[:class:`telegram.MessageId`]: On success, a tuple of :class:`~telegram.MessageId` of the sent messages is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -9057,10 +8720,6 @@ async def get_forum_topic_icon_stickers( Returns: tuple[:class:`telegram.Sticker`] - - Raises: - :class:`telegram.error.TelegramError` - """ result = await self._post( "getForumTopicIconStickers", @@ -9110,9 +8769,6 @@ async def create_forum_topic( Returns: :class:`telegram.ForumTopic` - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "chat_id": chat_id, @@ -9167,10 +8823,6 @@ async def edit_forum_topic( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -9213,10 +8865,6 @@ async def close_forum_topic( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -9257,10 +8905,6 @@ async def reopen_forum_topic( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -9301,10 +8945,6 @@ async def delete_forum_topic( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -9346,10 +8986,6 @@ async def unpin_all_forum_topic_messages( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -9388,9 +9024,6 @@ async def unpin_all_general_forum_topic_messages( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = {"chat_id": chat_id} @@ -9430,10 +9063,6 @@ async def edit_general_forum_topic( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id, "name": name} @@ -9469,10 +9098,6 @@ async def close_general_forum_topic( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id} @@ -9509,10 +9134,6 @@ async def reopen_general_forum_topic( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id} @@ -9549,10 +9170,6 @@ async def hide_general_forum_topic( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id} @@ -9588,10 +9205,6 @@ async def unhide_general_forum_topic( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"chat_id": chat_id} @@ -9633,10 +9246,6 @@ async def set_my_description( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"description": description, "language_code": language_code} @@ -9678,10 +9287,6 @@ async def set_my_short_description( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"short_description": short_description, "language_code": language_code} @@ -9714,10 +9319,6 @@ async def get_my_description( Returns: :class:`telegram.BotDescription`: On success, the bot description is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data = {"language_code": language_code} return BotDescription.de_json( @@ -9753,10 +9354,6 @@ async def get_my_short_description( Returns: :class:`telegram.BotShortDescription`: On success, the bot short description is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data = {"language_code": language_code} return BotShortDescription.de_json( @@ -9803,10 +9400,6 @@ async def set_my_name( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = {"name": name, "language_code": language_code} @@ -9839,10 +9432,6 @@ async def get_my_name( Returns: :class:`telegram.BotName`: On success, the bot name is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data = {"language_code": language_code} return BotName.de_json( @@ -9882,9 +9471,6 @@ async def get_user_chat_boosts( Returns: :class:`telegram.UserChatBoosts`: On success, the object containing the list of boosts is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = {"chat_id": chat_id, "user_id": user_id} return UserChatBoosts.de_json( @@ -9945,9 +9531,6 @@ async def set_message_reaction( Returns: :obj:`bool` On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ allowed_reactions: set[str] = set(ReactionEmoji) parsed_reaction = ( @@ -10041,9 +9624,6 @@ async def gift_premium_subscription( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "user_id": user_id, @@ -10084,9 +9664,6 @@ async def get_business_connection( Returns: :class:`telegram.BusinessConnection`: On success, the object containing the business connection information is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = {"business_connection_id": business_connection_id} return BusinessConnection.de_json( @@ -10167,9 +9744,6 @@ async def get_business_account_gifts( Returns: :class:`telegram.OwnedGifts` - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10218,9 +9792,6 @@ async def get_business_account_star_balance( Returns: :class:`telegram.StarAmount` - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = {"business_connection_id": business_connection_id} return StarAmount.de_json( @@ -10265,9 +9836,6 @@ async def read_business_message( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10315,9 +9883,6 @@ async def delete_business_messages( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10397,9 +9962,6 @@ async def post_story( Returns: :class:`Story` - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10477,9 +10039,6 @@ async def edit_story( Returns: :class:`Story` - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10525,9 +10084,6 @@ async def delete_story( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10572,9 +10128,6 @@ async def set_business_account_name( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10615,9 +10168,6 @@ async def set_business_account_username( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10657,9 +10207,6 @@ async def set_business_account_bio( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10704,9 +10251,6 @@ async def set_business_account_gift_settings( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10751,10 +10295,6 @@ async def set_business_account_profile_photo( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10798,9 +10338,6 @@ async def remove_business_account_profile_photo( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10841,9 +10378,6 @@ async def convert_gift_to_stars( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10896,9 +10430,6 @@ async def upgrade_gift( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10953,9 +10484,6 @@ async def transfer_gift( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -10999,9 +10527,6 @@ async def transfer_business_account_stars( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -11050,9 +10575,6 @@ async def replace_sticker_in_set( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "user_id": user_id, @@ -11092,10 +10614,6 @@ async def refund_star_payment( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "user_id": user_id, @@ -11136,9 +10654,6 @@ async def get_star_transactions( Returns: :class:`telegram.StarTransactions`: On success. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = {"offset": offset, "limit": limit} @@ -11184,9 +10699,6 @@ async def edit_user_star_subscription( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "user_id": user_id, @@ -11224,9 +10736,6 @@ async def get_managed_bot_access_settings( Returns: :class:`telegram.BotAccessSettings`: The access settings of the managed bot. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { @@ -11275,9 +10784,6 @@ async def set_managed_bot_access_settings( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { @@ -11322,9 +10828,6 @@ async def get_user_personal_chat_messages( Returns: tuple[:class:`telegram.Message`, ...]: On success, a tuple of :class:`telegram.Message` objects is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = {"user_id": user_id, "limit": limit} @@ -11429,9 +10932,6 @@ async def send_paid_media( Returns: :class:`telegram.Message`: On success, the sent message is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { @@ -11506,10 +11006,6 @@ async def create_chat_subscription_invite_link( Returns: :class:`telegram.ChatInviteLink` - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -11559,10 +11055,6 @@ async def edit_chat_subscription_invite_link( Returns: :class:`telegram.ChatInviteLink` - - Raises: - :class:`telegram.error.TelegramError` - """ link = invite_link.invite_link if isinstance(invite_link, ChatInviteLink) else invite_link data: JSONDict = { @@ -11599,9 +11091,6 @@ async def get_available_gifts( Returns: :class:`telegram.Gifts` - - Raises: - :class:`telegram.error.TelegramError` """ return Gifts.de_json( await self._post( @@ -11674,9 +11163,6 @@ async def send_gift( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "user_id": user_id, @@ -11721,9 +11207,6 @@ async def verify_chat( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "chat_id": chat_id, @@ -11763,9 +11246,6 @@ async def verify_user( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "user_id": user_id, @@ -11801,9 +11281,6 @@ async def remove_chat_verification( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "chat_id": chat_id, @@ -11838,9 +11315,6 @@ async def remove_user_verification( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "user_id": user_id, @@ -11870,9 +11344,6 @@ async def get_my_star_balance( Returns: :class:`telegram.StarAmount` - - Raises: - :class:`telegram.error.TelegramError` """ return StarAmount.de_json( await self._post( @@ -11917,9 +11388,6 @@ async def approve_suggested_post( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "chat_id": chat_id, @@ -11964,9 +11432,6 @@ async def decline_suggested_post( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "chat_id": chat_id, @@ -12026,9 +11491,6 @@ async def repost_story( Returns: :class:`telegram.Story` - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "business_connection_id": business_connection_id, @@ -12097,9 +11559,6 @@ async def get_user_gifts( Returns: :class:`telegram.OwnedGifts`: The owned gifts for the user. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "user_id": user_id, @@ -12180,9 +11639,6 @@ async def get_chat_gifts( Returns: :class:`telegram.OwnedGifts`: The owned gifts for the chat. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { @@ -12230,10 +11686,6 @@ async def set_my_profile_photo( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "photo": photo, @@ -12264,10 +11716,6 @@ async def remove_my_profile_photo( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` - """ return await self._post( @@ -12307,9 +11755,6 @@ async def get_user_profile_audios( Returns: :class:`telegram.UserProfileAudios` - - Raises: - :class:`telegram.error.TelegramError` """ data = { @@ -12358,9 +11803,6 @@ async def set_chat_member_tag( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "chat_id": chat_id, @@ -12398,9 +11840,6 @@ async def get_managed_bot_token( Returns: :obj:`str`: The token of the managed bot. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "user_id": user_id, @@ -12436,9 +11875,6 @@ async def replace_managed_bot_token( Returns: :obj:`str`: The new token of the managed bot. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "user_id": user_id, @@ -12480,9 +11916,6 @@ async def save_prepared_keyboard_button( Returns: :class:`telegram.PreparedKeyboardButton` - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "user_id": user_id, @@ -12600,9 +12033,6 @@ async def send_live_photo( Returns: :class:`telegram.Message`: On success, the sent Message is returned. - Raises: - :class:`telegram.error.TelegramError` - """ data: JSONDict = { "chat_id": chat_id, @@ -12666,9 +12096,6 @@ async def delete_message_reaction( removed, if the reaction were added by a chat. Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "chat_id": chat_id, @@ -12716,9 +12143,6 @@ async def delete_all_message_reactions( Returns: :obj:`bool`: On success, :obj:`True` is returned. - - Raises: - :class:`telegram.error.TelegramError` """ data: JSONDict = { "chat_id": chat_id, diff --git a/src/telegram/_files/_inputstorycontent.py b/src/telegram/_files/inputstorycontent.py similarity index 100% rename from src/telegram/_files/_inputstorycontent.py rename to src/telegram/_files/inputstorycontent.py diff --git a/src/telegram/_files/video.py b/src/telegram/_files/video.py index d197e053cd2..eeb2fb584b8 100644 --- a/src/telegram/_files/video.py +++ b/src/telegram/_files/video.py @@ -105,7 +105,7 @@ class Video(_BaseThumbedMedium): .. deprecated:: v22.2 |time-period-int-deprecated| - qualities (Sequence[:class:`telegram.VideoQuality`]): Optional. List of available qualities + qualities (tuple[:class:`telegram.VideoQuality`]): Optional. List of available qualities of the video .. versionadded:: 22.7 diff --git a/src/telegram/_message.py b/src/telegram/_message.py index 5bc02a53ccc..d8d1a607b08 100644 --- a/src/telegram/_message.py +++ b/src/telegram/_message.py @@ -693,7 +693,7 @@ class Message(MaybeInaccessibleMessage): .. versionadded:: 22.7 sender_tag (:obj:`str`, optional): Tag or custom title of the sender of the message; for - supergroups only + supergroups only. .. versionadded:: 22.7 poll_option_added (:class:`telegram.PollOptionAdded`, optional): Service message: @@ -1146,7 +1146,7 @@ class Message(MaybeInaccessibleMessage): .. versionadded:: 22.7 sender_tag (:obj:`str`): Optional. Tag or custom title of the sender of the message; for - supergroups only + supergroups only. .. versionadded:: 22.7 poll_option_added (:class:`telegram.PollOptionAdded`): Optional. Service message: diff --git a/src/telegram/request/_requestparameter.py b/src/telegram/request/_requestparameter.py index 26a2285324f..70024558c0f 100644 --- a/src/telegram/request/_requestparameter.py +++ b/src/telegram/request/_requestparameter.py @@ -24,11 +24,11 @@ from dataclasses import dataclass from typing import final -from telegram._files._inputstorycontent import InputStoryContent from telegram._files.inputfile import InputFile from telegram._files.inputmedia import InputMedia, InputPaidMedia from telegram._files.inputprofilephoto import InputProfilePhoto, InputProfilePhotoStatic from telegram._files.inputsticker import InputSticker +from telegram._files.inputstorycontent import InputStoryContent from telegram._telegramobject import TelegramObject from telegram._utils.datetime import to_timestamp from telegram._utils.enum import StringEnum diff --git a/tests/test_business_methods.py b/tests/test_business_methods.py index 43a666eb07e..b31a7c4c270 100644 --- a/tests/test_business_methods.py +++ b/tests/test_business_methods.py @@ -33,7 +33,7 @@ StoryAreaTypeUniqueGift, User, ) -from telegram._files._inputstorycontent import InputStoryContentVideo +from telegram._files.inputstorycontent import InputStoryContentVideo from telegram._files.sticker import Sticker from telegram._gifts import AcceptedGiftTypes, Gift from telegram._inline.inlinekeyboardbutton import InlineKeyboardButton From 5d663af824914b2dbfa944ff6af60fc1888e99d6 Mon Sep 17 00:00:00 2001 From: Poolitzer Date: Fri, 12 Jun 2026 10:02:13 +0200 Subject: [PATCH 38/39] Bump version to v22.8 (#5262) --- .../5125.filter-fix.toml | 0 .../5177.fg9Y3ZSboi94mttv3eas2L.toml | 0 .../5183.Enoyw5yoT4S9uRxn7v5ud5.toml | 0 .../5184.YKdeCfWDDXk8v3DJx8nTeg.toml | 0 .../5185.VTFw6hNJ4A9mGayu2rduUt.toml | 0 .../5190.5mL9K6trXT6Y4vLgqxy9jE.toml | 0 .../5191.B6JK8KhgoqqawRrrWaSCDH.toml | 0 .../5192.VYDBGMwXeTVyvXESmEnfFa.toml | 0 .../5193.GqEK23Erwr49GQ79uJho38.toml | 0 .../5194.bnCTjH5QVYuyFDp4iCwJoV.toml | 0 .../5196.7keq7yJhXbMb9RyShLHz4D.toml | 0 .../5198.haofa4LoE6k2WQoAUSizM8.toml | 0 .../5199.4qywU9Faav8YhWivBBykyJ.toml | 0 .../5203.jxMZ7aSe7eDKXTvfEdZGzw.toml | 0 .../5205.KBqhJA9kouNAJmQYNwZHT2.toml | 0 .../5206.nXEjjZXrYZiWroBfBeQR3N.toml | 0 .../5209.msPrg8bYnmr89qXR4fr5bG.toml | 0 .../5217.5AVeFbQnudaXWyZ3TM7E5B.toml | 0 .../5220.iAgLEYgfuW46wtvCUx3dhM.toml | 0 .../5221.UEBLb7KFNwZkZ5fC3CyDBD.toml | 0 .../5222.dGHpTKrbVb9VQV8pVv9Lii.toml | 0 .../5223.6Ttu6UoyzkVQddzKrz947W.toml | 0 .../5224.YVKcje5xUdXTjJJurmAVVD.toml | 0 .../5229.87PBN4GFkuAaDhhgFwCYkY.toml | 0 .../5233.YJVhiqtdZZ57TBRdNh2swZ.toml | 0 .../5237.effective-user-fix.toml | 0 .../5240.iJXyH8RNWNNQpC47SfNHzw.toml | 0 .../5245.3Xu2uH53fGn822LB5Y7Pqk.toml | 0 .../5247.3DSJ9C36s7HCYSNTEUnDpT.toml | 0 .../5250.dTuzQhYiw2T8gfvp6Skj89.toml | 0 .../5252.Qo4J766e8AEw35gf9gLaJE.toml | 0 .../5253.A9BQTN98eeqDPBrVZ674qa.toml | 0 .../5254.kaanCYDk82Jg6P8S9pJG3C.toml | 0 .../5255.Sx6a4Mf4dD3bUfNWSty78T.toml | 0 .../5256.VFGHhZz3SchcMmJJLETLzs.toml | 0 .../5257.c4g6KoZswRyGb9ypHwfKzB.toml | 0 .../5259.iZALQbcHZUBRwuvxwMLpf4.toml | 0 .../5262.ULa4t5ti2rRFyMWgpmVccq.toml | 5 + .../5262.nZcWnWfJyHhUwG484KJx9q.toml | 5 + src/telegram/_bot.py | 54 +++++----- src/telegram/_botaccesssettings.py | 2 +- src/telegram/_chat.py | 8 +- src/telegram/_chatmember.py | 6 +- src/telegram/_chatpermissions.py | 6 +- src/telegram/_files/inputmedia.py | 66 ++++++------ src/telegram/_files/livephoto.py | 2 +- src/telegram/_keyboardbutton.py | 4 +- src/telegram/_keyboardbuttonrequest.py | 2 +- src/telegram/_managedbot.py | 4 +- src/telegram/_message.py | 40 +++---- src/telegram/_paidmedia.py | 2 +- src/telegram/_poll.py | 100 +++++++++--------- src/telegram/_preparedkeyboardbutton.py | 2 +- src/telegram/_reply.py | 8 +- src/telegram/_sentguestmessage.py | 2 +- src/telegram/_update.py | 20 ++-- src/telegram/_user.py | 24 ++--- src/telegram/_utils/types.py | 2 +- src/telegram/_version.py | 2 +- src/telegram/constants.py | 36 +++---- .../ext/_handlers/managedbotupdatedhandler.py | 2 +- src/telegram/ext/filters.py | 10 +- 62 files changed, 212 insertions(+), 202 deletions(-) rename changes/{unreleased => 22.8_2026-06-12}/5125.filter-fix.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5177.fg9Y3ZSboi94mttv3eas2L.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5183.Enoyw5yoT4S9uRxn7v5ud5.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5184.YKdeCfWDDXk8v3DJx8nTeg.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5185.VTFw6hNJ4A9mGayu2rduUt.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5190.5mL9K6trXT6Y4vLgqxy9jE.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5191.B6JK8KhgoqqawRrrWaSCDH.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5192.VYDBGMwXeTVyvXESmEnfFa.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5193.GqEK23Erwr49GQ79uJho38.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5194.bnCTjH5QVYuyFDp4iCwJoV.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5196.7keq7yJhXbMb9RyShLHz4D.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5198.haofa4LoE6k2WQoAUSizM8.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5199.4qywU9Faav8YhWivBBykyJ.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5203.jxMZ7aSe7eDKXTvfEdZGzw.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5205.KBqhJA9kouNAJmQYNwZHT2.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5206.nXEjjZXrYZiWroBfBeQR3N.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5209.msPrg8bYnmr89qXR4fr5bG.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5217.5AVeFbQnudaXWyZ3TM7E5B.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5220.iAgLEYgfuW46wtvCUx3dhM.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5221.UEBLb7KFNwZkZ5fC3CyDBD.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5222.dGHpTKrbVb9VQV8pVv9Lii.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5223.6Ttu6UoyzkVQddzKrz947W.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5224.YVKcje5xUdXTjJJurmAVVD.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5229.87PBN4GFkuAaDhhgFwCYkY.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5233.YJVhiqtdZZ57TBRdNh2swZ.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5237.effective-user-fix.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5240.iJXyH8RNWNNQpC47SfNHzw.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5245.3Xu2uH53fGn822LB5Y7Pqk.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5247.3DSJ9C36s7HCYSNTEUnDpT.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5250.dTuzQhYiw2T8gfvp6Skj89.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5252.Qo4J766e8AEw35gf9gLaJE.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5253.A9BQTN98eeqDPBrVZ674qa.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5254.kaanCYDk82Jg6P8S9pJG3C.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5255.Sx6a4Mf4dD3bUfNWSty78T.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5256.VFGHhZz3SchcMmJJLETLzs.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5257.c4g6KoZswRyGb9ypHwfKzB.toml (100%) rename changes/{unreleased => 22.8_2026-06-12}/5259.iZALQbcHZUBRwuvxwMLpf4.toml (100%) create mode 100644 changes/22.8_2026-06-12/5262.ULa4t5ti2rRFyMWgpmVccq.toml create mode 100644 changes/22.8_2026-06-12/5262.nZcWnWfJyHhUwG484KJx9q.toml diff --git a/changes/unreleased/5125.filter-fix.toml b/changes/22.8_2026-06-12/5125.filter-fix.toml similarity index 100% rename from changes/unreleased/5125.filter-fix.toml rename to changes/22.8_2026-06-12/5125.filter-fix.toml diff --git a/changes/unreleased/5177.fg9Y3ZSboi94mttv3eas2L.toml b/changes/22.8_2026-06-12/5177.fg9Y3ZSboi94mttv3eas2L.toml similarity index 100% rename from changes/unreleased/5177.fg9Y3ZSboi94mttv3eas2L.toml rename to changes/22.8_2026-06-12/5177.fg9Y3ZSboi94mttv3eas2L.toml diff --git a/changes/unreleased/5183.Enoyw5yoT4S9uRxn7v5ud5.toml b/changes/22.8_2026-06-12/5183.Enoyw5yoT4S9uRxn7v5ud5.toml similarity index 100% rename from changes/unreleased/5183.Enoyw5yoT4S9uRxn7v5ud5.toml rename to changes/22.8_2026-06-12/5183.Enoyw5yoT4S9uRxn7v5ud5.toml diff --git a/changes/unreleased/5184.YKdeCfWDDXk8v3DJx8nTeg.toml b/changes/22.8_2026-06-12/5184.YKdeCfWDDXk8v3DJx8nTeg.toml similarity index 100% rename from changes/unreleased/5184.YKdeCfWDDXk8v3DJx8nTeg.toml rename to changes/22.8_2026-06-12/5184.YKdeCfWDDXk8v3DJx8nTeg.toml diff --git a/changes/unreleased/5185.VTFw6hNJ4A9mGayu2rduUt.toml b/changes/22.8_2026-06-12/5185.VTFw6hNJ4A9mGayu2rduUt.toml similarity index 100% rename from changes/unreleased/5185.VTFw6hNJ4A9mGayu2rduUt.toml rename to changes/22.8_2026-06-12/5185.VTFw6hNJ4A9mGayu2rduUt.toml diff --git a/changes/unreleased/5190.5mL9K6trXT6Y4vLgqxy9jE.toml b/changes/22.8_2026-06-12/5190.5mL9K6trXT6Y4vLgqxy9jE.toml similarity index 100% rename from changes/unreleased/5190.5mL9K6trXT6Y4vLgqxy9jE.toml rename to changes/22.8_2026-06-12/5190.5mL9K6trXT6Y4vLgqxy9jE.toml diff --git a/changes/unreleased/5191.B6JK8KhgoqqawRrrWaSCDH.toml b/changes/22.8_2026-06-12/5191.B6JK8KhgoqqawRrrWaSCDH.toml similarity index 100% rename from changes/unreleased/5191.B6JK8KhgoqqawRrrWaSCDH.toml rename to changes/22.8_2026-06-12/5191.B6JK8KhgoqqawRrrWaSCDH.toml diff --git a/changes/unreleased/5192.VYDBGMwXeTVyvXESmEnfFa.toml b/changes/22.8_2026-06-12/5192.VYDBGMwXeTVyvXESmEnfFa.toml similarity index 100% rename from changes/unreleased/5192.VYDBGMwXeTVyvXESmEnfFa.toml rename to changes/22.8_2026-06-12/5192.VYDBGMwXeTVyvXESmEnfFa.toml diff --git a/changes/unreleased/5193.GqEK23Erwr49GQ79uJho38.toml b/changes/22.8_2026-06-12/5193.GqEK23Erwr49GQ79uJho38.toml similarity index 100% rename from changes/unreleased/5193.GqEK23Erwr49GQ79uJho38.toml rename to changes/22.8_2026-06-12/5193.GqEK23Erwr49GQ79uJho38.toml diff --git a/changes/unreleased/5194.bnCTjH5QVYuyFDp4iCwJoV.toml b/changes/22.8_2026-06-12/5194.bnCTjH5QVYuyFDp4iCwJoV.toml similarity index 100% rename from changes/unreleased/5194.bnCTjH5QVYuyFDp4iCwJoV.toml rename to changes/22.8_2026-06-12/5194.bnCTjH5QVYuyFDp4iCwJoV.toml diff --git a/changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml b/changes/22.8_2026-06-12/5196.7keq7yJhXbMb9RyShLHz4D.toml similarity index 100% rename from changes/unreleased/5196.7keq7yJhXbMb9RyShLHz4D.toml rename to changes/22.8_2026-06-12/5196.7keq7yJhXbMb9RyShLHz4D.toml diff --git a/changes/unreleased/5198.haofa4LoE6k2WQoAUSizM8.toml b/changes/22.8_2026-06-12/5198.haofa4LoE6k2WQoAUSizM8.toml similarity index 100% rename from changes/unreleased/5198.haofa4LoE6k2WQoAUSizM8.toml rename to changes/22.8_2026-06-12/5198.haofa4LoE6k2WQoAUSizM8.toml diff --git a/changes/unreleased/5199.4qywU9Faav8YhWivBBykyJ.toml b/changes/22.8_2026-06-12/5199.4qywU9Faav8YhWivBBykyJ.toml similarity index 100% rename from changes/unreleased/5199.4qywU9Faav8YhWivBBykyJ.toml rename to changes/22.8_2026-06-12/5199.4qywU9Faav8YhWivBBykyJ.toml diff --git a/changes/unreleased/5203.jxMZ7aSe7eDKXTvfEdZGzw.toml b/changes/22.8_2026-06-12/5203.jxMZ7aSe7eDKXTvfEdZGzw.toml similarity index 100% rename from changes/unreleased/5203.jxMZ7aSe7eDKXTvfEdZGzw.toml rename to changes/22.8_2026-06-12/5203.jxMZ7aSe7eDKXTvfEdZGzw.toml diff --git a/changes/unreleased/5205.KBqhJA9kouNAJmQYNwZHT2.toml b/changes/22.8_2026-06-12/5205.KBqhJA9kouNAJmQYNwZHT2.toml similarity index 100% rename from changes/unreleased/5205.KBqhJA9kouNAJmQYNwZHT2.toml rename to changes/22.8_2026-06-12/5205.KBqhJA9kouNAJmQYNwZHT2.toml diff --git a/changes/unreleased/5206.nXEjjZXrYZiWroBfBeQR3N.toml b/changes/22.8_2026-06-12/5206.nXEjjZXrYZiWroBfBeQR3N.toml similarity index 100% rename from changes/unreleased/5206.nXEjjZXrYZiWroBfBeQR3N.toml rename to changes/22.8_2026-06-12/5206.nXEjjZXrYZiWroBfBeQR3N.toml diff --git a/changes/unreleased/5209.msPrg8bYnmr89qXR4fr5bG.toml b/changes/22.8_2026-06-12/5209.msPrg8bYnmr89qXR4fr5bG.toml similarity index 100% rename from changes/unreleased/5209.msPrg8bYnmr89qXR4fr5bG.toml rename to changes/22.8_2026-06-12/5209.msPrg8bYnmr89qXR4fr5bG.toml diff --git a/changes/unreleased/5217.5AVeFbQnudaXWyZ3TM7E5B.toml b/changes/22.8_2026-06-12/5217.5AVeFbQnudaXWyZ3TM7E5B.toml similarity index 100% rename from changes/unreleased/5217.5AVeFbQnudaXWyZ3TM7E5B.toml rename to changes/22.8_2026-06-12/5217.5AVeFbQnudaXWyZ3TM7E5B.toml diff --git a/changes/unreleased/5220.iAgLEYgfuW46wtvCUx3dhM.toml b/changes/22.8_2026-06-12/5220.iAgLEYgfuW46wtvCUx3dhM.toml similarity index 100% rename from changes/unreleased/5220.iAgLEYgfuW46wtvCUx3dhM.toml rename to changes/22.8_2026-06-12/5220.iAgLEYgfuW46wtvCUx3dhM.toml diff --git a/changes/unreleased/5221.UEBLb7KFNwZkZ5fC3CyDBD.toml b/changes/22.8_2026-06-12/5221.UEBLb7KFNwZkZ5fC3CyDBD.toml similarity index 100% rename from changes/unreleased/5221.UEBLb7KFNwZkZ5fC3CyDBD.toml rename to changes/22.8_2026-06-12/5221.UEBLb7KFNwZkZ5fC3CyDBD.toml diff --git a/changes/unreleased/5222.dGHpTKrbVb9VQV8pVv9Lii.toml b/changes/22.8_2026-06-12/5222.dGHpTKrbVb9VQV8pVv9Lii.toml similarity index 100% rename from changes/unreleased/5222.dGHpTKrbVb9VQV8pVv9Lii.toml rename to changes/22.8_2026-06-12/5222.dGHpTKrbVb9VQV8pVv9Lii.toml diff --git a/changes/unreleased/5223.6Ttu6UoyzkVQddzKrz947W.toml b/changes/22.8_2026-06-12/5223.6Ttu6UoyzkVQddzKrz947W.toml similarity index 100% rename from changes/unreleased/5223.6Ttu6UoyzkVQddzKrz947W.toml rename to changes/22.8_2026-06-12/5223.6Ttu6UoyzkVQddzKrz947W.toml diff --git a/changes/unreleased/5224.YVKcje5xUdXTjJJurmAVVD.toml b/changes/22.8_2026-06-12/5224.YVKcje5xUdXTjJJurmAVVD.toml similarity index 100% rename from changes/unreleased/5224.YVKcje5xUdXTjJJurmAVVD.toml rename to changes/22.8_2026-06-12/5224.YVKcje5xUdXTjJJurmAVVD.toml diff --git a/changes/unreleased/5229.87PBN4GFkuAaDhhgFwCYkY.toml b/changes/22.8_2026-06-12/5229.87PBN4GFkuAaDhhgFwCYkY.toml similarity index 100% rename from changes/unreleased/5229.87PBN4GFkuAaDhhgFwCYkY.toml rename to changes/22.8_2026-06-12/5229.87PBN4GFkuAaDhhgFwCYkY.toml diff --git a/changes/unreleased/5233.YJVhiqtdZZ57TBRdNh2swZ.toml b/changes/22.8_2026-06-12/5233.YJVhiqtdZZ57TBRdNh2swZ.toml similarity index 100% rename from changes/unreleased/5233.YJVhiqtdZZ57TBRdNh2swZ.toml rename to changes/22.8_2026-06-12/5233.YJVhiqtdZZ57TBRdNh2swZ.toml diff --git a/changes/unreleased/5237.effective-user-fix.toml b/changes/22.8_2026-06-12/5237.effective-user-fix.toml similarity index 100% rename from changes/unreleased/5237.effective-user-fix.toml rename to changes/22.8_2026-06-12/5237.effective-user-fix.toml diff --git a/changes/unreleased/5240.iJXyH8RNWNNQpC47SfNHzw.toml b/changes/22.8_2026-06-12/5240.iJXyH8RNWNNQpC47SfNHzw.toml similarity index 100% rename from changes/unreleased/5240.iJXyH8RNWNNQpC47SfNHzw.toml rename to changes/22.8_2026-06-12/5240.iJXyH8RNWNNQpC47SfNHzw.toml diff --git a/changes/unreleased/5245.3Xu2uH53fGn822LB5Y7Pqk.toml b/changes/22.8_2026-06-12/5245.3Xu2uH53fGn822LB5Y7Pqk.toml similarity index 100% rename from changes/unreleased/5245.3Xu2uH53fGn822LB5Y7Pqk.toml rename to changes/22.8_2026-06-12/5245.3Xu2uH53fGn822LB5Y7Pqk.toml diff --git a/changes/unreleased/5247.3DSJ9C36s7HCYSNTEUnDpT.toml b/changes/22.8_2026-06-12/5247.3DSJ9C36s7HCYSNTEUnDpT.toml similarity index 100% rename from changes/unreleased/5247.3DSJ9C36s7HCYSNTEUnDpT.toml rename to changes/22.8_2026-06-12/5247.3DSJ9C36s7HCYSNTEUnDpT.toml diff --git a/changes/unreleased/5250.dTuzQhYiw2T8gfvp6Skj89.toml b/changes/22.8_2026-06-12/5250.dTuzQhYiw2T8gfvp6Skj89.toml similarity index 100% rename from changes/unreleased/5250.dTuzQhYiw2T8gfvp6Skj89.toml rename to changes/22.8_2026-06-12/5250.dTuzQhYiw2T8gfvp6Skj89.toml diff --git a/changes/unreleased/5252.Qo4J766e8AEw35gf9gLaJE.toml b/changes/22.8_2026-06-12/5252.Qo4J766e8AEw35gf9gLaJE.toml similarity index 100% rename from changes/unreleased/5252.Qo4J766e8AEw35gf9gLaJE.toml rename to changes/22.8_2026-06-12/5252.Qo4J766e8AEw35gf9gLaJE.toml diff --git a/changes/unreleased/5253.A9BQTN98eeqDPBrVZ674qa.toml b/changes/22.8_2026-06-12/5253.A9BQTN98eeqDPBrVZ674qa.toml similarity index 100% rename from changes/unreleased/5253.A9BQTN98eeqDPBrVZ674qa.toml rename to changes/22.8_2026-06-12/5253.A9BQTN98eeqDPBrVZ674qa.toml diff --git a/changes/unreleased/5254.kaanCYDk82Jg6P8S9pJG3C.toml b/changes/22.8_2026-06-12/5254.kaanCYDk82Jg6P8S9pJG3C.toml similarity index 100% rename from changes/unreleased/5254.kaanCYDk82Jg6P8S9pJG3C.toml rename to changes/22.8_2026-06-12/5254.kaanCYDk82Jg6P8S9pJG3C.toml diff --git a/changes/unreleased/5255.Sx6a4Mf4dD3bUfNWSty78T.toml b/changes/22.8_2026-06-12/5255.Sx6a4Mf4dD3bUfNWSty78T.toml similarity index 100% rename from changes/unreleased/5255.Sx6a4Mf4dD3bUfNWSty78T.toml rename to changes/22.8_2026-06-12/5255.Sx6a4Mf4dD3bUfNWSty78T.toml diff --git a/changes/unreleased/5256.VFGHhZz3SchcMmJJLETLzs.toml b/changes/22.8_2026-06-12/5256.VFGHhZz3SchcMmJJLETLzs.toml similarity index 100% rename from changes/unreleased/5256.VFGHhZz3SchcMmJJLETLzs.toml rename to changes/22.8_2026-06-12/5256.VFGHhZz3SchcMmJJLETLzs.toml diff --git a/changes/unreleased/5257.c4g6KoZswRyGb9ypHwfKzB.toml b/changes/22.8_2026-06-12/5257.c4g6KoZswRyGb9ypHwfKzB.toml similarity index 100% rename from changes/unreleased/5257.c4g6KoZswRyGb9ypHwfKzB.toml rename to changes/22.8_2026-06-12/5257.c4g6KoZswRyGb9ypHwfKzB.toml diff --git a/changes/unreleased/5259.iZALQbcHZUBRwuvxwMLpf4.toml b/changes/22.8_2026-06-12/5259.iZALQbcHZUBRwuvxwMLpf4.toml similarity index 100% rename from changes/unreleased/5259.iZALQbcHZUBRwuvxwMLpf4.toml rename to changes/22.8_2026-06-12/5259.iZALQbcHZUBRwuvxwMLpf4.toml diff --git a/changes/22.8_2026-06-12/5262.ULa4t5ti2rRFyMWgpmVccq.toml b/changes/22.8_2026-06-12/5262.ULa4t5ti2rRFyMWgpmVccq.toml new file mode 100644 index 00000000000..b24cd91e5b7 --- /dev/null +++ b/changes/22.8_2026-06-12/5262.ULa4t5ti2rRFyMWgpmVccq.toml @@ -0,0 +1,5 @@ +other = "Bump Version to 22.8" +[[pull_requests]] +uid = "5262" +author_uids = ["Poolitzer"] +closes_threads = [] diff --git a/changes/22.8_2026-06-12/5262.nZcWnWfJyHhUwG484KJx9q.toml b/changes/22.8_2026-06-12/5262.nZcWnWfJyHhUwG484KJx9q.toml new file mode 100644 index 00000000000..b24cd91e5b7 --- /dev/null +++ b/changes/22.8_2026-06-12/5262.nZcWnWfJyHhUwG484KJx9q.toml @@ -0,0 +1,5 @@ +other = "Bump Version to 22.8" +[[pull_requests]] +uid = "5262" +author_uids = ["Poolitzer"] +closes_threads = [] diff --git a/src/telegram/_bot.py b/src/telegram/_bot.py index 07939f3942f..3cb94e3ad07 100644 --- a/src/telegram/_bot.py +++ b/src/telegram/_bot.py @@ -1247,7 +1247,7 @@ async def send_message_draft( 0-:tg-const:`telegram.constants.MessageLimit.MAX_TEXT_LENGTH` characters after entities parsing. Pass an empty text to show a "Thinking..." placeholder. - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 Bot API 10.0 now makes this an optional parameter. message_thread_id (:obj:`int`, optional): Unique identifier for the target @@ -5031,7 +5031,7 @@ async def get_chat_administrators( that are administrators of the chat. By default, bots other than the current bot are omitted. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Returns: tuple[:class:`telegram.ChatMember`]: On success, returns a tuple of ``ChatMember`` @@ -5721,7 +5721,7 @@ async def answer_guest_query( ) -> SentGuestMessage: """Use this method to reply to a received guest message. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: guest_query_id (:obj:`str`): Unique identifier for the query to be answered. @@ -7376,7 +7376,7 @@ async def send_poll( is_anonymous: bool | None = None, type: str | None = None, # pylint: disable=redefined-builtin allows_multiple_answers: bool | None = None, - # tags: deprecated in NEXT.VERSION, to be removed + # tags: deprecated in 22.8, to be removed # replaced by `correct_option_ids` correct_option_id: CorrectOptionID | None = None, is_closed: bool | None = None, @@ -7446,7 +7446,7 @@ async def send_poll( correct_option_id (:obj:`int`, optional): 0-based identifier of the correct answer option, required for polls in quiz mode. - .. deprecated:: NEXT.VERSION + .. deprecated:: 22.8 Bot API 9.6 replaces this with :paramref:`correct_option_ids` instead. explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect @@ -7517,44 +7517,44 @@ async def send_poll( change the chosen answer options, defaults to :obj:`False` for quizzes and to :obj:`True` for regular polls - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 allow_adding_options (:obj:`bool`, optional): :obj:`True`, if answer options can be added to the poll after creation; not supported for anonymous polls and quizzes - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 hide_results_until_closes (:obj:`bool`, optional): :obj:`True`, if poll results must be shown only after the poll closes - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 correct_option_ids (Sequence[:class:`int`], optional): A list of monotonically increasing 0-based identifiers of the correct answer options, required for polls in quiz mode. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 description (:obj:`str`, optional): Description of the poll to be sent, 0-:tg-const:`telegram.Poll.MAX_DESCRIPTION_CHARACTERS` characters after entities parsing. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 description_parse_mode (:obj:`str`, optional): Mode for parsing entities in the poll description. See the constants in :class:`telegram.constants.ParseMode` - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 description_entities (Sequence[:class:`telegram.MessageEntity`], optional): A JSON-serialized list of special entities that appear in the poll description, which can be specified instead of :paramref:`description_parse_mode` - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 shuffle_options (:obj:`bool`, optional): :obj:`True`, if the poll options must be shown in random order - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 members_only (:obj:`bool`, optional): :obj:`True`, if voting is limited to users who have been members of the chat where the poll is being sent for more than :tg-const:`telegram.Poll.MIN_MEMBERSHIP_HOURS` hours; for channel chats only - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 country_codes (Sequence[:obj:`str`], optional): A list of 0-:tg-const:`telegram.constants.PollLimit.MAX_COUNTRY_CODES` two-letter ``ISO 3166-1 alpha-2`` country codes indicating the countries from which users can @@ -7562,15 +7562,15 @@ async def send_poll( users with anonymous numbers to vote. If omitted or empty, then users from any country can participate in the poll. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 explanation_media (:class:`telegram.InputPollMedia`, optional): Media added to the quiz explanation - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 media (:class:`telegram.InputPollMedia`, optional): Media added to the poll description. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -7599,7 +7599,7 @@ async def send_poll( if correct_option_id is not None: warn( PTBDeprecationWarning( - version="NEXT.VERSION", + version="22.8", message="Bot API 9.6 deprecated `correct_option_id` in favour of " "`correct_option_ids`, please use that.", ), @@ -10728,7 +10728,7 @@ async def get_managed_bot_access_settings( """ Use this method to get the access settings of a managed bot. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: user_id (:obj:`int`): User identifier of the managed bot whose access settings will be @@ -10770,7 +10770,7 @@ async def set_managed_bot_access_settings( """ Use this method to change the access settings of a managed bot. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: user_id (:obj:`int`): User identifier of the managed bot whose access settings will be @@ -10817,7 +10817,7 @@ async def get_user_personal_chat_messages( Use this method to get the last messages from the personal chat (i.e., the chat currently added to their profile) of a given user. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: user_id (:obj:`int`): Unique identifier of the target user. @@ -11833,7 +11833,7 @@ async def get_managed_bot_token( """ Use this method to get the token of a managed bot. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: user_id (:obj:`int`): User identifier of the managed bot whose token will be returned. @@ -11868,7 +11868,7 @@ async def replace_managed_bot_token( """ Use this method to revoke the current token of a managed bot and generate a new one. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: user_id (:obj:`int`): User identifier of the managed bot whose token will be replaced. @@ -11904,7 +11904,7 @@ async def save_prepared_keyboard_button( """ Stores a keyboard button that can be used by a user within a Mini App. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: user_id (:obj:`int`): Unique identifier of the target user that can use the button. @@ -11970,7 +11970,7 @@ async def send_live_photo( .. seealso:: :wiki:`Working with Files and Media ` - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: chat_id (:obj:`int` | :obj:`str`): |chat_id_channel| @@ -12085,7 +12085,7 @@ async def delete_message_reaction( The bot must have the :attr:`~telegram.ChatMemberAdministrator.can_delete_messages` administrator right in the chat. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: chat_id (:obj:`int` | :obj:`str`): |chat_id_group| @@ -12132,7 +12132,7 @@ async def delete_all_message_reactions( :attr:`~telegram.ChatMemberAdministrator.can_delete_messages` administrator right in the chat. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: chat_id (:obj:`int` | :obj:`str`): |chat_id_group| diff --git a/src/telegram/_botaccesssettings.py b/src/telegram/_botaccesssettings.py index 745d63ae67b..07158a77527 100644 --- a/src/telegram/_botaccesssettings.py +++ b/src/telegram/_botaccesssettings.py @@ -37,7 +37,7 @@ class BotAccessSettings(TelegramObject): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`is_access_restricted` and :attr:`added_users` are equal. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: is_access_restricted (:obj:`bool`): :obj:`True`, if only selected users can access the bot. diff --git a/src/telegram/_chat.py b/src/telegram/_chat.py index d7b7a60c73b..fcdec9a6d71 100644 --- a/src/telegram/_chat.py +++ b/src/telegram/_chat.py @@ -1110,7 +1110,7 @@ async def send_message_draft( For the documentation of the arguments, please see :meth:`telegram.Bot.send_message_draft`. - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 Bot API 10.0 makes the ``text`` argument optional. Returns: @@ -1392,7 +1392,7 @@ async def send_live_photo( For the documentation of the arguments, please see :meth:`telegram.Bot.send_live_photo`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Returns: :class:`telegram.Message`: On success, instance representing the message posted. @@ -4155,7 +4155,7 @@ async def delete_reaction( For the documentation of the arguments, please see :meth:`telegram.Bot.delete_message_reaction`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Returns: :obj:`bool`: On success, :obj:`True` is returned. @@ -4195,7 +4195,7 @@ async def delete_all_reactions( For the documentation of the arguments, please see :meth:`telegram.Bot.delete_all_message_reactions`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Returns: :obj:`bool`: On success, :obj:`True` is returned. diff --git a/src/telegram/_chatmember.py b/src/telegram/_chatmember.py index 58cce787710..eafacdfedba 100644 --- a/src/telegram/_chatmember.py +++ b/src/telegram/_chatmember.py @@ -521,7 +521,7 @@ class ChatMemberRestricted(ChatMember): can_react_to_messages (:obj:`bool`): :obj:`True`, if the user is allowed to react to messages. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 tag (:obj:`str`, optional): Tag of the member. .. versionadded:: 22.7 @@ -582,7 +582,7 @@ class ChatMemberRestricted(ChatMember): can_react_to_messages (:obj:`bool`): :obj:`True`, if the user is allowed to react to messages. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 tag (:obj:`str`): Optional. Tag of the member. .. versionadded:: 22.7 @@ -632,7 +632,7 @@ def __init__( can_send_voice_notes: bool, can_edit_tag: bool, tag: str | None = None, - # tags: NEXT.VERSION + # tags: 22.8 # temporarily optional to make it not breaking can_react_to_messages: bool | None = None, *, diff --git a/src/telegram/_chatpermissions.py b/src/telegram/_chatpermissions.py index 7ca1871473d..8b93b7fe123 100644 --- a/src/telegram/_chatpermissions.py +++ b/src/telegram/_chatpermissions.py @@ -50,7 +50,7 @@ class ChatPermissions(TelegramObject): .. versionchanged:: 22.7 :attr:`can_edit_tag` is considered as well when comparing objects of this type in terms of equality. - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 :attr:`can_react_to_messages` is considered as well when comparing objects of this type in terms of equality. @@ -106,7 +106,7 @@ class ChatPermissions(TelegramObject): can_react_to_messages (:obj:`bool`, optional): :obj:`True`, if the user is allowed to react to messages. If omitted, defaults to the value of :attr:`can_send_messages`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Attributes: can_send_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to send text @@ -155,7 +155,7 @@ class ChatPermissions(TelegramObject): can_react_to_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to react to messages. If omitted, defaults to the value of :attr:`can_send_messages`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ diff --git a/src/telegram/_files/inputmedia.py b/src/telegram/_files/inputmedia.py index 80886258414..f33301ee5e6 100644 --- a/src/telegram/_files/inputmedia.py +++ b/src/telegram/_files/inputmedia.py @@ -176,7 +176,7 @@ class InputPaidMedia(TelegramObject): LIVE_PHOTO: Final[str] = constants.InputPaidMediaType.LIVE_PHOTO """:const:`telegram.constants.InputPaidMediaType.LIVE_PHOTO` - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ __slots__ = ("media", "type") @@ -342,7 +342,7 @@ class InputPaidMediaLivePhoto(InputPaidMedia): .. seealso:: :wiki:`Working with Files and Media ` - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \ @@ -404,8 +404,8 @@ class InputMediaAnimation(InputMedia): filename_depr (:obj:`str`, optional): Positional placeholder for keyword only parameter :paramref:`filename`. For backward compatibility. - .. versionadded:: NEXT.VERSION - .. deprecated:: NEXT.VERSION + .. versionadded:: 22.8 + .. deprecated:: 22.8 This parameter is deprecated, use :paramref:`filename` instead. caption (:obj:`str`, optional): Caption of the animation to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters @@ -441,7 +441,7 @@ class InputMediaAnimation(InputMedia): :obj:`tempfile` module. .. versionadded:: 13.1 - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 This parameter is now keyword-only. Attributes: @@ -494,7 +494,7 @@ def __init__( height: int | None = None, duration: TimePeriod | None = None, caption_entities: Sequence[MessageEntity] | None = None, - # tag: deprecated NEXT.VERSION + # tag: deprecated 22.8 filename_depr: str | None = None, # - has_spoiler: bool | None = None, @@ -509,7 +509,7 @@ def __init__( if filename_depr is not None: warn( PTBDeprecationWarning( - "NEXT.VERSION", + "22.8", "Positional passing of `filename` or keyword usage of `filename_depr`" " is deprecated. `filename` will become a keyword-only argument.", ), @@ -565,8 +565,8 @@ class InputMediaPhoto(InputMedia): filename_depr (:obj:`str`, optional): Positional placeholder for keyword only parameter :paramref:`filename`. For backward compatibility. - .. versionadded:: NEXT.VERSION - .. deprecated:: NEXT.VERSION + .. versionadded:: 22.8 + .. deprecated:: 22.8 This parameter is deprecated, use :paramref:`filename` instead. caption (:obj:`str`, optional ): Caption of the photo to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after @@ -590,7 +590,7 @@ class InputMediaPhoto(InputMedia): :obj:`tempfile` module. .. versionadded:: 13.1 - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 This parameter is now keyword-only. Attributes: @@ -626,7 +626,7 @@ def __init__( caption: str | None = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Sequence[MessageEntity] | None = None, - # tag: deprecated NEXT.VERSION + # tag: deprecated 22.8 filename_depr: str | None = None, # - has_spoiler: bool | None = None, @@ -640,7 +640,7 @@ def __init__( if filename_depr is not None: warn( PTBDeprecationWarning( - "NEXT.VERSION", + "22.8", "Positional passing of `filename` or keyword usage of `filename_depr`" " is deprecated. `filename` will become a keyword-only argument.", ), @@ -693,8 +693,8 @@ class InputMediaVideo(InputMedia): filename_depr (:obj:`str`, optional): Positional placeholder for keyword only parameter :paramref:`filename`. For backward compatibility. - .. versionadded:: NEXT.VERSION - .. deprecated:: NEXT.VERSION + .. versionadded:: 22.8 + .. deprecated:: 22.8 This parameter is deprecated, use :paramref:`filename` instead. caption (:obj:`str`, optional): Caption of the video to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after @@ -738,7 +738,7 @@ class InputMediaVideo(InputMedia): :obj:`tempfile` module. .. versionadded:: 13.1 - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 This parameter is now keyword-only. Attributes: @@ -803,7 +803,7 @@ def __init__( supports_streaming: bool | None = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Sequence[MessageEntity] | None = None, - # tag: deprecated NEXT.VERSION + # tag: deprecated 22.8 filename_depr: str | None = None, # - has_spoiler: bool | None = None, @@ -820,7 +820,7 @@ def __init__( if filename_depr is not None: warn( PTBDeprecationWarning( - "NEXT.VERSION", + "22.8", "Positional passing of `filename` or keyword usage of `filename_depr`" " is deprecated. `filename` will become a keyword-only argument.", ), @@ -869,7 +869,7 @@ def duration(self) -> int | dtm.timedelta | None: class InputMediaLocation(_BaseInputMedia): """Represents a location to be sent. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: latitude (:obj:`float`): Latitude of the location. @@ -905,7 +905,7 @@ def __init__( class InputMediaVenue(_BaseInputMedia): """Represents a venue to be sent. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: latitude (:obj:`float`): Latitude of the location. @@ -976,7 +976,7 @@ class InputMediaSticker(_BaseInputMedia): .. seealso:: :wiki:`Working with Files and Media ` - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` | \ @@ -1039,8 +1039,8 @@ class InputMediaAudio(InputMedia): filename_depr (:obj:`str`, optional): Positional placeholder for keyword only parameter :paramref:`filename`. For backward compatibility. - .. versionadded:: NEXT.VERSION - .. deprecated:: NEXT.VERSION + .. versionadded:: 22.8 + .. deprecated:: 22.8 This parameter is deprecated, use :paramref:`filename` instead. caption (:obj:`str`, optional): Caption of the audio to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after @@ -1070,7 +1070,7 @@ class InputMediaAudio(InputMedia): :obj:`tempfile` module. .. versionadded:: 13.1 - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 This parameter is now keyword-only. Attributes: @@ -1111,7 +1111,7 @@ def __init__( performer: str | None = None, title: str | None = None, caption_entities: Sequence[MessageEntity] | None = None, - # tag: deprecated NEXT.VERSION + # tag: deprecated 22.8 filename_depr: str | None = None, # - thumbnail: "FileInput | None" = None, @@ -1124,7 +1124,7 @@ def __init__( if filename_depr is not None: warn( PTBDeprecationWarning( - "NEXT.VERSION", + "22.8", "Positional passing of `filename` or keyword usage of `filename_depr`" " is deprecated. `filename` will become a keyword-only argument.", ), @@ -1181,8 +1181,8 @@ class InputMediaDocument(InputMedia): filename_depr (:obj:`str`, optional): Positional placeholder for keyword only parameter :paramref:`filename`. For backward compatibility. - .. versionadded:: NEXT.VERSION - .. deprecated:: NEXT.VERSION + .. versionadded:: 22.8 + .. deprecated:: 22.8 This parameter is deprecated, use :paramref:`filename` instead. caption (:obj:`str`, optional): Caption of the document to be sent, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters after @@ -1207,7 +1207,7 @@ class InputMediaDocument(InputMedia): :obj:`tempfile` module. .. versionadded:: 13.1 - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 This parameter is now keyword-only. Attributes: @@ -1240,7 +1240,7 @@ def __init__( parse_mode: ODVInput[str] = DEFAULT_NONE, disable_content_type_detection: bool | None = None, caption_entities: Sequence[MessageEntity] | None = None, - # tag: deprecated NEXT.VERSION + # tag: deprecated 22.8 filename_depr: str | None = None, # - thumbnail: "FileInput | None" = None, @@ -1253,7 +1253,7 @@ def __init__( if filename_depr is not None: warn( PTBDeprecationWarning( - "NEXT.VERSION", + "22.8", "Positional passing of `filename` or keyword usage of `filename_depr`" " is deprecated. `filename` will become a keyword-only argument.", ), @@ -1285,7 +1285,7 @@ class InputMediaLivePhoto(InputMedia): .. seealso:: :wiki:`Working with Files and Media ` - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: media (:obj:`str` | :term:`file object` | :class:`~telegram.InputFile` | :obj:`bytes` \ @@ -1364,7 +1364,7 @@ def __init__( ) """Type alias for InputPollMedia objects. -versionadded:: NEXT.VERSION +versionadded:: 22.8 """ InputPollOptionMedia: TypeAlias = ( @@ -1378,5 +1378,5 @@ def __init__( ) """Type alias for InputPollOptionMedia objects. -.. versionadded:: NEXT.VERSION +.. versionadded:: 22.8 """ diff --git a/src/telegram/_files/livephoto.py b/src/telegram/_files/livephoto.py index 3700827fa6d..46edb118869 100644 --- a/src/telegram/_files/livephoto.py +++ b/src/telegram/_files/livephoto.py @@ -39,7 +39,7 @@ class LivePhoto(_BaseMedium): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`file_unique_id` is equal. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: file_id (:obj:`str`): Identifier for the video file which can be used to download or reuse diff --git a/src/telegram/_keyboardbutton.py b/src/telegram/_keyboardbutton.py index 98aeeaeea35..27d6e6ae955 100644 --- a/src/telegram/_keyboardbutton.py +++ b/src/telegram/_keyboardbutton.py @@ -130,7 +130,7 @@ class KeyboardButton(TelegramObject): in the `@BotFather ` Mini App. Available in private chats only. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Attributes: @@ -185,7 +185,7 @@ class KeyboardButton(TelegramObject): in the `@BotFather ` Mini App. Available in private chats only. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ __slots__ = ( diff --git a/src/telegram/_keyboardbuttonrequest.py b/src/telegram/_keyboardbuttonrequest.py index fa8e3513c1c..780ec4850d5 100644 --- a/src/telegram/_keyboardbuttonrequest.py +++ b/src/telegram/_keyboardbuttonrequest.py @@ -280,7 +280,7 @@ class KeyboardButtonRequestManagedBot(TelegramObject): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`request_id` is equal. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: request_id (:obj:`int`): Signed 32-bit identifier of the request. Must be unique diff --git a/src/telegram/_managedbot.py b/src/telegram/_managedbot.py index 109983c656e..2ead362d78b 100644 --- a/src/telegram/_managedbot.py +++ b/src/telegram/_managedbot.py @@ -38,7 +38,7 @@ class ManagedBotCreated(TelegramObject): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`bot` is equal. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: bot (:class:`telegram.User`): Information about the bot. The bot's token can be fetched @@ -79,7 +79,7 @@ class ManagedBotUpdated(TelegramObject): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`user` and :attr:`bot` are equal. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: user (:class:`telegram.User`): User that created the bot. diff --git a/src/telegram/_message.py b/src/telegram/_message.py index d8d1a607b08..c52351df67d 100644 --- a/src/telegram/_message.py +++ b/src/telegram/_message.py @@ -699,40 +699,40 @@ class Message(MaybeInaccessibleMessage): poll_option_added (:class:`telegram.PollOptionAdded`, optional): Service message: answer option was added to a poll. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 poll_option_deleted (:class:`telegram.PollOptionDeleted`, optional): Service message: answer option was deleted from a poll. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 reply_to_poll_option_id (:obj:`str`, optional): Persistent identifier of the specific poll option that is being replied to. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 managed_bot_created (:class:`telegram.ManagedBotCreated`, optional): Service message: user created a bot that will be managed by the current bot. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 guest_bot_caller_user (:class:`telegram.User`, optional): For a message sent by a guest bot, this is the user whose original message triggered the bot's response. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 guest_bot_caller_chat (:class:`telegram.Chat`, optional): For a message sent by a guest bot, this is the chat whose original message triggered the bot's response. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 guest_query_id (:obj:`str`, optional): The unique identifier for the guest query. Use this identifier with the method :meth:`telegram.Bot.answer_guest_query` to send a response message. If non-empty, the message belongs to the chat where the guest bot was summoned, which may not coincide with other existing bot chats sharing the same identifier. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 live_photo (:class:`telegram.LivePhoto`, optional): Message is a live photo, information about the live photo. For backward compatibility, when this field is set, the photo field will also be set. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Attributes: message_id (:obj:`int`): Unique message identifier inside this chat. In specific instances @@ -1152,39 +1152,39 @@ class Message(MaybeInaccessibleMessage): poll_option_added (:class:`telegram.PollOptionAdded`): Optional. Service message: answer option was added to a poll. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 poll_option_deleted (:class:`telegram.PollOptionDeleted`): Optional. Service message: answer option was deleted from a poll. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 reply_to_poll_option_id (:obj:`str`): Optional. Persistent identifier of the specific poll option that is being replied to. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 managed_bot_created (:class:`telegram.ManagedBotCreated`): Optional. Service message: user created a bot that will be managed by the current bot. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 guest_bot_caller_user (:class:`telegram.User`): Optional. For a message sent by a guest bot, this is the user whose original message triggered the bot's response. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 guest_bot_caller_chat (:class:`telegram.Chat`): Optional. For a message sent by a guest bot, this is the chat whose original message triggered the bot's response. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 guest_query_id (:obj:`str`): Optional. The unique identifier for the guest query. Use this identifier with the method :meth:`telegram.Bot.answer_guest_query` to send a response message. If non-empty, the message belongs to the chat where the guest bot was summoned, which may not coincide with other existing bot chats sharing the same identifier. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 live_photo (:class:`telegram.LivePhoto`): Optional. Message is a live photo, information about the live photo. For backward compatibility, when this field is set, the photo field will also be set. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 .. |custom_emoji_no_md1_support| replace:: Since custom emoji entities are not supported by :attr:`~telegram.constants.ParseMode.MARKDOWN`, this method now raises a @@ -2279,7 +2279,7 @@ async def reply_text_draft( .. versionadded:: 22.6 - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 Bot API 10.0 makes the ``text`` argument optional. Returns: @@ -2756,7 +2756,7 @@ async def reply_live_photo( For the documentation of the arguments, please see :meth:`telegram.Bot.send_live_photo`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Keyword Args: do_quote (:obj:`bool` | :obj:`dict`, optional): |do_quote| @@ -5400,7 +5400,7 @@ async def delete_reaction( For the documentation of the arguments, please see :meth:`telegram.Bot.delete_message_reaction`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Returns: :obj:`bool` On success, :obj:`True` is returned. @@ -5436,7 +5436,7 @@ async def answer_guest_query( For the documentation of the arguments, please see :meth:`telegram.Bot.answer_guest_query`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Returns: :class:`telegram.SentGuestMessage`: On success, a diff --git a/src/telegram/_paidmedia.py b/src/telegram/_paidmedia.py index ecf6830cd99..6b746147cb3 100644 --- a/src/telegram/_paidmedia.py +++ b/src/telegram/_paidmedia.py @@ -262,7 +262,7 @@ class PaidMediaLivePhoto(PaidMedia): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`live_photo` are equal. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: type (:obj:`str`): Type of the paid media, always :tg-const:`telegram.PaidMedia.LIVE_PHOTO` diff --git a/src/telegram/_poll.py b/src/telegram/_poll.py index 2f834403038..dd3689492ac 100644 --- a/src/telegram/_poll.py +++ b/src/telegram/_poll.py @@ -65,7 +65,7 @@ class PollMedia(TelegramObject): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if all of their attributes are equal. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: animation (:class:`telegram.Animation`, optional): Media is an animation, information about @@ -195,7 +195,7 @@ class InputPollOption(TelegramObject): This list is empty if the text does not contain entities. media (:class:`telegram.InputPollOptionMedia`, optional): Media added to the poll option. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Attributes: text (:obj:`str`): Option text, @@ -210,7 +210,7 @@ class InputPollOption(TelegramObject): This list is empty if the text does not contain entities. media (:class:`telegram.InputPollOptionMedia`): Optional. Media added to the poll option. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ __slots__ = ("media", "text", "text_entities", "text_parse_mode") @@ -234,18 +234,18 @@ def __init__( self._freeze() - # tags: deprecated NEXT.VERSION + # tags: deprecated 22.8 @classmethod def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "InputPollOption": """See :meth:`telegram.TelegramObject.de_json`. The :paramref:`media` field will not be included for deserialization. - .. deprecated:: NEXT.VERSION + .. deprecated:: 22.8 This class is input only and will be removed in the next version. """ warn( PTBDeprecationWarning( - "NEXT.VERSION", + "22.8", "`InputPollOption.de_json` is deprecated. This class is input only and will be " "removed in the next version. The `media` field will not be included for " "deserialization.", @@ -267,7 +267,7 @@ class PollOption(TelegramObject): considered equal, if their :attr:`text`, :attr:`voter_count` and :attr:`persistent_id` are equal. - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 Added attribute :attr:`persistent_id` to equality checks. @@ -275,7 +275,7 @@ class PollOption(TelegramObject): persistent_id (:obj:`str`): Unique identifier of the option, persistent on option addition and deletion. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 text (:obj:`str`): Option text, :tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH` characters. @@ -287,25 +287,25 @@ class PollOption(TelegramObject): .. versionadded:: 21.2 media (:class:`telegram.PollMedia`, optional): Media added to the poll option. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 added_by_user (:class:`telegram.User`, optional): User who added the option; omitted if the option wasn't added by a user after poll creation. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 added_by_chat (:class:`telegram.Chat`, optional): Chat that added the option; omitted if the option wasn't added by a chat after poll creation. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 addition_date (:obj:`datetime.datetime`, optional): Point in time when the option was added; omitted if the option existed in the original poll. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Attributes: persistent_id (:obj:`str`): Unique identifier of the option, persistent on option addition and deletion. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 text (:obj:`str`): Option text, :tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH` characters. @@ -318,19 +318,19 @@ class PollOption(TelegramObject): .. versionadded:: 21.2 media (:class:`telegram.PollMedia`): Optional. Media added to the poll option. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 added_by_user (:class:`telegram.User`): Optional. User who added the option; omitted if the option wasn't added by a user after poll creation. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 added_by_chat (:class:`telegram.Chat`): Optional. Chat that added the option; omitted if the option wasn't added by a chat after poll creation. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 addition_date (:obj:`datetime.datetime`): Optional. Point in time when the option was added; omitted if the option existed in the original poll. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ __slots__ = ( @@ -353,7 +353,7 @@ def __init__( added_by_chat: Chat | None = None, addition_date: dtm.datetime | None = None, media: PollMedia | None = None, - # tags: required in NEXT.VERSION, bot api 9.6 + # tags: required in 22.8, bot api 9.6 # temporarily optional to avoid breaking changes persistent_id: str | None = None, *, @@ -474,7 +474,7 @@ class PollAnswer(TelegramObject): option_persistent_ids (Sequence[:obj:`str`]): Persistent identifiers of the chosen answer options. May be empty if the vote was retracted. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 user (:class:`telegram.User`, optional): The user that changed the answer to the poll, if the voter isn't anonymous. If the voter is anonymous, this field will contain the user :tg-const:`telegram.constants.ChatID.FAKE_CHANNEL` for backwards compatibility. @@ -496,7 +496,7 @@ class PollAnswer(TelegramObject): option_persistent_ids (tuple[:obj:`str`]): Persistent identifiers of the chosen answer options. May be empty if the vote was retracted. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 user (:class:`telegram.User`): Optional. The user, who changed the answer to the poll, if the voter isn't anonymous. If the voter is anonymous, this field will contain the user :tg-const:`telegram.constants.ChatID.FAKE_CHANNEL` for backwards compatibility @@ -517,7 +517,7 @@ def __init__( option_ids: Sequence[int], user: User | None = None, voter_chat: Chat | None = None, - # tags: required in NEXT.VERSION, bot api 9.6 + # tags: required in 22.8, bot api 9.6 # temporarily optional to avoid breaking changes option_persistent_ids: Sequence[str] | None = None, *, @@ -560,7 +560,7 @@ class PollOptionAdded(TelegramObject): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`option_persistent_id`, and :attr:`option_text` are equal. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: option_persistent_id (:obj:`str`): Unique identifier of the added option. @@ -675,7 +675,7 @@ class PollOptionDeleted(TelegramObject): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`option_persistent_id`, :attr:`option_text` are equal. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: option_persistent_id (:obj:`str`): Unique identifier of the deleted option. @@ -809,12 +809,12 @@ class Poll(TelegramObject): members of the chat where the poll was originally sent for more than :tg-const:`telegram.Poll.MIN_MEMBERSHIP_HOURS` hours. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 correct_option_id (:obj:`int`, optional): A zero based identifier of the correct answer option. Available only for closed polls in the quiz mode, which were sent (not forwarded), by the bot or to a private chat with the bot. - .. deprecated:: NEXT.VERSION + .. deprecated:: 22.8 Use :paramref:`correct_option_ids` instead. explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, @@ -831,7 +831,7 @@ class Poll(TelegramObject): explanation_media (:class:`telegram.PollMedia`, optional): Media added to the quiz explanation. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 open_period (:obj:`int` | :class:`datetime.timedelta`, optional): Amount of time in seconds the poll will be active after creation. @@ -850,30 +850,30 @@ class Poll(TelegramObject): allows_revoting (:obj:`bool`, optional): :obj:`True`, if the poll allows to change the chosenanswer options. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 correct_option_ids (Sequence[:class:`int`], optional): Array of 0-based identifiers of the correct answer options. Available only for polls in quiz mode which are closed or were sent (not forwarded) by the bot or to the private chat with the bot. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 country_codes (Sequence[:obj:`str`], optional): A list of two-letter ``ISO 3166-1 alpha-2`` country codes indicating the countries from which users can vote in the poll. The country code ``"FT"`` is used for users with anonymous numbers. If omitted, then users from any country can participate in the poll. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 description (:obj:`str`, optional): Description of the poll; for polls inside the :class:`~telegram.Message` object only. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 description_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities like usernames, URLs, bot commands, etc. that appear in the description - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 media (:class:`telegram.PollMedia`, optional): Media added to the poll description; for polls inside the :class:`~telegram.Message` object only. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Attributes: id (:obj:`str`): Unique poll identifier. @@ -892,7 +892,7 @@ class Poll(TelegramObject): members of the chat where the poll was originally sent for more than :tg-const:`telegram.Poll.MIN_MEMBERSHIP_HOURS` hours. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 explanation (:obj:`str`): Optional. Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-:tg-const:`telegram.Poll.MAX_EXPLANATION_LENGTH` characters. @@ -908,7 +908,7 @@ class Poll(TelegramObject): explanation_media (:class:`telegram.PollMedia`): Optional. Media added to the quiz explanation. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 open_period (:obj:`int` | :class:`datetime.timedelta`): Optional. Amount of time in seconds the poll will be active after creation. @@ -928,30 +928,30 @@ class Poll(TelegramObject): allows_revoting (:obj:`bool`): :obj:`True`, if the poll allows to change the chosenanswer options - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 correct_option_ids (tuple[:class:`int`]): Array of 0-based identifiers of the correct answer options. Available only for polls in quiz mode which are closed or were sent (not forwarded) by the bot or to the private chat with the bot. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 country_codes (tuple[:obj:`str`]): Optional. A list of two-letter ``ISO 3166-1 alpha-2`` country codes indicating the countries from which users can vote in the poll. The country code ``"FT"`` is used for users with anonymous numbers. If omitted, then users from any country can participate in the poll. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 description (:obj:`str`): Optional. Description of the poll; for polls inside the Message object only - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 description_entities (tuple[:class:`telegram.MessageEntity`]): Special entities like usernames, URLs, bot commands, etc. that appear in the description - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 media (:class:`telegram.PollMedia`): Optional. Media added to the poll description; for polls inside the Message object only. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ @@ -989,7 +989,7 @@ def __init__( is_anonymous: bool, type: str, # pylint: disable=redefined-builtin allows_multiple_answers: bool, - # tags: deprecated NEXT.VERSION + # tags: deprecated 22.8 # Removed in bot api 9.6: correct_option_id: int | None = None, # --- @@ -998,7 +998,7 @@ def __init__( open_period: TimePeriod | None = None, close_date: dtm.datetime | None = None, question_entities: Sequence[MessageEntity] | None = None, - # tags: required in NEXT.VERSION + # tags: required in 22.8 # temporarily optional to avoid breaking changes allows_revoting: bool | None = None, members_only: bool | None = None, @@ -1030,11 +1030,11 @@ def __init__( self.allows_revoting: bool = allows_revoting self.members_only: bool = members_only - # tag: deprecated NEXT.VERSION + # tag: deprecated 22.8 if correct_option_id is not None: warn( PTBDeprecationWarning( - "NEXT.VERSION", + "22.8", "The parameter `correct_option_id` is deprecated. " "Use `correct_option_ids` instead.", ), @@ -1196,7 +1196,7 @@ def parse_description_entity(self, entity: MessageEntity) -> str: """Returns the text in :attr:`description` from a given :class:`telegram.MessageEntity` of :attr:`description_entities`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Note: This method is present because Telegram calculates the offset and length in @@ -1227,7 +1227,7 @@ def parse_description_entities( It contains entities from this polls description filtered by their ``type`` attribute as the key, and the text that each entity belongs to as the value of the :obj:`dict`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Note: This method should always be used instead of the :attr:`description_entities` @@ -1255,12 +1255,12 @@ def correct_option_id(self) -> int | None: option. Available only for closed polls in the quiz mode, which were sent (not forwarded), by the bot or to a private chat with the bot. - .. deprecated:: NEXT.VERSION + .. deprecated:: 22.8 Use :attr:`correct_option_ids` instead. """ warn( PTBDeprecationWarning( - "NEXT.VERSION", + "22.8", "The attribute `correct_option_id` is deprecated. " "Use `correct_option_ids` instead.", ), @@ -1325,10 +1325,10 @@ def correct_option_id(self) -> int | None: MAX_DESCRIPTION_CHARACTERS: Final[int] = constants.PollLimit.MAX_DESCRIPTION_CHARACTERS """:const:`telegram.constants.PollLimit.MAX_DESCRIPTION_CHARACTERS` - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ MIN_MEMBERSHIP_HOURS: Final[int] = constants.PollLimit.MIN_MEMBERSHIP_HOURS """:const:`telegram.constants.PollLimit.MIN_MEMBERSHIP_HOURS` - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ diff --git a/src/telegram/_preparedkeyboardbutton.py b/src/telegram/_preparedkeyboardbutton.py index 0a650f9469e..cd83f6b0dd7 100644 --- a/src/telegram/_preparedkeyboardbutton.py +++ b/src/telegram/_preparedkeyboardbutton.py @@ -31,7 +31,7 @@ class PreparedKeyboardButton(TelegramObject): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`id` is equal. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: id (:obj:`str`): Unique identifier of the keyboard button. diff --git a/src/telegram/_reply.py b/src/telegram/_reply.py index a8e37cafacb..bbf0f6d9010 100644 --- a/src/telegram/_reply.py +++ b/src/telegram/_reply.py @@ -118,7 +118,7 @@ class ExternalReplyInfo(TelegramObject): live_photo (:class:`telegram.LivePhoto`, optional): Message is a live photo, information about the live photo. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Attributes: origin (:class:`telegram.MessageOrigin`): Origin of the message replied to by the given @@ -174,7 +174,7 @@ class ExternalReplyInfo(TelegramObject): live_photo (:class:`telegram.LivePhoto`): Optional. Message is a live photo, information about the live photo. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ @@ -427,7 +427,7 @@ class ReplyParameters(TelegramObject): poll_option_id (:obj:`str`, optional): Persistent identifier of the specific poll option to be replied to. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Attributes: message_id (:obj:`int`): Identifier of the message that will be replied to in the current @@ -458,7 +458,7 @@ class ReplyParameters(TelegramObject): poll_option_id (:obj:`str`): Optional. Persistent identifier of the specific poll option to be replied to. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ __slots__ = ( diff --git a/src/telegram/_sentguestmessage.py b/src/telegram/_sentguestmessage.py index ecbc6ce6fe6..daea15ebdee 100644 --- a/src/telegram/_sentguestmessage.py +++ b/src/telegram/_sentguestmessage.py @@ -28,7 +28,7 @@ class SentGuestMessage(TelegramObject): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`inline_message_id` are equal. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: inline_message_id (:obj:`str`): Identifier of the sent inline message. diff --git a/src/telegram/_update.py b/src/telegram/_update.py index 09c06896594..1af292cce8d 100644 --- a/src/telegram/_update.py +++ b/src/telegram/_update.py @@ -167,12 +167,12 @@ class Update(TelegramObject): managed_bot (:class:`telegram.ManagedBotUpdated`, optional): A new bot was created to be managed by the bot, or token or owner of a managed bot was changed. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 guest_message (:class:`telegram.Message`, optional): New guest message. The bot can use the field :attr:`telegram.Message.guest_query_id` and the method :meth:`telegram.Bot.answer_guest_query` to send a message in response. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Attributes: @@ -289,12 +289,12 @@ class Update(TelegramObject): managed_bot (:class:`telegram.ManagedBotUpdated`): Optional. A new bot was created to be managed by the bot, or token or owner of a managed bot was changed. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 guest_message (:class:`telegram.Message`): Optional. New guest message. The bot can use the field :attr:`telegram.Message.guest_query_id` and the method :meth:`telegram.Bot.answerGuestQuery` to send a message in response. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ __slots__ = ( @@ -428,12 +428,12 @@ class Update(TelegramObject): MANAGED_BOT: Final[str] = constants.UpdateType.MANAGED_BOT """:const:`telegram.constants.UpdateType.MANAGED_BOT` - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ GUEST_MESSAGE: Final[str] = constants.UpdateType.GUEST_MESSAGE """:const:`telegram.constants.UpdateType.GUEST_MESSAGE` - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ ALL_TYPES: Final[list[str]] = list(constants.UpdateType) @@ -533,7 +533,7 @@ def effective_user(self) -> "User | None": .. versionchanged:: 21.6 This property now also considers :attr:`purchased_paid_media`. - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 This property now also considers :attr:`managed_bot`, :attr:`guest_message`, :attr:`channel_post`, and :attr:`edited_channel_post`. @@ -626,7 +626,7 @@ def effective_sender(self) -> "User | Chat | None": is present. - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 This property now also considers :attr:`guest_message`. Example: @@ -683,7 +683,7 @@ def effective_chat(self) -> "Chat | None": This property now also considers :attr:`business_message`, :attr:`edited_business_message`, and :attr:`deleted_business_messages`. - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 This property now also considers :attr:`guest_message`. Example: @@ -747,7 +747,7 @@ def effective_message(self) -> Message | None: This property now also considers :attr:`business_message`, and :attr:`edited_business_message`. - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 This property now also considers :attr:`guest_message`. Tip: diff --git a/src/telegram/_user.py b/src/telegram/_user.py index 6eb3a6cf18a..eb95639da8c 100644 --- a/src/telegram/_user.py +++ b/src/telegram/_user.py @@ -132,12 +132,12 @@ class User(TelegramObject): can_manage_bots (:obj:`bool`, optional): :obj:`True`, if other bots can be created to be controlled by the bot. Returned only in :meth:`telegram.Bot.get_me`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 supports_guest_queries (:obj:`bool`, optional): :obj:`True`, if the bot supports guest queries from chats it is not a member of. Returned only in :meth:`telegram.Bot.get_me`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Attributes: id (:obj:`int`): Unique identifier for this user or bot. @@ -181,12 +181,12 @@ class User(TelegramObject): can_manage_bots (:obj:`bool`): Optional. :obj:`True`, if other bots can be created to be controlled by the bot. Returned only in :meth:`telegram.Bot.get_me`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 supports_guest_queries (:obj:`bool`): Optional. :obj:`True`, if the bot supports guest queries from chats it is not a member of. Returned only in :meth:`telegram.Bot.get_me`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 .. |user_chat_id_note| replace:: This shortcuts build on the assumption that :attr:`User.id` coincides with the :attr:`Chat.id` of the private chat with the user. This has been the @@ -567,7 +567,7 @@ async def send_message_draft( .. versionadded:: 22.6 - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 Bot API 10.0 makes the ``text`` argument optional. Returns: @@ -756,7 +756,7 @@ async def send_live_photo( For the documentation of the arguments, please see :meth:`telegram.Bot.send_live_photo`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Note: |user_chat_id_note| @@ -2859,7 +2859,7 @@ async def replace_token( For the documentation of the arguments, please see :meth:`telegram.Bot.replace_managed_bot_token`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Returns: :obj:`str`: On success, :obj:`str` is returned. @@ -2893,7 +2893,7 @@ async def get_managed_bot_access_settings( For the documentation of the arguments, please see :meth:`telegram.Bot.get_managed_bot_access_settings`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Returns: :class:`telegram.BotAccessSettings`: On success, returns the access settings of the bot @@ -2931,7 +2931,7 @@ async def set_managed_bot_access_settings( For the documentation of the arguments, please see :meth:`telegram.Bot.set_managed_bot_access_settings`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Returns: :obj:`bool`: On success, :obj:`True` is returned. @@ -2972,7 +2972,7 @@ async def delete_reaction( For the documentation of the arguments, please see :meth:`telegram.Bot.delete_message_reaction`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Returns: :obj:`bool`: On success, :obj:`True` is returned. @@ -3010,7 +3010,7 @@ async def get_personal_chat_messages( For the documentation of the arguments, please see :meth:`telegram.Bot.get_user_personal_chat_messages`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Returns: tuple[:class:`telegram.Message`]: On success, a tuple of messages from the personal @@ -3050,7 +3050,7 @@ async def delete_all_reactions( For the documentation of the arguments, please see :meth:`telegram.Bot.delete_all_message_reactions`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Returns: :obj:`bool`: On success, :obj:`True` is returned. diff --git a/src/telegram/_utils/types.py b/src/telegram/_utils/types.py index 0684219ad01..a6c70545593 100644 --- a/src/telegram/_utils/types.py +++ b/src/telegram/_utils/types.py @@ -95,7 +95,7 @@ CorrectOptionIds: TypeAlias = Sequence[Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]] """ -.. versionadded:: NEXT.VERSION +.. versionadded:: 22.8 """ MarkdownVersion: TypeAlias = Literal[1, 2] diff --git a/src/telegram/_version.py b/src/telegram/_version.py index d905e7c0797..257e57c95c8 100644 --- a/src/telegram/_version.py +++ b/src/telegram/_version.py @@ -51,6 +51,6 @@ def __str__(self) -> str: __version_info__: Final[Version] = Version( - major=22, minor=7, micro=0, releaselevel="final", serial=0 + major=22, minor=8, micro=0, releaselevel="final", serial=0 ) __version__: Final[str] = str(__version_info__) diff --git a/src/telegram/constants.py b/src/telegram/constants.py index 0bcd88e8f2c..2369f2d435b 100644 --- a/src/telegram/constants.py +++ b/src/telegram/constants.py @@ -1527,7 +1527,7 @@ class BaseInputMediaType(StringEnum): :class:`telegram.InputPollMedia` and :class:`telegram.InputPollOptionMedia`. The enum members of this enumeration are instances of :class:`str` and can be treated as such. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ __slots__ = () @@ -1556,7 +1556,7 @@ class InputMediaType(StringEnum): """This enum contains the available types of :class:`telegram.InputMedia`. The enum members of this enumeration are instances of :class:`str` and can be treated as such. - .. deprecated:: NEXT.VERSION + .. deprecated:: 22.8 Use :class:`telegram.constants.BaseInputMediaType` instead. .. versionadded:: 20.0 @@ -1577,7 +1577,7 @@ class InputMediaType(StringEnum): LIVE_PHOTO = "live_photo" """:obj:`str`: Type of :class:`telegram.InputMediaLivePhoto`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ @@ -1597,7 +1597,7 @@ class InputPaidMediaType(StringEnum): LIVE_PHOTO = "live_photo" """:obj:`str`: Type of :class:`telegram.InputPaidMediaLivePhoto`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ @@ -2014,7 +2014,7 @@ class MessageAttachmentType(StringEnum): LIVE_PHOTO = "live_photo" """:obj:`str`: Messages with :attr:`telegram.Message.live_photo`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ LOCATION = "location" """:obj:`str`: Messages with :attr:`telegram.Message.location`.""" @@ -2401,7 +2401,7 @@ class MessageType(StringEnum): LIVE_PHOTO = "live_photo" """:obj:`str`: Messages with :attr:`telegram.Message.live_photo`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ LOCATION = "location" """:obj:`str`: Messages with :attr:`telegram.Message.location`.""" @@ -2430,12 +2430,12 @@ class MessageType(StringEnum): POLL_OPTION_ADDED = "poll_option_added" """:obj:`str`: Messages with :attr:`telegram.Message.poll_option_added`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ POLL_OPTION_DELETED = "poll_option_deleted" """:obj:`str`: Messages with :attr:`telegram.Message.poll_option_deleted`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ SUGGESTED_POST_APPROVAL_FAILED = "suggested_post_approval_failed" """:obj:`str`: Messages with :attr:`telegram.Message.suggested_post_approval_failed`. @@ -2627,7 +2627,7 @@ class PaidMediaType(StringEnum): LIVE_PHOTO = "live_photo" """:obj:`str`: The type of :class:`telegram.PaidMediaLivePhoto` - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ @@ -2636,7 +2636,7 @@ class PersonalChatMessagesLimit(IntEnum): :paramref:`telegram.Bot.get_user_personal_chat_messages.limit`. The enum members of this enumeration are instances of :class:`int` and can be treated as such. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ __slots__ = () @@ -3512,7 +3512,7 @@ class PollLimit(IntEnum): to the :paramref:`~telegram.Bot.send_poll.options` parameter of :meth:`telegram.Bot.send_poll`. - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 Bot API 10.0 decreased this value from ``2`` to ``1``. """ MAX_OPTION_NUMBER = 12 @@ -3545,27 +3545,27 @@ class PollLimit(IntEnum): Also used in the :paramref:`~telegram.Bot.send_poll.close_date` parameter of :meth:`telegram.Bot.send_poll`. - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 Changed from ``600`` to ``2628000`` since Bot API 9.6. """ MAX_DESCRIPTION_CHARACTERS = 1024 """:obj:`int`: Maximum value allowed for the :paramref:`~telegram.Bot.send_poll.description` parameter of :meth:`telegram.Bot.send_poll`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ MIN_MEMBERSHIP_HOURS = 24 """:obj:`int`: Minimum number of hours a user must have been a member of the chat before they can vote in a members-only poll. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ MAX_COUNTRY_CODES = 12 """:obj:`int`: Maximum number of two-letter ``ISO 3166-1 alpha-2`` country codes passed in a :obj:`list` to the :paramref:`~telegram.Bot.send_poll.country_codes` parameter of :meth:`telegram.Bot.send_poll`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ @@ -3719,12 +3719,12 @@ class UpdateType(StringEnum): MANAGED_BOT = "managed_bot" """:obj:`str`: Updates with :attr:`telegram.Update.managed_bot`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ GUEST_MESSAGE = "guest_message" """:obj:`str`: Updates with :attr:`telegram.Update.guest_message`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ @@ -4149,7 +4149,7 @@ class ManagedBotAccessLimit(IntEnum): """This enum contains limitations for :meth:`~telegram.Bot.set_managed_bot_access_settings`. The enum members of this enumeration are instances of :class:`int` and can be treated as such. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ __slots__ = () diff --git a/src/telegram/ext/_handlers/managedbotupdatedhandler.py b/src/telegram/ext/_handlers/managedbotupdatedhandler.py index 2715f06c21e..6a52e8c2d4c 100644 --- a/src/telegram/ext/_handlers/managedbotupdatedhandler.py +++ b/src/telegram/ext/_handlers/managedbotupdatedhandler.py @@ -34,7 +34,7 @@ class ManagedBotUpdatedHandler(BaseHandler[Update, CCT, RT]): """Handler class to handle :attr:`updated Telegram Managed Bots `. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 Args: callback (:term:`coroutine function`): The callback function for this handler. Will be diff --git a/src/telegram/ext/filters.py b/src/telegram/ext/filters.py index 701179da083..a86e4b96399 100644 --- a/src/telegram/ext/filters.py +++ b/src/telegram/ext/filters.py @@ -274,7 +274,7 @@ def check_update(self, update: Update) -> bool | FilterDataDict | None: :attr:`~telegram.Update.business_message` or :attr:`~telegram.Update.edited_business_message`. - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 22.8 This filter now also returns :obj:`True` if the update contains :attr:`~telegram.Update.guest_message`. @@ -1668,7 +1668,7 @@ def filter(self, message: Message) -> bool: LIVE_PHOTO = _LivePhoto(name="filters.LIVE_PHOTO") """Messages that contain :attr:`telegram.Message.live_photo`. -.. versionadded:: NEXT.VERSION +.. versionadded:: 22.8 """ @@ -2374,7 +2374,7 @@ def filter(self, message: Message) -> bool: POLL_OPTION_ADDED = _PollOptionAdded(name="filters.StatusUpdate.POLL_OPTION_ADDED") """Messages that contain :attr:`telegram.Message.poll_option_added`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ class _PollOptionDeleted(MessageFilter): @@ -2385,7 +2385,7 @@ def filter(self, message: Message) -> bool: POLL_OPTION_DELETED = _PollOptionDeleted(name="filters.StatusUpdate.POLL_OPTION_DELETED") """Messages that contain :attr:`telegram.Message.poll_option_deleted`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ class _ProximityAlertTriggered(MessageFilter): @@ -2924,7 +2924,7 @@ def filter(self, update: Update) -> bool: GUEST_MESSAGE = _GuestMessage(name="filters.UpdateType.GUEST_MESSAGE") """Updates with :attr:`telegram.Update.guest_message`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 22.8 """ From afb9fc4898a4c84d9766a9b5c9b2bf35da81bd1c Mon Sep 17 00:00:00 2001 From: Poolitzer Date: Fri, 12 Jun 2026 10:03:03 +0200 Subject: [PATCH 39/39] Fix: Delete the chango here I wasn't able to in the PR, it was too quick --- changes/22.8_2026-06-12/5262.ULa4t5ti2rRFyMWgpmVccq.toml | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 changes/22.8_2026-06-12/5262.ULa4t5ti2rRFyMWgpmVccq.toml diff --git a/changes/22.8_2026-06-12/5262.ULa4t5ti2rRFyMWgpmVccq.toml b/changes/22.8_2026-06-12/5262.ULa4t5ti2rRFyMWgpmVccq.toml deleted file mode 100644 index b24cd91e5b7..00000000000 --- a/changes/22.8_2026-06-12/5262.ULa4t5ti2rRFyMWgpmVccq.toml +++ /dev/null @@ -1,5 +0,0 @@ -other = "Bump Version to 22.8" -[[pull_requests]] -uid = "5262" -author_uids = ["Poolitzer"] -closes_threads = []