diff --git a/.bumpversion.cfg b/.bumpversion.cfg index e8560a6a..9c2cb695 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.3.0a7 +current_version = 3.3.0a9 commit = False tag = False diff --git a/README.md b/README.md index 4a8b4d72..63bcb3a5 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ with GraphQL.js. The current stable version 3.2.6 of GraphQL-core is up-to-date with GraphQL.js version 16.8.2 and supports Python versions 3.6 to 3.13. -You can also try out the latest alpha version 3.3.0a8 of GraphQL-core, +You can also try out the latest alpha version 3.3.0a9 of GraphQL-core, which is up-to-date with GraphQL.js version 17.0.0a3. Please note that this new minor version of GraphQL-core does not support Python 3.6 anymore. diff --git a/docs/conf.py b/docs/conf.py index 7e805756..6d2d8429 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,7 +60,7 @@ # The short X.Y version. # version = '3.3' # The full version, including alpha/beta/rc tags. -version = release = "3.3.0a8" +version = release = "3.3.0a9" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -138,79 +138,78 @@ } # ignore the following undocumented or internal references: -ignore_references = set( - [ - "GNT", - "GT", - "KT", - "T", - "VT", - "TContext", - "Enum", - "traceback", - "types.TracebackType", - "TypeMap", - "AwaitableOrValue", - "DeferredFragmentRecord", - "DeferUsage", - "EnterLeaveVisitor", - "ExperimentalIncrementalExecutionResults", - "FieldGroup", - "FormattedIncrementalResult", - "FormattedPendingResult", - "FormattedSourceLocation", - "GraphQLAbstractType", - "GraphQLCompositeType", - "GraphQLEnumValueMap", - "GraphQLErrorExtensions", - "GraphQLFieldResolver", - "GraphQLInputType", - "GraphQLNullableType", - "GraphQLOutputType", - "GraphQLTypeResolver", - "GroupedFieldSet", - "IncrementalDataRecord", - "IncrementalResult", - "InitialResultRecord", - "Middleware", - "PendingResult", - "StreamItemsRecord", - "StreamRecord", - "SubsequentDataRecord", - "asyncio.events.AbstractEventLoop", - "collections.abc.MutableMapping", - "collections.abc.MutableSet", - "enum.Enum", - "graphql.execution.collect_fields.DeferUsage", - "graphql.execution.collect_fields.CollectFieldsResult", - "graphql.execution.collect_fields.FieldGroup", - "graphql.execution.execute.StreamArguments", - "graphql.execution.execute.StreamUsage", - "graphql.execution.map_async_iterable.map_async_iterable", - "graphql.execution.incremental_publisher.CompletedResult", - "graphql.execution.incremental_publisher.DeferredFragmentRecord", - "graphql.execution.incremental_publisher.DeferredGroupedFieldSetRecord", - "graphql.execution.incremental_publisher.FormattedCompletedResult", - "graphql.execution.incremental_publisher.FormattedPendingResult", - "graphql.execution.incremental_publisher.IncrementalPublisher", - "graphql.execution.incremental_publisher.InitialResultRecord", - "graphql.execution.incremental_publisher.PendingResult", - "graphql.execution.incremental_publisher.StreamItemsRecord", - "graphql.execution.incremental_publisher.StreamRecord", - "graphql.execution.Middleware", - "graphql.language.lexer.EscapeSequence", - "graphql.language.visitor.EnterLeaveVisitor", - "graphql.pyutils.ref_map.K", - "graphql.pyutils.ref_map.V", - "graphql.type.definition.GT_co", - "graphql.type.definition.GNT_co", - "graphql.type.definition.TContext", - "graphql.type.schema.InterfaceImplementations", - "graphql.validation.validation_context.VariableUsage", - "graphql.validation.rules.known_argument_names.KnownArgumentNamesOnDirectivesRule", - "graphql.validation.rules.provided_required_arguments.ProvidedRequiredArgumentsOnDirectivesRule", - ] -) +ignore_references = { + "GNT", + "GT", + "KT", + "T", + "VT", + "TContext", + "Enum", + "traceback", + "types.TracebackType", + "TypeMap", + "AwaitableOrValue", + "DeferredFragmentRecord", + "DeferUsage", + "EnterLeaveVisitor", + "ExperimentalIncrementalExecutionResults", + "FieldGroup", + "FormattedIncrementalResult", + "FormattedPendingResult", + "FormattedSourceLocation", + "GraphQLAbstractType", + "GraphQLCompositeType", + "GraphQLEnumValueMap", + "GraphQLErrorExtensions", + "GraphQLFieldResolver", + "GraphQLInputType", + "GraphQLNullableType", + "GraphQLOutputType", + "GraphQLTypeResolver", + "GroupedFieldSet", + "IncrementalDataRecord", + "IncrementalResult", + "InitialResultRecord", + "Middleware", + "PendingResult", + "StreamItemsRecord", + "StreamRecord", + "SubsequentDataRecord", + "asyncio.events.AbstractEventLoop", + "collections.abc.MutableMapping", + "collections.abc.MutableSet", + "enum.Enum", + "graphql.execution.build_field_plan.FieldGroup", + "graphql.execution.build_field_plan.FieldPlan", + "graphql.execution.collect_fields.DeferUsage", + "graphql.execution.execute.StreamArguments", + "graphql.execution.execute.SubFieldPlan", + "graphql.execution.execute.StreamUsage", + "graphql.execution.map_async_iterable.map_async_iterable", + "graphql.execution.incremental_publisher.CompletedResult", + "graphql.execution.incremental_publisher.DeferredFragmentRecord", + "graphql.execution.incremental_publisher.DeferredGroupedFieldSetRecord", + "graphql.execution.incremental_publisher.FormattedCompletedResult", + "graphql.execution.incremental_publisher.FormattedPendingResult", + "graphql.execution.incremental_publisher.IncrementalPublisher", + "graphql.execution.incremental_publisher.InitialResultRecord", + "graphql.execution.incremental_publisher.PendingResult", + "graphql.execution.incremental_publisher.StreamItemsRecord", + "graphql.execution.incremental_publisher.StreamRecord", + "graphql.execution.Middleware", + "graphql.language.lexer.EscapeSequence", + "graphql.language.visitor.EnterLeaveVisitor", + "graphql.pyutils.ref_map.K", + "graphql.pyutils.ref_map.V", + "graphql.type.definition.GT_co", + "graphql.type.definition.GNT_co", + "graphql.type.definition.TContext", + "graphql.type.schema.InterfaceImplementations", + "graphql.validation.validation_context.VariableUsage", + "graphql.validation.rules.known_argument_names.KnownArgumentNamesOnDirectivesRule", + "graphql.validation.rules.provided_required_arguments.ProvidedRequiredArgumentsOnDirectivesRule", +} ignore_references.update(__builtins__.keys()) @@ -228,10 +227,12 @@ def on_missing_reference(app, env, node, contnode): name = target.rsplit(".", 1)[-1] if name in ("GT", "GNT", "KT", "T", "VT"): return contnode - if typ == "obj": - if target.startswith("typing."): - if name in ("Any", "Optional", "Union"): - return contnode + if ( + typ == "obj" + and target.startswith("typing.") + and name in ("Any", "Optional", "Union") + ): + return contnode if typ != "class": return None if "." in target: # maybe too specific diff --git a/poetry.lock b/poetry.lock index 436e7dc1..1b0fabbd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -104,27 +104,27 @@ files = [ [[package]] name = "cachetools" -version = "6.0.0" +version = "6.1.0" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.9" groups = ["test"] markers = "python_version >= \"3.9\"" files = [ - {file = "cachetools-6.0.0-py3-none-any.whl", hash = "sha256:82e73ba88f7b30228b5507dce1a1f878498fc669d972aef2dde4f3a3c24f103e"}, - {file = "cachetools-6.0.0.tar.gz", hash = "sha256:f225782b84438f828328fc2ad74346522f27e5b1440f4e9fd18b20ebfd1aa2cf"}, + {file = "cachetools-6.1.0-py3-none-any.whl", hash = "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e"}, + {file = "cachetools-6.1.0.tar.gz", hash = "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587"}, ] [[package]] name = "certifi" -version = "2025.4.26" +version = "2025.6.15" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" groups = ["doc"] files = [ - {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"}, - {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"}, + {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"}, + {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"}, ] [[package]] @@ -582,80 +582,80 @@ toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "coverage" -version = "7.8.2" +version = "7.9.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["test"] markers = "python_version >= \"3.9\"" files = [ - {file = "coverage-7.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd8ec21e1443fd7a447881332f7ce9d35b8fbd2849e761bb290b584535636b0a"}, - {file = "coverage-7.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c26c2396674816deaeae7ded0e2b42c26537280f8fe313335858ffff35019be"}, - {file = "coverage-7.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1aec326ed237e5880bfe69ad41616d333712c7937bcefc1343145e972938f9b3"}, - {file = "coverage-7.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e818796f71702d7a13e50c70de2a1924f729228580bcba1607cccf32eea46e6"}, - {file = "coverage-7.8.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:546e537d9e24efc765c9c891328f30f826e3e4808e31f5d0f87c4ba12bbd1622"}, - {file = "coverage-7.8.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab9b09a2349f58e73f8ebc06fac546dd623e23b063e5398343c5270072e3201c"}, - {file = "coverage-7.8.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fd51355ab8a372d89fb0e6a31719e825cf8df8b6724bee942fb5b92c3f016ba3"}, - {file = "coverage-7.8.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0774df1e093acb6c9e4d58bce7f86656aeed6c132a16e2337692c12786b32404"}, - {file = "coverage-7.8.2-cp310-cp310-win32.whl", hash = "sha256:00f2e2f2e37f47e5f54423aeefd6c32a7dbcedc033fcd3928a4f4948e8b96af7"}, - {file = "coverage-7.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:145b07bea229821d51811bf15eeab346c236d523838eda395ea969d120d13347"}, - {file = "coverage-7.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b99058eef42e6a8dcd135afb068b3d53aff3921ce699e127602efff9956457a9"}, - {file = "coverage-7.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5feb7f2c3e6ea94d3b877def0270dff0947b8d8c04cfa34a17be0a4dc1836879"}, - {file = "coverage-7.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:670a13249b957bb9050fab12d86acef7bf8f6a879b9d1a883799276e0d4c674a"}, - {file = "coverage-7.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bdc8bf760459a4a4187b452213e04d039990211f98644c7292adf1e471162b5"}, - {file = "coverage-7.8.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07a989c867986c2a75f158f03fdb413128aad29aca9d4dbce5fc755672d96f11"}, - {file = "coverage-7.8.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2db10dedeb619a771ef0e2949ccba7b75e33905de959c2643a4607bef2f3fb3a"}, - {file = "coverage-7.8.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e6ea7dba4e92926b7b5f0990634b78ea02f208d04af520c73a7c876d5a8d36cb"}, - {file = "coverage-7.8.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ef2f22795a7aca99fc3c84393a55a53dd18ab8c93fb431004e4d8f0774150f54"}, - {file = "coverage-7.8.2-cp311-cp311-win32.whl", hash = "sha256:641988828bc18a6368fe72355df5f1703e44411adbe49bba5644b941ce6f2e3a"}, - {file = "coverage-7.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8ab4a51cb39dc1933ba627e0875046d150e88478dbe22ce145a68393e9652975"}, - {file = "coverage-7.8.2-cp311-cp311-win_arm64.whl", hash = "sha256:8966a821e2083c74d88cca5b7dcccc0a3a888a596a04c0b9668a891de3a0cc53"}, - {file = "coverage-7.8.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2f6fe3654468d061942591aef56686131335b7a8325684eda85dacdf311356c"}, - {file = "coverage-7.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76090fab50610798cc05241bf83b603477c40ee87acd358b66196ab0ca44ffa1"}, - {file = "coverage-7.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd0a0a5054be160777a7920b731a0570284db5142abaaf81bcbb282b8d99279"}, - {file = "coverage-7.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da23ce9a3d356d0affe9c7036030b5c8f14556bd970c9b224f9c8205505e3b99"}, - {file = "coverage-7.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9392773cffeb8d7e042a7b15b82a414011e9d2b5fdbbd3f7e6a6b17d5e21b20"}, - {file = "coverage-7.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:876cbfd0b09ce09d81585d266c07a32657beb3eaec896f39484b631555be0fe2"}, - {file = "coverage-7.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3da9b771c98977a13fbc3830f6caa85cae6c9c83911d24cb2d218e9394259c57"}, - {file = "coverage-7.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a990f6510b3292686713bfef26d0049cd63b9c7bb17e0864f133cbfd2e6167f"}, - {file = "coverage-7.8.2-cp312-cp312-win32.whl", hash = "sha256:bf8111cddd0f2b54d34e96613e7fbdd59a673f0cf5574b61134ae75b6f5a33b8"}, - {file = "coverage-7.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:86a323a275e9e44cdf228af9b71c5030861d4d2610886ab920d9945672a81223"}, - {file = "coverage-7.8.2-cp312-cp312-win_arm64.whl", hash = "sha256:820157de3a589e992689ffcda8639fbabb313b323d26388d02e154164c57b07f"}, - {file = "coverage-7.8.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ea561010914ec1c26ab4188aef8b1567272ef6de096312716f90e5baa79ef8ca"}, - {file = "coverage-7.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cb86337a4fcdd0e598ff2caeb513ac604d2f3da6d53df2c8e368e07ee38e277d"}, - {file = "coverage-7.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a4636ddb666971345541b59899e969f3b301143dd86b0ddbb570bd591f1e85"}, - {file = "coverage-7.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5040536cf9b13fb033f76bcb5e1e5cb3b57c4807fef37db9e0ed129c6a094257"}, - {file = "coverage-7.8.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc67994df9bcd7e0150a47ef41278b9e0a0ea187caba72414b71dc590b99a108"}, - {file = "coverage-7.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e6c86888fd076d9e0fe848af0a2142bf606044dc5ceee0aa9eddb56e26895a0"}, - {file = "coverage-7.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:684ca9f58119b8e26bef860db33524ae0365601492e86ba0b71d513f525e7050"}, - {file = "coverage-7.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8165584ddedb49204c4e18da083913bdf6a982bfb558632a79bdaadcdafd0d48"}, - {file = "coverage-7.8.2-cp313-cp313-win32.whl", hash = "sha256:34759ee2c65362163699cc917bdb2a54114dd06d19bab860725f94ef45a3d9b7"}, - {file = "coverage-7.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:2f9bc608fbafaee40eb60a9a53dbfb90f53cc66d3d32c2849dc27cf5638a21e3"}, - {file = "coverage-7.8.2-cp313-cp313-win_arm64.whl", hash = "sha256:9fe449ee461a3b0c7105690419d0b0aba1232f4ff6d120a9e241e58a556733f7"}, - {file = "coverage-7.8.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8369a7c8ef66bded2b6484053749ff220dbf83cba84f3398c84c51a6f748a008"}, - {file = "coverage-7.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:159b81df53a5fcbc7d45dae3adad554fdbde9829a994e15227b3f9d816d00b36"}, - {file = "coverage-7.8.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6fcbbd35a96192d042c691c9e0c49ef54bd7ed865846a3c9d624c30bb67ce46"}, - {file = "coverage-7.8.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05364b9cc82f138cc86128dc4e2e1251c2981a2218bfcd556fe6b0fbaa3501be"}, - {file = "coverage-7.8.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46d532db4e5ff3979ce47d18e2fe8ecad283eeb7367726da0e5ef88e4fe64740"}, - {file = "coverage-7.8.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4000a31c34932e7e4fa0381a3d6deb43dc0c8f458e3e7ea6502e6238e10be625"}, - {file = "coverage-7.8.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:43ff5033d657cd51f83015c3b7a443287250dc14e69910577c3e03bd2e06f27b"}, - {file = "coverage-7.8.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94316e13f0981cbbba132c1f9f365cac1d26716aaac130866ca812006f662199"}, - {file = "coverage-7.8.2-cp313-cp313t-win32.whl", hash = "sha256:3f5673888d3676d0a745c3d0e16da338c5eea300cb1f4ada9c872981265e76d8"}, - {file = "coverage-7.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:2c08b05ee8d7861e45dc5a2cc4195c8c66dca5ac613144eb6ebeaff2d502e73d"}, - {file = "coverage-7.8.2-cp313-cp313t-win_arm64.whl", hash = "sha256:1e1448bb72b387755e1ff3ef1268a06617afd94188164960dba8d0245a46004b"}, - {file = "coverage-7.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:496948261eaac5ac9cf43f5d0a9f6eb7a6d4cb3bedb2c5d294138142f5c18f2a"}, - {file = "coverage-7.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eacd2de0d30871eff893bab0b67840a96445edcb3c8fd915e6b11ac4b2f3fa6d"}, - {file = "coverage-7.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b039ffddc99ad65d5078ef300e0c7eed08c270dc26570440e3ef18beb816c1ca"}, - {file = "coverage-7.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e49824808d4375ede9dd84e9961a59c47f9113039f1a525e6be170aa4f5c34d"}, - {file = "coverage-7.8.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b069938961dfad881dc2f8d02b47645cd2f455d3809ba92a8a687bf513839787"}, - {file = "coverage-7.8.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:de77c3ba8bb686d1c411e78ee1b97e6e0b963fb98b1637658dd9ad2c875cf9d7"}, - {file = "coverage-7.8.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1676628065a498943bd3f64f099bb573e08cf1bc6088bbe33cf4424e0876f4b3"}, - {file = "coverage-7.8.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8e1a26e7e50076e35f7afafde570ca2b4d7900a491174ca357d29dece5aacee7"}, - {file = "coverage-7.8.2-cp39-cp39-win32.whl", hash = "sha256:6782a12bf76fa61ad9350d5a6ef5f3f020b57f5e6305cbc663803f2ebd0f270a"}, - {file = "coverage-7.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:1efa4166ba75ccefd647f2d78b64f53f14fb82622bc94c5a5cb0a622f50f1c9e"}, - {file = "coverage-7.8.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:ec455eedf3ba0bbdf8f5a570012617eb305c63cb9f03428d39bf544cb2b94837"}, - {file = "coverage-7.8.2-py3-none-any.whl", hash = "sha256:726f32ee3713f7359696331a18daf0c3b3a70bb0ae71141b9d3c52be7c595e32"}, - {file = "coverage-7.8.2.tar.gz", hash = "sha256:a886d531373a1f6ff9fad2a2ba4a045b68467b779ae729ee0b3b10ac20033b27"}, + {file = "coverage-7.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc94d7c5e8423920787c33d811c0be67b7be83c705f001f7180c7b186dcf10ca"}, + {file = "coverage-7.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16aa0830d0c08a2c40c264cef801db8bc4fc0e1892782e45bcacbd5889270509"}, + {file = "coverage-7.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf95981b126f23db63e9dbe4cf65bd71f9a6305696fa5e2262693bc4e2183f5b"}, + {file = "coverage-7.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f05031cf21699785cd47cb7485f67df619e7bcdae38e0fde40d23d3d0210d3c3"}, + {file = "coverage-7.9.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4fbcab8764dc072cb651a4bcda4d11fb5658a1d8d68842a862a6610bd8cfa3"}, + {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16649a7330ec307942ed27d06ee7e7a38417144620bb3d6e9a18ded8a2d3e5"}, + {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cea0a27a89e6432705fffc178064503508e3c0184b4f061700e771a09de58187"}, + {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e980b53a959fa53b6f05343afbd1e6f44a23ed6c23c4b4c56c6662bbb40c82ce"}, + {file = "coverage-7.9.1-cp310-cp310-win32.whl", hash = "sha256:70760b4c5560be6ca70d11f8988ee6542b003f982b32f83d5ac0b72476607b70"}, + {file = "coverage-7.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a66e8f628b71f78c0e0342003d53b53101ba4e00ea8dabb799d9dba0abbbcebe"}, + {file = "coverage-7.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95c765060e65c692da2d2f51a9499c5e9f5cf5453aeaf1420e3fc847cc060582"}, + {file = "coverage-7.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba383dc6afd5ec5b7a0d0c23d38895db0e15bcba7fb0fa8901f245267ac30d86"}, + {file = "coverage-7.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37ae0383f13cbdcf1e5e7014489b0d71cc0106458878ccde52e8a12ced4298ed"}, + {file = "coverage-7.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69aa417a030bf11ec46149636314c24c8d60fadb12fc0ee8f10fda0d918c879d"}, + {file = "coverage-7.9.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4be2a28656afe279b34d4f91c3e26eccf2f85500d4a4ff0b1f8b54bf807338"}, + {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:382e7ddd5289f140259b610e5f5c58f713d025cb2f66d0eb17e68d0a94278875"}, + {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e5532482344186c543c37bfad0ee6069e8ae4fc38d073b8bc836fc8f03c9e250"}, + {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a39d18b3f50cc121d0ce3838d32d58bd1d15dab89c910358ebefc3665712256c"}, + {file = "coverage-7.9.1-cp311-cp311-win32.whl", hash = "sha256:dd24bd8d77c98557880def750782df77ab2b6885a18483dc8588792247174b32"}, + {file = "coverage-7.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b55ad10a35a21b8015eabddc9ba31eb590f54adc9cd39bcf09ff5349fd52125"}, + {file = "coverage-7.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ad935f0016be24c0e97fc8c40c465f9c4b85cbbe6eac48934c0dc4d2568321e"}, + {file = "coverage-7.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8de12b4b87c20de895f10567639c0797b621b22897b0af3ce4b4e204a743626"}, + {file = "coverage-7.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5add197315a054e92cee1b5f686a2bcba60c4c3e66ee3de77ace6c867bdee7cb"}, + {file = "coverage-7.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600a1d4106fe66f41e5d0136dfbc68fe7200a5cbe85610ddf094f8f22e1b0300"}, + {file = "coverage-7.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a876e4c3e5a2a1715a6608906aa5a2e0475b9c0f68343c2ada98110512ab1d8"}, + {file = "coverage-7.9.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f34346dd63010453922c8e628a52ea2d2ccd73cb2487f7700ac531b247c8a5"}, + {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:888f8eee13f2377ce86d44f338968eedec3291876b0b8a7289247ba52cb984cd"}, + {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9969ef1e69b8c8e1e70d591f91bbc37fc9a3621e447525d1602801a24ceda898"}, + {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60c458224331ee3f1a5b472773e4a085cc27a86a0b48205409d364272d67140d"}, + {file = "coverage-7.9.1-cp312-cp312-win32.whl", hash = "sha256:5f646a99a8c2b3ff4c6a6e081f78fad0dde275cd59f8f49dc4eab2e394332e74"}, + {file = "coverage-7.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:30f445f85c353090b83e552dcbbdad3ec84c7967e108c3ae54556ca69955563e"}, + {file = "coverage-7.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:af41da5dca398d3474129c58cb2b106a5d93bbb196be0d307ac82311ca234342"}, + {file = "coverage-7.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:31324f18d5969feef7344a932c32428a2d1a3e50b15a6404e97cba1cc9b2c631"}, + {file = "coverage-7.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c804506d624e8a20fb3108764c52e0eef664e29d21692afa375e0dd98dc384f"}, + {file = "coverage-7.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef64c27bc40189f36fcc50c3fb8f16ccda73b6a0b80d9bd6e6ce4cffcd810bbd"}, + {file = "coverage-7.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4fe2348cc6ec372e25adec0219ee2334a68d2f5222e0cba9c0d613394e12d86"}, + {file = "coverage-7.9.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34ed2186fe52fcc24d4561041979a0dec69adae7bce2ae8d1c49eace13e55c43"}, + {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25308bd3d00d5eedd5ae7d4357161f4df743e3c0240fa773ee1b0f75e6c7c0f1"}, + {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73e9439310f65d55a5a1e0564b48e34f5369bee943d72c88378f2d576f5a5751"}, + {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ab6be0859141b53aa89412a82454b482c81cf750de4f29223d52268a86de67"}, + {file = "coverage-7.9.1-cp313-cp313-win32.whl", hash = "sha256:64bdd969456e2d02a8b08aa047a92d269c7ac1f47e0c977675d550c9a0863643"}, + {file = "coverage-7.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:be9e3f68ca9edb897c2184ad0eee815c635565dbe7a0e7e814dc1f7cbab92c0a"}, + {file = "coverage-7.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:1c503289ffef1d5105d91bbb4d62cbe4b14bec4d13ca225f9c73cde9bb46207d"}, + {file = "coverage-7.9.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0b3496922cb5f4215bf5caaef4cf12364a26b0be82e9ed6d050f3352cf2d7ef0"}, + {file = "coverage-7.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9565c3ab1c93310569ec0d86b017f128f027cab0b622b7af288696d7ed43a16d"}, + {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2241ad5dbf79ae1d9c08fe52b36d03ca122fb9ac6bca0f34439e99f8327ac89f"}, + {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bb5838701ca68b10ebc0937dbd0eb81974bac54447c55cd58dea5bca8451029"}, + {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a25f814591a8c0c5372c11ac8967f669b97444c47fd794926e175c4047ece"}, + {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2d04b16a6062516df97969f1ae7efd0de9c31eb6ebdceaa0d213b21c0ca1a683"}, + {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7931b9e249edefb07cd6ae10c702788546341d5fe44db5b6108a25da4dca513f"}, + {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52e92b01041151bf607ee858e5a56c62d4b70f4dac85b8c8cb7fb8a351ab2c10"}, + {file = "coverage-7.9.1-cp313-cp313t-win32.whl", hash = "sha256:684e2110ed84fd1ca5f40e89aa44adf1729dc85444004111aa01866507adf363"}, + {file = "coverage-7.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:437c576979e4db840539674e68c84b3cda82bc824dd138d56bead1435f1cb5d7"}, + {file = "coverage-7.9.1-cp313-cp313t-win_arm64.whl", hash = "sha256:18a0912944d70aaf5f399e350445738a1a20b50fbea788f640751c2ed9208b6c"}, + {file = "coverage-7.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f424507f57878e424d9a95dc4ead3fbdd72fd201e404e861e465f28ea469951"}, + {file = "coverage-7.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:535fde4001b2783ac80865d90e7cc7798b6b126f4cd8a8c54acfe76804e54e58"}, + {file = "coverage-7.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02532fd3290bb8fa6bec876520842428e2a6ed6c27014eca81b031c2d30e3f71"}, + {file = "coverage-7.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56f5eb308b17bca3bbff810f55ee26d51926d9f89ba92707ee41d3c061257e55"}, + {file = "coverage-7.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfa447506c1a52271f1b0de3f42ea0fa14676052549095e378d5bff1c505ff7b"}, + {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9ca8e220006966b4a7b68e8984a6aee645a0384b0769e829ba60281fe61ec4f7"}, + {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49f1d0788ba5b7ba65933f3a18864117c6506619f5ca80326b478f72acf3f385"}, + {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68cd53aec6f45b8e4724c0950ce86eacb775c6be01ce6e3669fe4f3a21e768ed"}, + {file = "coverage-7.9.1-cp39-cp39-win32.whl", hash = "sha256:95335095b6c7b1cc14c3f3f17d5452ce677e8490d101698562b2ffcacc304c8d"}, + {file = "coverage-7.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:e1b5191d1648acc439b24721caab2fd0c86679d8549ed2c84d5a7ec1bedcc244"}, + {file = "coverage-7.9.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:db0f04118d1db74db6c9e1cb1898532c7dcc220f1d2718f058601f7c3f499514"}, + {file = "coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c"}, + {file = "coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec"}, ] [package.dependencies] @@ -1224,49 +1224,50 @@ reports = ["lxml"] [[package]] name = "mypy" -version = "1.15.0" +version = "1.16.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["lint"] markers = "python_version >= \"3.9\"" files = [ - {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"}, - {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"}, - {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"}, - {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"}, - {file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"}, - {file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"}, - {file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"}, - {file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"}, - {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"}, - {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"}, - {file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"}, - {file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"}, - {file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"}, - {file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"}, - {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"}, - {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"}, - {file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"}, - {file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"}, - {file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"}, - {file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"}, - {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"}, - {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"}, - {file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"}, - {file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"}, - {file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"}, - {file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"}, - {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"}, - {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"}, - {file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"}, - {file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"}, - {file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"}, - {file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"}, + {file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"}, + {file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"}, + {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"}, + {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"}, + {file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"}, + {file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"}, + {file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"}, + {file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"}, + {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"}, + {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"}, + {file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"}, + {file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"}, + {file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"}, + {file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"}, + {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"}, + {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"}, + {file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"}, + {file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"}, + {file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"}, + {file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"}, + {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"}, + {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"}, + {file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"}, + {file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"}, + {file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"}, + {file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"}, + {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"}, + {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"}, + {file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"}, + {file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"}, + {file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"}, + {file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"}, ] [package.dependencies] mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing_extensions = ">=4.6.0" @@ -1329,6 +1330,19 @@ files = [ {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["lint"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + [[package]] name = "platformdirs" version = "4.0.0" @@ -1597,7 +1611,7 @@ description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" groups = ["test"] -markers = "python_version >= \"3.8\"" +markers = "python_version == \"3.8\"" files = [ {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, @@ -1614,6 +1628,31 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest" +version = "8.4.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +groups = ["test"] +markers = "python_version >= \"3.9\"" +files = [ + {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, + {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + [[package]] name = "pytest-asyncio" version = "0.21.2" @@ -1818,20 +1857,21 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-cov" -version = "6.1.1" +version = "6.2.1" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" groups = ["test"] markers = "python_version >= \"3.9\"" files = [ - {file = "pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde"}, - {file = "pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a"}, + {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"}, + {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"}, ] [package.dependencies] coverage = {version = ">=7.5", extras = ["toml"]} -pytest = ">=4.6" +pluggy = ">=1.2" +pytest = ">=6.2.5" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] @@ -1904,20 +1944,20 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests" -version = "2.32.3" +version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" groups = ["doc"] markers = "python_version >= \"3.8\"" files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, + {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, + {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" +charset_normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" @@ -1948,30 +1988,30 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruff" -version = "0.11.11" +version = "0.12.0" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["lint"] files = [ - {file = "ruff-0.11.11-py3-none-linux_armv6l.whl", hash = "sha256:9924e5ae54125ed8958a4f7de320dab7380f6e9fa3195e3dc3b137c6842a0092"}, - {file = "ruff-0.11.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8a93276393d91e952f790148eb226658dd275cddfde96c6ca304873f11d2ae4"}, - {file = "ruff-0.11.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6e333dbe2e6ae84cdedefa943dfd6434753ad321764fd937eef9d6b62022bcd"}, - {file = "ruff-0.11.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7885d9a5e4c77b24e8c88aba8c80be9255fa22ab326019dac2356cff42089fc6"}, - {file = "ruff-0.11.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b5ab797fcc09121ed82e9b12b6f27e34859e4227080a42d090881be888755d4"}, - {file = "ruff-0.11.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e231ff3132c1119ece836487a02785f099a43992b95c2f62847d29bace3c75ac"}, - {file = "ruff-0.11.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a97c9babe1d4081037a90289986925726b802d180cca784ac8da2bbbc335f709"}, - {file = "ruff-0.11.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8c4ddcbe8a19f59f57fd814b8b117d4fcea9bee7c0492e6cf5fdc22cfa563c8"}, - {file = "ruff-0.11.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6224076c344a7694c6fbbb70d4f2a7b730f6d47d2a9dc1e7f9d9bb583faf390b"}, - {file = "ruff-0.11.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:882821fcdf7ae8db7a951df1903d9cb032bbe838852e5fc3c2b6c3ab54e39875"}, - {file = "ruff-0.11.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:dcec2d50756463d9df075a26a85a6affbc1b0148873da3997286caf1ce03cae1"}, - {file = "ruff-0.11.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99c28505ecbaeb6594701a74e395b187ee083ee26478c1a795d35084d53ebd81"}, - {file = "ruff-0.11.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9263f9e5aa4ff1dec765e99810f1cc53f0c868c5329b69f13845f699fe74f639"}, - {file = "ruff-0.11.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:64ac6f885e3ecb2fdbb71de2701d4e34526651f1e8503af8fb30d4915a3fe345"}, - {file = "ruff-0.11.11-py3-none-win32.whl", hash = "sha256:1adcb9a18802268aaa891ffb67b1c94cd70578f126637118e8099b8e4adcf112"}, - {file = "ruff-0.11.11-py3-none-win_amd64.whl", hash = "sha256:748b4bb245f11e91a04a4ff0f96e386711df0a30412b9fe0c74d5bdc0e4a531f"}, - {file = "ruff-0.11.11-py3-none-win_arm64.whl", hash = "sha256:6c51f136c0364ab1b774767aa8b86331bd8e9d414e2d107db7a2189f35ea1f7b"}, - {file = "ruff-0.11.11.tar.gz", hash = "sha256:7774173cc7c1980e6bf67569ebb7085989a78a103922fb83ef3dfe230cd0687d"}, + {file = "ruff-0.12.0-py3-none-linux_armv6l.whl", hash = "sha256:5652a9ecdb308a1754d96a68827755f28d5dfb416b06f60fd9e13f26191a8848"}, + {file = "ruff-0.12.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:05ed0c914fabc602fc1f3b42c53aa219e5736cb030cdd85640c32dbc73da74a6"}, + {file = "ruff-0.12.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:07a7aa9b69ac3fcfda3c507916d5d1bca10821fe3797d46bad10f2c6de1edda0"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7731c3eec50af71597243bace7ec6104616ca56dda2b99c89935fe926bdcd48"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:952d0630eae628250ab1c70a7fffb641b03e6b4a2d3f3ec6c1d19b4ab6c6c807"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c021f04ea06966b02614d442e94071781c424ab8e02ec7af2f037b4c1e01cc82"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d235618283718ee2fe14db07f954f9b2423700919dc688eacf3f8797a11315c"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0758038f81beec8cc52ca22de9685b8ae7f7cc18c013ec2050012862cc9165"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:139b3d28027987b78fc8d6cfb61165447bdf3740e650b7c480744873688808c2"}, + {file = "ruff-0.12.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68853e8517b17bba004152aebd9dd77d5213e503a5f2789395b25f26acac0da4"}, + {file = "ruff-0.12.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3a9512af224b9ac4757f7010843771da6b2b0935a9e5e76bb407caa901a1a514"}, + {file = "ruff-0.12.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b08df3d96db798e5beb488d4df03011874aff919a97dcc2dd8539bb2be5d6a88"}, + {file = "ruff-0.12.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6a315992297a7435a66259073681bb0d8647a826b7a6de45c6934b2ca3a9ed51"}, + {file = "ruff-0.12.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e55e44e770e061f55a7dbc6e9aed47feea07731d809a3710feda2262d2d4d8a"}, + {file = "ruff-0.12.0-py3-none-win32.whl", hash = "sha256:7162a4c816f8d1555eb195c46ae0bd819834d2a3f18f98cc63819a7b46f474fb"}, + {file = "ruff-0.12.0-py3-none-win_amd64.whl", hash = "sha256:d00b7a157b8fb6d3827b49d3324da34a1e3f93492c1f97b08e222ad7e9b291e0"}, + {file = "ruff-0.12.0-py3-none-win_arm64.whl", hash = "sha256:8cd24580405ad8c1cc64d61725bca091d6b6da7eb3d36f72cc605467069d7e8b"}, + {file = "ruff-0.12.0.tar.gz", hash = "sha256:4d047db3662418d4a848a3fdbfaf17488b34b62f527ed6f10cb8afd78135bc5c"}, ] [[package]] @@ -2544,15 +2584,15 @@ test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.4)", "pytest-mock (>=3.14)"] [[package]] name = "tox" -version = "4.26.0" +version = "4.27.0" description = "tox is a generic virtualenv management and test command line tool" optional = false python-versions = ">=3.9" groups = ["test"] markers = "python_version >= \"3.9\"" files = [ - {file = "tox-4.26.0-py3-none-any.whl", hash = "sha256:75f17aaf09face9b97bd41645028d9f722301e912be8b4c65a3f938024560224"}, - {file = "tox-4.26.0.tar.gz", hash = "sha256:a83b3b67b0159fa58e44e646505079e35a43317a62d2ae94725e0586266faeca"}, + {file = "tox-4.27.0-py3-none-any.whl", hash = "sha256:2b8a7fb986b82aa2c830c0615082a490d134e0626dbc9189986da46a313c4f20"}, + {file = "tox-4.27.0.tar.gz", hash = "sha256:b97d5ecc0c0d5755bcc5348387fef793e1bfa68eb33746412f4c60881d7f5f57"}, ] [package.dependencies] @@ -2643,11 +2683,24 @@ description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["main", "lint", "test"] +markers = "python_version == \"3.8\"" files = [ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, ] -markers = {main = "python_version >= \"3.8\" and python_version < \"3.10\"", lint = "python_version >= \"3.8\"", test = "python_version >= \"3.8\" and python_version <= \"3.10\""} + +[[package]] +name = "typing-extensions" +version = "4.14.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main", "lint", "test"] +files = [ + {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"}, + {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"}, +] +markers = {main = "python_version == \"3.9\"", lint = "python_version >= \"3.9\"", test = "python_version >= \"3.9\" and python_version < \"3.11\""} [[package]] name = "urllib3" @@ -2689,15 +2742,15 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "urllib3" -version = "2.4.0" +version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["doc"] markers = "python_version >= \"3.9\"" files = [ - {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, - {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] @@ -2791,15 +2844,15 @@ type = ["pytest-mypy"] [[package]] name = "zipp" -version = "3.21.0" +version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" groups = ["doc", "test"] markers = "python_version == \"3.9\"" files = [ - {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, - {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, + {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, + {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] @@ -2807,10 +2860,10 @@ check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \" cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.7" -content-hash = "6175c8fcbba0fa4c6c6b2221b3392b843e3a137e1456e263de89f997b2c3e0ce" +content-hash = "3a799a01d8f5813c295459d99bcfb2bed3ac5a8a0b25f89115755e996bbd219b" diff --git a/pyproject.toml b/pyproject.toml index 1fcfded8..c5c1ca31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "graphql-core" -version = "3.3.0a8" +version = "3.3.0a9" description = """\ GraphQL-core is a Python port of GraphQL.js,\ the JavaScript reference implementation for GraphQL.""" @@ -85,9 +85,9 @@ tox = [ optional = true [tool.poetry.group.lint.dependencies] -ruff = ">=0.11,<0.12" +ruff = ">=0.12,<0.13" mypy = [ - { version = "^1.15", python = ">=3.9" }, + { version = "^1.16", python = ">=3.9" }, { version = "~1.14", python = ">=3.8,<3.9" }, { version = "~1.4", python = "<3.8" } ] @@ -165,6 +165,7 @@ ignore = [ "D401", # do not always require imperative mood in first line "FBT001", "FBT002", "FBT003", # allow boolean parameters "ISC001", # allow string literal concatenation for auto-formatting + "PLC0415", # allow run-time imports to avoid circular dependencies "PGH003", # type ignores do not need to be specific "PLR2004", # allow some "magic" values "PYI034", # do not check return value of new method diff --git a/src/graphql/execution/build_field_plan.py b/src/graphql/execution/build_field_plan.py new file mode 100644 index 00000000..a8937a0d --- /dev/null +++ b/src/graphql/execution/build_field_plan.py @@ -0,0 +1,135 @@ +"""Build field plan""" + +from __future__ import annotations + +import sys +from typing import TYPE_CHECKING, Dict, NamedTuple + +from ..pyutils import RefMap, RefSet +from .collect_fields import DeferUsage, FieldDetails + +if TYPE_CHECKING: + from ..language import FieldNode + +try: + from typing import TypeAlias +except ImportError: # Python < 3.10 + from typing_extensions import TypeAlias + +__all__ = [ + "DeferUsageSet", + "FieldGroup", + "FieldPlan", + "GroupedFieldSet", + "NewGroupedFieldSetDetails", + "build_field_plan", +] + + +DeferUsageSet: TypeAlias = RefSet[DeferUsage] + + +class FieldGroup(NamedTuple): + """A group of fields with defer usages.""" + + fields: list[FieldDetails] + defer_usages: DeferUsageSet | None = None + + def to_nodes(self) -> list[FieldNode]: + """Return the field nodes in this group.""" + return [field_details.node for field_details in self.fields] + + +if sys.version_info < (3, 9): + GroupedFieldSet: TypeAlias = Dict[str, FieldGroup] +else: # Python >= 3.9 + GroupedFieldSet: TypeAlias = dict[str, FieldGroup] + + +class NewGroupedFieldSetDetails(NamedTuple): + """Details of a new grouped field set.""" + + grouped_field_set: GroupedFieldSet + should_initiate_defer: bool + + +class FieldPlan(NamedTuple): + """A plan for executing fields.""" + + grouped_field_set: GroupedFieldSet + new_grouped_field_set_details_map: RefMap[DeferUsageSet, NewGroupedFieldSetDetails] + + +def build_field_plan( + fields: dict[str, list[FieldDetails]], + parent_defer_usages: DeferUsageSet | None = None, +) -> FieldPlan: + """Build a plan for executing fields.""" + if parent_defer_usages is None: + parent_defer_usages = RefSet() + + grouped_field_set: GroupedFieldSet = {} + + new_grouped_field_set_details_map: RefMap[ + DeferUsageSet, NewGroupedFieldSetDetails + ] = RefMap() + + map_: dict[str, tuple[DeferUsageSet, list[FieldDetails]]] = {} + + for response_key, field_details_list in fields.items(): + defer_usage_set: RefSet[DeferUsage] = RefSet() + in_original_result = False + for field_details in field_details_list: + defer_usage = field_details.defer_usage + if defer_usage is None: + in_original_result = True + continue + defer_usage_set.add(defer_usage) + if in_original_result: + defer_usage_set.clear() + else: + defer_usage_set -= { + defer_usage + for defer_usage in defer_usage_set + if any( + ancestor in defer_usage_set for ancestor in defer_usage.ancestors + ) + } + map_[response_key] = (defer_usage_set, field_details_list) + + for response_key, [defer_usage_set, field_details_list] in map_.items(): + if defer_usage_set == parent_defer_usages: + field_group = grouped_field_set.get(response_key) + if field_group is None: # pragma: no cover else + field_group = FieldGroup([], defer_usage_set) + grouped_field_set[response_key] = field_group + field_group.fields.extend(field_details_list) + continue + + for ( + new_grouped_field_set_defer_usage_set, + new_grouped_field_set_details, + ) in new_grouped_field_set_details_map.items(): + if new_grouped_field_set_defer_usage_set == defer_usage_set: + new_grouped_field_set = new_grouped_field_set_details.grouped_field_set + break + else: + new_grouped_field_set = {} + new_grouped_field_set_details = NewGroupedFieldSetDetails( + new_grouped_field_set, + any( + defer_usage not in parent_defer_usages + for defer_usage in defer_usage_set + ), + ) + new_grouped_field_set_details_map[defer_usage_set] = ( + new_grouped_field_set_details + ) + + field_group = new_grouped_field_set.get(response_key) + if field_group is None: # pragma: no cover else + field_group = FieldGroup([], defer_usage_set) + new_grouped_field_set[response_key] = field_group + field_group.fields.extend(field_details_list) + + return FieldPlan(grouped_field_set, new_grouped_field_set_details_map) diff --git a/src/graphql/execution/collect_fields.py b/src/graphql/execution/collect_fields.py index c3fc99cc..0c2ae348 100644 --- a/src/graphql/execution/collect_fields.py +++ b/src/graphql/execution/collect_fields.py @@ -2,8 +2,8 @@ from __future__ import annotations -import sys -from typing import Any, Dict, NamedTuple, Union, cast +from collections import defaultdict +from typing import Any, NamedTuple from ..language import ( FieldNode, @@ -14,7 +14,6 @@ OperationType, SelectionSetNode, ) -from ..pyutils import RefMap, RefSet from ..type import ( GraphQLDeferDirective, GraphQLIncludeDirective, @@ -26,81 +25,38 @@ from ..utilities.type_from_ast import type_from_ast from .values import get_directive_values -try: - from typing import TypeAlias -except ImportError: # Python < 3.10 - from typing_extensions import TypeAlias - - __all__ = [ - "NON_DEFERRED_TARGET_SET", "CollectFieldsContext", - "CollectFieldsResult", + "CollectedFields", "DeferUsage", - "DeferUsageSet", "FieldDetails", - "FieldGroup", - "GroupedFieldSetDetails", - "Target", - "TargetSet", "collect_fields", "collect_subfields", ] class DeferUsage(NamedTuple): - """An optionally labelled list of ancestor targets.""" + """An optionally labelled linked list of defer usages.""" label: str | None - ancestors: list[Target] - - -Target: TypeAlias = Union[DeferUsage, None] - -TargetSet: TypeAlias = RefSet[Target] -DeferUsageSet: TypeAlias = RefSet[DeferUsage] + parent_defer_usage: DeferUsage | None - -NON_DEFERRED_TARGET_SET: TargetSet = RefSet([None]) + @property + def ancestors(self) -> list[DeferUsage]: + """Get the ancestors of this defer usage.""" + ancestors: list[DeferUsage] = [] + parent_defer_usage = self.parent_defer_usage + while parent_defer_usage is not None: + ancestors.append(parent_defer_usage) + parent_defer_usage = parent_defer_usage.parent_defer_usage + return ancestors[::-1] class FieldDetails(NamedTuple): - """A field node and its target.""" + """A field node and its defer usage.""" node: FieldNode - target: Target - - -class FieldGroup(NamedTuple): - """A group of fields that share the same target set.""" - - fields: list[FieldDetails] - targets: TargetSet - - def to_nodes(self) -> list[FieldNode]: - """Return the field nodes in this group.""" - return [field_details.node for field_details in self.fields] - - -if sys.version_info < (3, 9): - GroupedFieldSet: TypeAlias = Dict[str, FieldGroup] -else: # Python >= 3.9 - GroupedFieldSet: TypeAlias = dict[str, FieldGroup] - - -class GroupedFieldSetDetails(NamedTuple): - """A grouped field set with defer info.""" - - grouped_field_set: GroupedFieldSet - should_initiate_defer: bool - - -class CollectFieldsResult(NamedTuple): - """Collected fields and deferred usages.""" - - grouped_field_set: GroupedFieldSet - new_grouped_field_set_details: RefMap[DeferUsageSet, GroupedFieldSetDetails] - new_defer_usages: list[DeferUsage] + defer_usage: DeferUsage | None class CollectFieldsContext(NamedTuple): @@ -111,19 +67,23 @@ class CollectFieldsContext(NamedTuple): variable_values: dict[str, Any] operation: OperationDefinitionNode runtime_type: GraphQLObjectType - targets_by_key: dict[str, TargetSet] - fields_by_target: RefMap[Target, dict[str, list[FieldNode]]] - new_defer_usages: list[DeferUsage] visited_fragment_names: set[str] +class CollectedFields(NamedTuple): + """Collected fields with new defer usages.""" + + fields: dict[str, list[FieldDetails]] + new_defer_usages: list[DeferUsage] + + def collect_fields( schema: GraphQLSchema, fragments: dict[str, FragmentDefinitionNode], variable_values: dict[str, Any], runtime_type: GraphQLObjectType, operation: OperationDefinitionNode, -) -> CollectFieldsResult: +) -> CollectedFields: """Collect fields. Given a selection_set, collects all the fields and returns them. @@ -134,23 +94,21 @@ def collect_fields( For internal use only. """ + grouped_field_set: dict[str, list[FieldDetails]] = defaultdict(list) + new_defer_usages: list[DeferUsage] = [] context = CollectFieldsContext( schema, fragments, variable_values, operation, runtime_type, - {}, - RefMap(), - [], set(), ) - collect_fields_impl(context, operation.selection_set) - return CollectFieldsResult( - *build_grouped_field_sets(context.targets_by_key, context.fields_by_target), - context.new_defer_usages, + collect_fields_impl( + context, operation.selection_set, grouped_field_set, new_defer_usages ) + return CollectedFields(grouped_field_set, new_defer_usages) def collect_subfields( @@ -159,8 +117,8 @@ def collect_subfields( variable_values: dict[str, Any], operation: OperationDefinitionNode, return_type: GraphQLObjectType, - field_group: FieldGroup, -) -> CollectFieldsResult: + field_details: list[FieldDetails], +) -> CollectedFields: """Collect subfields. Given a list of field nodes, collects all the subfields of the passed in fields, @@ -178,30 +136,31 @@ def collect_subfields( variable_values, operation, return_type, - {}, - RefMap(), - [], set(), ) + sub_grouped_field_set: dict[str, list[FieldDetails]] = defaultdict(list) + new_defer_usages: list[DeferUsage] = [] - for field_details in field_group.fields: - node = field_details.node + for field_detail in field_details: + node = field_detail.node if node.selection_set: - collect_fields_impl(context, node.selection_set, field_details.target) + collect_fields_impl( + context, + node.selection_set, + sub_grouped_field_set, + new_defer_usages, + field_detail.defer_usage, + ) - return CollectFieldsResult( - *build_grouped_field_sets( - context.targets_by_key, context.fields_by_target, field_group.targets - ), - context.new_defer_usages, - ) + return CollectedFields(sub_grouped_field_set, new_defer_usages) def collect_fields_impl( context: CollectFieldsContext, selection_set: SelectionSetNode, - parent_target: Target | None = None, - new_target: Target | None = None, + grouped_field_set: dict[str, list[FieldDetails]], + new_defer_usages: list[DeferUsage], + defer_usage: DeferUsage | None = None, ) -> None: """Collect fields (internal implementation).""" ( @@ -210,97 +169,87 @@ def collect_fields_impl( variable_values, operation, runtime_type, - targets_by_key, - fields_by_target, - new_defer_usages, visited_fragment_names, ) = context - ancestors: list[Target] - for selection in selection_set.selections: if isinstance(selection, FieldNode): if not should_include_node(variable_values, selection): continue key = get_field_entry_key(selection) - target = new_target or parent_target - key_targets = targets_by_key.get(key) - if key_targets is None: - key_targets = RefSet([target]) - targets_by_key[key] = key_targets - else: - key_targets.add(target) - target_fields = fields_by_target.get(target) - if target_fields is None: - fields_by_target[target] = {key: [selection]} - else: - field_nodes = target_fields.get(key) - if field_nodes is None: - target_fields[key] = [selection] - else: - field_nodes.append(selection) + grouped_field_set[key].append(FieldDetails(selection, defer_usage)) elif isinstance(selection, InlineFragmentNode): if not should_include_node( variable_values, selection ) or not does_fragment_condition_match(schema, selection, runtime_type): continue - defer = get_defer_values(operation, variable_values, selection) + new_defer_usage = get_defer_usage( + operation, variable_values, selection, defer_usage + ) - if defer: - ancestors = ( - [None] - if parent_target is None - else [parent_target, *parent_target.ancestors] + if new_defer_usage is None: + collect_fields_impl( + context, + selection.selection_set, + grouped_field_set, + new_defer_usages, + defer_usage, ) - target = DeferUsage(defer.label, ancestors) - new_defer_usages.append(target) else: - target = new_target - - collect_fields_impl(context, selection.selection_set, parent_target, target) + new_defer_usages.append(new_defer_usage) + collect_fields_impl( + context, + selection.selection_set, + grouped_field_set, + new_defer_usages, + new_defer_usage, + ) elif isinstance(selection, FragmentSpreadNode): # pragma: no cover else frag_name = selection.name.value - if not should_include_node(variable_values, selection): - continue + new_defer_usage = get_defer_usage( + operation, variable_values, selection, defer_usage + ) - defer = get_defer_values(operation, variable_values, selection) - if frag_name in visited_fragment_names and not defer: + if new_defer_usage is None and ( + frag_name in visited_fragment_names + or not should_include_node(variable_values, selection) + ): continue fragment = fragments.get(frag_name) - if not fragment or not does_fragment_condition_match( + if fragment is None or not does_fragment_condition_match( schema, fragment, runtime_type ): continue - if defer: - ancestors = ( - [None] - if parent_target is None - else [parent_target, *parent_target.ancestors] + if new_defer_usage is None: + visited_fragment_names.add(frag_name) + collect_fields_impl( + context, + fragment.selection_set, + grouped_field_set, + new_defer_usages, + defer_usage, ) - target = DeferUsage(defer.label, ancestors) - new_defer_usages.append(target) else: - visited_fragment_names.add(frag_name) - target = new_target - - collect_fields_impl(context, fragment.selection_set, parent_target, target) - - -class DeferValues(NamedTuple): - """Values of an active defer directive.""" - - label: str | None + new_defer_usages.append(new_defer_usage) + collect_fields_impl( + context, + fragment.selection_set, + grouped_field_set, + new_defer_usages, + new_defer_usage, + ) -def get_defer_values( +def get_defer_usage( operation: OperationDefinitionNode, variable_values: dict[str, Any], node: FragmentSpreadNode | InlineFragmentNode, -) -> DeferValues | None: + parent_defer_usage: DeferUsage | None, +) -> DeferUsage | None: """Get values of defer directive if active. Returns an object containing the `@defer` arguments if a field should be @@ -319,7 +268,7 @@ def get_defer_values( ) raise TypeError(msg) - return DeferValues(defer.get("label")) + return DeferUsage(defer.get("label"), parent_defer_usage) def should_include_node( @@ -360,111 +309,3 @@ def does_fragment_condition_match( def get_field_entry_key(node: FieldNode) -> str: """Implement the logic to compute the key of a given field's entry""" return node.alias.value if node.alias else node.name.value - - -def build_grouped_field_sets( - targets_by_key: dict[str, TargetSet], - fields_by_target: RefMap[Target, dict[str, list[FieldNode]]], - parent_targets: TargetSet = NON_DEFERRED_TARGET_SET, -) -> tuple[GroupedFieldSet, RefMap[DeferUsageSet, GroupedFieldSetDetails]]: - """Build grouped field sets.""" - parent_target_keys, target_set_details_map = get_target_set_details( - targets_by_key, parent_targets - ) - - grouped_field_set = ( - get_ordered_grouped_field_set( - parent_target_keys, parent_targets, targets_by_key, fields_by_target - ) - if parent_target_keys - else {} - ) - - new_grouped_field_set_details: RefMap[DeferUsageSet, GroupedFieldSetDetails] = ( - RefMap() - ) - - for masking_targets, target_set_details in target_set_details_map.items(): - keys, should_initiate_defer = target_set_details - - new_grouped_field_set = get_ordered_grouped_field_set( - keys, masking_targets, targets_by_key, fields_by_target - ) - - # All TargetSets that causes new grouped field sets consist only of DeferUsages - # and have should_initiate_defer defined - - new_grouped_field_set_details[cast("DeferUsageSet", masking_targets)] = ( - GroupedFieldSetDetails(new_grouped_field_set, should_initiate_defer) - ) - - return grouped_field_set, new_grouped_field_set_details - - -class TargetSetDetails(NamedTuple): - """A set of target keys with defer info.""" - - keys: set[str] - should_initiate_defer: bool - - -def get_target_set_details( - targets_by_key: dict[str, TargetSet], parent_targets: TargetSet -) -> tuple[set[str], RefMap[TargetSet, TargetSetDetails]]: - """Get target set details.""" - parent_target_keys: set[str] = set() - target_set_details_map: RefMap[TargetSet, TargetSetDetails] = RefMap() - - for response_key, targets in targets_by_key.items(): - masking_target_list: list[Target] = [] - for target in targets: - if not target or all( - ancestor not in targets for ancestor in target.ancestors - ): - masking_target_list.append(target) - - masking_targets: TargetSet = RefSet(masking_target_list) - if masking_targets == parent_targets: - parent_target_keys.add(response_key) - continue - - for target_set, target_set_details in target_set_details_map.items(): - if target_set == masking_targets: - target_set_details.keys.add(response_key) - break - else: - target_set_details = TargetSetDetails( - {response_key}, - any( - defer_usage not in parent_targets for defer_usage in masking_targets - ), - ) - target_set_details_map[masking_targets] = target_set_details - - return parent_target_keys, target_set_details_map - - -def get_ordered_grouped_field_set( - keys: set[str], - masking_targets: TargetSet, - targets_by_key: dict[str, TargetSet], - fields_by_target: RefMap[Target, dict[str, list[FieldNode]]], -) -> GroupedFieldSet: - """Get ordered grouped field set.""" - grouped_field_set: GroupedFieldSet = {} - - first_target = next(iter(masking_targets)) - first_fields = fields_by_target[first_target] - for key in list(first_fields): - if key in keys: - field_group = grouped_field_set.get(key) - if field_group is None: # pragma: no cover else - field_group = FieldGroup([], masking_targets) - grouped_field_set[key] = field_group - for target in targets_by_key[key]: - fields_for_target = fields_by_target[target] - nodes = fields_for_target[key] - del fields_for_target[key] - field_group.fields.extend(FieldDetails(node, target) for node in nodes) - - return grouped_field_set diff --git a/src/graphql/execution/execute.py b/src/graphql/execution/execute.py index fd4dd5ea..c8ae17b8 100644 --- a/src/graphql/execution/execute.py +++ b/src/graphql/execution/execute.py @@ -40,6 +40,7 @@ from ..error import GraphQLError, located_error from ..language import ( DocumentNode, + FieldNode, FragmentDefinitionNode, OperationDefinitionNode, OperationType, @@ -75,18 +76,14 @@ is_object_type, ) from .async_iterables import map_async_iterable -from .collect_fields import ( - NON_DEFERRED_TARGET_SET, - CollectFieldsResult, - DeferUsage, +from .build_field_plan import ( DeferUsageSet, - FieldDetails, FieldGroup, GroupedFieldSet, - GroupedFieldSetDetails, - collect_fields, - collect_subfields, + NewGroupedFieldSetDetails, + build_field_plan, ) +from .collect_fields import DeferUsage, FieldDetails, collect_fields, collect_subfields from .incremental_publisher import ( ASYNC_DELAY, DeferredFragmentRecord, @@ -158,6 +155,14 @@ class StreamUsage(NamedTuple): field_group: FieldGroup +class SubFieldPlan(NamedTuple): + """A plan for executing fields with defer usages.""" + + grouped_field_set: GroupedFieldSet + new_grouped_field_set_details_map: RefMap[DeferUsageSet, NewGroupedFieldSetDetails] + new_defer_usages: list[DeferUsage] + + class ExecutionContext: """Data that must be available at all points during query execution. @@ -208,7 +213,7 @@ def __init__( if is_awaitable: self.is_awaitable = is_awaitable self._canceled_iterators: set[AsyncIterator] = set() - self._subfields_cache: dict[tuple, CollectFieldsResult] = {} + self._sub_field_plan_cache: dict[tuple, SubFieldPlan] = {} self._tasks: set[Awaitable] = set() self._stream_usages: RefMap[FieldGroup, StreamUsage] = RefMap() @@ -326,11 +331,10 @@ def execute_operation( ) raise GraphQLError(msg, operation) - grouped_field_set, new_grouped_field_set_details, new_defer_usages = ( - collect_fields( - schema, self.fragments, self.variable_values, root_type, operation - ) + fields, new_defer_usages = collect_fields( + schema, self.fragments, self.variable_values, root_type, operation ) + grouped_field_set, new_grouped_field_set_details_map = build_field_plan(fields) incremental_publisher = self.incremental_publisher new_defer_map = add_new_deferred_fragments( @@ -341,7 +345,7 @@ def execute_operation( new_deferred_grouped_field_set_records = add_new_deferred_grouped_field_sets( incremental_publisher, - new_grouped_field_set_details, + new_grouped_field_set_details_map, new_defer_map, path, ) @@ -502,7 +506,9 @@ def execute_field( if self.middleware_manager: resolve_fn = self.middleware_manager.get_field_resolver(resolve_fn) - info = self.build_resolve_info(field_def, field_group, parent_type, path) + info = self.build_resolve_info( + field_def, field_group.to_nodes(), parent_type, path + ) # Get the resolve function, regardless of if its result is normal or abrupt # (error). @@ -575,7 +581,7 @@ async def await_completed() -> Any: def build_resolve_info( self, field_def: GraphQLField, - field_group: FieldGroup, + field_nodes: list[FieldNode], parent_type: GraphQLObjectType, path: Path, ) -> GraphQLResolveInfo: @@ -586,8 +592,8 @@ def build_resolve_info( # The resolve function's first argument is a collection of information about # the current execution state. return GraphQLResolveInfo( - field_group.fields[0].node.name.value, - field_group.to_nodes(), + field_nodes[0].name.value, + field_nodes, field_def.type, parent_type, path, @@ -807,8 +813,7 @@ def get_stream_usage( [ FieldDetails(field_details.node, None) for field_details in field_group.fields - ], - NON_DEFERRED_TARGET_SET, + ] ) stream_usage = StreamUsage( @@ -1273,8 +1278,8 @@ def collect_and_execute_subfields( defer_map: RefMap[DeferUsage, DeferredFragmentRecord], ) -> AwaitableOrValue[dict[str, Any]]: """Collect sub-fields to execute to complete this value.""" - grouped_field_set, new_grouped_field_set_details, new_defer_usages = ( - self.collect_subfields(return_type, field_group) + grouped_field_set, new_grouped_field_set_details_map, new_defer_usages = ( + self.build_sub_field_plan(return_type, field_group) ) incremental_publisher = self.incremental_publisher @@ -1286,7 +1291,10 @@ def collect_and_execute_subfields( path, ) new_deferred_grouped_field_set_records = add_new_deferred_grouped_field_sets( - incremental_publisher, new_grouped_field_set_details, new_defer_map, path + incremental_publisher, + new_grouped_field_set_details_map, + new_defer_map, + path, ) sub_fields = self.execute_fields( @@ -1308,17 +1316,16 @@ def collect_and_execute_subfields( return sub_fields - def collect_subfields( + def build_sub_field_plan( self, return_type: GraphQLObjectType, field_group: FieldGroup - ) -> CollectFieldsResult: + ) -> SubFieldPlan: """Collect subfields. - A cached collection of relevant subfields with regard to the return type is - kept in the execution context as ``_subfields_cache``. This ensures the - subfields are not repeatedly calculated, which saves overhead when resolving - lists of values. + A memoized function for building subfield plans with regard to the return type. + Memoizing ensures the subfields are not repeatedly calculated, which saves + overhead when resolving lists of values. """ - cache = self._subfields_cache + cache = self._sub_field_plan_cache # We cannot use the field_group itself as key for the cache, since it # is not hashable as a list. We also do not want to use the field_group # itself (converted to a tuple) as keys, since hashing them is slow. @@ -1331,18 +1338,20 @@ def collect_subfields( if len(field_group) == 1 # optimize most frequent case else (return_type, *map(id, field_group)) ) - sub_fields_and_patches = cache.get(key) - if sub_fields_and_patches is None: - sub_fields_and_patches = collect_subfields( + sub_field_plan = cache.get(key) + if sub_field_plan is None: + sub_fields, new_defer_usages = collect_subfields( self.schema, self.fragments, self.variable_values, self.operation, return_type, - field_group, + field_group.fields, ) - cache[key] = sub_fields_and_patches - return sub_fields_and_patches + field_plan = build_field_plan(sub_fields, field_group.defer_usages) + sub_field_plan = SubFieldPlan(*field_plan, new_defer_usages) + cache[key] = sub_field_plan + return sub_field_plan def map_source_to_response( self, result_or_stream: ExecutionResult | AsyncIterable[Any] @@ -1954,14 +1963,10 @@ def add_new_deferred_fragments( new_defer_map = RefMap() if defer_map is None else RefMap(defer_map.items()) # For each new DeferUsage object: - for defer_usage in new_defer_usages: - ancestors = defer_usage.ancestors - parent_defer_usage = ancestors[0] if ancestors else None - - # If the parent target is defined, the parent target is a DeferUsage object - # and the parent result record is the DeferredFragmentRecord corresponding - # to that DeferUsage. - # If the parent target is not defined, the parent result record is either: + for new_defer_usage in new_defer_usages: + parent_defer_usage = new_defer_usage.parent_defer_usage + + # If the parent defer usage is not defined, the parent result record is either: # - the InitialResultRecord, or # - a StreamItemsRecord, as `@defer` may be nested under `@stream`. parent = ( @@ -1975,7 +1980,7 @@ def add_new_deferred_fragments( ) # Instantiate the new record. - deferred_fragment_record = DeferredFragmentRecord(path, defer_usage.label) + deferred_fragment_record = DeferredFragmentRecord(path, new_defer_usage.label) # Report the new record to the Incremental Publisher. incremental_publisher.report_new_defer_fragment_record( @@ -1983,7 +1988,7 @@ def add_new_deferred_fragments( ) # Update the map. - new_defer_map[defer_usage] = deferred_fragment_record + new_defer_map[new_defer_usage] = deferred_fragment_record return new_defer_map @@ -1997,7 +2002,9 @@ def deferred_fragment_record_from_defer_usage( def add_new_deferred_grouped_field_sets( incremental_publisher: IncrementalPublisher, - new_grouped_field_set_details: Mapping[DeferUsageSet, GroupedFieldSetDetails], + new_grouped_field_set_details_map: Mapping[ + DeferUsageSet, NewGroupedFieldSetDetails + ], defer_map: RefMap[DeferUsage, DeferredFragmentRecord], path: Path | None = None, ) -> list[DeferredGroupedFieldSetRecord]: @@ -2005,16 +2012,16 @@ def add_new_deferred_grouped_field_sets( new_deferred_grouped_field_set_records: list[DeferredGroupedFieldSetRecord] = [] for ( - new_grouped_field_set_defer_usages, - grouped_field_set_details, - ) in new_grouped_field_set_details.items(): + defer_usage_set, + [grouped_field_set, should_initiate_defer], + ) in new_grouped_field_set_details_map.items(): deferred_fragment_records = get_deferred_fragment_records( - new_grouped_field_set_defer_usages, defer_map + defer_usage_set, defer_map ) deferred_grouped_field_set_record = DeferredGroupedFieldSetRecord( deferred_fragment_records, - grouped_field_set_details.grouped_field_set, - grouped_field_set_details.should_initiate_defer, + grouped_field_set, + should_initiate_defer, path, ) incremental_publisher.report_new_deferred_grouped_filed_set_record( @@ -2292,24 +2299,26 @@ def execute_subscription( msg = "Schema is not configured to execute subscription operation." raise GraphQLError(msg, context.operation) - grouped_field_set = collect_fields( + fields = collect_fields( schema, context.fragments, context.variable_values, root_type, context.operation, - ).grouped_field_set - first_root_field = next(iter(grouped_field_set.items())) - response_name, field_group = first_root_field - field_name = field_group.fields[0].node.name.value + ).fields + + first_root_field = next(iter(fields.items())) + response_name, field_details_list = first_root_field + field_name = field_details_list[0].node.name.value field_def = schema.get_field(root_type, field_name) + field_nodes = [field_details.node for field_details in field_details_list] if not field_def: msg = f"The subscription field '{field_name}' is not defined." - raise GraphQLError(msg, field_group.to_nodes()) + raise GraphQLError(msg, field_nodes) path = Path(None, response_name, root_type.name) - info = context.build_resolve_info(field_def, field_group, root_type, path) + info = context.build_resolve_info(field_def, field_nodes, root_type, path) # Implements the "ResolveFieldEventStream" algorithm from GraphQL specification. # It differs from "ResolveFieldValue" due to providing a different `resolveFn`. @@ -2317,9 +2326,7 @@ def execute_subscription( try: # Build a dictionary of arguments from the field.arguments AST, using the # variables scope to fulfill any variable references. - args = get_argument_values( - field_def, field_group.fields[0].node, context.variable_values - ) + args = get_argument_values(field_def, field_nodes[0], context.variable_values) # Call the `subscribe()` resolver or the default resolver to produce an # AsyncIterable yielding raw payloads. @@ -2332,16 +2339,14 @@ async def await_result() -> AsyncIterable[Any]: try: return assert_event_stream(await result) except Exception as error: - raise located_error( - error, field_group.to_nodes(), path.as_list() - ) from error + raise located_error(error, field_nodes, path.as_list()) from error return await_result() return assert_event_stream(result) except Exception as error: - raise located_error(error, field_group.to_nodes(), path.as_list()) from error + raise located_error(error, field_nodes, path.as_list()) from error def assert_event_stream(result: Any) -> AsyncIterable: diff --git a/src/graphql/execution/incremental_publisher.py b/src/graphql/execution/incremental_publisher.py index 51475f79..6de707bc 100644 --- a/src/graphql/execution/incremental_publisher.py +++ b/src/graphql/execution/incremental_publisher.py @@ -26,7 +26,7 @@ if TYPE_CHECKING: from ..error import GraphQLError, GraphQLFormattedError from ..pyutils import Path - from .collect_fields import GroupedFieldSet + from .build_field_plan import GroupedFieldSet __all__ = [ "ASYNC_DELAY", @@ -64,7 +64,7 @@ class FormattedPendingResult(TypedDict, total=False): label: str -class PendingResult: +class PendingResult: # noqa: PLW1641 """Pending execution result""" id: str @@ -127,7 +127,7 @@ class FormattedCompletedResult(TypedDict, total=False): errors: list[GraphQLFormattedError] -class CompletedResult: +class CompletedResult: # noqa: PLW1641 """Completed execution result""" id: str @@ -192,7 +192,7 @@ class FormattedExecutionResult(TypedDict, total=False): extensions: dict[str, Any] -class ExecutionResult: +class ExecutionResult: # noqa: PLW1641 """The result of GraphQL execution. - ``data`` is the result of a successful execution of the query. @@ -267,7 +267,7 @@ class FormattedInitialIncrementalExecutionResult(TypedDict, total=False): extensions: dict[str, Any] -class InitialIncrementalExecutionResult: +class InitialIncrementalExecutionResult: # noqa: PLW1641 """Initial incremental execution result.""" data: dict[str, Any] | None @@ -369,7 +369,7 @@ class FormattedIncrementalDeferResult(TypedDict, total=False): extensions: dict[str, Any] -class IncrementalDeferResult: +class IncrementalDeferResult: # noqa: PLW1641 """Incremental deferred execution result""" data: dict[str, Any] @@ -461,7 +461,7 @@ class FormattedIncrementalStreamResult(TypedDict, total=False): extensions: dict[str, Any] -class IncrementalStreamResult: +class IncrementalStreamResult: # noqa: PLW1641 """Incremental streamed execution result""" items: list[Any] @@ -560,7 +560,7 @@ class FormattedSubsequentIncrementalExecutionResult(TypedDict, total=False): extensions: dict[str, Any] -class SubsequentIncrementalExecutionResult: +class SubsequentIncrementalExecutionResult: # noqa: PLW1641 """Subsequent incremental execution result.""" __slots__ = "completed", "extensions", "has_next", "incremental", "pending" @@ -802,10 +802,7 @@ def build_data_response( self, initial_result_record: InitialResultRecord, data: dict[str, Any] | None ) -> ExecutionResult | ExperimentalIncrementalExecutionResults: """Build response for the given data.""" - for child in initial_result_record.children: - if child.filtered: - continue - self._publish(child) + pending_sources = self._publish(initial_result_record.children) errors = initial_result_record.errors or None if errors: @@ -816,14 +813,7 @@ def build_data_response( error.message, ) ) - pending = self._pending - if pending: - pending_sources: RefSet[DeferredFragmentRecord | StreamRecord] = RefSet( - subsequent_result_record.stream_record - if isinstance(subsequent_result_record, StreamItemsRecord) - else subsequent_result_record - for subsequent_result_record in pending - ) + if pending_sources: return ExperimentalIncrementalExecutionResults( initial_result=InitialIncrementalExecutionResult( data, @@ -994,17 +984,7 @@ def _process_pending( completed_results: list[CompletedResult] = [] to_result = self._completed_record_to_result for subsequent_result_record in completed_records: - for child in subsequent_result_record.children: - if child.filtered: - continue - pending_source: DeferredFragmentRecord | StreamRecord = ( - child.stream_record - if isinstance(child, StreamItemsRecord) - else child - ) - if not pending_source.pending_sent: - new_pending_sources.add(pending_source) - self._publish(child) + self._publish(subsequent_result_record.children, new_pending_sources) incremental_result: IncrementalResult if isinstance(subsequent_result_record, StreamItemsRecord): if subsequent_result_record.is_final_record: @@ -1060,7 +1040,7 @@ def _get_incremental_defer_result( max_length: int | None = None id_with_longest_path: str | None = None for fragment_record in fragment_records: - if fragment_record.id is None: + if fragment_record.id is None: # pragma: no cover continue length = len(fragment_record.path) if max_length is None or length > max_length: @@ -1090,20 +1070,45 @@ def _completed_record_to_result( completed_record.errors or None, ) - def _publish(self, subsequent_result_record: SubsequentResultRecord) -> None: - """Publish the given incremental data record.""" - if isinstance(subsequent_result_record, StreamItemsRecord): - if subsequent_result_record.is_completed: - self._push(subsequent_result_record) - else: + def _publish( + self, + subsequent_result_records: dict[SubsequentResultRecord, None], + pending_sources: RefSet[DeferredFragmentRecord | StreamRecord] | None = None, + ) -> RefSet[DeferredFragmentRecord | StreamRecord]: + """Publish the given set of incremental data record.""" + if pending_sources is None: + pending_sources = RefSet() + empty_records: list[SubsequentResultRecord] = [] + + for subsequent_result_record in subsequent_result_records: + if subsequent_result_record.filtered: + continue + if isinstance(subsequent_result_record, StreamItemsRecord): + if subsequent_result_record.is_completed: + self._push(subsequent_result_record) + else: + self._introduce(subsequent_result_record) + + stream = subsequent_result_record.stream_record + if not stream.pending_sent: + pending_sources.add(stream) + continue + + if subsequent_result_record._pending: # noqa: SLF001 self._introduce(subsequent_result_record) - elif subsequent_result_record._pending: # noqa: SLF001 - self._introduce(subsequent_result_record) - elif ( - subsequent_result_record.deferred_grouped_field_set_records - or subsequent_result_record.children - ): - self._push(subsequent_result_record) + elif not subsequent_result_record.deferred_grouped_field_set_records: + empty_records.append(subsequent_result_record) + continue + else: + self._push(subsequent_result_record) + + if not subsequent_result_record.pending_sent: # pragma: no cover else + pending_sources.add(subsequent_result_record) + + for empty_record in empty_records: + self._publish(empty_record.children, pending_sources) + + return pending_sources @staticmethod def _get_children( diff --git a/src/graphql/language/location.py b/src/graphql/language/location.py index 7af55082..f4369974 100644 --- a/src/graphql/language/location.py +++ b/src/graphql/language/location.py @@ -41,6 +41,9 @@ def __eq__(self, other: object) -> bool: def __ne__(self, other: object) -> bool: return not self == other + def __hash__(self) -> int: + return hash((self.line, self.column)) + def get_location(source: Source, position: int) -> SourceLocation: """Get the line and column for a character position in the source. diff --git a/src/graphql/language/source.py b/src/graphql/language/source.py index d54bf969..17d5e15d 100644 --- a/src/graphql/language/source.py +++ b/src/graphql/language/source.py @@ -72,6 +72,9 @@ def __eq__(self, other: object) -> bool: def __ne__(self, other: object) -> bool: return not self == other + def __hash__(self) -> int: + return hash(self.body) + def is_source(source: Any) -> TypeGuard[Source]: """Test if the given value is a Source object. diff --git a/src/graphql/pyutils/inspect.py b/src/graphql/pyutils/inspect.py index ed4920be..37b95f9b 100644 --- a/src/graphql/pyutils/inspect.py +++ b/src/graphql/pyutils/inspect.py @@ -171,7 +171,7 @@ def trunc_list(s: list) -> list: if len(s) > max_list_size: i = max_list_size // 2 j = i - 1 - s = s[:i] + [ELLIPSIS] + s[-j:] + s = [*s[:i], ELLIPSIS, *s[-j:]] return s diff --git a/src/graphql/type/definition.py b/src/graphql/type/definition.py index c334488d..528e3df3 100644 --- a/src/graphql/type/definition.py +++ b/src/graphql/type/definition.py @@ -479,7 +479,7 @@ class GraphQLFieldKwargs(TypedDict, total=False): ast_node: FieldDefinitionNode | None -class GraphQLField: +class GraphQLField: # noqa: PLW1641 """Definition of a GraphQL field""" type: GraphQLOutputType @@ -644,7 +644,7 @@ class GraphQLArgumentKwargs(TypedDict, total=False): ast_node: InputValueDefinitionNode | None -class GraphQLArgument: +class GraphQLArgument: # noqa: PLW1641 """Definition of a GraphQL argument""" type: GraphQLInputType @@ -1219,7 +1219,7 @@ class GraphQLEnumValueKwargs(TypedDict, total=False): ast_node: EnumValueDefinitionNode | None -class GraphQLEnumValue: +class GraphQLEnumValue: # noqa: PLW1641 """A GraphQL enum value.""" value: Any @@ -1394,7 +1394,7 @@ class GraphQLInputFieldKwargs(TypedDict, total=False): ast_node: InputValueDefinitionNode | None -class GraphQLInputField: +class GraphQLInputField: # noqa: PLW1641 """Definition of a GraphQL input field""" type: GraphQLInputType diff --git a/src/graphql/type/directives.py b/src/graphql/type/directives.py index ecd201c2..bd3e360f 100644 --- a/src/graphql/type/directives.py +++ b/src/graphql/type/directives.py @@ -49,7 +49,7 @@ class GraphQLDirectiveKwargs(TypedDict, total=False): ast_node: ast.DirectiveDefinitionNode | None -class GraphQLDirective: +class GraphQLDirective: # noqa: PLW1641 """GraphQL Directive Directives are used by the GraphQL runtime as a way of modifying execution behavior. diff --git a/src/graphql/utilities/ast_to_dict.py b/src/graphql/utilities/ast_to_dict.py index 3a2b3504..10f13c15 100644 --- a/src/graphql/utilities/ast_to_dict.py +++ b/src/graphql/utilities/ast_to_dict.py @@ -48,7 +48,7 @@ def ast_to_dict( res.update( { key: ast_to_dict(getattr(node, key), locations, cache) - for key in ("kind",) + node.keys[1:] + for key in ("kind", *node.keys[1:]) } ) if locations: diff --git a/src/graphql/validation/rules/single_field_subscriptions.py b/src/graphql/validation/rules/single_field_subscriptions.py index 89235856..5ebbc1b3 100644 --- a/src/graphql/validation/rules/single_field_subscriptions.py +++ b/src/graphql/validation/rules/single_field_subscriptions.py @@ -5,7 +5,7 @@ from typing import Any from ...error import GraphQLError -from ...execution.collect_fields import FieldGroup, collect_fields +from ...execution.collect_fields import FieldDetails, collect_fields from ...language import ( FieldNode, FragmentDefinitionNode, @@ -17,8 +17,8 @@ __all__ = ["SingleFieldSubscriptionsRule"] -def to_nodes(field_group: FieldGroup) -> list[FieldNode]: - return [field_details.node for field_details in field_group.fields] +def to_nodes(field_details_list: list[FieldDetails]) -> list[FieldNode]: + return [field_details.node for field_details in field_details_list] class SingleFieldSubscriptionsRule(ValidationRule): @@ -46,15 +46,15 @@ def enter_operation_definition( for definition in document.definitions if isinstance(definition, FragmentDefinitionNode) } - grouped_field_set = collect_fields( + fields = collect_fields( schema, fragments, variable_values, subscription_type, node, - ).grouped_field_set - if len(grouped_field_set) > 1: - field_groups = list(grouped_field_set.values()) + ).fields + if len(fields) > 1: + field_groups = list(fields.values()) extra_field_groups = field_groups[1:] extra_field_selection = [ node @@ -72,7 +72,7 @@ def enter_operation_definition( extra_field_selection, ) ) - for field_group in grouped_field_set.values(): + for field_group in fields.values(): field_name = to_nodes(field_group)[0].name.value if field_name.startswith("__"): self.report_error( diff --git a/src/graphql/version.py b/src/graphql/version.py index d27e9e32..fa63f8af 100644 --- a/src/graphql/version.py +++ b/src/graphql/version.py @@ -8,7 +8,7 @@ __all__ = ["version", "version_info", "version_info_js", "version_js"] -version = "3.3.0a8" +version = "3.3.0a9" version_js = "17.0.0a3" diff --git a/tests/execution/test_defer.py b/tests/execution/test_defer.py index 78315cc8..1fcfa25c 100644 --- a/tests/execution/test_defer.py +++ b/tests/execution/test_defer.py @@ -214,7 +214,7 @@ def can_compare_pending_result(): assert result == tuple(args.values()) assert result == tuple(args.values())[:2] assert result != tuple(args.values())[:1] - assert result != tuple(args.values())[:1] + (["bar", 2],) + assert result != (*tuple(args.values())[:1], ["bar", 2]) assert result == args assert result != {**args, "id": "bar"} assert result != {**args, "path": ["bar", 2]} @@ -239,7 +239,7 @@ def can_compare_completed_result(): ) assert result == tuple(args.values()) assert result != tuple(args.values())[:1] - assert result != tuple(args.values())[:1] + ([GraphQLError("oops")],) + assert result != (*tuple(args.values())[:1], [GraphQLError("oops")]) assert result == args assert result != {**args, "id": "bar"} assert result != {**args, "errors": [{"message": "oops"}]} @@ -744,15 +744,17 @@ async def can_defer_a_fragment_within_an_already_deferred_fragment(): assert result == [ { "data": {"hero": {}}, - "pending": [ - {"id": "0", "path": ["hero"], "label": "DeferTop"}, - {"id": "1", "path": ["hero"], "label": "DeferNested"}, - ], + "pending": [{"id": "0", "path": ["hero"], "label": "DeferTop"}], + "hasNext": True, + }, + { + "pending": [{"id": "1", "path": ["hero"], "label": "DeferNested"}], + "incremental": [{"data": {"id": "1"}, "id": "0"}], + "completed": [{"id": "0"}], "hasNext": True, }, { "incremental": [ - {"data": {"id": "1"}, "id": "0"}, { "data": { "friends": [ @@ -764,7 +766,7 @@ async def can_defer_a_fragment_within_an_already_deferred_fragment(): "id": "1", }, ], - "completed": [{"id": "0"}, {"id": "1"}], + "completed": [{"id": "1"}], "hasNext": False, }, ] @@ -856,6 +858,36 @@ async def does_not_emit_empty_defer_fragments(): assert result == {"data": {"hero": {}}} + @pytest.mark.asyncio + async def emits_children_of_empty_defer_fragments(): + document = parse( + """ + query HeroNameQuery { + hero { + ... @defer { + ... @defer { + name + } + } + } + } + """ + ) + result = await complete(document) + + assert result == [ + { + "data": {"hero": {}}, + "pending": [{"id": "0", "path": ["hero"]}], + "hasNext": True, + }, + { + "incremental": [{"data": {"name": "Luke"}, "id": "0"}], + "completed": [{"id": "0"}], + "hasNext": False, + }, + ] + @pytest.mark.asyncio async def separately_emits_defer_fragments_different_labels_varying_fields(): document = parse( @@ -1362,22 +1394,17 @@ async def deduplicates_fields_from_deferred_fragments_branches_same_level(): { "data": {"hero": {"nestedObject": {"deeperObject": {}}}}, "pending": [ - {"id": "0", "path": ["hero"]}, + {"id": "0", "path": ["hero", "nestedObject", "deeperObject"]}, {"id": "1", "path": ["hero", "nestedObject", "deeperObject"]}, ], "hasNext": True, }, { - "pending": [ - {"id": "2", "path": ["hero", "nestedObject", "deeperObject"]} + "incremental": [ + {"data": {"foo": "foo"}, "id": "0"}, + {"data": {"bar": "bar"}, "id": "1"}, ], - "incremental": [{"data": {"foo": "foo"}, "id": "1"}], "completed": [{"id": "0"}, {"id": "1"}], - "hasNext": True, - }, - { - "incremental": [{"data": {"bar": "bar"}, "id": "2"}], - "completed": [{"id": "2"}], "hasNext": False, }, ] diff --git a/tests/language/test_location.py b/tests/language/test_location.py index c9ae2c14..1210795a 100644 --- a/tests/language/test_location.py +++ b/tests/language/test_location.py @@ -41,3 +41,10 @@ def can_compare_with_formatted_location(): different_location = SourceLocation(2, 2).formatted assert not location == different_location # noqa: SIM201 assert location != different_location + + def can_be_hashed(): + location = SourceLocation(1, 2) + same_location = SourceLocation(1, 2) + assert hash(location) == hash(same_location) + different_location = SourceLocation(2, 2) + assert hash(location) != hash(different_location) diff --git a/tests/language/test_source.py b/tests/language/test_source.py index b973410d..94553109 100644 --- a/tests/language/test_source.py +++ b/tests/language/test_source.py @@ -69,6 +69,13 @@ def can_be_compared(): assert not source == "bar" # noqa: SIM201 assert source != "bar" + def can_be_hashed(): + source = Source("foo") + same_source = Source("foo") + assert hash(source) == hash(same_source) + different_source = Source("bar") + assert hash(source) != hash(different_source) + def can_create_weak_reference(): source = Source("foo") ref = weakref.ref(source) diff --git a/tests/pyutils/test_description.py b/tests/pyutils/test_description.py index 781ab14e..8b82734f 100644 --- a/tests/pyutils/test_description.py +++ b/tests/pyutils/test_description.py @@ -170,7 +170,7 @@ def graphql_directive(): assert directive.description is lazy_string assert str(directive.description).endswith("lazy?") - def handels_introspection(): + def handles_introspection(): class Lazy: def __init__(self, text: str): self.text = text diff --git a/tests/pyutils/test_ref_map.py b/tests/pyutils/test_ref_map.py index 96e15c58..95c0056f 100644 --- a/tests/pyutils/test_ref_map.py +++ b/tests/pyutils/test_ref_map.py @@ -28,7 +28,7 @@ def can_create_a_map_with_scalar_keys_and_values(): assert list(m.items()) == [("a", 1), ("b", 2), ("c", 3)] for k, v in m.items(): assert k in m - assert m[k] == v + assert m[k] == v # noqa: PLR1733 assert m.get(k) == v assert v not in m with pytest.raises(KeyError): @@ -65,7 +65,7 @@ def can_create_a_map_with_three_objects_as_keys(): assert list(m.items()) == [(obj1, 1), (obj2, 2), (obj3, 3)] for k, v in m.items(): assert k in m - assert m[k] == v + assert m[k] == v # noqa: PLR1733 assert m.get(k) == v assert v not in m with pytest.raises(KeyError): diff --git a/tests/utilities/test_coerce_input_value.py b/tests/utilities/test_coerce_input_value.py index 90af6cb9..5882e5c5 100644 --- a/tests/utilities/test_coerce_input_value.py +++ b/tests/utilities/test_coerce_input_value.py @@ -1,6 +1,6 @@ from __future__ import annotations -from math import nan +from math import isnan, nan from typing import Any, NamedTuple import pytest @@ -91,7 +91,7 @@ def returns_no_error_for_null_result(): def returns_no_error_for_nan_result(): result = _coerce_value({"value": nan}, TestScalar) - assert expect_value(result) is nan + assert isnan(expect_value(result)) def returns_an_error_for_undefined_result(): result = _coerce_value({"value": Undefined}, TestScalar) @@ -371,7 +371,7 @@ def returns_nan_as_value(): result = _coerce_value({}, _get_test_input_object(nan)) result_value = expect_value(result) assert "foo" in result_value - assert result_value["foo"] is nan + assert isnan(result_value["foo"]) def describe_for_graphql_list(): TestList = GraphQLList(GraphQLInt) diff --git a/tests/utilities/test_value_from_ast_untyped.py b/tests/utilities/test_value_from_ast_untyped.py index 0461cc20..354cfe74 100644 --- a/tests/utilities/test_value_from_ast_untyped.py +++ b/tests/utilities/test_value_from_ast_untyped.py @@ -1,6 +1,6 @@ from __future__ import annotations -from math import nan +from math import isnan, nan from typing import Any from graphql.language import FloatValueNode, IntValueNode, parse_value @@ -14,8 +14,8 @@ def _compare_value(value: Any, expected: Any): assert value is None elif expected is Undefined: assert value is Undefined - elif expected is nan: - assert value is nan + elif isinstance(expected, float) and isnan(expected): + assert isnan(value) else: assert value == expected @@ -65,7 +65,7 @@ def parses_variables(): _expect_value_from_vars("$testVariable", None, Undefined) def parse_invalid_int_as_nan(): - assert value_from_ast_untyped(IntValueNode(value="invalid")) is nan + assert isnan(value_from_ast_untyped(IntValueNode(value="invalid"))) def parse_invalid_float_as_nan(): - assert value_from_ast_untyped(FloatValueNode(value="invalid")) is nan + assert isnan(value_from_ast_untyped(FloatValueNode(value="invalid"))) diff --git a/tox.ini b/tox.ini index 4f8dc1f7..685389e6 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,7 @@ python = [testenv:ruff] basepython = python3.12 -deps = ruff>=0.11,<0.12 +deps = ruff>=0.12,<0.13 commands = ruff check src tests ruff format --check src tests @@ -26,7 +26,7 @@ commands = [testenv:mypy] basepython = python3.12 deps = - mypy>=1.15,<2 + mypy>=1.16,<2 pytest>=8.3,<9 commands = mypy src tests