From 6747fa32759bc1e87629e4ca28edc6d3adc4ccac Mon Sep 17 00:00:00 2001 From: Thiago Bellini Ribeiro Date: Fri, 31 May 2024 23:07:31 +0100 Subject: [PATCH] feat: Nested optimization for lists and connections --- .github/workflows/tests.yml | 3 - .pre-commit-config.yaml | 2 +- poetry.lock | 449 +++++++++--------- pyproject.toml | 5 +- strawberry_django/fields/field.py | 12 +- strawberry_django/fields/types.py | 15 +- strawberry_django/optimizer.py | 223 +++++++-- strawberry_django/pagination.py | 83 +++- strawberry_django/queryset.py | 10 +- strawberry_django/relay.py | 116 ++++- strawberry_django/resolvers.py | 24 + tests/fields/test_input.py | 10 - tests/fields/test_types.py | 8 - tests/filters/test_filters.py | 14 +- tests/filters/test_filters_v2.py | 9 - tests/projects/schema.py | 19 +- tests/projects/snapshots/schema.gql | 7 +- .../snapshots/schema_with_inheritance.gql | 14 +- tests/test_optimizer.py | 169 ++++++- 19 files changed, 837 insertions(+), 355 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 17a1fb322..f50d586b7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -43,9 +43,6 @@ jobs: fail-fast: false matrix: django-version: - - 3.2.* - - 4.0.* - - 4.1.* - 4.2.* - 5.0.* python-version: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af77f30c6..d650f748e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: - id: check-xml - id: check-symlinks - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.5 + rev: v0.4.7 hooks: - id: ruff-format - id: ruff diff --git a/poetry.lock b/poetry.lock index 62c2d70ef..619fb51da 100644 --- a/poetry.lock +++ b/poetry.lock @@ -233,63 +233,63 @@ files = [ [[package]] name = "coverage" -version = "7.5.1" +version = "7.5.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"}, - {file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146"}, - {file = "coverage-7.5.1-cp310-cp310-win32.whl", hash = "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228"}, - {file = "coverage-7.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8"}, - {file = "coverage-7.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428"}, - {file = "coverage-7.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987"}, - {file = "coverage-7.5.1-cp311-cp311-win32.whl", hash = "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136"}, - {file = "coverage-7.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd"}, - {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"}, - {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"}, - {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"}, - {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"}, - {file = "coverage-7.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7"}, - {file = "coverage-7.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d"}, - {file = "coverage-7.5.1-cp38-cp38-win32.whl", hash = "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41"}, - {file = "coverage-7.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de"}, - {file = "coverage-7.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1"}, - {file = "coverage-7.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668"}, - {file = "coverage-7.5.1-cp39-cp39-win32.whl", hash = "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981"}, - {file = "coverage-7.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f"}, - {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"}, - {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"}, + {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, + {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, + {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, + {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, + {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, + {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, + {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, + {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, + {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, + {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, + {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, + {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, + {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, + {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, ] [package.dependencies] @@ -346,17 +346,17 @@ typing_extensions = ">=4.0.0" [[package]] name = "django-debug-toolbar" -version = "4.3.0" +version = "4.4.2" description = "A configurable set of panels that display various debug information about the current request/response." optional = false python-versions = ">=3.8" files = [ - {file = "django_debug_toolbar-4.3.0-py3-none-any.whl", hash = "sha256:e09b7dcb8417b743234dfc57c95a7c1d1d87a88844abd13b4c5387f807b31bf6"}, - {file = "django_debug_toolbar-4.3.0.tar.gz", hash = "sha256:0b0dddee5ea29b9cb678593bc0d7a6d76b21d7799cb68e091a2148341a80f3c4"}, + {file = "django_debug_toolbar-4.4.2-py3-none-any.whl", hash = "sha256:5d7afb2ea5f8730241e5b0735396e16cd1fd8c6b53a2f3e1e30bbab9abb23728"}, + {file = "django_debug_toolbar-4.4.2.tar.gz", hash = "sha256:9204050fcb1e4f74216c5b024bc76081451926a6303993d6c513f5e142675927"}, ] [package.dependencies] -django = ">=3.2.4" +django = ">=4.2.9" sqlparse = ">=0.2" [[package]] @@ -431,13 +431,13 @@ doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"] [[package]] name = "faker" -version = "25.1.0" +version = "25.3.0" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.8" files = [ - {file = "Faker-25.1.0-py3-none-any.whl", hash = "sha256:24e28dce0b89683bb9e017e042b971c8c4909cff551b6d46f1e207674c7c2526"}, - {file = "Faker-25.1.0.tar.gz", hash = "sha256:2107618cf306bb188dcfea3e5cfd94aa92d65c7293a2437c1e96a99c83274755"}, + {file = "Faker-25.3.0-py3-none-any.whl", hash = "sha256:0158d47e955b6ec22134c0a74ebb7ed34fe600896208bafbf1008db831b17f04"}, + {file = "Faker-25.3.0.tar.gz", hash = "sha256:bcbe31eee5ef4bbf87ce36c4eba53c01e2a1d912fde2a4d3528b430d2beb784f"}, ] [package.dependencies] @@ -473,13 +473,13 @@ files = [ [[package]] name = "griffe" -version = "0.44.0" +version = "0.45.2" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.8" files = [ - {file = "griffe-0.44.0-py3-none-any.whl", hash = "sha256:8a4471c469ba980b87c843f1168850ce39d0c1d0c7be140dca2480f76c8e5446"}, - {file = "griffe-0.44.0.tar.gz", hash = "sha256:34aee1571042f9bf00529bc715de4516fb6f482b164e90d030300601009e0223"}, + {file = "griffe-0.45.2-py3-none-any.whl", hash = "sha256:297ec8530d0c68e5b98ff86fb588ebc3aa3559bb5dc21f3caea8d9542a350133"}, + {file = "griffe-0.45.2.tar.gz", hash = "sha256:83ce7dcaafd8cb7f43cbf1a455155015a1eb624b1ffd93249e5e1c4a22b2fdb2"}, ] [package.dependencies] @@ -742,13 +742,13 @@ pyyaml = "*" [[package]] name = "mkdocs-material" -version = "9.5.21" +version = "9.5.25" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.21-py3-none-any.whl", hash = "sha256:210e1f179682cd4be17d5c641b2f4559574b9dea2f589c3f0e7c17c5bd1959bc"}, - {file = "mkdocs_material-9.5.21.tar.gz", hash = "sha256:049f82770f40559d3c2aa2259c562ea7257dbb4aaa9624323b5ef27b2d95a450"}, + {file = "mkdocs_material-9.5.25-py3-none-any.whl", hash = "sha256:68fdab047a0b9bfbefe79ce267e8a7daaf5128bcf7867065fcd201ee335fece1"}, + {file = "mkdocs_material-9.5.25.tar.gz", hash = "sha256:d0662561efb725b712207e0ee01f035ca15633f29a64628e24f01ec99d7078f4"}, ] [package.dependencies] @@ -828,18 +828,18 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.10.0" +version = "1.10.3" description = "A Python handler for mkdocstrings." optional = false python-versions = ">=3.8" files = [ - {file = "mkdocstrings_python-1.10.0-py3-none-any.whl", hash = "sha256:ba833fbd9d178a4b9d5cb2553a4df06e51dc1f51e41559a4d2398c16a6f69ecc"}, - {file = "mkdocstrings_python-1.10.0.tar.gz", hash = "sha256:71678fac657d4d2bb301eed4e4d2d91499c095fd1f8a90fa76422a87a5693828"}, + {file = "mkdocstrings_python-1.10.3-py3-none-any.whl", hash = "sha256:11ff6d21d3818fb03af82c3ea6225b1534837e17f790aa5f09626524171f949b"}, + {file = "mkdocstrings_python-1.10.3.tar.gz", hash = "sha256:321cf9c732907ab2b1fedaafa28765eaa089d89320f35f7206d00ea266889d03"}, ] [package.dependencies] griffe = ">=0.44" -mkdocstrings = ">=0.24.2" +mkdocstrings = ">=0.25" [[package]] name = "packaging" @@ -961,13 +961,13 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.2.1" +version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, - {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] @@ -1024,13 +1024,13 @@ extra = ["pygments (>=2.12)"] [[package]] name = "pytest" -version = "8.2.0" +version = "8.2.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, - {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, + {file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"}, + {file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"}, ] [package.dependencies] @@ -1046,13 +1046,13 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pytest-asyncio" -version = "0.23.6" +version = "0.23.7" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"}, - {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"}, + {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, + {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, ] [package.dependencies] @@ -1245,101 +1245,101 @@ pyyaml = "*" [[package]] name = "regex" -version = "2024.4.28" +version = "2024.5.15" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" files = [ - {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd196d056b40af073d95a2879678585f0b74ad35190fac04ca67954c582c6b61"}, - {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8bb381f777351bd534462f63e1c6afb10a7caa9fa2a421ae22c26e796fe31b1f"}, - {file = "regex-2024.4.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:47af45b6153522733aa6e92543938e97a70ce0900649ba626cf5aad290b737b6"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d6a550425cc51c656331af0e2b1651e90eaaa23fb4acde577cf15068e2e20f"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf29304a8011feb58913c382902fde3395957a47645bf848eea695839aa101b7"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:92da587eee39a52c91aebea8b850e4e4f095fe5928d415cb7ed656b3460ae79a"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6277d426e2f31bdbacb377d17a7475e32b2d7d1f02faaecc48d8e370c6a3ff31"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28e1f28d07220c0f3da0e8fcd5a115bbb53f8b55cecf9bec0c946eb9a059a94c"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aaa179975a64790c1f2701ac562b5eeb733946eeb036b5bcca05c8d928a62f10"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6f435946b7bf7a1b438b4e6b149b947c837cb23c704e780c19ba3e6855dbbdd3"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:19d6c11bf35a6ad077eb23852827f91c804eeb71ecb85db4ee1386825b9dc4db"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:fdae0120cddc839eb8e3c15faa8ad541cc6d906d3eb24d82fb041cfe2807bc1e"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e672cf9caaf669053121f1766d659a8813bd547edef6e009205378faf45c67b8"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f57515750d07e14743db55d59759893fdb21d2668f39e549a7d6cad5d70f9fea"}, - {file = "regex-2024.4.28-cp310-cp310-win32.whl", hash = "sha256:a1409c4eccb6981c7baabc8888d3550df518add6e06fe74fa1d9312c1838652d"}, - {file = "regex-2024.4.28-cp310-cp310-win_amd64.whl", hash = "sha256:1f687a28640f763f23f8a9801fe9e1b37338bb1ca5d564ddd41619458f1f22d1"}, - {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84077821c85f222362b72fdc44f7a3a13587a013a45cf14534df1cbbdc9a6796"}, - {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45d4503de8f4f3dc02f1d28a9b039e5504a02cc18906cfe744c11def942e9eb"}, - {file = "regex-2024.4.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:457c2cd5a646dd4ed536c92b535d73548fb8e216ebee602aa9f48e068fc393f3"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b51739ddfd013c6f657b55a508de8b9ea78b56d22b236052c3a85a675102dc6"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:459226445c7d7454981c4c0ce0ad1a72e1e751c3e417f305722bbcee6697e06a"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:670fa596984b08a4a769491cbdf22350431970d0112e03d7e4eeaecaafcd0fec"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe00f4fe11c8a521b173e6324d862ee7ee3412bf7107570c9b564fe1119b56fb"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36f392dc7763fe7924575475736bddf9ab9f7a66b920932d0ea50c2ded2f5636"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:23a412b7b1a7063f81a742463f38821097b6a37ce1e5b89dd8e871d14dbfd86b"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f1d6e4b7b2ae3a6a9df53efbf199e4bfcff0959dbdb5fd9ced34d4407348e39a"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:499334ad139557de97cbc4347ee921c0e2b5e9c0f009859e74f3f77918339257"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:0940038bec2fe9e26b203d636c44d31dd8766abc1fe66262da6484bd82461ccf"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:66372c2a01782c5fe8e04bff4a2a0121a9897e19223d9eab30c54c50b2ebeb7f"}, - {file = "regex-2024.4.28-cp311-cp311-win32.whl", hash = "sha256:c77d10ec3c1cf328b2f501ca32583625987ea0f23a0c2a49b37a39ee5c4c4630"}, - {file = "regex-2024.4.28-cp311-cp311-win_amd64.whl", hash = "sha256:fc0916c4295c64d6890a46e02d4482bb5ccf33bf1a824c0eaa9e83b148291f90"}, - {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:08a1749f04fee2811c7617fdd46d2e46d09106fa8f475c884b65c01326eb15c5"}, - {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b8eb28995771c087a73338f695a08c9abfdf723d185e57b97f6175c5051ff1ae"}, - {file = "regex-2024.4.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd7ef715ccb8040954d44cfeff17e6b8e9f79c8019daae2fd30a8806ef5435c0"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb0315a2b26fde4005a7c401707c5352df274460f2f85b209cf6024271373013"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2fc053228a6bd3a17a9b0a3f15c3ab3cf95727b00557e92e1cfe094b88cc662"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fe9739a686dc44733d52d6e4f7b9c77b285e49edf8570754b322bca6b85b4cc"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74fcf77d979364f9b69fcf8200849ca29a374973dc193a7317698aa37d8b01c"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:965fd0cf4694d76f6564896b422724ec7b959ef927a7cb187fc6b3f4e4f59833"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2fef0b38c34ae675fcbb1b5db760d40c3fc3612cfa186e9e50df5782cac02bcd"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bc365ce25f6c7c5ed70e4bc674f9137f52b7dd6a125037f9132a7be52b8a252f"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ac69b394764bb857429b031d29d9604842bc4cbfd964d764b1af1868eeebc4f0"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:144a1fc54765f5c5c36d6d4b073299832aa1ec6a746a6452c3ee7b46b3d3b11d"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2630ca4e152c221072fd4a56d4622b5ada876f668ecd24d5ab62544ae6793ed6"}, - {file = "regex-2024.4.28-cp312-cp312-win32.whl", hash = "sha256:7f3502f03b4da52bbe8ba962621daa846f38489cae5c4a7b5d738f15f6443d17"}, - {file = "regex-2024.4.28-cp312-cp312-win_amd64.whl", hash = "sha256:0dd3f69098511e71880fb00f5815db9ed0ef62c05775395968299cb400aeab82"}, - {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:374f690e1dd0dbdcddea4a5c9bdd97632cf656c69113f7cd6a361f2a67221cb6"}, - {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f87ae6b96374db20f180eab083aafe419b194e96e4f282c40191e71980c666"}, - {file = "regex-2024.4.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5dbc1bcc7413eebe5f18196e22804a3be1bfdfc7e2afd415e12c068624d48247"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f85151ec5a232335f1be022b09fbbe459042ea1951d8a48fef251223fc67eee1"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57ba112e5530530fd175ed550373eb263db4ca98b5f00694d73b18b9a02e7185"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:224803b74aab56aa7be313f92a8d9911dcade37e5f167db62a738d0c85fdac4b"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a54a047b607fd2d2d52a05e6ad294602f1e0dec2291152b745870afc47c1397"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a2a512d623f1f2d01d881513af9fc6a7c46e5cfffb7dc50c38ce959f9246c94"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c06bf3f38f0707592898428636cbb75d0a846651b053a1cf748763e3063a6925"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1031a5e7b048ee371ab3653aad3030ecfad6ee9ecdc85f0242c57751a05b0ac4"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7a353ebfa7154c871a35caca7bfd8f9e18666829a1dc187115b80e35a29393e"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7e76b9cfbf5ced1aca15a0e5b6f229344d9b3123439ffce552b11faab0114a02"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5ce479ecc068bc2a74cb98dd8dba99e070d1b2f4a8371a7dfe631f85db70fe6e"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d77b6f63f806578c604dca209280e4c54f0fa9a8128bb8d2cc5fb6f99da4150"}, - {file = "regex-2024.4.28-cp38-cp38-win32.whl", hash = "sha256:d84308f097d7a513359757c69707ad339da799e53b7393819ec2ea36bc4beb58"}, - {file = "regex-2024.4.28-cp38-cp38-win_amd64.whl", hash = "sha256:2cc1b87bba1dd1a898e664a31012725e48af826bf3971e786c53e32e02adae6c"}, - {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7413167c507a768eafb5424413c5b2f515c606be5bb4ef8c5dee43925aa5718b"}, - {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:108e2dcf0b53a7c4ab8986842a8edcb8ab2e59919a74ff51c296772e8e74d0ae"}, - {file = "regex-2024.4.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f1c5742c31ba7d72f2dedf7968998730664b45e38827637e0f04a2ac7de2f5f1"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecc6148228c9ae25ce403eade13a0961de1cb016bdb35c6eafd8e7b87ad028b1"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7d893c8cf0e2429b823ef1a1d360a25950ed11f0e2a9df2b5198821832e1947"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4290035b169578ffbbfa50d904d26bec16a94526071ebec3dadbebf67a26b25e"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a22ae1cfd82e4ffa2066eb3390777dc79468f866f0625261a93e44cdf6482b"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd24fd140b69f0b0bcc9165c397e9b2e89ecbeda83303abf2a072609f60239e2"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:39fb166d2196413bead229cd64a2ffd6ec78ebab83fff7d2701103cf9f4dfd26"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9301cc6db4d83d2c0719f7fcda37229691745168bf6ae849bea2e85fc769175d"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c3d389e8d76a49923683123730c33e9553063d9041658f23897f0b396b2386f"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:99ef6289b62042500d581170d06e17f5353b111a15aa6b25b05b91c6886df8fc"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b91d529b47798c016d4b4c1d06cc826ac40d196da54f0de3c519f5a297c5076a"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:43548ad74ea50456e1c68d3c67fff3de64c6edb85bcd511d1136f9b5376fc9d1"}, - {file = "regex-2024.4.28-cp39-cp39-win32.whl", hash = "sha256:05d9b6578a22db7dedb4df81451f360395828b04f4513980b6bd7a1412c679cc"}, - {file = "regex-2024.4.28-cp39-cp39-win_amd64.whl", hash = "sha256:3986217ec830c2109875be740531feb8ddafe0dfa49767cdcd072ed7e8927962"}, - {file = "regex-2024.4.28.tar.gz", hash = "sha256:83ab366777ea45d58f72593adf35d36ca911ea8bd838483c1823b883a121b0e4"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53"}, + {file = "regex-2024.5.15-cp310-cp310-win32.whl", hash = "sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3"}, + {file = "regex-2024.5.15-cp310-cp310-win_amd64.whl", hash = "sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68"}, + {file = "regex-2024.5.15-cp311-cp311-win32.whl", hash = "sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa"}, + {file = "regex-2024.5.15-cp311-cp311-win_amd64.whl", hash = "sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80"}, + {file = "regex-2024.5.15-cp312-cp312-win32.whl", hash = "sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe"}, + {file = "regex-2024.5.15-cp312-cp312-win_amd64.whl", hash = "sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741"}, + {file = "regex-2024.5.15-cp38-cp38-win32.whl", hash = "sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9"}, + {file = "regex-2024.5.15-cp38-cp38-win_amd64.whl", hash = "sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456"}, + {file = "regex-2024.5.15-cp39-cp39-win32.whl", hash = "sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694"}, + {file = "regex-2024.5.15-cp39-cp39-win_amd64.whl", hash = "sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388"}, + {file = "regex-2024.5.15.tar.gz", hash = "sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c"}, ] [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -1354,28 +1354,28 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.4.4" +version = "0.4.7" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.4.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:29d44ef5bb6a08e235c8249294fa8d431adc1426bfda99ed493119e6f9ea1bf6"}, - {file = "ruff-0.4.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c4efe62b5bbb24178c950732ddd40712b878a9b96b1d02b0ff0b08a090cbd891"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c8e2f1e8fc12d07ab521a9005d68a969e167b589cbcaee354cb61e9d9de9c15"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60ed88b636a463214905c002fa3eaab19795679ed55529f91e488db3fe8976ab"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b90fc5e170fc71c712cc4d9ab0e24ea505c6a9e4ebf346787a67e691dfb72e85"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8e7e6ebc10ef16dcdc77fd5557ee60647512b400e4a60bdc4849468f076f6eef"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9ddb2c494fb79fc208cd15ffe08f32b7682519e067413dbaf5f4b01a6087bcd"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c51c928a14f9f0a871082603e25a1588059b7e08a920f2f9fa7157b5bf08cfe9"}, - {file = "ruff-0.4.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5eb0a4bfd6400b7d07c09a7725e1a98c3b838be557fee229ac0f84d9aa49c36"}, - {file = "ruff-0.4.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b1867ee9bf3acc21778dcb293db504692eda5f7a11a6e6cc40890182a9f9e595"}, - {file = "ruff-0.4.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1aecced1269481ef2894cc495647392a34b0bf3e28ff53ed95a385b13aa45768"}, - {file = "ruff-0.4.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da73eb616b3241a307b837f32756dc20a0b07e2bcb694fec73699c93d04a69e"}, - {file = "ruff-0.4.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:958b4ea5589706a81065e2a776237de2ecc3e763342e5cc8e02a4a4d8a5e6f95"}, - {file = "ruff-0.4.4-py3-none-win32.whl", hash = "sha256:cb53473849f011bca6e754f2cdf47cafc9c4f4ff4570003a0dad0b9b6890e876"}, - {file = "ruff-0.4.4-py3-none-win_amd64.whl", hash = "sha256:424e5b72597482543b684c11def82669cc6b395aa8cc69acc1858b5ef3e5daae"}, - {file = "ruff-0.4.4-py3-none-win_arm64.whl", hash = "sha256:39df0537b47d3b597293edbb95baf54ff5b49589eb7ff41926d8243caa995ea6"}, - {file = "ruff-0.4.4.tar.gz", hash = "sha256:f87ea42d5cdebdc6a69761a9d0bc83ae9b3b30d0ad78952005ba6568d6c022af"}, + {file = "ruff-0.4.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e089371c67892a73b6bb1525608e89a2aca1b77b5440acf7a71dda5dac958f9e"}, + {file = "ruff-0.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:10f973d521d910e5f9c72ab27e409e839089f955be8a4c8826601a6323a89753"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59c3d110970001dfa494bcd95478e62286c751126dfb15c3c46e7915fc49694f"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa9773c6c00f4958f73b317bc0fd125295110c3776089f6ef318f4b775f0abe4"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07fc80bbb61e42b3b23b10fda6a2a0f5a067f810180a3760c5ef1b456c21b9db"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fa4dafe3fe66d90e2e2b63fa1591dd6e3f090ca2128daa0be33db894e6c18648"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7c0083febdec17571455903b184a10026603a1de078428ba155e7ce9358c5f6"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad1b20e66a44057c326168437d680a2166c177c939346b19c0d6b08a62a37589"}, + {file = "ruff-0.4.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbf5d818553add7511c38b05532d94a407f499d1a76ebb0cad0374e32bc67202"}, + {file = "ruff-0.4.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:50e9651578b629baec3d1513b2534de0ac7ed7753e1382272b8d609997e27e83"}, + {file = "ruff-0.4.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8874a9df7766cb956b218a0a239e0a5d23d9e843e4da1e113ae1d27ee420877a"}, + {file = "ruff-0.4.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b9de9a6e49f7d529decd09381c0860c3f82fa0b0ea00ea78409b785d2308a567"}, + {file = "ruff-0.4.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:13a1768b0691619822ae6d446132dbdfd568b700ecd3652b20d4e8bc1e498f78"}, + {file = "ruff-0.4.7-py3-none-win32.whl", hash = "sha256:769e5a51df61e07e887b81e6f039e7ed3573316ab7dd9f635c5afaa310e4030e"}, + {file = "ruff-0.4.7-py3-none-win_amd64.whl", hash = "sha256:9e3ab684ad403a9ed1226894c32c3ab9c2e0718440f6f50c7c5829932bc9e054"}, + {file = "ruff-0.4.7-py3-none-win_arm64.whl", hash = "sha256:10f2204b9a613988e3484194c2c9e96a22079206b22b787605c255f130db5ed7"}, + {file = "ruff-0.4.7.tar.gz", hash = "sha256:2331d2b051dc77a289a653fcc6a42cce357087c5975738157cd966590b18b5e1"}, ] [[package]] @@ -1406,13 +1406,13 @@ doc = ["sphinx"] [[package]] name = "strawberry-graphql" -version = "0.227.4" +version = "0.234.0" description = "A library for creating GraphQL APIs" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "strawberry_graphql-0.227.4-py3-none-any.whl", hash = "sha256:73e501d9f37ec66dd61942629b23e2d928417ba0b1f40b2fa8d01b540576a79c"}, - {file = "strawberry_graphql-0.227.4.tar.gz", hash = "sha256:3bad964ea1e46bddef2d1e20a4fea4619918826191d5442311e0246e1e228299"}, + {file = "strawberry_graphql-0.234.0-py3-none-any.whl", hash = "sha256:7619637b17cce41c1e6c359aec356db432ed0a8e5222f152419d774f98ac9351"}, + {file = "strawberry_graphql-0.234.0.tar.gz", hash = "sha256:7b95f5bb540de571a701da36305d8498e97eae09cc932b574a5acc7d27ec9dad"}, ] [package.dependencies] @@ -1464,13 +1464,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.1" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, + {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, ] [[package]] @@ -1503,40 +1503,43 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "watchdog" -version = "4.0.0" +version = "4.0.1" description = "Filesystem events monitoring" optional = false python-versions = ">=3.8" files = [ - {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b"}, - {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b"}, - {file = "watchdog-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:45cc09cc4c3b43fb10b59ef4d07318d9a3ecdbff03abd2e36e77b6dd9f9a5c85"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eed82cdf79cd7f0232e2fdc1ad05b06a5e102a43e331f7d041e5f0e0a34a51c4"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba30a896166f0fee83183cec913298151b73164160d965af2e93a20bbd2ab605"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d18d7f18a47de6863cd480734613502904611730f8def45fc52a5d97503e5101"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2895bf0518361a9728773083908801a376743bcc37dfa252b801af8fd281b1ca"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87e9df830022488e235dd601478c15ad73a0389628588ba0b028cb74eb72fed8"}, - {file = "watchdog-4.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b"}, - {file = "watchdog-4.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92"}, - {file = "watchdog-4.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269"}, - {file = "watchdog-4.0.0-py3-none-win32.whl", hash = "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c"}, - {file = "watchdog-4.0.0-py3-none-win_amd64.whl", hash = "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245"}, - {file = "watchdog-4.0.0-py3-none-win_ia64.whl", hash = "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7"}, - {file = "watchdog-4.0.0.tar.gz", hash = "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, + {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, + {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, + {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, + {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, ] [package.extras] @@ -1558,18 +1561,18 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [[package]] name = "zipp" -version = "3.18.1" +version = "3.19.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, - {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, + {file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"}, + {file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [extras] debug-toolbar = ["django-debug-toolbar"] @@ -1578,4 +1581,4 @@ enum = ["django-choices-field"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4.0" -content-hash = "b8da678b34783efdb6fee362b17e0d3c48c7bf1d581ed17e24392597e9fe21c0" +content-hash = "bbf720de53ef7a47df41901e17d85422d81507352f6ad95f282355b09443f532" diff --git a/pyproject.toml b/pyproject.toml index ba2803c92..43c248028 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,9 +26,6 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Framework :: Django", - "Framework :: Django :: 3.2", - "Framework :: Django :: 4.0", - "Framework :: Django :: 4.1", "Framework :: Django :: 4.2", "Framework :: Django :: 5.0", ] @@ -39,7 +36,7 @@ django = ">=3.2" asgiref = ">=3.8" django-choices-field = { version = ">=2.2.2", optional = true } django-debug-toolbar = { version = ">=3.4", optional = true } -strawberry-graphql = ">=0.227.1" +strawberry-graphql = ">=0.234.0" [tool.poetry.group.dev.dependencies] Markdown = "^3.3.7" diff --git a/strawberry_django/fields/field.py b/strawberry_django/fields/field.py index c5640f0df..ad0eea2a3 100644 --- a/strawberry_django/fields/field.py +++ b/strawberry_django/fields/field.py @@ -36,7 +36,7 @@ from strawberry_django.descriptors import ModelProperty from strawberry_django.fields.base import StrawberryDjangoFieldBase from strawberry_django.filters import FILTERS_ARG, StrawberryDjangoFieldFilters -from strawberry_django.optimizer import OptimizerStore +from strawberry_django.optimizer import OptimizerStore, is_optimized_by_prefetching from strawberry_django.ordering import ORDER_ARG, StrawberryDjangoFieldOrdering from strawberry_django.pagination import StrawberryDjangoPagination from strawberry_django.permissions import filter_with_perms @@ -46,6 +46,7 @@ default_qs_hook, django_getattr, django_resolver, + resolve_base_manager, ) if TYPE_CHECKING: @@ -222,7 +223,7 @@ async def async_resolver(): resolved = await result # type: ignore if isinstance(resolved, BaseManager): - resolved = resolved.all() + resolved = resolve_base_manager(resolved) if isinstance(resolved, models.QuerySet): if "info" not in kwargs: @@ -237,7 +238,7 @@ async def async_resolver(): return async_resolver() if isinstance(result, BaseManager): - result = result.all() + result = resolve_base_manager(result) if isinstance(result, models.QuerySet): if "info" not in kwargs: @@ -279,6 +280,11 @@ def qs_hook(qs: models.QuerySet): return qs_hook def get_queryset(self, queryset, info, **kwargs): + # If the queryset been optimized at prefetch phase, this function has already been + # called by the optimizer extension, meaning we don't want to call it again + if is_optimized_by_prefetching(queryset): + return queryset + queryset = run_type_get_queryset(queryset, self.django_type, info) queryset = super().get_queryset( filter_with_perms(queryset, info), info, **kwargs diff --git a/strawberry_django/fields/types.py b/strawberry_django/fields/types.py index 8f0f2f03b..fcf7afda7 100644 --- a/strawberry_django/fields/types.py +++ b/strawberry_django/fields/types.py @@ -18,7 +18,6 @@ Union, ) -import django import strawberry from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured from django.db.models import Field, Model, fields @@ -239,6 +238,7 @@ def __hash__(self): fields.IntegerField: int, fields.PositiveIntegerField: int, fields.PositiveSmallIntegerField: int, + fields.PositiveBigIntegerField: int, fields.SlugField: str, fields.SmallAutoField: strawberry.ID, fields.SmallIntegerField: int, @@ -246,6 +246,7 @@ def __hash__(self): fields.TimeField: datetime.time, fields.URLField: str, fields.UUIDField: uuid.UUID, + json.JSONField: JSON, files.FileField: DjangoFileType, files.ImageField: DjangoImageType, related.ForeignKey: DjangoModelType, @@ -256,18 +257,6 @@ def __hash__(self): reverse_related.OneToOneRel: DjangoModelType, } -if hasattr(fields, "NullBooleanField"): - # NullBooleanField was deprecated and will soon be removed - field_type_map[fields.NullBooleanField] = Optional[bool] # type: ignore - -if django.VERSION >= (3, 1): - field_type_map.update( - { - json.JSONField: JSON, - fields.PositiveBigIntegerField: int, - }, - ) - try: from django.contrib.gis import geos from django.contrib.gis.db import models as geos_fields diff --git a/strawberry_django/optimizer.py b/strawberry_django/optimizer.py index a9c26f9e6..1463cc814 100644 --- a/strawberry_django/optimizer.py +++ b/strawberry_django/optimizer.py @@ -28,7 +28,13 @@ ) from django.db.models.manager import BaseManager from django.db.models.query import QuerySet -from graphql import FieldNode, GraphQLObjectType, GraphQLOutputType, GraphQLWrappingType +from graphql import ( + FieldNode, + GraphQLObjectType, + GraphQLOutputType, + GraphQLWrappingType, + get_argument_values, +) from graphql.execution.collect_fields import collect_sub_fields from graphql.language.ast import OperationType from graphql.type.definition import GraphQLResolveInfo, get_named_type @@ -36,14 +42,18 @@ from strawberry.extensions import SchemaExtension from strawberry.lazy_type import LazyType from strawberry.object_type import StrawberryObjectDefinition +from strawberry.relay.utils import SliceMetadata from strawberry.schema.schema import Schema +from strawberry.schema.schema_converter import get_arguments from strawberry.type import get_object_definition from strawberry.types.info import Info from strawberry.utils.typing import eval_type from typing_extensions import assert_never, assert_type, get_args from strawberry_django.fields.types import resolve_model_field_name +from strawberry_django.pagination import apply_window_pagination from strawberry_django.queryset import get_queryset_config, run_type_get_queryset +from strawberry_django.relay import ListConnectionWithTotalCount from strawberry_django.resolvers import django_fetch from .descriptors import ModelProperty @@ -63,6 +73,7 @@ ) if TYPE_CHECKING: + from strawberry.field import StrawberryField from strawberry.types.execution import ExecutionContext from strawberry.utils.await_maybe import AwaitableOrValue @@ -75,6 +86,7 @@ "optimize", ] +NESTED_PREFETCH_MARK = "_strawberry_nested_prefetch_optimized" _M = TypeVar("_M", bound=models.Model) _sentinel = object() @@ -101,6 +113,8 @@ class OptimizerConfig: Enable `QuerySet.prefetch_related` optimizations enable_annotate: Enable `QuerySet.annotate` optimizations + enable_nested_relations_prefetch: + Enable prefetch of nested relations optimizations. prefetch_custom_queryset: Use custom instead of _base_manager for prefetch querysets @@ -110,6 +124,7 @@ class OptimizerConfig: enable_select_related: bool = dataclasses.field(default=True) enable_prefetch_related: bool = dataclasses.field(default=True) enable_annotate: bool = dataclasses.field(default=True) + enable_nested_relations_prefetch: bool = dataclasses.field(default=True) prefetch_custom_queryset: bool = dataclasses.field(default=False) @@ -299,7 +314,8 @@ def get_related_fields_with_prefix( if nested: # If there are nested relations, dive deeper fields.extend( get_related_fields_with_prefix( - nested, prefix=current_path + "__" + nested, + prefix=f"{current_path}{LOOKUP_SEP}", ) ) return fields @@ -336,32 +352,149 @@ def get_related_fields_with_prefix( def _get_prefetch_queryset( remote_model: type[models.Model], - field, + schema: Schema, + field: StrawberryField, + parent_type: GraphQLObjectType, + field_node: FieldNode, + *, config: OptimizerConfig | None, info: GraphQLResolveInfo, + related_field_id: str | None = None, ) -> QuerySet: qs = remote_model._base_manager.all() # type: ignore - if not config or not config.prefetch_custom_queryset: - return qs - remote_type_defs = get_args(field.type_annotation.annotation) - if len(remote_type_defs) != 1: - raise TypeError(f"Expected exactly one remote type: {remote_type_defs}") - if type(remote_type_defs[0]) is ForwardRef: - remote_type = eval_type( - remote_type_defs[0], - field.type_annotation.namespace, - None, + if config and config.prefetch_custom_queryset and field.type_annotation is not None: + remote_type_defs = get_args(field.type_annotation.annotation) + if len(remote_type_defs) != 1: + raise TypeError(f"Expected exactly one remote type: {remote_type_defs}") + + if type(remote_type_defs[0]) is ForwardRef: + remote_type = eval_type( + remote_type_defs[0], + field.type_annotation.namespace, + None, + ) + else: + remote_type = remote_type_defs[0] + + qs = run_type_get_queryset( + qs, + remote_type, + info=Info( + _raw_info=info, + _field=field, + ), ) - else: - remote_type = remote_type_defs[0] - return run_type_get_queryset( + return _optimize_prefetch_queryset( qs, - remote_type, - # FIXME: Find out if the fact that info can be a GraphQLResolveInfo is a problem - info=info, # type: ignore + schema, + field, + parent_type, + field_node, + config=config, + info=info, + related_field_id=related_field_id, + ) + + +def _optimize_prefetch_queryset( + qs: QuerySet[_M], + schema: Schema, + field: StrawberryField, + parent_type: GraphQLObjectType, + field_node: FieldNode, + *, + config: OptimizerConfig | None, + info: GraphQLResolveInfo, + related_field_id: str | None = None, +) -> QuerySet[_M]: + from strawberry_django.fields.field import ( + StrawberryDjangoConnectionExtension, + StrawberryDjangoField, + ) + + if ( + not config + or not config.enable_nested_relations_prefetch + or related_field_id is None + or not isinstance(field, StrawberryDjangoField) + or is_optimized_by_prefetching(qs) + ): + return qs + + mark_optimized = True + + strawberry_schema = cast(Schema, info.schema._strawberry_schema) # type: ignore + field_name = strawberry_schema.config.name_converter.from_field(field) + field_info = Info( + _raw_info=info, + _field=field, ) + _field_args, field_kwargs = get_arguments( + field=field, + source=None, + info=field_info, + kwargs=get_argument_values( + parent_type.fields[field_name], + field_node, + info.variable_values, + ), + config=strawberry_schema.config, + scalar_registry=strawberry_schema.schema_converter.scalar_registry, + ) + field_kwargs.pop("info", None) + + # Disable the optimizer to avoid doint double optimization while running get_queryset + with DjangoOptimizerExtension.disabled(): + qs = field.get_queryset( + qs, + field_info, + _strawberry_related_field_id=related_field_id, + **field_kwargs, + ) + + connection_extension = next( + ( + e + for e in field.extensions + if isinstance(e, StrawberryDjangoConnectionExtension) + ), + None, + ) + if connection_extension is not None: + connection_type_def = get_object_definition( + connection_extension.connection_type, + strict=True, + ) + connection_type = ( + connection_type_def.concrete_of + and connection_type_def.concrete_of.origin + ) + if ( + connection_type is relay.ListConnection + or connection_type is ListConnectionWithTotalCount + ): + slice_metadata = SliceMetadata.from_arguments( + Info(_raw_info=info, _field=field), + first=field_kwargs.get("first"), + last=field_kwargs.get("last"), + before=field_kwargs.get("before"), + after=field_kwargs.get("after"), + ) + qs = apply_window_pagination( + qs, + related_field_id=related_field_id, + offset=slice_metadata.start, + limit=slice_metadata.end - slice_metadata.start, + ) + else: + mark_optimized = False + + if mark_optimized: + qs = mark_optimized_by_prefetching(qs) + + return qs def _get_selections( @@ -583,6 +716,7 @@ def _get_model_hints( ) if f_store is not None: + related_field_id = None if ( (config is None or config.enable_only) and f_store.only @@ -598,9 +732,10 @@ def _get_model_hints( f_store.only.append(model_field.object_id_field_name) f_store.only.append(model_field.content_type_field_name) else: - f_store.only.append( - remote_field.attname or remote_field.name, + related_field_id = ( + remote_field.attname or remote_field.name ) + f_store.only.append(related_field_id) path_lookup = f"{path}{LOOKUP_SEP}" if store.only and f_store.only: @@ -627,15 +762,15 @@ def _get_model_hints( cache.setdefault(remote_model, []).append((level, f_store)) - # If prefetch_custom_queryset is false, use _base_manager here - # instead of _default_manager because we are getting related - # objects, and not querying it directly. Else use the type's - # get_queryset and model's custom QuerySet. base_qs = _get_prefetch_queryset( remote_model, + schema, field, - config, - info, + parent_type, + f_selection, + config=config, + info=f_info, + related_field_id=related_field_id, ) f_qs = f_store.apply(base_qs, info=f_info, config=config) f_prefetch = Prefetch(path, queryset=f_qs) @@ -671,12 +806,6 @@ def _get_model_hints_from_connection( cache: dict[type[models.Model], list[tuple[int, OptimizerStore]]] | None = None, level: int = 0, ) -> OptimizerStore | None: - # TODO: Connections are mostly used for pagination so it doesn't make sense for - # us to optimize those, as our prefetch would be thrown away causing an extra - # useless query. Is there a way for us to properly optimize this in the future? - if level > 0: - return None - store = None n_type = object_definition.type_var_map.get("NodeType") @@ -785,7 +914,7 @@ def optimize( # Avoid optimizing twice and also modify an already resolved queryset if ( - get_queryset_config(qs).optimized or qs._result_cache is not None # type: ignore + is_optimized(qs) or qs._result_cache is not None # type: ignore ): return qs @@ -843,6 +972,23 @@ def optimize( return qs +def is_optimized(qs: QuerySet) -> bool: + return get_queryset_config(qs).optimized or is_optimized_by_prefetching(qs) + + +def mark_optimized_by_prefetching(qs: QuerySet[_M]) -> QuerySet[_M]: + # This is a bit of a hack, but there is no easy way to mark a related manager + # as optimized at this phase, so we just add a mark to the queryset that + # we can check leater on using is_optimized_by_prefetching + return qs.annotate(**{ + NESTED_PREFETCH_MARK: models.Value(True), + }) + + +def is_optimized_by_prefetching(qs: QuerySet) -> bool: + return NESTED_PREFETCH_MARK in qs.query.annotations + + optimizer: contextvars.ContextVar[DjangoOptimizerExtension | None] = ( contextvars.ContextVar( "optimizer_ctx", @@ -862,6 +1008,12 @@ class DjangoOptimizerExtension(SchemaExtension): Enable `QuerySet.select_related` optimizations enable_prefetch_related_optimization: Enable `QuerySet.prefetch_related` optimizations + enable_nested_relations_prefetch: + Enable prefetch of nested relations. This will allow for nested + relations to be prefetched even when using filters/ordering/pagination. + Note however that for connections, it will only work when for the + `ListConnection` and `ListConnectionWithTotalCount` types, as this optimization + is not safe to be applied automatically for custom connections. enable_annotate_optimization: Enable `QuerySet.annotate` optimizations @@ -893,6 +1045,7 @@ def __init__( enable_select_related_optimization: bool = True, enable_prefetch_related_optimization: bool = True, enable_annotate_optimization: bool = True, + enable_nested_relations_prefetch: bool = True, execution_context: ExecutionContext | None = None, prefetch_custom_queryset: bool = False, ): @@ -901,6 +1054,7 @@ def __init__( self.enable_select_related = enable_select_related_optimization self.enable_prefetch_related = enable_prefetch_related_optimization self.enable_annotate_optimization = enable_annotate_optimization + self.enable_nested_relations_prefetch = enable_nested_relations_prefetch self.prefetch_custom_queryset = prefetch_custom_queryset def on_execute(self) -> Generator[None, None, None]: @@ -934,6 +1088,7 @@ def resolve( enable_prefetch_related=self.enable_prefetch_related, enable_annotate=self.enable_annotate_optimization, prefetch_custom_queryset=self.prefetch_custom_queryset, + enable_nested_relations_prefetch=self.enable_nested_relations_prefetch, ) ret = django_fetch(optimize(qs=ret, info=info, config=config)) diff --git a/strawberry_django/pagination.py b/strawberry_django/pagination.py index cbd24de19..3750d6f3f 100644 --- a/strawberry_django/pagination.py +++ b/strawberry_django/pagination.py @@ -1,6 +1,9 @@ from typing import TYPE_CHECKING, List, Optional, TypeVar, Union import strawberry +from django.db import DEFAULT_DB_ALIAS +from django.db.models import Count, Window +from django.db.models.functions import RowNumber from strawberry.arguments import StrawberryArgument from strawberry.types import Info from strawberry.unset import UNSET, UnsetType @@ -22,17 +25,76 @@ class OffsetPaginationInput: limit: int = -1 -def apply(pagination: Optional[object], queryset: _QS) -> _QS: +def apply( + pagination: Optional[object], + queryset: _QS, + *, + related_field_id: Optional[str] = None, +) -> _QS: + """Apply pagination to a queryset. + + Args: + ---- + pagination: The pagination input. + queryset: The queryset to apply pagination to. + related_field_id: The related field id to apply pagination to. + When provided, the pagination will be applied using window functions + instead of slicing the queryset. + Useful for prefetches, as those cannot be sliced after being filtered + + """ if pagination in (None, strawberry.UNSET): # noqa: PLR6201 return queryset if not isinstance(pagination, OffsetPaginationInput): raise TypeError(f"Don't know how to resolve pagination {pagination!r}") - start = pagination.offset - stop = start + pagination.limit - - return queryset[start:stop] + if related_field_id is not None: + queryset = apply_window_pagination( + queryset, + related_field_id=related_field_id, + offset=pagination.offset, + limit=pagination.limit, + ) + else: + start = pagination.offset + stop = start + pagination.limit + queryset = queryset[start:stop] + + return queryset + + +def apply_window_pagination( + queryset: _QS, + *, + related_field_id: str, + offset: int = 0, + limit: int = -1, +) -> _QS: + # Based on https://github.com/django/django/pull/15957 + order_by = [ + expr + for expr, _ in queryset.query.get_compiler( + using=queryset._db or DEFAULT_DB_ALIAS # type: ignore + ).get_order_by() + ] + queryset = queryset.annotate( + _strawberry_row_number=Window( + RowNumber(), + partition_by=related_field_id, + order_by=order_by, + ), + _strawberry_total_count=Window( + Count(1), + partition_by=related_field_id, + ), + ) + if offset: + queryset = queryset.filter(_strawberry_row_number__gt=offset) + if limit >= 0: + queryset = queryset.filter(_strawberry_row_number__lte=offset + limit) + + return queryset class StrawberryDjangoPagination(StrawberryDjangoFieldBase): @@ -81,8 +143,10 @@ def apply_pagination( self, queryset: _QS, pagination: Optional[object] = None, + *, + related_field_id: Optional[str] = None, ) -> _QS: - return apply(pagination, queryset) + return apply(pagination, queryset, related_field_id=related_field_id) def get_queryset( self, @@ -90,7 +154,12 @@ def get_queryset( info: Info, *, pagination: Optional[object] = None, + _strawberry_related_field_id: Optional[str] = None, **kwargs, ) -> _QS: queryset = super().get_queryset(queryset, info, **kwargs) - return self.apply_pagination(queryset, pagination) + return self.apply_pagination( + queryset, + pagination, + related_field_id=_strawberry_related_field_id, + ) diff --git a/strawberry_django/queryset.py b/strawberry_django/queryset.py index d2f5b6ac4..d2457d470 100644 --- a/strawberry_django/queryset.py +++ b/strawberry_django/queryset.py @@ -1,9 +1,13 @@ +from __future__ import annotations + import dataclasses -from typing import Any, Optional, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar from django.db.models import Model from django.db.models.query import QuerySet -from strawberry import Info + +if TYPE_CHECKING: + from strawberry import Info _M = TypeVar("_M", bound=Model) @@ -23,7 +27,7 @@ def get_queryset_config(queryset: QuerySet) -> StrawberryDjangoQuerySetConfig: def run_type_get_queryset( qs: QuerySet[_M], origin: Any, - info: Optional[Info] = None, + info: Info | None = None, ) -> QuerySet[_M]: config = get_queryset_config(qs) get_queryset = getattr(origin, "get_queryset", None) diff --git a/strawberry_django/relay.py b/strawberry_django/relay.py index c2260b1bc..d3101bfe0 100644 --- a/strawberry_django/relay.py +++ b/strawberry_django/relay.py @@ -1,4 +1,5 @@ import inspect +import warnings from typing import ( Any, Iterable, @@ -12,16 +13,15 @@ overload, ) -import django import strawberry from asgiref.sync import sync_to_async from django.db import models from strawberry import relay from strawberry.relay.exceptions import NodeIDAnnotationError from strawberry.relay.types import NodeIterableType +from strawberry.type import StrawberryContainer, get_object_definition from strawberry.types.info import Info from strawberry.utils.await_maybe import AwaitableOrValue -from strawberry.utils.inspect import in_async_context from typing_extensions import Literal, Self from strawberry_django.queryset import run_type_get_queryset @@ -50,7 +50,30 @@ class ListConnectionWithTotalCount(relay.ListConnection[relay.NodeType]): @strawberry.field(description="Total quantity of existing nodes.") @django_resolver def total_count(self) -> Optional[int]: + from .optimizer import is_optimized_by_prefetching + assert self.nodes is not None + + if isinstance(self.nodes, models.QuerySet) and is_optimized_by_prefetching( + self.nodes + ): + result = cast(List[relay.NodeType], self.nodes._result_cache) # type: ignore + try: + return ( + result[0]._strawberry_total_count # type: ignore + if result + else 0 + ) + except AttributeError: + warnings.warn( + ( + "Pagination annotations not found, falling back to QuerySet resolution. " + "This might cause n+1 issues..." + ), + RuntimeWarning, + stacklevel=2, + ) + total_count = None try: total_count = cast( @@ -75,18 +98,28 @@ def resolve_connection( last: Optional[int] = None, **kwargs: Any, ) -> AwaitableOrValue[Self]: - # FIXME: Asynchronous queryset iteration is only available on Django 4.1+. - # Remove this when Django we can remove support for django 4.0 and older - if django.VERSION < (4, 1) and in_async_context(): - return sync_to_async(cls.resolve_connection)( # type: ignore - nodes, - info=info, - before=before, - after=after, - first=first, - last=last, - **kwargs, - ) + from strawberry_django.optimizer import is_optimized_by_prefetching + + if isinstance(nodes, models.QuerySet) and is_optimized_by_prefetching(nodes): + try: + return cls.resolve_connection_from_cache( + nodes, + info=info, + before=before, + after=after, + first=first, + last=last, + **kwargs, + ) + except AttributeError: + warnings.warn( + ( + "Pagination annotations not found, falling back to QuerySet resolution. " + "This might cause N+1 issues..." + ), + RuntimeWarning, + stacklevel=2, + ) conn = super().resolve_connection( nodes, @@ -111,6 +144,61 @@ async def wrapper(): conn.nodes = nodes return conn + @classmethod + def resolve_connection_from_cache( + cls, + nodes: NodeIterableType[relay.NodeType], + *, + info: Info, + before: Optional[str] = None, + after: Optional[str] = None, + first: Optional[int] = None, + last: Optional[int] = None, + **kwargs: Any, + ) -> AwaitableOrValue[Self]: + """Resolve the connection from the prefetched cache. + + NOTE: This will try to access `node._strawberry_total_count` and + `node._strawberry_row_number` attributes from the nodes. If they + don't exist, `AttriuteError` will be raised, meaning that we should + fallback to the queryset resolution. + """ + result = nodes._result_cache # type: ignore + + type_def = get_object_definition(cls, strict=True) + field_def = type_def.get_field("edges") + assert field_def + + field = field_def.resolve_type(type_definition=type_def) + while isinstance(field, StrawberryContainer): + field = field.of_type + + edge_class = cast(relay.Edge[relay.NodeType], field) + + edges: List[relay.Edge] = [ + edge_class.resolve_edge( + cls.resolve_node(node, info=info, **kwargs), + cursor=node._strawberry_row_number - 1, + ) + for node in result + ] + has_previous_page = ( + nodes[0]._strawberry_row_number > 1 # type: ignore + if result + else False + ) + has_next_page = result._strawberry_row_number < result if result else False + + return cls( + edges=edges, + page_info=relay.PageInfo( + start_cursor=edges[0].cursor if edges else None, + end_cursor=edges[-1].cursor if edges else None, + has_previous_page=has_previous_page, + has_next_page=has_next_page, + ), + ) + @overload def resolve_model_nodes( diff --git a/strawberry_django/resolvers.py b/strawberry_django/resolvers.py index 77d1c974c..c4e186b0d 100644 --- a/strawberry_django/resolvers.py +++ b/strawberry_django/resolvers.py @@ -184,3 +184,27 @@ def _django_getattr( if empty_file_descriptor_as_null and isinstance(result, FileDescriptor): result = None return result + + +def resolve_base_manager(manager: BaseManager) -> Any: + if (result_instance := getattr(manager, "instance", None)) is not None: + prefetched_cache = getattr(result_instance, "_prefetched_objects_cache", {}) + # Both ManyRelatedManager and RelatedManager are defined inside functions, which + # prevents us from importing and checking isinstance on them directly. + try: + # ManyRelatedManager + return list(prefetched_cache[manager.prefetch_cache_name]) # type: ignore + except (AttributeError, KeyError): + try: + # RelatedManager + result_field = manager.field # type: ignore + cache_name = ( + # 5.1+ uses "cache_name" instead of "get_cache_name() + getattr(result_field.remote_field, "cache_name", None) + or result_field.remote_field.get_cache_name() + ) + return list(prefetched_cache[cache_name]) + except (AttributeError, KeyError): + pass + + return manager.all() diff --git a/tests/fields/test_input.py b/tests/fields/test_input.py index c04b1a290..ab7d84641 100644 --- a/tests/fields/test_input.py +++ b/tests/fields/test_input.py @@ -11,12 +11,6 @@ class InputFieldsModel(models.Model): default = models.IntegerField(default=1) blank = models.IntegerField(blank=True) null = models.IntegerField(null=True) - # NullBoleanField is deprecated and will be removed in Django 5.0 - null_boolean = ( - models.NullBooleanField() # type: ignore - if hasattr(models, "NullBooleanField") - else models.BooleanField(null=True) - ) def test_input_type(): @@ -27,7 +21,6 @@ class InputType: default: auto blank: auto null: auto - null_boolean: auto assert [ (f.name, f.type) for f in get_object_definition(InputType, strict=True).fields @@ -37,7 +30,6 @@ class InputType: ("default", StrawberryOptional(int)), ("blank", StrawberryOptional(int)), ("null", StrawberryOptional(int)), - ("null_boolean", StrawberryOptional(bool)), ] @@ -49,7 +41,6 @@ class InputType: default: auto blank: auto null: auto - null_boolean: auto assert [ (f.name, f.type) for f in get_object_definition(InputType, strict=True).fields @@ -59,7 +50,6 @@ class InputType: ("default", StrawberryOptional(int)), ("blank", StrawberryOptional(int)), ("null", StrawberryOptional(int)), - ("null_boolean", StrawberryOptional(bool)), ] diff --git a/tests/fields/test_types.py b/tests/fields/test_types.py index e9f810ef4..1935c776f 100644 --- a/tests/fields/test_types.py +++ b/tests/fields/test_types.py @@ -36,12 +36,6 @@ class FieldTypesModel(models.Model): generic_ip_address = models.GenericIPAddressField() integer = models.IntegerField() image = models.ImageField() - # NullBooleanField was deprecated and will soon be removed - null_boolean = ( - models.NullBooleanField() # type: ignore - if hasattr(models, "NullBooleanField") - else models.BooleanField(null=True) - ) positive_big_integer = models.PositiveBigIntegerField() positive_integer = models.PositiveIntegerField() positive_small_integer = models.PositiveSmallIntegerField() @@ -86,7 +80,6 @@ class Type: generic_ip_address: auto integer: auto image: auto - null_boolean: auto positive_big_integer: auto positive_integer: auto positive_small_integer: auto @@ -113,7 +106,6 @@ class Type: ("generic_ip_address", str), ("integer", int), ("image", strawberry_django.DjangoImageType), - ("null_boolean", StrawberryOptional(bool)), ("positive_big_integer", int), ("positive_integer", int), ("positive_small_integer", int), diff --git a/tests/filters/test_filters.py b/tests/filters/test_filters.py index 406e4e95e..111aa5b95 100644 --- a/tests/filters/test_filters.py +++ b/tests/filters/test_filters.py @@ -2,7 +2,6 @@ from enum import Enum from typing import Generic, List, Optional, TypeVar, cast -import django import pytest import strawberry from django.test import override_settings @@ -282,14 +281,6 @@ class Query: async def fruits(self, filters: FruitFilter) -> List[Fruit]: queryset = models.Fruit.objects.all() queryset = strawberry_django.filters.apply(filters, queryset) - if django.VERSION < (4, 1): - from asgiref.sync import sync_to_async - - @sync_to_async - def helper(): - return cast(List[Fruit], list(queryset)) - - return await helper() # cast fixes funny typing issue between list and List return cast(List[Fruit], [fruit async for fruit in queryset]) @@ -300,9 +291,8 @@ def helper(): assert isinstance(result, ExecutionResult) assert not result.errors assert result.data is not None - assert result.data["fruits"] == [ - {"id": "1", "name": "strawberry"}, - ] + assert len(result.data["fruits"]) == 1 + assert result.data["fruits"][0]["name"] == "strawberry" def test_resolver_filter_with_inheritance(vegetables): diff --git a/tests/filters/test_filters_v2.py b/tests/filters/test_filters_v2.py index de170c926..3fff143c9 100644 --- a/tests/filters/test_filters_v2.py +++ b/tests/filters/test_filters_v2.py @@ -2,7 +2,6 @@ from enum import Enum from typing import Any, List, Optional, cast -import django import pytest import strawberry from django.db.models import Case, Count, Q, QuerySet, Value, When @@ -448,14 +447,6 @@ async def fruits(self, filters: FruitFilter) -> List[Fruit]: queryset = models.Fruit.objects.all() _info: Any = object() queryset = strawberry_django.filters.apply(filters, queryset, _info) - if django.VERSION < (4, 1): - from asgiref.sync import sync_to_async - - @sync_to_async - def helper(): - return cast(List[Fruit], list(queryset)) - - return await helper() # cast fixes funny typing issue between list and List return cast(List[Fruit], [fruit async for fruit in queryset]) diff --git a/tests/projects/schema.py b/tests/projects/schema.py index 2d082c1cd..d75dbbf0c 100644 --- a/tests/projects/schema.py +++ b/tests/projects/schema.py @@ -128,19 +128,30 @@ class MilestoneOrder: project: Optional[ProjectOrder] -@strawberry_django.filter(Issue) +@strawberry_django.filter(Issue, lookups=True) class IssueFilter: + name: strawberry.auto + @strawberry_django.filter_field() def search(self, value: str, prefix: str) -> Q: return Q(name__contains=value) +@strawberry_django.order(Issue) +class IssueOrder: + name: strawberry.auto + + @strawberry_django.type(Milestone, filters=MilestoneFilter, order=MilestoneOrder) class MilestoneType(relay.Node): name: strawberry.auto due_date: strawberry.auto project: ProjectType - issues: List["IssueType"] + issues: List["IssueType"] = strawberry_django.field( + filters=IssueFilter, + order=IssueOrder, + pagination=True, + ) @strawberry_django.field( prefetch_related=[ @@ -162,7 +173,9 @@ def my_issues(self) -> List["IssueType"]: return self._my_issues # type: ignore @strawberry_django.connection( - ListConnectionWithTotalCount["IssueType"], filters=IssueFilter + ListConnectionWithTotalCount["IssueType"], + field_name="issues", + filters=IssueFilter, ) def issues_with_filters(self) -> List["IssueType"]: return self.issues.all() # type: ignore diff --git a/tests/projects/snapshots/schema.gql b/tests/projects/snapshots/schema.gql index 57aa2e890..4aba3d1f4 100644 --- a/tests/projects/snapshots/schema.gql +++ b/tests/projects/snapshots/schema.gql @@ -241,6 +241,7 @@ input IssueAssigneeInputPartialListInput { } input IssueFilter { + name: StrFilterLookup AND: IssueFilter OR: IssueFilter NOT: IssueFilter @@ -280,6 +281,10 @@ input IssueInputPartialWithoutId { issueAssignees: IssueAssigneeInputPartialListInput } +input IssueOrder { + name: Ordering +} + type IssueType implements Node { """The Globally Unique ID of this object""" id: GlobalID! @@ -366,7 +371,7 @@ type MilestoneType implements Node { name: String! dueDate: Date project: ProjectType! - issues: [IssueType!]! + issues(filters: IssueFilter, order: IssueOrder, pagination: OffsetPaginationInput): [IssueType!]! myIssues: [IssueType!]! issuesWithFilters( filters: IssueFilter diff --git a/tests/projects/snapshots/schema_with_inheritance.gql b/tests/projects/snapshots/schema_with_inheritance.gql index 5c454611b..0e33523eb 100644 --- a/tests/projects/snapshots/schema_with_inheritance.gql +++ b/tests/projects/snapshots/schema_with_inheritance.gql @@ -64,6 +64,7 @@ The `ID` scalar type represents a unique identifier, often used to refetch an ob scalar GlobalID @specifiedBy(url: "https://relay.dev/graphql/objectidentification.htm") input IssueFilter { + name: StrFilterLookup AND: IssueFilter OR: IssueFilter NOT: IssueFilter @@ -80,6 +81,10 @@ input IssueInputSubclass { extra: Int } +input IssueOrder { + name: Ordering +} + type IssueType implements Node { """The Globally Unique ID of this object""" id: GlobalID! @@ -156,7 +161,7 @@ type MilestoneType implements Node { name: String! dueDate: Date project: ProjectType! - issues: [IssueType!]! + issues(filters: IssueFilter, order: IssueOrder, pagination: OffsetPaginationInput): [IssueType!]! myIssues: [IssueType!]! issuesWithFilters( filters: IssueFilter @@ -183,7 +188,7 @@ type MilestoneTypeSubclass implements Node { name: String! dueDate: Date project: ProjectType! - issues: [IssueType!]! + issues(filters: IssueFilter, order: IssueOrder, pagination: OffsetPaginationInput): [IssueType!]! myIssues: [IssueType!]! issuesWithFilters( filters: IssueFilter @@ -219,6 +224,11 @@ input NodeInput { id: GlobalID! } +input OffsetPaginationInput { + offset: Int! = 0 + limit: Int! = -1 +} + type OperationInfo { """List of messages returned by the operation.""" messages: [OperationMessage!]! diff --git a/tests/test_optimizer.py b/tests/test_optimizer.py index 7dd7acfa5..7915e9e62 100644 --- a/tests/test_optimizer.py +++ b/tests/test_optimizer.py @@ -619,7 +619,7 @@ def test_query_connection_nested(db, gql_client: GraphQLTestClient): for issue in t2_issues: t2.issues.add(issue) - with assert_num_queries(5): + with assert_num_queries(2 if DjangoOptimizerExtension.enabled.get() else 5): res = gql_client.query(query) assert res.data == { @@ -1014,7 +1014,7 @@ def test_query_nested_connection_with_filter(db, gql_client: GraphQLTestClient): issue3 = IssueFactory.create(milestone=milestone, name="Bar Foo") IssueFactory.create(milestone=milestone, name="Bar Bin") - with assert_num_queries(5 if DjangoOptimizerExtension.enabled.get() else 2): + with assert_num_queries(2): res = gql_client.query(query, {"id": to_base64("MilestoneType", milestone.pk)}) assert isinstance(res.data, dict) @@ -1055,12 +1055,16 @@ class Query: query1 = utils.generate_query(Query, enable_optimizer=False) query_str = """ + fragment f on ProjectTypeWithPrefetch { + milestones (pagination: {limit: 1}) { + name + } + } + query TestQuery { projects { name - milestones (pagination: {limit: 1}) { - name - } + ...f } } """ @@ -1104,3 +1108,158 @@ class Query: }, ], } + + +@pytest.mark.django_db(transaction=True) +def test_nested_prefetch_with_filter(db, gql_client: GraphQLTestClient): + query = """ + query TestQuery ($id: GlobalID!) { + milestone(id: $id) { + id + name + issues (filters: {search: "Foo"}) { + id + name + } + } + } + """ + + milestone = MilestoneFactory.create() + issue1 = IssueFactory.create(milestone=milestone, name="Foo") + issue2 = IssueFactory.create(milestone=milestone, name="Foo Bar") + IssueFactory.create(milestone=milestone, name="Bar") + issue4 = IssueFactory.create(milestone=milestone, name="Bar Foo") + IssueFactory.create(milestone=milestone, name="Bar Bin") + + with assert_num_queries(2): + res = gql_client.query( + query, + {"id": to_base64("MilestoneType", milestone.pk)}, + ) + + assert isinstance(res.data, dict) + assert res.data == { + "milestone": { + "id": to_base64("MilestoneType", milestone.pk), + "name": milestone.name, + "issues": [ + { + "id": to_base64("IssueType", issue.pk), + "name": issue.name, + } + for issue in [issue1, issue2, issue4] + ], + }, + } + + +@pytest.mark.django_db(transaction=True) +def test_nested_prefetch_with_filter_and_pagination(db, gql_client: GraphQLTestClient): + query = """ + query TestQuery ($id: GlobalID!) { + milestone(id: $id) { + id + name + issues (filters: {search: "Foo"}, pagination: {limit: 2}) { + id + name + } + } + } + """ + + milestone = MilestoneFactory.create() + issue1 = IssueFactory.create(milestone=milestone, name="Foo") + issue2 = IssueFactory.create(milestone=milestone, name="Foo Bar") + IssueFactory.create(milestone=milestone, name="Bar") + IssueFactory.create(milestone=milestone, name="Bar Foo") + IssueFactory.create(milestone=milestone, name="Bar Bin") + + with assert_num_queries(2): + res = gql_client.query( + query, + {"id": to_base64("MilestoneType", milestone.pk)}, + ) + + assert isinstance(res.data, dict) + assert res.data == { + "milestone": { + "id": to_base64("MilestoneType", milestone.pk), + "name": milestone.name, + "issues": [ + { + "id": to_base64("IssueType", issue.pk), + "name": issue.name, + } + for issue in [issue1, issue2] + ], + }, + } + + +@pytest.mark.django_db(transaction=True) +def test_nested_prefetch_with_multiple_levels(db, gql_client: GraphQLTestClient): + query = """ + query TestQuery ($id: GlobalID!) { + milestone(id: $id) { + id + name + issues (order: { name: ASC }) { + id + name + tags { + id + name + } + } + } + } + """ + + milestone = MilestoneFactory.create() + + issue1 = IssueFactory.create(milestone=milestone, name="2Foo") + issue2 = IssueFactory.create(milestone=milestone, name="1Foo") + issue3 = IssueFactory.create(milestone=milestone, name="4Foo") + issue4 = IssueFactory.create(milestone=milestone, name="3Foo") + issue5 = IssueFactory.create(milestone=milestone, name="5Foo") + + tag1 = TagFactory.create() + issue1.tags.add(tag1) + issue2.tags.add(tag1) + tag2 = TagFactory.create() + issue2.tags.add(tag2) + issue3.tags.add(tag2) + + with assert_num_queries(3 if DjangoOptimizerExtension.enabled.get() else 7): + res = gql_client.query( + query, + {"id": to_base64("MilestoneType", milestone.pk)}, + ) + + expected_issues = [ + { + "id": to_base64("IssueType", issue.pk), + "name": issue.name, + "tags": [ + {"id": to_base64("TagType", tag.pk), "name": tag.name} for tag in tags + ], + } + for issue, tags in [ + (issue2, [tag1, tag2]), + (issue1, [tag1]), + (issue4, []), + (issue3, [tag2]), + (issue5, []), + ] + ] + + assert isinstance(res.data, dict) + assert res.data == { + "milestone": { + "id": to_base64("MilestoneType", milestone.pk), + "name": milestone.name, + "issues": expected_issues, + }, + }